summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/parsers/duration.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnetdata/parsers/duration.c')
-rw-r--r--src/libnetdata/parsers/duration.c252
1 files changed, 252 insertions, 0 deletions
diff --git a/src/libnetdata/parsers/duration.c b/src/libnetdata/parsers/duration.c
new file mode 100644
index 000000000..16dc5170c
--- /dev/null
+++ b/src/libnetdata/parsers/duration.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "duration.h"
+
+#ifdef NSEC_PER_USEC
+#undef NSEC_PER_USEC
+#endif
+#define NSEC_PER_USEC (1000ULL)
+
+#ifdef USEC_PER_MS
+#undef USEC_PER_MS
+#endif
+#define USEC_PER_MS (1000ULL)
+
+#ifdef NSEC_PER_SEC
+#undef NSEC_PER_SEC
+#endif
+#define NSEC_PER_SEC (1000000000ULL)
+
+#define NSEC_PER_MS (USEC_PER_MS * NSEC_PER_USEC)
+#define NSEC_PER_MIN (NSEC_PER_SEC * 60ULL)
+#define NSEC_PER_HOUR (NSEC_PER_MIN * 60ULL)
+#define NSEC_PER_DAY (NSEC_PER_HOUR * 24ULL)
+#define NSEC_PER_WEEK (NSEC_PER_DAY * 7ULL)
+#define NSEC_PER_MONTH (NSEC_PER_DAY * 30ULL)
+#define NSEC_PER_QUARTER (NSEC_PER_MONTH * 3ULL)
+
+// more accurate, but not an integer multiple of days, weeks, months
+#define NSEC_PER_YEAR (NSEC_PER_DAY * 365ULL)
+
+// Define a structure to map time units to their multipliers
+static const struct duration_unit {
+ const char *unit;
+ const bool formatter; // true when this unit should be used when formatting to string
+ const snsec_t multiplier;
+} units[] = {
+
+ // IMPORTANT: the order of this array is crucial!
+ // The array should be sorted from the smaller unit to the biggest unit.
+
+ { .unit = "ns", .formatter = true, .multiplier = 1 }, // UCUM
+ { .unit = "us", .formatter = true, .multiplier = NSEC_PER_USEC }, // UCUM
+ { .unit = "ms", .formatter = true, .multiplier = NSEC_PER_MS }, // UCUM
+ { .unit = "s", .formatter = true, .multiplier = NSEC_PER_SEC }, // UCUM
+ { .unit = "m", .formatter = true, .multiplier = NSEC_PER_MIN }, // -
+ { .unit = "min", .formatter = false, .multiplier = NSEC_PER_MIN }, // UCUM
+ { .unit = "h", .formatter = true, .multiplier = NSEC_PER_HOUR }, // UCUM
+ { .unit = "d", .formatter = true, .multiplier = NSEC_PER_DAY }, // UCUM
+ { .unit = "w", .formatter = false, .multiplier = NSEC_PER_WEEK }, // -
+ { .unit = "wk", .formatter = false, .multiplier = NSEC_PER_WEEK }, // UCUM
+ { .unit = "mo", .formatter = true, .multiplier = NSEC_PER_MONTH }, // UCUM
+ { .unit = "M", .formatter = false, .multiplier = NSEC_PER_MONTH }, // compatibility
+ { .unit = "q", .formatter = false, .multiplier = NSEC_PER_QUARTER }, // -
+ { .unit = "y", .formatter = true, .multiplier = NSEC_PER_YEAR }, // -
+ { .unit = "Y", .formatter = false, .multiplier = NSEC_PER_YEAR }, // compatibility
+ { .unit = "a", .formatter = false, .multiplier = NSEC_PER_YEAR }, // UCUM
+};
+
+static inline const struct duration_unit *duration_find_unit(const char *unit) {
+ if(!unit || !*unit)
+ unit = "ns";
+
+ for (size_t i = 0; i < sizeof(units) / sizeof(units[0]); i++) {
+ const struct duration_unit *du = &units[i];
+ if ((uint8_t)unit[0] == (uint8_t)du->unit[0] && strcmp(unit, du->unit) == 0)
+ return du;
+ }
+
+ return NULL;
+}
+
+inline int64_t duration_round_to_resolution(int64_t value, int64_t resolution) {
+ if(value > 0)
+ return (value + ((resolution - 1) / 2)) / resolution;
+
+ if(value < 0)
+ return (value - ((resolution - 1) / 2)) / resolution;
+
+ return 0;
+}
+
+// -------------------------------------------------------------------------------------------------------------------
+// parse a duration string
+
+bool duration_parse(const char *duration, int64_t *result, const char *default_unit) {
+ if (!duration || !*duration) {
+ *result = 0;
+ return false;
+ }
+
+ const struct duration_unit *du_def = duration_find_unit(default_unit);
+ if(!du_def) {
+ *result = 0;
+ return false;
+ }
+
+ int64_t sign = 1;
+ const char *s = duration;
+ while (isspace((uint8_t)*s)) s++;
+ if(*s == '-') {
+ s++;
+ sign = -1;
+ }
+
+ int64_t v = 0;
+
+ while (*s) {
+ // Skip leading spaces
+ while (isspace((uint8_t)*s)) s++;
+
+ // compatibility
+ if(*s == 'n' && strcmp(s, "never") == 0) {
+ *result = 0;
+ return true;
+ }
+
+ if(*s == 'o' && strcmp(s, "off") == 0) {
+ *result = 0;
+ return true;
+ }
+
+ // Parse the number
+ const char *number_start = s;
+ NETDATA_DOUBLE value = str2ndd(s, (char **)&s);
+
+ // If no valid number found, return default
+ if (s == number_start) {
+ *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 duration_unit *du;
+ if (unit_len == 0)
+ du = du_def;
+ else {
+ if (unit_len >= sizeof(unit)) unit_len = sizeof(unit) - 1;
+ strncpyz(unit, unit_start, unit_len);
+ du = duration_find_unit(unit);
+ if(!du) {
+ *result = 0;
+ return false;
+ }
+ }
+
+ v += (int64_t)round(value * (NETDATA_DOUBLE)du->multiplier);
+ }
+
+ v *= sign;
+
+ if(du_def->multiplier == 1)
+ *result = v;
+ else
+ *result = duration_round_to_resolution(v, du_def->multiplier);
+
+ return true;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// generate a string to represent a duration
+
+ssize_t duration_snprintf(char *dst, size_t dst_size, int64_t value, const char *unit, bool add_spaces) {
+ 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 char *sign = "";
+ if(value < 0) {
+ sign = "-";
+ value = -value;
+ }
+
+ const struct duration_unit *du_min = duration_find_unit(unit);
+ size_t offset = 0;
+
+ int64_t nsec = value * du_min->multiplier;
+
+ // Iterate through units from largest to smallest
+ for (size_t i = sizeof(units) / sizeof(units[0]) - 1; i > 0 && nsec > 0; i--) {
+ const struct duration_unit *du = &units[i];
+ if(!units[i].formatter && du != du_min)
+ continue;
+
+ // IMPORTANT:
+ // The week (7 days) is not aligned to the quarter (~91 days) or the year (365.25 days).
+ // To make sure that the value returned can be parsed back without loss,
+ // we have to round the value per unit (inside this loop), not globally.
+ // Otherwise, we have to make sure that all larger units are integer multiples of the smaller ones.
+
+ int64_t multiplier = units[i].multiplier;
+ int64_t rounded = (du == du_min) ? (duration_round_to_resolution(nsec, multiplier) * multiplier) : nsec;
+
+ int64_t unit_count = rounded / multiplier;
+ if (unit_count > 0) {
+ const char *space = (add_spaces && offset) ? " " : "";
+ int written = snprintfz(dst + offset, dst_size - offset,
+ "%s%s%" PRIi64 "%s", space, sign, unit_count, units[i].unit);
+
+ if (written < 0)
+ return -3;
+
+ sign = "";
+ offset += written;
+
+ if (offset >= dst_size) {
+ // buffer overflow
+ return (ssize_t)offset;
+ }
+
+ if(unit_count * multiplier >= nsec)
+ break;
+ else
+ nsec -= unit_count * multiplier;
+ }
+
+ if(du == du_min)
+ // we should not go to smaller units
+ break;
+ }
+
+ if (offset == 0)
+ // nothing has been written
+ offset = snprintfz(dst, dst_size, "off");
+
+ return (ssize_t)offset;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// compatibility for parsing seconds in int.
+
+bool duration_parse_seconds(const char *str, int *result) {
+ int64_t v;
+
+ if(duration_parse_time_t(str, &v)) {
+ *result = (int)v;
+ return true;
+ }
+
+ return false;
+}