Skip to main content

dsi_progress_logger/
utils.rs

1/*
2 * SPDX-FileCopyrightText: 2023 Inria
3 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4 *
5 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6 */
7
8/// A unit of time, from nanoseconds to days.
9///
10/// Time units are used to display timings and speeds. Usually the most
11/// convenient unit is chosen automatically, but you can fix a specific unit
12/// with [`ProgressLog::time_unit`](crate::ProgressLog::time_unit).
13#[derive(Debug, Copy, Clone)]
14pub enum TimeUnit {
15    /// A nanosecond (10⁻⁹ seconds).
16    NanoSeconds,
17    /// A microsecond (10⁻⁶ seconds).
18    MicroSeconds,
19    /// A millisecond (10⁻³ seconds).
20    MilliSeconds,
21    /// A second.
22    Seconds,
23    /// A minute (60 seconds).
24    Minutes,
25    /// An hour (3600 seconds).
26    Hours,
27    /// A day (86400 seconds).
28    Days,
29}
30
31impl TimeUnit {
32    /// All time units, in increasing order of duration.
33    pub const VALUES: [TimeUnit; 7] = [
34        TimeUnit::NanoSeconds,
35        TimeUnit::MicroSeconds,
36        TimeUnit::MilliSeconds,
37        TimeUnit::Seconds,
38        TimeUnit::Minutes,
39        TimeUnit::Hours,
40        TimeUnit::Days,
41    ];
42
43    /// Returns the label used to display this time unit (e.g., `s` for
44    /// [`Seconds`](TimeUnit::Seconds)).
45    pub const fn label(&self) -> &'static str {
46        match self {
47            TimeUnit::NanoSeconds => "ns",
48            TimeUnit::MicroSeconds => "μs",
49            TimeUnit::MilliSeconds => "ms",
50            TimeUnit::Seconds => "s",
51            TimeUnit::Minutes => "m",
52            TimeUnit::Hours => "h",
53            TimeUnit::Days => "d",
54        }
55    }
56
57    /// Returns the length of this time unit in seconds.
58    pub const fn as_seconds(&self) -> f64 {
59        match self {
60            TimeUnit::NanoSeconds => 1.0e-9,
61            TimeUnit::MicroSeconds => 1.0e-6,
62            TimeUnit::MilliSeconds => 1.0e-3,
63            TimeUnit::Seconds => 1.0,
64            TimeUnit::Minutes => 60.0,
65            TimeUnit::Hours => 3600.0,
66            TimeUnit::Days => 86400.0,
67        }
68    }
69
70    /// Returns the largest time unit not exceeding the given duration in
71    /// seconds; it is used to display per-item timings.
72    ///
73    /// For durations shorter than a nanosecond,
74    /// [`NanoSeconds`](TimeUnit::NanoSeconds) is returned.
75    pub const fn nice_time_unit(seconds: f64) -> Self {
76        let mut i = TimeUnit::VALUES.len();
77        while i > 0 {
78            i -= 1;
79            if seconds >= TimeUnit::VALUES[i].as_seconds() {
80                return TimeUnit::VALUES[i];
81            }
82        }
83        TimeUnit::NanoSeconds
84    }
85
86    /// Returns the smallest time unit, starting from
87    /// [`Seconds`](TimeUnit::Seconds), in which an activity taking the given
88    /// number of seconds per item progresses at a speed of at least one item
89    /// per unit; it is used to display speeds.
90    ///
91    /// For activities slower than one item per day, [`Days`](TimeUnit::Days)
92    /// is returned.
93    pub const fn nice_speed_unit(seconds: f64) -> Self {
94        let mut i = 3;
95        while i < TimeUnit::VALUES.len() {
96            if seconds <= TimeUnit::VALUES[i].as_seconds() {
97                return TimeUnit::VALUES[i];
98            }
99            i += 1;
100        }
101        TimeUnit::Days
102    }
103
104    /// Pretty-prints a duration expressed in milliseconds.
105    ///
106    /// Durations of less than a second are displayed as `{}ms`; longer
107    /// durations are displayed as days, hours, minutes, and seconds (e.g.,
108    /// `1d 3h 10m 5s`), discarding the sub-second part.
109    pub fn pretty_print(milliseconds: u128) -> String {
110        let mut result = String::new();
111
112        if milliseconds < 1000 {
113            return format!("{}ms", milliseconds);
114        }
115
116        let mut seconds = milliseconds / 1000;
117
118        for unit in [TimeUnit::Days, TimeUnit::Hours, TimeUnit::Minutes] {
119            let to_seconds = unit.as_seconds() as u128;
120            if seconds >= to_seconds {
121                result.push_str(&format!("{}{} ", seconds / to_seconds, unit.label(),));
122                seconds %= to_seconds;
123            }
124        }
125
126        result.push_str(&format!("{}s", seconds));
127
128        result
129    }
130}
131
132/// Scales down a value by powers of 1000, returning the scaled value and the
133/// associated SI prefix.
134///
135/// For example, `scale(1234.0)` returns `(1.234, "k")`. Values of a
136/// septillion (10²⁴) or more are all scaled to yottas (`Y`).
137pub const fn scale(mut val: f64) -> (f64, &'static str) {
138    const UNITS: &[&str] = &["", "k", "M", "G", "T", "P", "E", "Z", "Y"];
139    let mut i = 0;
140    while i < UNITS.len() {
141        if val < 1000.0 {
142            return (val, UNITS[i]);
143        }
144        val /= 1000.0;
145        i += 1;
146    }
147
148    (val, "Y")
149}
150
151/// Formats a value scaled with [`scale`] using two decimal digits (e.g.,
152/// `1.23G`).
153pub fn humanize(val: f64) -> String {
154    let (val, unit) = scale(val);
155    format!("{:.2}{}", val, unit)
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_scale() {
164        assert_eq!(scale(1000.0), (1.0, "k"));
165        assert_eq!(scale(300_000.0), (300.0, "k"));
166        assert_eq!(scale(1_000_000_000.0), (1.0, "G"));
167    }
168
169    #[test]
170    fn test_humanize() {
171        assert_eq!(humanize(1000.0), "1.00k");
172        assert_eq!(humanize(12_345.0), "12.35k");
173        assert_eq!(humanize(1_234_567_890.0), "1.23G");
174    }
175
176    #[test]
177    fn test_pretty_print() {
178        assert_eq!(TimeUnit::pretty_print(500), "500ms");
179        assert_eq!(TimeUnit::pretty_print(1000), "1s");
180        assert_eq!(TimeUnit::pretty_print(1500), "1s");
181        assert_eq!(TimeUnit::pretty_print(90_061_000), "1d 1h 1m 1s");
182    }
183
184    #[test]
185    fn test_nice_units() {
186        assert_eq!(TimeUnit::nice_time_unit(0.5).label(), "ms");
187        assert_eq!(TimeUnit::nice_time_unit(100.0).label(), "m");
188        assert_eq!(TimeUnit::nice_speed_unit(0.5).label(), "s");
189        assert_eq!(TimeUnit::nice_speed_unit(100.0).label(), "h");
190    }
191}