232 lines · 6.8 KB · Rust
RawDownload
use gpui::{Context, Task, WeakEntity};use livekit_client::ConnectionQuality;use std::time::Duration; use super::room::Room; #[derive(Clone, Default)]pub struct CallStats {    pub connection_quality: Option<ConnectionQuality>,    pub effective_quality: Option<ConnectionQuality>,    pub latency_ms: Option<f64>,    pub jitter_ms: Option<f64>,    pub packet_loss_pct: Option<f64>,    pub input_lag: Option<Duration>,} pub struct CallDiagnostics {    stats: CallStats,    room: WeakEntity<Room>,    poll_task: Option<Task<()>>,    stats_update_task: Option<Task<()>>,} impl CallDiagnostics {    pub fn new(room: WeakEntity<Room>, cx: &mut Context<Self>) -> Self {        let mut this = Self {            stats: CallStats::default(),            room,            poll_task: None,            stats_update_task: None,        };        this.start_polling(cx);        this    }     pub fn stats(&self) -> &CallStats {        &self.stats    }     fn start_polling(&mut self, cx: &mut Context<Self>) {        self.poll_task = Some(cx.spawn(async move |this, cx| {            loop {                if this.update(cx, |this, cx| this.poll_stats(cx)).is_err() {                    break;                }                cx.background_executor().timer(Duration::from_secs(1)).await;            }        }));    }     fn poll_stats(&mut self, cx: &mut Context<Self>) {        let Some(room) = self.room.upgrade() else {            return;        };         let connection_quality = room.read(cx).connection_quality();        self.stats.connection_quality = Some(connection_quality);        self.stats.input_lag = room.read(cx).input_lag();         let stats_future = room.read(cx).get_stats(cx);         let background_task = cx.background_executor().spawn(async move {            let session_stats = stats_future.await;            session_stats.map(|stats| compute_network_stats(&stats))        });         self.stats_update_task = Some(cx.spawn(async move |this, cx| {            let result = background_task.await;            this.update(cx, |this, cx| {                if let Some(computed) = result {                    this.stats.latency_ms = computed.latency_ms;                    this.stats.jitter_ms = computed.jitter_ms;                    this.stats.packet_loss_pct = computed.packet_loss_pct;                }                let quality = this                    .stats                    .connection_quality                    .unwrap_or(ConnectionQuality::Lost);                this.stats.effective_quality =                    Some(effective_connection_quality(quality, &this.stats));                cx.notify();            })            .ok();        }));    }} struct ComputedNetworkStats {    latency_ms: Option<f64>,    jitter_ms: Option<f64>,    packet_loss_pct: Option<f64>,} fn compute_network_stats(stats: &livekit_client::SessionStats) -> ComputedNetworkStats {    let mut min_rtt: Option<f64> = None;    let mut max_jitter: Option<f64> = None;    let mut total_packets_received: u64 = 0;    let mut total_packets_lost: i64 = 0;     let all_stats = stats        .publisher_stats        .iter()        .chain(stats.subscriber_stats.iter());     for stat in all_stats {        extract_metrics(            stat,            &mut min_rtt,            &mut max_jitter,            &mut total_packets_received,            &mut total_packets_lost,        );    }     let total_expected = total_packets_received as i64 + total_packets_lost;    let packet_loss_pct = if total_expected > 0 {        Some((total_packets_lost as f64 / total_expected as f64) * 100.0)    } else {        None    };     ComputedNetworkStats {        latency_ms: min_rtt.map(|rtt| rtt * 1000.0),        jitter_ms: max_jitter.map(|j| j * 1000.0),        packet_loss_pct,    }} #[cfg(all(    not(rust_analyzer),    any(        test,        feature = "test-support",        all(target_os = "windows", target_env = "gnu"),        target_os = "freebsd"    )))]fn extract_metrics(    _stat: &livekit_client::RtcStats,    _min_rtt: &mut Option<f64>,    _max_jitter: &mut Option<f64>,    _total_packets_received: &mut u64,    _total_packets_lost: &mut i64,) {} #[cfg(any(    rust_analyzer,    not(any(        test,        feature = "test-support",        all(target_os = "windows", target_env = "gnu"),        target_os = "freebsd"    ))))]fn extract_metrics(    stat: &livekit_client::RtcStats,    min_rtt: &mut Option<f64>,    max_jitter: &mut Option<f64>,    total_packets_received: &mut u64,    total_packets_lost: &mut i64,) {    use livekit_client::RtcStats;     match stat {        RtcStats::CandidatePair(pair) => {            let rtt = pair.candidate_pair.current_round_trip_time;            if rtt > 0.0 {                *min_rtt = Some(match *min_rtt {                    Some(current) => current.min(rtt),                    None => rtt,                });            }        }        RtcStats::InboundRtp(inbound) => {            let jitter = inbound.received.jitter;            if jitter > 0.0 {                *max_jitter = Some(match *max_jitter {                    Some(current) => current.max(jitter),                    None => jitter,                });            }            *total_packets_received += inbound.received.packets_received;            *total_packets_lost += inbound.received.packets_lost;        }        RtcStats::RemoteInboundRtp(remote_inbound) => {            let rtt = remote_inbound.remote_inbound.round_trip_time;            if rtt > 0.0 {                *min_rtt = Some(match *min_rtt {                    Some(current) => current.min(rtt),                    None => rtt,                });            }        }        _ => {}    }} fn metric_quality(value: f64, warn_threshold: f64, error_threshold: f64) -> ConnectionQuality {    if value < warn_threshold {        ConnectionQuality::Excellent    } else if value < error_threshold {        ConnectionQuality::Poor    } else {        ConnectionQuality::Lost    }} /// Computes the effective connection quality by taking the worst of the/// LiveKit-reported quality and each individual metric rating.fn effective_connection_quality(    livekit_quality: ConnectionQuality,    stats: &CallStats,) -> ConnectionQuality {    let mut worst = livekit_quality;     if let Some(latency) = stats.latency_ms {        worst = worst.max(metric_quality(latency, 100.0, 300.0));    }    if let Some(jitter) = stats.jitter_ms {        worst = worst.max(metric_quality(jitter, 30.0, 75.0));    }    if let Some(loss) = stats.packet_loss_pct {        worst = worst.max(metric_quality(loss, 1.0, 5.0));    }    if let Some(lag) = stats.input_lag {        let lag_ms = lag.as_secs_f64() * 1000.0;        worst = worst.max(metric_quality(lag_ms, 20.0, 50.0));    }     worst}