统计的收集 · 跨线程汇集成一份报告

getStats 这一次调用看着简单,背后却要从多条线程上的多个子系统各取一部分指标、合并成一份报告,还要缓存以防频繁调用。这一章读 rtc_stats_collector.h,讲清 RTCStatsCollector 的异步收集、分线程产出、合并与缓存机制。

作者 David更新于 第 102 篇(共 112 篇)

统计的收集 · 跨线程汇集成一份报告

上一章看了 RTCStatsReport 的结构。但这份报告从哪来?getStats() 看着是一次简单调用,背后却是一次跨多线程、从多个子系统取数、合并、缓存的复杂协作。这一章看 RTCStatsCollector 怎么干这活——它也把第 10 部分的线程模型照进了现实。

为什么收集是异步的

先看接口(真实源码 rtc_stats_collector.h):

// rtc_stats_collector.h(节选,真实源码)
class RTCStatsCollector {
  // 注意:不是"返回报告",而是"传入回调,报告好了再回调你"
  void GetStatsReport(scoped_refptr<RTCStatsCollectorCallback> callback);
  // ... Produce*_s 系列:分别产出各类 stats
};

GetStatsReport 是异步回调式的(传 callback、不同步返回),因为一份完整报告的数据散落在不同线程上**——必须去各条线程“取回属于它的那部分”,没法在调用线程当场凑齐。** 回想第 10 部分的铁律:每份数据归属某条线程,只能在那条线程访问。而一份 stats 报告要的数据偏偏来自三条线程:

于是收集必然是“分头去各线程取、再合并”的异步过程——这就是它回调式、而非同步返回的原因。API 的异步形状,是底层线程模型的直接后果(又一个“地基塑造上层”的例子)。

分线程产出、再合并

读源码会看到一系列 Produce*_s 方法(_s = signaling 线程那部分),各管一类 stats:

// rtc_stats_collector.h(节选,真实源码)
void ProduceCertificateStats_s(...);        // 证书
void ProduceDataChannelStats_s(...);        // 数据通道
void ProduceIceCandidateAndPairStats_s(...);// ICE 候选与路径
void ProduceMediaSourceStats_s(...);        // 媒体源
void ProducePeerConnectionStats_s(...);     // PC 级
void ProduceRTPStreamStats_s(...);          // 收发 RTP 流
// ...以及在 network / worker 线程上收集的对应部分

收集流程大致是:

图表加载中…

各线程各自产出“自己那部分 stats”(在数据的归属线程上安全地读),再汇总成一份完整的 RTCStatsReport,最后回调给应用。这正是第 10 部分“数据归属线程 + PostTask 跨线程”的一次完整实战:不加锁,而是把收集任务派发到每份数据所在的线程。

缓存:防止频繁调用压垮系统

一个重要细节:报告会缓存一小段时间(真实源码注释 reports are cached for cache_lifetime_ ms):

getStats() 若被频繁调用(如仪表盘每秒刷新、甚至更密),每次都跨三条线程重新收集会很贵——所以 RTCStatsCollector 把刚产出的报告缓存 cache_lifetime_ 毫秒**,期间再调用直接返回缓存,不重新收集。** 这是个经典的“用缓存换开销”权衡:stats 是监控用途,几十毫秒的陈旧完全可接受,但省下的跨线程收集开销很可观。缓存过期后的下一次调用才真正重新收集、刷新缓存。所以你若每秒调好几次 getStats,大部分调用其实吃的是缓存——这是有意的性能设计,不是 bug。需要“保证新鲜”的场景,源码另有显式刷新的路径。

和第 1 部分的 StatsCollector 呼应

第 1 部分介绍 PeerConnection 组件时,RTCStatsCollector 作为其中一员出过场——那时说它“负责 getStats”。这一章补齐了细节:它不是简单地读几个变量,而是一个跨线程的收集协调器,把散落三处的指标安全汇集成 W3C 标准报告。它是控制面组件,但要伸手到媒体面(worker)和网络面(network)取数——因此天然是异步的。

小结与下一章

getStats() 背后是 RTCStatsCollector 的一次跨线程收集:它异步回调式工作(GetStatsReport(callback)),因为报告数据散落在 network / worker / signaling 三线程,必须各去归属线程取回、再合并——一系列 Produce*_s 方法分头产出各类 stats,汇总成一份 RTCStatsReport。这是第 10 部分“数据归属线程 + PostTask”纪律的实战,也解释了 getStats 为何是异步 API。报告缓存 cache_lifetime_ 毫秒,避免频繁调用反复触发昂贵收集。

快照支柱看完了。下一章转向另一支柱——黑匣子 RtcEventLog。👉 RtcEventLog 事件日志。

参考资料

本页目录