Package-level declarations
Types
The heap allocations of another JVM on this machine, read with the JDK alone: the Attach API starts the JVM's local management agent (or finds it started), and its ThreadMXBean answers the bytes all its threads, the finished ones included, allocated since it started (JDK 21). connect attaches once, before the measured phase (B.O-2: starting the agent costs the target a moment); each Reader.read after is one call over the open connection.
The HTTP client of a run, speaking TransferApi.
The closed-loop saturation finder: concurrency workers each send an operation and send the next when it completes, for a step of stepSeconds after warmUpSeconds; the concurrency grows growth times a step from startConcurrency (doubles by default; 4 sweeps 1 to 1,024 in six steps instead of eleven) until the throughput stops growing by minGain two steps in a row (unless it sweeps), errors pass maxErrorRate, or maxConcurrency is reached; or, with concurrencies, exactly those steps in that order, up to the first whose errors pass maxErrorRate. The highest throughput seen is the saturation point the open-loop runs take their 50% and 80% rates from. A closed loop's latencies understate a saturated server's (coordinated omission): they are recorded only to show where the knee is, never as a result.
How many operations of an open-loop run may be outstanding: as many as its rate sends within one request's timeout (one sent beyond that could only time out), at most InFlightCap.PER_TARGET per target. Each one outstanding holds a connection, so the ceiling keeps an overloaded server's answer generator-overload or 429, never a storm of connections it refuses. boundBy names the bound that applied, which the run's result records.
Which of the accounts seeded accounts a request targets: uniform, zipf (rank k drawn with a probability proportional to 1 / k^s, s = 1.1 by default) or hot (a share hotFraction of the requests to the first hotKeys accounts, the rest uniform over all). Draws come from a seeded generator, so a run's sequence of keys is the same on both versions.
The scenarios a run sends, by weight: one scenario (--scenario=E1), or a mix (--mix=E1:50,E4-state:40,E4-list:10) whose operation i runs the scenario at slot i of a smoothly interleaved cycle (smooth weighted round-robin), so every second sends the same proportions and no scenario comes in bursts. A mix records each scenario's latencies apart, so the writes' latency under the reads (the query bulkheads, the write priority) shows.
The open-loop driver: operations are scheduled at a fixed arrival rate, the ith at start + i / rate, whether or not the earlier ones have completed, so a slow server cannot slow the arrivals down (coordinated omission). Each operation's latency is measured from its scheduled time, not from when it was sent, so a late send counts against it too.
The CPU of the load generator's whole process tree: this JVM and every process it started, by pid. The samplers it starts for a run (jstat JVMs, the docker clients) inherit its CPUs, so they take the generator's cores too, and a share of those cores that counted only this JVM could read a saturated generator as one with room to spare.
The nodes an open-loop run sends to, round robin, as a load balancer with passive health checks would (performance item 6): a node whose connection fails is left out for cooldownNanos, then tried again, so a node stopped in a hand-over run gets no more requests until it is back.
The benchmark server's routes and wait headers (wow-http): /transfer/account/{id}/<command>, Wow-Wait plans.