Isolate node and clients on separate CPUs for the locust perf test - #8161
Draft
Amaury Chamayou (achamayou) wants to merge 18 commits into
Draft
Isolate node and clients on separate CPUs for the locust perf test#8161Amaury Chamayou (achamayou) wants to merge 18 commits into
Amaury Chamayou (achamayou) wants to merge 18 commits into
Conversation
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-cpu-isolation-for-perf
branch
3 times, most recently
from
August 14, 2026 16:44
6a803b7 to
82c3896
Compare
Amaury Chamayou (achamayou)
force-pushed
the
locust-blocking-perf
branch
from
August 14, 2026 16:45
9583136 to
d4162fb
Compare
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-cpu-isolation-for-perf
branch
from
August 14, 2026 16:45
82c3896 to
35256d4
Compare
Adds pi_basic_blocking_locust, which measures the same blocking-write workload as pi_basic_blocking but drives it with locust rather than piccolo, so the number of concurrent clients can be varied. The load is defined in tests/infra/basicperf_locustfile.py and uses FastHttpUser, since HttpUser cannot drive enough requests per second to saturate the service. tests/basicperf_locust.py owns the network, runs locust against it, and converts locust statistics into bencher metrics (throughput, latency, memory). The key space helper shared with basicperf.py moves to tests/infra/key_space.py, since basicperf.py can only be imported from tests/infra. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
The three files added by this branch were committed with CRLF, unlike every other Python file under tests/. No functional change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
Locust --run-time starts counting when locust starts, so it includes the ramp, and --reset-stats discards the statistics gathered during the ramp without extending the deadline. The measurement window was therefore shorter than requested, and shrank as the spawn rate was lowered, until it disappeared entirely. This mattered because varying the client count is the point of this test: at --users 128 --spawn-rate 4 the run ended mid-ramp and reported 127 tx/s instead of the ~1250 tx/s that 128 users actually sustain, and did so without failing. Start the shutdown timer from locust spawning_complete instead, so the window is the same length whatever the spawn rate is, and rename --run-time-s to --measure-time-s to describe what it now does. --run-time is kept as a backstop against a run which never finishes spawning. Also fail, rather than report, when a run ends without having spawned all users, or when the window measured is shorter than the one asked for. Both produce plausible looking figures that do not describe steady state. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
Locust workers reach the master on port 5557 by default, so a second locust run anywhere on the same machine fails to bind. Pick a free port per run instead, via the existing infra.net helper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
Locust writes N/A rather than a number in the statistics CSV when it has too few samples to compute a percentile. float() then raised a bare ValueError, after the network had already been stopped, losing the run with no indication of what had gone wrong. Read the numeric columns through a helper which reports the column and value, and says that the run did not gather enough data. Found by a run which produced almost no samples. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
add_piccolo_test passes --snapshot-tx-interval 10000 for every piccolo perf test. add_e2e_test does not pass it at all, and e2e_args defaults it to 10, which is sensible for functional tests but not for a benchmark. This test was therefore writing and fsyncing a ~213KB snapshot every 10 transactions for the whole run, which measures the disk rather than the service, and makes the figure incomparable with Basic Blocking. Found by the vegeta comparison work, which hit the same defect. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
Blocking writes return once their transaction commits, and commit cannot outpace the signature interval, so a single interval only measures one regime. Sweeping three separates them: at 1s and 100ms the workload is latency-bound and throughput is simply the client count divided by the interval, while at 5ms the node becomes the limit and the benchmark measures capacity instead. Each interval gets its own network, since the interval is fixed in the node configuration at startup. consensus_update_timeout_ms moves with it, as in commit_latency.py, because commit cannot be observed faster than the primary sends updates. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
A benchmark added by a branch has no main runs to build an EWMA baseline from, so render_chart skipped it entirely. That made a new benchmark invisible on the very pull request which adds it, which is when it is most worth seeing. Plot such a benchmark against the branch's own earliest run instead, so its movement across the branch's runs is visible, and mark it as new. It carries no standard deviation band and is never coloured as an improvement or a regression, because there is nothing on main to compare it against. Borrowing a related benchmark's baseline was considered and rejected: an axis normalised against something which measures a different thing shows a difference which is not a change in CCF, and because the chart scale follows the largest axis, one such axis compresses every other benchmark into illegibility. Also truncate long axis labels in the middle rather than at the end. Benchmarks measured at several settings differ only in their suffix, so truncating the end left the 100ms and 1000ms axes indistinguishable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
The blocking workload is latency-bound at the longer signature intervals, so throughput there is set by the client count: 320 clients raises the 100ms point from ~1260 to ~3100 tx/s and the 1s point from ~130 to ~314, both within a few percent of clients divided by interval. Ten sending processes rather than four keeps locust from becoming the limit while driving that many clients, since each process drives all of its users from a single thread. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
The shortest interval is the point at which the node, rather than the signature timer, becomes the limit, so it is the one which measures capacity. Shortening it to 2ms pushes further past the latency-bound regime. The node ticks every 1ms in these tests, so a 2ms interval is representable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
The previous version reported a benchmark absent from main by its value alone, marked (new), with no percentage. That read differently from every other axis, and the marker made the label long enough to be truncated, which is exactly what it should not have been for benchmarks whose names differ only in a suffix. Treat such a benchmark like any other axis instead, using this branch's earliest run as its reference in place of the main EWMA baseline, so it is normalized, labelled and coloured identically. The chart description records that the reference differs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
Truncating the middle of the whole label cut out several words at once. Elide word by word from the left instead, keeping each word's first and last letter and replacing the middle with a single ellipsis character, so a label degrades gradually and the last word, which is what distinguishes one setting of a benchmark from another, stays readable longest. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
A benchmark with no main history had its standard deviation hardcoded to zero, so all four band curves collapsed to a single point at the baseline while every other axis carried a spread. That left a visible pinch in the band and, because nothing fell inside a zero-width noise threshold, any movement between branch runs was coloured as an improvement or a regression. Measure its spread the same way as for a benchmark with main history, across the runs available, which for such a benchmark are the branch's own. The radial zoom already covered these axes, since their values were always part of the data the scale is fitted to. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
Pin the CCF node and the locust clients to disjoint sets of CPUs, so that the Basic Blocking Locust benchmark measures the node rather than how the node and the load generator happened to divide the machine between them. The mechanism is a taskset prefix, which applies the mask before exec so it is inherited by every thread and grandchild. That needs no privileges and no cgroup access, so it works inside the unprivileged CI container. Isolation is off by default and enabled only for pi_basic_blocking_locust, so other tests are unaffected. Where it cannot be applied the test runs unisolated with a warning, and the mode is recorded in bencher.json. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Amaury Chamayou (achamayou)
force-pushed
the
locust-blocking-perf
branch
from
August 18, 2026 16:07
dce652f to
829f0b8
Compare
Amaury Chamayou (achamayou)
force-pushed
the
achamayou-cpu-isolation-for-perf
branch
from
August 18, 2026 16:07
35256d4 to
500605e
Compare
Amaury Chamayou (achamayou)
force-pushed
the
locust-blocking-perf
branch
from
August 18, 2026 18:47
829f0b8 to
1a5905c
Compare
Eddy Ashton (eddyashton)
force-pushed
the
locust-blocking-perf
branch
from
August 19, 2026 08:28
2793ac9 to
5fc8b59
Compare
Amaury Chamayou (achamayou)
force-pushed
the
locust-blocking-perf
branch
from
August 19, 2026 10:08
5fc8b59 to
f6723fd
Compare
Keep main's 2ms, 20ms, and 100ms benchmark intervals while applying CPU isolation, and restore the standalone isolation test to bucket_c. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Register cpu_isolation_test in the checked-in bucket_c snapshot so the CI inventory matches CMake. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b591ca67-881e-413e-b387-e4f091201815
Keep the generated snapshot's final blank line so the byte-for-byte CI comparison remains stable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b591ca67-881e-413e-b387-e4f091201815
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
Corrected local measurements show no measurable benefit at the latency-bound 100ms point: isolated and unisolated runs both settle around 1260 tx/s with sub-1% CV. This change is intended to remove node/client CPU contention from the CPU-bound CI measurements; it does not claim a 100ms throughput improvement.
Summary
Pins the CCF node and Locust load generators to disjoint CPU sets for
pi_basic_blocking_locust, so the benchmark is not affected by how the node and clients happen to share cores.taskset.autoforpi_basic_blocking_locustonly.main's current 2ms, 20ms, and 100ms signature-interval sweep.tasksetis unavailable.bencher.jsonunder a test-specific metadata key, preserving metadata written by earlier benchmarks.util-linuxin the test image to providetaskset.No privileges or writable cgroups are required:
tasksetapplies affinity beforeexec, and the mask is inherited by threads and child processes.Update against
mainThe branch now includes
mainatf5a815bab. The overlaps from the squash-merged benchmark and radar work (#8158 and #8171) were resolved by retainingmain's benchmark intervals and radar implementation, then applying only the CPU-isolation changes on top.The net PR diff is limited to the isolation implementation and its integration. The previously unregistered standalone test is now wired into CTest as
cpu_isolation_testinbucket_c.Measurement status
On a corrected 12-thread local setup, the 100ms configuration showed no meaningful difference:
The earlier 5ms experiment saturated that host and was too noisy to support a conclusion. Fresh CI measurements are still needed for the current CPU-bound 2ms and 20ms points, so the PR remains a draft.
Testing
tests/infra/test_cpu_isolation.py: 12 cases pass after the merge, including real child/grandchild affinity, topology handling, graceful fallback, environment overrides, and metadata persistence.cpu_isolation_testis registered inbucket_cfor both virtual CI configurations.No
CHANGELOG.mdentry: this only changes test infrastructure.