1use std::collections::VecDeque;
8
9pub const SAMPLES: usize = 30;
11
12const BARS: [char; 8] = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
13
14#[derive(Debug, Clone, Default)]
15pub struct Throughput {
16 last: Option<(u64, u64)>,
17 samples: VecDeque<(u64, u64)>,
19}
20
21impl Throughput {
22 pub fn sample(&mut self, received_total: u64, sent_total: u64) {
24 if let Some((rx, tx)) = self.last {
25 self.samples.push_back((
26 received_total.saturating_sub(rx),
27 sent_total.saturating_sub(tx),
28 ));
29 while self.samples.len() > SAMPLES {
30 self.samples.pop_front();
31 }
32 }
33 self.last = Some((received_total, sent_total));
34 }
35
36 pub fn rate(&self) -> (u64, u64) {
38 self.samples.back().copied().unwrap_or((0, 0))
39 }
40
41 pub fn samples(&self) -> impl Iterator<Item = u64> + '_ {
42 self.samples.iter().map(|(rx, tx)| rx + tx)
43 }
44
45 pub fn is_idle(&self) -> bool {
47 self.samples().all(|s| s == 0)
48 }
49
50 pub fn sparkline(&self, width: usize) -> String {
54 if width == 0 {
55 return String::new();
56 }
57 let values: Vec<u64> = self.samples().collect();
58 let window: Vec<u64> = values.iter().rev().take(width).rev().copied().collect();
59 let peak = window.iter().copied().max().unwrap_or(0);
60 let mut out = String::with_capacity(width * 3);
61 for _ in window.len()..width {
62 out.push(' ');
63 }
64 for value in window {
65 let glyph = if peak == 0 || value == 0 {
66 BARS[0]
67 } else {
68 let level = ((value * 7) / peak).clamp(1, 7) as usize;
70 BARS[level]
71 };
72 out.push(glyph);
73 }
74 out
75 }
76}
77
78pub fn human_bytes(n: u64) -> String {
80 const UNITS: [&str; 5] = ["", "K", "M", "G", "T"];
81 if n < 1000 {
82 return n.to_string();
83 }
84 let mut value = n as f64;
85 let mut unit = 0;
86 while value >= 1000.0 && unit < UNITS.len() - 1 {
87 value /= 1000.0;
88 unit += 1;
89 }
90 if value >= 100.0 {
91 format!("{value:.0}{}", UNITS[unit])
92 } else {
93 format!("{value:.1}{}", UNITS[unit])
94 }
95}
96
97pub fn human_rate(n: u64) -> String {
99 format!("{}/s", human_bytes(n))
100}
101
102pub fn human_duration(secs: u64) -> String {
104 if secs < 60 {
105 format!("{secs}s")
106 } else if secs < 3600 {
107 format!("{}m{:02}s", secs / 60, secs % 60)
108 } else if secs < 86_400 {
109 format!("{}h{:02}m", secs / 3600, (secs % 3600) / 60)
110 } else {
111 format!("{}d{:02}h", secs / 86_400, (secs % 86_400) / 3600)
112 }
113}
114
115pub fn clock(unix_ms: u64) -> String {
117 use chrono::TimeZone;
118 chrono::Local
119 .timestamp_millis_opt(unix_ms as i64)
120 .single()
121 .map(|t| t.format("%H:%M:%S").to_string())
122 .unwrap_or_else(|| "--:--:--".to_string())
123}
124
125pub fn clock_now() -> String {
127 chrono::Local::now().format("%H:%M:%S").to_string()
128}