1use std::fmt;
4use std::num::NonZeroUsize;
5use std::time::Instant;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct HistorySize(NonZeroUsize);
10
11impl HistorySize {
12 pub const DEFAULT: Self = Self(NonZeroUsize::new(60).unwrap());
14
15 #[must_use]
20 pub const fn new(size: usize) -> Self {
21 match NonZeroUsize::new(size) {
22 Some(non_zero) => Self(non_zero),
23 None => panic!("HistorySize must be non-zero"),
24 }
25 }
26
27 #[must_use]
29 #[inline]
30 pub const fn get(&self) -> usize {
31 self.0.get()
32 }
33}
34
35impl Default for HistorySize {
36 fn default() -> Self {
37 Self::DEFAULT
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
46pub struct Throughput(f64);
47
48impl Throughput {
49 #[must_use]
51 #[inline]
52 pub const fn new(bps: f64) -> Self {
53 Self(bps)
54 }
55
56 #[must_use]
58 #[inline]
59 pub const fn zero() -> Self {
60 Self(0.0)
61 }
62
63 #[must_use]
65 #[inline]
66 pub const fn get(&self) -> f64 {
67 self.0
68 }
69
70 #[must_use]
72 pub fn format(&self) -> String {
73 const KIB: f64 = 1_024.0;
74 const MIB: f64 = KIB * 1_024.0;
75 const GIB: f64 = MIB * 1_024.0;
76 const TIB: f64 = GIB * 1_024.0;
77 const PIB: f64 = TIB * 1_024.0;
78
79 if self.0 >= PIB {
80 format!("{:.2} PiB/s", self.0 / PIB)
81 } else if self.0 >= TIB {
82 format!("{:.2} TiB/s", self.0 / TIB)
83 } else if self.0 >= GIB {
84 format!("{:.2} GiB/s", self.0 / GIB)
85 } else if self.0 >= MIB {
86 format!("{:.2} MiB/s", self.0 / MIB)
87 } else if self.0 >= KIB {
88 format!("{:.2} KiB/s", self.0 / KIB)
89 } else {
90 format!("{:.0} B/s", self.0)
91 }
92 }
93}
94
95impl Default for Throughput {
96 fn default() -> Self {
97 Self::zero()
98 }
99}
100
101impl fmt::Display for Throughput {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "{}", self.format())
104 }
105}
106
107impl From<f64> for Throughput {
108 fn from(bps: f64) -> Self {
109 Self::new(bps)
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
115pub struct CommandsPerSecond(f64);
116
117impl CommandsPerSecond {
118 #[must_use]
120 #[inline]
121 pub const fn new(cps: f64) -> Self {
122 Self(cps)
123 }
124
125 #[must_use]
127 #[inline]
128 pub const fn zero() -> Self {
129 Self(0.0)
130 }
131
132 #[must_use]
134 #[inline]
135 pub const fn get(&self) -> f64 {
136 self.0
137 }
138}
139
140impl Default for CommandsPerSecond {
141 fn default() -> Self {
142 Self::zero()
143 }
144}
145
146impl fmt::Display for CommandsPerSecond {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 write!(f, "{:.1}", self.0)
149 }
150}
151
152impl From<f64> for CommandsPerSecond {
153 fn from(cps: f64) -> Self {
154 Self::new(cps)
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
160pub struct Timestamp(Instant);
161
162impl Timestamp {
163 #[must_use]
165 #[inline]
166 pub const fn new(instant: Instant) -> Self {
167 Self(instant)
168 }
169
170 #[must_use]
172 #[inline]
173 pub fn now() -> Self {
174 Self(Instant::now())
175 }
176
177 #[must_use]
179 #[inline]
180 pub const fn into_inner(self) -> Instant {
181 self.0
182 }
183
184 #[must_use]
186 #[inline]
187 pub fn elapsed(&self) -> std::time::Duration {
188 self.0.elapsed()
189 }
190
191 #[must_use]
193 #[inline]
194 pub fn duration_since(&self, earlier: Self) -> std::time::Duration {
195 self.0.duration_since(earlier.0)
196 }
197}
198
199impl From<Instant> for Timestamp {
200 fn from(instant: Instant) -> Self {
201 Self::new(instant)
202 }
203}
204
205impl From<Timestamp> for Instant {
206 fn from(ts: Timestamp) -> Self {
207 ts.into_inner()
208 }
209}
210
211#[cfg(test)]
212#[allow(clippy::float_cmp)] mod tests {
214 use super::*;
215
216 #[test]
218 fn test_history_size() {
219 let size = HistorySize::new(60);
220 assert_eq!(size.get(), 60);
221 assert_eq!(HistorySize::DEFAULT.get(), 60);
222 assert_eq!(HistorySize::default().get(), 60);
223 }
224
225 #[test]
226 #[should_panic(expected = "HistorySize must be non-zero")]
227 fn test_history_size_zero_panics() {
228 let _ = HistorySize::new(0);
229 }
230
231 #[test]
232 fn test_history_size_custom_values() {
233 assert_eq!(HistorySize::new(100).get(), 100);
234 assert_eq!(HistorySize::new(1).get(), 1);
235 assert_eq!(HistorySize::new(1000).get(), 1000);
236 }
237
238 #[test]
240 fn test_bytes_per_second() {
241 let bps = Throughput::new(1_500_000.0);
242 assert_eq!(bps.format(), "1.43 MiB/s");
243
244 let bps2 = Throughput::new(2_500.0);
245 assert_eq!(bps2.format(), "2.44 KiB/s");
246
247 let bps3 = Throughput::new(500.0);
248 assert_eq!(bps3.format(), "500 B/s");
249
250 assert_eq!(Throughput::zero().get(), 0.0);
251 }
252
253 #[test]
254 fn test_bytes_per_second_format_boundaries() {
255 assert_eq!(Throughput::new(1_048_577.0).format(), "1.00 MiB/s");
257 assert_eq!(Throughput::new(1_048_575.0).format(), "1024.00 KiB/s");
259 assert_eq!(Throughput::new(1_025.0).format(), "1.00 KiB/s");
261 assert_eq!(Throughput::new(999.0).format(), "999 B/s");
263 assert_eq!(Throughput::zero().format(), "0 B/s");
265 }
266
267 #[test]
268 fn test_bytes_per_second_default() {
269 let bps = Throughput::default();
270 assert_eq!(bps.get(), 0.0);
271 }
272
273 #[test]
274 fn test_bytes_per_second_display() {
275 assert_eq!(Throughput::new(1_500_000.0).to_string(), "1.43 MiB/s");
276 assert_eq!(Throughput::new(2_500.0).to_string(), "2.44 KiB/s");
277 assert_eq!(Throughput::new(500.0).to_string(), "500 B/s");
278 }
279
280 #[test]
281 fn test_bytes_per_second_from_f64() {
282 let bps = Throughput::from(1234.5);
283 assert_eq!(bps.get(), 1234.5);
284 }
285
286 #[test]
288 fn test_commands_per_second() {
289 let cps = CommandsPerSecond::new(123.456);
290 assert_eq!(cps.to_string(), "123.5");
291
292 assert_eq!(CommandsPerSecond::zero().get(), 0.0);
293 }
294
295 #[test]
296 fn test_commands_per_second_default() {
297 let cps = CommandsPerSecond::default();
298 assert_eq!(cps.get(), 0.0);
299 }
300
301 #[test]
302 fn test_commands_per_second_display() {
303 assert_eq!(CommandsPerSecond::new(0.0).to_string(), "0.0");
304 assert_eq!(CommandsPerSecond::new(1.5).to_string(), "1.5");
305 assert_eq!(CommandsPerSecond::new(99.99).to_string(), "100.0");
306 }
307
308 #[test]
309 fn test_commands_per_second_from_f64() {
310 let cps = CommandsPerSecond::from(42.7);
311 assert_eq!(cps.get(), 42.7);
312 }
313
314 #[test]
316 fn test_timestamp() {
317 let ts = Timestamp::now();
318 std::thread::sleep(std::time::Duration::from_millis(10));
319 assert!(ts.elapsed() >= std::time::Duration::from_millis(10));
320 }
321
322 #[test]
323 fn test_timestamp_duration_since() {
324 let ts1 = Timestamp::now();
325 std::thread::sleep(std::time::Duration::from_millis(10));
326 let ts2 = Timestamp::now();
327
328 let duration = ts2.duration_since(ts1);
329 assert!(duration >= std::time::Duration::from_millis(10));
330 }
331
332 #[test]
333 fn test_timestamp_from_instant() {
334 let instant = Instant::now();
335 let ts = Timestamp::from(instant);
336 assert_eq!(ts.into_inner(), instant);
337 }
338
339 #[test]
340 fn test_timestamp_into_instant() {
341 let instant = Instant::now();
342 let ts = Timestamp::new(instant);
343 let instant2: Instant = ts.into();
344 assert_eq!(instant, instant2);
345 }
346}