Skip to content

Load testing an HTTP request ​

An HTTP request node can send its request many times over, on a profile of requests per second, many at once — and measure what comes back: latency percentiles, errors, the rate it reached. Thresholds decide whether the step passes, and Compare puts the numbers beside an earlier run's.

A load is a setting of the node, not a node of its own: the rest of the experiment — emulators, impairment relays, other branches — runs around it as usual.

Putting a request under load ​

  1. Select an HTTP request node and fill in its request.
  2. In its properties, turn on send under load.
  3. Choose a Profile and its numbers. The chart under them, Rate over time, draws the rate and says how many requests it adds up to, in how many seconds.
  4. Set At once — how many requests may be in flight at once.
  5. Add or change Thresholds.
  6. Run the experiment.

A load starts as a Ramp from 0 to 100 requests per second over 30 000 ms, 32 at once, with two thresholds: p95 < 500 ms and Errors < 1 %.

Load replaces Repeat and Retry. Turning it on turns them off, and a node with load and either of them is refused (node.load_alone): a failed request is counted, not tried again. Only an HTTP request can run under load (node.load_unsupported).

The request is read once. Its templates are resolved when the step starts, so every request of the load is the same one: {{counter}} and {{uuid}} take one value for all of them. See templates.

One client for the whole load. The requests share the run's cookie jar when Keep cookies between requests is on, and one Digest memory, so one challenge answers them all. Each request has the node's own timeout.

The Inspector gets a sample: at most one exchange every 100 ms, so a load does not flood the Inspector.

Profiles ​

ProfileSettingsThe rate over time
ConstantRate, req/s, Duration, msthe rate throughout
RampFrom, req/s, To, req/s, Duration, msin a straight line from one rate to the other
StepsFrom, req/s, Step, req/s, Each, ms, Stepsthe first rate, then one step more at each level, every level for the same time
SpikeBase, req/s, Peak, req/s, Spike at, ms, Spike for, ms, Duration, msthe base rate, the peak for a while from a given moment, then the base rate again
RandomRate, req/s, Duration, msarrivals at random, the rate on average

Switching the shape keeps what carries over: how long it runs and the highest rate it reaches.

Limits ​

SettingRange
Rate, req/s of Constant and Random, Peak, req/s0.1–100 000 requests/s
From, req/s, To, req/s, Base, req/s0–100 000 requests/s
Every level of Steps, the last one included0–100 000 requests/s; the step may be negative
Duration, ms, Each, ms100–300 000 ms
Steps1–100, and all the levels together at most 300 000 ms
A spikelonger than 0 ms, and over by the end of the duration
At once1–512
Thresholdsat most 16, each value a number, 0 or more

A profile that adds up to no request at all is refused (load.nothing_planned). The rates are those of the HTTP burst.

A profile may last as long as a whole run, 300 s — but the run's time limit counts every step, so leave room for the rest of the experiment.

How many requests ​

A profile's requests are its rate added up over time:

ProfileRequests
Constant, 100/s for 1000 ms100
Ramp, 0 → 100/s over 2000 ms100
Steps, from 10/s up by 10/s, 3 levels of 1000 ms60 (10 + 20 + 30)
Spike, 10/s with 100/s from 1000 ms for 500 ms, 2000 ms in all65
Random, 200/s for 10 000 ms2000 on average

The schedule ​

The n-th request is due at the moment the profile's count reaches n — the first one at once. Every moment is computed from the start of the load, so a late wake-up never shifts the requests after it, and the rate the profile describes is the rate asked for.

Random draws the gaps between arrivals at random, from the run's seed: the same seed gives the same moments, so a random load can be repeated exactly. See seeds.

Missed requests. At most At once requests are in flight. When every one of them is still waiting for its answer, the next request waits for a free slot. If it would go out more than 50 ms after its moment, it is not sent late: it is skipped and counted as missed, with every other request that fell due meanwhile, and the load goes on with the first one still on time. Many missed requests mean the server, or At once, could not keep up with the profile.

While it runs ​

