Skip to main content

rustrtc/
stats.rs

1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::{collections::BTreeMap, sync::Arc, time::SystemTime};
5
6use crate::errors::RtcResult;
7
8pub type DynProvider = dyn StatsProvider + Send + Sync + 'static;
9
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
11pub struct StatsId(String);
12
13impl StatsId {
14    pub fn new(value: impl Into<String>) -> Self {
15        Self(value.into())
16    }
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20pub enum StatsKind {
21    InboundRtp,
22    OutboundRtp,
23    RemoteInboundRtp,
24    RemoteOutboundRtp,
25    Transport,
26    IceCandidatePair,
27    DataChannel,
28    MediaSource,
29    MediaSink,
30    Custom(String),
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct StatsEntry {
35    pub id: StatsId,
36    pub kind: StatsKind,
37    pub timestamp: SystemTime,
38    pub values: BTreeMap<String, Value>,
39}
40
41impl StatsEntry {
42    pub fn new(id: StatsId, kind: StatsKind) -> Self {
43        Self {
44            id,
45            kind,
46            timestamp: SystemTime::now(),
47            values: BTreeMap::new(),
48        }
49    }
50
51    pub fn with_value(mut self, key: impl Into<String>, value: Value) -> Self {
52        self.values.insert(key.into(), value);
53        self
54    }
55}
56
57impl std::fmt::Display for StatsEntry {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "[{:?}/{}]", self.kind, self.id.0)?;
60        for (k, v) in &self.values {
61            // Attempt to display strings without quotes for cleaner logs, fallback to standard display
62            if let Some(s) = v.as_str() {
63                write!(f, " {}={}", k, s)?;
64            } else {
65                write!(f, " {}={}", k, v)?;
66            }
67        }
68        Ok(())
69    }
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct StatsReport {
74    pub collected_at: SystemTime,
75    pub entries: Vec<StatsEntry>,
76}
77
78impl StatsReport {
79    pub fn new(entries: Vec<StatsEntry>) -> Self {
80        Self {
81            collected_at: SystemTime::now(),
82            entries,
83        }
84    }
85
86    pub fn merge(mut self, mut other: StatsReport) -> Self {
87        self.entries.append(&mut other.entries);
88        self.collected_at = self.collected_at.max(other.collected_at);
89        self
90    }
91}
92
93impl std::fmt::Display for StatsReport {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "StatsReport(len={})", self.entries.len())?;
96        for entry in &self.entries {
97            write!(f, " {}", entry)?;
98        }
99        Ok(())
100    }
101}
102
103#[async_trait]
104pub trait StatsProvider: Send + Sync {
105    async fn collect(&self) -> RtcResult<Vec<StatsEntry>>;
106}
107
108pub async fn gather_once(providers: &[Arc<DynProvider>]) -> RtcResult<StatsReport> {
109    let mut entries = Vec::new();
110    for provider in providers {
111        entries.extend(provider.collect().await?);
112    }
113    Ok(StatsReport::new(entries))
114}