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 #[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 #[serde(rename = "connectedUsers")]
308 pub connected: usize,
309 #[serde(rename = "disconnectedUsers")]
311 pub disconnected: usize,
312 #[serde(rename = "totalUsers")]
314 pub total: usize,
315 #[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}