xref: /webrtc/webrtc/src/stats/mod.rs (revision 5b79f08a)
1 use crate::data_channel::data_channel_state::RTCDataChannelState;
2 use crate::data_channel::RTCDataChannel;
3 use crate::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint;
4 use crate::peer_connection::certificate::RTCCertificate;
5 use crate::rtp_transceiver::rtp_codec::RTCRtpCodecParameters;
6 use crate::rtp_transceiver::{PayloadType, SSRC};
7 use crate::sctp_transport::RTCSctpTransport;
8 
9 use ice::agent::agent_stats::{CandidatePairStats, CandidateStats};
10 use ice::agent::Agent;
11 use ice::candidate::{CandidatePairState, CandidateType};
12 use ice::network_type::NetworkType;
13 use stats_collector::StatsCollector;
14 
15 use serde::{Serialize, Serializer};
16 use std::collections::HashMap;
17 use std::sync::Arc;
18 use std::time::SystemTime;
19 use tokio::time::Instant;
20 
21 mod serialize;
22 pub mod stats_collector;
23 
24 #[derive(Debug, Serialize)]
25 pub enum RTCStatsType {
26     #[serde(rename = "candidate-pair")]
27     CandidatePair,
28     #[serde(rename = "certificate")]
29     Certificate,
30     #[serde(rename = "codec")]
31     Codec,
32     #[serde(rename = "csrc")]
33     CSRC,
34     #[serde(rename = "data-channel")]
35     DataChannel,
36     #[serde(rename = "inbound-rtp")]
37     InboundRTP,
38     #[serde(rename = "local-candidate")]
39     LocalCandidate,
40     #[serde(rename = "outbound-rtp")]
41     OutboundRTP,
42     #[serde(rename = "peer-connection")]
43     PeerConnection,
44     #[serde(rename = "receiver")]
45     Receiver,
46     #[serde(rename = "remote-candidate")]
47     RemoteCandidate,
48     #[serde(rename = "remote-inbound-rtp")]
49     RemoteInboundRTP,
50     #[serde(rename = "remote-outbound-rtp")]
51     RemoteOutboundRTP,
52     #[serde(rename = "sender")]
53     Sender,
54     #[serde(rename = "transport")]
55     Transport,
56 }
57 
58 pub enum SourceStatsType {
59     LocalCandidate(CandidateStats),
60     RemoteCandidate(CandidateStats),
61 }
62 
63 #[derive(Debug)]
64 pub enum StatsReportType {
65     CandidatePair(ICECandidatePairStats),
66     CertificateStats(CertificateStats),
67     Codec(CodecStats),
68     DataChannel(DataChannelStats),
69     LocalCandidate(ICECandidateStats),
70     PeerConnection(PeerConnectionStats),
71     RemoteCandidate(ICECandidateStats),
72     SCTPTransport(ICETransportStats),
73     Transport(ICETransportStats),
74     InboundRTP(InboundRTPStats),
75     OutboundRTP(OutboundRTPStats),
76     RemoteInboundRTP(RemoteInboundRTPStats),
77     RemoteOutboundRTP(RemoteOutboundRTPStats),
78 }
79 
80 impl From<SourceStatsType> for StatsReportType {
from(stats: SourceStatsType) -> Self81     fn from(stats: SourceStatsType) -> Self {
82         match stats {
83             SourceStatsType::LocalCandidate(stats) => StatsReportType::LocalCandidate(
84                 ICECandidateStats::new(stats, RTCStatsType::LocalCandidate),
85             ),
86             SourceStatsType::RemoteCandidate(stats) => StatsReportType::RemoteCandidate(
87                 ICECandidateStats::new(stats, RTCStatsType::RemoteCandidate),
88             ),
89         }
90     }
91 }
92 
93 impl Serialize for StatsReportType {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,94     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
95     where
96         S: Serializer,
97     {
98         match self {
99             StatsReportType::CandidatePair(stats) => stats.serialize(serializer),
100             StatsReportType::CertificateStats(stats) => stats.serialize(serializer),
101             StatsReportType::Codec(stats) => stats.serialize(serializer),
102             StatsReportType::DataChannel(stats) => stats.serialize(serializer),
103             StatsReportType::LocalCandidate(stats) => stats.serialize(serializer),
104             StatsReportType::PeerConnection(stats) => stats.serialize(serializer),
105             StatsReportType::RemoteCandidate(stats) => stats.serialize(serializer),
106             StatsReportType::SCTPTransport(stats) => stats.serialize(serializer),
107             StatsReportType::Transport(stats) => stats.serialize(serializer),
108             StatsReportType::InboundRTP(stats) => stats.serialize(serializer),
109             StatsReportType::OutboundRTP(stats) => stats.serialize(serializer),
110             StatsReportType::RemoteInboundRTP(stats) => stats.serialize(serializer),
111             StatsReportType::RemoteOutboundRTP(stats) => stats.serialize(serializer),
112         }
113     }
114 }
115 
116 #[derive(Debug)]
117 pub struct StatsReport {
118     pub reports: HashMap<String, StatsReportType>,
119 }
120 
121 impl From<StatsCollector> for StatsReport {
from(collector: StatsCollector) -> Self122     fn from(collector: StatsCollector) -> Self {
123         StatsReport {
124             reports: collector.into_reports(),
125         }
126     }
127 }
128 
129 impl Serialize for StatsReport {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,130     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131     where
132         S: Serializer,
133     {
134         self.reports.serialize(serializer)
135     }
136 }
137 
138 #[derive(Debug, Serialize)]
139 #[serde(rename_all = "camelCase")]
140 pub struct ICECandidatePairStats {
141     // RTCStats
142     #[serde(with = "serialize::instant_to_epoch_seconds")]
143     pub timestamp: Instant,
144     #[serde(rename = "type")]
145     pub stats_type: RTCStatsType,
146     pub id: String,
147 
148     // RTCIceCandidatePairStats
149     // TODO: Add `transportId`
150     pub local_candidate_id: String,
151     pub remote_candidate_id: String,
152     pub state: CandidatePairState,
153     pub nominated: bool,
154     pub packets_sent: u32,
155     pub packets_received: u32,
156     pub bytes_sent: u64,
157     pub bytes_received: u64,
158     #[serde(with = "serialize::instant_to_epoch_seconds")]
159     pub last_packet_sent_timestamp: Instant,
160     #[serde(with = "serialize::instant_to_epoch_seconds")]
161     pub last_packet_received_timestamp: Instant,
162     pub total_round_trip_time: f64,
163     pub current_round_trip_time: f64,
164     pub available_outgoing_bitrate: f64,
165     pub available_incoming_bitrate: f64,
166     pub requests_received: u64,
167     pub requests_sent: u64,
168     pub responses_received: u64,
169     pub responses_sent: u64,
170     pub consent_requests_sent: u64,
171     // TODO: Add `packetsDiscardedOnSend`
172     // TODO: Add `bytesDiscardedOnSend`
173 
174     // Non-canon
175     pub circuit_breaker_trigger_count: u32,
176     #[serde(with = "serialize::instant_to_epoch_seconds")]
177     pub consent_expired_timestamp: Instant,
178     #[serde(with = "serialize::instant_to_epoch_seconds")]
179     pub first_request_timestamp: Instant,
180     #[serde(with = "serialize::instant_to_epoch_seconds")]
181     pub last_request_timestamp: Instant,
182     pub retransmissions_sent: u64,
183 }
184 
185 impl From<CandidatePairStats> for ICECandidatePairStats {
from(stats: CandidatePairStats) -> Self186     fn from(stats: CandidatePairStats) -> Self {
187         ICECandidatePairStats {
188             available_incoming_bitrate: stats.available_incoming_bitrate,
189             available_outgoing_bitrate: stats.available_outgoing_bitrate,
190             bytes_received: stats.bytes_received,
191             bytes_sent: stats.bytes_sent,
192             circuit_breaker_trigger_count: stats.circuit_breaker_trigger_count,
193             consent_expired_timestamp: stats.consent_expired_timestamp,
194             consent_requests_sent: stats.consent_requests_sent,
195             current_round_trip_time: stats.current_round_trip_time,
196             first_request_timestamp: stats.first_request_timestamp,
197             id: format!("{}-{}", stats.local_candidate_id, stats.remote_candidate_id),
198             last_packet_received_timestamp: stats.last_packet_received_timestamp,
199             last_packet_sent_timestamp: stats.last_packet_sent_timestamp,
200             last_request_timestamp: stats.last_request_timestamp,
201             local_candidate_id: stats.local_candidate_id,
202             nominated: stats.nominated,
203             packets_received: stats.packets_received,
204             packets_sent: stats.packets_sent,
205             remote_candidate_id: stats.remote_candidate_id,
206             requests_received: stats.requests_received,
207             requests_sent: stats.requests_sent,
208             responses_received: stats.responses_received,
209             responses_sent: stats.responses_sent,
210             retransmissions_sent: stats.retransmissions_sent,
211             state: stats.state,
212             stats_type: RTCStatsType::CandidatePair,
213             timestamp: stats.timestamp,
214             total_round_trip_time: stats.total_round_trip_time,
215         }
216     }
217 }
218 
219 #[derive(Debug, Serialize)]
220 #[serde(rename_all = "camelCase")]
221 pub struct ICECandidateStats {
222     // RTCStats
223     #[serde(with = "serialize::instant_to_epoch_seconds")]
224     pub timestamp: Instant,
225     #[serde(rename = "type")]
226     pub stats_type: RTCStatsType,
227     pub id: String,
228 
229     // RTCIceCandidateStats
230     pub candidate_type: CandidateType,
231     pub deleted: bool,
232     pub ip: String,
233     pub network_type: NetworkType,
234     pub port: u16,
235     pub priority: u32,
236     pub relay_protocol: String,
237     pub url: String,
238 }
239 
240 impl ICECandidateStats {
new(stats: CandidateStats, stats_type: RTCStatsType) -> Self241     fn new(stats: CandidateStats, stats_type: RTCStatsType) -> Self {
242         ICECandidateStats {
243             candidate_type: stats.candidate_type,
244             deleted: stats.deleted,
245             id: stats.id,
246             ip: stats.ip,
247             network_type: stats.network_type,
248             port: stats.port,
249             priority: stats.priority,
250             relay_protocol: stats.relay_protocol,
251             stats_type,
252             timestamp: stats.timestamp,
253             url: stats.url,
254         }
255     }
256 }
257 
258 #[derive(Debug, Serialize)]
259 #[serde(rename_all = "camelCase")]
260 pub struct ICETransportStats {
261     // RTCStats
262     #[serde(with = "serialize::instant_to_epoch_seconds")]
263     pub timestamp: Instant,
264     #[serde(rename = "type")]
265     pub stats_type: RTCStatsType,
266     pub id: String,
267 
268     // Non-canon
269     pub bytes_received: usize,
270     pub bytes_sent: usize,
271 }
272 
273 impl ICETransportStats {
new(id: String, agent: Arc<Agent>) -> Self274     pub(crate) fn new(id: String, agent: Arc<Agent>) -> Self {
275         ICETransportStats {
276             id,
277             bytes_received: agent.get_bytes_received(),
278             bytes_sent: agent.get_bytes_sent(),
279             stats_type: RTCStatsType::Transport,
280             timestamp: Instant::now(),
281         }
282     }
283 }
284 
285 #[derive(Debug, Serialize)]
286 #[serde(rename_all = "camelCase")]
287 pub struct CertificateStats {
288     // RTCStats
289     #[serde(with = "serialize::instant_to_epoch_seconds")]
290     pub timestamp: Instant,
291     #[serde(rename = "type")]
292     pub stats_type: RTCStatsType,
293     pub id: String,
294 
295     // RTCCertificateStats
296     pub fingerprint: String,
297     pub fingerprint_algorithm: String,
298     // TODO: Add `base64Certificate` and `issuerCertificateId`.
299 }
300 
301 impl CertificateStats {
new(cert: &RTCCertificate, fingerprint: RTCDtlsFingerprint) -> Self302     pub(crate) fn new(cert: &RTCCertificate, fingerprint: RTCDtlsFingerprint) -> Self {
303         CertificateStats {
304             // TODO: base64_certificate
305             fingerprint: fingerprint.value,
306             fingerprint_algorithm: fingerprint.algorithm,
307             id: cert.stats_id.clone(),
308             // TODO: issuer_certificate_id
309             stats_type: RTCStatsType::Certificate,
310             timestamp: Instant::now(),
311         }
312     }
313 }
314 
315 #[derive(Debug, Serialize)]
316 #[serde(rename_all = "camelCase")]
317 pub struct CodecStats {
318     // RTCStats
319     #[serde(with = "serialize::instant_to_epoch_seconds")]
320     pub timestamp: Instant,
321     #[serde(rename = "type")]
322     pub stats_type: RTCStatsType,
323     pub id: String,
324 
325     // RTCCodecStats
326     pub payload_type: PayloadType,
327     pub mime_type: String,
328     pub channels: u16,
329     pub clock_rate: u32,
330     pub sdp_fmtp_line: String,
331     // TODO: Add `transportId`
332 }
333 
334 impl From<&RTCRtpCodecParameters> for CodecStats {
from(codec: &RTCRtpCodecParameters) -> Self335     fn from(codec: &RTCRtpCodecParameters) -> Self {
336         CodecStats {
337             channels: codec.capability.channels,
338             clock_rate: codec.capability.clock_rate,
339             id: codec.stats_id.clone(),
340             mime_type: codec.capability.mime_type.clone(),
341             payload_type: codec.payload_type,
342             sdp_fmtp_line: codec.capability.sdp_fmtp_line.clone(),
343             stats_type: RTCStatsType::Codec,
344             timestamp: Instant::now(),
345         }
346     }
347 }
348 
349 #[derive(Debug, Serialize)]
350 #[serde(rename_all = "camelCase")]
351 pub struct DataChannelStats {
352     // RTCStats
353     #[serde(with = "serialize::instant_to_epoch_seconds")]
354     pub timestamp: Instant,
355     #[serde(rename = "type")]
356     pub stats_type: RTCStatsType,
357     pub id: String,
358 
359     // RTCDataChannelStats
360     pub bytes_received: usize,
361     pub bytes_sent: usize,
362     pub data_channel_identifier: u16,
363     pub label: String,
364     pub messages_received: usize,
365     pub messages_sent: usize,
366     pub protocol: String,
367     pub state: RTCDataChannelState,
368 }
369 
370 impl DataChannelStats {
from(data_channel: &RTCDataChannel) -> Self371     pub(crate) async fn from(data_channel: &RTCDataChannel) -> Self {
372         let state = data_channel.ready_state();
373 
374         let mut bytes_received = 0;
375         let mut bytes_sent = 0;
376         let mut messages_received = 0;
377         let mut messages_sent = 0;
378 
379         let lock = data_channel.data_channel.lock().await;
380 
381         if let Some(internal) = &*lock {
382             bytes_received = internal.bytes_received();
383             bytes_sent = internal.bytes_sent();
384             messages_received = internal.messages_received();
385             messages_sent = internal.messages_sent();
386         }
387 
388         Self {
389             bytes_received,
390             bytes_sent,
391             data_channel_identifier: data_channel.id(), // TODO: "The value is initially null"
392             id: data_channel.stats_id.clone(),
393             label: data_channel.label.clone(),
394             messages_received,
395             messages_sent,
396             protocol: data_channel.protocol.clone(),
397             state,
398             stats_type: RTCStatsType::DataChannel,
399             timestamp: Instant::now(),
400         }
401     }
402 }
403 
404 #[derive(Debug, Serialize)]
405 #[serde(rename_all = "camelCase")]
406 pub struct PeerConnectionStats {
407     // RTCStats
408     #[serde(with = "serialize::instant_to_epoch_seconds")]
409     pub timestamp: Instant,
410     #[serde(rename = "type")]
411     pub stats_type: RTCStatsType,
412     pub id: String,
413 
414     // RTCPeerConnectionStats
415     pub data_channels_closed: u32,
416     pub data_channels_opened: u32,
417 
418     // Non-canon
419     pub data_channels_accepted: u32,
420     pub data_channels_requested: u32,
421 }
422 
423 impl PeerConnectionStats {
new(transport: &RTCSctpTransport, stats_id: String, data_channels_closed: u32) -> Self424     pub fn new(transport: &RTCSctpTransport, stats_id: String, data_channels_closed: u32) -> Self {
425         PeerConnectionStats {
426             data_channels_accepted: transport.data_channels_accepted(),
427             data_channels_closed,
428             data_channels_opened: transport.data_channels_opened(),
429             data_channels_requested: transport.data_channels_requested(),
430             id: stats_id,
431             stats_type: RTCStatsType::PeerConnection,
432             timestamp: Instant::now(),
433         }
434     }
435 }
436 
437 #[derive(Debug, Serialize)]
438 #[serde(rename_all = "camelCase")]
439 pub struct InboundRTPStats {
440     // RTCStats
441     #[serde(with = "serialize::instant_to_epoch_seconds")]
442     pub timestamp: Instant,
443     #[serde(rename = "type")]
444     pub stats_type: RTCStatsType,
445     pub id: String,
446 
447     // RTCRtpStreamStats
448     pub ssrc: SSRC,
449     pub kind: &'static str, // Either "video" or "audio"
450     // TODO: Add tranportId
451     // TODO: Add codecId
452 
453     // RTCReceivedRtpStreamStats
454     pub packets_received: u64,
455     // TODO: packetsLost
456     // TOOD: jitter(maybe, might be uattainable for the same reason as `framesDropped`)
457     // NB: `framesDropped` can't be produced since we aren't decoding, might be worth introducing a
458     // way for consumers to control this in the future.
459 
460     // RTCInboundRtpStreamStats
461     pub track_identifier: String,
462     pub mid: String,
463     // TODO: `remoteId`
464     // NB: `framesDecoded`, `frameWidth`, frameHeight`, `framesPerSecond`, `qpSum`,
465     // `totalDecodeTime`, `totalInterFrameDelay`, and `totalSquaredInterFrameDelay` are all decoder
466     // specific values and can't be produced since we aren't decoding.
467     pub last_packet_received_timestamp: Option<SystemTime>,
468     pub header_bytes_received: u64,
469     // TODO: `packetsDiscarded`. This value only makes sense if we have jitter buffer, which we
470     // cannot assume.
471     // TODO: `fecPacketsReceived`, `fecPacketsDiscarded`
472     pub bytes_received: u64,
473     pub nack_count: u64,
474     pub fir_count: Option<u64>,
475     pub pli_count: Option<u64>,
476     // NB: `totalProcessingDelay`, `estimatedPlayoutTimestamp`, `jitterBufferDelay`,
477     // `jitterBufferTargetDelay`, `jitterBufferEmittedCount`, `jitterBufferMinimumDelay`,
478     // `totalSamplesReceived`, `concealedSamples`, `silentConcealedSamples`, `concealmentEvents`,
479     // `insertedSamplesForDeceleration`, `removedSamplesForAcceleration`, `audioLevel`,
480     // `totalAudioEneregy`, `totalSampleDuration`, `framesReceived, and `decoderImplementation` are
481     // all decoder specific and can't be produced since we aren't decoding.
482 }
483 
484 #[derive(Debug, Serialize)]
485 #[serde(rename_all = "camelCase")]
486 pub struct OutboundRTPStats {
487     // RTCStats
488     #[serde(with = "serialize::instant_to_epoch_seconds")]
489     pub timestamp: Instant,
490     #[serde(rename = "type")]
491     pub stats_type: RTCStatsType,
492     pub id: String,
493 
494     // RTCRtpStreamStats
495     pub ssrc: SSRC,
496     pub kind: &'static str, // Either "video" or "audio"
497     // TODO: Add tranportId
498     // TODO: Add codecId
499 
500     // RTCSentRtpStreamStats
501     pub packets_sent: u64,
502     pub bytes_sent: u64,
503 
504     // RTCOutboundRtpStreamStats
505     // NB: non-canon in browsers this is available via `RTCMediaSourceStats` which we are unlikely to implement
506     pub track_identifier: String,
507     pub mid: String,
508     // TODO: `mediaSourceId` and `remoteId`
509     pub rid: Option<String>,
510     pub header_bytes_sent: u64,
511     // TODO: `retransmittedPacketsSent` and `retransmittedPacketsSent`
512     // NB: `targetBitrate`, `totalEncodedBytesTarget`, `frameWidth` `frameHeight`, `framesPerSecond`, `framesSent`,
513     // `hugeFramesSent`, `framesEncoded`, `keyFramesEncoded`, `qpSum`, and `totalEncodeTime` are
514     // all encoder specific and can't be produced snce we aren't encoding.
515     // TODO: `totalPacketSendDelay` time from `TrackLocalWriter::write_rtp` to being written to
516     // socket.
517 
518     // NB: `qualityLimitationReason`, `qualityLimitationDurations`, and `qualityLimitationResolutionChanges` are all
519     // encoder specific and can't be produced since we aren't encoding.
520     pub nack_count: u64,
521     pub fir_count: Option<u64>,
522     pub pli_count: Option<u64>,
523     // NB: `encoderImplementation` is encoder specific and can't be produced since we aren't
524     // encoding.
525 }
526 
527 #[derive(Debug, Serialize)]
528 #[serde(rename_all = "camelCase")]
529 pub struct RemoteInboundRTPStats {
530     // RTCStats
531     #[serde(with = "serialize::instant_to_epoch_seconds")]
532     pub timestamp: Instant,
533     #[serde(rename = "type")]
534     pub stats_type: RTCStatsType,
535     pub id: String,
536 
537     // RTCRtpStreamStats
538     pub ssrc: SSRC,
539     pub kind: &'static str, // Either "video" or "audio"
540     // TODO: Add tranportId
541     // TODO: Add codecId
542 
543     // RTCReceivedRtpStreamStats
544     pub packets_received: u64,
545     pub packets_lost: i64,
546     // TOOD: jitter(maybe, might be uattainable for the same reason as `framesDropped`)
547     // NB: `framesDropped` can't be produced since we aren't decoding, might be worth introducing a
548     // way for consumers to control this in the future.
549 
550     // RTCRemoteInboundRtpStreamStats
551     pub local_id: String,
552     pub round_trip_time: Option<f64>,
553     pub total_round_trip_time: f64,
554     pub fraction_lost: f64,
555     pub round_trip_time_measurements: u64,
556 }
557 
558 #[derive(Debug, Serialize)]
559 #[serde(rename_all = "camelCase")]
560 pub struct RemoteOutboundRTPStats {
561     // RTCStats
562     #[serde(with = "serialize::instant_to_epoch_seconds")]
563     pub timestamp: Instant,
564     #[serde(rename = "type")]
565     pub stats_type: RTCStatsType,
566     pub id: String,
567 
568     // RTCRtpStreamStats
569     pub ssrc: SSRC,
570     pub kind: &'static str, // Either "video" or "audio"
571     // TODO: Add tranportId
572     // TODO: Add codecId
573 
574     // RTCSentRtpStreamStats
575     pub packets_sent: u64,
576     pub bytes_sent: u64,
577 
578     // RTCRemoteOutboundRtpStreamStats
579     pub local_id: String,
580     // TODO: `remote_timestamp`
581     pub round_trip_time: Option<f64>,
582     pub reports_sent: u64,
583     pub total_round_trip_time: f64,
584     pub round_trip_time_measurements: u64,
585 }
586