Diagnostics

mattebox@0.14.0

This chapter covers stats, the trace, and replaying a trace.

Stats

engine.stats.throughput; // measured throughput, bits per second
engine.stats.snapshot();  // the full kernel state, read-only
engine.stats.trace();     // the entries kept, oldest first, none unless traceCapacity asks

The snapshot holds the presentation, the buffers, the scheduling state, the active tracks, the quality state, and the live window.

QoE metrics

The qoe stage measures the viewer’s experience and keeps it in the page. Nothing is sent anywhere. It is in full; add it to another preset with stages.

import qoe from 'mattebox/stages/qoe';

const engine = hls({ stages: [qoe()] });
engine.on('qoe:metrics', ({ reason, startupTime, rebuffers, rebufferDuration, switches }) => {
  analytics.record(reason, { startupTime, rebuffers, rebufferDuration, switches });
});
Field Meaning
startupTime Seconds from the load, or the first play request if later, to the first frame playing
rebuffers Times playback waited for data after startup, seeks excluded
rebufferDuration Seconds spent waiting, the current wait included
switches Video quality changes after the first selection

qoe:metrics fires with reason set to 'startup', 'rebuffer', or 'switch' each time a figure changes. engine.qoe reads the current figures. A new load starts them over.

The trace

Every message the engine handles, and the effects it produced, is a trace entry: a timestamp, the message, the effects, and a digest of the state after it. A byte payload is recorded as its length, { $bytes: n }, never the bytes. The engine keeps none of them. It hands each one to trace listeners as it happens, so whoever wants a history keeps their own.

engine.on('trace', (entry) => {
  history.push(entry);
  if (history.length > 500) history.shift();
});

A page that wants the engine to keep the ring sets traceCapacity. Then engine.stats.trace() answers with the entries, oldest first, and engine.error carries them for the last fatal error. A stall on a viewer’s TV comes with the exact sequence that led to it.

import { exportTrace, mattebox } from 'mattebox';

const engine = mattebox({ stages, config: { traceCapacity: 500 } });

engine.on('error', (error) => {
  if (!error.fatal) return;
  send({ error, trace: exportTrace(engine.stats.trace()) });
});

Replay

The reducer is pure, so a trace can be replayed offline. Feed the recorded messages into a fresh reducer and check that the same effects come out. If not, the result points at the first entry where the current code disagrees.

import { createReducer, initialState, replay } from 'mattebox';

const result = replay(entries, createReducer([]), initialState({}));
if (!result.ok) {
  console.log('diverged at', result.divergedAt, result.expected, result.actual);
}

Build the reducer with the same stage slices as the engine that recorded the trace. Slices see accepted commands only; a trace recorded before that rule, holding a command the kernel rejected, diverges on replay at that entry.

The playground

The engine playground: a video, its transport, and tabs for the stream, quality and diagnostics.
The hosted playground.

The hosted playground shows all of this live: the trace, the buffered ranges per rendition, the quality constraints, and a toggle per stage. Paste a manifest URL to try your own stream.

Example

Log recovery actions and throughput, and export the trace on demand.

import { exportTrace, mattebox } from 'mattebox';
import hlsCmaf from 'mattebox/protocols/hls-cmaf';
import abr from 'mattebox/stages/abr';
import recovery from 'mattebox/stages/recovery';

const engine = mattebox({ stages: [hlsCmaf(), abr(), recovery()], config: { traceCapacity: 2000 } });

for (const event of ['recovery:excluded', 'recovery:nudge', 'recovery:flush', 'recovery:skip']) {
  engine.on(event, (payload) => console.log(event, payload));
}

setInterval(() => {
  bandwidth.textContent = `${(engine.stats.throughput / 1_000_000).toFixed(1)} Mbit/s`;
}, 1000);

exportButton.onclick = () => {
  const blob = new Blob([exportTrace(engine.stats.trace())], { type: 'application/json' });
  window.open(URL.createObjectURL(blob));
};

Next: 13 Builds and targets.

Read this page's source on GitHub