Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
233 lines
6.8 KiB
Rust
233 lines
6.8 KiB
Rust
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
|
|
}
|