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    #[serde(skip_serializing_if = "Option::is_none")]
174    pub video_soft_pause_remaining_ms: Option<u64>,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct DiagnosticsPublication {
180    pub active: bool,
181    pub encoding_ids: Vec<u64>,
182    pub media_kind: DiagnosticsMediaKind,
183    pub source_id: u64,
184    pub stream_id: String,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub transport_media_id: Option<u64>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct DiagnosticsSourceEncoding {
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub codec: Option<String>,
194    pub encoding_id: u64,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub max_bitrate_bps: Option<u64>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub resolution_scale: Option<u16>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub max_framerate: Option<u16>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub policy_role: Option<String>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub payload_type: Option<u8>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub primary_ssrc: Option<u32>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub repair_ssrc: Option<u32>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub rid: Option<String>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub last_packet_age_ms: Option<u64>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub last_keyframe_age_ms: Option<u64>,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
218#[serde(rename_all = "camelCase")]
219pub struct DiagnosticsSource {
220    pub active: bool,
221    #[serde(default, skip_serializing_if = "Option::is_none")]
222    pub active_speaker: Option<DiagnosticsActiveSpeaker>,
223    pub current_incoming_bitrate_bps: u64,
224    pub encodings: Vec<DiagnosticsSourceEncoding>,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub last_packet_age_ms: Option<u64>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub last_keyframe_age_ms: Option<u64>,
229    pub media_kind: DiagnosticsMediaKind,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub mid: Option<String>,
232    pub owner_user_id: UserId,
233    pub source_id: u64,
234    pub stream_id: String,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub transport_media_id: Option<u64>,
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub video_bitrate_cap_bps: Option<u64>,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct DiagnosticsPendingUpgrade {
244    pub selector: DiagnosticsSourceSelector,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub encoding_id: Option<u64>,
247    pub remaining_ms: u64,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
251#[serde(rename_all = "camelCase")]
252pub struct DiagnosticsSourceSelection {
253    pub active: bool,
254    pub active_video_route_count: usize,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub latest_receiver_bandwidth_estimate_bps: Option<u64>,
257    pub policy_allows_delivery: bool,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub policy_pause_reason: Option<DiagnosticsPolicyPauseReason>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub pending_upgrade: Option<DiagnosticsPendingUpgrade>,
262    pub selection_reason: DiagnosticsSourceSelectionReason,
263    pub selector: DiagnosticsSourceSelector,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub selected_estimated_bitrate_bps: Option<u64>,
266    pub selected_video_bitrate_bps: u64,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub selected_video_budget_bps: Option<u64>,
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub selected_encoding_id: Option<u64>,
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub selected_rid: Option<String>,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
276#[serde(rename_all = "camelCase")]
277pub struct DiagnosticsSubscription {
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub consumer_transport_media_id: Option<u64>,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub layout_priority: Option<DiagnosticsVideoRoutePriority>,
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub layout_role: Option<DiagnosticsVideoLayoutRole>,
284    pub producer_user_id: UserId,
285    pub selection: DiagnosticsSourceSelection,
286    pub source_id: u64,
287    pub state: DiagnosticsRouteState,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub source_transport_media_id: Option<u64>,
290    pub stream_id: String,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub struct DiagnosticsUserView {
296    pub publications: Vec<DiagnosticsPublication>,
297    pub user_id: UserId,
298    pub user_info: UserInfo,
299    pub subscriptions: Vec<DiagnosticsSubscription>,
300    pub transport: DiagnosticsUserTransport,
301}
302
303#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct DiagnosticsTransportCounts {
306    /// connected count serialized as `connectedUsers`.
307    #[serde(rename = "connectedUsers")]
308    pub connected: usize,
309    /// disconnected count serialized as `disconnectedUsers`.
310    #[serde(rename = "disconnectedUsers")]
311    pub disconnected: usize,
312    /// total count serialized as `totalUsers`.
313    #[serde(rename = "totalUsers")]
314    pub total: usize,
315    /// unknown count serialized as `unknownUsers`.
316    #[serde(rename = "unknownUsers")]
317    pub unknown: usize,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase")]
322pub struct DiagnosticsRoomSummary {
323    pub create_date: String,
324    pub media_worker_id: usize,
325    pub publication_count: usize,
326    pub recording_state: RecordingState,
327    pub remote_address: String,
328    pub source_count: usize,
329    pub user_count: usize,
330    pub subscription_count: usize,
331    pub transport: DiagnosticsTransportCounts,
332    pub uuid: String,
333    pub web_rtc_enabled: bool,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
337#[serde(rename_all = "camelCase")]
338pub struct DiagnosticsUserSummary {
339    pub audio_incoming_bitrate_bps: u64,
340    pub camera_incoming_bitrate_bps: u64,
341    pub connection_id: u64,
342    #[serde(skip_serializing_if = "Option::is_none")]
343    pub health: Option<DiagnosticsTransportHealth>,
344    pub incoming_bitrate_bps: u64,
345    pub media_worker_id: usize,
346    pub publication_count: usize,
347    pub room_id: String,
348    pub screen_incoming_bitrate_bps: u64,
349    pub subscription_count: usize,
350    pub user_id: UserId,
351    pub user_key: String,
352}
353
354#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
355#[serde(rename_all = "camelCase")]
356pub struct DiagnosticsWorkerPressure {
357    pub command_backlog_depth: usize,
358    pub egress_bitrate_bps: u64,
359    pub packet_loop_delay_ms: Option<u64>,
360    pub relay_mailbox_depth: usize,
361    pub worker_pressure_score: u8,
362}
363
364#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
365#[serde(rename_all = "camelCase")]
366pub struct DiagnosticsWorkerSummary {
367    pub connected_user_count: usize,
368    pub disconnected_user_count: usize,
369    pub media_worker_id: usize,
370    pub pressure: DiagnosticsWorkerPressure,
371    pub publication_count: usize,
372    pub room_count: usize,
373    pub subscription_count: usize,
374    pub unknown_user_count: usize,
375    pub user_count: usize,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub struct DiagnosticsRoomDetail {
381    pub users: Vec<DiagnosticsUserView>,
382    pub sources: Vec<DiagnosticsSource>,
383    pub summary: DiagnosticsRoomSummary,
384}
385
386#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct DiagnosticsUserDetail {
389    pub room_id: String,
390    pub recording_state: RecordingState,
391    pub user: DiagnosticsUserView,
392}
393
394#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct DiagnosticsSummaryResponse {
397    pub rooms_active: usize,
398    pub publications_active: usize,
399    pub recording_rooms_active: usize,
400    pub users_active: usize,
401    pub subscriptions_active: usize,
402    pub transport: DiagnosticsTransportCounts,
403}