apollo_framework/fmt.rs
1//! Provides formatting helpers for durations and byte sizes.
2use std::fmt::Write;
3use std::time::Duration;
4
5/// Formats a duration given in microseconds.
6///
7/// This function determines the ideal unit (ranging from microseconds to seconds) to provide
8/// a concise representation.
9///
10/// Note that a helper function [format_short_duration](format_short_duration) is also provided
11/// which directly returns a String. This function also provides some examples.
12pub fn format_micros(micros: i32, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
13 if micros < 1_000 {
14 write!(f, "{} us", micros)
15 } else if micros < 10_000 {
16 write!(f, "{:.2} ms", micros as f32 / 1_000.)
17 } else if micros < 100_000 {
18 write!(f, "{:.1} ms", micros as f32 / 1_000.)
19 } else if micros < 1_000_000 {
20 write!(f, "{} ms", micros / 1_000)
21 } else if micros < 10_000_000 {
22 write!(f, "{:.2} s", micros as f32 / 1_000_000.)
23 } else if micros < 100_000_000 {
24 write!(f, "{:.1} s", micros as f32 / 1_000_000.)
25 } else {
26 write!(f, "{} s", micros / 1_000_000)
27 }
28}
29
30/// Formats a duration given in microseconds and returns a String representation.
31///
32/// This function determines the ideal unit (ranging from microseconds to seconds) to provide
33/// a concise representation.
34///
35/// Note that a helper function [format_micros](format_micros) is also provided
36/// which directly consumes a **std::fmt::Write**.
37///
38/// # Examples
39///
40/// ```
41/// assert_eq!(apollo_framework::fmt::format_short_duration(100), "100 us");
42/// assert_eq!(apollo_framework::fmt::format_short_duration(8_192), "8.19 ms");
43/// assert_eq!(apollo_framework::fmt::format_short_duration(32_768), "32.8 ms");
44/// assert_eq!(apollo_framework::fmt::format_short_duration(128_123), "128 ms");
45/// assert_eq!(apollo_framework::fmt::format_short_duration(1_128_123), "1.13 s");
46/// assert_eq!(apollo_framework::fmt::format_short_duration(10_128_123), "10.1 s");
47/// assert_eq!(apollo_framework::fmt::format_short_duration(101_000_000), "101 s");
48/// ```
49pub fn format_short_duration(duration_in_micros: i32) -> String {
50 let mut result = String::new();
51 let _ = format_micros(duration_in_micros, &mut result);
52 result
53}
54
55/// Formats a given size in bytes.
56///
57/// This function determines the ideal unit (ranging from bytes to petabytes) to provide
58/// a concise representation.
59///
60/// Note that a helper function [format_size](format_size) is also provided
61/// which directly returns a String. This function also provides some examples.
62pub fn format_bytes(size_in_bytes: usize, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
63 if size_in_bytes == 1 {
64 return write!(f, "1 byte");
65 } else if size_in_bytes < 1024 {
66 return write!(f, "{} bytes", size_in_bytes);
67 }
68
69 let mut magnitude = 0;
70 let mut size = size_in_bytes as f32;
71 while size > 1024. && magnitude < 5 {
72 size /= 1024.;
73 magnitude += 1;
74 }
75
76 if size <= 10. {
77 write!(f, "{:.2} ", size)?;
78 } else if size <= 100. {
79 write!(f, "{:.1} ", size)?;
80 } else {
81 write!(f, "{:.0} ", size)?;
82 }
83
84 match magnitude {
85 0 => write!(f, "Bytes"),
86 1 => write!(f, "KiB"),
87 2 => write!(f, "MiB"),
88 3 => write!(f, "GiB"),
89 4 => write!(f, "TiB"),
90 _ => write!(f, "PiB"),
91 }
92}
93
94/// Formats a given size in bytes.
95///
96/// This function determines the ideal unit (ranging from bytes to petabytes) to provide
97/// a concise representation.
98///
99/// Note that a helper function [format_bytes](format_bytes) is also provided
100/// which directly consumes a **std::fmt::Write**.
101///
102/// # Examples
103///
104/// ```
105/// assert_eq!(apollo_framework::fmt::format_size(0), "0 bytes");
106/// assert_eq!(apollo_framework::fmt::format_size(1), "1 byte");
107/// assert_eq!(apollo_framework::fmt::format_size(100), "100 bytes");
108/// assert_eq!(apollo_framework::fmt::format_size(8_734), "8.53 KiB");
109/// assert_eq!(apollo_framework::fmt::format_size(87_340), "85.3 KiB");
110/// assert_eq!(apollo_framework::fmt::format_size(873_400), "853 KiB");
111/// assert_eq!(apollo_framework::fmt::format_size(8_734_000), "8.33 MiB");
112/// assert_eq!(apollo_framework::fmt::format_size(87_340_000), "83.3 MiB");
113/// assert_eq!(apollo_framework::fmt::format_size(873_400_000), "833 MiB");
114/// assert_eq!(apollo_framework::fmt::format_size(8_734_000_000), "8.13 GiB");
115/// assert_eq!(apollo_framework::fmt::format_size(87_340_000_000), "81.3 GiB");
116/// assert_eq!(apollo_framework::fmt::format_size(873_400_000_000), "813 GiB");
117/// assert_eq!(apollo_framework::fmt::format_size(8_734_000_000_000), "7.94 TiB");
118/// assert_eq!(apollo_framework::fmt::format_size(87_340_000_000_000), "79.4 TiB");
119/// assert_eq!(apollo_framework::fmt::format_size(873_400_000_000_000), "794 TiB");
120/// assert_eq!(apollo_framework::fmt::format_size(8_734_000_000_000_000), "7.76 PiB");
121/// assert_eq!(apollo_framework::fmt::format_size(87_340_000_000_000_000), "77.6 PiB");
122/// assert_eq!(apollo_framework::fmt::format_size(873_400_000_000_000_000), "776 PiB");
123/// ```
124pub fn format_size(size_in_bytes: usize) -> String {
125 let mut result = String::new();
126 let _ = format_bytes(size_in_bytes, &mut result);
127
128 result
129}
130
131/// Parses a file size from a given string.
132///
133/// This string can have the following suffixes:
134/// * **k** or **K**: multiplies the given value by 1024 thus treats the value as KiB
135/// * **m** or **M**: multiplies the given value by 1.048.576 thus treats the value as MiB
136/// * **g** or **G**: multiplies the given value by 1.073.741.824 thus treats the value as GiB
137/// * **t** or **T**: multiplies the given value by 1.099.511.627.776 thus treats the value as TiB
138///
139/// Returns an **Err** if either a non-integer value is given or if an unknow suffix was provided.
140///
141/// # Examples
142///
143/// ```
144/// assert_eq!(apollo_framework::fmt::parse_size("100").unwrap(), 100);
145/// assert_eq!(apollo_framework::fmt::parse_size("100b").unwrap(), 100);
146/// assert_eq!(apollo_framework::fmt::parse_size("8k").unwrap(), 8192);
147/// assert_eq!(apollo_framework::fmt::parse_size("8m").unwrap(), 8 * 1024 * 1024);
148/// assert_eq!(apollo_framework::fmt::parse_size("4 G").unwrap(), 4 * 1024 * 1024 * 1024);
149/// assert_eq!(apollo_framework::fmt::parse_size("3 T").unwrap(), 3 * 1024 * 1024 * 1024 * 1024);
150///
151/// // An invalid suffix results in an error...
152/// assert_eq!(apollo_framework::fmt::parse_size("3 Y").is_err(), true);
153///
154/// // Decimal numbers result in an error...
155/// assert_eq!(apollo_framework::fmt::parse_size("1.2g").is_err(), true);
156///
157/// // Negative numbers result in an error...
158/// assert_eq!(apollo_framework::fmt::parse_size("-1").is_err(), true);
159/// ```
160pub fn parse_size(str: impl AsRef<str>) -> anyhow::Result<usize> {
161 lazy_static::lazy_static! {
162 static ref NUMBER_AND_SUFFIX: regex::Regex =
163 regex::Regex::new(r"^ *(\d+) *([bBkKmMgGtT]?) *$").unwrap();
164 }
165
166 match NUMBER_AND_SUFFIX.captures(str.as_ref()) {
167 Some(captures) => {
168 let number = captures[1].parse::<usize>().unwrap();
169 match &captures[2] {
170 "k" | "K" => Ok(number * 1024),
171 "m" | "M" => Ok(number * 1024 * 1024),
172 "g" | "G" => Ok(number * 1024 * 1024 * 1024),
173 "t" | "T" => Ok(number * 1024 * 1024 * 1024 * 1024),
174 _ => Ok(number),
175 }
176 }
177 None => Err(anyhow::anyhow!(
178 "Cannot parse '{}' into a size expression.\
179 Expected a positive number and optionally 'b', 'k', 'm', 'g' or 't' as suffix.",
180 str.as_ref()
181 )),
182 }
183}
184
185/// Parses a duration from a given string.
186///
187/// This string can have the following suffixes:
188/// * **ms** or **MS**: treats the value as milliseconds
189/// * **s** or **S**: treats the value as seconds
190/// * **m** or **M**: treats the value as minutes
191/// * **h** or **H**: treats the value as hours
192/// * **d** or **D**: treats the value as days
193///
194/// Returns an **Err** if either a non-integer value is given or if an unknow suffix was provided.
195///
196/// # Examples
197///
198/// ```
199/// # use std::time::Duration;
200/// assert_eq!(apollo_framework::fmt::parse_duration("100 ms").unwrap(), Duration::from_millis(100));
201/// assert_eq!(apollo_framework::fmt::parse_duration("12 s").unwrap(), Duration::from_secs(12));
202/// assert_eq!(apollo_framework::fmt::parse_duration("3 M").unwrap(), Duration::from_secs(3 * 60));
203/// assert_eq!(apollo_framework::fmt::parse_duration("2 H").unwrap(), Duration::from_secs(2 * 60 * 60));
204/// assert_eq!(apollo_framework::fmt::parse_duration("5 d").unwrap(), Duration::from_secs(5 * 24 * 60 * 60));
205///
206/// // An invalid suffix results in an error...
207/// assert_eq!(apollo_framework::fmt::parse_duration("3 Y").is_err(), true);
208///
209/// // Decimal numbers result in an error...
210/// assert_eq!(apollo_framework::fmt::parse_duration("1.2s").is_err(), true);
211///
212/// // Negative numbers result in an error...
213/// assert_eq!(apollo_framework::fmt::parse_duration("-1m").is_err(), true);
214/// ```
215pub fn parse_duration(str: impl AsRef<str>) -> anyhow::Result<Duration> {
216 lazy_static::lazy_static! {
217 static ref NUMBER_AND_SUFFIX: regex::Regex =
218 regex::Regex::new(r"^ *(\d+) *((ms|s|m|h|d|MS|S|M|H|D)?) *$").unwrap();
219 }
220
221 match NUMBER_AND_SUFFIX.captures(str.as_ref()) {
222 Some(captures) => {
223 let number = captures[1].parse::<u64>().unwrap();
224 match &captures[2] {
225 "s" | "S" => Ok(Duration::from_secs(number)),
226 "m" | "M" => Ok(Duration::from_secs(number * 60)),
227 "h" | "H" => Ok(Duration::from_secs(number * 60 * 60)),
228 "d" | "D" => Ok(Duration::from_secs(number * 60 * 60 * 24)),
229 _ => Ok(Duration::from_millis(number)),
230 }
231 }
232 None => Err(anyhow::anyhow!(
233 "Cannot parse '{}' into a duration expression.\
234 Expected a positive number an optionally 'ms', 's', 'm', 'h' or 'd' as suffix.",
235 str.as_ref()
236 )),
237 }
238}
239
240/// Formats a duration into a string like "5d 3h 17m 2s 12ms".
241///
242/// As the format indicates this is mostly used for "shorter" durations which rather run in
243/// seconds or minutes rather than several days. However, if required, we can still format such
244/// a value, even if outputting milliseconds in this case is kind of questionable.
245///
246/// Also note that for formatting sort durations (in microseconds) we have a dedicated function
247/// named [format_short_duration](format_short_duration).
248///
249/// # Examples
250///
251/// ```
252/// # use std::time::Duration;
253/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_millis(13)), "13ms");
254/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_millis(1013)), "1s 13ms");
255/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_millis(62_013)), "1m 2s 13ms");
256/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(60 * 32 + 13)), "32m 13s");
257/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(60 * 61)), "1h 1m");
258/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(4 * 60 * 60)), "4h");
259/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(24 * 60 * 60 + 60 * 60 + 60)), "1d 1h 1m");
260/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(24 * 60 * 60 + 60 * 60 + 59)), "1d 1h 59s");
261/// ```
262pub fn format_duration(duration: Duration) -> String {
263 let mut result = String::new();
264
265 let mut value = duration.as_millis();
266 {
267 let days = value / (1000 * 60 * 60 * 24);
268 if days > 0 {
269 let _ = write!(result, "{}d", days);
270 value %= 1000 * 60 * 60 * 24;
271 }
272 }
273 {
274 let hours = value / (1000 * 60 * 60);
275 if hours > 0 {
276 if !result.is_empty() {
277 result.push(' ');
278 }
279 let _ = write!(result, "{}h", hours);
280 value %= 1000 * 60 * 60;
281 }
282 }
283 {
284 let minutes = value / (1000 * 60);
285 if minutes > 0 {
286 if !result.is_empty() {
287 result.push(' ');
288 }
289 let _ = write!(result, "{}m", minutes);
290 value %= 1000 * 60;
291 }
292 }
293 {
294 let seconds = value / 1000;
295 if seconds > 0 {
296 if !result.is_empty() {
297 result.push(' ');
298 }
299 let _ = write!(result, "{}s", seconds);
300 value %= 1000;
301 }
302 }
303 if value > 0 {
304 if !result.is_empty() {
305 result.push(' ');
306 }
307 let _ = write!(result, "{}ms", value);
308 }
309
310 result
311}