# Streams Overview KPIs

> What each tile on the Streams Overview screen measures, which query feeds it, how often it refreshes, and why some figures render as a floor or a dash.

The Overview screen is the Streams developer home. It is a *snapshot* screen:
every panel is a short, cheap query that refreshes on its own timer. The
Analytics screens are the place for exact, windowed numbers. This page explains
where each Overview figure comes from so you can tell a real change from a
refresh-timing artefact.

Every query on this screen is scoped to the org id from the current tenant
context and is disabled until that org id resolves.

## The five KPI tiles and where each figure comes from

The KPI strip across the top is fed by `streams.analytics.overviewKpis`, with
one exception — the **Live streams** tile, which is counted client-side from a
page of `streams.liveInputs.list`.

| Tile | Value rendered | Source field | Formatting |
| ---- | -------------- | ------------ | ---------- |
| Concurrent viewers | Viewers across all live streams | `overviewKpis.viewers_now` | Locale-formatted integer. Subtitle reads `across N live streams`. |
| Live streams | Count of live inputs | `liveInputs.list` rows where `is_live` is true **or** `status === 'active'` | Shown as `N / total`, where `total` is every input in the org. |
| P50 glass-to-glass | Median end-to-end latency | `overviewKpis.g2g_p50_ms` | Milliseconds, integer. The tile displays a `250ms` target and flags the value as healthy when it is above zero and at or below that target. |
| Egress · 24h | Bytes delivered in the last 24 h | `overviewKpis.bytes_egress_24h` | Divided by 1024⁴ and rendered as TB to two decimals. |
| Cache hit · edge | Edge cache hit ratio | `overviewKpis.cache_hit_ratio` | Percent to one decimal. Flagged healthy at 95% or above. |

Two things to note about the **Concurrent viewers** tile. The number is the
analytics rollup's `viewers_now`. The `across N live streams` subtitle next to
it is *not* from the same query — it is the same client-side count that drives
the Live streams tile. The two can disagree briefly after a stream starts or
stops, because they refresh independently.

The `total` denominator on the Live streams tile comes from a separate,
deliberately tiny call: `liveInputs.list` with `perPage: 1`, read only for its
`total`. It is cached with a 60-second stale time, so a newly created input can
take a moment to move the denominator.

## Windows and refresh cadence per panel

The screen does not have one clock. Each panel picks the cheapest cadence that
still feels live, so panels drift relative to one another between ticks.

| Panel | Query | Window | Refresh |
| ----- | ----- | ------ | ------- |
| KPI strip | `analytics.overviewKpis` | Fixed by the procedure (egress tile is 24 h) | 30 s |
| Live now list | `liveInputs.list` (`perPage: 100`) | Instant snapshot | 30 s |
| Input total (tile denominator) | `liveInputs.list` (`perPage: 1`) | Instant snapshot | On demand, 60 s stale time |
| Recent events | `eventDeliveries.list` (`limit: 6`) | Last 6 deliveries | 30 s |
| Concurrent viewers chart | `analytics.viewerSeries` | 24 h, 10-minute buckets | 60 s |
| Glass-to-glass P50/P95/P99 | `analytics.qoeKpis` | 60 minutes | 30 s |
| Glass-to-glass histogram | `analytics.g2gHistogram` | 60 minutes | 60 s |
| Delivery minutes / Egress / Storage meters | `analytics.usage` | Current billing cycle | 60 s |
| Pool limits behind those meters | `analytics.billing` | Current billing cycle | 120 s |

The practical consequence: the **P50 glass-to-glass KPI tile** and the **P50
under the histogram** are different numbers from different procedures. The tile
reads `overviewKpis.g2g_p50_ms`; the histogram panel reads
`qoeKpis.g2g_p50_ms` over an explicit 60-minute window and refreshes on a
different timer. If you need one authoritative percentile, read the latency
panel, not the strip.

## Why the live stream count can be a floor

The Live streams tile and the `across N live streams` subtitle sometimes render
with a trailing `+`, for example `12+`. That means *at least* twelve, not
exactly twelve.

The reason is how liveness is resolved. `liveInputs.list` filters on the
Postgres `status` column first, then reconciles each returned row against the
relay to set `is_live`. Because that reconcile happens **after** the database
filter, the screen cannot ask the server for "only live rows" and trust the
count — it fetches one page of up to 100 inputs and counts the live ones
client-side.

