speedtest/
throughput.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::fmt::{self, Debug, Display};
6use std::time::Duration;
7
8/// Internal representation keeps a value in bits per second (bps).
9#[derive(Copy, Clone, PartialEq, PartialOrd)]
10pub struct Throughput(f64);
11impl Throughput {
12    pub fn from_len_and_duration(len: u32, duration: Duration) -> Self {
13        let len = f64::from(len);
14        let secs = duration.as_secs_f64();
15
16        Self(len * 8f64 / secs)
17    }
18
19    pub fn as_f64(&self) -> f64 {
20        self.0
21    }
22}
23
24impl Debug for Throughput {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(f, "{self}")
27    }
28}
29
30impl Display for Throughput {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        let Self(bits_per_second) = self;
33        let (value, suffix) = f64_to_value_and_suffix(*bits_per_second);
34        write!(f, "{value:.1} {suffix}bps")
35    }
36}
37
38fn f64_to_value_and_suffix(v: f64) -> (f64, &'static str) {
39    for (m, n) in [(1e9, "G"), (1e6, "M"), (1e3, "K")] {
40        if v >= m {
41            return (v / m, n);
42        }
43    }
44    (v, "")
45}
46
47#[derive(Copy, Clone)]
48pub struct BytesFormatter(pub u64);
49
50impl Display for BytesFormatter {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        let Self(v) = self;
53        let (value, suffix) = f64_to_value_and_suffix(*v as f64);
54        write!(f, "{value:.1} {suffix}B")
55    }
56}
57
58#[cfg(test)]
59mod test {
60    use super::*;
61    use test_case::test_case;
62
63    #[test]
64    fn value_and_suffix() {
65        assert_eq!(f64_to_value_and_suffix(1e12), (1e3, "G"));
66        assert_eq!(f64_to_value_and_suffix(2e9), (2.0, "G"));
67        assert_eq!(f64_to_value_and_suffix(2e6), (2.0, "M"));
68        assert_eq!(f64_to_value_and_suffix(2e3), (2.0, "K"));
69        assert_eq!(f64_to_value_and_suffix(2.0), (2.0, ""));
70    }
71
72    #[test_case(10, Duration::from_secs(1) => Throughput(80.0))]
73    #[test_case(1000, Duration::from_millis(8) => Throughput(1e6))]
74    #[test_case(1_000_000_000, Duration::from_secs(1) => Throughput(8e9))]
75    fn throughput_from_len_and_duration(len: u32, duration: Duration) -> Throughput {
76        Throughput::from_len_and_duration(len, duration)
77    }
78
79    #[test]
80    fn throughput_display() {
81        assert_eq!(Throughput(1.0).to_string(), "1.0 bps");
82        assert_eq!(Throughput(10e3).to_string(), "10.0 Kbps");
83    }
84
85    #[test]
86    fn bytes_display() {
87        assert_eq!(BytesFormatter(1).to_string(), "1.0 B");
88        assert_eq!(BytesFormatter(1000).to_string(), "1.0 KB");
89    }
90}