master
c 211 lines 8.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "size.h"
4
5 // Define multipliers for base 2 (binary) units
6 #define SIZE_MULTIPLIER_BASE2 1024ULL
7 #define SIZE_MULTIPLIER_KiB (SIZE_MULTIPLIER_BASE2)
8 #define SIZE_MULTIPLIER_MiB (SIZE_MULTIPLIER_KiB * SIZE_MULTIPLIER_BASE2)
9 #define SIZE_MULTIPLIER_GiB (SIZE_MULTIPLIER_MiB * SIZE_MULTIPLIER_BASE2)
10 #define SIZE_MULTIPLIER_TiB (SIZE_MULTIPLIER_GiB * SIZE_MULTIPLIER_BASE2)
11 #define SIZE_MULTIPLIER_PiB (SIZE_MULTIPLIER_TiB * SIZE_MULTIPLIER_BASE2)
12 //#define SIZE_MULTIPLIER_EiB (SIZE_MULTIPLIER_PiB * SIZE_MULTIPLIER_BASE2)
13 //#define SIZE_MULTIPLIER_ZiB (SIZE_MULTIPLIER_EiB * SIZE_MULTIPLIER_BASE2)
14 //#define SIZE_MULTIPLIER_YiB (SIZE_MULTIPLIER_ZiB * SIZE_MULTIPLIER_BASE2)
15
16 // Define multipliers for base 10 (decimal) units
17 #define SIZE_MULTIPLIER_BASE10 1000ULL
18 #define SIZE_MULTIPLIER_K (SIZE_MULTIPLIER_BASE10)
19 #define SIZE_MULTIPLIER_M (SIZE_MULTIPLIER_K * SIZE_MULTIPLIER_BASE10)
20 #define SIZE_MULTIPLIER_G (SIZE_MULTIPLIER_M * SIZE_MULTIPLIER_BASE10)
21 #define SIZE_MULTIPLIER_T (SIZE_MULTIPLIER_G * SIZE_MULTIPLIER_BASE10)
22 #define SIZE_MULTIPLIER_P (SIZE_MULTIPLIER_T * SIZE_MULTIPLIER_BASE10)
23 //#define SIZE_MULTIPLIER_E (SIZE_MULTIPLIER_P * SIZE_MULTIPLIER_BASE10)
24 //#define SIZE_MULTIPLIER_Z (SIZE_MULTIPLIER_E * SIZE_MULTIPLIER_BASE10)
25 //#define SIZE_MULTIPLIER_Y (SIZE_MULTIPLIER_Z * SIZE_MULTIPLIER_BASE10)
26
27 // Define a structure to map size units to their multipliers
28 static const struct size_unit {
29 const char *unit;
30 const uint8_t base;
31 const bool formatter; // true when this unit should be used when formatting to string
32 const uint64_t multiplier;
33 } size_units[] = {
34 // the order of this table is important: smaller to bigger units!
35
36 { .unit = "B", .base = 2, .formatter = true, .multiplier = 1ULL },
37 { .unit = "k", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_K },
38 { .unit = "K", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_K },
39 { .unit = "KB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_K },
40 { .unit = "KiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_KiB },
41 { .unit = "M", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_M },
42 { .unit = "MB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_M },
43 { .unit = "MiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_MiB },
44 { .unit = "G", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_G },
45 { .unit = "GB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_G },
46 { .unit = "GiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_GiB },
47 { .unit = "T", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_T },
48 { .unit = "TB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_T },
49 { .unit = "TiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_TiB },
50 { .unit = "P", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_P },
51 { .unit = "PB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_P },
52 { .unit = "PiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_PiB },
53 // { .unit = "E", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_E },
54 // { .unit = "EB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_E },
55 // { .unit = "EiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_EiB },
56 // { .unit = "Z", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_Z },
57 // { .unit = "ZB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_Z },
58 // { .unit = "ZiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_ZiB },
59 // { .unit = "Y", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_Y },
60 // { .unit = "YB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_Y },
61 // { .unit = "YiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_YiB },
62 };
63
64 static inline const struct size_unit *size_find_unit(const char *unit) {
65 if (!unit || !*unit) unit = "B";
66
67 for (size_t i = 0; i < sizeof(size_units) / sizeof(size_units[0]); i++) {
68 const struct size_unit *su = &size_units[i];
69 if ((uint8_t)unit[0] == (uint8_t)su->unit[0] && strcmp(unit, su->unit) == 0)
70 return su;
71 }
72
73 return NULL;
74 }
75
76 static inline double size_round_to_resolution_dbl2(uint64_t value, uint64_t resolution) {
77 double converted = (double)value / (double)resolution;
78 return round(converted * 100.0) / 100.0;
79 }
80
81 static inline uint64_t size_round_to_resolution_int(uint64_t value, uint64_t resolution) {
82 return (value + (resolution / 2)) / resolution;
83 }
84
85 // -------------------------------------------------------------------------------------------------------------------
86 // parse a size string
87
88 bool size_parse(const char *size_str, uint64_t *result, const char *default_unit) {
89 if (!size_str || !*size_str) {
90 *result = 0;
91 return false;
92 }
93
94 const struct size_unit *su_def = size_find_unit(default_unit);
95 if(!su_def) {
96 *result = 0;
97 return false;
98 }
99
100 const char *s = size_str;
101
102 // Skip leading spaces
103 while (isspace((uint8_t)*s)) s++;
104
105 if(strcmp(s, "off") == 0) {
106 *result = 0;
107 return true;
108 }
109
110 // Parse the number
111 const char *number_start = s;
112 NETDATA_DOUBLE value = strtondd(s, (char **)&s);
113
114 // If no valid number found, return false
115 if (s == number_start || value < 0) {
116 *result = 0;
117 return false;
118 }
119
120 // Skip spaces between number and unit
121 while (isspace((uint8_t)*s)) s++;
122
123 const char *unit_start = s;
124 while (isalpha((uint8_t)*s)) s++;
125
126 char unit[4];
127 size_t unit_len = s - unit_start;
128 const struct size_unit *su;
129 if (unit_len == 0)
130 su = su_def;
131 else {
132 if (unit_len >= sizeof(unit)) unit_len = sizeof(unit) - 1;
133 memcpy(unit, unit_start, unit_len);
134 unit[unit_len] = '\0';
135 su = size_find_unit(unit);
136 if (!su) {
137 *result = 0;
138 return false;
139 }
140 }
141
142 uint64_t bytes = (uint64_t)round(value * (NETDATA_DOUBLE)su->multiplier);
143 *result = size_round_to_resolution_int(bytes, su_def->multiplier);
144
145 return true;
146 }
147
148 // --------------------------------------------------------------------------------------------------------------------
149 // generate a string to represent a size
150
151 ssize_t size_snprintf(char *dst, size_t dst_size, uint64_t value, const char *unit, bool accurate) {
152 if (!dst || dst_size == 0) return -1;
153 if (dst_size == 1) {
154 dst[0] = '\0';
155 return -2;
156 }
157
158 if (value == 0)
159 return snprintfz(dst, dst_size, "off");
160
161 const struct size_unit *su_def = size_find_unit(unit);
162 if(!su_def) return -3;
163
164 // use the units multiplier to find the units
165 uint64_t bytes = value * su_def->multiplier;
166
167 // Find the best unit to represent the size with up to 2 fractional digits
168 const struct size_unit *su_best = su_def;
169 for (size_t i = 0; i < sizeof(size_units) / sizeof(size_units[0]); i++) {
170 const struct size_unit *su = &size_units[i];
171 if (su->base != su_def->base || // not the right base
172 su->multiplier < su_def->multiplier || // the multiplier is too small
173 (!su->formatter && su != su_def) || // it is not to be used in formatting (except our unit)
174 (bytes < su->multiplier && su != su_def) ) // the converted value will be <1.0
175 continue;
176
177 double converted = size_round_to_resolution_dbl2(bytes, su->multiplier);
178 uint64_t reversed_bytes = (uint64_t)round((converted * (double)su->multiplier));
179
180 if(accurate) {
181 // no precision loss is required
182 if (reversed_bytes == bytes && converted > 1.0)
183 // no precision loss, this is good to use
184 su_best = su;
185 }
186 else {
187 if(converted > 1.0)
188 su_best = su;
189 }
190 }
191
192 double converted = size_round_to_resolution_dbl2(bytes, su_best->multiplier);
193
194 // print it either with 0, 1 or 2 fractional digits
195 int written;
196 if(converted == (double)((uint64_t)converted))
197 written = snprintfz(dst, dst_size, "%.0f%s", converted, su_best->unit);
198 else if(converted * 10.0 == (double)((uint64_t)(converted * 10.0)))
199 written = snprintfz(dst, dst_size, "%.1f%s", converted, su_best->unit);
200 else
201 written = snprintfz(dst, dst_size, "%.2f%s", converted, su_best->unit);
202
203 if (written < 0)
204 return -4;
205
206 if ((size_t)written >= dst_size)
207 return (ssize_t)(dst_size - 1);
208
209 return written;
210 }
211