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

  1. Open chrome://webrtc-internals in 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.
  2. 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.
  3. Scroll to the stat graphs. Find the candidate-pair group and watch state: it should move in-progress β†’ succeeded. The currentRoundTripTime graph should settle to a flat line; a pair stuck in-progress with no RTT means connectivity checks are failing.
  4. Inspect the transport group for DTLS. dtlsState should reach connected; if ICE succeeded but DTLS stays connecting, you have a certificate or handshake problem, not a NAT one.
  5. 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

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.