OBSERVERTC / Client Monitor

Metrics & missing values

Distinguish browser measurements, adapted stats and calculated values.

Metrics help you explain what a client is sending, receiving, and playing. Start with the overview measurements, then follow the affected track or stream when something looks wrong. A missing measurement means the browser or current interval did not supply enough evidence; it does not mean the value is zero.

When interpreting a value, distinguish its origin: browser stat, adapted/inferred stat, derived measurement, declared context, detector/score result, identity/lifecycle metadata. Application-defined measurements belong to application extensions.

Monitor ownership

The authoritative raw-stat families and exact field sets are in the schema inventory. Their monitor ownership is:

MonitorBrowser report / contentWire location under a PeerConnectionSample
InboundRtpMonitorinbound-rtp: received/lost packets, jitter, received bytes, frame decode/render/freeze counters, jitter buffer, concealment, FEC/RTX, corruptioninboundRtps[]
OutboundRtpMonitoroutbound-rtp: sent packets/bytes, encoded frames, target bitrate, encode cost, QP, limitation durations, retransmission and feedbackoutboundRtps[]
RemoteInboundRtpMonitorremote-inbound-rtp: receiver-reported loss/jitter/RTT for local outbound streamremoteInboundRtps[]
RemoteOutboundRtpMonitorremote-outbound-rtp: sender reports for a local inbound streamremoteOutboundRtps[]
CodecMonitorcodec identifiers, MIME type, clock rate, channels, FMTP, transport linkcodecs[]
MediaSourceMonitormedia-source capture geometry/fps/frames and audio energy/durationmediaSources[]
MediaPlayoutMonitormedia-playout synthesized audio duration/events, playout delay, sample totalsmediaPlayouts[]
IceTransportMonitortransport: packets/bytes, ICE/DTLS state/roles, pair/certificate references, negotiated cryptoiceTransports[]
IceCandidateMonitorlocal-candidate/remote-candidate addresses, ports, type, protocol, relay metadataiceCandidates[]
IceCandidatePairMonitorcandidate-pair states, nomination, connectivity checks, RTT, throughput/BWE, endpoint refsiceCandidatePairs[]
CertificateMonitorcertificate fingerprint/algorithm/base64 and issuer referencecertificates[]
DataChannelMonitordata-channel state, label, protocol, identifier, messages/bytesdataChannels[]
PeerConnectionTransportMonitorpeer-connection report: dataChannelsOpened/ClosedpeerConnectionTransports[]
InboundTrackMonitor / OutboundTrackMonitorMediaStreamTrack binding, context, aggregate/derived values; no matching browser track report assumedinboundTracks[] / outboundTracks[], limited projection
PeerConnectionMonitorgraph owner and aggregate, not the browser’s peer-connection reportPC envelope
ClientMonitorcross-PC aggregates, events, issues, extensions and scoreClientSample root
ExtensionStatsMonitorapplication-defined payload, identity and freshnessextensionStats[] at root
SelectedIcePathhistory/view over selected ICE tuple and its evidenceno dedicated sample record
Source references

The full field inventory is intentionally not reduced to the above examples.

Core formulas

Let Δx = positiveDelta(current, previous), dtMs = current.timestamp − previous.timestamp, dt = dtMs/1000. positiveDelta returns undefined, not zero, on missing inputs or a backwards counter. Each consumer may handle that differently; some explicitly coalesce to zero. Read the formula appendix for every assignment and its guards.

