Stats API 与监控:getStats() 接口详解、关键指标采集

做实时通信,最怕什么?

不是代码写不出来,而是线上出了问题,你两眼一抹黑。用户说「卡」,你说「我看看」——结果啥也看不到。这就是没有监控的后果。

我个人习惯,在项目初期就把 Stats API 集成进去。别等到用户投诉了再补,那时候就晚了。

getStats() 接口:你的千里眼

WebRTC 的 Stats API,说白了就是一个接口,让你能拿到 PeerConnection 内部的各种统计数据。它不是什么黑魔法,就是浏览器给你开的一扇窗。

调用方式很简单:

const pc = new RTCPeerConnection(configuration);

// 获取所有统计信息
const stats = await pc.getStats();

// 或者指定 track 获取
const senderStats = await pc.getStats(sender);

返回的是一个 Map,key 是字符串 ID,value 是 RTCStats 对象。每个对象都有 type 字段,告诉你这是啥类型的统计。

核心类型一览:

  • inbound-rtp — 接收端 RTP 统计
  • outbound-rtp — 发送端 RTP 统计
  • candidate-pair — ICE 连接质量
  • remote-inbound-rtp — 远端接收情况
  • track — 音视频轨道的状态

嗯,这里要注意:不同浏览器返回的字段名可能略有差异。我在项目中踩过这个坑,Chrome 和 Safari 的字段命名就不完全一样。所以写代码时最好做一层兼容处理。

关键指标采集:丢包率、抖动、RTT

这三个指标,是衡量 WebRTC 通话质量的「三驾马车」。你想想看,用户说卡,无非就是这三个出了问题。

丢包率(Packet Loss Rate)

丢包率怎么算?不是直接拿到的,得自己算。

// 从 inbound-rtp 中获取
const inboundStats = stats.get(/* inbound-rtp 的 id */);
const packetsLost = inboundStats.packetsLost;
const packetsReceived = inboundStats.packetsReceived;
const totalPackets = packetsLost + packetsReceived;
const lossRate = totalPackets > 0 ? packetsLost / totalPackets : 0;

丢包率超过 5%,用户就能感知到卡顿。超过 10%,基本就没法正常通话了。

我的经验:丢包率要分方向看。上行丢包和下行丢包,原因完全不同。上行丢包通常是发送端网络问题,下行丢包可能是接收端带宽不足。我曾经遇到一个案例,用户一直抱怨听不到声音,结果发现是上行丢包率高达 30%,而他自己完全不知道。

抖动(Jitter)

抖动,就是网络延迟的变化程度。你想想看,如果每个包到达的时间都一样,那很完美。但现实是,有的包快,有的包慢,这就产生了抖动。

// 直接从 inbound-rtp 获取
const jitter = inboundStats.jitter; // 单位:秒

// 注意:Chrome 返回的是秒,Safari 返回的是毫秒
// 建议统一转换为毫秒处理
const jitterMs = jitter * 1000;

抖动值一般要求在 30ms 以下。超过 50ms,用户就会感觉到声音忽快忽慢。

避坑指南:我曾经在集成时发现抖动值忽高忽低,排查了半天,结果是采样周期太短。getStats() 的采样间隔建议至少 1 秒,太频繁反而会引入噪声。

RTT(往返时延)

RTT 就是数据从发送到收到确认的时间。它直接影响通话的实时性。

// 从 candidate-pair 获取
const candidatePair = stats.get(/* candidate-pair 的 id */);
const rtt = candidatePair.currentRoundTripTime; // 单位:秒

// 或者从 remote-inbound-rtp 获取
const remoteStats = stats.get(/* remote-inbound-rtp 的 id */);
const rttFromRemote = remoteStats.roundTripTime;

RTT 在 100ms 以内算优秀,200ms 以内可接受,超过 300ms 就会有明显的延迟感。

指标 优秀 良好 较差
丢包率 < 1% 1% - 5% > 5%
抖动 < 20ms 20ms - 50ms > 50ms
RTT < 100ms 100ms - 200ms > 200ms

监控系统集成:把数据用起来

光拿到数据没用,得把数据送到监控系统里,才能发挥价值。

我一般会这样做:

  1. 定时采集:每隔 1-2 秒调用一次 getStats()
  2. 数据聚合:计算平均值、最大值、最小值
  3. 上报服务端:通过 WebSocket 或 HTTP 发送到后端
  4. 可视化展示:在 Grafana 或自建看板上展示
// 一个简单的采集循环
async function collectStats(pc, callback) {
  const stats = await pc.getStats();
  const report = {};

  stats.forEach(stat => {
    if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
      report.videoInbound = {
        packetsLost: stat.packetsLost,
        packetsReceived: stat.packetsReceived,
        jitter: stat.jitter,
        timestamp: stat.timestamp
      };
    }
    // 其他类型同理...
  });

  callback(report);
  setTimeout(() => collectStats(pc, callback), 1000);
}

我的建议:不要把所有原始数据都上报。先做一次聚合,比如 10 秒内的平均值。这样既减少了网络开销,又能平滑掉瞬时波动。我曾经见过有人每秒上报一次,结果监控系统直接被数据淹没了。

知识体系总览

下面这张图,帮你理清 Stats API 的核心脉络:

Stats API 监控体系 getStats() 丢包率 packetsLost / total 抖动 jitter 字段 RTT currentRoundTripTime inbound-rtp 接收端统计 inbound-rtp 接收端统计 candidate-pair ICE 连接统计 监控系统集成(Grafana / 自建看板)

这张图把整个流程串起来了。从 getStats() 出发,拿到三个核心指标,再汇聚到监控系统。每一步都不复杂,但缺一不可。

总结一下:

  • getStats() 是 WebRTC 监控的入口,必须掌握
  • 丢包率、抖动、RTT 是三个核心指标,各有各的计算方式和阈值
  • 监控系统集成不是可选项,而是必选项
  • 数据采集要合理,别太频繁也别太稀疏

好了,这一章就到这里。记住一句话:没有监控的 WebRTC,就像闭着眼睛开车。你永远不知道下一秒会撞上什么。


公众号:蓝海资料掘金营,微信deep3321