loadreport — monitor resource profiler
loadreport answers one question: on an idle box with no site traffic, which recurring
script is burning the load average?
BOA's monitor machinery (second.sh, minute.sh, the /var/xdrago/monitor/check/ guards)
fans out roughly twenty short-lived helper scripts every ~5 seconds, and each one re-sources
/root/.barracuda.cnf and runs pgrep scans. That churn alone can pin a 2 CPU / 4 GB box at
a load of 3–4 with zero requests served.
loadreport is the read-only /proc profiler that attributes the CPU and RSS of that churn
back to the script responsible, so you can decide what to throttle.
It is dependency-free (pure bash + /proc, sort/awk only at print time), writes no
configuration, and changes nothing. Linux only. Source: aegir/tools/bin/loadreport.
Where it lives
loadreport is deployed exactly like fpmreport: the real file is /opt/local/bin/loadreport
and a symlink at /usr/local/bin/loadreport puts it on PATH (fetched and symlinked by
BOA.sh.txt, chmod 700). Invoke it as loadreport from a root shell.
It is not one of the /var/xdrago/ monitors — it does not run itself in a loop and does
not enforce anything. It is a diagnostic you run by hand, plus one low-priority cron entry
(below) that logs a periodic sample for history.
What problem it solves
The targets are deliberately hostile to ordinary profilers:
- the monitor children live for a fraction of a second, so a
top/pssnapshot almost never catches them; sysstat/pidstat/process accounting are not installed on a stock BOA box.
loadreport sidesteps all of that by sampling /proc repeatedly across a window and
reconstructing per-script totals from short-lived processes.
cron fires second.sh / minute.sh once a minute
│
│ self-loop with `sleep 5` → ~5 s granularity
▼
fan-out: ~20 short-lived monitor scripts every ~5 s
│ each re-sources /root/.barracuda.cnf, runs pgrep
▼
load average climbs on an otherwise idle box
│
▼
loadreport ── samples /proc over a window ──► who did it?
How it works
The script's header and sampling code spell out the model. The important parts:
- Identity by
(pid, starttime). Because the targets are short-lived and PIDs get recycled, every process is keyed by its PID and its start tick. Two different scripts that happen to reuse a PID are never conflated. - Birth-aware CPU charging. The window start is recorded in boot-relative clock ticks from
/proc/uptime. A process whose start tick is at or after the window start was born during the window and is charged its full cumulative CPU (utime+stime) from birth; a pre-existing process is charged only its in-window delta. This is what makes per-run totals for a script that spawns fresh every tick meaningful. - bash/sh resolve to the script. A process whose
commisbash/sh/dashis relabelled to the basename of the first*.sh/*.pl/*.phpargument in itscmdline. So a hundredbashwrappers collapse intominute.sh,scan_nginx.sh, etc., instead of a uselessbashbucket. - Helper forks roll up to the launcher.
pgrep,awk,bc,sleepand friends are attributed to the BOA launcher that spawned them by walking the/procparent chain (depth-bounded to 12). The walk stops at a known launcher or atcron/crond/CRON(anything past that is(non-cron)). The recognised launchers aresecond.sh,minute.sh,runner.sh,guest-fire.sh,guest-water.sh,owl.sh,clear.sh,ip_access.sh,user_admin_access.sh,ai_policy.sh,nginx_deny.sh,nginx_deny6.sh,migration_proxy_realip.sh,cloudflare_realip.sh,manage_ltd_users.sh,manage_solr_config.sh,purge_binlogs.sh,mysql_cleanup.sh,graceful.sh. - Systemic fork/ctxt bounds. The window's
processesandctxtdeltas from/proc/statbound the total fork and context-switch churn, including processes too short-lived to ever appear in a sample. - Self-excluded.
loadreportskips its own PID and its ownsleepchild so the profiler never charges itself.
Two views
The human report prints two tables:
- BY COMMAND — one row per resolved script basename: summed CPU seconds, percent of one core over the span, spawn count, peak RSS for a single process, and concurrent RSS (the worst-case sum of that script's live instances in any one sampling pass).
- BY LAUNCHER (subtree) — the same CPU rolled up to the owning cron launcher, so a launcher's whole cost (itself plus every helper fork it triggers) lands in one number. This is the view that tells you which cron entry is the real load source.
Usage
| Invocation | Mode | Effect |
|---|---|---|
loadreport |
live | Default human report. Profiles for _LOADPROF_WINDOW (60 s) sampling every _LOADPROF_INTERVAL (1 s), prints the two tables, top _LOADPROF_TOP (25) rows. |
loadreport --window N |
live | Override the profiling window in seconds (validated >= 1, else 60). |
loadreport --interval S |
live | Override the sample interval in seconds; accepts a decimal (e.g. 0.5). Must be > 0, else 1. |
loadreport --top N |
live | Show the top N rows by command instead of 25. |
loadreport --all |
live | Show every row. |
loadreport --json |
live | Emit one machine-readable JSON object instead of the tables. |
loadreport --log |
log | Profile once, append a JSONL record to the data dir, prune old logs. The */30 cron entry. |
loadreport --data DIR\|FILE… [--days N] |
data | Summarise logged JSONL history; --days N restricts to the last N days. |
loadreport -h / --help |
— | Usage. |
Numeric inputs are validated defensively and fall back to the default on garbage rather than dividing by zero: a zero interval would collapse the run to a single useless sample, so it is rejected.
Environment tunables
Every default is overridable from the process environment or an equivalent CLI flag.
loadreport does not read /root/.barracuda.cnf (unlike the watchdogs), so these are
tuned by exporting the variable before the run, not from a control file:
| Variable | Default | Meaning |
|---|---|---|
_LOADPROF_WINDOW |
60 |
profiling window, seconds |
_LOADPROF_INTERVAL |
1 |
sample interval, seconds (decimal ok) |
_LOADPROF_TOP |
25 |
rows in the BY COMMAND table |
_LOADPROF_DATA |
/var/log/boa/load-profile |
history directory for --log/--data |
_LOADPROF_KEEP_DAYS |
14 |
retention for --log pruning, days |
_LOADPROF_PROC |
/proc |
procfs root override — testing/internal only, lets the sampler run against a synthetic fixture off-box; not an operator tunable |
The periodic logger
The root crontab runs the logger every 30 minutes at idle priority:
*/30 * * * * /usr/bin/nice -n10 /usr/bin/ionice -c3 \
bash /opt/local/bin/loadreport --log >/dev/null 2>&1
The nice -n10 / ionice -c3 (idle I/O class) keeps the logger off the back of real work —
it profiles for a minute, so it should never compete. Proxy nodes are profiled like any
other box (since the 2026-08-09 marker narrowing — relay load is exactly what a PX0 node
carries). The --log path:
- Runs a normal live profile, then appends the JSON object as one line to
${_LOADPROF_DATA}/YYYY-MM-DD.jsonl(one file per day). - Prunes
*.jsonlolder than_LOADPROF_KEEP_DAYS(default 14) viafind … -mtime +N -delete, so the history stays bounded.
Read that history back with --data, which walks the retained records, averages CPU seconds
per command across them, and tracks the peak concurrent RSS:
loadreport --data /var/log/boa/load-profile --days 7
Reading the output
The example below is illustrative, constructed from the printf formats in the source —
it is not captured from a real box. It shows the shape of a quiet 2 CPU box where the monitor
fan-out, not site traffic, is the load:
loadreport — BOA recurring-script resource profile
host example span 60s interval 1s cores 2 HZ 100
monitor throttle: SLOW (RAM 3915 MB; cnf: slow.cron)
load1 3.41 -> 3.18
forks during span: 2280 (2280/min) ctxt switches: 41200 (687/s)
distinct processes sampled: 214
BY COMMAND CPU_s %1core spawns peakRSS concRSS
minute.sh 9.74 16.2 11 6.1 34.8
second.sh 7.05 11.8 12 5.9 41.2
scan_nginx.sh 3.61 6.0 3 9.4 9.4
pgrep 2.88 4.8 180 1.2 12.6
ip_access.sh 1.10 1.8 1 4.7 4.7
BY LAUNCHER (subtree) CPU_s %1core procs
minute.sh 14.92 24.8 96
second.sh 11.40 19.0 104
(non-cron) 1.95 3.2 14
How to read it:
- The
monitor throttle:line is the box's live throttle class (CI/SLOW/NORMAL) as classified by the deployed monitors — the canonical way to confirm what class a box is actually running in. It is drift-proof by design:loadreportextracts (sed) and evals the_monitor_box_classfunction from the deployed/var/xdrago/minute.shrather than re-implementing the precedence, so it always reflects what the box does, not what the docs say. On a box still running a pre-throttleminute.shit printsn/a — pre-throttle minute.sh. The parenthesisedcnf:list names which class signals are present (jenkins,slow.cron,fast.cron,force.queue.runner, ornone), and addsjenkins(var)when_FORCE_CI_BOX=YESis set in/root/.barracuda.cnf— so a box forced CI by the variable rather than by the marker file is visible in the same diagnostic. It is the quickest way to see which signals the classifier is weighing when a box carries contradictory ones. An unprivileged run that cannot read the cnf printscnf-unreadableinstead of silently under-reporting the variable.--jsonemits the class as"monitor_class". See Cron cadence & idle-load throttle for what each class changes. - The summary line —
forks during spanandctxt switches— is the systemic churn.2280/minforks on an idle box is the fan-out, not your sites. This number includes processes too short-lived to be named individually (see the caveat below). - BY COMMAND names the worst scripts.
pgrepwith 180 spawns but little CPU each is the per-child cost of the monitor scans, multiplied across the fan-out. - BY LAUNCHER rolls every one of those
pgrep/awkforks back underminute.sh/second.sh. When those two subtrees dominate the launcher table — as here — the fan-out is your idle load. concRSSis the worst-case concurrent memory:minute.shpeaked at ~35 MB of simultaneously-live instances even though any single one was ~6 MB.scan_nginx.shshows up as its own BY COMMAND row, but because it is not in the launcher list its cost rolls up underminute.sh(its launcher, vianginx_guard.sh) in the BY LAUNCHER table.
When the fan-out is the problem — throttle it
The diagnosis that points back at this topic: a high forks/min combined with second.sh
/ minute.sh dominating the BY LAUNCHER table, on a box with little or no real traffic. That
is the monitor fan-out spending your CPU on pgrep scans nobody needs at full cadence on a
small or idle host.
The same change that shipped loadreport also added a box classifier (_monitor_box_class,
CI / SLOW / NORMAL) to second.sh and minute.sh to throttle that fan-out on small, idle, or
CI hosts while leaving NORMAL boxes unchanged. The relevant /root/.barracuda.cnf overrides
are _MONITOR_FANOUT_ITER and _MONITOR_FANOUT_SLEEP (minute.sh) and _MONITOR_HEAVY_EVERY
(second.sh).
See Cron cadence & idle-load throttle for the classifier and how
to tune it; loadreport is the before/after measurement tool for that change — and its
monitor throttle: header line (above) reads the class straight from the deployed minute.sh,
so a single run confirms whether a throttle you expect is actually in effect.
The --log history is exactly the artefact you want here: take a baseline over a few days,
apply a throttle, and compare the averaged per-command CPU with --data rather than eyeballing
one run.
Caveat — sub-tick processes are counted, not named
A process born and reaped entirely between two ticks never appears in a sample, so it is
not named in either table. It is still counted in the systemic forks total from
/proc/stat, which is why that number can exceed the sum of the named spawns.
If the fork total is high but the named tables look light, you are missing short-lived churn:
rerun with a finer --interval (e.g. --interval 0.5 or 0.25) to catch more of it.
There is a floor — processes shorter than the sample gap are inherently invisible by name — but
the /proc/stat counters always bound the true total.
Related
- Cron cadence & idle-load throttle — the
_monitor_box_classclassifier and the_MONITOR_FANOUT_*/_MONITOR_HEAVY_EVERYknobs that reduce the fan-outloadreportmeasures. - Process guards & auth scanners — the heavy fan-out
(
_proc_control+ scanners) thatloadreportattributes tosecond.sh. - Service auto-healing watchdogs — the
minute.shfan-out that dominates the BY LAUNCHER table on an idle box. - Abuse Guard — the security-facing member of the same
/var/xdrago/monitor/check/machinery (scan_nginx.sh). It shows up inloadreportas its own BY COMMAND row; because it is not a recognised launcher, its cost rolls up underminute.sh(vianginx_guard.sh) in the BY LAUNCHER table. This page measures it; that topic explains what it does. - Reference appendix — where the
_LOADPROF_*and_MONITOR_*overrides are documented.