1use std::fmt;
2
3use crate::counter::{AnyCounter, BytesFormat, KnownCounterKind};
4
5/// Formats an `f64` to the given number of significant figures.
6pub(crate) fn format_f64(val: f64, sig_figs: usize) -> String {
7 let mut str = val.to_string();
8
9 if let Some(dot_index) = str.find('.') {
10 let fract_digits = sig_figs.saturating_sub(dot_index);
11
12 if fract_digits == 0 {
13 str.truncate(dot_index);
14 } else {
15 let fract_start = dot_index + 1;
16 let fract_end = fract_start + fract_digits;
17 let fract_range = fract_start..fract_end;
18
19 if let Some(fract_str) = str.get(fract_range) {
20 // Get the offset from the end before all 0s.
21 let pre_zero = fract_str.bytes().rev().enumerate().find_map(|(i, b)| {
22 if b != b'0' {
23 Some(i)
24 } else {
25 None
26 }
27 });
28
29 if let Some(pre_zero) = pre_zero {
30 str.truncate(fract_end - pre_zero);
31 } else {
32 str.truncate(dot_index);
33 }
34 }
35 }
36 }
37
38 str
39}
40
41pub(crate) fn format_bytes(val: f64, sig_figs: usize, bytes_format: BytesFormat) -> String {
42 let (val: f64, scale: Scale) = scale_value(value:val, bytes_format);
43
44 let mut result: String = format_f64(val, sig_figs);
45 result.push(ch:' ');
46 result.push_str(string:scale.suffix(format:ScaleFormat::Bytes(bytes_format)));
47 result
48}
49
50pub(crate) struct DisplayThroughput<'a> {
51 pub counter: &'a AnyCounter,
52 pub picos: f64,
53 pub bytes_format: BytesFormat,
54}
55
56impl fmt::Debug for DisplayThroughput<'_> {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 fmt::Display::fmt(self, f)
59 }
60}
61
62impl fmt::Display for DisplayThroughput<'_> {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 let picos = self.picos;
65 let count = self.counter.count();
66 let count_per_sec = if count == 0 { 0. } else { count as f64 * (1e12 / picos) };
67
68 let format = match self.counter.kind {
69 KnownCounterKind::Bytes => ScaleFormat::BytesThroughput(self.bytes_format),
70 KnownCounterKind::Chars => ScaleFormat::CharsThroughput,
71 KnownCounterKind::Cycles => ScaleFormat::CyclesThroughput,
72 KnownCounterKind::Items => ScaleFormat::ItemsThroughput,
73 };
74
75 let (val, scale) = scale_value(count_per_sec, format.bytes_format());
76
77 let sig_figs = f.precision().unwrap_or(4);
78
79 let mut str = format_f64(val, sig_figs);
80 str.push(' ');
81 str.push_str(scale.suffix(format));
82
83 // Fill up to specified width.
84 if let Some(fill_len) = f.width().and_then(|width| width.checked_sub(str.len())) {
85 match f.align() {
86 None | Some(fmt::Alignment::Left) => {
87 str.extend(std::iter::repeat(f.fill()).take(fill_len));
88 }
89 _ => return Err(fmt::Error),
90 }
91 }
92
93 f.write_str(&str)
94 }
95}
96
97/// Converts a value to the appropriate scale.
98fn scale_value(value: f64, bytes_format: BytesFormat) -> (f64, Scale) {
99 let starts: &'static [f64; _] = scale_starts(bytes_format);
100
101 let scale: Scale = if value.is_infinite() || value < starts[1] {
102 Scale::One
103 } else if value < starts[2] {
104 Scale::Kilo
105 } else if value < starts[3] {
106 Scale::Mega
107 } else if value < starts[4] {
108 Scale::Giga
109 } else if value < starts[5] {
110 Scale::Tera
111 } else {
112 Scale::Peta
113 };
114
115 (value / starts[scale as usize], scale)
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub(crate) enum Scale {
120 One,
121 Kilo,
122 Mega,
123 Giga,
124 Tera,
125 Peta,
126}
127
128#[derive(Clone, Copy)]
129pub(crate) enum ScaleFormat {
130 Bytes(BytesFormat),
131 BytesThroughput(BytesFormat),
132 CharsThroughput,
133 CyclesThroughput,
134 ItemsThroughput,
135}
136
137impl ScaleFormat {
138 pub fn bytes_format(self) -> BytesFormat {
139 match self {
140 Self::Bytes(format: BytesFormat) | Self::BytesThroughput(format: BytesFormat) => format,
141 Self::CharsThroughput | Self::CyclesThroughput | Self::ItemsThroughput => {
142 BytesFormat::Decimal
143 }
144 }
145 }
146}
147
148fn scale_starts(bytes_format: BytesFormat) -> &'static [f64; Scale::COUNT] {
149 const STARTS: &[[f64; Scale::COUNT]; 2] = &[
150 [1., 1e3, 1e6, 1e9, 1e12, 1e15],
151 [
152 1.,
153 1024.,
154 1024u64.pow(exp:2) as f64,
155 1024u64.pow(exp:3) as f64,
156 1024u64.pow(exp:4) as f64,
157 1024u64.pow(exp:5) as f64,
158 ],
159 ];
160
161 &STARTS[bytes_format as usize]
162}
163
164impl Scale {
165 const COUNT: usize = 6;
166
167 pub fn suffix(self, format: ScaleFormat) -> &'static str {
168 match format {
169 ScaleFormat::Bytes(format) => {
170 const SUFFIXES: &[[&str; Scale::COUNT]; 2] = &[
171 ["B", "KB", "MB", "GB", "TB", "PB"],
172 ["B", "KiB", "MiB", "GiB", "TiB", "PiB"],
173 ];
174
175 SUFFIXES[format as usize][self as usize]
176 }
177 ScaleFormat::BytesThroughput(format) => {
178 const SUFFIXES: &[[&str; Scale::COUNT]; 2] = &[
179 ["B/s", "KB/s", "MB/s", "GB/s", "TB/s", "PB/s"],
180 ["B/s", "KiB/s", "MiB/s", "GiB/s", "TiB/s", "PiB/s"],
181 ];
182
183 SUFFIXES[format as usize][self as usize]
184 }
185 ScaleFormat::CharsThroughput => {
186 const SUFFIXES: &[&str; Scale::COUNT] =
187 &["char/s", "Kchar/s", "Mchar/s", "Gchar/s", "Tchar/s", "Pchar/s"];
188
189 SUFFIXES[self as usize]
190 }
191 ScaleFormat::CyclesThroughput => {
192 const SUFFIXES: &[&str; Scale::COUNT] = &["Hz", "KHz", "MHz", "GHz", "THz", "PHz"];
193
194 SUFFIXES[self as usize]
195 }
196 ScaleFormat::ItemsThroughput => {
197 const SUFFIXES: &[&str; Scale::COUNT] =
198 &["item/s", "Kitem/s", "Mitem/s", "Gitem/s", "Titem/s", "Pitem/s"];
199
200 SUFFIXES[self as usize]
201 }
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn scale_value() {
212 #[track_caller]
213 fn test(n: f64, format: BytesFormat, expected_value: f64, expected_scale: Scale) {
214 assert_eq!(super::scale_value(n, format), (expected_value, expected_scale));
215 }
216
217 #[track_caller]
218 fn test_decimal(n: f64, expected_value: f64, expected_scale: Scale) {
219 test(n, BytesFormat::Decimal, expected_value, expected_scale);
220 }
221
222 test_decimal(1., 1., Scale::One);
223 test_decimal(1_000., 1., Scale::Kilo);
224 test_decimal(1_000_000., 1., Scale::Mega);
225 test_decimal(1_000_000_000., 1., Scale::Giga);
226 test_decimal(1_000_000_000_000., 1., Scale::Tera);
227 test_decimal(1_000_000_000_000_000., 1., Scale::Peta);
228 }
229}
230