Design notes

Module map

module responsibility
scenario.py YAML to frozen dataclasses (Plan, ScenarioDef, Step, LoadSpec), validation, CSV feeders, CLI overrides
template.py, jsonpath.py {{ }} rendering, JSONPath subset
schedule.py arrival-time generator for piecewise-linear rates
engine.py virtual users, open/closed loops, step execution, checks, extraction. Clock and transport are injected
hist.py, stats.py log-bucketed histogram; per-step and per-second recorder, all mergeable
runner.py one thread or N spawned processes; one-second delta messages aggregated by the parent
slo.py SLO parsing and evaluation
results.py, report_html.py, compare.py JSON, HTML (inline SVG), comparison
dashboard.py, cli.py rich live view; click commands
har.py HAR to scenario

Histogram

HDR-style: integers are stored exactly below 2^b where b = ceil(log2(2 * 10^digits)). Above that, every power-of-two range [2^k, 2^(k+1)) is divided into 2^(b-1) equal sub-buckets, so a bucket is at most 1/2^(b-1) <= 10^-digits of its lower bound wide. Example with 3 digits: b = 11, 1024 sub-buckets per octave; the value 1,234,567 us lands in a bucket whose width is 1024 us (0.08%). Counts live in a sparse dict keyed by bucket index, so an empty or narrow histogram is tiny and merging two histograms is a dictionary sum. Latencies are recorded in microseconds. percentile(p) returns the upper bound of the bucket holding rank ceil(p/100 * n), clamped to the observed min/max, which matches numpy's inverted_cdf method within the precision (this is what the tests compare against).

Workers ship one-second delta recorders (JSON-friendly dicts) to the parent, which merges them. Merged results equal what a single histogram would have recorded, so multi-process runs lose no accuracy.

Arrival process

For a rate function r(t) the expected number of arrivals up to t is L(t) = integral of r. A Poisson process with that rate is obtained by drawing unit-rate exponential gaps e_i in the L domain and mapping back: arrival i happens at t_i = L^-1(sum e_i). For a linear segment r(t) = r0 + s t, inverting r0 t + s t^2 / 2 = x gives t = 2x / (r0 + sqrt(r0^2 + 2 s x)), which is stable for s = 0 too. Constant arrivals use gaps of exactly 1.

Worked example: stages [10s: 0 -> 100/s]. L(10) = 500, and half the arrivals (250) occur by t = 10 / sqrt(2) = 7.07s because the rate is low at the start. With several workers each runs the same schedule at rate / n; a superposition of independent Poisson processes is Poisson at the summed rate.

Execution model

Testing with a fake clock

Engine takes a clock (now, sleep) and a transport callable. tests/fakeclock.py is a virtual-time driver: it lets all tasks run until they block, then jumps time to the earliest sleeper. The coordinated-omission tests run the real engine, feeders, recorder and histogram against a fake server with a stall window; a 10 s simulated test finishes in milliseconds with fully deterministic results.

Limitations