作者:Jay Suresh Nirmal|Meazure Learning 高级软件工程师,专注分布式系统、实时通信与云原生架构
原文:https://hackernoon.com/six-webrtc-getstats-mistakes-to-avoid
一个出现故障的监控面板相对容易被发现。而一个数字看起来可信、实际却错误的监控面板,要危险得多。
这正是 WebRTC getStats() API 的陷阱之一。
在我从事实时通信系统的工作中,经常用 WebRTC 统计信息排查连接质量、恢复行为和生产可靠性问题。有一个反复出现的难题:某个指标可能被正确采集,却仍然被错误解读。
该 API 暴露了关于 RTP 流、丢包、抖动、候选帧对(candidate pairs)、编解码器、帧处理、往返时间等详细信息。但这些值的行为并不相同:有些是累计计数器,有些是瞬时测量值,有些依赖于远端上报的信息,有些会在被监控对象发生变化时消失。
如果忽略这些区别,计算结果依然可能看起来完全正常:码率曲线一直在动,丢包率接近零,抖动表现优异,什么都不会崩溃。
只是这些数字回答的问题,和你以为自己在问的问题,根本不是同一个。
在信任一套 WebRTC 监控管线之前,下面这六个错误值得先排查一遍。
1. 大多数值是计数器(Counters),不是速率(Rates)
像 bytesReceived、packetsReceived、framesDecoded、nackCount 这类值,在 WebRTC 统计模型中都是累计计数器。
因此,只读一次 bytesReceived 并不能告诉你当前码率,它只告诉你该统计对象累计收到了多少字节。
要计算速率,需要对比两次观测:
const bitrate =
((curr.bytesReceived - prev.bytesReceived) * 8) /
((curr.timestamp - prev.timestamp) / 1000);
减法本身很简单。容易出错的是它周围的细节。
第一,要使用统计对象自带的时间戳,而不是假设两次轮询调用之间的间隔就是测量间隔。轮询循环可能每秒钟执行一次,但这并不意味着两份报告所代表的底层测量正好相隔一秒。
第二,要确保 curr 和 prev 描述的是同一个统计对象。
这就引出了下一个问题。
2. 你正在测量的对象可能在底层发生变化
假设某套监控实现从每份报告中找到当前选中的 ICE 候选对,并在相邻两次观测之间计算差值。
在选中对不变的情况下这没问题,直到选中对发生切换。
例如,一次 ICE 重启(ICE restart)可能导致候选对对象消失、新对象出现。替代对象拥有自己的身份和计数器。WebRTC Stats 规范为被监控对象定义了稳定的标识符;对监控管线而言,关键是不能把替代对象当作前一个对象的延续。
如果监控代码把新对象当作旧对象的下一次观测来处理,相减计数器就可能产生离谱的结果,比如一个巨大的负差值。
请按统计对象的 id 进行匹配:
const prevStat = prevReport.get(curr.id);
if (!prevStat) {
// No baseline exists for this object yet.
// Store it and wait for another sample.
return null;
}
一个 stats ID 标识一个特定的被监控对象。这里的错误不在于对象的 ID 会随机变化,而是对象本身可能被替换。
当之前观测到的对象消失、或有新对象出现时,应把它当作一次对象切换(transition),而不是跨对象做插值。
3. 更频繁地调用 getStats() 并不保证拿到更新的测量值
人们很容易想当然地认为:getStats() 调用得越频繁,就能得到越细粒度的测量。
这个假设不安全。
WebRTC Stats 规范允许实现使用缓存或节流(throttling),并且没有给应用控制底层各项测量采样节奏的能力。因此,两次独立调用拿到的报告,其统计对象的时间戳可能根本没有前进。
如果你基于这样的两次观测计算速率,测量间隔可能是零:
const deltaMs = curr.timestamp - prev.timestamp;
if (deltaMs <= 0) {
return null;
}
没有这个防护,计算就可能出现除零错误。而改用轮询循环的墙钟间隔反而可能更糟,因为底层数据没变时,仍可能算出一个看起来可信的速率。
请根据你要测量的对象选择合适的轮询间隔,但不要把轮询频率当作”收到了新测量值”的证据。
检查时间戳。

