Track a workload¶
watch and run track the CPU and memory usage of one workload, meaning a
process and everything it spawns, and write a log you read back with report and pdf.
Both sample until the workload ends. Ctrl-C or a SIGTERM stops them early,
and the log still ends with a complete summary.
Use run when you can: it starts the command itself, so nothing is missed. Use
watch for something that is already running. How it
works explains the difference.
watch: attach to a running process¶
treehawk watch train.py # substring of the command line
treehawk watch --exact "python train.py" # the whole command line
treehawk watch --regex 'worker-\d+' # a regular expression
treehawk watch --pid 4213 # one process id
| Option | |
|---|---|
KEYWORD |
text to look for in the command line of a running process |
-e, --exact TEXT |
the full command line, matched whole |
-r, --regex PATTERN |
a regular expression over the command line |
-p, --pid PID |
one process id |
--wait |
if nothing matches yet, keep looking until something does |
Give exactly one of the first four. Keyword and regex matches are case-insensitive, and every matching process joins the workload. treehawk never matches itself or its own ancestors, such as the shell you typed the command in.
If nothing matches, watch exits with code 2 unless you gave --wait. The
workload is left running when the watch ends.
run: start a command¶
treehawk run -- python train.py --epochs 10
The command goes after --.
- On Linux,
runstarts the command in a transient systemd scope, which is a cgroup of its own. The kernel then counts every descendant, including processes that live and die between two samples. - Without systemd or cgroup v2 (common in containers and CI),
runfalls back to tracking through/procand records why in the log header'snotes.--no-isolatechooses that mode on purpose. - On macOS there is no boundary to create, so
runstarts the command in its own session and infers membership. See Platforms. Ctrl-CandSIGTERMare forwarded to the command. If the command fails,treehawk runexits with its status.
What the command prints¶
The dashboard repaints part of the terminal, so the command's output would overwrite it. Instead, treehawk gives the command a pty of its own and reads from it:
- the last few lines appear in an
outputpanel at the bottom of the dashboard; - the whole stream, escape codes included, is kept beside the log as
.out(treehawk-20260913-100000.jsonlgetstreehawk-20260913-100000.out).
treehawk uses a pty rather than a pipe so the command still sees a terminal. It keeps its colours and its line buffering, and behaves as it would if you ran it directly.
treehawk run -- python train.py # output in the dashboard, kept in .out
tail -f treehawk-*.out # the full stream, from another shell
treehawk run --no-capture -- htop # hand the terminal over instead
Output is not captured, and no .out is written, in these cases:
- with
--no-capture, which you need for a command that prompts for input or draws its own full-screen view; - with
--quiet, since there is no dashboard to protect; - when treehawk's own output goes to a pipe or a file.
Options¶
These are shared by watch and run, except the two marked run only.
| Option | Default | |
|---|---|---|
-i, --interval SECONDS |
1.0 |
time between samples, at least 0.01 |
-o, --output PATH |
treehawk-<timestamp>.jsonl |
the log; - writes it to stdout |
--csv |
write CSV instead of JSON Lines | |
-q, --quiet |
no dashboard, just the log |
Advanced options, for awkward situations:
| Option | Default | |
|---|---|---|
-d, --duration SECONDS |
until the workload ends | stop after this long |
--expand RULE |
all four | only let these membership rules adopt processes: tree, cgroup, session, orphan; repeat for several |
--no-pss |
skip the fair-memory read (PSS on Linux, phys footprint on macOS); cheaper per sample, but summed RSS over-counts shared pages | |
--aggregate-only |
log only the workload total, not a row per process; much smaller logs for runs lasting days | |
--no-isolate |
run only: do not ask for a cgroup, track through the process table |
|
--no-capture |
run only: let the command write to this terminal instead of the dashboard |
Without a terminal (a pipe, CI), the dashboard is replaced by plain lines.
Exit codes¶
| Code | Meaning |
|---|---|
0 |
finished, or stopped with Ctrl-C / SIGTERM |
1 |
treehawk could not do what was asked: an invalid option value, an unreadable log, a command that could not start |
2 |
watch found no matching process, or the command line itself was malformed (unknown option, missing argument) |
| other | run: the command's own exit status |
In CI¶
Fail a job when a test suite uses too much memory:
treehawk run --quiet -o pytest.jsonl -- uv run pytest
treehawk report pytest.jsonl --json | jq -e '.summary.peak_pss_bytes < 2 * 1024 * 1024 * 1024'