disktest_lib/
util.rs

1// -*- coding: utf-8 -*-
2//
3// disktest - Storage tester
4//
5// Copyright 2020-2024 Michael Büsch <m@bues.ch>
6//
7// Licensed under the Apache License version 2.0
8// or the MIT license, at your option.
9// SPDX-License-Identifier: Apache-2.0 OR MIT
10//
11
12use anyhow as ah;
13use std::fmt::Write;
14use std::time::Duration;
15
16const EIB: u64 = 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
17const PIB: u64 = 1024 * 1024 * 1024 * 1024 * 1024;
18const TIB: u64 = 1024 * 1024 * 1024 * 1024;
19const GIB: u64 = 1024 * 1024 * 1024;
20const MIB: u64 = 1024 * 1024;
21const KIB: u64 = 1024;
22
23const EIBM1: u64 = EIB - 1;
24const PIBM1: u64 = PIB - 1;
25const TIBM1: u64 = TIB - 1;
26const GIBM1: u64 = GIB - 1;
27const MIBM1: u64 = MIB - 1;
28//const KIBM1: u64 = KIB - 1;
29
30const EB: u64 = 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
31const PB: u64 = 1000 * 1000 * 1000 * 1000 * 1000;
32const TB: u64 = 1000 * 1000 * 1000 * 1000;
33const GB: u64 = 1000 * 1000 * 1000;
34const MB: u64 = 1000 * 1000;
35const KB: u64 = 1000;
36
37const EBM1: u64 = EB - 1;
38const PBM1: u64 = PB - 1;
39const TBM1: u64 = TB - 1;
40const GBM1: u64 = GB - 1;
41const MBM1: u64 = MB - 1;
42
43pub fn prettybytes(count: u64, binary: bool, decimal: bool, bytes: bool) -> String {
44    let mut ret = String::new();
45
46    if !binary && !decimal {
47        return ret;
48    }
49
50    if count < KIB {
51        let _ = write!(ret, "{} bytes", count);
52        return ret;
53    }
54
55    if binary {
56        let _ = match count {
57            EIB..=u64::MAX => write!(ret, "{:.4} EiB", ((count / TIB) as f64) / (MIB as f64)),
58            PIB..=EIBM1 => write!(ret, "{:.4} PiB", ((count / GIB) as f64) / (MIB as f64)),
59            TIB..=PIBM1 => write!(ret, "{:.4} TiB", ((count / MIB) as f64) / (MIB as f64)),
60            GIB..=TIBM1 => write!(ret, "{:.2} GiB", ((count / MIB) as f64) / (KIB as f64)),
61            MIB..=GIBM1 => write!(ret, "{:.1} MiB", (count as f64) / (MIB as f64)),
62            0..=MIBM1 => write!(ret, "{:.1} kiB", (count as f64) / (KIB as f64)),
63        };
64    }
65
66    let paren = if !ret.is_empty() && (decimal || bytes) {
67        ret.push_str(" (");
68        true
69    } else {
70        false
71    };
72
73    if decimal {
74        let _ = match count {
75            EB..=u64::MAX => write!(ret, "{:.4} EB", ((count / TB) as f64) / (MB as f64)),
76            PB..=EBM1 => write!(ret, "{:.4} PB", ((count / GB) as f64) / (MB as f64)),
77            TB..=PBM1 => write!(ret, "{:.4} TB", ((count / MB) as f64) / (MB as f64)),
78            GB..=TBM1 => write!(ret, "{:.2} GB", ((count / MB) as f64) / (KB as f64)),
79            MB..=GBM1 => write!(ret, "{:.1} MB", (count as f64) / (MB as f64)),
80            0..=MBM1 => write!(ret, "{:.1} kB", (count as f64) / (KB as f64)),
81        };
82    }
83
84    if bytes {
85        if decimal {
86            ret.push_str(", ");
87        }
88        let _ = write!(ret, "{} bytes", count);
89    }
90
91    if paren {
92        ret.push(')');
93    }
94
95    ret
96}
97
98fn try_one_parsebytes(s: &str, suffix: &str, factor: u64) -> ah::Result<u64> {
99    let Some(s) = s.strip_suffix(suffix) else {
100        return Err(ah::format_err!("Value suffix does not match."));
101    };
102    let s = s.trim();
103    if let Ok(value) = s.parse::<u64>() {
104        // Integer value.
105        let Some(prod) = value.checked_mul(factor) else {
106            return Err(ah::format_err!("Value integer overflow."));
107        };
108        Ok(prod)
109    } else if let Ok(value) = s.parse::<f64>() {
110        // Floating point value.
111        let factor = factor as f64;
112        if value.log2() + factor.log2() >= 61.0 {
113            return Err(ah::format_err!("Value float overflow."));
114        }
115        Ok((value * factor).round() as u64)
116    } else {
117        Err(ah::format_err!("Value is neither integer nor float."))
118    }
119}
120
121/// Parse a string describing a byte size into a number.
122pub fn parsebytes(s: &str) -> ah::Result<u64> {
123    let s = s.trim().to_lowercase();
124
125    if let Ok(v) = try_one_parsebytes(&s, "eib", EIB) {
126        Ok(v)
127    } else if let Ok(v) = try_one_parsebytes(&s, "pib", PIB) {
128        Ok(v)
129    } else if let Ok(v) = try_one_parsebytes(&s, "tib", TIB) {
130        Ok(v)
131    } else if let Ok(v) = try_one_parsebytes(&s, "gib", GIB) {
132        Ok(v)
133    } else if let Ok(v) = try_one_parsebytes(&s, "mib", MIB) {
134        Ok(v)
135    } else if let Ok(v) = try_one_parsebytes(&s, "kib", KIB) {
136        Ok(v)
137    } else if let Ok(v) = try_one_parsebytes(&s, "e", EIB) {
138        Ok(v)
139    } else if let Ok(v) = try_one_parsebytes(&s, "p", PIB) {
140        Ok(v)
141    } else if let Ok(v) = try_one_parsebytes(&s, "t", TIB) {
142        Ok(v)
143    } else if let Ok(v) = try_one_parsebytes(&s, "g", GIB) {
144        Ok(v)
145    } else if let Ok(v) = try_one_parsebytes(&s, "m", MIB) {
146        Ok(v)
147    } else if let Ok(v) = try_one_parsebytes(&s, "k", KIB) {
148        Ok(v)
149    } else if let Ok(v) = try_one_parsebytes(&s, "eb", EB) {
150        Ok(v)
151    } else if let Ok(v) = try_one_parsebytes(&s, "pb", PB) {
152        Ok(v)
153    } else if let Ok(v) = try_one_parsebytes(&s, "tb", TB) {
154        Ok(v)
155    } else if let Ok(v) = try_one_parsebytes(&s, "gb", GB) {
156        Ok(v)
157    } else if let Ok(v) = try_one_parsebytes(&s, "mb", MB) {
158        Ok(v)
159    } else if let Ok(v) = try_one_parsebytes(&s, "kb", KB) {
160        Ok(v)
161    } else if let Ok(v) = s.parse::<u64>() {
162        // byte count w/o suffix.
163        Ok(v)
164    } else {
165        Err(ah::format_err!("Cannot parse byte count: {}", s))
166    }
167}
168
169pub trait Hhmmss {
170    fn hhmmss(&self) -> String;
171}
172
173impl Hhmmss for Duration {
174    fn hhmmss(&self) -> String {
175        let secs = self.as_secs();
176        let secs_lim = (99 * 60 * 60) + (59 * 60) + 59;
177        let lim = if secs > secs_lim { ">" } else { "" };
178        let secs = secs.min(secs_lim);
179        let h = secs / (60 * 60);
180        let rem = secs % (60 * 60);
181        let m = rem / 60;
182        let s = rem % 60;
183        format!("{}{:02}h:{:02}m:{:02}s", lim, h, m, s)
184    }
185}
186
187/// Fold a byte vector into a smaller byte vector using XOR operation.
188/// If output_size is bigger than input.len(), the trailing bytes
189/// will be filled with zeros.
190pub fn fold(input: &[u8], output_size: usize) -> Vec<u8> {
191    let mut output = vec![0; output_size];
192
193    if output_size > 0 {
194        for (i, data) in input.iter().enumerate() {
195            output[i % output_size] ^= data;
196        }
197    }
198
199    output
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_prettybytes() {
208        assert_eq!(prettybytes(42, true, true, false), "42 bytes");
209        assert_eq!(
210            prettybytes(42 * 1024, true, true, false),
211            "42.0 kiB (43.0 kB)"
212        );
213        assert_eq!(
214            prettybytes(42 * 1024 * 1024, true, true, false),
215            "42.0 MiB (44.0 MB)"
216        );
217        assert_eq!(
218            prettybytes(42 * 1024 * 1024 * 1024, true, true, false),
219            "42.00 GiB (45.10 GB)"
220        );
221        assert_eq!(
222            prettybytes(42 * 1024 * 1024 * 1024 * 1024, true, true, false),
223            "42.0000 TiB (46.1795 TB)"
224        );
225        assert_eq!(
226            prettybytes(42 * 1024 * 1024 * 1024 * 1024 * 1024, true, true, false),
227            "42.0000 PiB (47.2878 PB)"
228        );
229        assert_eq!(
230            prettybytes(
231                2 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
232                true,
233                true,
234                false
235            ),
236            "2.0000 EiB (2.3058 EB)"
237        );
238
239        assert_eq!(prettybytes(42, true, false, false), "42 bytes");
240        assert_eq!(prettybytes(42, false, true, false), "42 bytes");
241        assert_eq!(prettybytes(42, false, false, false), "");
242
243        assert_eq!(prettybytes(42 * 1024, true, false, false), "42.0 kiB");
244        assert_eq!(prettybytes(42 * 1024, false, true, false), "43.0 kB");
245        assert_eq!(prettybytes(42 * 1024, false, false, false), "");
246
247        assert_eq!(
248            prettybytes(42 * 1024, true, true, true),
249            "42.0 kiB (43.0 kB, 43008 bytes)"
250        );
251        assert_eq!(
252            prettybytes(42 * 1024, true, false, true),
253            "42.0 kiB (43008 bytes)"
254        );
255    }
256
257    #[test]
258    fn test_parsebytes() {
259        // No suffix.
260        assert_eq!(parsebytes("42").unwrap(), 42);
261
262        // Binary suffix, integer.
263        assert_eq!(parsebytes("42kib").unwrap(), 42 * 1024);
264        assert_eq!(parsebytes("42 mib").unwrap(), 42 * 1024 * 1024);
265        assert_eq!(parsebytes(" 42 gib ").unwrap(), 42 * 1024 * 1024 * 1024);
266        assert_eq!(parsebytes("42Tib").unwrap(), 42 * 1024 * 1024 * 1024 * 1024);
267        assert_eq!(
268            parsebytes("42PiB").unwrap(),
269            42 * 1024 * 1024 * 1024 * 1024 * 1024
270        );
271        assert_eq!(
272            parsebytes("2 EIB ").unwrap(),
273            2 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
274        );
275
276        // Binary suffix, fractional.
277        assert_eq!(parsebytes("42.5kib").unwrap(), 42 * 1024 + 1024 / 2);
278        assert_eq!(
279            parsebytes("42.5 mib").unwrap(),
280            42 * 1024 * 1024 + 1024 * 1024 / 2
281        );
282        assert_eq!(
283            parsebytes(" 42.5 gib ").unwrap(),
284            42 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 / 2
285        );
286        assert_eq!(
287            parsebytes("42.5Tib").unwrap(),
288            42 * 1024 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 * 1024 / 2
289        );
290        assert_eq!(
291            parsebytes("42.5PiB").unwrap(),
292            42 * 1024 * 1024 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 * 1024 * 1024 / 2
293        );
294        assert_eq!(
295            parsebytes("1.5 EIB ").unwrap(),
296            1024 * 1024 * 1024 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 * 1024 * 1024 * 1024 / 2
297        );
298
299        // Binary suffix, integer.
300        assert_eq!(parsebytes("42k").unwrap(), 42 * 1024);
301        assert_eq!(parsebytes("42 m").unwrap(), 42 * 1024 * 1024);
302        assert_eq!(parsebytes(" 42 g ").unwrap(), 42 * 1024 * 1024 * 1024);
303        assert_eq!(parsebytes("42T").unwrap(), 42 * 1024 * 1024 * 1024 * 1024);
304        assert_eq!(
305            parsebytes("42P").unwrap(),
306            42 * 1024 * 1024 * 1024 * 1024 * 1024
307        );
308        assert_eq!(
309            parsebytes("2 E ").unwrap(),
310            2 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
311        );
312
313        // Binary suffix, fractional.
314        assert_eq!(parsebytes("42.5k").unwrap(), 42 * 1024 + 1024 / 2);
315        assert_eq!(
316            parsebytes("42.5 m").unwrap(),
317            42 * 1024 * 1024 + 1024 * 1024 / 2
318        );
319        assert_eq!(
320            parsebytes(" 42.5 g ").unwrap(),
321            42 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 / 2
322        );
323        assert_eq!(
324            parsebytes("42.5T").unwrap(),
325            42 * 1024 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 * 1024 / 2
326        );
327        assert_eq!(
328            parsebytes("42.5P").unwrap(),
329            42 * 1024 * 1024 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 * 1024 * 1024 / 2
330        );
331        assert_eq!(
332            parsebytes("1.5 E ").unwrap(),
333            1024 * 1024 * 1024 * 1024 * 1024 * 1024 + 1024 * 1024 * 1024 * 1024 * 1024 * 1024 / 2
334        );
335
336        // Decimal suffix, integer.
337        assert_eq!(parsebytes("42kb").unwrap(), 42 * 1000);
338        assert_eq!(parsebytes("42 mb").unwrap(), 42 * 1000 * 1000);
339        assert_eq!(parsebytes(" 42 gb ").unwrap(), 42 * 1000 * 1000 * 1000);
340        assert_eq!(parsebytes("42Tb").unwrap(), 42 * 1000 * 1000 * 1000 * 1000);
341        assert_eq!(
342            parsebytes("42PB").unwrap(),
343            42 * 1000 * 1000 * 1000 * 1000 * 1000
344        );
345        assert_eq!(
346            parsebytes("2 EB ").unwrap(),
347            2 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000
348        );
349
350        // Decimal suffix, fractional.
351        assert_eq!(parsebytes("42.5kb").unwrap(), 42 * 1000 + 1000 / 2);
352        assert_eq!(
353            parsebytes("42.5 mb").unwrap(),
354            42 * 1000 * 1000 + 1000 * 1000 / 2
355        );
356        assert_eq!(
357            parsebytes(" 42.5 gb ").unwrap(),
358            42 * 1000 * 1000 * 1000 + 1000 * 1000 * 1000 / 2
359        );
360        assert_eq!(
361            parsebytes("42.5Tb").unwrap(),
362            42 * 1000 * 1000 * 1000 * 1000 + 1000 * 1000 * 1000 * 1000 / 2
363        );
364        assert_eq!(
365            parsebytes("42.5PB").unwrap(),
366            42 * 1000 * 1000 * 1000 * 1000 * 1000 + 1000 * 1000 * 1000 * 1000 * 1000 / 2
367        );
368        assert_eq!(
369            parsebytes("1.5 EB ").unwrap(),
370            1000 * 1000 * 1000 * 1000 * 1000 * 1000 + 1000 * 1000 * 1000 * 1000 * 1000 * 1000 / 2
371        );
372    }
373
374    #[test]
375    fn test_hhmmss() {
376        assert_eq!(Duration::from_secs(0).hhmmss(), "00h:00m:00s");
377        assert_eq!(
378            Duration::from_secs((2 * 60 * 60) + (3 * 60) + 4).hhmmss(),
379            "02h:03m:04s"
380        );
381        assert_eq!(
382            Duration::from_secs((23 * 60 * 60) + (59 * 60) + 59).hhmmss(),
383            "23h:59m:59s"
384        );
385        assert_eq!(
386            Duration::from_secs((99 * 60 * 60) + (59 * 60) + 59).hhmmss(),
387            "99h:59m:59s"
388        );
389        assert_eq!(
390            Duration::from_secs((99 * 60 * 60) + (59 * 60) + 59 + 1).hhmmss(),
391            ">99h:59m:59s"
392        );
393    }
394
395    #[test]
396    fn test_fold() {
397        assert_eq!(fold(&[0x55, 0x55, 0xAA, 0xAA], 2), [0xFF, 0xFF]);
398        assert_eq!(fold(&[0x55, 0x55, 0x55, 0x55], 2), [0x00, 0x00]);
399        assert_eq!(fold(&[0x55, 0x55, 0xAA, 0x55], 2), [0xFF, 0x00]);
400        assert_eq!(fold(&[0x55, 0x55, 0x55, 0xAA], 2), [0x00, 0xFF]);
401        assert_eq!(
402            fold(&[0x98, 0xB1, 0x5B, 0x47, 0x8F, 0xF7, 0x9C, 0x6F], 3),
403            [0x43, 0x51, 0xAC]
404        );
405        assert_eq!(fold(&[0x12, 0x34, 0x56, 0x78], 4), [0x12, 0x34, 0x56, 0x78]);
406        assert_eq!(
407            fold(&[0x12, 0x34, 0x56, 0x78], 6),
408            [0x12, 0x34, 0x56, 0x78, 0x00, 0x00]
409        );
410        assert_eq!(fold(&[0x12, 0x34, 0x56, 0x78], 0), []);
411    }
412}
413
414// vim: ts=4 sw=4 expandtab