diff options
Diffstat (limited to 'src/libnetdata/parsers/size.c')
-rw-r--r-- | src/libnetdata/parsers/size.c | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/libnetdata/parsers/size.c b/src/libnetdata/parsers/size.c new file mode 100644 index 000000000..d3a24c540 --- /dev/null +++ b/src/libnetdata/parsers/size.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "size.h" + +// Define multipliers for base 2 (binary) units +#define SIZE_MULTIPLIER_BASE2 1024ULL +#define SIZE_MULTIPLIER_KiB (SIZE_MULTIPLIER_BASE2) +#define SIZE_MULTIPLIER_MiB (SIZE_MULTIPLIER_KiB * SIZE_MULTIPLIER_BASE2) +#define SIZE_MULTIPLIER_GiB (SIZE_MULTIPLIER_MiB * SIZE_MULTIPLIER_BASE2) +#define SIZE_MULTIPLIER_TiB (SIZE_MULTIPLIER_GiB * SIZE_MULTIPLIER_BASE2) +#define SIZE_MULTIPLIER_PiB (SIZE_MULTIPLIER_TiB * SIZE_MULTIPLIER_BASE2) +//#define SIZE_MULTIPLIER_EiB (SIZE_MULTIPLIER_PiB * SIZE_MULTIPLIER_BASE2) +//#define SIZE_MULTIPLIER_ZiB (SIZE_MULTIPLIER_EiB * SIZE_MULTIPLIER_BASE2) +//#define SIZE_MULTIPLIER_YiB (SIZE_MULTIPLIER_ZiB * SIZE_MULTIPLIER_BASE2) + +// Define multipliers for base 10 (decimal) units +#define SIZE_MULTIPLIER_BASE10 1000ULL +#define SIZE_MULTIPLIER_K (SIZE_MULTIPLIER_BASE10) +#define SIZE_MULTIPLIER_M (SIZE_MULTIPLIER_K * SIZE_MULTIPLIER_BASE10) +#define SIZE_MULTIPLIER_G (SIZE_MULTIPLIER_M * SIZE_MULTIPLIER_BASE10) +#define SIZE_MULTIPLIER_T (SIZE_MULTIPLIER_G * SIZE_MULTIPLIER_BASE10) +#define SIZE_MULTIPLIER_P (SIZE_MULTIPLIER_T * SIZE_MULTIPLIER_BASE10) +//#define SIZE_MULTIPLIER_E (SIZE_MULTIPLIER_P * SIZE_MULTIPLIER_BASE10) +//#define SIZE_MULTIPLIER_Z (SIZE_MULTIPLIER_E * SIZE_MULTIPLIER_BASE10) +//#define SIZE_MULTIPLIER_Y (SIZE_MULTIPLIER_Z * SIZE_MULTIPLIER_BASE10) + +// Define a structure to map size units to their multipliers +static const struct size_unit { + const char *unit; + const uint8_t base; + const bool formatter; // true when this unit should be used when formatting to string + const uint64_t multiplier; +} size_units[] = { + // the order of this table is important: smaller to bigger units! + + { .unit = "B", .base = 2, .formatter = true, .multiplier = 1ULL }, + { .unit = "k", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_K }, + { .unit = "K", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_K }, + { .unit = "KB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_K }, + { .unit = "KiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_KiB }, + { .unit = "M", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_M }, + { .unit = "MB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_M }, + { .unit = "MiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_MiB }, + { .unit = "G", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_G }, + { .unit = "GB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_G }, + { .unit = "GiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_GiB }, + { .unit = "T", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_T }, + { .unit = "TB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_T }, + { .unit = "TiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_TiB }, + { .unit = "P", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_P }, + { .unit = "PB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_P }, + { .unit = "PiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_PiB }, +// { .unit = "E", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_E }, +// { .unit = "EB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_E }, +// { .unit = "EiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_EiB }, +// { .unit = "Z", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_Z }, +// { .unit = "ZB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_Z }, +// { .unit = "ZiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_ZiB }, +// { .unit = "Y", .base = 10, .formatter = true, .multiplier = SIZE_MULTIPLIER_Y }, +// { .unit = "YB", .base = 10, .formatter = false, .multiplier = SIZE_MULTIPLIER_Y }, +// { .unit = "YiB", .base = 2, .formatter = true, .multiplier = SIZE_MULTIPLIER_YiB }, +}; + +static inline const struct size_unit *size_find_unit(const char *unit) { + if (!unit || !*unit) unit = "B"; + + for (size_t i = 0; i < sizeof(size_units) / sizeof(size_units[0]); i++) { + const struct size_unit *su = &size_units[i]; + if ((uint8_t)unit[0] == (uint8_t)su->unit[0] && strcmp(unit, su->unit) == 0) + return su; + } + + return NULL; +} + +static inline double size_round_to_resolution_dbl2(uint64_t value, uint64_t resolution) { + double converted = (double)value / (double)resolution; + return round(converted * 100.0) / 100.0; +} + +static inline uint64_t size_round_to_resolution_int(uint64_t value, uint64_t resolution) { + return (value + (resolution / 2)) / resolution; +} + +// ------------------------------------------------------------------------------------------------------------------- +// parse a size string + +bool size_parse(const char *size_str, uint64_t *result, const char *default_unit) { + if (!size_str || !*size_str) { + *result = 0; + return false; + } + + const struct size_unit *su_def = size_find_unit(default_unit); + if(!su_def) { + *result = 0; + return false; + } + + const char *s = size_str; + + // Skip leading spaces + while (isspace((uint8_t)*s)) s++; + + if(strcmp(s, "off") == 0) { + *result = 0; + return true; + } + + // Parse the number + const char *number_start = s; + NETDATA_DOUBLE value = strtondd(s, (char **)&s); + + // If no valid number found, return false + if (s == number_start || value < 0) { + *result = 0; + return false; + } + + // Skip spaces between number and unit + while (isspace((uint8_t)*s)) s++; + + const char *unit_start = s; + while (isalpha((uint8_t)*s)) s++; + + char unit[4]; + size_t unit_len = s - unit_start; + const struct size_unit *su; + if (unit_len == 0) + su = su_def; + else { + if (unit_len >= sizeof(unit)) unit_len = sizeof(unit) - 1; + strncpy(unit, unit_start, unit_len); + unit[unit_len] = '\0'; + su = size_find_unit(unit); + if (!su) { + *result = 0; + return false; + } + } + + uint64_t bytes = (uint64_t)round(value * (NETDATA_DOUBLE)su->multiplier); + *result = size_round_to_resolution_int(bytes, su_def->multiplier); + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------- +// generate a string to represent a size + +ssize_t size_snprintf(char *dst, size_t dst_size, uint64_t value, const char *unit, bool accurate) { + if (!dst || dst_size == 0) return -1; + if (dst_size == 1) { + dst[0] = '\0'; + return -2; + } + + if (value == 0) + return snprintfz(dst, dst_size, "off"); + + const struct size_unit *su_def = size_find_unit(unit); + if(!su_def) return -3; + + // use the units multiplier to find the units + uint64_t bytes = value * su_def->multiplier; + + // Find the best unit to represent the size with up to 2 fractional digits + const struct size_unit *su_best = su_def; + for (size_t i = 0; i < sizeof(size_units) / sizeof(size_units[0]); i++) { + const struct size_unit *su = &size_units[i]; + if (su->base != su_def->base || // not the right base + su->multiplier < su_def->multiplier || // the multiplier is too small + (!su->formatter && su != su_def) || // it is not to be used in formatting (except our unit) + (bytes < su->multiplier && su != su_def) ) // the converted value will be <1.0 + continue; + + double converted = size_round_to_resolution_dbl2(bytes, su->multiplier); + + uint64_t reversed_bytes = (uint64_t)(converted * (double)su->multiplier); + + if(accurate) { + // no precision loss is required + if (reversed_bytes == bytes) + // no precision loss, this is good to use + su_best = su; + } + else { + if(converted > 1.0) + su_best = su; + } + } + + double converted = size_round_to_resolution_dbl2(bytes, su_best->multiplier); + + // print it either with 0, 1 or 2 fractional digits + int written; + if(converted == (double)((uint64_t)converted)) + written = snprintfz(dst, dst_size, "%.0f%s", converted, su_best->unit); + else if(converted * 10.0 == (double)((uint64_t)(converted * 10.0))) + written = snprintfz(dst, dst_size, "%.1f%s", converted, su_best->unit); + else + written = snprintfz(dst, dst_size, "%.2f%s", converted, su_best->unit); + + if (written < 0) + return -4; + + if ((size_t)written >= dst_size) + return (ssize_t)(dst_size - 1); + + return written; +} + |