Skip to main content

rtc_ice/agent/
agent_stats.rs

1use crate::agent::Agent;
2use std::time::Instant;
3
4use crate::candidate::{CandidateType, candidate_pair::CandidatePairState};
5use crate::network_type::NetworkType;
6
7/// Contains ICE candidate pair statistics.
8pub struct CandidatePairStats {
9    /// The timestamp associated with this struct.
10    /// When this snapshot was taken.
11    pub timestamp: Instant,
12
13    /// The id of the local candidate.
14    pub local_candidate_id: String,
15
16    /// The id of the remote candidate.
17    pub remote_candidate_id: String,
18
19    /// The state of the checklist for the local and remote candidates in a pair.
20    pub state: CandidatePairState,
21
22    /// It is true when this valid pair that should be used for media,
23    /// if it is the highest-priority one amongst those whose nominated flag is set.
24    pub nominated: bool,
25
26    /// The total number of packets sent on this candidate pair.
27    pub packets_sent: u32,
28
29    /// The total number of packets received on this candidate pair.
30    pub packets_received: u32,
31
32    /// The total number of payload bytes sent on this candidate pair not including headers or
33    /// padding.
34    pub bytes_sent: u64,
35
36    /// The total number of payload bytes received on this candidate pair not including headers or
37    /// padding.
38    pub bytes_received: u64,
39
40    /// The timestamp at which the last packet was sent on this particular candidate pair, excluding
41    /// STUN packets.
42    pub last_packet_sent_timestamp: Instant,
43
44    /// The timestamp at which the last packet was received on this particular candidate pair,
45    /// excluding STUN packets.
46    pub last_packet_received_timestamp: Instant,
47
48    /// The timestamp at which the first STUN request was sent on this particular candidate pair.
49    pub first_request_timestamp: Instant,
50
51    /// The timestamp at which the last STUN request was sent on this particular candidate pair.
52    /// The average interval between two consecutive connectivity checks sent can be calculated with
53    /// (last_request_timestamp - first_request_timestamp) / requests_sent.
54    pub last_request_timestamp: Instant,
55
56    /// Timestamp at which the last STUN response was received on this particular candidate pair.
57    pub last_response_timestamp: Instant,
58
59    /// The sum of all round trip time measurements in seconds since the beginning of the session,
60    /// based on STUN connectivity check responses (responses_received), including those that reply
61    /// to requests that are sent in order to verify consent. The average round trip time can be
62    /// computed from total_round_trip_time by dividing it by responses_received.
63    pub total_round_trip_time: f64,
64
65    /// The latest round trip time measured in seconds, computed from both STUN connectivity checks,
66    /// including those that are sent for consent verification.
67    pub current_round_trip_time: f64,
68
69    /// It is calculated by the underlying congestion control by combining the available bitrate for
70    /// all the outgoing RTP streams using this candidate pair. The bitrate measurement does not
71    /// count the size of the IP or other transport layers like TCP or UDP. It is similar to the
72    /// TIAS defined in RFC 3890, i.e., it is measured in bits per second and the bitrate is
73    /// calculated over a 1 second window.
74    pub available_outgoing_bitrate: f64,
75
76    /// It is calculated by the underlying congestion control by combining the available bitrate for
77    /// all the incoming RTP streams using this candidate pair. The bitrate measurement does not
78    /// count the size of the IP or other transport layers like TCP or UDP. It is similar to the
79    /// TIAS defined in  RFC 3890, i.e., it is measured in bits per second and the bitrate is
80    /// calculated over a 1 second window.
81    pub available_incoming_bitrate: f64,
82
83    /// The number of times the circuit breaker is triggered for this particular 5-tuple,
84    /// ceasing transmission.
85    pub circuit_breaker_trigger_count: u32,
86
87    /// The total number of connectivity check requests received (including retransmissions).
88    /// It is impossible for the receiver to tell whether the request was sent in order to check
89    /// connectivity or check consent, so all connectivity checks requests are counted here.
90    pub requests_received: u64,
91
92    /// The total number of connectivity check requests sent (not including retransmissions).
93    pub requests_sent: u64,
94
95    /// The total number of connectivity check responses received.
96    pub responses_received: u64,
97
98    /// The total number of connectivity check responses sent. Since we cannot distinguish
99    /// connectivity check requests and consent requests, all responses are counted.
100    pub responses_sent: u64,
101
102    /// The total number of connectivity check request retransmissions received.
103    pub retransmissions_received: u64,
104
105    /// The total number of connectivity check request retransmissions sent.
106    pub retransmissions_sent: u64,
107
108    /// The total number of consent requests sent.
109    pub consent_requests_sent: u64,
110
111    /// The timestamp at which the latest valid STUN binding response expired.
112    pub consent_expired_timestamp: Instant,
113}
114
115impl CandidatePairStats {
116    /// An empty stats record stamped at `now`.
117    ///
118    /// This replaces `Default`: every field here is a timestamp, and a `Default` impl cannot be
119    /// handed one — it would have to reach for the clock, which is what §3.1(c) of the design
120    /// removes. A `getStats` report taken under a virtual clock must read in virtual time
121    /// throughout, or it is more confusing than no report at all.
122    pub fn new(now: Instant) -> Self {
123        Self {
124            timestamp: now,
125            local_candidate_id: String::new(),
126            remote_candidate_id: String::new(),
127            state: CandidatePairState::default(),
128            nominated: false,
129            packets_sent: 0,
130            packets_received: 0,
131            bytes_sent: 0,
132            bytes_received: 0,
133            last_packet_sent_timestamp: now,
134            last_packet_received_timestamp: now,
135            first_request_timestamp: now,
136            last_request_timestamp: now,
137            last_response_timestamp: now,
138            total_round_trip_time: 0.0,
139            current_round_trip_time: 0.0,
140            available_outgoing_bitrate: 0.0,
141            available_incoming_bitrate: 0.0,
142            circuit_breaker_trigger_count: 0,
143            requests_received: 0,
144            requests_sent: 0,
145            responses_received: 0,
146            responses_sent: 0,
147            retransmissions_received: 0,
148            retransmissions_sent: 0,
149            consent_requests_sent: 0,
150            consent_expired_timestamp: now,
151        }
152    }
153}
154
155/// Contains ICE candidate statistics related to the `ICETransport` objects.
156#[derive(Debug, Clone)]
157pub struct CandidateStats {
158    /// The timestamp associated with this struct.
159    pub timestamp: Instant,
160
161    /// The candidate id.
162    pub id: String,
163
164    /// The type of network interface used by the base of a local candidate (the address the ICE
165    /// agent sends from). Only present for local candidates; it's not possible to know what type of
166    /// network interface a remote candidate is using.
167    ///
168    /// Note: This stat only tells you about the network interface used by the first "hop"; it's
169    /// possible that a connection will be bottlenecked by another type of network.  For example,
170    /// when using Wi-Fi tethering, the networkType of the relevant candidate would be "wifi", even
171    /// when the next hop is over a cellular connection.
172    pub network_type: NetworkType,
173
174    /// The IP address of the candidate, allowing for IPv4 addresses and IPv6 addresses, but fully
175    /// qualified domain names (FQDNs) are not allowed.
176    pub ip: String,
177
178    /// The port number of the candidate.
179    pub port: u16,
180
181    /// The `Type` field of the ICECandidate.
182    pub candidate_type: CandidateType,
183
184    /// The `priority` field of the ICECandidate.
185    pub priority: u32,
186
187    /// The url of the TURN or STUN server indicated in the that translated this IP address.
188    /// It is the url address surfaced in an PeerConnectionICEEvent.
189    pub url: String,
190
191    /// The protocol used by the endpoint to communicate with the TURN server. This is only present
192    /// for local candidates. Valid values for the TURN url protocol is one of udp, tcp, or tls.
193    pub relay_protocol: String,
194
195    /// It is true if the candidate has been deleted/freed. For host candidates, this means that any
196    /// network resources (typically a socket) associated with the candidate have been released. For
197    /// TURN candidates, this means the TURN allocation is no longer active.
198    ///
199    /// Only defined for local candidates. For remote candidates, this property is not applicable.
200    pub deleted: bool,
201}
202
203impl CandidateStats {
204    /// An empty stats record stamped at `now`. Replaces `Default`, for the reason given on
205    /// [`CandidatePairStats::new`].
206    pub fn new(now: Instant) -> Self {
207        Self {
208            timestamp: now,
209            id: String::new(),
210            network_type: NetworkType::default(),
211            ip: String::new(),
212            port: 0,
213            candidate_type: CandidateType::default(),
214            priority: 0,
215            url: String::new(),
216            relay_protocol: String::new(),
217            deleted: false,
218        }
219    }
220}
221
222impl Agent {
223    /// Returns a list of candidate pair stats.
224    pub fn get_candidate_pairs_stats(&self, now: Instant) -> Vec<CandidatePairStats> {
225        let mut res = Vec::with_capacity(self.candidate_pairs.len());
226        for cp in &self.candidate_pairs {
227            let stat = CandidatePairStats {
228                timestamp: now,
229                local_candidate_id: self.local_candidates[cp.local_index].id().to_string(),
230                remote_candidate_id: self.remote_candidates[cp.remote_index].id().to_string(),
231                state: cp.state,
232                nominated: cp.nominated,
233                // STUN transaction stats
234                requests_sent: cp.requests_sent,
235                requests_received: cp.requests_received,
236                responses_sent: cp.responses_sent,
237                responses_received: cp.responses_received,
238                consent_requests_sent: cp.consent_requests_sent,
239                // RTT tracking (convert Duration to seconds as f64)
240                total_round_trip_time: cp.total_round_trip_time.as_secs_f64(),
241                current_round_trip_time: cp.current_round_trip_time.as_secs_f64(),
242                ..CandidatePairStats::new(now)
243            };
244            res.push(stat);
245        }
246        res
247    }
248
249    /// Returns a list of local candidates stats.
250    pub fn get_local_candidates_stats(&self, now: Instant) -> Vec<CandidateStats> {
251        let mut res = Vec::with_capacity(self.local_candidates.len());
252        for c in &self.local_candidates {
253            let stat = CandidateStats {
254                timestamp: now,
255                id: c.id().to_string(),
256                network_type: c.network_type(),
257                ip: c.address().to_owned(),
258                port: c.port(),
259                candidate_type: c.candidate_type(),
260                priority: c.priority(),
261                // URL string
262                relay_protocol: "udp".to_owned(),
263                // Deleted bool
264                ..CandidateStats::new(now)
265            };
266            res.push(stat);
267        }
268        res
269    }
270
271    /// Returns a list of remote candidates stats.
272    pub fn get_remote_candidates_stats(&self, now: Instant) -> Vec<CandidateStats> {
273        let mut res = Vec::with_capacity(self.remote_candidates.len());
274        for c in &self.remote_candidates {
275            let stat = CandidateStats {
276                timestamp: now,
277                id: c.id().to_string(),
278                network_type: c.network_type(),
279                ip: c.address().to_owned(),
280                port: c.port(),
281                candidate_type: c.candidate_type(),
282                priority: c.priority(),
283                // URL string
284                relay_protocol: "udp".to_owned(),
285                // Deleted bool
286                ..CandidateStats::new(now)
287            };
288            res.push(stat);
289        }
290        res
291    }
292}