summaryrefslogtreecommitdiffstats
path: root/src/display/components/display_bandwidth.rs
blob: bf2b526bac3dc4280116c1a6a53d22bde5f3fde5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::fmt;

use derivative::Derivative;

use crate::cli::UnitFamily;

#[derive(Copy, Clone, Debug)]
pub struct DisplayBandwidth {
    pub bandwidth: f64,
    pub unit_family: BandwidthUnitFamily,
}

impl fmt::Display for DisplayBandwidth {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (div, suffix) = self.unit_family.get_unit_for(self.bandwidth);
        write!(f, "{:.2}{suffix}", self.bandwidth / div)
    }
}

/// Type wrapper around [`UnitFamily`] to provide extra functionality.
#[derive(Copy, Clone, Derivative, Default, Eq, PartialEq)]
#[derivative(Debug = "transparent")]
pub struct BandwidthUnitFamily(UnitFamily);
impl From<UnitFamily> for BandwidthUnitFamily {
    fn from(value: UnitFamily) -> Self {
        Self(value)
    }
}
impl BandwidthUnitFamily {
    #[inline]
    /// Returns an array of tuples, corresponding to the steps of this unit family.
    ///
    /// Each step contains a divisor, an upper bound, and a unit suffix.
    fn steps(&self) -> [(f64, f64, &'static str); 6] {
        /// The fraction of the next unit the value has to meet to step up.
        const STEP_UP_FRAC: f64 = 0.95;
        /// Binary base: 2^10.
        const BB: f64 = 1024.0;

        use UnitFamily as F;
        // probably could macro this stuff, but I'm too lazy
        match self.0 {
            F::BinBytes => [
                (1.0, BB * STEP_UP_FRAC, "B"),
                (BB, BB.powi(2) * STEP_UP_FRAC, "KiB"),
                (BB.powi(2), BB.powi(3) * STEP_UP_FRAC, "MiB"),
                (BB.powi(3), BB.powi(4) * STEP_UP_FRAC, "GiB"),
                (BB.powi(4), BB.powi(5) * STEP_UP_FRAC, "TiB"),
                (BB.powi(5), f64::MAX, "PiB"),
            ],
            F::BinBits => [
                (1.0 / 8.0, BB / 8.0 * STEP_UP_FRAC, "b"),
                (BB / 8.0, BB.powi(2) / 8.0 * STEP_UP_FRAC, "Kib"),
                (BB.powi(2) / 8.0, BB.powi(3) / 8.0 * STEP_UP_FRAC, "Mib"),
                (BB.powi(3) / 8.0, BB.powi(4) / 8.0 * STEP_UP_FRAC, "Gib"),
                (BB.powi(4) / 8.0, BB.powi(5) / 8.0 * STEP_UP_FRAC, "Tib"),
                (BB.powi(5) / 8.0, f64::MAX, "Pib"),
            ],
            F::SiBytes => [
                (1.0, 1e3 * STEP_UP_FRAC, "B"),
                (1e3, 1e6 * STEP_UP_FRAC, "kB"),
                (1e6, 1e9 * STEP_UP_FRAC, "MB"),
                (1e9, 1e12 * STEP_UP_FRAC, "GB"),
                (1e12, 1e15 * STEP_UP_FRAC, "TB"),
                (1e15, f64::MAX, "PB"),
            ],
            F::SiBits => [
                (1.0 / 8.0, 1e3 / 8.0 * STEP_UP_FRAC, "b"),
                (1e3 / 8.0, 1e6 / 8.0 * STEP_UP_FRAC, "kb"),
                (1e6 / 8.0, 1e9 / 8.0 * STEP_UP_FRAC, "Mb"),
                (1e9 / 8.0, 1e12 / 8.0 * STEP_UP_FRAC, "Gb"),
                (1e12 / 8.0, 1e15 / 8.0 * STEP_UP_FRAC, "Tb"),
                (1e15 / 8.0, f64::MAX, "Pb"),
            ],
        }
    }

    /// Select a unit for a given value, returning its divisor and suffix.
    fn get_unit_for(&self, bytes: f64) -> (f64, &'static str) {
        let Some((div, _, suffix)) = self
            .steps()
            .into_iter()
            .find(|&(_, bound, _)| bound >= bytes)
        else {
            panic!("Cannot select an appropriate unit for {bytes:.2}B.")
        };

        (div, suffix)
    }
}

#[cfg(test)]
mod tests {
    use std::fmt::Write;

    use insta::assert_snapshot;
    use itertools::Itertools;
    use strum::IntoEnumIterator;

    use crate::{cli::UnitFamily, display::DisplayBandwidth};

    #[test]
    fn bandwidth_formatting() {
        let test_bandwidths_formatted = UnitFamily::iter()
            .map_into()
            .cartesian_product(
                // I feel like this is a decent selection of values
                (-6..60)
                    .map(|exp| 2f64.powi(exp))
                    .chain((-5..45).map(|exp| 2.5f64.powi(exp)))
                    .chain((-4..38).map(|exp| 3f64.powi(exp)))
                    .chain((-3..26).map(|exp| 5f64.powi(exp))),
            )
            .map(|(unit_family, bandwidth)| DisplayBandwidth {
                bandwidth,
                unit_family,
            })
            .fold(String::new(), |mut buf, b| {
                let _ = writeln!(buf, "{b:?}: {b}");
                buf
            });

        assert_snapshot!(test_bandwidths_formatted);
    }
}