Performance Metrics

Performance metrics are the numbers that describe how fast a system responds and how much work it completes under a given load. Five carry most of the meaning: response time (how long one request takes, seen from the client), latency (how long something waits or travels), throughput (how much work is completed per second), error rate (what share of the work fails) and resource utilisation (how busy the CPU, memory, disks, network and connection pools are).

An example: a checkout API answers in 290 ms when one tester calls it. At 50 requests per second it may still answer in 300 ms, or it may take 4 seconds with 3% errors while the database runs out of connections. Only the metrics measured together, over the same time window, tell you which of the two happened and why.

Why it matters

One number such as "the average response time was 400 ms" can hide a failing system. Errors that return quickly pull the average down, a load tool that sent less traffic than planned makes the system look healthy, and a server already at 95% CPU has no room for the next peak. Decisions such as "can we launch the campaign?" are only as good as the metrics behind them.

For the tester, metrics are the shared language with developers, operations and the product owner. A finding that names the metric, where it was measured, over which period and at what load can be acted on; "the system is slow" cannot.

Response time and the parts it is made of

Response time is the time from the moment a client starts a request until it has received the complete response. It is measured on the client side, so it includes everything on the way: finding the server, opening the connection, the server's own work and the transfer back. Server logs often record a shorter duration that leaves the network out, so label every number with where it was measured.

On a new HTTPS connection it splits into the four parts below. curl prints the moment each part ends with its -w option; the values are seconds counted from the start, so each part is the difference between two neighbours. In the sample, waiting is 0.244 − 0.061 = 0.183 s of the 0.289 s total. When waiting dominates, look at the server; when connection set-up dominates, look at the network, the distance to the server and connection reuse.

  • DNS lookup: the host name is resolved to an IP address.
  • TCP connect and TLS handshake: the connection is opened and secured. A reused (kept-alive) connection skips both.
  • Waiting: the request travels to the server, the server processes it and the first byte of the response travels back. The end of this phase, counted from the start, is the time to first byte (TTFB); k6 uses the same name for the waiting phase alone.
  • Content download: the rest of the response arrives. A large or uncompressed body makes this part grow.
fmt='dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect}'
fmt="$fmt ttfb=%{time_starttransfer} total=%{time_total}\n"
curl -s -o /dev/null -w "$fmt" https://shop.example.com/api/cart

# output in seconds from the start, rounded to milliseconds:
# dns=0.012 tcp=0.030 tls=0.061 ttfb=0.244 total=0.289
One HTTPS request timed by curl: 289 ms in total, of which 61 ms is connection set-up, 183 ms waiting for the first byte and 45 ms downloading the body.

curl -w output for GET /api/cart (seconds from the start)

