Skip to main content

gosuto_libwebrtc/
stats.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use serde::Deserialize;
18
19use crate::data_channel::DataChannelState;
20
21/// Values from https://www.w3.org/TR/webrtc-stats/ (NOTE: Some of the structs are not in the SPEC
22/// but inside libwebrtc)
23/// serde will handle the magic of correctly deserializing the json into our structs.
24/// The enums values are inside encapsulated inside option because we're not sure about their
25/// default values (So we default to None instead of an arbitrary value)
26
27#[derive(Debug, Clone, Deserialize)]
28#[serde(tag = "type")]
29#[serde(rename_all = "kebab-case")]
30pub enum RtcStats {
31    Codec(CodecStats),
32    InboundRtp(InboundRtpStats),
33    OutboundRtp(OutboundRtpStats),
34    RemoteInboundRtp(RemoteInboundRtpStats),
35    RemoteOutboundRtp(RemoteOutboundRtpStats),
36    MediaSource(MediaSourceStats),
37    MediaPlayout(MediaPlayoutStats),
38    PeerConnection(PeerConnectionStats),
39    DataChannel(DataChannelStats),
40    Transport(TransportStats),
41    CandidatePair(CandidatePairStats),
42    LocalCandidate(LocalCandidateStats),
43    RemoteCandidate(RemoteCandidateStats),
44    Certificate(CertificateStats),
45    Stream(StreamStats),
46    Track, // Deprecated
47}
48
49#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
50#[serde(rename_all = "lowercase")]
51pub enum QualityLimitationReason {
52    #[default]
53    None,
54    Cpu,
55    Bandwidth,
56    Other,
57}
58
59#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
60#[serde(rename_all = "lowercase")]
61pub enum IceRole {
62    #[default]
63    Unknown,
64    Controlling,
65    Controlled,
66}
67
68#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
69#[serde(rename_all = "lowercase")]
70pub enum DtlsTransportState {
71    New,
72    Connecting,
73    Connected,
74    Closed,
75    Failed,
76}
77
78#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
79#[serde(rename_all = "lowercase")]
80pub enum IceTransportState {
81    New,
82    Checking,
83    Connected,
84    Completed,
85    Disconnected,
86    Failed,
87    Closed,
88}
89
90#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
91#[serde(rename_all = "lowercase")]
92pub enum DtlsRole {
93    Client,
94    Server,
95    #[default]
96    Unknown,
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
100#[serde(rename_all = "kebab-case")]
101pub enum IceCandidatePairState {
102    Frozen,
103    Waiting,
104    InProgress, // in-progress
105    Failed,
106    Succeeded,
107}
108
109#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
110#[serde(rename_all = "lowercase")]
111pub enum IceCandidateType {
112    Host,
113    Srflx,
114    Prflx,
115    Relay,
116}
117
118#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
119#[serde(rename_all = "lowercase")]
120pub enum IceServerTransportProtocol {
121    Udp,
122    Tcp,
123    Tls,
124}
125
126#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
127#[serde(rename_all = "lowercase")]
128pub enum IceTcpCandidateType {
129    Active,
130    Passive,
131    So,
132}
133
134#[derive(Debug, Default, Clone, Deserialize)]
135pub struct CodecStats {
136    #[serde(flatten)]
137    pub rtc: dictionaries::RtcStats,
138
139    #[serde(flatten)]
140    pub codec: dictionaries::CodecStats,
141}
142
143#[derive(Debug, Default, Clone, Deserialize)]
144pub struct InboundRtpStats {
145    #[serde(flatten)]
146    pub rtc: dictionaries::RtcStats,
147
148    #[serde(flatten)]
149    pub stream: dictionaries::RtpStreamStats,
150
151    #[serde(flatten)]
152    pub received: dictionaries::ReceivedRtpStreamStats,
153
154    #[serde(flatten)]
155    pub inbound: dictionaries::InboundRtpStreamStats,
156}
157
158#[derive(Debug, Default, Clone, Deserialize)]
159pub struct OutboundRtpStats {
160    #[serde(flatten)]
161    pub rtc: dictionaries::RtcStats,
162
163    #[serde(flatten)]
164    pub stream: dictionaries::RtpStreamStats,
165
166    #[serde(flatten)]
167    pub sent: dictionaries::SentRtpStreamStats,
168
169    #[serde(flatten)]
170    pub outbound: dictionaries::OutboundRtpStreamStats,
171}
172
173#[derive(Debug, Default, Clone, Deserialize)]
174pub struct RemoteInboundRtpStats {
175    #[serde(flatten)]
176    pub rtc: dictionaries::RtcStats,
177
178    #[serde(flatten)]
179    pub stream: dictionaries::RtpStreamStats,
180
181    #[serde(flatten)]
182    pub received: dictionaries::ReceivedRtpStreamStats,
183
184    #[serde(flatten)]
185    pub remote_inbound: dictionaries::RemoteInboundRtpStreamStats,
186}
187
188#[derive(Debug, Default, Clone, Deserialize)]
189pub struct RemoteOutboundRtpStats {
190    #[serde(flatten)]
191    pub rtc: dictionaries::RtcStats,
192
193    #[serde(flatten)]
194    pub stream: dictionaries::RtpStreamStats,
195
196    #[serde(flatten)]
197    pub sent: dictionaries::SentRtpStreamStats,
198
199    #[serde(flatten)]
200    pub remote_outbound: dictionaries::RemoteOutboundRtpStreamStats,
201}
202
203#[derive(Debug, Default, Clone, Deserialize)]
204pub struct MediaSourceStats {
205    #[serde(flatten)]
206    pub rtc: dictionaries::RtcStats,
207
208    #[serde(flatten)]
209    pub source: dictionaries::MediaSourceStats,
210
211    #[serde(flatten)]
212    pub audio: dictionaries::AudioSourceStats,
213
214    #[serde(flatten)]
215    pub video: dictionaries::VideoSourceStats,
216}
217
218#[derive(Debug, Default, Clone, Deserialize)]
219pub struct MediaPlayoutStats {
220    #[serde(flatten)]
221    pub rtc: dictionaries::RtcStats,
222
223    #[serde(flatten)]
224    pub audio_playout: dictionaries::AudioPlayoutStats,
225}
226
227#[derive(Debug, Default, Clone, Deserialize)]
228pub struct PeerConnectionStats {
229    #[serde(flatten)]
230    pub rtc: dictionaries::RtcStats,
231
232    #[serde(flatten)]
233    pub pc: dictionaries::PeerConnectionStats,
234}
235
236#[derive(Debug, Default, Clone, Deserialize)]
237pub struct DataChannelStats {
238    #[serde(flatten)]
239    pub rtc: dictionaries::RtcStats,
240
241    #[serde(flatten)]
242    pub dc: dictionaries::DataChannelStats,
243}
244
245#[derive(Debug, Default, Clone, Deserialize)]
246pub struct TransportStats {
247    #[serde(flatten)]
248    pub rtc: dictionaries::RtcStats,
249
250    #[serde(flatten)]
251    pub transport: dictionaries::TransportStats,
252}
253
254#[derive(Debug, Default, Clone, Deserialize)]
255pub struct CandidatePairStats {
256    #[serde(flatten)]
257    pub rtc: dictionaries::RtcStats,
258
259    #[serde(flatten)]
260    pub candidate_pair: dictionaries::CandidatePairStats,
261}
262
263#[derive(Debug, Default, Clone, Deserialize)]
264pub struct LocalCandidateStats {
265    #[serde(flatten)]
266    pub rtc: dictionaries::RtcStats,
267
268    #[serde(flatten)]
269    pub local_candidate: dictionaries::IceCandidateStats,
270}
271
272#[derive(Debug, Default, Clone, Deserialize)]
273pub struct RemoteCandidateStats {
274    #[serde(flatten)]
275    pub rtc: dictionaries::RtcStats,
276
277    #[serde(flatten)]
278    pub remote_candidate: dictionaries::IceCandidateStats,
279}
280
281#[derive(Debug, Default, Clone, Deserialize)]
282pub struct CertificateStats {
283    #[serde(flatten)]
284    pub rtc: dictionaries::RtcStats,
285
286    #[serde(flatten)]
287    pub certificate: dictionaries::CertificateStats,
288}
289
290#[derive(Debug, Default, Clone, Deserialize)]
291pub struct StreamStats {
292    #[serde(flatten)]
293    pub rtc: dictionaries::RtcStats,
294
295    #[serde(flatten)]
296    pub stream: dictionaries::StreamStats,
297}
298
299#[derive(Debug, Default, Clone, Deserialize)]
300pub struct TrackStats {}
301
302pub mod dictionaries {
303    use super::*;
304
305    #[derive(Debug, Default, Clone, Deserialize)]
306    #[serde(rename_all = "camelCase")]
307    #[serde(default)]
308    pub struct RtcStats {
309        pub id: String,
310        pub timestamp: i64,
311    }
312
313    #[derive(Debug, Default, Clone, Deserialize)]
314    #[serde(rename_all = "camelCase")]
315    #[serde(default)]
316    pub struct CodecStats {
317        pub payload_type: u32,
318        pub transport_id: String,
319        pub mime_type: String,
320        pub clock_rate: u32,
321        pub channels: u32,
322        pub sdp_fmtp_line: String,
323    }
324
325    #[derive(Debug, Default, Clone, Deserialize)]
326    #[serde(rename_all = "camelCase")]
327    #[serde(default)]
328    pub struct RtpStreamStats {
329        pub ssrc: u32,
330        pub kind: String,
331        pub transport_id: String,
332        pub codec_id: String,
333    }
334
335    #[derive(Debug, Default, Clone, Deserialize)]
336    #[serde(rename_all = "camelCase")]
337    #[serde(default)]
338    pub struct ReceivedRtpStreamStats {
339        pub packets_received: u64,
340        pub packets_lost: i64,
341        pub jitter: f64,
342    }
343
344    #[derive(Debug, Default, Clone, Deserialize)]
345    #[serde(rename_all = "camelCase")]
346    #[serde(default)]
347    pub struct InboundRtpStreamStats {
348        pub track_identifier: String,
349        pub mid: String,
350        pub remote_id: String,
351        pub frames_decoded: u32,
352        pub key_frames_decoded: u32,
353        pub frames_rendered: u32,
354        pub frames_dropped: u32,
355        pub frame_width: u32,
356        pub frame_height: u32,
357        pub frames_per_second: f64,
358        pub qp_sum: u64,
359        pub total_decode_time: f64,
360        pub total_inter_frame_delay: f64,
361        pub total_squared_inter_frame_delay: f64,
362        pub pause_count: u32,
363        pub total_pause_duration: f64,
364        pub freeze_count: u32,
365        pub total_freeze_duration: f64,
366        pub last_packet_received_timestamp: f64,
367        pub header_bytes_received: u64,
368        pub packets_discarded: u64,
369        pub fec_bytes_received: u64,
370        pub fec_packets_received: u64,
371        pub fec_packets_discarded: u64,
372        pub bytes_received: u64,
373        pub nack_count: u32,
374        pub fir_count: u32,
375        pub pli_count: u32,
376        pub total_processing_delay: f64,
377        pub estimated_playout_timestamp: f64,
378        pub jitter_buffer_delay: f64,
379        pub jitter_buffer_target_delay: f64,
380        pub jitter_buffer_emitted_count: u64,
381        pub jitter_buffer_minimum_delay: f64,
382        pub total_samples_received: u64,
383        pub concealed_samples: u64,
384        pub silent_concealed_samples: u64,
385        pub concealment_events: u64,
386        pub inserted_samples_for_deceleration: u64,
387        pub removed_samples_for_acceleration: u64,
388        pub audio_level: f64,
389        pub total_audio_energy: f64,
390        pub total_samples_duration: f64,
391        pub frames_received: u64,
392        pub decoder_implementation: String,
393        pub playout_id: String,
394        pub power_efficient_decoder: bool,
395        pub frames_assembled_from_multiple_packets: u64,
396        pub total_assembly_time: f64,
397        pub retransmitted_packets_received: u64,
398        pub retransmitted_bytes_received: u64,
399        pub rtx_ssrc: u32,
400        pub fec_ssrc: u32,
401    }
402
403    #[derive(Debug, Default, Clone, Deserialize)]
404    #[serde(rename_all = "camelCase")]
405    #[serde(default)]
406    pub struct SentRtpStreamStats {
407        pub packets_sent: u64,
408        pub bytes_sent: u64,
409    }
410
411    #[derive(Debug, Default, Clone, Deserialize)]
412    #[serde(rename_all = "camelCase")]
413    #[serde(default)]
414    pub struct OutboundRtpStreamStats {
415        pub mid: String,
416        pub media_source_id: String,
417        pub remote_id: String,
418        pub rid: String,
419        pub header_bytes_sent: u64,
420        pub retransmitted_packets_sent: u64,
421        pub retransmitted_bytes_sent: u64,
422        pub rtx_ssrc: u32,
423        pub target_bitrate: f64,
424        pub total_encoded_bytes_target: u64,
425        pub frame_width: u32,
426        pub frame_height: u32,
427        pub frames_per_second: f64,
428        pub frames_sent: u32,
429        pub huge_frames_sent: u32,
430        pub frames_encoded: u32,
431        pub key_frames_encoded: u32,
432        pub qp_sum: u64,
433        pub total_encode_time: f64,
434        pub total_packet_send_delay: f64,
435        pub quality_limitation_reason: QualityLimitationReason,
436        pub quality_limitation_durations: HashMap<String, f64>,
437        pub quality_limitation_resolution_changes: u32,
438        pub nack_count: u32,
439        pub fir_count: u32,
440        pub pli_count: u32,
441        pub encoder_implementation: String,
442        pub power_efficient_encoder: bool,
443        pub active: bool,
444        pub scalibility_mode: String,
445    }
446
447    #[derive(Debug, Default, Clone, Deserialize)]
448    #[serde(rename_all = "camelCase")]
449    #[serde(default)]
450    pub struct RemoteInboundRtpStreamStats {
451        pub local_id: String,
452        pub round_trip_time: f64,
453        pub total_round_trip_time: f64,
454        pub fraction_lost: f64,
455        pub round_trip_time_measurements: u64,
456    }
457
458    #[derive(Debug, Default, Clone, Deserialize)]
459    #[serde(rename_all = "camelCase")]
460    #[serde(default)]
461    pub struct RemoteOutboundRtpStreamStats {
462        pub local_id: String,
463        pub remote_timestamp: f64,
464        pub reports_sent: u64,
465        pub round_trip_time: f64,
466        pub total_round_trip_time: f64,
467        pub round_trip_time_measurements: u64,
468    }
469
470    #[derive(Debug, Default, Clone, Deserialize)]
471    #[serde(rename_all = "camelCase")]
472    #[serde(default)]
473    pub struct MediaSourceStats {
474        pub track_identifier: String,
475        pub kind: String,
476    }
477
478    #[derive(Debug, Default, Clone, Deserialize)]
479    #[serde(rename_all = "camelCase")]
480    #[serde(default)]
481    pub struct AudioSourceStats {
482        pub audio_level: f64,
483        pub total_audio_energy: f64,
484        pub total_samples_duration: f64,
485        pub echo_return_loss: f64,
486        pub echo_return_loss_enhancement: f64,
487        pub dropped_samples_duration: f64,
488        pub dropped_samples_events: u32,
489        pub total_capture_delay: f64,
490        pub total_samples_captured: u64,
491    }
492
493    #[derive(Debug, Default, Clone, Deserialize)]
494    #[serde(rename_all = "camelCase")]
495    #[serde(default)]
496    pub struct VideoSourceStats {
497        pub width: u32,
498        pub height: u32,
499        pub frames: u32,
500        pub frames_per_second: f64,
501    }
502
503    #[derive(Debug, Default, Clone, Deserialize)]
504    #[serde(rename_all = "camelCase")]
505    #[serde(default)]
506    pub struct AudioPlayoutStats {
507        pub kind: String,
508        pub synthesized_samples_duration: f64,
509        pub synthesized_samples_events: u32,
510        pub total_samples_duration: f64,
511        pub total_playout_delay: f64,
512        pub total_samples_count: u64,
513    }
514
515    #[derive(Debug, Default, Clone, Deserialize)]
516    #[serde(rename_all = "camelCase")]
517    #[serde(default)]
518    pub struct PeerConnectionStats {
519        pub data_channels_opened: u32,
520        pub data_channels_closed: u32,
521    }
522
523    #[derive(Debug, Default, Clone, Deserialize)]
524    #[serde(rename_all = "camelCase")]
525    #[serde(default)]
526    pub struct DataChannelStats {
527        pub label: String,
528        pub protocol: String,
529        pub data_channel_identifier: i32,
530        pub state: Option<DataChannelState>,
531        pub messages_sent: u32,
532        pub bytes_sent: u64,
533        pub messages_received: u32,
534        pub bytes_received: u64,
535    }
536
537    #[derive(Debug, Default, Clone, Deserialize)]
538    #[serde(rename_all = "camelCase")]
539    #[serde(default)]
540    pub struct TransportStats {
541        pub packets_sent: u64,
542        pub packets_received: u64,
543        pub bytes_sent: u64,
544        pub bytes_received: u64,
545        pub ice_role: IceRole,
546        pub ice_local_username_fragment: String,
547        pub dtls_state: Option<DtlsTransportState>,
548        pub ice_state: Option<IceTransportState>,
549        pub selected_candidate_pair_id: String,
550        pub local_certificate_id: String,
551        pub remote_certificate_id: String,
552        pub tls_version: String,
553        pub dtls_cipher: String,
554        pub dtls_role: DtlsRole,
555        pub srtp_cipher: String,
556        pub selected_candidate_pair_changes: u32,
557    }
558
559    #[derive(Debug, Default, Clone, Deserialize)]
560    #[serde(rename_all = "camelCase")]
561    #[serde(default)]
562    pub struct CandidatePairStats {
563        pub transport_id: String,
564        pub local_candidate_id: String,
565        pub remote_candidate_id: String,
566        pub state: Option<IceCandidatePairState>,
567        pub nominated: bool,
568        pub packets_sent: u64,
569        pub packets_received: u64,
570        pub bytes_sent: u64,
571        pub bytes_received: u64,
572        pub last_packet_sent_timestamp: f64,
573        pub last_packet_received_timestamp: f64,
574        pub total_round_trip_time: f64,
575        pub current_round_trip_time: f64,
576        pub available_outgoing_bitrate: f64,
577        pub available_incoming_bitrate: f64,
578        pub requests_received: u64,
579        pub requests_sent: u64,
580        pub responses_received: u64,
581        pub responses_sent: u64,
582        pub consent_requests_sent: u64,
583        pub packets_discarded_on_send: u32,
584        pub bytes_discarded_on_send: u64,
585    }
586
587    #[derive(Debug, Default, Clone, Deserialize)]
588    #[serde(rename_all = "camelCase")]
589    #[serde(default)]
590    pub struct IceCandidateStats {
591        pub transport_id: String,
592        pub address: String,
593        pub port: i32,
594        pub protocol: String,
595        pub candidate_type: Option<IceCandidateType>,
596        pub priority: i32,
597        pub url: String,
598        pub relay_protocol: Option<IceServerTransportProtocol>,
599        pub foundation: String,
600        pub related_address: String,
601        pub related_port: i32,
602        pub username_fragment: String,
603        pub tcp_type: Option<IceTcpCandidateType>,
604    }
605
606    #[derive(Debug, Default, Clone, Deserialize)]
607    #[serde(rename_all = "camelCase")]
608    #[serde(default)]
609    pub struct CertificateStats {
610        pub fingerprint: String,
611        pub fingerprint_algorithm: String,
612        pub base64_certificate: String,
613        pub issuer_certificate_id: String,
614    }
615
616    #[derive(Debug, Default, Clone, Deserialize)]
617    #[serde(rename_all = "camelCase")]
618    #[serde(default)]
619    pub struct StreamStats {
620        pub id: String,
621        pub stream_identifier: String,
622        // pub timestamp: i64,
623    }
624}