So the screen compares the number of rows it received against the `total`
reported by the same query:

- `total` equals the rows returned → the count is exact, no `+`.
- `total` is greater than the rows returned → there are inputs the page never
  saw, any of which might be live. The count is a lower bound and the tile
  appends `+`.

An org with more than one page of inputs will therefore always see the `+`,
even if nothing is broken. To get an exact live count, open the Live streams
screen, which pages through the full set.

The **Live now** panel under the endpoint card is a preview of the same page: it
renders the first four live rows with their per-stream `viewers_now` and
`g2g_p50_ms`. "View all" goes to the full list.

## Billing cycle meters when no plan pool is set

The "This billing cycle" card shows three meters. Their used values and their
limits come from two different procedures, and the limits are optional.

`analytics.billing` returns a `pools` object which may contain
`stream_egress_gb` and `vod_storage_gb`, each with a `limit` and a `remaining`.
`analytics.usage` returns raw metered counters: `delivery_minutes`,
`egress_bytes` and `storage_bytes`.

How each meter resolves:

- **Egress** and **Storage (VOD)** — if the matching pool exists, the meter
  shows `limit − remaining` as used (never below zero) against the pool's
  limit. If the plan defines no such pool, the meter falls back to the metered
  counter from `analytics.usage`, converting bytes to GB by dividing by 1024³,
  and renders with a limit of zero. All three values are rounded to one
  decimal.
- **Delivery minutes** — always rendered with a limit of zero. This modality has
  no delivery-minute pool on the Overview screen, so the meter is a **counter,
  not a quota**. It shows minutes consumed so far in the current billing cycle
  and nothing else.

A meter drawn against a zero limit is expected whenever the plan does not carve
out a pool for that resource. It does not mean your allowance is exhausted, and
it does not mean usage is unmetered — the used figure is still the real metered
number. Read the limit as "no pool configured".

Because `analytics.billing` refreshes every 120 s and `analytics.usage` every
60 s, a meter can briefly show a used value computed one way and a limit from
the previous billing tick. It reconciles on the next refresh.

## Reading a dash, a plus, or an empty chart

The screen distinguishes "no data yet" from "zero" in several places. What you
see tells you which query is still cold.

- **Em dash (`—`) in a KPI tile.** The underlying query has not returned yet, or
  returned nothing. The Live streams tile shows a dash specifically while
  `liveInputs.list` is loading, then switches to a count.
- **Em dash on P50 / P95 / P99 under the histogram.** `qoeKpis` reported zero
  for that percentile, which the panel treats as "no samples" rather than
  "0 ms". Percentiles only render once the 60-minute window contains latency
  samples.
- **Trailing `+` on a count.** A lower bound — see
  [Why the live stream count can be a floor](#why-the-live-stream-count-can-be-a-floor).
- **"No viewer samples yet."** The 24-hour viewer chart needs at least two
  buckets with a non-zero value. Fewer than two 10-minute buckets, or an
  all-zero series, renders this caption instead of a flat line at zero. A brand
  new org sees this until the relay rollup produces its second bucket.
- **Flat empty histogram.** When `g2gHistogram` returns no buckets, the panel
  draws fourteen empty bars so the axis labels stay in place. The P50 marker is
  hidden in that case; it only appears when `qoeKpis` reports a non-zero P50.
- **"No active inputs — your first encoder publish will show up here."** The
  Live now panel has rows available but none of them are live, or the org has
  no inputs at all.
- **"No webhook deliveries yet."** `eventDeliveries.list` returned nothing. Once
  deliveries exist, the panel shows the six most recent with their HTTP response
  code and event type; a code in the 2xx range renders as success, anything else
  as a failure.

## The histogram bars and the P50 marker

The latency histogram plots the first fourteen buckets returned by
`g2gHistogram` over the 60-minute window, labelled from 80 ms to 600+ ms. The
marker showing where your median sits is derived, not returned: the panel takes
`qoeKpis.g2g_p50_ms`, divides by 40 to map milliseconds onto a bucket index, and
clamps the result to the last drawn bucket. A median past the top of the axis
therefore parks the marker on the final bar rather than disappearing off the
edge. Read the marker as approximate and the `P50` stat below it as the number.

## Related

- [Streams SDK methods](/modalities/streams/sdk-methods)
- [Telemetry](/platform/telemetry)
- [Telephony metrics](/glossary/metrics)
