master
c 183 lines 6.48 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "entries.h"
4
5 // Define multipliers for base 10 (decimal) units
6 #define ENTRIES_MULTIPLIER_BASE10 1000ULL
7 #define ENTRIES_MULTIPLIER_K (ENTRIES_MULTIPLIER_BASE10)
8 #define ENTRIES_MULTIPLIER_M (ENTRIES_MULTIPLIER_K * ENTRIES_MULTIPLIER_BASE10)
9 #define ENTRIES_MULTIPLIER_G (ENTRIES_MULTIPLIER_M * ENTRIES_MULTIPLIER_BASE10)
10 #define ENTRIES_MULTIPLIER_T (ENTRIES_MULTIPLIER_G * ENTRIES_MULTIPLIER_BASE10)
11 #define ENTRIES_MULTIPLIER_P (ENTRIES_MULTIPLIER_T * ENTRIES_MULTIPLIER_BASE10)
12 #define ENTRIES_MULTIPLIER_E (ENTRIES_MULTIPLIER_P * ENTRIES_MULTIPLIER_BASE10)
13 #define ENTRIES_MULTIPLIER_Z (ENTRIES_MULTIPLIER_E * ENTRIES_MULTIPLIER_BASE10)
14 #define ENTRIES_MULTIPLIER_Y (ENTRIES_MULTIPLIER_Z * ENTRIES_MULTIPLIER_BASE10)
15
16 // Define a structure to map size units to their multipliers
17 static const struct size_unit {
18 const char *unit;
19 const bool formatter; // true when this unit should be used when formatting to string
20 const uint64_t multiplier;
21 } entries_units[] = {
22 // the order of this table is important: smaller to bigger units!
23
24 { .unit = "", .formatter = true, .multiplier = 1ULL },
25 { .unit = "k", .formatter = false, .multiplier = ENTRIES_MULTIPLIER_K },
26 { .unit = "K", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_K },
27 { .unit = "M", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_M },
28 { .unit = "G", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_G },
29 { .unit = "T", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_T },
30 { .unit = "P", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_P },
31 { .unit = "E", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_E },
32 { .unit = "Z", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_Z },
33 { .unit = "Y", .formatter = true, .multiplier = ENTRIES_MULTIPLIER_Y },
34 };
35
36 static inline const struct size_unit *entries_find_unit(const char *unit) {
37 if (!unit || !*unit) unit = "";
38
39 for (size_t i = 0; i < sizeof(entries_units) / sizeof(entries_units[0]); i++) {
40 const struct size_unit *su = &entries_units[i];
41 if ((uint8_t)unit[0] == (uint8_t)su->unit[0] && strcmp(unit, su->unit) == 0)
42 return su;
43 }
44
45 return NULL;
46 }
47
48 static inline double entries_round_to_resolution_dbl2(uint64_t value, uint64_t resolution) {
49 double converted = (double)value / (double)resolution;
50 return round(converted * 100.0) / 100.0;
51 }
52
53 static inline uint64_t entries_round_to_resolution_int(uint64_t value, uint64_t resolution) {
54 return (value + (resolution / 2)) / resolution;
55 }
56
57 // -------------------------------------------------------------------------------------------------------------------
58 // parse a size string
59
60 bool entries_parse(const char *entries_str, uint64_t *result, const char *default_unit) {
61 if (!entries_str || !*entries_str) {
62 *result = 0;
63 return false;
64 }
65
66 const struct size_unit *su_def = entries_find_unit(default_unit);
67 if(!su_def) {
68 *result = 0;
69 return false;
70 }
71
72 const char *s = entries_str;
73
74 // Skip leading spaces
75 while (isspace((uint8_t)*s)) s++;
76
77 if(strcmp(s, "off") == 0) {
78 *result = 0;
79 return true;
80 }
81
82 // Parse the number
83 const char *number_start = s;
84 NETDATA_DOUBLE value = strtondd(s, (char **)&s);
85
86 // If no valid number found, return false
87 if (s == number_start || value < 0) {
88 *result = 0;
89 return false;
90 }
91
92 // Skip spaces between number and unit
93 while (isspace((uint8_t)*s)) s++;
94
95 const char *unit_start = s;
96 while (isalpha((uint8_t)*s)) s++;
97
98 char unit[4];
99 size_t unit_len = s - unit_start;
100 const struct size_unit *su;
101 if (unit_len == 0)
102 su = su_def;
103 else {
104 if (unit_len >= sizeof(unit)) unit_len = sizeof(unit) - 1;
105 memcpy(unit, unit_start, unit_len);
106 unit[unit_len] = '\0';
107 su = entries_find_unit(unit);
108 if (!su) {
109 *result = 0;
110 return false;
111 }
112 }
113
114 uint64_t bytes = (uint64_t)round(value * (NETDATA_DOUBLE)su->multiplier);
115 *result = entries_round_to_resolution_int(bytes, su_def->multiplier);
116
117 return true;
118 }
119
120 // --------------------------------------------------------------------------------------------------------------------
121 // generate a string to represent a size
122
123 ssize_t entries_snprintf(char *dst, size_t dst_size, uint64_t value, const char *unit, bool accurate) {
124 if (!dst || dst_size == 0) return -1;
125 if (dst_size == 1) {
126 dst[0] = '\0';
127 return -2;
128 }
129
130 if (value == 0)
131 return snprintfz(dst, dst_size, "off");
132
133 const struct size_unit *su_def = entries_find_unit(unit);
134 if(!su_def) return -3;
135
136 // use the units multiplier to find the units
137 uint64_t bytes = value * su_def->multiplier;
138
139 // Find the best unit to represent the size with up to 2 fractional digits
140 const struct size_unit *su_best = su_def;
141 for (size_t i = 0; i < sizeof(entries_units) / sizeof(entries_units[0]); i++) {
142 const struct size_unit *su = &entries_units[i];
143 if (su->multiplier < su_def->multiplier || // the multiplier is too small
144 (!su->formatter && su != su_def) || // it is not to be used in formatting (except our unit)
145 (bytes < su->multiplier && su != su_def) ) // the converted value will be <1.0
146 continue;
147
148 double converted = entries_round_to_resolution_dbl2(bytes, su->multiplier);
149
150 uint64_t reversed_bytes = (uint64_t)round((converted * (double)su->multiplier));
151
152 if(accurate) {
153 // no precision loss is required
154 if (reversed_bytes == bytes && converted > 1.0)
155 // no precision loss, this is good to use
156 su_best = su;
157 }
158 else {
159 if(converted > 1.0)
160 su_best = su;
161 }
162 }
163
164 double converted = entries_round_to_resolution_dbl2(bytes, su_best->multiplier);
165
166 // print it either with 0, 1 or 2 fractional digits
167 int written;
168 if(converted == (double)((uint64_t)converted))
169 written = snprintfz(dst, dst_size, "%.0f%s", converted, su_best->unit);
170 else if(converted * 10.0 == (double)((uint64_t)(converted * 10.0)))
171 written = snprintfz(dst, dst_size, "%.1f%s", converted, su_best->unit);
172 else
173 written = snprintfz(dst, dst_size, "%.2f%s", converted, su_best->unit);
174
175 if (written < 0)
176 return -4;
177
178 if ((size_t)written >= dst_size)
179 return (ssize_t)(dst_size - 1);
180
181 return written;
182 }
183