summaryrefslogtreecommitdiffstats
path: root/libnetdata/buffer
diff options
context:
space:
mode:
Diffstat (limited to 'libnetdata/buffer')
-rw-r--r--libnetdata/buffer/Makefile.am8
-rw-r--r--libnetdata/buffer/README.md20
-rw-r--r--libnetdata/buffer/buffer.c503
-rw-r--r--libnetdata/buffer/buffer.h1228
4 files changed, 1759 insertions, 0 deletions
diff --git a/libnetdata/buffer/Makefile.am b/libnetdata/buffer/Makefile.am
new file mode 100644
index 00000000..161784b8
--- /dev/null
+++ b/libnetdata/buffer/Makefile.am
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+dist_noinst_DATA = \
+ README.md \
+ $(NULL)
diff --git a/libnetdata/buffer/README.md b/libnetdata/buffer/README.md
new file mode 100644
index 00000000..2937ae14
--- /dev/null
+++ b/libnetdata/buffer/README.md
@@ -0,0 +1,20 @@
+<!--
+title: "BUFFER"
+custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/buffer/README.md
+sidebar_label: "BUFFER library"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata"
+-->
+
+# BUFFER
+
+`BUFFER` is a convenience library for working with strings in `C`.
+Mainly, `BUFFER`s eliminate the need for tracking the string length, thus providing
+a safe alternative for string operations.
+
+Also, they are super fast in printing and appending data to the string and its `buffer_strlen()`
+is just a lookup (it does not traverse the string).
+
+Netdata uses `BUFFER`s for preparing web responses and buffering data to be sent upstream or
+to external databases.
diff --git a/libnetdata/buffer/buffer.c b/libnetdata/buffer/buffer.c
new file mode 100644
index 00000000..64f9cce4
--- /dev/null
+++ b/libnetdata/buffer/buffer.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "../libnetdata.h"
+
+static inline void buffer_overflow_init(BUFFER *b)
+{
+ b->buffer[b->size] = '\0';
+ strcpy(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF);
+}
+
+void buffer_reset(BUFFER *wb) {
+ buffer_flush(wb);
+
+ wb->content_type = CT_TEXT_PLAIN;
+ wb->options = 0;
+ wb->date = 0;
+ wb->expires = 0;
+ buffer_no_cacheable(wb);
+
+ buffer_overflow_check(wb);
+}
+
+void buffer_char_replace(BUFFER *wb, char from, char to) {
+ char *s = wb->buffer, *end = &wb->buffer[wb->len];
+
+ while(s != end) {
+ if(*s == from) *s = to;
+ s++;
+ }
+
+ buffer_overflow_check(wb);
+}
+
+void buffer_print_sn_flags(BUFFER *wb, SN_FLAGS flags, bool send_anomaly_bit) {
+ if(unlikely(flags == SN_EMPTY_SLOT)) {
+ buffer_fast_strcat(wb, "E", 1);
+ return;
+ }
+
+ size_t printed = 0;
+ if(likely(send_anomaly_bit && (flags & SN_FLAG_NOT_ANOMALOUS))) {
+ buffer_fast_strcat(wb, "A", 1);
+ printed++;
+ }
+
+ if(unlikely(flags & SN_FLAG_RESET)) {
+ buffer_fast_strcat(wb, "R", 1);
+ printed++;
+ }
+
+ if(!printed)
+ buffer_fast_strcat(wb, "''", 2);
+}
+
+void buffer_strcat_htmlescape(BUFFER *wb, const char *txt)
+{
+ while(*txt) {
+ switch(*txt) {
+ case '&': buffer_strcat(wb, "&amp;"); break;
+ case '<': buffer_strcat(wb, "&lt;"); break;
+ case '>': buffer_strcat(wb, "&gt;"); break;
+ case '"': buffer_strcat(wb, "&quot;"); break;
+ case '/': buffer_strcat(wb, "&#x2F;"); break;
+ case '\'': buffer_strcat(wb, "&#x27;"); break;
+ default: {
+ buffer_need_bytes(wb, 1);
+ wb->buffer[wb->len++] = *txt;
+ }
+ }
+ txt++;
+ }
+
+ buffer_overflow_check(wb);
+}
+
+void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...)
+{
+ if(unlikely(!fmt || !*fmt)) return;
+
+ buffer_need_bytes(wb, len + 1);
+
+ va_list args;
+ va_start(args, fmt);
+ // vsnprintfz() returns the number of bytes actually written - after possible truncation
+ wb->len += vsnprintfz(&wb->buffer[wb->len], len, fmt, args);
+ va_end(args);
+
+ buffer_overflow_check(wb);
+
+ // the buffer is \0 terminated by vsnprintfz
+}
+
+inline void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args) {
+ if(unlikely(!fmt || !*fmt)) return;
+
+ size_t full_size_bytes = 0, need = 2, space_remaining = 0;
+
+ do {
+ need += full_size_bytes + 2;
+
+ buffer_need_bytes(wb, need);
+
+ space_remaining = wb->size - wb->len - 1;
+
+ // Use the copy of va_list for vsnprintf
+ va_list args_copy;
+ va_copy(args_copy, args);
+ // vsnprintf() returns the number of bytes required, even if bigger than the buffer provided
+ full_size_bytes = (size_t) vsnprintf(&wb->buffer[wb->len], space_remaining, fmt, args_copy);
+ va_end(args_copy);
+
+ } while(full_size_bytes >= space_remaining);
+
+ wb->len += full_size_bytes;
+
+ wb->buffer[wb->len] = '\0';
+ buffer_overflow_check(wb);
+}
+
+void buffer_sprintf(BUFFER *wb, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ buffer_vsprintf(wb, fmt, args);
+ va_end(args);
+}
+
+// generate a javascript date, the fastest possible way...
+void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds)
+{
+ // 10 20 30 = 35
+ // 01234567890123456789012345678901234
+ // Date(2014,04,01,03,28,20)
+
+ buffer_need_bytes(wb, 30);
+
+ char *b = &wb->buffer[wb->len], *p;
+ unsigned int *q = (unsigned int *)b;
+
+ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ *q++ = 0x65746144; // "Date" backwards.
+ #else
+ *q++ = 0x44617465; // "Date"
+ #endif
+ p = (char *)q;
+
+ *p++ = '(';
+ *p++ = '0' + year / 1000; year %= 1000;
+ *p++ = '0' + year / 100; year %= 100;
+ *p++ = '0' + year / 10;
+ *p++ = '0' + year % 10;
+ *p++ = ',';
+ *p = '0' + month / 10; if (*p != '0') p++;
+ *p++ = '0' + month % 10;
+ *p++ = ',';
+ *p = '0' + day / 10; if (*p != '0') p++;
+ *p++ = '0' + day % 10;
+ *p++ = ',';
+ *p = '0' + hours / 10; if (*p != '0') p++;
+ *p++ = '0' + hours % 10;
+ *p++ = ',';
+ *p = '0' + minutes / 10; if (*p != '0') p++;
+ *p++ = '0' + minutes % 10;
+ *p++ = ',';
+ *p = '0' + seconds / 10; if (*p != '0') p++;
+ *p++ = '0' + seconds % 10;
+
+ unsigned short *r = (unsigned short *)p;
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ *r++ = 0x0029; // ")\0" backwards.
+ #else
+ *r++ = 0x2900; // ")\0"
+ #endif
+
+ wb->len += (size_t)((char *)r - b - 1);
+
+ // terminate it
+ wb->buffer[wb->len] = '\0';
+ buffer_overflow_check(wb);
+}
+
+// generate a date, the fastest possible way...
+void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds)
+{
+ // 10 20 30 = 35
+ // 01234567890123456789012345678901234
+ // 2014-04-01 03:28:20
+
+ buffer_need_bytes(wb, 36);
+
+ char *b = &wb->buffer[wb->len];
+ char *p = b;
+
+ *p++ = '0' + year / 1000; year %= 1000;
+ *p++ = '0' + year / 100; year %= 100;
+ *p++ = '0' + year / 10;
+ *p++ = '0' + year % 10;
+ *p++ = '-';
+ *p++ = '0' + month / 10;
+ *p++ = '0' + month % 10;
+ *p++ = '-';
+ *p++ = '0' + day / 10;
+ *p++ = '0' + day % 10;
+ *p++ = ' ';
+ *p++ = '0' + hours / 10;
+ *p++ = '0' + hours % 10;
+ *p++ = ':';
+ *p++ = '0' + minutes / 10;
+ *p++ = '0' + minutes % 10;
+ *p++ = ':';
+ *p++ = '0' + seconds / 10;
+ *p++ = '0' + seconds % 10;
+ *p = '\0';
+
+ wb->len += (size_t)(p - b);
+
+ // terminate it
+ wb->buffer[wb->len] = '\0';
+ buffer_overflow_check(wb);
+}
+
+BUFFER *buffer_create(size_t size, size_t *statistics)
+{
+ BUFFER *b;
+
+ netdata_log_debug(D_WEB_BUFFER, "Creating new web buffer of size %zu.", size);
+
+ b = callocz(1, sizeof(BUFFER));
+ b->buffer = mallocz(size + sizeof(BUFFER_OVERFLOW_EOF) + 2);
+ b->buffer[0] = '\0';
+ b->size = size;
+ b->content_type = CT_TEXT_PLAIN;
+ b->statistics = statistics;
+ buffer_no_cacheable(b);
+ buffer_overflow_init(b);
+ buffer_overflow_check(b);
+
+ if(b->statistics)
+ __atomic_add_fetch(b->statistics, b->size + sizeof(BUFFER) + sizeof(BUFFER_OVERFLOW_EOF) + 2, __ATOMIC_RELAXED);
+
+ return(b);
+}
+
+void buffer_free(BUFFER *b) {
+ if(unlikely(!b)) return;
+
+ buffer_overflow_check(b);
+
+ netdata_log_debug(D_WEB_BUFFER, "Freeing web buffer of size %zu.", b->size);
+
+ if(b->statistics)
+ __atomic_sub_fetch(b->statistics, b->size + sizeof(BUFFER) + sizeof(BUFFER_OVERFLOW_EOF) + 2, __ATOMIC_RELAXED);
+
+ freez(b->buffer);
+ freez(b);
+}
+
+void buffer_increase(BUFFER *b, size_t free_size_required) {
+ buffer_overflow_check(b);
+
+ size_t left = b->size - b->len;
+ if(left >= free_size_required) return;
+
+ size_t wanted = free_size_required - left;
+ size_t minimum = WEB_DATA_LENGTH_INCREASE_STEP;
+ if(minimum > wanted) wanted = minimum;
+
+ size_t optimal = (b->size > 5*1024*1024) ? b->size / 2 : b->size;
+ if(optimal > wanted) wanted = optimal;
+
+ netdata_log_debug(D_WEB_BUFFER, "Increasing data buffer from size %zu to %zu.", b->size, b->size + wanted);
+
+ b->buffer = reallocz(b->buffer, b->size + wanted + sizeof(BUFFER_OVERFLOW_EOF) + 2);
+ b->size += wanted;
+
+ if(b->statistics)
+ __atomic_add_fetch(b->statistics, wanted, __ATOMIC_RELAXED);
+
+ buffer_overflow_init(b);
+ buffer_overflow_check(b);
+}
+
+// ----------------------------------------------------------------------------
+
+void buffer_json_initialize(BUFFER *wb, const char *key_quote, const char *value_quote, int depth,
+ bool add_anonymous_object, BUFFER_JSON_OPTIONS options) {
+ strncpyz(wb->json.key_quote, key_quote, BUFFER_QUOTE_MAX_SIZE);
+ strncpyz(wb->json.value_quote, value_quote, BUFFER_QUOTE_MAX_SIZE);
+
+ wb->json.depth = (int8_t)(depth - 1);
+ _buffer_json_depth_push(wb, BUFFER_JSON_OBJECT);
+
+ if(add_anonymous_object)
+ buffer_fast_strcat(wb, "{", 1);
+ else
+ options |= BUFFER_JSON_OPTIONS_NON_ANONYMOUS;
+
+ wb->json.options = options;
+
+ wb->content_type = CT_APPLICATION_JSON;
+ buffer_no_cacheable(wb);
+}
+
+void buffer_json_finalize(BUFFER *wb) {
+ while(wb->json.depth >= 0) {
+ switch(wb->json.stack[wb->json.depth].type) {
+ case BUFFER_JSON_OBJECT:
+ if (wb->json.depth == 0)
+ if (!(wb->json.options & BUFFER_JSON_OPTIONS_NON_ANONYMOUS))
+ buffer_json_object_close(wb);
+ else
+ _buffer_json_depth_pop(wb);
+ else
+ buffer_json_object_close(wb);
+ break;
+ case BUFFER_JSON_ARRAY:
+ buffer_json_array_close(wb);
+ break;
+
+ default:
+ internal_fatal(true, "BUFFER: unknown json member type in stack");
+ break;
+ }
+ }
+
+ if(!(wb->json.options & BUFFER_JSON_OPTIONS_MINIFY))
+ buffer_fast_strcat(wb, "\n", 1);
+}
+
+// ----------------------------------------------------------------------------
+
+const char hex_digits[16] = "0123456789ABCDEF";
+const char base64_digits[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+unsigned char hex_value_from_ascii[256];
+unsigned char base64_value_from_ascii[256];
+
+__attribute__((constructor)) void initialize_ascii_maps(void) {
+ for(size_t i = 0 ; i < 256 ; i++) {
+ hex_value_from_ascii[i] = 255;
+ base64_value_from_ascii[i] = 255;
+ }
+
+ for(size_t i = 0; i < 16 ; i++)
+ hex_value_from_ascii[(int)hex_digits[i]] = i;
+
+ for(size_t i = 0; i < 64 ; i++)
+ base64_value_from_ascii[(int)base64_digits[i]] = i;
+}
+
+// ----------------------------------------------------------------------------
+// unit test
+
+static int buffer_expect(BUFFER *wb, const char *expected) {
+ const char *generated = buffer_tostring(wb);
+
+ if(strcmp(generated, expected) != 0) {
+ netdata_log_error("BUFFER: mismatch.\nGenerated:\n%s\nExpected:\n%s\n",
+ generated, expected);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int buffer_uint64_roundtrip(BUFFER *wb, NUMBER_ENCODING encoding, uint64_t value, const char *expected) {
+ int errors = 0;
+ buffer_flush(wb);
+ buffer_print_uint64_encoded(wb, encoding, value);
+
+ if(expected)
+ errors += buffer_expect(wb, expected);
+
+ uint64_t v = str2ull_encoded(buffer_tostring(wb));
+ if(v != value) {
+ netdata_log_error("BUFFER: string '%s' does resolves to %llu, expected %llu",
+ buffer_tostring(wb), (unsigned long long)v, (unsigned long long)value);
+ errors++;
+ }
+ buffer_flush(wb);
+ return errors;
+}
+
+static int buffer_int64_roundtrip(BUFFER *wb, NUMBER_ENCODING encoding, int64_t value, const char *expected) {
+ int errors = 0;
+ buffer_flush(wb);
+ buffer_print_int64_encoded(wb, encoding, value);
+
+ if(expected)
+ errors += buffer_expect(wb, expected);
+
+ int64_t v = str2ll_encoded(buffer_tostring(wb));
+ if(v != value) {
+ netdata_log_error("BUFFER: string '%s' does resolves to %lld, expected %lld",
+ buffer_tostring(wb), (long long)v, (long long)value);
+ errors++;
+ }
+ buffer_flush(wb);
+ return errors;
+}
+
+static int buffer_double_roundtrip(BUFFER *wb, NUMBER_ENCODING encoding, NETDATA_DOUBLE value, const char *expected) {
+ int errors = 0;
+ buffer_flush(wb);
+ buffer_print_netdata_double_encoded(wb, encoding, value);
+
+ if(expected)
+ errors += buffer_expect(wb, expected);
+
+ NETDATA_DOUBLE v = str2ndd_encoded(buffer_tostring(wb), NULL);
+ if(v != value) {
+ netdata_log_error("BUFFER: string '%s' does resolves to %.12f, expected %.12f",
+ buffer_tostring(wb), v, value);
+ errors++;
+ }
+ buffer_flush(wb);
+ return errors;
+}
+
+int buffer_unittest(void) {
+ int errors = 0;
+ BUFFER *wb = buffer_create(0, NULL);
+
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 0, "0");
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_HEX, 0, "0x0");
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_BASE64, 0, "#A");
+
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 1676071986, "1676071986");
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_HEX, 1676071986, "0x63E6D432");
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_BASE64, 1676071986, "#Bj5tQy");
+
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 18446744073709551615ULL, "18446744073709551615");
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_HEX, 18446744073709551615ULL, "0xFFFFFFFFFFFFFFFF");
+ buffer_uint64_roundtrip(wb, NUMBER_ENCODING_BASE64, 18446744073709551615ULL, "#P//////////");
+
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 0, "0");
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_HEX, 0, "0x0");
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_BASE64, 0, "#A");
+
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, -1676071986, "-1676071986");
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_HEX, -1676071986, "-0x63E6D432");
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_BASE64, -1676071986, "-#Bj5tQy");
+
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, (int64_t)-9223372036854775807ULL, "-9223372036854775807");
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_HEX, (int64_t)-9223372036854775807ULL, "-0x7FFFFFFFFFFFFFFF");
+ buffer_int64_roundtrip(wb, NUMBER_ENCODING_BASE64, (int64_t)-9223372036854775807ULL, "-#H//////////");
+
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 0, "0");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 0, "%0");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 0, "@A");
+
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 1.5, "1.5");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 1.5, "%3FF8000000000000");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 1.5, "@D/4AAAAAAAA");
+
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 1.23e+14, "123000000000000");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 1.23e+14, "%42DBF78AD3AC0000");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 1.23e+14, "@ELb94rTrAAA");
+
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 9.12345678901234567890123456789e+45, "9.123456789012346128e+45");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 9.12345678901234567890123456789e+45, "%497991C25C9E4309");
+ buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 9.12345678901234567890123456789e+45, "@El5kcJcnkMJ");
+
+ buffer_flush(wb);
+
+ {
+ char buf[1024 + 1];
+ for(size_t i = 0; i < 1024 ;i++)
+ buf[i] = (char)(i % 26) + 'A';
+ buf[1024] = '\0';
+
+ buffer_strcat(wb, buf);
+ errors += buffer_expect(wb, buf);
+ }
+
+ buffer_flush(wb);
+
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+ buffer_json_finalize(wb);
+ errors += buffer_expect(wb, "{\n}\n");
+
+ buffer_flush(wb);
+
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+ buffer_json_member_add_string(wb, "hello", "world");
+ buffer_json_member_add_string(wb, "alpha", "this: \" is a double quote");
+ buffer_json_member_add_object(wb, "object1");
+ buffer_json_member_add_string(wb, "hello", "world");
+ buffer_json_finalize(wb);
+ errors += buffer_expect(wb, "{\n \"hello\":\"world\",\n \"alpha\":\"this: \\\" is a double quote\",\n \"object1\":{\n \"hello\":\"world\"\n }\n}\n");
+
+ buffer_free(wb);
+ return errors;
+}
+
+#ifdef ENABLE_H2O
+h2o_iovec_t buffer_to_h2o_iovec(BUFFER *wb) {
+ h2o_iovec_t ret;
+ ret.base = wb->buffer;
+ ret.len = wb->len;
+ return ret;
+}
+#endif
diff --git a/libnetdata/buffer/buffer.h b/libnetdata/buffer/buffer.h
new file mode 100644
index 00000000..88d3f028
--- /dev/null
+++ b/libnetdata/buffer/buffer.h
@@ -0,0 +1,1228 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WEB_BUFFER_H
+#define NETDATA_WEB_BUFFER_H 1
+
+#include "../string/utf8.h"
+#include "../libnetdata.h"
+
+#ifdef ENABLE_H2O
+#include "h2o/memory.h"
+#endif
+
+#define WEB_DATA_LENGTH_INCREASE_STEP 1024
+
+#define BUFFER_JSON_MAX_DEPTH 32 // max is 255
+
+extern const char hex_digits[16];
+extern const char base64_digits[64];
+extern unsigned char hex_value_from_ascii[256];
+extern unsigned char base64_value_from_ascii[256];
+
+typedef enum __attribute__ ((__packed__)) {
+ BUFFER_JSON_EMPTY = 0,
+ BUFFER_JSON_OBJECT,
+ BUFFER_JSON_ARRAY,
+} BUFFER_JSON_NODE_TYPE;
+
+typedef struct web_buffer_json_node {
+ BUFFER_JSON_NODE_TYPE type;
+ uint32_t count:24;
+} BUFFER_JSON_NODE;
+
+#define BUFFER_QUOTE_MAX_SIZE 7
+
+typedef enum __attribute__ ((__packed__)) {
+ WB_CONTENT_CACHEABLE = (1 << 0),
+ WB_CONTENT_NO_CACHEABLE = (1 << 1),
+} BUFFER_OPTIONS;
+
+typedef enum __attribute__ ((__packed__)) {
+ CT_NONE = 0,
+ CT_APPLICATION_JSON,
+ CT_TEXT_PLAIN,
+ CT_TEXT_HTML,
+ CT_APPLICATION_X_JAVASCRIPT,
+ CT_TEXT_CSS,
+ CT_TEXT_XML,
+ CT_APPLICATION_XML,
+ CT_TEXT_XSL,
+ CT_APPLICATION_OCTET_STREAM,
+ CT_APPLICATION_X_FONT_TRUETYPE,
+ CT_APPLICATION_X_FONT_OPENTYPE,
+ CT_APPLICATION_FONT_WOFF,
+ CT_APPLICATION_FONT_WOFF2,
+ CT_APPLICATION_VND_MS_FONTOBJ,
+ CT_IMAGE_SVG_XML,
+ CT_IMAGE_PNG,
+ CT_IMAGE_JPG,
+ CT_IMAGE_GIF,
+ CT_IMAGE_XICON,
+ CT_IMAGE_ICNS,
+ CT_IMAGE_BMP,
+ CT_PROMETHEUS,
+ CT_AUDIO_MPEG,
+ CT_AUDIO_OGG,
+ CT_VIDEO_MP4,
+ CT_APPLICATION_PDF,
+ CT_APPLICATION_ZIP,
+} HTTP_CONTENT_TYPE;
+
+typedef enum __attribute__ ((__packed__)) {
+ BUFFER_JSON_OPTIONS_DEFAULT = 0,
+ BUFFER_JSON_OPTIONS_MINIFY = (1 << 0),
+ BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS = (1 << 1),
+ BUFFER_JSON_OPTIONS_NON_ANONYMOUS = (1 << 2),
+} BUFFER_JSON_OPTIONS;
+
+typedef struct web_buffer {
+ size_t size; // allocation size of buffer, in bytes
+ size_t len; // current data length in buffer, in bytes
+ char *buffer; // the buffer itself
+ HTTP_CONTENT_TYPE content_type; // the content type of the data in the buffer
+ BUFFER_OPTIONS options; // options related to the content
+ time_t date; // the timestamp this content has been generated
+ time_t expires; // the timestamp this content expires
+ size_t *statistics;
+
+ struct {
+ char key_quote[BUFFER_QUOTE_MAX_SIZE + 1];
+ char value_quote[BUFFER_QUOTE_MAX_SIZE + 1];
+ int8_t depth;
+ BUFFER_JSON_OPTIONS options;
+ BUFFER_JSON_NODE stack[BUFFER_JSON_MAX_DEPTH];
+ } json;
+} BUFFER;
+
+#define CLEAN_BUFFER _cleanup_(buffer_freep) BUFFER
+
+#define buffer_cacheable(wb) do { (wb)->options |= WB_CONTENT_CACHEABLE; if((wb)->options & WB_CONTENT_NO_CACHEABLE) (wb)->options &= ~WB_CONTENT_NO_CACHEABLE; } while(0)
+#define buffer_no_cacheable(wb) do { (wb)->options |= WB_CONTENT_NO_CACHEABLE; if((wb)->options & WB_CONTENT_CACHEABLE) (wb)->options &= ~WB_CONTENT_CACHEABLE; (wb)->expires = 0; } while(0)
+
+#define buffer_strlen(wb) ((wb)->len)
+
+#define BUFFER_OVERFLOW_EOF "EOF"
+
+#ifdef NETDATA_INTERNAL_CHECKS
+#define buffer_overflow_check(b) _buffer_overflow_check(b)
+#else
+#define buffer_overflow_check(b)
+#endif
+
+static inline void _buffer_overflow_check(BUFFER *b) {
+ assert(b->len <= b->size &&
+ "BUFFER: length is above buffer size.");
+
+ assert(!(b->buffer && (b->buffer[b->size] != '\0' || strcmp(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF) != 0)) &&
+ "BUFFER: detected overflow.");
+}
+
+static inline void buffer_flush(BUFFER *wb) {
+ wb->len = 0;
+
+ wb->json.depth = 0;
+ wb->json.stack[0].type = BUFFER_JSON_EMPTY;
+ wb->json.stack[0].count = 0;
+
+ if(wb->buffer)
+ wb->buffer[0] = '\0';
+}
+
+void buffer_reset(BUFFER *wb);
+
+void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds);
+void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds);
+
+BUFFER *buffer_create(size_t size, size_t *statistics);
+void buffer_free(BUFFER *b);
+void buffer_increase(BUFFER *b, size_t free_size_required);
+
+static inline void buffer_freep(BUFFER **bp) {
+ if(bp) buffer_free(*bp);
+}
+
+void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...) PRINTFLIKE(3, 4);
+void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args);
+void buffer_sprintf(BUFFER *wb, const char *fmt, ...) PRINTFLIKE(2,3);
+void buffer_strcat_htmlescape(BUFFER *wb, const char *txt);
+
+void buffer_char_replace(BUFFER *wb, char from, char to);
+
+void buffer_print_sn_flags(BUFFER *wb, SN_FLAGS flags, bool send_anomaly_bit);
+
+#ifdef ENABLE_H2O
+h2o_iovec_t buffer_to_h2o_iovec(BUFFER *wb);
+#endif
+
+static inline void buffer_need_bytes(BUFFER *buffer, size_t needed_free_size) {
+ if(unlikely(buffer->len + needed_free_size >= buffer->size))
+ buffer_increase(buffer, needed_free_size + 1);
+}
+
+void buffer_json_initialize(BUFFER *wb, const char *key_quote, const char *value_quote, int depth,
+ bool add_anonymous_object, BUFFER_JSON_OPTIONS options);
+
+void buffer_json_finalize(BUFFER *wb);
+
+static const char *buffer_tostring(BUFFER *wb)
+{
+ buffer_need_bytes(wb, 1);
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+
+ return(wb->buffer);
+}
+
+static inline void _buffer_json_depth_push(BUFFER *wb, BUFFER_JSON_NODE_TYPE type) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ assert(wb->json.depth <= BUFFER_JSON_MAX_DEPTH && "BUFFER JSON: max nesting reached");
+#endif
+ wb->json.depth++;
+ wb->json.stack[wb->json.depth].count = 0;
+ wb->json.stack[wb->json.depth].type = type;
+}
+
+static inline void _buffer_json_depth_pop(BUFFER *wb) {
+ wb->json.depth--;
+}
+
+static inline void buffer_fast_charcat(BUFFER *wb, const char c) {
+
+ buffer_need_bytes(wb, 2);
+ *(&wb->buffer[wb->len]) = c;
+ wb->len += 1;
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_fast_rawcat(BUFFER *wb, const char *txt, size_t len) {
+ if(unlikely(!txt || !*txt || !len)) return;
+
+ buffer_need_bytes(wb, len + 1);
+
+ const char *t = txt;
+ const char *e = &txt[len];
+
+ char *d = &wb->buffer[wb->len];
+
+ while(t != e)
+ *d++ = *t++;
+
+ wb->len += len;
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_putc(BUFFER *wb, char c) {
+ buffer_need_bytes(wb, 2);
+ wb->buffer[wb->len++] = c;
+ wb->buffer[wb->len] = '\0';
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_fast_strcat(BUFFER *wb, const char *txt, size_t len) {
+ if(unlikely(!txt || !*txt || !len)) return;
+
+ buffer_need_bytes(wb, len + 1);
+
+ const char *t = txt;
+ const char *e = &txt[len];
+
+ char *d = &wb->buffer[wb->len];
+
+ while(t != e
+#ifdef NETDATA_INTERNAL_CHECKS
+ && *t
+#endif
+ )
+ *d++ = *t++;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ assert(!(t != e && !*t) && "BUFFER: source string is shorter than the length given.");
+#endif
+
+ wb->len += len;
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_strcat(BUFFER *wb, const char *txt) {
+ if(unlikely(!txt || !*txt)) return;
+
+ const char *t = txt;
+ while(*t) {
+ buffer_need_bytes(wb, 100);
+ char *s = &wb->buffer[wb->len];
+ char *d = s;
+ const char *e = &wb->buffer[wb->size];
+
+ while(*t && d < e)
+ *d++ = *t++;
+
+ wb->len += d - s;
+ }
+
+ buffer_need_bytes(wb, 1);
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_contents_replace(BUFFER *wb, const char *txt, size_t len) {
+ wb->len = 0;
+ buffer_need_bytes(wb, len + 1);
+
+ memcpy(wb->buffer, txt, len);
+ wb->len = len;
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_strncat(BUFFER *wb, const char *txt, size_t len) {
+ if(unlikely(!txt || !*txt)) return;
+
+ buffer_need_bytes(wb, len + 1);
+
+ memcpy(&wb->buffer[wb->len], txt, len);
+
+ wb->len += len;
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_memcat(BUFFER *wb, const void *mem, size_t bytes) {
+ if(unlikely(!mem)) return;
+
+ buffer_need_bytes(wb, bytes + 1);
+
+ memcpy(&wb->buffer[wb->len], mem, bytes);
+
+ wb->len += bytes;
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_json_strcat(BUFFER *wb, const char *txt) {
+ if(unlikely(!txt || !*txt)) return;
+
+ const unsigned char *t = (const unsigned char *)txt;
+ while(*t) {
+ buffer_need_bytes(wb, 110);
+ unsigned char *s = (unsigned char *)&wb->buffer[wb->len];
+ unsigned char *d = s;
+ const unsigned char *e = (unsigned char *)&wb->buffer[wb->size - 10]; // make room for the max escape sequence
+
+ while(*t && d < e) {
+#ifdef BUFFER_JSON_ESCAPE_UTF
+ if(unlikely(IS_UTF8_STARTBYTE(*t) && IS_UTF8_BYTE(t[1]))) {
+ // UTF-8 multi-byte encoded character
+
+ // find how big this character is (2-4 bytes)
+ size_t utf_character_size = 2;
+ while(utf_character_size < 4 && t[utf_character_size] && IS_UTF8_BYTE(t[utf_character_size]) && !IS_UTF8_STARTBYTE(t[utf_character_size]))
+ utf_character_size++;
+
+ uint32_t code_point = 0;
+ for (size_t i = 0; i < utf_character_size; i++) {
+ code_point <<= 6;
+ code_point |= (t[i] & 0x3F);
+ }
+
+ t += utf_character_size;
+
+ // encode as \u escape sequence
+ *d++ = '\\';
+ *d++ = 'u';
+ *d++ = hex_digits[(code_point >> 12) & 0xf];
+ *d++ = hex_digits[(code_point >> 8) & 0xf];
+ *d++ = hex_digits[(code_point >> 4) & 0xf];
+ *d++ = hex_digits[code_point & 0xf];
+ }
+ else
+#endif
+ if(unlikely(*t < ' ')) {
+ uint32_t v = *t++;
+ *d++ = '\\';
+ *d++ = 'u';
+ *d++ = hex_digits[(v >> 12) & 0xf];
+ *d++ = hex_digits[(v >> 8) & 0xf];
+ *d++ = hex_digits[(v >> 4) & 0xf];
+ *d++ = hex_digits[v & 0xf];
+ }
+ else {
+ if (unlikely(*t == '\\' || *t == '\"'))
+ *d++ = '\\';
+
+ *d++ = *t++;
+ }
+ }
+
+ wb->len += d - s;
+ }
+
+ buffer_need_bytes(wb, 1);
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_json_quoted_strcat(BUFFER *wb, const char *txt) {
+ if(unlikely(!txt || !*txt)) return;
+
+ if(*txt == '"')
+ txt++;
+
+ const char *t = txt;
+ while(*t) {
+ buffer_need_bytes(wb, 100);
+ char *s = &wb->buffer[wb->len];
+ char *d = s;
+ const char *e = &wb->buffer[wb->size - 1]; // remove 1 to make room for the escape character
+
+ while(*t && d < e) {
+ if(unlikely(*t == '"' && !t[1])) {
+ t++;
+ continue;
+ }
+
+ if(unlikely(*t == '\\' || *t == '"'))
+ *d++ = '\\';
+
+ *d++ = *t++;
+ }
+
+ wb->len += d - s;
+ }
+
+ buffer_need_bytes(wb, 1);
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+// This trick seems to give an 80% speed increase in 32bit systems
+// print_number_llu_r() will just print the digits up to the
+// point the remaining value fits in 32 bits, and then calls
+// print_number_lu_r() to print the rest with 32 bit arithmetic.
+
+static inline char *print_uint32_reversed(char *dst, uint32_t value) {
+ char *d = dst;
+ do *d++ = (char)('0' + (value % 10)); while((value /= 10));
+ return d;
+}
+
+static inline char *print_uint64_reversed(char *dst, uint64_t value) {
+#ifdef ENV32BIT
+ if(value <= (uint64_t)0xffffffff)
+ return print_uint32_reversed(dst, value);
+
+ char *d = dst;
+ do *d++ = (char)('0' + (value % 10)); while((value /= 10) && value > (uint64_t)0xffffffff);
+ if(value) return print_uint32_reversed(d, value);
+ return d;
+#else
+ char *d = dst;
+ do *d++ = (char)('0' + (value % 10)); while((value /= 10));
+ return d;
+#endif
+}
+
+static inline char *print_uint32_hex_reversed(char *dst, uint32_t value) {
+ static const char *digits = "0123456789ABCDEF";
+ char *d = dst;
+ do *d++ = digits[value & 0xf]; while((value >>= 4));
+ return d;
+}
+
+static inline char *print_uint64_hex_reversed(char *dst, uint64_t value) {
+#ifdef ENV32BIT
+ if(value <= (uint64_t)0xffffffff)
+ return print_uint32_hex_reversed(dst, value);
+
+ char *d = dst;
+ do *d++ = hex_digits[value & 0xf]; while((value >>= 4) && value > (uint64_t)0xffffffff);
+ if(value) return print_uint32_hex_reversed(d, value);
+ return d;
+#else
+ char *d = dst;
+ do *d++ = hex_digits[value & 0xf]; while((value >>= 4));
+ return d;
+#endif
+}
+
+static inline char *print_uint64_base64_reversed(char *dst, uint64_t value) {
+ char *d = dst;
+ do *d++ = base64_digits[value & 63]; while ((value >>= 6));
+ return d;
+}
+
+static inline void char_array_reverse(char *from, char *to) {
+ // from and to are inclusive
+ char *begin = from, *end = to, aux;
+ while (end > begin) aux = *end, *end-- = *begin, *begin++ = aux;
+}
+
+static inline int print_netdata_double(char *dst, NETDATA_DOUBLE value) {
+ char *s = dst;
+
+ if(unlikely(value < 0)) {
+ *s++ = '-';
+ value = fabsndd(value);
+ }
+
+ uint64_t fractional_precision = 10000000ULL; // fractional part 7 digits
+ int fractional_wanted_digits = 7;
+ int exponent = 0;
+ if(unlikely(value >= (NETDATA_DOUBLE)(UINT64_MAX / 10))) {
+ // the number is too big to print using 64bit numbers
+ // so, let's convert it to exponential notation
+ exponent = (int)(floorndd(log10ndd(value)));
+ value /= powndd(10, exponent);
+
+ // the max precision we can support is 18 digits
+ // (UINT64_MAX is 20, but the first is 1)
+ fractional_precision = 1000000000000000000ULL; // fractional part 18 digits
+ fractional_wanted_digits = 18;
+ }
+
+ char *d = s;
+ NETDATA_DOUBLE integral_d, fractional_d;
+ fractional_d = modfndd(value, &integral_d);
+
+ // get the integral and the fractional parts as 64-bit integers
+ uint64_t integral = (uint64_t)integral_d;
+ uint64_t fractional = (uint64_t)llrintndd(fractional_d * (NETDATA_DOUBLE)fractional_precision);
+ if(unlikely(fractional >= fractional_precision)) {
+ integral++;
+ fractional -= fractional_precision;
+ }
+
+ // convert the integral part to string (reversed)
+ d = print_uint64_reversed(d, integral);
+ char_array_reverse(s, d - 1); // copy reversed the integral string
+
+ if(likely(fractional != 0)) {
+ *d++ = '.'; // add the dot
+
+ // convert the fractional part to string (reversed)
+ d = print_uint64_reversed(s = d, fractional);
+
+ while(d - s < fractional_wanted_digits) *d++ = '0'; // prepend zeros to reach precision
+ char_array_reverse(s, d - 1); // copy reversed the fractional string
+
+ // remove trailing zeros from the fractional part
+ while(*(d - 1) == '0') d--;
+ }
+
+ if(unlikely(exponent != 0)) {
+ *d++ = 'e';
+ *d++ = '+';
+ d = print_uint32_reversed(s = d, exponent);
+ char_array_reverse(s, d - 1);
+ }
+
+ *d = '\0';
+ return (int)(d - dst);
+}
+
+static inline void buffer_print_uint64(BUFFER *wb, uint64_t value) {
+ buffer_need_bytes(wb, 50);
+
+ char *s = &wb->buffer[wb->len];
+ char *d = print_uint64_reversed(s, value);
+ char_array_reverse(s, d - 1);
+ *d = '\0';
+ wb->len += d - s;
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_int64(BUFFER *wb, int64_t value) {
+ buffer_need_bytes(wb, 50);
+
+ if(value < 0) {
+ buffer_fast_strcat(wb, "-", 1);
+ value = -value;
+ }
+
+ buffer_print_uint64(wb, (uint64_t)value);
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_uint64_hex(BUFFER *wb, uint64_t value) {
+ buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1);
+
+ buffer_fast_strcat(wb, HEX_PREFIX, sizeof(HEX_PREFIX) - 1);
+
+ char *s = &wb->buffer[wb->len];
+ char *d = print_uint64_hex_reversed(s, value);
+ char_array_reverse(s, d - 1);
+ *d = '\0';
+ wb->len += d - s;
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_uint64_base64(BUFFER *wb, uint64_t value) {
+ buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1);
+
+ buffer_fast_strcat(wb, IEEE754_UINT64_B64_PREFIX, sizeof(IEEE754_UINT64_B64_PREFIX) - 1);
+
+ char *s = &wb->buffer[wb->len];
+ char *d = print_uint64_base64_reversed(s, value);
+ char_array_reverse(s, d - 1);
+ *d = '\0';
+ wb->len += d - s;
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_int64_hex(BUFFER *wb, int64_t value) {
+ buffer_need_bytes(wb, 2);
+
+ if(value < 0) {
+ buffer_fast_strcat(wb, "-", 1);
+ value = -value;
+ }
+
+ buffer_print_uint64_hex(wb, (uint64_t)value);
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_int64_base64(BUFFER *wb, int64_t value) {
+ buffer_need_bytes(wb, 2);
+
+ if(value < 0) {
+ buffer_fast_strcat(wb, "-", 1);
+ value = -value;
+ }
+
+ buffer_print_uint64_base64(wb, (uint64_t)value);
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_netdata_double(BUFFER *wb, NETDATA_DOUBLE value) {
+ buffer_need_bytes(wb, 512 + 2);
+
+ if(isnan(value) || isinf(value)) {
+ buffer_fast_strcat(wb, "null", 4);
+ return;
+ }
+ else
+ wb->len += print_netdata_double(&wb->buffer[wb->len], value);
+
+ // terminate it
+ buffer_need_bytes(wb, 1);
+ wb->buffer[wb->len] = '\0';
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_netdata_double_hex(BUFFER *wb, NETDATA_DOUBLE value) {
+ buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1 + 1);
+
+ uint64_t *ptr = (uint64_t *) (&value);
+ buffer_fast_strcat(wb, IEEE754_DOUBLE_HEX_PREFIX, sizeof(IEEE754_DOUBLE_HEX_PREFIX) - 1);
+
+ char *s = &wb->buffer[wb->len];
+ char *d = print_uint64_hex_reversed(s, *ptr);
+ char_array_reverse(s, d - 1);
+ *d = '\0';
+ wb->len += d - s;
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_netdata_double_base64(BUFFER *wb, NETDATA_DOUBLE value) {
+ buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1 + 1);
+
+ uint64_t *ptr = (uint64_t *) (&value);
+ buffer_fast_strcat(wb, IEEE754_DOUBLE_B64_PREFIX, sizeof(IEEE754_DOUBLE_B64_PREFIX) - 1);
+
+ char *s = &wb->buffer[wb->len];
+ char *d = print_uint64_base64_reversed(s, *ptr);
+ char_array_reverse(s, d - 1);
+ *d = '\0';
+ wb->len += d - s;
+
+ buffer_overflow_check(wb);
+}
+
+typedef enum {
+ NUMBER_ENCODING_DECIMAL,
+ NUMBER_ENCODING_HEX,
+ NUMBER_ENCODING_BASE64,
+} NUMBER_ENCODING;
+
+static inline void buffer_print_int64_encoded(BUFFER *wb, NUMBER_ENCODING encoding, int64_t value) {
+ if(encoding == NUMBER_ENCODING_BASE64)
+ return buffer_print_int64_base64(wb, value);
+
+ if(encoding == NUMBER_ENCODING_HEX)
+ return buffer_print_int64_hex(wb, value);
+
+ return buffer_print_int64(wb, value);
+}
+
+static inline void buffer_print_uint64_encoded(BUFFER *wb, NUMBER_ENCODING encoding, uint64_t value) {
+ if(encoding == NUMBER_ENCODING_BASE64)
+ return buffer_print_uint64_base64(wb, value);
+
+ if(encoding == NUMBER_ENCODING_HEX)
+ return buffer_print_uint64_hex(wb, value);
+
+ return buffer_print_uint64(wb, value);
+}
+
+static inline void buffer_print_netdata_double_encoded(BUFFER *wb, NUMBER_ENCODING encoding, NETDATA_DOUBLE value) {
+ if(encoding == NUMBER_ENCODING_BASE64)
+ return buffer_print_netdata_double_base64(wb, value);
+
+ if(encoding == NUMBER_ENCODING_HEX)
+ return buffer_print_netdata_double_hex(wb, value);
+
+ return buffer_print_netdata_double(wb, value);
+}
+
+static inline void buffer_print_spaces(BUFFER *wb, size_t spaces) {
+ buffer_need_bytes(wb, spaces * 4 + 1);
+
+ char *d = &wb->buffer[wb->len];
+ for(size_t i = 0; i < spaces; i++) {
+ *d++ = ' ';
+ *d++ = ' ';
+ *d++ = ' ';
+ *d++ = ' ';
+ }
+
+ *d = '\0';
+ wb->len += spaces * 4;
+
+ buffer_overflow_check(wb);
+}
+
+static inline void buffer_print_json_comma(BUFFER *wb) {
+ if(wb->json.stack[wb->json.depth].count)
+ buffer_fast_strcat(wb, ",", 1);
+}
+
+static inline void buffer_print_json_comma_newline_spacing(BUFFER *wb) {
+ buffer_print_json_comma(wb);
+
+ if((wb->json.options & BUFFER_JSON_OPTIONS_MINIFY) ||
+ (wb->json.stack[wb->json.depth].type == BUFFER_JSON_ARRAY && !(wb->json.options & BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS)))
+ return;
+
+ buffer_fast_strcat(wb, "\n", 1);
+ buffer_print_spaces(wb, wb->json.depth + 1);
+}
+
+static inline void buffer_print_json_key(BUFFER *wb, const char *key) {
+ buffer_strcat(wb, wb->json.key_quote);
+ buffer_json_strcat(wb, key);
+ buffer_strcat(wb, wb->json.key_quote);
+}
+
+static inline void buffer_json_add_string_value(BUFFER *wb, const char *value) {
+ if(value) {
+ buffer_strcat(wb, wb->json.value_quote);
+ buffer_json_strcat(wb, value);
+ buffer_strcat(wb, wb->json.value_quote);
+ }
+ else
+ buffer_fast_strcat(wb, "null", 4);
+}
+
+static inline void buffer_json_add_quoted_string_value(BUFFER *wb, const char *value) {
+ if(value) {
+ buffer_strcat(wb, wb->json.value_quote);
+ buffer_json_quoted_strcat(wb, value);
+ buffer_strcat(wb, wb->json.value_quote);
+ }
+ else
+ buffer_fast_strcat(wb, "null", 4);
+}
+
+static inline void buffer_json_member_add_object(BUFFER *wb, const char *key) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":{", 2);
+ wb->json.stack[wb->json.depth].count++;
+
+ _buffer_json_depth_push(wb, BUFFER_JSON_OBJECT);
+}
+
+static inline void buffer_json_object_close(BUFFER *wb) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ assert(wb->json.depth >= 0 && "BUFFER JSON: nothing is open to close it");
+ assert(wb->json.stack[wb->json.depth].type == BUFFER_JSON_OBJECT && "BUFFER JSON: an object is not open to close it");
+#endif
+ if(!(wb->json.options & BUFFER_JSON_OPTIONS_MINIFY)) {
+ buffer_fast_strcat(wb, "\n", 1);
+ buffer_print_spaces(wb, wb->json.depth);
+ }
+ buffer_fast_strcat(wb, "}", 1);
+ _buffer_json_depth_pop(wb);
+}
+
+static inline void buffer_json_member_add_string(BUFFER *wb, const char *key, const char *value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+ buffer_json_add_string_value(wb, value);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_string_or_omit(BUFFER *wb, const char *key, const char *value) {
+ if(value && *value)
+ buffer_json_member_add_string(wb, key, value);
+}
+
+static inline void buffer_json_member_add_string_or_empty(BUFFER *wb, const char *key, const char *value) {
+ if(!value)
+ value = "";
+
+ buffer_json_member_add_string(wb, key, value);
+}
+
+static inline void buffer_json_member_add_quoted_string(BUFFER *wb, const char *key, const char *value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+
+ if(!value || strcmp(value, "null") == 0)
+ buffer_fast_strcat(wb, "null", 4);
+ else
+ buffer_json_add_quoted_string_value(wb, value);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_uuid(BUFFER *wb, const char *key, uuid_t *value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+
+ if(value && !uuid_is_null(*value)) {
+ char uuid[GUID_LEN + 1];
+ uuid_unparse_lower(*value, uuid);
+ buffer_json_add_string_value(wb, uuid);
+ }
+ else
+ buffer_json_add_string_value(wb, NULL);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_boolean(BUFFER *wb, const char *key, bool value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+ buffer_strcat(wb, value?"true":"false");
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_array(BUFFER *wb, const char *key) {
+ buffer_print_json_comma_newline_spacing(wb);
+ if (key) {
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":[", 2);
+ }
+ else
+ buffer_fast_strcat(wb, "[", 1);
+
+ wb->json.stack[wb->json.depth].count++;
+
+ _buffer_json_depth_push(wb, BUFFER_JSON_ARRAY);
+}
+
+static inline void buffer_json_add_array_item_array(BUFFER *wb) {
+ if(!(wb->json.options & BUFFER_JSON_OPTIONS_MINIFY) && wb->json.stack[wb->json.depth].type == BUFFER_JSON_ARRAY) {
+ // an array inside another array
+ buffer_print_json_comma(wb);
+ buffer_fast_strcat(wb, "\n", 1);
+ buffer_print_spaces(wb, wb->json.depth + 1);
+ }
+ else
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_fast_strcat(wb, "[", 1);
+ wb->json.stack[wb->json.depth].count++;
+
+ _buffer_json_depth_push(wb, BUFFER_JSON_ARRAY);
+}
+
+static inline void buffer_json_add_array_item_string(BUFFER *wb, const char *value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_json_add_string_value(wb, value);
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_double(BUFFER *wb, NETDATA_DOUBLE value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_print_netdata_double(wb, value);
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_int64(BUFFER *wb, int64_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_print_int64(wb, value);
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_uint64(BUFFER *wb, uint64_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_print_uint64(wb, value);
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_boolean(BUFFER *wb, bool value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_strcat(wb, value ? "true" : "false");
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_time_t(BUFFER *wb, time_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_print_int64(wb, value);
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_time_ms(BUFFER *wb, time_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_print_int64(wb, value);
+ buffer_fast_strcat(wb, "000", 3);
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_time_t2ms(BUFFER *wb, time_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_print_int64(wb, value);
+ buffer_fast_strcat(wb, "000", 3);
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_add_array_item_object(BUFFER *wb) {
+ buffer_print_json_comma_newline_spacing(wb);
+
+ buffer_fast_strcat(wb, "{", 1);
+ wb->json.stack[wb->json.depth].count++;
+
+ _buffer_json_depth_push(wb, BUFFER_JSON_OBJECT);
+}
+
+static inline void buffer_json_member_add_time_t(BUFFER *wb, const char *key, time_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+ buffer_print_int64(wb, value);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_time_t2ms(BUFFER *wb, const char *key, time_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+ buffer_print_int64(wb, value);
+ buffer_fast_strcat(wb, "000", 3);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_uint64(BUFFER *wb, const char *key, uint64_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+ buffer_print_uint64(wb, value);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_int64(BUFFER *wb, const char *key, int64_t value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+ buffer_print_int64(wb, value);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_member_add_double(BUFFER *wb, const char *key, NETDATA_DOUBLE value) {
+ buffer_print_json_comma_newline_spacing(wb);
+ buffer_print_json_key(wb, key);
+ buffer_fast_strcat(wb, ":", 1);
+ buffer_print_netdata_double(wb, value);
+
+ wb->json.stack[wb->json.depth].count++;
+}
+
+static inline void buffer_json_array_close(BUFFER *wb) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ assert(wb->json.depth >= 0 && "BUFFER JSON: nothing is open to close it");
+ assert(wb->json.stack[wb->json.depth].type == BUFFER_JSON_ARRAY && "BUFFER JSON: an array is not open to close it");
+#endif
+ if(wb->json.options & BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS) {
+ buffer_fast_strcat(wb, "\n", 1);
+ buffer_print_spaces(wb, wb->json.depth);
+ }
+
+ buffer_fast_strcat(wb, "]", 1);
+ _buffer_json_depth_pop(wb);
+}
+
+typedef enum __attribute__((packed)) {
+ RRDF_FIELD_OPTS_NONE = 0,
+ RRDF_FIELD_OPTS_UNIQUE_KEY = (1 << 0), // the field is the unique key of the row
+ RRDF_FIELD_OPTS_VISIBLE = (1 << 1), // the field should be visible by default
+ RRDF_FIELD_OPTS_STICKY = (1 << 2), // the field should be sticky
+ RRDF_FIELD_OPTS_FULL_WIDTH = (1 << 3), // the field should get full width
+ RRDF_FIELD_OPTS_WRAP = (1 << 4), // the field should wrap
+ RRDF_FIELD_OPTS_DUMMY = (1 << 5), // not a presentable field
+ RRDF_FIELD_OPTS_EXPANDED_FILTER = (1 << 6), // show the filter expanded
+} RRDF_FIELD_OPTIONS;
+
+typedef enum __attribute__((packed)) {
+ RRDF_FIELD_TYPE_NONE,
+ RRDF_FIELD_TYPE_INTEGER,
+ RRDF_FIELD_TYPE_BOOLEAN,
+ RRDF_FIELD_TYPE_STRING,
+ RRDF_FIELD_TYPE_DETAIL_STRING,
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
+ RRDF_FIELD_TYPE_DURATION,
+ RRDF_FIELD_TYPE_TIMESTAMP,
+ RRDF_FIELD_TYPE_ARRAY,
+} RRDF_FIELD_TYPE;
+
+static inline const char *rrdf_field_type_to_string(RRDF_FIELD_TYPE type) {
+ switch(type) {
+ default:
+ case RRDF_FIELD_TYPE_NONE:
+ return "none";
+
+ case RRDF_FIELD_TYPE_INTEGER:
+ return "integer";
+
+ case RRDF_FIELD_TYPE_BOOLEAN:
+ return "boolean";
+
+ case RRDF_FIELD_TYPE_STRING:
+ return "string";
+
+ case RRDF_FIELD_TYPE_DETAIL_STRING:
+ return "detail-string";
+
+ case RRDF_FIELD_TYPE_BAR_WITH_INTEGER:
+ return "bar-with-integer";
+
+ case RRDF_FIELD_TYPE_DURATION:
+ return "duration";
+
+ case RRDF_FIELD_TYPE_TIMESTAMP:
+ return "timestamp";
+
+ case RRDF_FIELD_TYPE_ARRAY:
+ return "array";
+ }
+}
+
+typedef enum __attribute__((packed)) {
+ RRDF_FIELD_VISUAL_VALUE, // show the value, possibly applying a transformation
+ RRDF_FIELD_VISUAL_BAR, // show the value and a bar, respecting the max field to fill the bar at 100%
+ RRDF_FIELD_VISUAL_PILL, //
+ RRDF_FIELD_VISUAL_RICH, //
+ RRDR_FIELD_VISUAL_ROW_OPTIONS, // this is a dummy column that is used for row options
+} RRDF_FIELD_VISUAL;
+
+static inline const char *rrdf_field_visual_to_string(RRDF_FIELD_VISUAL visual) {
+ switch(visual) {
+ default:
+ case RRDF_FIELD_VISUAL_VALUE:
+ return "value";
+
+ case RRDF_FIELD_VISUAL_BAR:
+ return "bar";
+
+ case RRDF_FIELD_VISUAL_PILL:
+ return "pill";
+
+ case RRDF_FIELD_VISUAL_RICH:
+ return "richValue";
+
+ case RRDR_FIELD_VISUAL_ROW_OPTIONS:
+ return "rowOptions";
+ }
+}
+
+typedef enum __attribute__((packed)) {
+ RRDF_FIELD_TRANSFORM_NONE, // show the value as-is
+ RRDF_FIELD_TRANSFORM_NUMBER, // show the value respecting the decimal_points
+ RRDF_FIELD_TRANSFORM_DURATION_S, // transform as duration in second to a human-readable duration
+ RRDF_FIELD_TRANSFORM_DATETIME_MS, // UNIX epoch timestamp in ms
+ RRDF_FIELD_TRANSFORM_DATETIME_USEC, // UNIX epoch timestamp in usec
+} RRDF_FIELD_TRANSFORM;
+
+static inline const char *rrdf_field_transform_to_string(RRDF_FIELD_TRANSFORM transform) {
+ switch(transform) {
+ default:
+ case RRDF_FIELD_TRANSFORM_NONE:
+ return "none";
+
+ case RRDF_FIELD_TRANSFORM_NUMBER:
+ return "number";
+
+ case RRDF_FIELD_TRANSFORM_DURATION_S:
+ return "duration";
+
+ case RRDF_FIELD_TRANSFORM_DATETIME_MS:
+ return "datetime";
+
+ case RRDF_FIELD_TRANSFORM_DATETIME_USEC:
+ return "datetime_usec";
+ }
+}
+
+typedef enum __attribute__((packed)) {
+ RRDF_FIELD_SORT_ASCENDING = (1 << 0),
+ RRDF_FIELD_SORT_DESCENDING = (1 << 1),
+
+ RRDF_FIELD_SORT_FIXED = (1 << 7),
+} RRDF_FIELD_SORT;
+
+static inline const char *rrdf_field_sort_to_string(RRDF_FIELD_SORT sort) {
+ if(sort & RRDF_FIELD_SORT_DESCENDING)
+ return "descending";
+
+ else
+ return "ascending";
+}
+
+typedef enum __attribute__((packed)) {
+ RRDF_FIELD_SUMMARY_UNIQUECOUNT, // Finds the number of unique values of a group of rows
+ RRDF_FIELD_SUMMARY_SUM, // Sums the values of a group of rows
+ RRDF_FIELD_SUMMARY_MIN, // Finds the minimum value of a group of rows
+ RRDF_FIELD_SUMMARY_MAX, // Finds the maximum value of a group of rows
+ // RRDF_FIELD_SUMMARY_EXTENT, // Finds the minimum and maximum values of a group of rows
+ RRDF_FIELD_SUMMARY_MEAN, // Finds the mean/average value of a group of rows
+ RRDF_FIELD_SUMMARY_MEDIAN, // Finds the median value of a group of rows
+ // RRDF_FIELD_SUMMARY_UNIQUE, // Finds the unique values of a group of rows
+ RRDF_FIELD_SUMMARY_COUNT, // Calculates the number of rows in a group
+} RRDF_FIELD_SUMMARY;
+
+static inline const char *rrdf_field_summary_to_string(RRDF_FIELD_SUMMARY summary) {
+ switch(summary) {
+ default:
+ case RRDF_FIELD_SUMMARY_COUNT:
+ return "count";
+
+ case RRDF_FIELD_SUMMARY_UNIQUECOUNT:
+ return "uniqueCount";
+
+ case RRDF_FIELD_SUMMARY_SUM:
+ return "sum";
+
+ case RRDF_FIELD_SUMMARY_MIN:
+ return "min";
+
+ case RRDF_FIELD_SUMMARY_MEAN:
+ return "mean";
+
+ case RRDF_FIELD_SUMMARY_MEDIAN:
+ return "median";
+
+ case RRDF_FIELD_SUMMARY_MAX:
+ return "max";
+ }
+}
+
+typedef enum __attribute__((packed)) {
+ RRDF_FIELD_FILTER_NONE = 0,
+ RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_FILTER_FACET,
+} RRDF_FIELD_FILTER;
+
+static inline const char *rrdf_field_filter_to_string(RRDF_FIELD_FILTER filter) {
+ switch(filter) {
+ case RRDF_FIELD_FILTER_RANGE:
+ return "range";
+
+ case RRDF_FIELD_FILTER_MULTISELECT:
+ return "multiselect";
+
+ case RRDF_FIELD_FILTER_FACET:
+ return "facet";
+
+ default:
+ case RRDF_FIELD_FILTER_NONE:
+ return "none";
+ }
+}
+
+static inline void
+buffer_rrdf_table_add_field(BUFFER *wb, size_t field_id, const char *key, const char *name, RRDF_FIELD_TYPE type,
+ RRDF_FIELD_VISUAL visual, RRDF_FIELD_TRANSFORM transform, size_t decimal_points,
+ const char *units, NETDATA_DOUBLE max, RRDF_FIELD_SORT sort, const char *pointer_to,
+ RRDF_FIELD_SUMMARY summary, RRDF_FIELD_FILTER filter, RRDF_FIELD_OPTIONS options,
+ const char *default_value) {
+
+ buffer_json_member_add_object(wb, key);
+ {
+ buffer_json_member_add_uint64(wb, "index", field_id);
+ buffer_json_member_add_boolean(wb, "unique_key", options & RRDF_FIELD_OPTS_UNIQUE_KEY);
+ buffer_json_member_add_string(wb, "name", name);
+ buffer_json_member_add_boolean(wb, "visible", options & RRDF_FIELD_OPTS_VISIBLE);
+ buffer_json_member_add_string(wb, "type", rrdf_field_type_to_string(type));
+ buffer_json_member_add_string_or_omit(wb, "units", units);
+ buffer_json_member_add_string(wb, "visualization", rrdf_field_visual_to_string(visual));
+
+ buffer_json_member_add_object(wb, "value_options");
+ {
+ buffer_json_member_add_string_or_omit(wb, "units", units);
+ buffer_json_member_add_string(wb, "transform", rrdf_field_transform_to_string(transform));
+ buffer_json_member_add_uint64(wb, "decimal_points", decimal_points);
+ buffer_json_member_add_string(wb, "default_value", default_value);
+ }
+ buffer_json_object_close(wb);
+
+ if (!isnan((NETDATA_DOUBLE) (max)))
+ buffer_json_member_add_double(wb, "max", (NETDATA_DOUBLE) (max));
+
+ buffer_json_member_add_string_or_omit(wb, "pointer_to", pointer_to);
+ buffer_json_member_add_string(wb, "sort", rrdf_field_sort_to_string(sort));
+ buffer_json_member_add_boolean(wb, "sortable", !(sort & RRDF_FIELD_SORT_FIXED));
+ buffer_json_member_add_boolean(wb, "sticky", options & RRDF_FIELD_OPTS_STICKY);
+ buffer_json_member_add_string(wb, "summary", rrdf_field_summary_to_string(summary));
+ buffer_json_member_add_string(wb, "filter", rrdf_field_filter_to_string(filter));
+
+ buffer_json_member_add_boolean(wb, "full_width", options & RRDF_FIELD_OPTS_FULL_WIDTH);
+ buffer_json_member_add_boolean(wb, "wrap", options & RRDF_FIELD_OPTS_WRAP);
+ buffer_json_member_add_boolean(wb, "default_expanded_filter", options & RRDF_FIELD_OPTS_EXPANDED_FILTER);
+
+ if(options & RRDF_FIELD_OPTS_DUMMY)
+ buffer_json_member_add_boolean(wb, "dummy", true);
+ }
+ buffer_json_object_close(wb);
+}
+
+#endif /* NETDATA_WEB_BUFFER_H */