use std::time::Instant;
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use unicode_width::UnicodeWidthStr;
use super::design;
use super::theme;
use super::tunnels_format::{ClientActivity, RosterRow, build_roster, format_age, format_bps};
use crate::app::App;
use crate::tunnel::{TunnelRule, format_uptime};
use crate::tunnel_live::{
ChannelEventKind, ChannelKind, ClientPeer, DisplayClient, TunnelLiveSnapshot,
};
const INDENT: &str = " ";
const SWIMLANE_WINDOW_SECS: u64 = 20;
const SWIMLANE_MIN_ROWS: usize = 2;
const ROSTER_MIN_VISIBLE: usize = 3;
pub(super) const DASHBOARD_MIN_BODY_HEIGHT: u16 = 8;
struct SelectedRow {
alias: String,
rule: TunnelRule,
started_at: Option<Instant>,
}
fn selected_row(app: &App) -> Option<SelectedRow> {
let sel = app.ui.tunnels_overview_state.selected()?;
let pairs = super::tunnels_overview::visible_pairs(app);
let (alias, rule) = pairs.into_iter().nth(sel)?;
let started_at = app.tunnels.active.get(&alias).map(|a| a.started_at);
Some(SelectedRow {
alias,
rule,
started_at,
})
}
struct LiveView {
is_active: bool,
uptime_secs: u64,
clients: Vec<DisplayClient>,
last_exit: Option<(i32, String)>,
closed_events_smooth: Vec<(u32, ChannelKind, f64, f64)>,
currently_open: Vec<(u32, f64, ChannelKind)>,
#[allow(dead_code)]
peak_concurrent: u32,
}
impl LiveView {
fn from_snapshot(snap: &TunnelLiveSnapshot) -> Self {
let mut clients = snap.clients.clone();
clients.sort_by_key(|c| c.age_secs);
let closed_events_smooth: Vec<(u32, ChannelKind, f64, f64)> = snap
.events
.iter()
.filter(|e| e.kind == ChannelEventKind::Close && e.age_secs <= SWIMLANE_WINDOW_SECS)
.map(|e| {
let close_age = e.age_secs as f64;
let dur = e.duration_secs.unwrap_or(0) as f64;
(e.channel_id, e.channel_kind, close_age, dur)
})
.collect();
let currently_open: Vec<(u32, f64, ChannelKind)> = snap
.currently_open
.iter()
.map(|(id, age, kind)| (*id, *age as f64, *kind))
.collect();
Self {
is_active: snap.last_exit.is_none(),
uptime_secs: snap.uptime_secs,
clients,
last_exit: snap.last_exit.clone(),
closed_events_smooth,
currently_open,
peak_concurrent: snap.peak_concurrent,
}
}
fn from_runtime(app: &App, alias: &str, rule: &TunnelRule) -> Option<Self> {
let tunnel = app.tunnels.active.get(alias)?;
let now = Instant::now();
let uptime_secs = now.saturating_duration_since(tunnel.started_at).as_secs();
let mut clients: Vec<DisplayClient> = app
.tunnels
.clients
.get(&rule.bind_port)
.map(|peers| {
peers
.iter()
.map(|p: &ClientPeer| DisplayClient {
src: p.src.clone(),
process: p.process.clone(),
age_secs: now.saturating_duration_since(p.since).as_secs(),
pid: p.pid,
responsible_app: p.responsible_app.clone(),
current_rx_bps: p.current_rx_bps,
current_tx_bps: p.current_tx_bps,
viz_history: app
.tunnels
.peer_viz
.get(&(rule.bind_port, p.src.clone()))
.copied()
.unwrap_or([0u64; crate::tunnel_live::PEER_VIZ_BUCKETS]),
throughput_ready: p.last_sample_at.is_some(),
})
.collect()
})
.unwrap_or_default();
clients.sort_by_key(|c| c.age_secs);
let closed_events_smooth: Vec<(u32, ChannelKind, f64, f64)> = tunnel
.live
.events
.iter()
.filter_map(|e| {
if e.kind != ChannelEventKind::Close {
return None;
}
let kind = e.channel_kind?;
if !kind.is_user_visible() {
return None;
}
let close_age = now.saturating_duration_since(e.at).as_secs_f64();
if close_age > SWIMLANE_WINDOW_SECS as f64 {
return None;
}
let dur = e
.opened_at
.map(|o| e.at.saturating_duration_since(o).as_secs_f64())
.unwrap_or(0.0);
Some((e.channel_id, kind, close_age, dur))
})
.collect();
let currently_open: Vec<(u32, f64, ChannelKind)> = tunnel
.live
.channel_open
.iter()
.filter(|(_, (_, kind))| kind.is_user_visible())
.map(|(id, (open_at, kind))| {
(
*id,
now.saturating_duration_since(*open_at).as_secs_f64(),
*kind,
)
})
.collect();
Some(Self {
is_active: tunnel.live.last_exit.is_none(),
uptime_secs,
clients,
last_exit: tunnel.live.last_exit.clone(),
closed_events_smooth,
currently_open,
peak_concurrent: tunnel.live.peak_concurrent,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct Lane {
channel_id: u32,
kind: ChannelKind,
open_age_secs: f64,
close_age_secs: Option<f64>,
}
impl Lane {
fn is_open(&self) -> bool {
self.close_age_secs.is_none()
}
}
fn build_lanes(live: &LiveView) -> Vec<Lane> {
let mut lanes: Vec<Lane> = Vec::new();
for (id, age, kind) in &live.currently_open {
lanes.push(Lane {
channel_id: *id,
kind: *kind,
open_age_secs: *age,
close_age_secs: None,
});
}
for (id, kind, close_age, dur) in &live.closed_events_smooth {
let open_age = close_age + dur;
lanes.push(Lane {
channel_id: *id,
kind: *kind,
open_age_secs: open_age,
close_age_secs: Some(*close_age),
});
}
lanes.sort_by(|a, b| match (a.is_open(), b.is_open()) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => a
.open_age_secs
.partial_cmp(&b.open_age_secs)
.unwrap_or(std::cmp::Ordering::Equal),
});
lanes
}
pub fn render(frame: &mut Frame, app: &mut App, area: Rect, spinner_tick: u64) {
let row = match selected_row(app) {
Some(r) => r,
None => {
let block = design::main_block_line(Line::default());
frame.render_widget(block, area);
return;
}
};
let live: Option<LiveView> = if app.demo_mode {
app.tunnels
.demo_live_snapshots
.get(&row.alias)
.map(LiveView::from_snapshot)
} else {
LiveView::from_runtime(app, &row.alias, &row.rule)
};
let lanes: Vec<Lane> = live.as_ref().map(build_lanes).unwrap_or_default();
let roster: Vec<RosterRow> = live
.as_ref()
.map(|l| build_roster(&l.clients))
.unwrap_or_default();
let title = build_title(&row, live.as_ref(), &lanes, &roster, spinner_tick);
let block = design::main_block_line(title);
let inner = block.inner(area);
frame.render_widget(block, area);
if inner.height == 0 {
return;
}
let inner_w = inner.width as usize;
if lanes.is_empty() && roster.is_empty() {
render_waiting_hint(frame, inner, inner_w);
return;
}
let inner_h = inner.height as usize;
let alloc = allocate_sections(lanes.len(), roster.len(), inner_h);
let mut y: u16 = 1;
let visible_roster = &roster[..alloc.roster_visible];
let (identity_w, spark_w) = roster_layout(visible_roster, inner_w);
let phase = sparkline_phase(app);
for r in visible_roster {
paint_at(
frame,
inner,
y,
roster_line(r, inner_w, identity_w, spark_w, phase),
);
y += 1;
}
if alloc.roster_truncated > 0 {
paint_at(frame, inner, y, more_line(alloc.roster_truncated, inner_w));
y += 1;
}
if alloc.roster_visible > 0 && alloc.lanes_visible > 0 {
y += 1; }
let chart_w = chart_width(inner_w);
for lane in lanes.iter().take(alloc.lanes_visible) {
paint_at(frame, inner, y, swimlane_lane_line(lane, chart_w, inner_w));
y += 1;
}
if alloc.lanes_truncated > 0 {
paint_at(frame, inner, y, more_line(alloc.lanes_truncated, inner_w));
}
}
fn sparkline_phase(app: &App) -> f64 {
if app.demo_mode {
return 0.0;
}
let Some(last) = app.tunnels.peer_viz_last_push else {
return 0.0;
};
let Some(prev) = app.tunnels.peer_viz_prev_push else {
return 0.0;
};
let interval_ms = last.saturating_duration_since(prev).as_millis() as f64;
if interval_ms <= 0.0 {
return 0.0;
}
let elapsed_ms = Instant::now().saturating_duration_since(last).as_millis() as f64;
(elapsed_ms / interval_ms).clamp(0.0, 1.0)
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
struct SectionAlloc {
lanes_visible: usize,
lanes_truncated: usize,
roster_visible: usize,
roster_truncated: usize,
}
fn allocate_sections(lanes_total: usize, roster_total: usize, inner_h: usize) -> SectionAlloc {
if inner_h == 0 || (lanes_total == 0 && roster_total == 0) {
return SectionAlloc::default();
}
let separator = if lanes_total > 0 && roster_total > 0 {
1
} else {
0
};
let budget = inner_h.saturating_sub(2 + separator);
if lanes_total + roster_total <= budget {
return SectionAlloc {
lanes_visible: lanes_total,
lanes_truncated: 0,
roster_visible: roster_total,
roster_truncated: 0,
};
}
let roster_cap = ((budget * 2) / 3).max(ROSTER_MIN_VISIBLE).min(budget);
let (roster_visible, roster_truncated) = section_split(roster_total, roster_cap);
let roster_used = roster_visible + (if roster_truncated > 0 { 1 } else { 0 });
let lanes_budget = budget.saturating_sub(roster_used);
let (lanes_visible, lanes_truncated) = if lanes_total == 0 {
(0, 0)
} else {
section_split(lanes_total, lanes_budget.max(SWIMLANE_MIN_ROWS).min(budget))
};
SectionAlloc {
lanes_visible,
lanes_truncated,
roster_visible,
roster_truncated,
}
}
fn section_split(total: usize, budget: usize) -> (usize, usize) {
if total == 0 || budget == 0 {
return (0, 0);
}
if total <= budget {
return (total, 0);
}
let visible = budget.saturating_sub(1).max(1);
let truncated = total - visible;
(visible, truncated)
}
fn paint_at(frame: &mut Frame, inner: Rect, y_offset: u16, line: Line<'static>) {
if y_offset >= inner.height {
return;
}
let area = Rect::new(inner.x, inner.y + y_offset, inner.width, 1);
frame.render_widget(Paragraph::new(line), area);
}
fn build_title(
row: &SelectedRow,
live: Option<&LiveView>,
lanes: &[Lane],
roster: &[RosterRow],
spinner_tick: u64,
) -> Line<'static> {
let active = live.is_some_and(|l| l.is_active);
let failed = live.is_some_and(|l| l.last_exit.is_some());
let uptime_secs = live
.map(|l| l.uptime_secs)
.or_else(|| {
row.started_at
.map(|s| Instant::now().saturating_duration_since(s).as_secs())
})
.unwrap_or(0);
let uptime = format_uptime(std::time::Duration::from_secs(uptime_secs));
let (dot_glyph, dot_style, state_word, state_style): (&str, Style, &str, Style) = if failed {
("\u{25CF}", theme::error(), "broken", theme::error())
} else if active {
(
"\u{25CF}",
theme::online_dot_pulsing(spinner_tick),
"active",
theme::online_dot_pulsing(spinner_tick),
)
} else {
("\u{25CB}", theme::muted(), "stopped", theme::muted())
};
let _ = (lanes, roster);
let mut spans: Vec<Span<'static>> = vec![
Span::raw(" "),
Span::styled(dot_glyph.to_string(), dot_style),
Span::raw(" "),
Span::styled(state_word.to_string(), state_style),
];
if active {
spans.push(Span::styled(" \u{00B7} ".to_string(), theme::muted()));
spans.push(Span::styled(format!("up {}", uptime), theme::muted()));
}
if let Some((_, err)) = live.and_then(|l| l.last_exit.as_ref()) {
spans.push(Span::styled(" \u{00B7} ".to_string(), theme::muted()));
spans.push(Span::styled(err.clone(), theme::error()));
}
spans.push(Span::raw(" "));
Line::from(spans)
}
fn render_waiting_hint(frame: &mut Frame, inner: Rect, inner_w: usize) {
if inner.height == 0 {
return;
}
let mid = inner.height / 2;
let text = "waiting for first connection";
let pad = inner_w.saturating_sub(text.width()) / 2;
let line = Line::from(vec![
Span::raw(" ".repeat(pad)),
Span::styled(text.to_string(), theme::muted()),
]);
paint_at(frame, inner, mid, line);
}
fn swimlane_lane_line(lane: &Lane, chart_w: usize, inner_w: usize) -> Line<'static> {
let id_cell = format!("ch#{}", lane.channel_id);
let id_padded = pad_right(&id_cell, 6);
let kind_cell = pad_right(channel_kind_short(lane.kind), 7);
let chart_w_f = chart_w as f64;
let max_col = (chart_w_f - 1.0).max(0.0);
let age_to_col = |age: f64| -> f64 {
let clamped = age.clamp(0.0, SWIMLANE_WINDOW_SECS as f64);
let frac = (SWIMLANE_WINDOW_SECS as f64 - clamped) / SWIMLANE_WINDOW_SECS as f64;
(max_col * frac).clamp(0.0, max_col)
};
let open_pos = age_to_col(lane.open_age_secs);
let close_pos = match lane.close_age_secs {
Some(age) => age_to_col(age),
None => max_col,
};
let mut chart: Vec<char> = vec![' '; chart_w];
if chart_w > 0 && open_pos <= close_pos {
for (i, cell) in chart.iter_mut().enumerate().take(chart_w) {
let left_mid = i as f64 + 0.25;
let right_mid = i as f64 + 0.75;
let left_in = left_mid >= open_pos && left_mid <= close_pos;
let right_in = right_mid >= open_pos && right_mid <= close_pos;
*cell = match (left_in, right_in) {
(true, true) => '\u{2500}',
(true, false) => '\u{2574}',
(false, true) => '\u{2576}',
(false, false) => ' ',
};
}
let open_int = open_pos.floor() as usize;
if open_int < chart_w && lane.open_age_secs < SWIMLANE_WINDOW_SECS as f64 {
chart[open_int] = '\u{251C}';
}
let close_int = close_pos.ceil() as usize;
if close_int < chart_w {
chart[close_int] = match lane.close_age_secs {
None => '\u{25B8}',
Some(_) => '\u{2524}',
};
}
}
let chart_str: String = chart.iter().collect();
let line_style = if lane.is_open() {
theme::online_dot()
} else {
theme::muted()
};
let right_label = match lane.close_age_secs {
None => format_age(lane.open_age_secs.floor() as u64),
Some(close) => {
let dur = (lane.open_age_secs - close).max(0.0);
format_age(dur.floor() as u64)
}
};
let right_padded = pad_left(&right_label, 12);
let mut spans: Vec<Span<'static>> = vec![
Span::raw(INDENT),
Span::styled(id_padded, theme::muted()),
Span::raw(" "),
Span::styled(kind_cell, theme::muted()),
Span::styled(chart_str, line_style),
Span::raw(" "),
Span::styled(right_padded, theme::muted()),
];
let used: usize = spans.iter().map(|s| s.content.width()).sum();
if used < inner_w {
spans.push(Span::raw(" ".repeat(inner_w - used)));
}
Line::from(spans)
}
const CONNECTION_W: usize = 16;
const PID_W: usize = 13;
const BPS_W: usize = 11;
const SPARK_MIN_W: usize = 8;
const IDENTITY_MAX_W: usize = 36;
fn identity_spans(row: &RosterRow) -> (Vec<Span<'static>>, usize) {
use super::tunnels_format::helper_is_noise;
let process_style = if row.is_group || row.activity == ClientActivity::Active {
theme::bold()
} else if row.activity == ClientActivity::Idle {
theme::muted()
} else {
Style::default()
};
let hide_process = !row.is_group && !row.app.is_empty() && helper_is_noise(&row.app);
if hide_process {
let app_text = truncate(&row.app, IDENTITY_MAX_W);
let width = app_text.width();
let app_style = if row.activity == ClientActivity::Active {
theme::bold()
} else if row.activity == ClientActivity::Idle {
theme::muted()
} else {
Style::default()
};
return (vec![Span::styled(app_text, app_style)], width);
}
let mut spans: Vec<Span<'static>> = Vec::with_capacity(3);
let mut width = 0usize;
if !row.app.is_empty() {
let app_text = truncate(&row.app, IDENTITY_MAX_W);
width += app_text.width();
spans.push(Span::styled(app_text, theme::muted()));
let arrow = " \u{2192} ".to_string();
width += arrow.width();
spans.push(Span::styled(arrow, theme::muted()));
}
let proc_budget = IDENTITY_MAX_W.saturating_sub(width);
let proc_text = truncate(&row.process, proc_budget.max(1));
width += proc_text.width();
spans.push(Span::styled(proc_text, process_style));
(spans, width)
}
fn connection_text(row: &RosterRow) -> String {
let age = format_age(row.age_secs);
if row.is_group || row.src_port.is_empty() {
age
} else {
format!("{} \u{00B7} {}", row.src_port, age)
}
}
fn pid_text(row: &RosterRow) -> String {
use super::tunnels_format::helper_is_noise;
if row.is_group || row.pid == 0 {
return String::new();
}
if !row.app.is_empty() && helper_is_noise(&row.app) {
return String::new();
}
format!("[pid {}]", row.pid)
}
fn roster_line(
row: &RosterRow,
inner_w: usize,
identity_w: usize,
spark_w: usize,
phase: f64,
) -> Line<'static> {
let (mut id_spans, id_used) = identity_spans(row);
if id_used < identity_w {
id_spans.push(Span::raw(" ".repeat(identity_w - id_used)));
}
let connection_cell = pad_right(&connection_text(row), CONNECTION_W);
let pid_cell = pad_right(&pid_text(row), PID_W);
let combined_bps = row.current_rx_bps.saturating_add(row.current_tx_bps);
let (spark_text, spark_style) = if spark_w == 0 {
(String::new(), theme::muted())
} else if row.throughput_ready {
(
sparkline_for(&row.viz_history, spark_w, phase),
if combined_bps > 0 {
theme::accent()
} else {
theme::muted()
},
)
} else {
(" ".repeat(spark_w), theme::muted())
};
let (bps_text, bps_style) = if row.throughput_ready {
let bps = format_bps(combined_bps);
(
pad_left(&bps, BPS_W),
if combined_bps > 0 {
theme::accent_bold()
} else {
theme::muted()
},
)
} else {
(pad_left("\u{2014}", BPS_W), theme::muted())
};
let mut spans: Vec<Span<'static>> = vec![Span::raw(INDENT)];
spans.extend(id_spans);
spans.push(Span::raw(" "));
spans.push(Span::styled(connection_cell, theme::muted()));
spans.push(Span::raw(" "));
spans.push(Span::styled(pid_cell, theme::muted()));
spans.push(Span::raw(" "));
spans.push(Span::styled(spark_text, spark_style));
spans.push(Span::raw(" "));
spans.push(Span::styled(bps_text, bps_style));
let used: usize = spans.iter().map(|s| s.content.width()).sum();
if used < inner_w {
spans.push(Span::raw(" ".repeat(inner_w - used)));
}
Line::from(spans)
}
fn roster_layout(roster: &[RosterRow], inner_w: usize) -> (usize, usize) {
let identity_w = roster
.iter()
.map(|r| identity_spans(r).1)
.max()
.unwrap_or(0)
.min(IDENTITY_MAX_W);
let fixed = INDENT.width() + identity_w + 2 + CONNECTION_W + 2 + PID_W + 2 + 1 + BPS_W + 1;
let spark_w = if inner_w > fixed {
let avail = inner_w - fixed;
if avail >= SPARK_MIN_W { avail } else { 0 }
} else {
0
};
(identity_w, spark_w)
}
fn sparkline_for(history: &[u64], width: usize, phase: f64) -> String {
if width == 0 {
return String::new();
}
let n = history.len();
if n == 0 {
return " ".repeat(width);
}
let scale = percentile_75(history);
if scale == 0 {
return " ".repeat(width);
}
const COL0_BITS: [u8; 4] = [0x01, 0x02, 0x04, 0x40];
const COL1_BITS: [u8; 4] = [0x08, 0x10, 0x20, 0x80];
let total_subcols = width * 2;
let phase = phase.clamp(0.0, 1.0);
let mut out = String::with_capacity(width * 4);
for cell in 0..width {
let mut bits: u8 = 0;
for sub in 0..2 {
let subcol = cell * 2 + sub;
let pos = (subcol as f64 + 0.5) * (n as f64) / (total_subcols.max(1) as f64) + phase;
let lo = pos.floor() as i64;
let frac = pos - pos.floor();
let v_lo = if lo < 0 {
0.0
} else if lo as usize >= n {
history[n - 1] as f64
} else {
history[lo as usize] as f64
};
let v_hi = if lo + 1 < 0 {
0.0
} else if (lo + 1) as usize >= n {
history[n - 1] as f64
} else {
history[(lo + 1) as usize] as f64
};
let v = v_lo * (1.0 - frac) + v_hi * frac;
let level = ((v / scale as f64) * 4.0).round() as usize;
let level = level.min(4);
if level == 0 {
continue;
}
let col_bits = if sub == 0 { &COL0_BITS } else { &COL1_BITS };
for row in 0..level {
bits |= col_bits[3 - row];
}
}
let ch = if bits == 0 {
' '
} else {
char::from_u32(0x2800 + bits as u32).unwrap_or(' ')
};
out.push(ch);
}
out
}
fn percentile_75(history: &[u64]) -> u64 {
if history.is_empty() {
return 0;
}
let mut sorted: Vec<u64> = history.to_vec();
sorted.sort_unstable();
let idx = ((sorted.len() as f64) * 0.75) as usize;
let p75 = sorted[idx.min(sorted.len() - 1)];
if p75 > 0 {
p75
} else {
sorted[sorted.len() - 1]
}
}
fn more_line(remaining: usize, inner_w: usize) -> Line<'static> {
let text = format!("+{} more", remaining);
let mut spans = vec![Span::raw(INDENT), Span::styled(text, theme::muted())];
let used: usize = spans.iter().map(|s| s.content.width()).sum();
if used < inner_w {
spans.push(Span::raw(" ".repeat(inner_w - used)));
}
Line::from(spans)
}
fn chart_width(inner_w: usize) -> usize {
let prefix = INDENT.width() + 6 + 1 + 7;
let suffix = 1 + 12;
inner_w.saturating_sub(prefix).saturating_sub(suffix)
}
fn channel_kind_short(kind: ChannelKind) -> &'static str {
match kind {
ChannelKind::Direct => "direct",
ChannelKind::Forwarded => "fwd-in",
ChannelKind::Dynamic => "socks",
ChannelKind::Other => "other",
}
}
fn pad_left(s: &str, w: usize) -> String {
let cur = s.width();
if cur >= w {
s.to_string()
} else {
format!("{}{}", " ".repeat(w - cur), s)
}
}
fn pad_right(s: &str, w: usize) -> String {
let cur = s.width();
if cur >= w {
s.to_string()
} else {
format!("{}{}", s, " ".repeat(w - cur))
}
}
fn truncate(s: &str, w: usize) -> String {
let cur = s.width();
if cur <= w {
return s.to_string();
}
let mut out = String::new();
let mut used = 0;
for ch in s.chars() {
let cw = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
if used + cw + 1 > w {
break;
}
out.push(ch);
used += cw;
}
out.push('\u{2026}');
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_appends_ellipsis() {
assert_eq!(truncate("psql", 10), "psql");
assert_eq!(truncate("very-long-process-name", 10), "very-long\u{2026}");
}
#[test]
fn channel_kind_short_strings() {
assert_eq!(channel_kind_short(ChannelKind::Direct), "direct");
assert_eq!(channel_kind_short(ChannelKind::Forwarded), "fwd-in");
assert_eq!(channel_kind_short(ChannelKind::Dynamic), "socks");
assert_eq!(channel_kind_short(ChannelKind::Other), "other");
}
#[test]
fn percentile_75_ignores_single_outlier_peak() {
let mut h = [1_000u64; 12];
h[3] = 100_000;
let p = percentile_75(&h);
assert_eq!(p, 1_000, "p75 must equal typical value, not the peak");
}
#[test]
fn percentile_75_falls_back_to_max_when_percentile_is_zero() {
let mut h = [0u64; 12];
h[11] = 5_000;
let p = percentile_75(&h);
assert_eq!(p, 5_000);
}
#[test]
fn percentile_75_returns_zero_for_all_zero_history() {
let h = [0u64; 12];
assert_eq!(percentile_75(&h), 0);
}
#[test]
fn percentile_75_handles_empty_slice() {
assert_eq!(percentile_75(&[]), 0);
}
#[test]
fn build_lanes_open_first_then_closed() {
let live = LiveView {
is_active: true,
uptime_secs: 100,
clients: vec![],
last_exit: None,
closed_events_smooth: vec![(5, ChannelKind::Dynamic, 30.0, 10.0)],
currently_open: vec![(7, 4.0, ChannelKind::Direct)],
peak_concurrent: 2,
};
let lanes = build_lanes(&live);
assert_eq!(lanes.len(), 2);
assert_eq!(lanes[0].channel_id, 7);
assert!(lanes[0].is_open());
assert_eq!(lanes[1].channel_id, 5);
assert!(!lanes[1].is_open());
}
}