pingap_util/
format.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use bytes::BytesMut;
16
17const SEC: u64 = 1_000;
18
19/// Formats a duration in milliseconds into a human readable string
20/// For durations < 1000ms, formats as milliseconds
21/// For durations >= 1000ms, formats as seconds with up to one decimal place
22///
23/// # Arguments
24/// * `buf` - BytesMut buffer to write the formatted string into
25/// * `ms` - Duration in milliseconds to format
26///
27/// # Returns
28/// BytesMut buffer containing the formatted string
29#[inline]
30pub fn format_duration(mut buf: BytesMut, ms: u64) -> BytesMut {
31    if ms < 1000 {
32        buf.extend(itoa::Buffer::new().format(ms).as_bytes());
33        buf.extend(b"ms");
34    } else {
35        buf.extend(itoa::Buffer::new().format(ms / SEC).as_bytes());
36        let value = (ms % SEC) / 100;
37        if value != 0 {
38            buf.extend(b".");
39            buf.extend(itoa::Buffer::new().format(value).as_bytes());
40        }
41        buf.extend(b"s");
42    }
43    buf
44}
45
46const B_100: usize = 100;
47const KB: usize = 1_000;
48const KB_100: usize = 100 * KB;
49const MB: usize = 1_000_000;
50const MB_100: usize = 100 * MB;
51const GB: usize = 1_000_000_000;
52
53/// Formats a byte size into a human readable string with appropriate units (B, KB, MB, GB)
54/// The function will add decimal points for values between units (e.g., 1.5KB)
55///
56/// # Arguments
57/// * `buf` - BytesMut buffer to write the formatted string into
58/// * `size` - Size in bytes to format
59///
60/// # Returns
61/// BytesMut buffer containing the formatted string
62#[inline]
63pub fn format_byte_size(mut buf: BytesMut, size: usize) -> BytesMut {
64    if size < KB {
65        buf.extend(itoa::Buffer::new().format(size).as_bytes());
66        buf.extend(b"B");
67    } else if size < MB {
68        buf.extend(itoa::Buffer::new().format(size / KB).as_bytes());
69        let value = (size % KB) / B_100;
70        if value != 0 {
71            buf.extend(b".");
72            buf.extend(itoa::Buffer::new().format(value).as_bytes());
73        }
74        buf.extend(b"KB");
75    } else if size < GB {
76        buf.extend(itoa::Buffer::new().format(size / MB).as_bytes());
77        let value = (size % MB) / KB_100;
78        if value != 0 {
79            buf.extend(b".");
80            buf.extend(itoa::Buffer::new().format(value).as_bytes());
81        }
82        buf.extend(b"MB");
83    } else {
84        buf.extend(itoa::Buffer::new().format(size / GB).as_bytes());
85        let value = (size % GB) / MB_100;
86        if value != 0 {
87            buf.extend(b".");
88            buf.extend(itoa::Buffer::new().format(value).as_bytes());
89        }
90        buf.extend(b"GB");
91    }
92    buf
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_format_byte_size() {
101        let mut buf = BytesMut::with_capacity(1024);
102        buf = format_byte_size(buf, 512);
103        assert_eq!(
104            "512B",
105            std::string::String::from_utf8_lossy(&buf).to_string()
106        );
107
108        buf.clear();
109        buf = format_byte_size(buf, 1024);
110        assert_eq!(
111            "1KB",
112            std::string::String::from_utf8_lossy(&buf).to_string()
113        );
114
115        buf.clear();
116        buf = format_byte_size(buf, 1124);
117        assert_eq!(
118            "1.1KB",
119            std::string::String::from_utf8_lossy(&buf).to_string()
120        );
121
122        buf.clear();
123        buf = format_byte_size(buf, 1020 * 1000);
124        assert_eq!(
125            "1MB",
126            std::string::String::from_utf8_lossy(&buf).to_string()
127        );
128
129        buf.clear();
130        buf = format_byte_size(buf, 1220 * 1000);
131        assert_eq!(
132            "1.2MB",
133            std::string::String::from_utf8_lossy(&buf).to_string()
134        );
135
136        buf.clear();
137        buf = format_byte_size(buf, 122220 * 1000);
138        assert_eq!(
139            "122.2MB",
140            std::string::String::from_utf8_lossy(&buf).to_string()
141        );
142
143        buf.clear();
144        buf = format_byte_size(buf, 1000 * 1000 * 1000 + 500 * 1000 * 1000);
145        assert_eq!(
146            "1.5GB",
147            std::string::String::from_utf8_lossy(&buf).to_string()
148        );
149    }
150
151    #[test]
152    fn test_format_duration() {
153        let mut buf = BytesMut::with_capacity(1024);
154        buf = format_duration(buf, 100);
155        assert_eq!(
156            "100ms",
157            std::string::String::from_utf8_lossy(&buf).to_string()
158        );
159
160        buf.clear();
161        buf = format_duration(buf, 12400);
162        assert_eq!(
163            "12.4s",
164            std::string::String::from_utf8_lossy(&buf).to_string()
165        );
166    }
167}