1use 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 #[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 #[serde(rename = "connectedUsers")]
297 pub connected: usize,
298 #[serde(rename = "disconnectedUsers")]
300 pub disconnected: usize,
301 #[serde(rename = "totalUsers")]
303 pub total: usize,
304 #[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}