The Analytics screen answers one question: what did a turn cost, and what did you get back when the turn was abandoned. The screen covers the whole fleet. It does not filter by tenant, model, or namespace. It uses independent panels, each backed by its own query and its own fixed time window. The most common mistake is to read a number off the wrong panel, so start with where the numbers come from.

The two panels and their two data sources

The gateway panel is a billing-meter rollup. mod_inference calls meter() for every completion it serves, which writes rows into the billing stream. Each row is a (dimension, units) pair; the panel aggregates those rows by dimension name. The turn panel is per-turn telemetry. mod_inference / mod_agentruntime emit one row per agent turn into agent_turn_sample, carrying the full stage breakdown for that turn. Benchmarks can write the same shape by posting to /v0/events?name=agent_turn_sample. The By modality panel reads the same billing table as the gateway panel. It sums the inference.modality_turns and inference.modality_ms meters for each lane (voice, text, image, video, robotics). A lane with no turns does not appear. The panel cannot tell “we served no video” from “video is not deployed”, so it shows only what ran. Because the panels read different tables written by different code paths, they can disagree in coverage. That is expected, not a bug — see Fixed windows and why one panel can be empty.

Gateway meters: requests, occupancy, units

Every KPI in the top panel is a sum or average over rows whose dimension starts with inference.: Requests being defined as “rows carrying an occupancy meter” is worth remembering when you reconcile against your own logs: a completion that never reached the point of holding the model does not emit inference.occupancy_ms and therefore is not counted as a request here. Occupancy is also summed (occupancy_ms_total) by the procedure, which is the figure to use if you want total GPU-time held across the window rather than a per-turn average.

The abort breakdown: superseded, caller, deadline

An abort is a turn the gateway stopped generating on purpose. The gateway meters a separate dimension per reason, so the three counters are disjoint and sum toward inference.cancelled_turns: These three are diagnostic, not interchangeable:
  • A high caller share is a turn-detection / pacing signal: the agent is speaking when the human still wants the floor.
  • A high superseded share means turns are being issued faster than they complete — state churns under the generation.
  • A high deadline share is a latency signal: generation is slow enough, or started late enough, that answers stop being timely.
Each abort is also a saving, which is what the next section quantifies.

How GPU saved and tokens reclaimed are derived

There are two different savings figures on this screen, computed two different ways. They are not the same number and should not be compared directly. Gateway panel — Abort rate as a proxy. The gateway panel has no per-turn detail, so it derives a rate, not a saving:
cancelled is the summed inference.cancelled_turns units and requests is the occupancy-row count. The hint on the card reads “turns cancelled early → GPU reclaimed” because a cancelled turn stops holding the model early; the share of cancelled turns is a stand-in for reclaimed GPU time. An exact milliseconds-saved figure is not available from billing meters alone — it needs per-turn rows. Turn panel — measured per turn. agent_turn_sample carries gpu_saved_pct and tokens_avoided on each row, so the savings are measured rather than inferred: Two consequences follow from the avgIf shape. First, Abort GPU saved does not move when abort volume moves — it describes the average abort, not how many there were. Read it next to Abort rate. Second, with very few aborted turns in the window, it is an average over a very small sample and will look jumpy.

Per-turn stage latencies: EOU, LLM, TTS first audio

The second turn card breaks a turn into the stages you can act on separately. All four come from agent_turn_sample: tokens_per_s_avg (shown as Throughput on the first turn card) is the same shape. Note the nullIf(x, 0) wrapper on tokens_per_s, llm_ms and tts_ttfb_ms: a zero in those columns is treated as absent and dropped from the average rather than pulling it down. A turn that never reached the LLM stage, or never produced audio, does not depress the stage averages for turns that did. eou_ms and eou_prob are not null-guarded — every sampled turn is expected to carry them. Aborted turns still appear in these averages. An aborted turn has a real EOU time and a real (truncated) LLM time, so a window with many aborts will show shorter llm_ms_avg than a window without.

Fixed windows and why one panel can be empty

All windows are fixed by the screen. You do not choose them. The screen calls turnAnalytics({ windowDays: 30 }) and gatewayStats({ windowDays: 7 }). Each procedure accepts a windowDays input and clamps it to the 1–90 range, so other windows are reachable via the API, but the panels as rendered are 30 days and 7 days respectively. The split is deliberate:
  • The gateway panel is a live operational view. It lights up as soon as a call runs through the FirstByte inference gateway, because billing meters are emitted by the gateway itself on every completion. A short window keeps it reflective of current behaviour.
  • The turn panel needs per-turn telemetry, which is sampled. A longer window gives the averages and the aborted-turn subset enough rows to mean something.

Panel states you can see

The screen gates each panel on its own row count — hasGw requires requests > 0, hasData requires turns > 0:

Query errors

Each procedure wraps its query in a try. If the table does not exist yet or ClickHouse is unreachable, the procedure returns ok: false. The screen also treats a failed request as an error. In both cases the screen shows a Could not read usage data card with a Retry button. The card says that the usage store did not answer and that the numbers shown may be incomplete. A query error is not the same as “no traffic”. The empty state, “No usage data yet”, shows only when every query succeeded and returned no rows. While the queries run, the screen shows “Loading usage data…”. One past case of a wrong source: the gateway panel originally read the metric table, but mod_inference emits these dimensions through meter() (the billing stream) and never through metric(). The query matched zero rows and the panel rendered empty on a busy gateway. The procedure now reads the billing-event table.
  • Telemetry — the metric, trace, and CDR streams the gateway emits
  • Telephony Metrics — standard definitions for the call-level numbers