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)]
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
20impl Debug for Throughput {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        write!(f, "{self}")
23    }
24}
25
26impl Display for Throughput {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        let Self(bits_per_second) = self;
29        let (value, suffix) = f64_to_value_and_suffix(*bits_per_second);
30        write!(f, "{value:.1} {suffix}bps")
31    }
32}
33
34fn f64_to_value_and_suffix(v: f64) -> (f64, &'static str) {
35    for (m, n) in [(1e9, "G"), (1e6, "M"), (1e3, "K")] {
36        if v >= m {
37            return (v / m, n);
38        }
39    }
40    (v, "")
41}
42
43#[derive(Copy, Clone)]
44pub struct BytesFormatter(pub u64);
45
46impl Display for BytesFormatter {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        let Self(v) = self;
49        let (value, suffix) = f64_to_value_and_suffix(*v as f64);
50        write!(f, "{value:.1} {suffix}B")
51    }
52}
53
54#[cfg(test)]
55mod test {
56    use super::*;
57    use test_case::test_case;
58
59    #[test]
60    fn value_and_suffix() {
61        assert_eq!(f64_to_value_and_suffix(1e12), (1e3, "G"));
62        assert_eq!(f64_to_value_and_suffix(2e9), (2.0, "G"));
63        assert_eq!(f64_to_value_and_suffix(2e6), (2.0, "M"));
64        assert_eq!(f64_to_value_and_suffix(2e3), (2.0, "K"));
65        assert_eq!(f64_to_value_and_suffix(2.0), (2.0, ""));
66    }
67
68    #[test_case(10, Duration::from_secs(1) => Throughput(80.0))]
69    #[test_case(1000, Duration::from_millis(8) => Throughput(1e6))]
70    #[test_case(1_000_000_000, Duration::from_secs(1) => Throughput(8e9))]
71    fn throughput_from_len_and_duration(len: u32, duration: Duration) -> Throughput {
72        Throughput::from_len_and_duration(len, duration)
73    }
74
75    #[test]
76    fn throughput_display() {
77        assert_eq!(Throughput(1.0).to_string(), "1.0 bps");
78        assert_eq!(Throughput(10e3).to_string(), "10.0 Kbps");
79    }
80
81    #[test]
82    fn bytes_display() {
83        assert_eq!(BytesFormatter(1).to_string(), "1.0 B");
84        assert_eq!(BytesFormatter(1000).to_string(), "1.0 KB");
85    }
86}