ValueImplemented calculation / scope
RTP bitratemax(0, Δbytes × 8 / dt)
RTP packet rateΔpackets / dt
Outbound payloadBitratemax(0, (ΔbytesSent − ΔheaderBytesSent − (ΔretransmittedBytesSent ?? 0)) × 8 / dt); this is the implementation even if the field name invites a different byte accounting assumption
Inbound deltaFractionLostΔlost/(Δlost+Δreceived) only when both deltas are present and both are positive; otherwise zero in that guarded block. This means all-loss/zero-received is not represented as 1 by this expression
Remote inbound deltaFractionLostΔlost/(Δlost+Δreceived) when denominator >0, else 0; no-new-RTCP-report resets interval readings
BitPerPixelbitrate/(width × height × framesPerSecond) with truthy geometry/fps/bitrate gates
Inbound avgFramesPerSecmean of last at most 10 truthy browser FPS readings
fpsVolatilitymean absolute deviation of those FPS readings divided by their mean; deprecated
ewmaFpsexisting EWMA ×0.9 + new FPS ×0.1; first/truthy-state handling matters
interFrameDelayVariationsqrt(max(0, ΔsquaredGap/N − (Δgap/N)²)) / (Δgap/N), N=ΔframesDecoded, N>1
inventedSpeechRatiomax(0, ΔconcealedSamples − (ΔsilentConcealedSamples ?? 0)) / ΔtotalSamplesReceived
timeStretchRate((ΔinsertedSamplesForDeceleration ?? 0)+(ΔremovedSamplesForAcceleration ?? 0))/ΔtotalSamplesReceived; a fraction, not per-second rate
concealmentEventRateΔconcealmentEvents/dt
discardRateΔpacketsDiscarded/(ΔpacketsDiscarded+(ΔpacketsReceived ?? 0)); zero if consumed count is zero
avgJitterBufferDelayInMs1000 × ΔjitterBufferDelay / ΔjitterBufferEmittedCount
jitterBufferTargetDelayInMsanalogous target-delay delta / emitted count ×1000
decodeTimePerFrameInMs1000 × ΔtotalDecodeTime / ΔframesDecoded
avgEncodeTimePerFrameInMs1000 × ΔtotalEncodeTime / ΔframesEncoded
avgQpPerFrameΔqpSum divided by decoded/encoded frames, depending on direction
normalizedQpinbound average QP divided by codec scale from qpScaleOf, clamped 0..1; unknown codec => undefined
droppedFrameRatio / renderRatioΔframesDropped/ΔframesReceived; ΔframesRendered/ΔframesDecoded
frozenTimeRatio / pausedTimeRatioΔtotalFreezesDuration/dt; ΔtotalPausesDuration/dt
keyFrameRate, PLI/FIR/NACK ratescorresponding counter delta / dt
retransmissionRatioretransmitted byte delta / total byte delta, capped at 1; zero/undefined behavior differs by direction
avgPacketSendDelayInMs1000 × ΔtotalPacketSendDelay/ΔpacketsSent
qualityLimitationDurationSharesnonnegative deltas of none/cpu/bandwidth/other divided by sum of those deltas; undefined for no progress
source producedFpsnonnegative Δsource.frames/dt
source rmsAudioLevelsqrt(ΔtotalAudioEnergy/ΔtotalSamplesDuration) when duration >0
playoutDelayPerSampleInMs1000 × ΔtotalPlayoutDelay/ΔtotalSamplesCount
synthesizedSamplesRatioΔsynthesizedSamplesDuration/ΔtotalSamplesDuration, with explicit fallback behavior in MediaPlayoutMonitor
ICE mean RTTΔtotalRoundTripTime/ΔresponsesReceived
RTCP mean RTTΔtotalRoundTripTime/ΔroundTripTimeMeasurements
Source references

Aggregates and transport stability

PC sums distinguish audio/video/data-channel directions. Quality-loss averages include streams that actually carried relevant packets; missing measurement differs from zero loss. RTT paths are kept separate (rtcpRttInSec, iceRttInSec), with current RTT preferring RTCP and falling back to ICE. Root avgRttInSec averages PC values and returns -1 with no PCs. PC legacy loss sums and newer per-stream means are not interchangeable.

Source references

transportStability is the normalized MOS-like value produced by utils/transportStability.ts: effective latency is RTT/2 + 2×jitter +10 ms; a piecewise latency impairment and 2.5×loss-percent reduce R; a cubic maps clamped R to MOS; normalized against this model’s best/worst MOS. It requires all three measurements. It is not a direct measurement of user satisfaction or a selected-pair-switch counter. client-monitor-js/src/utils/transportStability.ts.

Missing values and resets

There is no single universal nullification pass. The current code uses several distinct mechanisms:

  • Missing raw inputs: InboundRtpMonitor explicitly replaces known fields, including undefined; several other monitors use Object.assign and can retain omitted properties. Derived assignments are conditional, so some previous derived values can remain. Do not promise universal freshness without checking the owner.
  • Backwards counters: positiveDelta returns undefined; MediaPlayoutMonitor explicitly converts some such deltas to zero. Remote RTCP repeated/stale timestamps clear interval readings. Inbound repeated timestamps replace raw fields but return before recomputing derived values; outbound returns earlier.
  • Shared windows: SlicedWindow uses null for unavailable totals, rejects repeated timestamps, resets across excessive gaps and requires full slices. N samples span N−1 intervals. Detection/recovery are different slices; inspect their configured offsets rather than assuming a trailing duration in milliseconds.
  • Paused/ended/background/muted tracks: individual detectors stand down according to their own guards. There is no blanket rule that every detector handles every gate identically. Missing-input branches can preserve an open issue while publishing inputsUnavailable, whereas some detectors resolve it.
  • Zero score: full-scale deductions clamp the affected component to zero. Connectivity issues are deliberately not charged again on the PC; dry-track issues can zero media components. There is no global “any network fault nullifies every score” rule.
  • Aggregate absent dimensions: undefined/null dimensions are omitted from client RMSE; no measurable dimensions causes calculator early return, leaving the root’s previous/default score, which starts at 5. This differs from documentation saying the root necessarily becomes undefined.
  • Wire absence: undefined properties disappear under JSON serialization; Avro nullable fields and generated TS optional fields are separate representations. On-change ICE omissions must be retained by the receiver, but current observer code does not retain them (known limitations).
Source references

Browse the monitor catalog →

Version coverage varies by reference. Source revisions · Markdown source