Skip to main content

o_sfu_telemetry/diagnostics/
types.rs

1//! Serialized diagnostics response types and small format helpers.
2//!
3//!structs and enums shape returned by the diagnostics tools
4
5use std::collections::BTreeMap;
6
7use o_sfu_model::{RecordingState, UserId, UserInfo};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum DiagnosticsTransportHealth {
13    Connected,
14    Disconnected,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum DiagnosticsMediaKind {
20    Audio,
21    Video,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum DiagnosticsRouteState {
27    Active,
28    Inactive,
29    Pending,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum DiagnosticsSourceSelector {
35    Open,
36    Encoding,
37    RoomPolicyPinned,
38    RoomPolicyFeatured,
39    RoomPolicyReadableDetail,
40    RoomPolicyActiveSpeaker,
41    RoomPolicyVisibleThumbnail,
42    RoomPolicyHidden,
43    RoomPolicyOverflow,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum DiagnosticsSourceSelectionReason {
49    Open,
50    ReceiverAdaptation,
51    RoomPolicy,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "snake_case")]
56pub enum DiagnosticsPolicyPauseReason {
57    BudgetPressure,
58    HiddenTile,
59    OverflowTile,
60    MissingUsableLayer,
61    AudioSpeakerLimit,
62    ReceiverDeafened,
63    VideoDownloadLimit,
64    SourceBitrateLimit,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum DiagnosticsVideoLayoutRole {
70    Pinned,
71    Featured,
72    ReadableDetail,
73    ActiveSpeaker,
74    VisibleThumbnail,
75    Hidden,
76    Overflow,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81pub enum DiagnosticsVideoRoutePriority {
82    PinnedOrFeatured,
83    ReadableDetail,
84    ActiveSpeaker,
85    VisibleThumbnail,
86    HiddenOrOverflow,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum DiagnosticsActiveSpeakerState {
92    Active,
93    Idle,
94    Blocked,
95    RecentlyExpired,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100pub enum DiagnosticsActiveSpeakerReason {
101    Vad,
102    AudioLevel,
103    AudioLevelWarmup,
104    VadFalse,
105    LowNoise,
106    BelowSpeechThreshold,
107    MissingAudioMetadata,
108    Expired,
109    NoMetadata,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct DiagnosticsActiveSpeaker {
115    pub state: DiagnosticsActiveSpeakerState,
116    pub reason: DiagnosticsActiveSpeakerReason,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub last_audio_level_dbov: Option<i8>,
119    pub confidence_observations: u8,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub hold_remaining_ms: Option<u64>,
122}
123
124impl DiagnosticsActiveSpeaker {
125    #[must_use]
126    pub const fn idle() -> Self {
127        Self {
128            state: DiagnosticsActiveSpeakerState::Idle,
129            reason: DiagnosticsActiveSpeakerReason::NoMetadata,
130            last_audio_level_dbov: None,
131            confidence_observations: 0,
132            hold_remaining_ms: None,
133        }
134    }
135}
136
137#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct DiagnosticsIncomingBitrate {
140    #[serde(default)]
141    pub by_stream_bps: BTreeMap<String, u64>,
142    /// total bitrate serialized as `totalBps`.
143    #[serde(rename = "totalBps")]
144    pub total: u64,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct DiagnosticsQualitySummary {
150    pub current_incoming_bitrate: DiagnosticsIncomingBitrate,
151    pub sampled_metrics_available: bool,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub latest_bwe_bps: Option<u64>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub rtt_ms: Option<u64>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub ingress_loss_ppm: Option<u64>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub egress_loss_ppm: Option<u64>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub egress_jitter_rtp_timestamp_units: Option<u64>,
162    pub sample_count: u64,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct DiagnosticsUserTransport {
168    pub connection_id: u64,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub health: Option<DiagnosticsTransportHealth>,
171    pub media_worker_id: usize,
172    pub quality_summary: DiagnosticsQualitySummary,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct DiagnosticsPublication {
178    pub active: bool,
179    pub encoding_ids: Vec<u64>,
180    pub media_kind: DiagnosticsMediaKind,
181    pub source_id: u64,
182    pub stream_id: String,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub transport_media_id: Option<u64>,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct DiagnosticsSourceEncoding {
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub codec: Option<String>,
192    pub encoding_id: u64,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub max_bitrate_bps: Option<u64>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub resolution_scale: Option<u16>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub max_framerate: Option<u16>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub policy_role: Option<String>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub payload_type: Option<u8>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub primary_ssrc: Option<u32>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub repair_ssrc: Option<u32>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub rid: Option<String>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub last_packet_age_ms: Option<u64>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub last_keyframe_age_ms: Option<u64>,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct DiagnosticsSource {
218    pub active: bool,
219    #[serde(default, skip_serializing_if = "Option::is_none")]
220    pub active_speaker: Option<DiagnosticsActiveSpeaker>,
221    pub current_incoming_bitrate_bps: u64,
222    pub encodings: Vec<DiagnosticsSourceEncoding>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub last_packet_age_ms: Option<u64>,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub last_keyframe_age_ms: Option<u64>,
227    pub media_kind: DiagnosticsMediaKind,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub mid: Option<String>,
230    pub owner_user_id: UserId,
231    pub source_id: u64,
232    pub stream_id: String,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub transport_media_id: Option<u64>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub video_bitrate_cap_bps: Option<u64>,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct DiagnosticsSourceSelection {
242    pub active: bool,
243    pub active_video_route_count: usize,
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub latest_receiver_bandwidth_estimate_bps: Option<u64>,
246    pub policy_allows_delivery: bool,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub policy_pause_reason: Option<DiagnosticsPolicyPauseReason>,
249    pub pressure_observations: u8,
250    pub selection_reason: DiagnosticsSourceSelectionReason,
251    pub selector: DiagnosticsSourceSelector,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub selected_estimated_bitrate_bps: Option<u64>,
254    pub selected_video_bitrate_bps: u64,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub selected_video_budget_bps: Option<u64>,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub selected_encoding_id: Option<u64>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub selected_rid: Option<String>,
261    pub upgrade_observations: u8,
262}
263
264#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct DiagnosticsSubscription {
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub consumer_transport_media_id: Option<u64>,
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub layout_priority: Option<DiagnosticsVideoRoutePriority>,
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub layout_role: Option<DiagnosticsVideoLayoutRole>,
273    pub producer_user_id: UserId,
274    pub selection: DiagnosticsSourceSelection,
275    pub source_id: u64,
276    pub state: DiagnosticsRouteState,
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub source_transport_media_id: Option<u64>,
279    pub stream_id: String,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct DiagnosticsUserView {
285    pub publications: Vec<DiagnosticsPublication>,
286    pub user_id: UserId,
287    pub user_info: UserInfo,
288    pub subscriptions: Vec<DiagnosticsSubscription>,
289    pub transport: DiagnosticsUserTransport,
290}
291
292#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
293#[serde(rename_all = "camelCase")]
294pub struct DiagnosticsTransportCounts {
295    /// connected count serialized as `connectedUsers`.
296    #[serde(rename = "connectedUsers")]
297    pub connected: usize,
298    /// disconnected count serialized as `disconnectedUsers`.
299    #[serde(rename = "disconnectedUsers")]
300    pub disconnected: usize,
301    /// total count serialized as `totalUsers`.
302    #[serde(rename = "totalUsers")]
303    pub total: usize,
304    /// unknown count serialized as `unknownUsers`.
305    #[serde(rename = "unknownUsers")]
306    pub unknown: usize,
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
310#[serde(rename_all = "camelCase")]
311pub struct DiagnosticsRoomSummary {
312    pub create_date: String,
313    pub media_worker_id: usize,
314    pub publication_count: usize,
315    pub recording_state: RecordingState,
316    pub remote_address: String,
317    pub source_count: usize,
318    pub user_count: usize,
319    pub subscription_count: usize,
320    pub transport: DiagnosticsTransportCounts,
321    pub uuid: String,
322    pub web_rtc_enabled: bool,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
326#[serde(rename_all = "camelCase")]
327pub struct DiagnosticsUserSummary {
328    pub audio_incoming_bitrate_bps: u64,
329    pub camera_incoming_bitrate_bps: u64,
330    pub connection_id: u64,
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub health: Option<DiagnosticsTransportHealth>,
333    pub incoming_bitrate_bps: u64,
334    pub media_worker_id: usize,
335    pub publication_count: usize,
336    pub room_id: String,
337    pub screen_incoming_bitrate_bps: u64,
338    pub subscription_count: usize,
339    pub user_id: UserId,
340    pub user_key: String,
341}
342
343#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct DiagnosticsWorkerPressure {
346    pub command_backlog_depth: usize,
347    pub egress_bitrate_bps: u64,
348    pub packet_loop_delay_ms: Option<u64>,
349    pub relay_mailbox_depth: usize,
350    pub worker_pressure_score: u8,
351}
352
353#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
354#[serde(rename_all = "camelCase")]
355pub struct DiagnosticsWorkerSummary {
356    pub connected_user_count: usize,
357    pub disconnected_user_count: usize,
358    pub media_worker_id: usize,
359    pub pressure: DiagnosticsWorkerPressure,
360    pub publication_count: usize,
361    pub room_count: usize,
362    pub subscription_count: usize,
363    pub unknown_user_count: usize,
364    pub user_count: usize,
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub struct DiagnosticsRoomDetail {
370    pub users: Vec<DiagnosticsUserView>,
371    pub sources: Vec<DiagnosticsSource>,
372    pub summary: DiagnosticsRoomSummary,
373}
374
375#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct DiagnosticsUserDetail {
378    pub room_id: String,
379    pub recording_state: RecordingState,
380    pub user: DiagnosticsUserView,
381}
382
383#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct DiagnosticsSummaryResponse {
386    pub rooms_active: usize,
387    pub publications_active: usize,
388    pub recording_rooms_active: usize,
389    pub users_active: usize,
390    pub subscriptions_active: usize,
391    pub transport: DiagnosticsTransportCounts,
392}