Skip to content

Measure the circle

$ grim diagnose

"It is slow on my machine" is a sentence, not a measurement. grim diagnose turns it into numbers in under a minute: what the machine is, which Docker and which file sharing it runs, how many files the bind mount carries, how long the first byte of / and /admin takes, whether opcache is on inside the container. The numbers are printed, kept in the project under .grim/, and with --send they land on the portal next to the numbers of everybody else who runs the same project — with a baseline to read them against.

grim doctor asks whether the tools are there. grim diagnose asks how fast the project is with them.

Usage

$ grim diagnose                    # the quick set, a table, under a minute
$ grim diagnose --send             # the same, posted to the portal
$ grim diagnose --full             # plus a cold and a warm up, the image build, composer, vite, the suite
$ grim diagnose --json             # the payload instead of the table

Run it in the project directory with the stack up (grim up). Without a running stack the TTFB and opcache rows say so and the rest is still measured.

Arguments and options

Measure the machine and the running project: Docker, file sharing, bind mount size, TTFB, opcache — and send it to the portal on request

Usage

grim diagnose [--full] [--send] [--json]

Options

Option Description
--full Also time a cold and a warm up, the image build, composer install, the Vite build and the suite (minutes, restarts the stack)
--send Post the run to the portal (Diagnostics tab of the project) with your GitHub token
--json Print the payload instead of the table

What it actually does

  1. Reads the machine. OS, CPU cores, RAM, free disk under the project, the Docker server version, and on macOS the file sharing mode of Docker Desktop (virtiofs, grpc-fuse, osxfs) — read from /proc/mounts inside the app container, where the project's bind mount names its type (fakeowner is VirtioFS behind Docker Desktop's ownership shim); with no container running, Docker Desktop's settings file is the fallback, and it names only what differs from the defaults. Linux binds natively.
  2. Counts the bind mount. Files under the project without vendor, node_modules and .git, and again with them — the second number is what Docker Desktop has to keep in sync.
  3. Times the first byte. Five warm requests each to https://<project>.test/ and /admin (a custom tls.domain is honoured), the median in milliseconds. Not the mean: one slow request must not move the number. Only a 2xx or 3xx answer counts — Traefik's 404 while the stack is down is fast, and it is not the app.
  4. Asks the container. php -r inside app for the PHP version and opcache status, including the hit rate.
  5. With --full, times the slow things, each as an optional step: down + up cold, a restart warm, docker compose build, composer install, npx vite build, and the suite through the same runner grim test uses. Minutes, and the stack is restarted.
  6. Writes .grim/diagnose-<timestamp>.json — schema version 1, the project name, the versions of grim, core and the train, the metrics. The machine is a short hash of the hostname and grim's install id; no hostname, no paths, no .env values are in the file.
  7. With --send, posts that file to the portal with your GitHub token — the same door the deploy heartbeat uses. The run then shows under Registry → Projects → the project → Diagnostics, where a keeper can mark one run as the baseline; cells slower than twice the baseline are red.

Pitfalls

TTFB / says "no answer"

Nothing on https://<project>.test/ answered with a 2xx or 3xx. The stack is down (Traefik alone answers 404), or Traefik is not bound to 443 — grim up says which. The rest of the table is still valid.

The portal answers 404

Not sent — this portal does not take diagnose runs yet. The project's registry_url points at a portal older than the Diagnostics tab. The report is still kept locally; send it later or paste the JSON.

The numbers are worse than a colleague's

Compare the File sharing row first. On macOS virtiofs against grpc-fuse is often the whole difference, and it is a switch in Docker Desktop, not in the project — the row says where (Settings → General → Virtual Machine Options → VirtioFS) whenever it reads grpc-fuse or osxfs. Then the Files row: a bind mount with a fat node_modules is slow on every sharing mode — grim tune has the knobs for the container side.

--full restarts the stack and runs the suite; do not run it with uncommitted work in a state you would not want migrated or tested.

After it finishes

Nothing has changed on the machine except a new file under .grim/, which the project's .gitignore already hides. With --send, one more row on the portal.

The idea underneath