1 use anyhow
::{bail, Error}
;
3 use proxmox_schema
::{ApiStringFormat, ApiType, Schema, StringSchema, UpdaterType}
;
5 /// Size units for byte sizes
6 #[derive(Debug, Copy, Clone, PartialEq)]
24 /// Returns the scaling factor
25 pub fn factor(&self) -> f64 {
27 SizeUnit
::Byte
=> 1.0,
29 SizeUnit
::KByte
=> 1_000.0,
30 SizeUnit
::MByte
=> 1_000_000.0,
31 SizeUnit
::GByte
=> 1_000_000_000.0,
32 SizeUnit
::TByte
=> 1_000_000_000_000.0,
33 SizeUnit
::PByte
=> 1_000_000_000_000_000.0,
35 SizeUnit
::Kibi
=> 1024.0,
36 SizeUnit
::Mebi
=> 1024.0 * 1024.0,
37 SizeUnit
::Gibi
=> 1024.0 * 1024.0 * 1024.0,
38 SizeUnit
::Tebi
=> 1024.0 * 1024.0 * 1024.0 * 1024.0,
39 SizeUnit
::Pebi
=> 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0,
43 /// gets the biggest possible unit still having a value greater zero before the decimal point
44 /// 'binary' specifies if IEC (base 2) units should be used or SI (base 10) ones
45 pub fn auto_scale(size
: f64, binary
: bool
) -> SizeUnit
{
47 let bits
= 64 - (size
as u64).leading_zeros();
49 51.. => SizeUnit
::Pebi
,
50 41..=50 => SizeUnit
::Tebi
,
51 31..=40 => SizeUnit
::Gibi
,
52 21..=30 => SizeUnit
::Mebi
,
53 11..=20 => SizeUnit
::Kibi
,
57 if size
>= 1_000_000_000_000_000.0 {
59 } else if size
>= 1_000_000_000_000.0 {
61 } else if size
>= 1_000_000_000.0 {
63 } else if size
>= 1_000_000.0 {
65 } else if size
>= 1_000.0 {
74 /// Returns the string repesentation
75 impl std
::fmt
::Display
for SizeUnit
{
76 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
78 SizeUnit
::Byte
=> write
!(f
, "B"),
80 SizeUnit
::KByte
=> write
!(f
, "KB"),
81 SizeUnit
::MByte
=> write
!(f
, "MB"),
82 SizeUnit
::GByte
=> write
!(f
, "GB"),
83 SizeUnit
::TByte
=> write
!(f
, "TB"),
84 SizeUnit
::PByte
=> write
!(f
, "PB"),
86 SizeUnit
::Kibi
=> write
!(f
, "KiB"),
87 SizeUnit
::Mebi
=> write
!(f
, "MiB"),
88 SizeUnit
::Gibi
=> write
!(f
, "GiB"),
89 SizeUnit
::Tebi
=> write
!(f
, "TiB"),
90 SizeUnit
::Pebi
=> write
!(f
, "PiB"),
95 /// Strips a trailing SizeUnit inclusive trailing whitespace
96 /// Supports both IEC and SI based scales, the B/b byte symbol is optional.
97 fn strip_unit(v
: &str) -> (&str, SizeUnit
) {
98 let v
= v
.strip_suffix(&['b'
, 'B'
][..]).unwrap_or(v
); // byte is implied anyway
100 let (v
, binary
) = match v
.strip_suffix('i'
) {
101 Some(n
) => (n
, true),
105 let mut unit
= SizeUnit
::Byte
;
106 (v
.strip_suffix(|c
: char| match c
{
107 'k'
| 'K'
if !binary
=> { unit = SizeUnit::KByte; true }
108 'm'
| 'M'
if !binary
=> { unit = SizeUnit::MByte; true }
109 'g'
| 'G'
if !binary
=> { unit = SizeUnit::GByte; true }
110 't'
| 'T'
if !binary
=> { unit = SizeUnit::TByte; true }
111 'p'
| 'P'
if !binary
=> { unit = SizeUnit::PByte; true }
112 // binary (IEC recommended) variants
113 'k'
| 'K'
if binary
=> { unit = SizeUnit::Kibi; true }
114 'm'
| 'M'
if binary
=> { unit = SizeUnit::Mebi; true }
115 'g'
| 'G'
if binary
=> { unit = SizeUnit::Gibi; true }
116 't'
| 'T'
if binary
=> { unit = SizeUnit::Tebi; true }
117 'p'
| 'P'
if binary
=> { unit = SizeUnit::Pebi; true }
119 }).unwrap_or(v
).trim_end(), unit
)
122 /// Byte size which can be displayed in a human friendly way
123 #[derive(Debug, Copy, Clone, UpdaterType)]
124 pub struct HumanByte
{
125 /// The siginficant value, it does not includes any factor of the `unit`
127 /// The scale/unit of the value
131 fn verify_human_byte(s
: &str) -> Result
<(), Error
> {
132 match s
.parse
::<HumanByte
>() {
134 Err(err
) => bail
!("byte-size parse error for '{}': {}", s
, err
),
137 impl ApiType
for HumanByte
{
138 const API_SCHEMA
: Schema
= StringSchema
::new(
139 "Byte size with optional unit (B, KB (base 10), MB, GB, ..., KiB (base 2), MiB, Gib, ...).",
141 .format(&ApiStringFormat
::VerifyFn(verify_human_byte
))
148 /// Create instance with size and unit (size must be positive)
149 pub fn with_unit(size
: f64, unit
: SizeUnit
) -> Result
<Self, Error
> {
151 bail
!("byte size may not be negative");
153 Ok(HumanByte { size, unit }
)
156 /// Create a new instance with optimal binary unit computed
157 pub fn new_binary(size
: f64) -> Self {
158 let unit
= SizeUnit
::auto_scale(size
, true);
159 HumanByte { size: size / unit.factor(), unit }
162 /// Create a new instance with optimal decimal unit computed
163 pub fn new_decimal(size
: f64) -> Self {
164 let unit
= SizeUnit
::auto_scale(size
, false);
165 HumanByte { size: size / unit.factor(), unit }
168 /// Returns the size as u64 number of bytes
169 pub fn as_u64(&self) -> u64 {
173 /// Returns the size as f64 number of bytes
174 pub fn as_f64(&self) -> f64 {
175 self.size
* self.unit
.factor()
178 /// Returns a copy with optimal binary unit computed
179 pub fn auto_scale_binary(self) -> Self {
180 HumanByte
::new_binary(self.as_f64())
183 /// Returns a copy with optimal decimal unit computed
184 pub fn auto_scale_decimal(self) -> Self {
185 HumanByte
::new_decimal(self.as_f64())
189 impl From
<u64> for HumanByte
{
190 fn from(v
: u64) -> Self {
191 HumanByte
::new_binary(v
as f64)
194 impl From
<usize> for HumanByte
{
195 fn from(v
: usize) -> Self {
196 HumanByte
::new_binary(v
as f64)
200 impl std
::fmt
::Display
for HumanByte
{
201 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
202 let precision
= f
.precision().unwrap_or(3) as f64;
203 let precision_factor
= 1.0 * 10.0_f64.powf(precision
);
204 // this could cause loss of information, rust has sadly no shortest-max-X flt2dec fmt yet
205 let size
= ((self.size
* precision_factor
).round()) / precision_factor
;
206 write
!(f
, "{} {}", size
, self.unit
)
210 impl std
::str::FromStr
for HumanByte
{
213 fn from_str(v
: &str) -> Result
<Self, Error
> {
214 let (v
, unit
) = strip_unit(v
);
215 HumanByte
::with_unit(v
.parse()?
, unit
)
219 proxmox_serde
::forward_deserialize_to_from_str
!(HumanByte
);
220 proxmox_serde
::forward_serialize_to_display
!(HumanByte
);
223 fn test_human_byte_parser() -> Result
<(), Error
> {
224 assert
!("-10".parse
::<HumanByte
>().is_err()); // negative size
226 fn do_test(v
: &str, size
: f64, unit
: SizeUnit
, as_str
: &str) -> Result
<(), Error
> {
227 let h
: HumanByte
= v
.parse()?
;
230 bail
!("got unexpected size for '{}' ({} != {})", v
, h
.size
, size
);
233 bail
!("got unexpected unit for '{}' ({:?} != {:?})", v
, h
.unit
, unit
);
236 let new
= h
.to_string();
238 bail
!("to_string failed for '{}' ({:?} != {:?})", v
, new
, as_str
);
242 fn test(v
: &str, size
: f64, unit
: SizeUnit
, as_str
: &str) -> bool
{
243 match do_test(v
, size
, unit
, as_str
) {
246 eprintln
!("{}", err
); // makes debugging easier
252 assert
!(test("14", 14.0, SizeUnit
::Byte
, "14 B"));
253 assert
!(test("14.4", 14.4, SizeUnit
::Byte
, "14.4 B"));
254 assert
!(test("14.45", 14.45, SizeUnit
::Byte
, "14.45 B"));
255 assert
!(test("14.456", 14.456, SizeUnit
::Byte
, "14.456 B"));
256 assert
!(test("14.4567", 14.4567, SizeUnit
::Byte
, "14.457 B"));
258 let h
: HumanByte
= "1.2345678".parse()?
;
259 assert_eq
!(&format
!("{:.0}", h
), "1 B");
260 assert_eq
!(&format
!("{:.0}", h
.as_f64()), "1"); // use as_f64 to get raw bytes without unit
261 assert_eq
!(&format
!("{:.1}", h
), "1.2 B");
262 assert_eq
!(&format
!("{:.2}", h
), "1.23 B");
263 assert_eq
!(&format
!("{:.3}", h
), "1.235 B");
264 assert_eq
!(&format
!("{:.4}", h
), "1.2346 B");
265 assert_eq
!(&format
!("{:.5}", h
), "1.23457 B");
266 assert_eq
!(&format
!("{:.6}", h
), "1.234568 B");
267 assert_eq
!(&format
!("{:.7}", h
), "1.2345678 B");
268 assert_eq
!(&format
!("{:.8}", h
), "1.2345678 B");
270 assert
!(test("987654321", 987654321.0, SizeUnit
::Byte
, "987654321 B"));
272 assert
!(test("1300b", 1300.0, SizeUnit
::Byte
, "1300 B"));
273 assert
!(test("1300B", 1300.0, SizeUnit
::Byte
, "1300 B"));
274 assert
!(test("1300 B", 1300.0, SizeUnit
::Byte
, "1300 B"));
275 assert
!(test("1300 b", 1300.0, SizeUnit
::Byte
, "1300 B"));
277 assert
!(test("1.5KB", 1.5, SizeUnit
::KByte
, "1.5 KB"));
278 assert
!(test("1.5kb", 1.5, SizeUnit
::KByte
, "1.5 KB"));
279 assert
!(test("1.654321MB", 1.654_321, SizeUnit
::MByte
, "1.654 MB"));
281 assert
!(test("2.0GB", 2.0, SizeUnit
::GByte
, "2 GB"));
283 assert
!(test("1.4TB", 1.4, SizeUnit
::TByte
, "1.4 TB"));
284 assert
!(test("1.4tb", 1.4, SizeUnit
::TByte
, "1.4 TB"));
286 assert
!(test("2KiB", 2.0, SizeUnit
::Kibi
, "2 KiB"));
287 assert
!(test("2Ki", 2.0, SizeUnit
::Kibi
, "2 KiB"));
288 assert
!(test("2kib", 2.0, SizeUnit
::Kibi
, "2 KiB"));
290 assert
!(test("2.3454MiB", 2.3454, SizeUnit
::Mebi
, "2.345 MiB"));
291 assert
!(test("2.3456MiB", 2.3456, SizeUnit
::Mebi
, "2.346 MiB"));
293 assert
!(test("4gib", 4.0, SizeUnit
::Gibi
, "4 GiB"));
299 fn test_human_byte_auto_unit_decimal() {
300 fn convert(b
: u64) -> String
{
301 HumanByte
::new_decimal(b
as f64).to_string()
303 assert_eq
!(convert(987), "987 B");
304 assert_eq
!(convert(1022), "1.022 KB");
305 assert_eq
!(convert(9_000), "9 KB");
306 assert_eq
!(convert(1_000), "1 KB");
307 assert_eq
!(convert(1_000_000), "1 MB");
308 assert_eq
!(convert(1_000_000_000), "1 GB");
309 assert_eq
!(convert(1_000_000_000_000), "1 TB");
310 assert_eq
!(convert(1_000_000_000_000_000), "1 PB");
312 assert_eq
!(convert((1 << 30) + 103 * (1 << 20)), "1.182 GB");
313 assert_eq
!(convert((1 << 30) + 128 * (1 << 20)), "1.208 GB");
314 assert_eq
!(convert((2 << 50) + 500 * (1 << 40)), "2.802 PB");
318 fn test_human_byte_auto_unit_binary() {
319 fn convert(b
: u64) -> String
{
320 HumanByte
::from(b
).to_string()
322 assert_eq
!(convert(0), "0 B");
323 assert_eq
!(convert(987), "987 B");
324 assert_eq
!(convert(1022), "1022 B");
325 assert_eq
!(convert(9_000), "8.789 KiB");
326 assert_eq
!(convert(10_000_000), "9.537 MiB");
327 assert_eq
!(convert(10_000_000_000), "9.313 GiB");
328 assert_eq
!(convert(10_000_000_000_000), "9.095 TiB");
330 assert_eq
!(convert(1 << 10), "1 KiB");
331 assert_eq
!(convert((1 << 10) * 10), "10 KiB");
332 assert_eq
!(convert(1 << 20), "1 MiB");
333 assert_eq
!(convert(1 << 30), "1 GiB");
334 assert_eq
!(convert(1 << 40), "1 TiB");
335 assert_eq
!(convert(1 << 50), "1 PiB");
337 assert_eq
!(convert((1 << 30) + 103 * (1 << 20)), "1.101 GiB");
338 assert_eq
!(convert((1 << 30) + 128 * (1 << 20)), "1.125 GiB");
339 assert_eq
!(convert((1 << 40) + 128 * (1 << 30)), "1.125 TiB");
340 assert_eq
!(convert((2 << 50) + 512 * (1 << 40)), "2.5 PiB");