Reading chrome://webrtc-internals Dumps
chrome://webrtc-internals is the fastest path from βthe call failedβ to βICE never nominated a pair.β This guide is part of the Cross-Browser WebRTC Debugging guide, and it walks through capturing a dump, reading the live getStats timeline graphs, interpreting the candidate-pair, ICE, and DTLS event log, and exporting the JSON so a teammate can reproduce your analysis without your machine.
Context & Trade-offs
The page records only peer connections created after it loads, so opening it mid-failure gives you nothing β open it in a second tab before you start the call. Once attached, it samples getStats() roughly once per second and renders every numeric stat as a live graph, which is enough resolution to see a candidate pairβs round-trip time climb or RTP throughput collapse within a few seconds.
The trade-off versus pure getStats() polling in your app is fidelity versus portability: the dump captures the full API call log (every createOffer, addIceCandidate, setRemoteDescription) with timestamps that your application logs usually lack, but it exists only in Chrome. Use it for Chrome triage and a programmatic stats pipeline for cross-engine parity. The dump is also large β a two-minute call can produce several megabytes of JSON β so capture deliberately rather than leaving it running.
Minimal Runnable Implementation
You cannot script the dashboard, but you can emit a matching trace from your app so its 1-second samples line up with the graphs, which makes the dump far easier to read.
// Emit app-side markers that align with webrtc-internals' ~1 s sampling cadence.
// Reading the dump next to these logs lets you map a graph spike to a code event.
function attachInternalsAlignedLogging(pc) {
// Log every signalling/ICE transition the dump's event list also records.
pc.addEventListener('iceconnectionstatechange',
() => console.log('[ice]', Date.now(), pc.iceConnectionState));
pc.addEventListener('connectionstatechange',
() => console.log('[conn]', Date.now(), pc.connectionState));
setInterval(async () => {
const report = await pc.getStats();
for (const s of report.values()) {
if (s.type === 'candidate-pair' && s.nominated) {
// These three values are the headline graphs in the dump.
console.log('[pair]', Date.now(), {
state: s.state, // 'succeeded' once nominated
rtt: s.currentRoundTripTime, // matches the RTT graph (seconds)
bytesSent: s.bytesSent // matches the throughput graph
});
}
}
}, 1000);
}
Reproduction Steps & Debugging Log Patterns
- Open
chrome://webrtc-internalsin a new tab, then start your call in the original tab. The connection appears as a collapsible block keyed by page URL and a numeric id. - Expand the block. The top section is the API event log β a chronological list of method calls. A healthy negotiation reads
createOffer β setLocalDescription β setRemoteDescription (answer) β addIceCandidate (ΓN). A gap here is a signalling fault, not a network one. - Scroll to the stat graphs. Find the
candidate-pairgroup and watchstate: it should movein-progress β succeeded. ThecurrentRoundTripTimegraph should settle to a flat line; a pair stuckin-progresswith no RTT means connectivity checks are failing. - Inspect the transport group for DTLS.
dtlsStateshould reachconnected; if ICE succeeded but DTLS staysconnecting, you have a certificate or handshake problem, not a NAT one. - Click Create Dump at the top of the page and save the JSON for export.
Expected healthy console output alongside the graphs:
// [ice] 1718900000123 checking
// [ice] 1718900000540 connected // ICE nominated a pair
// [conn] 1718900000901 connected // DTLS + ICE both up
// [pair] 1718900001002 { state: 'succeeded', rtt: 0.032, bytesSent: 48210 }
A failing session instead shows iceConnectionState looping checking β disconnected while every candidate-pair graph reports state: 'failed' and currentRoundTripTime never appears. Cross-check that pattern against your ICE Candidate Gathering & Filtering policy β a filter dropping srflx candidates leaves only host pairs that cannot traverse NAT.
Common Implementation Mistakes
- Opening the page after the failure. It will not attach to an existing connection, so you capture an empty block. Open it first, every time.
- Reading throughput graphs without the event log. A flat
bytesSentgraph means nothing until you confirm from the event log thatsetRemoteDescriptionwas applied β otherwise you are debugging media when the fault is signalling. - Mistaking mDNS host candidates for a bug. Host candidates show
.localnames instead of IPs by default; this is expected obfuscation, not a missing address. - Closing the tab before exporting. The live graphs vanish on tab close. Click Create Dump before navigating away.
- Ignoring DTLS state. Engineers fixate on ICE and miss that
dtlsStatestalled atconnecting, which is a handshake/certificate issue with its own fix path.
FAQ
The dump is huge β can I trim it before sharing?
The exported JSON is plain text keyed by connection and stat type. Keep the connection block that failed and delete the others; the importer (drag the JSON back onto a fresh chrome://webrtc-internals page) reads partial files fine.
Why do the graphs stop updating mid-call?
Either the peer connection was closed (the event log shows close) or the tab lost focus and Chrome throttled timers. Confirm against your appβs own 1-second getStats log, which keeps running.
Related: this deep-dive sits under Cross-Browser WebRTC Debugging; compare it with diagnosing ICE failures with Firefox about:webrtc and debugging SDP m-line mismatches.