Serving Timing Summary
Serving Timing Summary
A company self-hosts an open-weight model behind the code-review assistant in its internal developer portal. Before changing the server's batching settings, the team runs a load test and logs one timing record per request. Your job is the analysis step: turn the records into a summary that cannot make an overloaded server look healthy.
Every time is in seconds on one monotonic clock. A request starts when the client sends it and ends when it reaches its terminal status. For a successful request, firstToken is when its first output token arrived and outputTokens is how many tokens it generated.
Implement summarize(records, windowStart, windowEnd). The observation window [windowStart, windowEnd] is the load test's whole measurement period, and it must contain every record.
Records
| Field | Meaning |
|---|---|
start | When the request was sent |
end | When the request reached its terminal status |
status | One of "success", "failed", "timeout", "cancelled", "rejected" |
firstToken | Successful requests only: when the first output token arrived |
outputTokens | Successful requests only: how many tokens the request generated |
A non-success record needs only start, end, and status. Its firstToken and outputTokens are ignored, even when they are present or malformed: a timed-out request may have streamed part of an answer, but that output is not useful work.
Validation
Check in this order and throw an Error with the given message at the first failure. Check the records in array order, and finish each record before moving on to the next one.
| Order | Message | Fails when |
|---|---|---|
| 1 | "Invalid observation window" | windowStart or windowEnd is not a finite number, or windowEnd <= windowStart |
| 2 | "Expected nonempty records" | records is not an array, or it is empty |
| 3 | "Record must be an object" | a record is null, an array, or not an object |
| 4 | "Invalid request times" | start or end is not a finite number, or windowStart <= start <= end <= windowEnd does not hold |
| 5 | "Invalid status" | status is not one of the five values |
| 6 | "Invalid successful output" | for a "success" record: firstToken is not a finite number with start <= firstToken <= end, or outputTokens is not an integer of at least 1 |
A finite number is a value of type number that is not NaN, Infinity, or -Infinity. Never convert: null, true, and "2.5" are not numbers. Every bound is inclusive: a request may start at windowStart, end at windowEnd, and receive its first token at its own start or end.
Times come before status on purpose. A record with broken timestamps is reported as a timing problem even when its status is also unknown.
Summary
Return an object with exactly these keys. Only successful requests contribute to latency, tokens, and throughput.
| Key | Value |
|---|---|
requests | The number of records |
successes | The number of "success" records |
nonSuccessFraction | 1 - successes / requests |
successfulRequestsPerSecond | successes / (windowEnd - windowStart) |
successfulOutputTokensPerSecond | The sum of outputTokens over successful records, divided by windowEnd - windowStart |
successLatencyP95S | p95 of end - start over successful records |
successTtftP95S | p95 of the time to first token, firstToken - start, over successful records |
successRequestTpotP95S | p95 of the per-request time per output token, (end - firstToken) / (outputTokens - 1), over successful records with outputTokens > 1 |
- Throughput uses the whole window. Divide by
windowEnd - windowStart, not by the time between the first and last record, and not by the sum of latencies. - One-token responses have no TPOT. A single-token response has no interval after its first token, so it is left out of the TPOT values. It still counts in latency, TTFT, and tokens.
- p95 is the nearest rank. Sort the n values in ascending numeric order and take the value at 1-based rank
ceil(0.95 * n), which issorted[max(0, ceil(0.95 * n) - 1)]. There is no interpolation: the p95 of the 20 values 1, 2, ..., 20 is 19, not 19.05 and not 20. - No values means
null. When a metric has no values (no successes, or no success with more than one token), its p95 isnull, never0orNaN. - Do not modify
recordsor the record objects.
Fixture
The tests summarize this load test over the window 0 to 10, variants of it, and the edge cases above.
| Record | Status | start | firstToken | end | outputTokens |
|---|---|---|---|---|---|
| R1 | success | 0 | 0.5 | 2.5 | 9 |
| R2 | success | 1 | 1.25 | 1.25 | 1 |
| R3 | timeout | 2 | none | 7 | none |
| R4 | rejected | 3 | none | 3 | none |
Example
summarize(
[
{ start: 0, end: 3, status: 'success', firstToken: 1, outputTokens: 5 },
{ start: 1, end: 2, status: 'failed' },
],
0,
4,
);
// {
// requests: 2, successes: 1, nonSuccessFraction: 0.5,
// successfulRequestsPerSecond: 0.25, successfulOutputTokensPerSecond: 1.25,
// successLatencyP95S: 3, successTtftP95S: 1, successRequestTpotP95S: 0.5,
// }The only success took 3 seconds, and its first token arrived after 1 second. Its other 4 tokens took the remaining 2 seconds, so its TPOT is 0.5. Both rates divide by the 4-second window, and the failed request counts only in requests and nonSuccessFraction.
Python
Implement summarize(records, window_start, window_end) with lists and dicts, and raise ValueError with the same messages. The keys are renamed:
| JavaScript | Python |
|---|---|
windowStart, windowEnd | window_start, window_end |
firstToken | first_token |
outputTokens | output_tokens |
nonSuccessFraction | non_success_fraction |
successfulRequestsPerSecond | successful_requests_per_second |
successfulOutputTokensPerSecond | successful_output_tokens_per_second |
successLatencyP95S | success_latency_p95_s |
successTtftP95S | success_ttft_p95_s |
successRequestTpotP95S | success_request_tpot_p95_s |
null | None |
requests, successes, start, end, status | unchanged |
In Python, records must be a list and each record a dict. A finite number is an int or float that is not a bool (because True == 1), and output_tokens must be an int that is not a bool.
Constraints
- A valid call has
1 <= records.length <= 10^5, but any argument or field may hold any value or be missing. Validation decides. - Valid times and token counts are small enough that the arithmetic never overflows.
Expected Complexity
O(n log n) time for n records, from sorting three lists of at most n values, and O(n) extra space.
0 views
Solution
Hints
