Package-level declarations

Types

Link copied to clipboard

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.

Link copied to clipboard
class Client(val http: HttpClient)

The HTTP client of a run, speaking TransferApi.

Link copied to clipboard
class ClosedLoop(client: Client, mix: Mix, keysOf: (Int) -> KeyChooser, targets: List<URI>, startConcurrency: Int, maxConcurrency: Int, warmUpSeconds: Int, stepSeconds: Int, minGain: Double = DEFAULT_MIN_GAIN, maxErrorRate: Double = DEFAULT_MAX_ERROR_RATE, sweep: Boolean = false, growth: Int = 2, concurrencies: List<Int>? = null, failAfterSeconds: Int? = null, cpuSeconds: () -> Double = ::ownCpuSeconds, log: (String) -> Unit = {})

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.

Link copied to clipboard
class Histogram

A log-linear histogram of non-negative values (microseconds here), the recorder of the load generator: the same bucketing HdrHistogram uses, without the dependency.

Link copied to clipboard

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.

Link copied to clipboard
object Json

The JSON the load generator needs without a library: it reads a field's first occurrence out of an answer, and writes its results from maps, lists, strings, numbers and booleans.

Link copied to clipboard
class KeyChooser(val distribution: String, val accounts: Int, seed: Long, zipfExponent: Double = DEFAULT_ZIPF_EXPONENT, hotFraction: Double = DEFAULT_HOT_FRACTION, hotKeys: Int = 1)

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.

Link copied to clipboard
class Mix(val parts: List<Pair<Scenario, Int>>)

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.

Link copied to clipboard
class OpenLoop(client: Client, mix: Mix, keys: KeyChooser, targets: List<URI>, rate: Double, warmUpSeconds: Int, measureSeconds: Int, inFlight: InFlightCap = InFlightCap.of(rate, TransferApi.REQUEST_TIMEOUT, targets.size), onPhase: (String) -> Unit = {})

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.

Link copied to clipboard
class Options

The --name=value options.

Link copied to clipboard

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.

Link copied to clipboard
object Report

The results of a run: the JSON the report is built from, and a short Markdown summary of it.

Link copied to clipboard
class Resources(processes: Map<String, Long>, containers: List<String>, mongoContainer: String?, postgresContainer: String?, postgresDatabase: String, kafkaContainer: String?)

The cost of a run, captured beside it over its measured phase:

Link copied to clipboard
object Samples

The sampled lines of Resources: each sample is the time it arrived (epoch milliseconds) and its values, and the measured phase a range of those times.

Link copied to clipboard
enum class Scenario : Enum<Scenario>

An end-to-end scenario: what one operation sends, and what makes it complete. An operation completes with null when it reached its completion point, or with why not (an error kind, counted in the results).

Link copied to clipboard
data class Target(val base: URI, val account: Int, val other: Int)

What one operation targets: an account, and for a transfer the account it goes to.

Link copied to clipboard
class Targets(bases: List<URI>, cooldownNanos: Long = DEFAULT_COOLDOWN_NANOS)

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.

Link copied to clipboard

The benchmark server's routes and wait headers (wow-http): /transfer/account/{id}/<command>, Wow-Wait plans.

Link copied to clipboard
enum class Wait : Enum<Wait>

The point a command's request waits for, the completion of a scenario (test/benchmarks/README.md).

Properties

Link copied to clipboard

How long a server may wait for a command's completion point before it answers: 10 s.

Functions

Link copied to clipboard
fun accountId(index: Int): String

The id of the seeded account index.

Link copied to clipboard
fun execute(vararg command: String, timeoutSeconds: Long = 30): String?

Runs command and gives back its standard output, or null when it fails or takes longer than timeoutSeconds.

Link copied to clipboard
fun main(args: Array<String>)

The load generator of the end-to-end benchmark (test/benchmarks/README.md):

Link copied to clipboard
fun ownerOf(index: Int): String

The owner of the seeded account index: a hundred owners share the accounts.

Link copied to clipboard
fun percent(part: Long, whole: Long): Double

A percentage of part in whole.