The run report#
Every job produces a run report, computed from a record Moruna keeps of every morsel. moruna.run returns it, a failed job attaches it to its exception as error.report, and the moruna command writes it to a file. Printing a report gives a summary; report.to_json() gives every field.
Job fields#
These are attributes of the report object and keys of to_json().
Field |
Meaning |
|---|---|
|
The job’s id. |
|
|
|
Whether the job continued from a checkpoint. |
|
The path of the latest checkpoint, if one was kept. |
|
Seconds from start to finish. |
|
The memory limit, CPU quota and devices the job started with, and where they came from. |
|
The most memory the whole process held during the job. |
|
|
|
The share of worker time spent running kernels. |
|
The share of time the operating system held the process back for exceeding its CPU quota. |
|
How fast the source delivered data. |
|
Whether the job spilled to disk. |
|
How much it spilled. |
|
For each stage, whether Python kernels ran free-threaded or under Python’s global lock. |
|
How morsels were sized. |
|
What the pipeline was waiting for, and for how long: reading, a kernel or writing. |
|
One entry per kernel; see below. |
|
Decisions Moruna made and facts it found, in sentences. |
|
The trace file, if |
|
For a |
|
For a |
to_json() and the report file also include the following. limits_timeline lists each change to the machine’s limits during the job. cpu_ns is the CPU time the job used. mem_byte_seconds is its memory use over time, the sum of memory held multiplied by time. peak_ceiling_bytes is the limit that peak_fraction_of_ceiling was measured against.
Stage fields#
stages has one entry for each kernel, in order.
Field |
Meaning |
|---|---|
|
Morsels the kernel processed. |
|
Rows received and returned. |
|
Bytes received and returned. |
|
Time the stage was active, and time spent inside the kernel across all workers. |
|
Throughput. |
|
The kernel’s memory use as a multiple of its input: typical and near the highest. |
|
Morsels that failed, and morsels skipped under the error policy. |
|
For a stateful kernel, the largest state and how much it grew. |
|
For a Python kernel, the memory it asked for itself, outside the data Moruna hands it; |
alloc has refusal_on (whether a request past the memory ceiling would be refused, the decorator’s memory_guard), then one entry each for python (Python objects), numpy (NumPy arrays) and arrow (pyarrow’s memory pool), each with requested_bytes, requests, largest_request_bytes, peak_bytes (the most one call held at once) and refused, and finally peak_bytes (the most one call held at once, all sources together), outside_arrow_peak_bytes and outside_arrow_fraction. A field that cannot be measured for a source is null: the peak of Python objects, and the largest request and refusals for Arrow, whose figures come from the pool’s own statistics around each call (with several workers they include what the others asked for at the same time). A kernel that works in Arrow and asks for little else has a small outside_arrow_fraction.
Reading a report#
A few patterns come up often:
peak_fraction_of_ceilingis low andworker_busy_fractionis high. The job was limited by CPU, not memory. More cores would make it faster; more memory would not.worker_busy_fractionis low. The kernels were waiting, usually for the source or the sink.bottleneck_timelinesays which.staging_engagedis true. Data waited long enough to be written to disk, usually because the sink was slower than the kernels. The job was correct but slower than it could have been.A stage’s
alloc.outside_arrow_fractionis high. The kernel builds most of its working memory as Python objects or NumPy arrays rather than Arrow. It costs memory Moruna can only observe, and is where a rewrite in Arrow, Polars or a standard kernel pays.amplification_p95is much higher thanamplification_p50. Some morsels cost far more memory than most, which makes Moruna size conservatively. Look for rows much larger than the rest.
The notes explain each decision Moruna made in a sentence, and are the first place to look when a job behaves unexpectedly.