Tie QoE metrics to the CDN edge
spot the gap and fix it.
A working guide to the QoE metrics that actually move live and on-demand delivery — rebuffer ratio, video-startup time, manifest-fetch failures, segment-fetch failures by CDN, CDN hit ratio, origin-shield health, and per-(CDN, geo) error rate. Covers the instrumentation blind spots that sit between the transcoding pipeline and the CDN edge, and how Streamwake's agentic ops layer bridges them: ranked root cause plus a recommended fix on every refresh.
Book a technical demo for Streaming observability
Read the postmortem — then bring your own incident to Streamwake.
Two ways to engage on this exact failure pattern: book a 30-minute technical demo where we walk through the probe cascade on your source, or hand us an archived incident and watch the agent diagnose it end-to-end.
Both routes land on the scoping intake form — no SDR gate.
What Streamwake checks
Five families of probes, each with a small, deterministic pass/fail verdict that reads both player-side and edge-side telemetry. Every check has a name — that's the name you'll see on the agents feed.
- rebuffer_ratio across session windows (0.5%/5%/24h) — warn above 0.5%, fail above 2%.
- video-startup time from manifest fetch to first frame; budget ≤ 2.5s p75.
- Join time across cohort — p50/p75/p95 read alongside the per-session rebuffer ratio.
- Failure rate under 0.1% over 5 min; p95 fetch latency ≤ 250ms.
- Detect partial-consistent failures across regions — aggregate green, geo red.
- Stale-while-revalidate serving masked as a successful manifest fetch is flagged via Warning: 110 + age > max-age.
- Per-CDN, per-POP segment-fetch success rate — outlier cells must surface, not dilute.
- 5xx share tracked separately from connection-time / first-byte timeouts.
- Outlier-POP detection vs the same CDN's healthy POPs — the partial-consistent cell mix names the failing POP exactly.
- hit_ratio on the manifest path ≥ 95% within the probe window — fail below.
- Shield bypass rate without a shield fault → reroute-rule regression flagged.
- Shield saturation flagged when shield latency > capacity under load, even when no x-cache: ERROR is emitted.
- Per-(provider, POP, geo) 5xx count — scoped, not aggregate.
- 4xx vs 5xx isolated — 4xx is application, 5xx is edge. Don't discount 4xx either.
- Egress saturation: bandwidth ratio approaching ceiling, queue depth growing.
Anatomy of an observability packet
The five things the agents actually probe at observability cadence — a player event timeline, an edge access log, a manifest metrics push, a ranked root-cause verdict, and the agent timeline that ties them together with a recommended fix and rebalance hints.
{
"session_id": "cksliveobs-sess-7421",
"player": "streamwake/2.4.1",
"events": [
{ "ts": "2026-08-06T10:42:11.002Z", "type": "MANIFEST_FETCH", "cdn": "cdn-A/fra02", "dur_ms": 182 },
{ "ts": "2026-08-06T10:42:11.547Z", "type": "FIRST_FRAME", "elapsed_ms": 545 },
{ "ts": "2026-08-06T10:42:14.114Z", "type": "SEGMENT_FETCH", "cdn": "cdn-A/fra02", "dur_ms": 88 },
{ "ts": "2026-08-06T10:42:15.031Z", "type": "SEGMENT_FETCH", "cdn": "cdn-A/fra02", "dur_ms": 94 },
{ "ts": "2026-08-06T10:42:16.230Z", "type": "REBUFFER_START", "reason": "segment_404", "cdn": "cdn-A/fra02" },
{ "ts": "2026-08-06T10:42:18.974Z", "type": "REBUFFER_END", "rebuffer_ms": 2744 },
{ "ts": "2026-08-06T10:42:19.012Z", "type": "SEGMENT_FETCH", "cdn": "cdn-A/fra02", "dur_ms": 79 }
],
"summary": {
"video_startup_time_ms": 545,
"rebuffer_ratio_session": 0.018,
"segment_fetch_p95_ms": 94
}
}cdn-A/fra02 - - [06/Aug/2026:10:42:14 +0000] "GET /live/event/seg1237.m4s HTTP/2" 200 871244 "-" "-" 94 "x-cache=MISS" "x-served-by=cache-fra6123"
cdn-A/fra02 - - [06/Aug/2026:10:42:15 +0000] "GET /live/event/seg1238.m4s HTTP/2" 404 312 "-" "-" 21 "x-cache=MISS" "x-served-by=cache-fra6123"
cdn-A/fra02 - - [06/Aug/2026:10:42:15 +0000] "GET /live/event/seg1238.m4s HTTP/2" 200 871312 "-" "-" 78 "x-cache=MISS" "x-served-by=cache-fra6123"
cdn-A/fra02 - - [06/Aug/2026:10:42:16 +0000] "GET /live/event/seg1239.m4s HTTP/2" 200 871408 "-" "-" 92 "x-cache=MISS" "x-served-by=cache-fra6123"
# session cksliveobs-sess-7421 read X-Cache: MISS on every segment in the burst;
# cdn-A/fra02 served 1x404 (seg1238) -> rebuffer on the player.{
"window": "2026-08-06T10:42:10/10:42:20",
"cdn": "cdn-A",
"manifest_fetch_count": 43821,
"manifest_fetch_failures": 19,
"manifest_fetch_latency_p95_ms": 287,
"per_geo_breakdown": [
{ "geo": "EMEA", "count": 18201, "failures": 18, "latency_p95_ms": 298, "fail_ratio": 0.00099 },
{ "geo": "AMER", "count": 14751, "failures": 1, "latency_p95_ms": 214, "fail_ratio": 0.00007 },
{ "geo": "APAC", "count": 10869, "failures": 0, "latency_p95_ms": 232, "fail_ratio": 0.0 }
],
"note": "vendor aggregate is staged per-cdn, geo, but the per-POP cells are not; per-cell greens can hide a red POP inside a green geo."
}{
"session_id": "cksliveobs-sess-7421",
"relapse_score": 0.86,
"ranked_root_cause": [
{ "rank": 1, "hypothesis": "cdn-A/fra02 segment-404 burst", "evidence": ["edge_access_log.404", "player.rebuffer_start"], "probability": 0.62 },
{ "rank": 2, "hypothesis": "manifest cache-control ttl widened", "evidence": ["manifest.fetch_latency_p95=287ms"], "probability": 0.27 },
{ "rank": 3, "hypothesis": "encoder/packager handoff drift", "evidence": ["packager->cdn.handoff_wait=412ms"], "probability": 0.11 }
],
"recommended_fix": {
"title": "Pin next probe to cdn-B for EMEA, tighten manifest cache-control to 1s",
"actions": [
{ "kind": "rebalance_hint", "value": "pin_segment_to_cdn_B_for_geo_EMEA" },
{ "kind": "config_change", "value": "manifest.cache-control: max-age=1, must-revalidate" }
],
"verification": "rebuffer_ratio < 0.005 and segment_fetch_p95_ms < 120 over next 5 cycles"
}
}- qoe.rebuffer_ratio → session rebuffer events / playtime
- qoe.video_startup_time → first-frame epoch − manifest-fetch epoch
- manifest.fetch_failures → 5xx on
*.m3u8/*.mpdper-(CDN, geo) cell - manifest.fetch_latency →
Server-Timing: manifest;dur=…+ edge access log - segment.fetch_failures_by_cdn → per-CDN
x-cache,via, status - segment.fetch_latency_by_cdn →
Server-Timing: segment-fetch;dur=… - cdn.hit_ratio →
x-cache: HITvsx-cache: MISSratio on manifest path - origin.shield_health → shield latency p95 + shield bypass rate
- edge.error_rate_per_pop → 5xx / total per
x-served-byPOP - edge.egress_health → per-POP bandwidth ratio + queue depth
The player event timeline is the QoE substrate: the rebuffer, startup, and segmentFetch events carry the per-segment CDN POP identity. That's where the rebuffer + startup signal originates; the agent reads the timeline once per session and feeds it into qoe.rebuffer_ratio and qoe.video_startup_time.
The edge access log is the CDN-side ground truth that joins player events back to POP identity. Per-segment status + x-cache + x-served-by form the population that segment,cdn.hit_ratio, and edge.error_rate_per_pop read from. The seg1238 404 chain in the sample is exactly the partial-consistent failure pattern those probes fail on.
Manifest metrics from the CDN provider's push are partial on multi-CDN because they aggregate across POPs. Per-(CDN, geo) is exposed by the vendor; per-(CDN, POP, geo) is not. Aggregate-greens can hide a red POP inside a green geo — which is why manifest.fetch_failures needs the per-cell clause to flip to fail when any cell moves past threshold.
The agent's ranked root cause + recommended fix is the bridge. The verdict ranks hypotheses by probability, picks a recommended fix, and emits a rebalance hint to keep streaming green while the fix is shipped. Without the bridge, the page renders numbers but on-call has to do the ranking manually — and on-call has to do it on the same partial-consistent telemetry that hid the failing cell to begin with.
The QoE-observability surfacing has too much volume for alert-only — too many POPs, too many geo cohorts, too many concurrent metrics. Streamwake ranks hypotheses + emits a rebalance + a recommended fix, so streaming stays green while engineering works the issue. Most failures are remediated before the on-call engineer finishes reading the timeline.
Ten ways observability leaves gaps
Each row: the symptom the player reports → the underlying cause → a fix that holds under the next probe cycle → the rebalance hint the Streamwake agent emits. The rebalance hint is the part the agent does on its own — it routes traffic around the failing cell while engineering works the actual fix.
Dashboards read rebuffer_ratio 0.0% over the rolling-7-day window, but users report clear stalls in session cohorts that the dashboard rolls up. Vendor QoE looks green; users disagree.
QoE reporting is feature-flagged off for that session cohort — A/B partition has a fallback to no-emit, so the rebuffer events never reach the analytics pipeline. The cohort missed the QoE source flag.
Surface the rebuffer signal from player.events.rebuffer directly into the QoE probe pipeline and stop trusting the vendor's aggregate. The qoe.rebuffer_ratio probe reads 0.018 on the player-side cohort that the vendor reports 0.0% on; cross-check both on every cycle until they converge.
Agent detects missing rebuffer via cross-check vs segment_fetch_latency outliers and emits rebalance_hints=[pin_qoe_source_to_event_pipeline]. The next probe stitches player.events to qoe.rebuffer_ratio on the same window — rebuffer_ratio flips to 1.8% and the failing cell is named.
p75 video-startup time climbs +18% over the last 7 days. The SLA board stays green throughout because its alerting window is rolling-24h, which dilutes the cohort trend.
A CDN cache-control change deployed 7 days ago widened manifest fetch latency from p95=214ms to p95=287ms, dragging startup time with it. The rolling-24h aggregate washes the change out; the rolling-7-day trend is the one that surfaces it.
Re-time the SLA alert on the p75 of the rolling-7-day cohort, not the rolling-24h aggregate. qoe.video_startup_time should be probed at agent cadence (30s) and the SLA alert should fire when p75 over the cohort hops more than ~10% week on week.
Agent surfaces the cohort trend, runs manifest.fetch_latency upstream to find the delta, and emits rebalance_hints=[tighten_manifest_cache_ttl_until_rev_checked]. Streaming stays green while engineering re-times the SLA window and rolls back the cache-control change.
Global manifest_fetch_failures reads 0.04% (green) but a single geo cohort reads 1.8% (red). Viewers in that cohort see consistent manifest timeouts; aggregate-vendor dashboards stay silent.
CDN telemetry rollup aggregates per-provider, not per-(CDN, geo). The per-geo breakdown Stage is exposed by the vendor, but the per-POP cells are not; per-cell greens can hide a red POP inside a green geo.
Scope the manifest.fetch_failures probe per-(CDN, geo) cell and fail the probe when any cell reads >0.5% over the probe window. The qoe→manifest-fetch ladder must read the per-cell value, not the per-provider rollup.
Agent sees the partial-consistent cell mix on EMEA and emits rebalance_hints=[pin_manifest_to_healthy_geo_pop]. Pin next probes to cdn-B/eu-west for EMEA; the manifest path on cdn-A/fra02 gets capacity headroom while the failing POP is repaired.
Viewers in one (CDN, geo) cohort see partial stalls; aggregate CDN-segment-fetch-failure is 0.07% — under the global alerting threshold. The cohort barely moves the global rollup, but the cohort is fully degraded.
Outlier cells are diluted by healthy cells in the rolling-1-min aggregate. The aggregate reads green because nine healthy (CDN, geo) cells average out one failing cell; the failing cell never crosses the global threshold.
Floor the per-cell failure threshold above the cohort-average floor — segment.fetch_failures_by_cdn must read the per-(CDN, geo) cell, not the rolled-up CDN aggregate. The threshold should be in absolute terms (>= 0.5% per cell) not aggregate (>= 0.1% per CDN).
Agent scopes the failing (CDN, geo) cell and emits rebalance_hints=[route_segment_to_alternate_pop_for_failing_geo]. Pin segment fetches to cdn-B for the failing geo cohort; cdn-A gets isolated while on-call narrows the causal cell.
Dashboard surfaces cdn.hit_ratio below the 95% optimize threshold 4-5 minutes after user-visible rebuffer lifted. The dashboard sample window is wider than the user-impact window; the alert-by-dashboard is too late.
The hit-ratio dashboard aggregates over a 5-minute window while the live edge path is computed at a smaller cadence. The optimize-threshold alert kicks in only after the dashboard window closes.
Emit the cdn.hit_ratio probe at agent cadence (≤30s) and fail the probe below the optimize threshold (95%) within the probe window. The dashboard can stay coarse — the probe is the source of truth.
Agent pre-warms the edge cache on the next probe cycle and emits rebalance_hints=[prewarm_warm_window_for_manifest_path]. The next probe reads hit_ratio=96% — user-visible rebuffer recovered within a single probe interval, before the dashboard window closed.
Shield saturation burn but no shield fault log. The shield latency p95 climbed to 612ms and bypass rate is 11%, but the dashboard reads green because the gating metric watches only x-cache: ERROR.
The shield-error-rate metric gates on x-cache: ERROR responses — the reroute rule emits x-cache: BYPASS when it bypasses the shield (without an error). Saturation re-routes traffic but the metric never flips to fail because the status reads BYPASS, not ERROR.
Count bypass-without-fault as a shield-fail class in the probe. The origin.shield_health probe should read shield_latency_p95 + bypass_rate together — bypass-without-fault is a fail even when no ERROR has been emitted.
Agent detects the bypass-without-fault spike and emits rebalance_hints=[pin_edge_to_origin_direct_until_shield_recovers]. The next probe witnesses origin-direct serving green; shield backpressure releases; bypass rate drops back under 1% within two cycles.
A CDN provider's aggregate error rate reads 0.4% (green), but a single geo cohort reads 6.1% (red). Vendor dashboards aggregate over CDN provider, not per (provider, POP, geo) — the failing cell never reaches the dashboard.
Vendor reports aggregate CDN health, not per-cell. The aggregate-greens hide a red POP inside a green geo, and on-call never sees the failing cell because the alerting window rolls up to the CDN provider level.
Scope the edge.error_rate_per_pop probe to per-(provider, POP, geo) cell and tag the failing cell by its POP identity. Aggregate thresholds should be derived from the per-cell view, not the other way around.
Agent scopes the failing cell (cdn-A/fra02, EMEA) and emits rebalance_hints=[route_egress_to_alternate_pop_for_failing_geo]. Pin egress to cdn-B/eu-west for EMEA; the failing cell on cdn-A/fra02 gets capacity headroom and on-call is paged for the single cell, not the whole provider.
A burst of client errors reads 'MSE SourceBuffer init failure on first segment'. Init.mp4 hasn't been pre-warmed at the edge when the first segment lands; the player fetch races the CDN ingest.
Packager emits segment asynchronously from CDN ingest. The instrumentation on each side ends at the silo boundary — packager telemetry and CDN ingest telemetry lack a shared join, so the handoff wait is never timed.
Bridge the gap by registering a packager->cdn.handoff_wait probe that times segment-emit to edge-readable on the first cycle. The probe should fire when handoff wait exceeds 500ms — the budget the player's startup path assumes.
Agent bridges the packager/CDN telemetries and emits rebalance_hints=[prewarm_init_for_new_session]. Pin the next init probe to cdn-B for EMEA cohort; pre-warm the edge cache on init.mp4 so the player read races the CDN ingest correctly.
4% of sessions show +3s on video-startup time. License-first-byte latency correlates — but the regression banner reads video-startup time without naming the license-fetch path.
License proxy is a separate observability silo. Either-side metric alone misses the join — qoe.video_startup_time looks green except in the 4% cohort, and license.first_byte looks green except on assets with high keyserver latency.
Emit a probe that joins qoe.video_startup_time with license.first_byte per session and flags the correlation. The probe should fire when both metrics move in tandem above their per-session thresholds.
Agent surfaces the license-fetch path in the timeline and emits rebalance_hints=[pin_license_to_alternate_keyserver_pop]. Pin the next license probe to a keyserver POP with a healthy first-byte profile; video-startup time drops below budget within the next three cycles.
Aggregate egress healthy across CDN. One geo cohort shows egress queue depth climbing and 5xx climbing within the same cohort. Aggregate vendor dashboards read green; the cohort is fully degraded.
Vendor reports aggregate CDN health, not per-cell. The aggregate reads green because nine healthy (CDN, geo) cells average out one failing cell; the failing cell never reaches the alerting threshold at aggregate.
Surface edge.egress_health as a per-cell (CDN, geo) probe and tag the failing cell by its POP identity. The bw_ratio and queue_depth metrics must be per-cell, not aggregate; the threshold should fire when per-cell bw_ratio >= 0.85 or queue_depth grows 3x in 5 minutes.
Agent scopes the failing cell (cdn-A/fra02) and emits rebalance_hints=[route_egress_to_alternate_pop_until_egress_recovers]. Pin egress to cdn-B/eu-west for EMEA; cdn-A/fra02 gets capacity headroom while on-call narrows the causal cell.
Diagnose with Streamwake
Register the OBSERVABILITY probe against POST /api/v1/streams, then read the agent timeline back through GET /api/v1/agents. The probe verdicts in the timeline are exactly the rows above — rebuffer, startup, manifest, segment, hit ratio, origin shield, edge error rate. The agent.ranked_root_cause and agent.recommended_fix blocks name the decision the agent emitted on every refresh.
The curl below registers a live source URL with a 30-second probe cadence. The OBSERVABILITY protocol routing makes the agent run all five probe families on every refresh — and the agent timeline surfaces each verdict plus a ranked root cause plus a recommended fix plus the rebalance hints the agent emits when a probe fails.
The cookie is the same better-auth.session_token that gates every /api/v1/* call — see the auth guide for how to mint one.
curl -X POST https://streamwake.polsia.io/api/v1/streams \
-H "content-type: application/json" \
-b "better-auth.session_token=<your-session-cookie>" \
-d '{
"sourceUrl": "https://cdn.example.com/live/event/manifest.m3u8",
"protocol": "OBSERVABILITY",
"probeIntervalSeconds": 30,
"agents": [
"qoe.rebuffer_ratio",
"qoe.video_startup_time",
"manifest.fetch_failures",
"manifest.fetch_latency",
"segment.fetch_failures_by_cdn",
"segment.fetch_latency_by_cdn",
"cdn.hit_ratio",
"origin.shield_health",
"edge.error_rate_per_pop",
"edge.egress_health"
]
}'curl https://streamwake.polsia.io/api/v1/agents?stream_id=<id> \
-b "better-auth.session_token=<your-session-cookie>"{
"stream_id": "ckobsstreaming-7421",
"source": "https://cdn.example.com/live/event/manifest.m3u8",
"protocol": "OBSERVABILITY",
"checked_at": "2026-08-06T10:42:20.014Z",
"checks": [
{ "probe": "qoe.rebuffer_ratio", "result": "fail", "value": 0.018, "threshold": 0.005, "detail": "session rebuffer_ratio=1.8% > 0.5% warn — burst of rebuffer events tied to cdn-A/fra02 segment 404 chain" },
{ "probe": "qoe.video_startup_time", "result": "pass", "value_ms": 545, "threshold_ms": 2500, "detail": "first-frame under 2500ms budget — manifest-to-first-frame reads 545ms p75" },
{ "probe": "manifest.fetch_failures", "result": "pass", "value": 0.00043, "threshold": 0.001, "detail": "global manifest_fetch_failures 0.043% under 0.1% threshold even though EMEA cell reads 1.8%" },
{ "probe": "manifest.fetch_latency", "result": "warn", "value_ms": 287, "threshold_ms": 250, "detail": "p95 fetch latency 287ms > 250ms budget on cdn-A/fra02 — manifest cache-control widened 7 days ago" },
{ "probe": "segment.fetch_failures_by_cdn", "result": "fail", "per_cdn": { "cdn-A": { "fail_ratio": 0.0049, "outlier_pop": "cdn-A/fra02" }, "cdn-B": { "fail_ratio": 0.0003 } }, "detail": "cdn-A/fra02 segment-fetch fail_ratio 0.49% vs healthy 0.03% on cdn-B — partial-consistent cell mix" },
{ "probe": "segment.fetch_latency_by_cdn", "result": "warn", "per_cdn": { "cdn-A/fra02": { "p95_ms": 412 }, "cdn-B/eu-west": { "p95_ms": 79 } }, "detail": "cdn-A/fra02 segment p95 latency 412ms — cdn-B/eu-west reads 79ms healthy" },
{ "probe": "cdn.hit_ratio", "result": "warn", "value": 0.91, "threshold": 0.95, "detail": "hit_ratio 91% under 95% optimize threshold — cdn-A/fra02 served MISS on every probe" },
{ "probe": "origin.shield_health", "result": "fail", "shield_latency_p95_ms": 612, "bypass_rate": 0.11, "bypass_without_fault": true, "detail": "shield bypass rate 11% — bypass-without-fault spike means reroute rule mis-configured" },
{ "probe": "edge.error_rate_per_pop", "result": "fail", "per_cell": { "cdn-A/fra02": 0.061, "cdn-A/eu-west": 0.004, "cdn-B/eu-west": 0.001 }, "detail": "cdn-A/fra02 error rate 6.1% vs healthy < 0.5% — cell mix partial-consistent" },
{ "probe": "edge.egress_health", "result": "warn", "per_cell": { "cdn-A/fra02": { "bw_ratio": 0.92, "queue_depth": 412 } }, "detail": "cdn-A/fra02 egress bandwidth 92% ceiling; queue depth climbing" }
],
"ranked_root_cause": [
{ "rank": 1, "hypothesis": "cdn-A/fra02 segment-fetch failures cluster", "probability": 0.62 },
{ "rank": 2, "hypothesis": "manifest cache-control widened 2026-07-30 deploy", "probability": 0.27 },
{ "rank": 3, "hypothesis": "encoder/packager handoff drift on packager->cdn edge", "probability": 0.11 }
],
"recommended_fix": {
"title": "Pin segment fetches to cdn-B for EMEA; tighten manifest cache-control to max-age=1",
"verification": "rebuffer_ratio < 0.005 and segment_fetch_p95_ms < 120 over the next 5 probe cycles"
},
"agent_rebalance_hints": [
"pin_segment_to_cdn_B_for_geo_EMEA",
"tighten_manifest_cache_control_to_1s",
"reroute_shield_bypass_to_origin_direct"
],
"summary": {
"first_failing_probe": "qoe.rebuffer_ratio",
"ranked_root_cause_top": "cdn-A/fra02 segment-fetch failures cluster",
"estimated_viewer_impact_pct": 14.2,
"fix_window_minutes": 8
}
}Want Streamwake to catch this on its own?
Sign up, register an OBSERVABILITY probe, and the same rebuffer / startup / manifest / segment / hit-ratio / shield / edge-error-rate probes that produced the timeline above run on every refresh — and surface in a Slack channel, a webhook, or the streams dashboard.
- Pick a recent on-call incident — manifest stall, edge miss, player-side stall, or peer congestion.
- We replay it through the same reliability-agent probe cascade used on the postmortem above.
- You walk away with a written what-could-have-been-Automated readout, not a sales deck.
Read the next protocol guide
The other live entries cover the canonical protocol-by-protocol failure patterns.