summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/parsers/entries.c
blob: d6ed31de16d447ffc5432bbf2bc7c42694931b3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// SPDX-License-Identifier: GPL-3.0-or-later

#include "entries.h"

// Define multipliers for base 10 (decimal) units
#define ENTRIES_MULTIPLIER_BASE10 1000ULL
#define ENTRIES_MULTIPLIER_K (ENTRIES_MULTIPLIER_BASE10)
#define ENTRIES_MULTIPLIER_M (ENTRIES_MULTIPLIER_K * ENTRIES_MULTIPLIER_BASE10)
#define ENTRIES_MULTIPLIER_G (ENTRIES_MULTIPLIER_M * ENTRIES_MULTIPLIER_BASE10)
#define ENTRIES_MULTIPLIER_T (ENTRIES_MULTIPLIER_G * ENTRIES_MULTIPLIER_BASE10)
#define ENTRIES_MULTIPLIER_P (ENTRIES_MULTIPLIER_T * ENTRIES_MULTIPLIER_BASE10)
#define ENTRIES_MULTIPLIER_E (ENTRIES_MULTIPLIER_P * ENTRIES_MULTIPLIER_BASE10)
#define ENTRIES_MULTIPLIER_Z (ENTRIES_MULTIPLIER_E * ENTRIES_MULTIPLIER_BASE10)
#define ENTRIES_MULTIPLIER_Y (ENTRIES_MULTIPLIER_Z * ENTRIES_MULTIPLIER_BASE10)

// Define a structure to map size units to their multipliers
static const struct size_unit {
    const char *unit;
    const bool formatter; // true when this unit should be used when formatting to string
    const uint64_t multiplier;
} entries_units[] = {
    // the order of this table is important: smaller to bigger units!

    { .unit = "",    .formatter = true,  .multiplier = 1ULL },
    { .unit = "k",   .formatter = false, .multiplier = ENTRIES_MULTIPLIER_K },
    { .unit = "K",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_K },
    { .unit = "M",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_M },
    { .unit = "G",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_G },
    { .unit = "T",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_T },
    { .unit = "P",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_P },
    { .unit = "E",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_E },
    { .unit = "Z",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_Z },
    { .unit = "Y",   .formatter = true,  .multiplier = ENTRIES_MULTIPLIER_Y },
};

static inline const struct size_unit *entries_find_unit(const char *unit) {
    if (!unit || !*unit) unit = "";

    for (size_t i = 0; i < sizeof(entries_units) / sizeof(entries_units[0]); i++) {
        const struct size_unit *su = &entries_units[i];
        if ((uint8_t)unit[0] == (uint8_t)su->unit[0] && strcmp(unit, su->unit) == 0)
            return su;
    }

    return NULL;
}

static inline double entries_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 entries_round_to_resolution_int(uint64_t value, uint64_t resolution) {
    return (value + (resolution / 2)) / resolution;
}

// -------------------------------------------------------------------------------------------------------------------
// parse a size string

bool entries_parse(const char *entries_str, uint64_t *result, const char *default_unit) {
    if (!entries_str || !*entries_str) {
        *result = 0;
        return false;
    }

    const struct size_unit *su_def = entries_find_unit(default_unit);
    if(!su_def) {
        *result = 0;
        return false;
    }

    const char *s = entries_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 = entries_find_unit(unit);
        if (!su) {
            *result = 0;
            return false;
        }
    }

    uint64_t bytes = (uint64_t)round(value * (NETDATA_DOUBLE)su->multiplier);
    *result = entries_round_to_resolution_int(bytes, su_def->multiplier);

    return true;
}

// --------------------------------------------------------------------------------------------------------------------
// generate a string to represent a size

ssize_t entries_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 = entries_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(entries_units) / sizeof(entries_units[0]); i++) {
        const struct size_unit *su = &entries_units[i];
        if (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 = entries_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 = entries_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;
}