Part 1:dns=0.012 Part 2:tcp=0.030 Part 3:tls=0.061 Part 4:ttfb=0.244 Part 5:total=0.289

  • 1–3Connection set-up: 61 ms before the request is sent, skipped on a reused connection
  • 1–4Time to first byte counted from the start, as curl does: 244 ms (k6's TTFB, http_req_waiting, is roughly part 4 alone)
  • 1–5Response time seen by the client: 289 ms
  1. 1DNS lookupThe host name is resolved to an IP address; a cached name costs almost nothing.12 ms
  2. 2TCP connectOne round trip opens the connection. A reused connection skips it.30 - 12 = 18 ms
  3. 3TLS handshakeSecures the connection: one round trip with TLS 1.3, also skipped on reuse.61 - 30 = 31 ms
  4. 4WaitingThe request travels, the server works and the first byte travels back. Slow server code shows up here.244 - 61 = 183 ms
  5. 5Content downloadThe rest of the body arrives; this part grows with response size and slow networks.289 - 244 = 45 ms

Illustrative timings for one request on a new connection. curl prints cumulative seconds from the start, so each part is the difference between two neighbours.

Try it: Response Time Analyzer Try it: Response Size Analyzer

Latency: say which one you mean

Latency is the most overloaded word in performance work. In networking it is the time data takes to travel, usually quoted as round-trip time (RTT). Some load tools use it for the time until the first part of the response arrives: JMeter's Latency stops there, while its Elapsed time runs until the last part. The four golden signals of the Google SRE book use latency for the whole time it takes to service a request, which this lesson calls response time.

The difference matters in practice. A new HTTPS connection needs about three round trips before the first byte arrives: one for the TCP handshake, one for a TLS 1.3 handshake and one for the request itself. With an RTT of 80 ms that is about 240 ms before the server's own work is counted, and a faster server cannot remove it; connection reuse removes the two set-up round trips, and a server closer to the users shortens all three. Tools also start and stop the clock at different points: k6's http_req_duration is sending plus waiting plus receiving and reports TCP connecting and TLS handshaking as separate metrics, while curl's time_total includes them. Write down which definition every number in a plan or report uses.

Try it: API Performance Comparator

Throughput, error rate and resource utilisation

Response time describes single requests. The next three metrics describe the system under load, and they only make sense when they are read together, over the same time window and at a known load.

  • Throughput: completed work per unit of time — requests per second (RPS), transactions per second (TPS) when one user action spans several requests, or bytes per second. It counts what the system finished, not what the load tool tried to send. Load tools such as JMeter count failed requests in their throughput too, so when a target is about successful work, count the successes separately. When load keeps rising and throughput stops rising, the system has reached a limit.
  • Error rate: failed requests divided by all requests in the same window, as a percentage. Agree in advance what counts as a failure: 5xx responses, timeouts and connection errors, and, as the SRE book points out, a 200 response with the wrong content; some teams also count, by policy, any response slower than an agreed limit. Report the response time of successful requests separately, because fast errors make a system look quicker than it is.
  • Resource utilisation: how busy each resource is — CPU, memory, disk, network and pools such as database connections and worker threads. Utilisation says how much of a resource is used; saturation says whether work is already queueing for it. A pool with 50 of 50 connections in use and requests waiting is saturated, whatever the CPU graph shows.
  • The load generator is a resource too: if its own CPU or network is at its limit, the results describe the tool, not the system under test.

Try it: HTTP Request Benchmark

Worked example: reading a ten-minute load run

A team runs ten minutes of steady load against POST /api/orders in staging. The target agreed with the product owner is at least 20 successful orders per second with an error rate below 0.5%. The summary below combines the load tool's results with the server dashboards.

From the summary: the throughput of successful orders is 11,880 / 600 = 19.8 per second, just under the target. The error rate is 120 / 12,000 = 1.0%, twice the limit. The resources explain why: the database CPU still has headroom, but the connection pool is saturated, so requests queue for a connection until they time out or the service answers 503. The finding to report is "connection pool exhausted at 20 orders per second, error rate 1.0%", with these numbers attached — not "the database is slow".

POST /api/orders · staging · 10 minutes of steady load

Duration                   600 s
Requests sent              12,000
Successful (201 Created)   11,880
Failed                     120   (84 timeouts after 5 s, 36 x HTTP 503)
Median response time       210 ms (successful requests only)
Database CPU               55% average
DB connection pool         50 of 50 in use, up to 30 requests waiting

The vocabulary you will meet

  • Transaction: a named group of requests measured as one user action, such as "checkout", from its first request to its last response.
  • Virtual user (VU): one simulated user in a load tool, running a script in a loop.
  • Think time: the pause a real user takes between actions. Leaving it out makes 100 virtual users send far more traffic than 100 real people.
  • Concurrency: how many users or requests are active at the same moment. It is not throughput: 100 concurrent users can produce 10 or 1,000 requests per second, depending on response time and think time.
  • Percentile (p95): the value that 95% of the measurements are at or below; the median is p50, the middle value. Reports rely on percentiles rather than averages, and percentiles have a lesson of their own.
  • SLI and SLO: a service level indicator is a measured metric, such as the share of requests answered within 300 ms; a service level objective is the target set for it.
  • Bottleneck and headroom: the bottleneck is the resource that limits throughput first; headroom is the spare capacity left before a resource reaches its limit.

Common mistakes

  • Reporting one average response time without saying whether failed requests are included.
  • Treating "latency" and "response time" as one number, or comparing two tools that start and stop the clock at different points.
  • Reading the load the tool was configured to send as throughput, instead of the requests the system actually completed.
  • Counting only 5xx responses as errors and missing timeouts, connection errors and 200 responses with the wrong body.
  • Reporting response times without resource utilisation, so the report says "slow" but not where the limit is.
  • Measuring from a laptop on Wi-Fi, or from a saturated load generator, and blaming the server for what the network or the tool added.

The QA perspective

Agree the metric definitions before the first run: which transactions are measured, where the clock starts and stops, what counts as an error, which percentiles and time window are reported, and the targets from the non-functional requirements. Ask developers or operations for the server dashboards so that resource utilisation is recorded on the same timeline as the load. Generate load only against systems you own or have written permission to test.

In the report, give every number its context: load level, duration, environment, tool, and whether it was measured on the client or the server. Keep the raw results so the next run can be compared like for like. When a target is missed, name the metric, the size of the miss and the resource that was saturated at that moment.

Practice exercise

Calculate and judge the metrics of a search API run

Your task

A five-minute steady-state run against GET /api/search in staging produced the summary below. The requirement says: "Search handles at least 40 requests per second with an error rate below 1% and a median response time under 300 ms."

Calculate the metrics, decide whether the requirement is met, and write the finding you would report and the question you would ask the team.

Material
GET /api/search · staging · 300 s steady state · 60 virtual users

Requests sent              13,500
HTTP 200                   13,257  (27 of them with body {"error":"index busy"})
HTTP 504                   135
Client timeouts (3 s)      108
Median response time       180 ms  (all responses, errors included)
App server CPU             92% average, 98% peak
Database CPU               35% average
Load generator CPU         40% average
Steps
  1. Count the successful requests and calculate their throughput.
  2. Count every kind of failure and calculate the error rate.
  3. Check what the median response time covers before comparing it with the target.
  4. Use the resource figures to locate the limit.
Show the model answer
Model answer

Throughput: 13,257 − 27 = 13,230 successful requests; 13,230 / 300 = 44.1 per second. The target of 40 per second is met.

Error rate: 135 HTTP 504 responses, 108 timeouts and 27 responses with status 200 but an error body make 270 failed requests; 270 / 13,500 = 2.0%, twice the 1% limit. The requirement is not met. Counting only the 504s gives exactly 1.0%, which still fails "below 1%" but hides half of the failures.

Median: 180 ms is under 300 ms, but it mixes failed and successful requests. Ask for the median of successful requests before calling this target met.

Limit: the application server CPU averages 92% and peaks at 98%, while the database (35%) has headroom and the load generator (40%) is not the bottleneck. Report: "Error rate 2.0% against a 1% limit at 44 requests per second; app server CPU 92–98%." Ask whether the staging application server has the same CPU allocation as production, and what the search service does for each request.

Frequently asked questions

Is latency the same as response time?

Not necessarily. Response time is the full time from sending a request to receiving the whole response. Latency can mean network travel time, the time to the first byte or, in some monitoring vocabularies, the whole response time. Check how your tool defines it and state which meaning each number has.

What is a good response time?

There is no universal number. The target comes from the non-functional requirements or an SLO for each transaction, for example a percentile under a stated time at a stated load. Without a target, compare against a baseline from an earlier release.

Why is throughput lower than the load I configured?

Because throughput counts the work the system completed. When responses slow down, each virtual user that waits for its response sends fewer requests; requests that time out never complete, and failed requests do not count when you measure successful throughput. A gap between the configured load and the throughput is a finding in itself.

References

Practise with Mutqan tools

Measure an endpoint's response time over several samples: min, max, average, median and p95.

Performance

Run a small, safe benchmark against a public endpoint (bounded requests and concurrency) and get throughput and latency percentiles.

Performance

Analyze response size: raw vs compressed bytes, headers size, JSON structure weight and largest fields.

Performance

Compare response times and sizes of two endpoints (e.g. v1 vs v2, staging vs production) side by side.

Performance

Go deeper

Your progress on this topic

Part of the QA Roadmap