Once a second the timeline shows the step as Under load, with the seconds gone, the requests sent, the rate over the last second, p95 so far and the failed requests. Stop ends the load at once and drops the requests in flight; a failure in another branch ends it within a second.

What is measured ​

After the last answer the step has its measurements, kept on its last timeline event and in the run report:

MeasurementWhat
plannedthe requests the profile adds up to (Random: on average)
sentrequests that were answered or failed
okanswered with a 2xx status
failedany other status, or no answer at all
misseddue while every slot was busy, and skipped
rpsrequests sent per second: sent ÷ the profile's duration — or ÷ the time until the last request went out, when that was later
error_ratefailed, in % of sent
min, mean, maxthe fastest, the average and the slowest request, ms
p50, p90, p95, p99the latency that 50, 90, 95 and 99 % of requests were at or under, ms
received_bytesbody bytes received in all
statusesrequests by status (200, 503) and, without one, by cause (timeout, refused, reset …)
secondseach second of the profile: requests sent, failed, their mean latency
histogramrequests by latency, up to 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10 000 ms, and slower

A request's latency runs from sending it to having read its whole answer, and a failed request counts with the time it took to fail. The percentiles are read from logarithmic buckets 1 % wide and are within 0.5 % of the true value, however long the load runs.

Thresholds ​

A threshold is a row of Metric, Comparison and Value; Threshold adds one.

MetricRead in
p50, p90, p95, p99ms
Mean, Slowestms
Errors% of the requests sent
Raterequests per second achieved
Missedrequests

Comparison is one of <, ≤, >, ≥. A few common ones:

MetricComparisonValueThe step fails when
p95<300one request in twenty or more took 300 ms or longer
Errors<11 % or more of the requests failed
Rate≥180the server could not take 180 requests a second
Missed≤0a single request had to be skipped

In a file a threshold is { "metric": "p95_ms", "op": "lt", "value": 300 }; the metrics are p50_ms, p90_ms, p95_ms, p99_ms, mean_ms, max_ms, error_rate, rps and missed, the comparisons lt, le, gt and ge.

The thresholds are read after the last answer, in their order. The step fails on the first that does not hold (load.threshold), its message giving the threshold and the value measured, and the run fails with it. Without thresholds a load passes whatever it measured. When another branch's failure ended the load early, that failure is the run's, not a threshold.

The result ​

When the step passes, the timeline sums it up: the requests, the rate, p95 and the share that failed. Select the node: its properties show Last run's load —

  • each threshold, ✓ Held or ✕ Not met, with the value measured;
  • Sent, Req/s, Errors with their share, Missed;
  • p50, p90, p95, p99, Avg, Max;
  • Each second: the requests of each second, the failed ones in red, and their mean latency as a line;
  • Latencies: how many requests took how long;
  • the statuses and causes, each with its count.

The command line prints the same numbers and each threshold's verdict; see signallab run.

Comparing two runs ​

  1. Run the experiment twice, or more.
  2. In the timeline, press Compare. It is there once a run has saved its report, and is disabled while a run goes.
  3. The latest run is After, the one before it Before; either list picks another run.

The lists hold the runs of this experiment — by its name — from the reports in the data folder, newest first, at most 50: each with its date and time, how it ended and its seed. Runs from the command line are there too when it used the same data folder. Renaming the experiment starts a new history.

For each load step, matched by node, a table shows every metric Before, After and the Change, in the unit and in %. A change the wrong way by 5 % or more — slower, more errors, more missed requests, a lower rate — is a regression and shows in red; from nothing to something counts too. Under the table, each threshold's verdict in both runs. A load step that only one of the runs has is marked only before or only after, without changes. Runs without load steps show No load steps in these runs.

From a script, experiment_runs lists the runs and experiment_compare compares two, by their report's file name; signallab mcp offers the same to an assistant (MCP).

Checks after a load ​

A load leaves no response of its own: it is measured, not checked. A check or Extract value after it needs another request without load before it on every path, or the experiment does not run (graph.needs_http). To check one answer of the API under load, put a plain HTTP request after the load, or in a parallel branch beside it.

Send now on a node under load sends its request once.