1use bytesize::ByteSize;
2use fundu::DurationParser;
3use human_repr::{HumanCount, HumanDuration, HumanThroughput};
4use std::str::FromStr;
5
6#[derive(Debug, Clone)]
8pub enum Measurement {
9 Duration(std::time::Duration),
10 DataSize(u64), DataRate(f64), Count(f64), }
14
15pub fn parse_value_with_unit(value: f64, unit_str: &str) -> Result<Measurement, String> {
18 if let Ok(duration) = parse_duration(value, unit_str) {
20 return Ok(Measurement::Duration(duration));
21 }
22
23 if let Ok(size) = parse_data_size(value, unit_str) {
25 return Ok(Measurement::DataSize(size));
26 }
27
28 if unit_str.contains("/s") {
30 if let Ok(rate) = parse_data_rate(value, unit_str) {
31 return Ok(Measurement::DataRate(rate));
32 }
33 }
34
35 Ok(Measurement::Count(value))
37}
38
39pub fn format_measurement(measurement: Measurement) -> String {
41 match measurement {
42 Measurement::Duration(d) => d.human_duration().to_string(),
43 Measurement::DataSize(bytes) => bytes.human_count_bytes().to_string(),
44 Measurement::DataRate(bps) => bps.human_throughput_bytes().to_string(),
45 Measurement::Count(v) => format!("{:.3}", v),
46 }
47}
48
49fn parse_duration(value: f64, unit: &str) -> Result<std::time::Duration, String> {
51 let parser = DurationParser::with_all_time_units();
52 let inputs = [format!("{}{}", value, unit), format!("{} {}", value, unit)];
54
55 for input in &inputs {
56 if let Ok(fundu_duration) = parser.parse(input) {
57 if let Ok(duration) = fundu_duration.try_into() {
58 return Ok(duration);
59 }
60 }
61 }
62
63 Err(format!("Failed to parse duration: {} {}", value, unit))
64}
65
66fn parse_data_size(value: f64, unit: &str) -> Result<u64, String> {
68 let normalized_unit = match unit.to_lowercase().as_str() {
70 "byte" | "bytes" => "B",
71 "kilobyte" | "kilobytes" => "KB",
72 "megabyte" | "megabytes" => "MB",
73 "gigabyte" | "gigabytes" => "GB",
74 "kibibyte" | "kibibytes" => "KiB",
75 "mebibyte" | "mebibytes" => "MiB",
76 "gibibyte" | "gibibytes" => "GiB",
77 _ => unit, };
79
80 let inputs = [
82 format!("{}{}", value, normalized_unit),
83 format!("{} {}", value, normalized_unit),
84 ];
85
86 for input in &inputs {
87 if let Ok(bs) = ByteSize::from_str(input) {
88 return Ok(bs.as_u64());
89 }
90 }
91
92 Err(format!("Failed to parse data size: {} {}", value, unit))
93}
94
95fn parse_data_rate(value: f64, unit_with_rate: &str) -> Result<f64, String> {
97 let parts: Vec<&str> = unit_with_rate.split('/').collect();
98 if parts.len() != 2 || parts[1] != "s" {
99 return Err("Invalid rate format".to_string());
100 }
101
102 let multiplier = match parts[0].to_lowercase().as_str() {
103 "b" => 1.0,
104 "kb" => 1_000.0,
105 "mb" => 1_000_000.0,
106 "gb" => 1_000_000_000.0,
107 "kib" => 1_024.0,
108 "mib" => 1_048_576.0,
109 "gib" => 1_073_741_824.0,
110 _ => return Err(format!("Unknown unit: {}", parts[0])),
111 };
112
113 Ok(value * multiplier)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_parse_duration_units() {
122 let m = parse_value_with_unit(9000.0, "ms").unwrap();
124 assert_eq!(format_measurement(m), "9s");
125
126 let m = parse_value_with_unit(125000.0, "ms").unwrap();
128 let formatted = format_measurement(m);
129 assert!(formatted.contains("2:05"));
130 }
131
132 #[test]
133 fn test_parse_data_size_units() {
134 let m = parse_value_with_unit(9000.0, "KB").unwrap();
136 assert_eq!(format_measurement(m), "9MB");
137
138 let m = parse_value_with_unit(1500.0, "MB").unwrap();
140 assert_eq!(format_measurement(m), "1.5GB");
141 }
142
143 #[test]
144 fn test_parse_data_rate_units() {
145 let m = parse_value_with_unit(9000.0, "KB/s").unwrap();
147 assert_eq!(format_measurement(m), "9MB/s");
148 }
149
150 #[test]
151 fn test_parse_fallback_to_count() {
152 let m = parse_value_with_unit(42.5, "widgets").unwrap();
154 assert_eq!(format_measurement(m), "42.500");
155 }
156
157 #[test]
158 fn test_duration_milliseconds() {
159 let m = parse_value_with_unit(9000.0, "ms").unwrap();
160 assert_eq!(format_measurement(m), "9s");
161 }
162
163 #[test]
164 fn test_duration_seconds_to_minutes() {
165 let m = parse_value_with_unit(125.0, "s").unwrap();
166 let formatted = format_measurement(m);
167 assert!(formatted.contains("2:05"));
168 }
169
170 #[test]
171 fn test_data_size_kilobytes() {
172 let m = parse_value_with_unit(9000.0, "KB").unwrap();
173 assert_eq!(format_measurement(m), "9MB");
174 }
175
176 #[test]
177 fn test_data_rate_megabytes() {
178 let m = parse_value_with_unit(1500.0, "MB/s").unwrap();
179 assert_eq!(format_measurement(m), "1.5GB/s");
180 }
181
182 #[test]
183 fn test_unknown_unit_fallback() {
184 let m = parse_value_with_unit(42.5, "widgets").unwrap();
186 assert!(matches!(m, Measurement::Count(_)));
187 }
188
189 #[test]
190 fn test_nanoseconds() {
191 let m = parse_value_with_unit(1_000_000.0, "ns").unwrap();
192 let formatted = format_measurement(m);
193 assert!(formatted.contains("ms") || formatted.contains("1"));
195 }
196
197 #[test]
198 fn test_bytes() {
199 let m = parse_value_with_unit(1024.0, "B").unwrap();
200 let formatted = format_measurement(m);
201 assert!(formatted.contains("1") || formatted.contains("B"));
203 }
204
205 #[test]
206 fn test_gigabytes() {
207 let m = parse_value_with_unit(2.5, "GB").unwrap();
208 assert_eq!(format_measurement(m), "2.5GB");
209 }
210
211 #[test]
212 fn test_hours() {
213 let m = parse_value_with_unit(2.0, "h").unwrap();
214 let formatted = format_measurement(m);
215 assert!(formatted.contains("2:00") || formatted.contains("h"));
217 }
218
219 #[test]
220 fn test_zero_values() {
221 let m = parse_value_with_unit(0.0, "ms").unwrap();
222 let formatted = format_measurement(m);
223 assert!(formatted.contains("0"));
224 }
225
226 #[test]
227 fn test_small_durations() {
228 let m = parse_value_with_unit(500.0, "ns").unwrap();
229 let formatted = format_measurement(m);
230 assert!(formatted.contains("ns") || formatted.contains("500"));
231 }
232
233 #[test]
234 fn test_bytes_unit_normalization() {
235 let m = parse_value_with_unit(1000.0, "bytes").unwrap();
237 assert!(
238 matches!(m, Measurement::DataSize(_)),
239 "Should parse 'bytes' as DataSize, got: {:?}",
240 m
241 );
242 let formatted = format_measurement(m);
243 assert_eq!(formatted, "1kB");
245
246 let m = parse_value_with_unit(500.0, "byte").unwrap();
248 assert!(matches!(m, Measurement::DataSize(_)));
249 let formatted = format_measurement(m);
250 assert_eq!(formatted, "500B");
251
252 let m = parse_value_with_unit(5000.0, "kilobytes").unwrap();
254 assert!(matches!(m, Measurement::DataSize(_)));
255 assert_eq!(format_measurement(m), "5MB");
257
258 let m = parse_value_with_unit(2000.0, "megabytes").unwrap();
259 assert!(matches!(m, Measurement::DataSize(_)));
260 assert_eq!(format_measurement(m), "2GB");
262 }
263}