19、统计与监控:getStats API详解、关键指标采集、WebRTC-internals工具使用

做WebRTC开发,最怕什么?

我最怕用户说「视频卡了」,然后我这边啥数据都没有。两眼一抹黑,根本不知道是网络抖了,还是编码器抽风了。

所以这一章,咱们聊聊怎么给WebRTC装上「仪表盘」。说白了,就是getStats API、关键指标采集,还有Chrome自带的那个神器——WebRTC-internals。

19.1 getStats API:WebRTC的OBD接口

getStats API 是浏览器暴露给我们的底层数据接口。你可以把它理解成汽车的OBD接口——插上去就能读到发动机转速、水温、油压。

在WebRTC里,这个接口能告诉你:

  • 发了多少包、收了多少包
  • 丢包率是多少
  • 延迟有多高
  • 码率稳不稳
  • 甚至CPU占用情况

调用方式很简单,但很多人用不对。我见过不少新手直接写:

peerConnection.getStats().then(stats => {
  stats.forEach(report => {
    console.log(report.type, report);
  });
});

嗯,这样能跑,但拿到的数据太原始了。你想想看,几十个report对象堆在一起,哪个是你要的?

我的习惯:先按 type 分类,再按 id 索引。这样结构清晰,查问题快得多。

19.2 关键指标:到底该盯哪些?

getStats 返回的数据少说几十项,但真正需要关注的,其实就这几个:

指标 类型 说明 健康阈值
packetsLost inbound-rtp / outbound-rtp 丢包数 < 总包数的2%
jitter inbound-rtp 抖动(毫秒) < 30ms
roundTripTime candidate-pair RTT 往返时间 < 200ms
framesPerSecond inbound-rtp / outbound-rtp 帧率 ≥ 15fps(视频通话)
bitrate (计算得出) 实际码率 接近目标码率

这里有个坑——bitrate 不是直接拿到的。getStats 返回的是 bytesSent 和 timestamp,你得自己算差值。

我曾经在项目里直接用了 bytesSent 的绝对值,结果画出来的码率曲线像心电图一样乱跳。后来才反应过来:要算 delta。

// 计算码率的正确姿势
let lastBytes = 0;
let lastTime = 0;

function calcBitrate(statsReport) {
  const now = statsReport.timestamp;
  const bytes = statsReport.bytesSent;
  if (lastTime === 0) {
    lastBytes = bytes;
    lastTime = now;
    return 0;
  }
  const deltaBytes = bytes - lastBytes;
  const deltaTime = (now - lastTime) / 1000; // 转成秒
  lastBytes = bytes;
  lastTime = now;
  return (deltaBytes * 8) / deltaTime; // bps
}
注意:getStats 的 timestamp 单位是毫秒,别直接当秒用。我刚开始就犯过这个错,算出来的码率大了1000倍。

19.3 采集策略:别太频繁,也别太懒

getStats 调用本身不重,但如果你每秒拉100次,浏览器也会累。我个人建议:

  • 常规监控:每1秒拉一次
  • 问题排查:遇到丢包或抖动时,临时提高到每200ms一次
  • 不要:在 oniceconnectionstatechange 等回调里直接调 getStats,容易造成回调风暴

我习惯用一个定时器,配合一个「降频」逻辑:

let pollInterval = 1000;
let timer = null;

function startStatsPoll(pc) {
  timer = setInterval(async () => {
    const stats = await pc.getStats();
    processStats(stats);
    // 如果发现丢包严重,自动提高采集频率
    if (isPacketLossHigh(stats)) {
      pollInterval = 200;
      resetTimer();
    } else {
      pollInterval = 1000;
    }
  }, pollInterval);
}

说白了,就是让采集频率跟着网络质量走。网络差的时候多看看,网络好的时候少折腾。

19.4 WebRTC-internals:Chrome自带的调试神器

你不需要写一行代码,就能看到所有WebRTC连接的内部状态。Chrome浏览器里输入:

chrome://webrtc-internals

打开之后,你会看到一个实时更新的仪表盘。这里面有:

  • 所有 PeerConnection 的完整状态
  • ICE 候选者列表和连接检查日志
  • RTP 流的收发统计
  • 甚至还有 SDP 的完整文本

我记得有一次线上反馈说「视频偶尔黑屏」,我本地死活复现不了。后来让用户打开 webrtc-internals,截图一看——inbound-rtp 的 framesDecoded 突然归零,但 packetsReceived 还在涨。这说明解码器崩了,跟网络没关系。直接定位到是 H.264 编码兼容性问题。

实用技巧:在 webrtc-internals 页面里,点击「Create Dump」可以导出所有数据的 JSON 文件。这个文件拿到本地,用脚本分析,比肉眼盯屏幕高效得多。

19.5 知识体系总览

下面这张图,是我自己梳理的统计监控知识结构。你可以把它当成一个检查清单:

WebRTC 统计与监控 getStats API 按 type 分类 按 id 索引 计算 delta 值 关键指标采集 丢包率 / 抖动 / RTT 帧率 / 码率 自适应采集频率 WebRTC-internals 实时仪表盘 ICE / RTP / SDP 导出 Dump 分析 三者结合:API 采集 → 指标分析 → 工具验证 采集频率建议:常规 1s,异常时 200ms 关键阈值:丢包 < 2% | 抖动 < 30ms | RTT < 200ms

19.6 避坑指南:我踩过的几个坑

做统计监控这几年,我踩过不少坑。挑几个典型的说说:

  • 坑一:getStats 在旧浏览器里是 callback 风格。 我一开始全用 Promise,结果在某个 Chrome 72 上直接报错。后来加了兼容判断。
  • 坑二:stats 对象的 id 不是固定的。 每次调用 getStats,某些对象的 id 会变。你不能用 id 做持久化缓存。
  • 坑三:别在 worker 线程里调 getStats。 PeerConnection 必须在主线程创建,getStats 也一样。我试过用 OffscreenCanvas 绕,不行。
  • 坑四:webrtc-internals 的数据是「最终一致性」的。 有时候你看到丢包率 50%,但过两秒就变成 5% 了。别慌,等几秒再看。
我的建议:在生产环境里,把关键指标(丢包率、RTT、帧率)每隔5秒上报一次到后端。这样出问题时,你有历史数据可以回溯。我曾经靠这个帮客户定位了一个「每隔23分钟卡顿一次」的奇葩问题——最后发现是运营商的路由器每23分钟做一次NAT表清理。

嗯,统计监控这块,说白了就是给WebRTC装上「黑匣子」。平时不显眼,但出了问题,它就是你的救命稻草。别等到用户投诉了才想起来加,那时候就晚了。

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