summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/parsers
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnetdata/parsers')
-rw-r--r--src/libnetdata/parsers/duration.c252
-rw-r--r--src/libnetdata/parsers/duration.h32
-rw-r--r--src/libnetdata/parsers/duration.html205
-rw-r--r--src/libnetdata/parsers/durations.md94
-rw-r--r--src/libnetdata/parsers/entries.c183
-rw-r--r--src/libnetdata/parsers/entries.h19
-rw-r--r--src/libnetdata/parsers/parsers.h12
-rw-r--r--src/libnetdata/parsers/size.c212
-rw-r--r--src/libnetdata/parsers/size.h20
-rw-r--r--src/libnetdata/parsers/sizes.md52
-rw-r--r--src/libnetdata/parsers/timeframe.c128
-rw-r--r--src/libnetdata/parsers/timeframe.h28
12 files changed, 1237 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;
+}
diff --git a/src/libnetdata/parsers/duration.h b/src/libnetdata/parsers/duration.h
new file mode 100644
index 000000000..b95da5d2f
--- /dev/null
+++ b/src/libnetdata/parsers/duration.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef LIBNETDATA_PARSERS_DURATION_H
+#define LIBNETDATA_PARSERS_DURATION_H
+
+#include "parsers.h"
+
+int64_t duration_round_to_resolution(int64_t value, int64_t resolution);
+
+// duration (string to number)
+bool duration_parse(const char *duration, int64_t *result, const char *default_unit);
+#define duration_parse_nsec_t(duration, ns_ptr) duration_parse(duration, ns_ptr, "ns")
+#define duration_parse_usec_t(duration, us_ptr) duration_parse(duration, us_ptr, "us")
+#define duration_parse_msec_t(duration, ms_ptr) duration_parse(duration, ms_ptr, "ms")
+#define duration_parse_time_t(duration, secs_ptr) duration_parse(duration, secs_ptr, "s")
+#define duration_parse_mins(duration, mins_ptr) duration_parse(duration, mins_ptr, "m")
+#define duration_parse_hours(duration, hours_ptr) duration_parse(duration, hours_ptr, "h")
+#define duration_parse_days(duration, days_ptr) duration_parse(duration, days_ptr, "d")
+
+// duration (number to string)
+ssize_t duration_snprintf(char *dst, size_t dst_size, int64_t value, const char *unit, bool add_spaces);
+#define duration_snprintf_nsec_t(dst, dst_size, ns) duration_snprintf(dst, dst_size, ns, "ns", false)
+#define duration_snprintf_usec_t(dst, dst_size, us) duration_snprintf(dst, dst_size, us, "us", false)
+#define duration_snprintf_msec_t(dst, dst_size, ms) duration_snprintf(dst, dst_size, ms, "ms", false)
+#define duration_snprintf_time_t(dst, dst_size, secs) duration_snprintf(dst, dst_size, secs, "s", false)
+#define duration_snprintf_mins(dst, dst_size, mins) duration_snprintf(dst, dst_size, mins, "m", false)
+#define duration_snprintf_hours(dst, dst_size, hours) duration_snprintf(dst, dst_size, hours, "h", false)
+#define duration_snprintf_days(dst, dst_size, days) duration_snprintf(dst, dst_size, days, "d", false)
+
+bool duration_parse_seconds(const char *str, int *result);
+
+#endif //LIBNETDATA_PARSERS_DURATION_H
diff --git a/src/libnetdata/parsers/duration.html b/src/libnetdata/parsers/duration.html
new file mode 100644
index 000000000..8f6f8a416
--- /dev/null
+++ b/src/libnetdata/parsers/duration.html
@@ -0,0 +1,205 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Duration Converter</title>
+ <style>
+ table {
+ width: 50%;
+ border-collapse: collapse;
+ margin-top: 20px;
+ }
+ table, th, td {
+ border: 1px solid black;
+ }
+ th, td {
+ padding: 10px;
+ text-align: center;
+ }
+ .error {
+ color: red;
+ margin-top: 10px;
+ }
+ </style>
+</head>
+<body>
+<h1>Duration Converter</h1>
+<input type="text" id="durationInput" placeholder="Enter duration (e.g., 10d-12h)">
+<div id="errorMessage" class="error"></div>
+
+<table id="resultTable">
+ <thead>
+ <tr>
+ <th>Unit</th>
+ <th>Value</th>
+ <th>Formatted</th>
+ <th>Check</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+</table>
+
+<script>
+ const NSEC_PER_USEC = 1000;
+ const USEC_PER_MS = 1000;
+ const NSEC_PER_SEC = 1000000000;
+ const NSEC_PER_MS = USEC_PER_MS * NSEC_PER_USEC;
+ const NSEC_PER_MIN = NSEC_PER_SEC * 60;
+ const NSEC_PER_HOUR = NSEC_PER_MIN * 60;
+ const NSEC_PER_DAY = NSEC_PER_HOUR * 24;
+ const NSEC_PER_WEEK = NSEC_PER_DAY * 7;
+ const NSEC_PER_YEAR = NSEC_PER_DAY * 365;
+ const NSEC_PER_MONTH = NSEC_PER_DAY * 30;
+ const NSEC_PER_QUARTER = NSEC_PER_MONTH * 3;
+
+ const units = [
+ { unit: "ns", formatter: true, multiplier: 1 },
+ { unit: "us", formatter: true, multiplier: NSEC_PER_USEC },
+ { unit: "ms", formatter: true, multiplier: NSEC_PER_MS },
+ { unit: "s", formatter: true, multiplier: NSEC_PER_SEC },
+ { unit: "m", formatter: true, multiplier: NSEC_PER_MIN },
+ { unit: "min", formatter: false, multiplier: NSEC_PER_MIN },
+ { unit: "h", formatter: true, multiplier: NSEC_PER_HOUR },
+ { unit: "d", formatter: true, multiplier: NSEC_PER_DAY },
+ { unit: "w", formatter: false, multiplier: NSEC_PER_WEEK },
+ { unit: "wk", formatter: false, multiplier: NSEC_PER_WEEK },
+ { unit: "mo", formatter: true, multiplier: NSEC_PER_MONTH },
+ { unit: "M", formatter: false, multiplier: NSEC_PER_MONTH },
+ { unit: "q", formatter: false, multiplier: NSEC_PER_QUARTER },
+ { unit: "y", formatter: true, multiplier: NSEC_PER_YEAR },
+ { unit: "Y", formatter: false, multiplier: NSEC_PER_YEAR },
+ { unit: "a", formatter: false, multiplier: NSEC_PER_YEAR }
+ ];
+
+ function durationFindUnit(unit) {
+ if (!unit) return units[0];
+ return units.find(u => u.unit === unit) || null;
+ }
+
+ function roundToResolution(value, resolution) {
+ if (value > 0) return Math.floor((value + (resolution - 1) / 2) / resolution);
+ if (value < 0) return Math.ceil((value - (resolution - 1) / 2) / resolution);
+ return 0;
+ }
+
+ function parseDouble(str) {
+ str = str.trim();
+ const match = str.match(/^[-+]?\d*\.?\d+/);
+ if (match) {
+ const number = parseFloat(match[0]);
+ const remainingStr = str.slice(match[0].length).trim();
+ return { number, remainingStr };
+ }
+ return { number: null, remainingStr: str };
+ }
+
+ function durationParse(duration, unit) {
+ if (!duration || !unit) return false;
+
+ let s = duration.trim();
+ let nsec = 0;
+ let isNegative = false;
+
+ // Handle leading negative sign
+ if (s.startsWith("-")) {
+ isNegative = true;
+ s = s.slice(1).trim();
+ }
+
+ while (s.length > 0) {
+ s = s.trim();
+
+ if (s.startsWith("never") || s.startsWith("off"))
+ return 0;
+
+ const { number, remainingStr } = parseDouble(s);
+ if (number === null) return false;
+
+ s = remainingStr;
+
+ const match = s.match(/^([a-zA-Z]*)/);
+ let currentUnit = unit;
+ if (match && match[0].length > 0) {
+ currentUnit = match[0];
+ s = s.slice(match[0].length).trim();
+ }
+
+ const du = durationFindUnit(currentUnit);
+ if (!du) return false;
+
+ nsec += number * du.multiplier;
+ }
+
+ const unitMultiplier = durationFindUnit(unit).multiplier;
+ nsec = roundToResolution(nsec, unitMultiplier);
+
+ return isNegative ? -nsec : nsec;
+ }
+
+ function durationSnprintf(value, unit) {
+ if (value === 0) return "off";
+
+ const duMin = durationFindUnit(unit);
+ let nsec = Math.abs(value) * duMin.multiplier;
+
+ const isNegative = value < 0;
+ let result = isNegative ? "-" : "";
+
+ for (let i = units.length - 1; i >= 0 && nsec !== 0; i--) {
+ const du = units[i];
+ if (!du.formatter && du !== duMin) continue;
+
+ const multiplier = du.multiplier;
+ const rounded = (du === duMin) ? roundToResolution(nsec, multiplier) * multiplier : nsec;
+ let unitCount = Math.floor(rounded / multiplier);
+
+ if (unitCount !== 0) {
+ result += `${unitCount}${du.unit}`;
+ nsec -= unitCount * multiplier;
+ }
+
+ if (du === duMin) break;
+ }
+
+ return result || "off";
+ }
+
+ function updateTable() {
+ const duration = document.getElementById("durationInput").value;
+ const tableBody = document.getElementById("resultTable").querySelector("tbody");
+ const errorMessage = document.getElementById("errorMessage");
+ tableBody.innerHTML = "";
+ errorMessage.textContent = "";
+
+ units.forEach(unit => {
+ let value = durationParse(duration, unit.unit);
+ let formatted;
+ let check;
+ if(value === false) {
+ value = "-";
+ formatted = "";
+ check = "parsing error";
+ }
+ else {
+ formatted = durationSnprintf(value, unit.unit);
+ const parsedValue = durationParse(formatted, unit.unit);
+ check = (parsedValue === value) ? "ok" : `re-parsing error (${parsedValue})`;
+ }
+
+ const row = `<tr>
+ <td>${unit.unit}</td>
+ <td>${value}</td>
+ <td>${formatted}</td>
+ <td>${check}</td>
+ </tr>`;
+ tableBody.innerHTML += row;
+ });
+ }
+
+ document.getElementById("durationInput").addEventListener("input", updateTable);
+</script>
+</body>
+</html>
diff --git a/src/libnetdata/parsers/durations.md b/src/libnetdata/parsers/durations.md
new file mode 100644
index 000000000..e952faa1a
--- /dev/null
+++ b/src/libnetdata/parsers/durations.md
@@ -0,0 +1,94 @@
+## Durations in Netdata
+
+Netdata provides a flexible and powerful way to specify durations for various configurations and operations, such as alerts, database retention, and other configuration options. Durations can be expressed in a variety of units, ranging from nanoseconds to years, allowing users to define time intervals in a human-readable format.
+
+### Supported Duration Units
+
+Netdata supports a wide range of duration units. The system follows the Unified Code for Units of Measure (UCUM) standard where applicable. Below is a table of all the supported units, their corresponding representations, and their compatibility:
+
+| Symbol | Description | Value | Compatibility | Formatter |
+|:------:|:------------:|:--------:|:-------------:|:---------:|
+| `ns` | Nanoseconds | `1ns` | UCUM | **Yes** |
+| `us` | Microseconds | `1000ns` | UCUM | **Yes** |
+| `ms` | Milliseconds | `1000us` | UCUM | **Yes** |
+| `s` | Seconds | `1000ms` | UCUM | **Yes** |
+| `m` | Minutes | `60s` | Natural | **Yes** |
+| `min` | Minutes | `60s` | UCUM | No |
+| `h` | Hours | `60m` | UCUM | **Yes** |
+| `d` | Days | `24h` | UCUM | **Yes** |
+| `w` | Weeks | `7d` | Natural | No |
+| `wk` | Weeks | `7d` | UCUM | No |
+| `mo` | Months | `30d` | UCUM | **Yes** |
+| `M` | Months | `30d` | Backwards | No |
+| `q` | Quarters | `3mo` | Natural | No |
+| `y` | Years | `365d` | Natural | **Yes** |
+| `Y` | Years | `365d` | Backwards | No |
+| `a` | Years | `365d` | UCUM | No |
+
+- **UCUM**: The unit is specified in the Unified Code for Units of Measure (UCUM) standard.
+- **Natural**: We feel that this is more natural for expressing durations with single letter units.
+- **Backwards**: This unit has been used in the past in Netdata, and we support it for backwards compatibility.
+
+### Duration Expression Format
+
+Netdata allows users to express durations in both simple and complex formats.
+
+- **Simple Formats**: A duration can be specified using a number followed by a unit, such as `5m` (5 minutes), `2h` (2 hours), or `1d` (1 day). Fractional numbers are also supported, such as `1.5d`, `3.5mo` or `1.2y`.
+
+- **Complex Formats**: A duration can also be composed of multiple units added together. For example:
+ - `1y2mo3w4d` represents 1 year, 2 months, 3 weeks, and 4 days.
+ - `15d-12h` represents 15 days minus 12 hours (which equals 14 days and 12 hours).
+
+Each number given in durations can be either positive or negative. For example `1h15m` is 1 hour and 15 minutes, but `1h-15m` results to `45m`.
+
+The same unit can be given multiple times, so that `1d0.5d` is `1d12h` and `1d-0.5d` is `12h`.
+
+The order of units in the expressions is irrelevant, so that `1m2h3d` is the same to `3d2h1m`.
+
+The system will parse durations with spaces in them, but we suggest to write them down in compact form, without spaces. This is required, especially in alerts configuration, since spaces in durations will affect how parent expressions are tokenized.
+
+### Duration Rounding
+
+Netdata provides various functions to parse and round durations according to specific needs:
+
+- **Default Rounding to Seconds**: Most duration uses in Netdata are rounded to the nearest second. For example, a duration of `1.4s` would round to `1s`, while `1.5s` would round to `2s`.
+
+- **Rounding to Larger Units**: In some cases, such as database retention, durations are rounded to larger units like days. Even when rounding to a larger unit, durations can still be expressed in smaller units (e.g., `24h86400s` for `2d`).
+
+### Maximum and Minimum Duration Limits
+
+Netdata's duration expressions can handle durations ranging from the minimum possible value of `-INT64_MAX` to the maximum of `INT64_MAX` in nanoseconds. This range translates approximately to durations between -292 years to +292 years.
+
+### Inconsistencies in Duration Units
+
+While Netdata provides a flexible system for specifying durations, some inconsistencies arise due to the way different units are defined:
+
+- **1 Year (`y`) = 365 Days (`d`)**: In Netdata, a year is defined as 365 days. This is an approximation, since the average year is about 365.25 days.
+
+- **1 Month (`mo`) = 30 Days (`d`)**: Similarly, a month in Netdata is defined as 30 days, which is also an approximation. In reality, months vary in length (28 to 31 days).
+
+- **1 Quarter (`q`) = 3 Months (`mo`) = 90 Days (`d`)**: A quarter is defined as 3 months, or 90 days, which aligns with the approximation of each month being 30 days.
+
+These definitions can lead to some unexpected results when performing arithmetic with durations:
+
+**Example of Inconsistency**:
+
+`1y-1d` in Netdata calculates to `364d` but also as `12mo4d` because `1y = 365d` and `1mo = 30d`. This is inconsistent because `1y` is defined as `12mo5d` or `4q5d` (given the approximations above).
+
+### Negative Durations
+
+When the first letter of a duration expression is the minus character, Netdata parses the entire expression as positive and then it negates the result. for example: `-1m15s` is `-75s`, not `-45s`. To get `-45s` the expression should be `-1m-15s`. So the initial `-` is treated like `-(expression)`.
+
+The same rule is applied when generating duration expressions.
+
+### Example Duration Expressions
+
+Here are some examples of valid duration expressions:
+
+1. **`30s`**: 30 seconds.
+2. **`5m`**: 5 minutes.
+3. **`2h30m`**: 2 hours and 30 minutes.
+4. **`1.5d`**: 1 day and 12 hours.
+5. **`1w3d4h`**: 1 week, 3 days, and 4 hours.
+6. **`1y2mo3d`**: 1 year, 2 months, and 3 days.
+7. **`15d-12h`**: 14 days and 12 hours.
diff --git a/src/libnetdata/parsers/entries.c b/src/libnetdata/parsers/entries.c
new file mode 100644
index 000000000..d6ed31de1
--- /dev/null
+++ b/src/libnetdata/parsers/entries.c
@@ -0,0 +1,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;
+}
+
diff --git a/src/libnetdata/parsers/entries.h b/src/libnetdata/parsers/entries.h
new file mode 100644
index 000000000..a90b8f6f4
--- /dev/null
+++ b/src/libnetdata/parsers/entries.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef LIBNETDATA_PARSERS_ENTRIES_H
+#define LIBNETDATA_PARSERS_ENTRIES_H
+
+#include "parsers.h"
+
+bool entries_parse(const char *entries_str, uint64_t *result, const char *default_unit);
+#define entries_parse_k(size_str, kb) size_parse(size_str, kb, "K")
+#define entries_parse_m(size_str, mb) size_parse(size_str, mb, "M")
+#define entries_parse_g(size_str, gb) size_parse(size_str, gb, "G")
+
+ssize_t entries_snprintf(char *dst, size_t dst_size, uint64_t value, const char *unit, bool accurate);
+#define entries_snprintf_n(dst, dst_size, value) size_snprintf(dst, dst_size, value, "", true)
+#define entries_snprintf_k(dst, dst_size, value) size_snprintf(dst, dst_size, value, "K", true)
+#define entries_snprintf_m(dst, dst_size, value) size_snprintf(dst, dst_size, value, "M", true)
+#define entries_snprintf_g(dst, dst_size, value) size_snprintf(dst, dst_size, value, "G", true)
+
+#endif //LIBNETDATA_PARSERS_ENTRIES_H
diff --git a/src/libnetdata/parsers/parsers.h b/src/libnetdata/parsers/parsers.h
new file mode 100644
index 000000000..27b60b040
--- /dev/null
+++ b/src/libnetdata/parsers/parsers.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_PARSERS_H
+#define NETDATA_PARSERS_H
+
+#include "../libnetdata.h"
+#include "size.h"
+#include "entries.h"
+#include "duration.h"
+#include "timeframe.h"
+
+#endif //NETDATA_PARSERS_H
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;
+}
+
diff --git a/src/libnetdata/parsers/size.h b/src/libnetdata/parsers/size.h
new file mode 100644
index 000000000..6abfe7235
--- /dev/null
+++ b/src/libnetdata/parsers/size.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef LIBNETDATA_PARSERS_SIZE_H
+#define LIBNETDATA_PARSERS_SIZE_H
+
+#include "parsers.h"
+
+bool size_parse(const char *size_str, uint64_t *result, const char *default_unit);
+#define size_parse_bytes(size_str, bytes) size_parse(size_str, bytes, "B")
+#define size_parse_kb(size_str, kb) size_parse(size_str, kb, "KiB")
+#define size_parse_mb(size_str, mb) size_parse(size_str, mb, "MiB")
+#define size_parse_gb(size_str, gb) size_parse(size_str, gb, "GiB")
+
+ssize_t size_snprintf(char *dst, size_t dst_size, uint64_t value, const char *unit, bool accurate);
+#define size_snprintf_bytes(dst, dst_size, value) size_snprintf(dst, dst_size, value, "B", true)
+#define size_snprintf_kb(dst, dst_size, value) size_snprintf(dst, dst_size, value, "KiB", true)
+#define size_snprintf_mb(dst, dst_size, value) size_snprintf(dst, dst_size, value, "MiB", true)
+#define size_snprintf_gb(dst, dst_size, value) size_snprintf(dst, dst_size, value, "GiB", true)
+
+#endif //LIBNETDATA_PARSERS_SIZE_H
diff --git a/src/libnetdata/parsers/sizes.md b/src/libnetdata/parsers/sizes.md
new file mode 100644
index 000000000..ac9e09053
--- /dev/null
+++ b/src/libnetdata/parsers/sizes.md
@@ -0,0 +1,52 @@
+## Data Sizes in Netdata
+
+Netdata provides a flexible system for specifying and formatting data sizes, used in various configurations and operations such as disk space management, and memory usage. This system allows users to specify data sizes in a human-readable format using multiple units from bytes to terabytes, supporting both binary (base-2) and decimal (base-10) standards. All units are UCUM-based for consistency and clarity.
+
+### Supported Size Units
+
+The following table lists all supported units and their corresponding values:
+
+| Symbol | Description | Value | Base | Formatter |
+|:------:|:-----------:|:---------:|:-------:|:---------:|
+| `B` | Bytes | `1B` | - | **Yes** |
+| `k` | Kilobytes | `1000B` | Base-10 | No |
+| `K` | Kilobytes | `1000B` | Base-10 | No |
+| `KB` | Kilobytes | `1000B` | Base-10 | No |
+| `KiB` | Kibibytes | `1024B` | Base-2 | **Yes** |
+| `M` | Megabytes | `1000K` | Base-10 | No |
+| `MB` | Megabytes | `1000K` | Base-10 | No |
+| `MiB` | Mebibytes | `1024KiB` | Base-2 | **Yes** |
+| `G` | Gigabytes | `1000M` | Base-10 | No |
+| `GB` | Gigabytes | `1000M` | Base-10 | No |
+| `GiB` | Gibibytes | `1024MiB` | Base-2 | **Yes** |
+| `T` | Terabytes | `1000G` | Base-10 | No |
+| `TB` | Terabytes | `1000G` | Base-10 | No |
+| `TiB` | Tebibytes | `1024GiB` | Base-2 | **Yes** |
+| `P` | Petabytes | `1000T` | Base-10 | No |
+| `PB` | Petabytes | `1000T` | Base-10 | No |
+| `PiB` | Pebibytes | `1024TiB` | Base-2 | **Yes** |
+
+### Size Expression Format
+
+Netdata allows users to express sizes using a number followed by a unit, such as `500MiB` (500 Mebibytes), `1GB` (1 Gigabyte), or `256K` (256 Kilobytes).
+
+- **Case Sensitivity**: Note that the parsing of units is case-sensitive.
+
+### Size Formatting
+
+Netdata formats a numeric size value (in bytes) into a human-readable string with an appropriate unit. The formatter's goal is to select the largest unit that can represent the size exactly, using up to two fractional digits. If two fractional digits are not enough to precisely represent the byte count, the formatter will use a smaller unit until it can accurately express the size, eventually falling back to bytes (`B`) if necessary.
+
+When formatting, Netdata prefers Base-2 units (`KiB`, `MiB`, `GiB`, etc.).
+
+- **Examples of Size Formatting**:
+ - **10,485,760 bytes** is formatted as `10MiB` (10 Mebibytes).
+ - **1,024 bytes** is formatted as `1KiB` (1 Kibibyte).
+ - **1,500 bytes** remains formatted as `1500B` because it cannot be precisely represented in `KiB` or any larger unit using up to two fractional digits.
+
+### Example Size Expressions
+
+Here are some examples of valid size expressions:
+
+1. `1024B`: 1024 bytes.
+2. `1KiB`: 1024 bytes.
+3. `5MiB`: 5 mebibytes (5 * 1024 * 1024 bytes).
diff --git a/src/libnetdata/parsers/timeframe.c b/src/libnetdata/parsers/timeframe.c
new file mode 100644
index 000000000..33ea69750
--- /dev/null
+++ b/src/libnetdata/parsers/timeframe.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "timeframe.h"
+
+// --------------------------------------------------------------------------------------------------------------------
+// timeframe
+/*
+TIMEFRAME timeframe_parse(const char *txt) {
+ if(!txt || !*txt)
+ return TIMEFRAME_INVALID;
+
+char buf[strlen(txt) + 1];
+memcpy(buf, txt, strlen(txt) + 1);
+char *s = trim_all(buf);
+if(!s)
+ return TIMEFRAME_INVALID;
+
+while(isspace(*s)) s++;
+
+if(strcasecmp(s, "this minute") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_THIS_MINUTE,
+ .before = 0,
+ };
+}
+if(strcasecmp(s, "this hour") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_THIS_HOUR,
+ .before = 0,
+ };
+}
+if(strcasecmp(s, "today") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_TODAY,
+ .before = 0,
+ };
+}
+if(strcasecmp(s, "this week") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_THIS_WEEK,
+ .before = 0,
+ };
+}
+if(strcasecmp(s, "this month") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_THIS_MONTH,
+ .before = 0,
+ };
+}
+if(strcasecmp(s, "this year") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_THIS_YEAR,
+ .before = 0,
+ };
+}
+
+if(strcasecmp(s, "last minute") == 0) {
+ return (TIMEFRAME) {
+ .after = -60,
+ .before = API_RELATIVE_TIME_THIS_MINUTE,
+ };
+}
+if(strcasecmp(s, "last hour") == 0) {
+ return (TIMEFRAME) {
+ .after = -3600,
+ .before = API_RELATIVE_TIME_THIS_HOUR,
+ };
+}
+if(strcasecmp(s, "yesterday") == 0) {
+ return (TIMEFRAME) {
+ .after = -86400,
+ .before = API_RELATIVE_TIME_TODAY,
+ };
+}
+if(strcasecmp(s, "this week") == 0) {
+ return (TIMEFRAME) {
+ .after = -86400 * 7,
+ .before = API_RELATIVE_TIME_THIS_WEEK,
+ };
+}
+if(strcasecmp(s, "this month") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_LAST_MONTH,
+ .before = API_RELATIVE_TIME_THIS_MONTH,
+ };
+}
+if(strcasecmp(s, "this year") == 0) {
+ return (TIMEFRAME) {
+ .after = API_RELATIVE_TIME_LAST_YEAR,
+ .before = API_RELATIVE_TIME_THIS_YEAR,
+ };
+}
+
+const char *end;
+double after = strtondd(s, (char **)&end);
+
+if(end == s)
+ return TIMEFRAME_INVALID;
+
+s = end;
+while(isspace(*s)) s++;
+
+time_t multiplier = 1;
+if(!isdigit(*s) && *s != '-') {
+ // after has units
+ bool found = false;
+
+ for (size_t i = 0; i < sizeof(units) / sizeof(units[0]); i++) {
+ size_t len = strlen(units[i].unit);
+
+ if (units[i].multiplier >= 1 * NSEC_PER_USEC &&
+ strncmp(s, units[i].unit, len) == 0 &&
+ (isspace(s[len]) || s[len] == '-')) {
+ multiplier = units[i].multiplier / NSEC_PER_SEC;
+ found = true;
+ s += len;
+ }
+ }
+
+ if(!found)
+ return TIMEFRAME_INVALID;
+}
+
+const char *dash = strchr(s, '-');
+if(!dash) return TIMEFRAME_INVALID;
+
+}
+*/
diff --git a/src/libnetdata/parsers/timeframe.h b/src/libnetdata/parsers/timeframe.h
new file mode 100644
index 000000000..a176dd30a
--- /dev/null
+++ b/src/libnetdata/parsers/timeframe.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_TIMEFRAME_H
+#define NETDATA_TIMEFRAME_H
+
+#include "parsers.h"
+
+typedef struct {
+ time_t after;
+ time_t before;
+} TIMEFRAME;
+
+#define API_RELATIVE_TIME_MAX (3 * 365 * 86400)
+
+#define API_RELATIVE_TIME_INVALID (-1000000000)
+
+#define API_RELATIVE_TIME_THIS_MINUTE (API_RELATIVE_TIME_INVALID - 1) // this minute at 00 seconds
+#define API_RELATIVE_TIME_THIS_HOUR (API_RELATIVE_TIME_INVALID - 2) // this hour at 00 minutes, 00 seconds
+#define API_RELATIVE_TIME_TODAY (API_RELATIVE_TIME_INVALID - 3) // today at 00:00:00
+#define API_RELATIVE_TIME_THIS_WEEK (API_RELATIVE_TIME_INVALID - 4) // this Monday, 00:00:00
+#define API_RELATIVE_TIME_THIS_MONTH (API_RELATIVE_TIME_INVALID - 5) // this month's 1st at 00:00:00
+#define API_RELATIVE_TIME_THIS_YEAR (API_RELATIVE_TIME_INVALID - 6) // this year's Jan 1st, at 00:00:00
+#define API_RELATIVE_TIME_LAST_MONTH (API_RELATIVE_TIME_INVALID - 7) // last month's 1st, at 00:00:00
+#define API_RELATIVE_TIME_LAST_YEAR (API_RELATIVE_TIME_INVALID - 8) // last year's Jan 1st, at 00:00:00
+
+#define TIMEFRAME_INVALID (TIMEFRAME){ .after = API_RELATIVE_TIME_INVALID, .before = API_RELATIVE_TIME_INVALID }
+
+#endif //NETDATA_TIMEFRAME_H