4.jitter以秒为单位测量
这个很容易被忽略。
对于 inbound-rtp 统计,jitter 以秒为单位表示。在 WebRTC Stats 规范中,currentRoundTripTime 等相关的往返时间统计同样以秒为单位。
例如这个值:
jitter = 0.024
意味着 24 毫秒,而不是 0.024 毫秒。
请显式进行换算:
const jitterMs = stat.jitter * 1000;
const rttMs = pair.currentRoundTripTime * 1000;
单位错误在可观测性系统里尤其讨厌,因为算出来的值往往看起来并不像坏了。
一个显示抖动 0.024 ms 的面板看起来很惊艳。但它错了一千倍。
5. packetsLost 已签署,丢包差值需要解读
packetsLost 看起来像是那种应该从零开始、只会增加的计数器。
但事实没那么简单。
WebRTC 将 packetsLost 定义为有符号值,遵循 RTP 的累计丢包语义。RFC 3550 §6.4.1 将累计丢包定义为”预期收到的包数”与”实际收到的包数”之差。由于实际收到数可能包含重复包,累计丢包值可能为负。
也就是说:
const lostDelta =
curr.packetsLost - prev.packetsLost;
并不保证得到正值。
因此,负差值不应被自动当作损坏的遥测数据。
做区间计算时,你可能会从这样的代码起步:
const lostDelta =
curr.packetsLost - prev.packetsLost;
const recvDelta =
curr.packetsReceived - prev.packetsReceived;
接下来怎么做,取决于你想暴露的指标。
监控面板可以有意识地为了展示效果把区间丢包负值钳制到零。如果是这样,那应该是对该面板指标的显式定义,而不是默认”底层 WebRTC 统计一定出错了”。
这里还有另一个陷阱:用累计丢包除以累计接收数,得到的是一个会话级数值,它会随着会话变长而逐渐变得迟钝。
如果问题是”这段时间内发生了什么?”,应该改用区间差值来计算。
6. bytesReceived 不会自动显示”媒体码率”
bytesReceived 听起来像是媒体吞吐量曲线理所当然的输入。
但你需要搞清楚这些字节代表什么。
对于入站 RTP 统计,重传和前向纠错(FEC)流量也会计入报告所暴露的字节计数器中。WebRTC Stats 规范在相应的 RTP 统计旁边定义了 retransmittedBytesReceived 和 fecBytesReceived。
如果实现暴露了对应字段,你可以把它们分离出来:
const originalBytes =
stat.bytesReceived
- (stat.retransmittedBytesReceived || 0)
- (stat.fecBytesReceived || 0);
不要假设这些字段始终存在,所以要显式处理它们缺失的情况。
更重要的是:想清楚这个指标到底要回答什么问题。
如果问题是”总共收到了多少 RTP 流量(含恢复开销)”,把恢复开销算进去可能是合适的;如果问题是”原始媒体数据有多少”,那可能就不该算进去。
两种测量都可以有用。麻烦始于把两者都简单地叫做”码率”。
还有一个陷阱:缺失的远端统计不等于零
remote-inbound-rtp 很有用,因为它暴露了远端端点如何看待你正在发送的媒体。
这些测量产生于远端,并通过 RTCP 报告回传。因此,对应的远端统计并不一定在 PeerConnection 一建立连接时就存在。
监控代码常常把缺失值变成零,因为存零很方便:
const remoteLoss = remote?.packetsLost || 0;
在语义上,这可能是错的。
在相关的远端报告到达之前,这个值不是零,而是 “尚未可知” 。
这个区别在指标做聚合时尤为重要。把不可用的测量当作零,会让会话开始阶段显得人为地健康,也会扭曲跨大量会话的平均值。
在测量真正存在之前,请把”未知”作为一种状态保留下来。
六个错误之下的共同模式
这六个错误各不相同,但大多源于同一个假设:getStats() 字段的名字会告诉你该怎么用它。
它不会。
framesPerSecond 是瞬时值(gauge)。
framesDecoded 是累计的。
jitter 以秒为单位。
jitterBufferDelay 随时间累计,与 jitterBufferEmittedCount 结合解读时才成为有意义的平均延迟。
远端测量可能尚不存在。对象会随着连接演进而消失、被替换。
在把某个 WebRTC 统计量放上监控面板之前,先回答四个问题:
- 这个值是累计的、瞬时的,还是推导出来的?
- 它的单位是什么?
- 这个值属于哪个对象?我跨报告比较的是同一个对象吗?
- 这个值是否可能合法地不可用?
这些问题比字段名有用得多。
因为 WebRTC 监控最危险的失败,很少是一块满是 NaN 的面板——
而是一块满是合理数字、却在测量错误对象的面板。
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/71798.html