避免 WebRTC getStats 的六个常见错误

作者: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)

像 bytesReceivedpacketsReceivedframesDecodednackCount 这类值,在 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;
}

没有这个防护,计算就可能出现除零错误。而改用轮询循环的墙钟间隔反而可能更糟,因为底层数据没变时,仍可能算出一个看起来可信的速率。

请根据你要测量的对象选择合适的轮询间隔,但不要把轮询频率当作”收到了新测量值”的证据。

检查时间戳。

避免 WebRTC getStats 的六个常见错误
从原始的 getStats() 报告到可靠的指标:对象匹配、基于时间戳的差值计算、正确的解读方式,才能把 WebRTC 统计变成有意义的监控数据。

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 统计量放上监控面板之前,先回答四个问题:

  1. 这个值是累计的、瞬时的,还是推导出来的?
  2. 它的单位是什么?
  3. 这个值属于哪个对象?我跨报告比较的是同一个对象吗?
  4. 这个值是否可能合法地不可用?

这些问题比字段名有用得多。

因为 WebRTC 监控最危险的失败,很少是一块满是 NaN 的面板——

而是一块满是合理数字、却在测量错误对象的面板。

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/71798.html

(0)

相关推荐