Boring Observability GitHub

See how much memory and CPU your Horizon workers use

A Worker Stats page for the Horizon dashboard: the memory and CPU your whole worker fleet uses, summed across every machine, over the last 24 hours.

$ composer require boring-o11y/horizon-worker-stats

boring-o11y/horizon-worker-stats · MIT · PHP 8.1+, Laravel 10 to 13, Horizon 5.24+

Horizon tells you how many jobs your workers get through and how long they wait. It does not tell you what running them costs. When a deploy doubles your workers' memory, or a new job pins a core for minutes at a time, the first sign is usually the OOM killer or a bigger bill.

This package adds two charts to the dashboard: the resident memory of every horizon:work process on every machine, summed, and the CPU cores they keep busy. They answer the sizing questions directly. How much memory does the fleet need at peak, did last Tuesday's release change it, and are the workers busy or just waiting on I/O.

A Worker Stats page in Horizon's sidebar

The Worker Stats page inside the Laravel Horizon dashboard, with a line chart of total worker memory in megabytes and another of CPU cores in use.

After composer require and a horizon:terminate, a Worker Stats link appears at the bottom of the sidebar and the charts fill in from then on. They cover 24 hours in 15-minute points by default, and both figures are configurable.

Measured from outside the worker

Each supervisor already loops about once a second. At most every 10 seconds, the package takes the pids of that supervisor's workers from its process pools and reads them: on Linux from /proc, two file reads per worker and no fork, and elsewhere with one ps call. Workers still finishing their last job after a scale-down are included, since they hold memory until they exit.

Reading the process from outside means the figure includes everything it holds: the framework, extensions and allocator slack. That is the number that matters when you size a machine. Nothing runs on the job path, because the sampler lives in the supervisor, not the workers.

Every supervisor writes into small Redis hashes on Horizon's connection, and the page weights each one by the part of the bucket it was running for. So a supervisor that restarts, or a machine removed halfway through a bucket, does not show up as a dip to zero. A stalled loop or a bucket nobody sampled is drawn as a gap, not a made-up line.

What it does not do

  • It shows the fleet, not individual jobs. It will tell you memory jumped at 13:40, not which job class caused it.
  • Memory is RSS, which counts shared pages once per process that maps them, so the sum slightly overstates what the machines actually hold.

The page gets into Horizon's compiled dashboard the same way Horizon Delayed Jobs does, by splicing into Horizon's layout at three anchors, and the two packages chain in either order. We wrote up how the layout override works separately.

Every configuration key, and how to run the test suite, is in the README on GitHub. Bugs and questions go in its issues.

Our other packages

  • Horizon Delayed Jobs

    A Retries page for the Horizon dashboard. It lists the jobs waiting out a backoff or a delay, which Horizon does not show, with a button to run one now.

  • Horizon Prometheus Exporter

    A Prometheus scrape endpoint for Laravel Horizon, with a Grafana dashboard. Counters for throughput, failures and retries per queue and job class that never reset, plus wait time, queue depth and workers.

  • httptheus

    Prometheus metrics for your application's outbound HTTP, with a Grafana dashboard to read them. Duration, host, endpoint and outcome of every transfer, and no bodies, headers or database.

  • wirestan

    PHPStan rules for Livewire. A public property the browser can set but the server trusts, like $tenantId seeded in mount(), fails CI until it is marked #[Locked].

  • Unique Job Middleware

    Checks a ShouldBeUnique job's lock again when a worker picks it up, and skips the job if another one holds it. Fires an event for each skip, so you can count them.