@cryptotaxi247 / netdata-1 / commits / cd584e035

ZSTD and GZIP/DEFLATE streaming support (#16268)

* move compression header to compression.h * prototype with zstd compression * updated capabilities * no need for resetting compression * left-over reset function * use ZSTD_compressStream() instead of ZSTD_compressStream2() for backwards compatibility * remove call to LZ4_decoderRingBufferSize() * debug signature failures * fix the buffers of lz4 * fix decoding of zstd * detect compression based on initialization; prefer ZSTD over LZ4 * allow both lz4 and zstd * initialize zstd streams * define missing ZSTD_CLEVEL_DEFAULT * log zero compressed size * debug log * flush compression buffer * add sender compression statistics * removed debugging messages * do not fail if zstd is not available * cleanup and buildinfo * fix max message size, use zstd level 1, add compressio ratio reporting * use compression level 1 * fix ratio title * better compression error logs * for backwards compatibility use buffers of COMPRESSION_MAX_CHUNK * switch to default compression level * additional streaming error conditions detection * do not expose compression stats when compression is not enabled * test for the right lz4 functions * moved lz4 and zstd to their own files * add gzip streaming compression * gzip error handling * added unittest for streaming compression * eliminate a copy of the uncompressed data during zstd compression * eliminate not needed zstd allocations * cleanup * decode gzip with Z_SYNC_FLUSH * set the decoding gzip algorithm * user configuration for compression levels and compression algorithms order * fix exclusion of not preferred compressions * remove now obsolete compression define, since gzip is always available * rename compression algorithms order in stream.conf * move common checks in compression.c * cleanup * backwards compatible error checking

Costa Tsaousis committed Oct 27, 2023 at 15:37 UTC cd584e0357b82ec5cad12156fd7a5b65f545a0d0
20 files changed +1344 -405
Makefile.am
+8
@@ -666,6 +666,13 @@ API_PLUGIN_FILES = \
666 STREAMING_PLUGIN_FILES = \
667 streaming/rrdpush.c \
668 streaming/compression.c \
669 + streaming/compression.h \
670 + streaming/compression_gzip.c \
671 + streaming/compression_gzip.h \
672 + streaming/compression_lz4.c \
673 + streaming/compression_lz4.h \
674 + streaming/compression_zstd.c \
675 + streaming/compression_zstd.h \
676 streaming/sender.c \
677 streaming/receiver.c \
678 streaming/replication.h \
@@ -1143,6 +1150,7 @@ NETDATA_COMMON_LIBS = \
1150 $(OPTIONAL_MQTT_LIBS) \
1151 $(OPTIONAL_UV_LIBS) \
1152 $(OPTIONAL_LZ4_LIBS) \
1153 + $(OPTIONAL_ZSTD_LIBS) \
1154 $(OPTIONAL_DATACHANNEL_LIBS) \
1155 libjudy.a \
1156 $(OPTIONAL_SSL_LIBS) \
configure.ac
+16 -3
@@ -555,16 +555,28 @@ OPTIONAL_UV_LIBS="${UV_LIBS}"
555
556 AC_CHECK_LIB(
557 [lz4],
558 - [LZ4_initStream],
558 + [LZ4_createStream],
559 [LZ4_LIBS_FAST="-llz4"]
560 )
561
562 AC_CHECK_LIB(
563 [lz4],
564 - [LZ4_compress_default],
564 + [LZ4_compress_fast_continue],
565 [LZ4_LIBS="-llz4"]
566 )
567
568 +# -----------------------------------------------------------------------------
569 +# zstd
570 +
571 +AC_CHECK_LIB([zstd], [ZSTD_createCStream, ZSTD_compressStream, ZSTD_decompressStream, ZSTD_createDStream],
572 + [LIBZSTD_FOUND=yes],
573 + [LIBZSTD_FOUND=no])
574 +
575 +if test "x$LIBZSTD_FOUND" = "xyes"; then
576 + AC_DEFINE([ENABLE_ZSTD], [1], [libzstd usability])
577 + OPTIONAL_ZSTD_LIBS="-lzstd"
578 +fi
579 +
580 # -----------------------------------------------------------------------------
581 # zlib
582
@@ -702,7 +714,7 @@ if test "${enable_lz4}" != "no"; then
714 AC_TRY_LINK(
715 [ #include <lz4.h> ],
716 [
705 - LZ4_stream_t* stream = LZ4_initStream(NULL, 0);
717 + LZ4_stream_t* stream = LZ4_createStream();
718 ],
719 [ enable_lz4="yes"],
720 [ enable_lz4="no" ]
@@ -1900,6 +1912,7 @@ AC_SUBST([OPTIONAL_MATH_LIBS])
1912 AC_SUBST([OPTIONAL_DATACHANNEL_LIBS])
1913 AC_SUBST([OPTIONAL_UV_LIBS])
1914 AC_SUBST([OPTIONAL_LZ4_LIBS])
1915 +AC_SUBST([OPTIONAL_ZSTD_LIBS])
1916 AC_SUBST([OPTIONAL_SSL_LIBS])
1917 AC_SUBST([OPTIONAL_JSONC_LIBS])
1918 AC_SUBST([OPTIONAL_YAML_LIBS])
daemon/buildinfo.c
+46 -4
@@ -48,6 +48,7 @@ typedef enum __attribute__((packed)) {
48 BIB_FEATURE_CLOUD,
49 BIB_FEATURE_HEALTH,
50 BIB_FEATURE_STREAMING,
51 + BIB_FEATURE_BACKFILLING,
52 BIB_FEATURE_REPLICATION,
53 BIB_FEATURE_STREAMING_COMPRESSION,
54 BIB_FEATURE_CONTEXTS,
@@ -66,6 +67,7 @@ typedef enum __attribute__((packed)) {
67 BIB_CONNECTIVITY_NATIVE_HTTPS,
68 BIB_CONNECTIVITY_TLS_HOST_VERIFY,
69 BIB_LIB_LZ4,
70 + BIB_LIB_ZSTD,
71 BIB_LIB_ZLIB,
72 BIB_LIB_JUDY,
73 BIB_LIB_DLIB,
@@ -484,6 +486,14 @@ static struct {
486 .json = "streaming",
487 .value = NULL,
488 },
489 + [BIB_FEATURE_BACKFILLING] = {
490 + .category = BIC_FEATURE,
491 + .type = BIT_BOOLEAN,
492 + .analytics = NULL,
493 + .print = "Back-filling (of higher database tiers)",
494 + .json = "back-filling",
495 + .value = NULL,
496 + },
497 [BIB_FEATURE_REPLICATION] = {
498 .category = BIC_FEATURE,
499 .type = BIT_BOOLEAN,
@@ -498,7 +508,7 @@ static struct {
508 .analytics = "Stream Compression",
509 .print = "Streaming and Replication Compression",
510 .json = "stream-compression",
501 - .value = "none",
511 + .value = NULL,
512 },
513 [BIB_FEATURE_CONTEXTS] = {
514 .category = BIC_FEATURE,
@@ -628,6 +638,14 @@ static struct {
638 .json = "lz4",
639 .value = NULL,
640 },
641 + [BIB_LIB_ZSTD] = {
642 + .category = BIC_LIBS,
643 + .type = BIT_BOOLEAN,
644 + .analytics = NULL,
645 + .print = "ZSTD (fast, lossless compression algorithm)",
646 + .json = "zstd",
647 + .value = NULL,
648 + },
649 [BIB_LIB_ZLIB] = {
650 .category = BIC_LIBS,
651 .type = BIT_BOOLEAN,
@@ -1029,6 +1047,23 @@ static void build_info_set_value(BUILD_INFO_SLOT slot, const char *value) {
1047 BUILD_INFO[slot].value = value;
1048 }
1049
1050 +static void build_info_append_value(BUILD_INFO_SLOT slot, const char *value) {
1051 + size_t size = BUILD_INFO[slot].value ? strlen(BUILD_INFO[slot].value) + 1 : 0;
1052 + size += strlen(value);
1053 + char buf[size + 1];
1054 +
1055 + if(BUILD_INFO[slot].value) {
1056 + strcpy(buf, BUILD_INFO[slot].value);
1057 + strcat(buf, " ");
1058 + strcat(buf, value);
1059 + }
1060 + else
1061 + strcpy(buf, value);
1062 +
1063 + freez((void *)BUILD_INFO[slot].value);
1064 + BUILD_INFO[slot].value = strdupz(buf);
1065 +}
1066 +
1067 static void build_info_set_value_strdupz(BUILD_INFO_SLOT slot, const char *value) {
1068 if(!value) value = "";
1069 build_info_set_value(slot, strdupz(value));
@@ -1075,14 +1110,18 @@ __attribute__((constructor)) void initialize_build_info(void) {
1110
1111 build_info_set_status(BIB_FEATURE_HEALTH, true);
1112 build_info_set_status(BIB_FEATURE_STREAMING, true);
1113 + build_info_set_status(BIB_FEATURE_BACKFILLING, true);
1114 build_info_set_status(BIB_FEATURE_REPLICATION, true);
1115
1080 -#ifdef ENABLE_RRDPUSH_COMPRESSION
1116 build_info_set_status(BIB_FEATURE_STREAMING_COMPRESSION, true);
1082 -#ifdef ENABLE_LZ4
1083 - build_info_set_value(BIB_FEATURE_STREAMING_COMPRESSION, "lz4");
1117 +
1118 +#ifdef ENABLE_ZSTD
1119 + build_info_append_value(BIB_FEATURE_STREAMING_COMPRESSION, "zstd");
1120 #endif
1121 +#ifdef ENABLE_LZ4
1122 + build_info_append_value(BIB_FEATURE_STREAMING_COMPRESSION, "lz4");
1123 #endif
1124 + build_info_append_value(BIB_FEATURE_STREAMING_COMPRESSION, "gzip");
1125
1126 build_info_set_status(BIB_FEATURE_CONTEXTS, true);
1127 build_info_set_status(BIB_FEATURE_TIERING, true);
@@ -1117,6 +1156,9 @@ __attribute__((constructor)) void initialize_build_info(void) {
1156 #ifdef ENABLE_LZ4
1157 build_info_set_status(BIB_LIB_LZ4, true);
1158 #endif
1159 +#ifdef ENABLE_ZSTD
1160 + build_info_set_status(BIB_LIB_ZSTD, true);
1161 +#endif
1162
1163 build_info_set_status(BIB_LIB_ZLIB, true);
1164
daemon/main.c
+7
@@ -1337,6 +1337,7 @@ int julytest(void);
1337 int pluginsd_parser_unittest(void);
1338 void replication_initialize(void);
1339 void bearer_tokens_init(void);
1340 +int unittest_rrdpush_compressions(void);
1341
1342 int main(int argc, char **argv) {
1343 // initialize the system clocks
@@ -1550,6 +1551,10 @@ int main(int argc, char **argv) {
1551 unittest_running = true;
1552 return pluginsd_parser_unittest();
1553 }
1554 + else if(strcmp(optarg, "rrdpush_compressions_test") == 0) {
1555 + unittest_running = true;
1556 + return unittest_rrdpush_compressions();
1557 + }
1558 else if(strncmp(optarg, createdataset_string, strlen(createdataset_string)) == 0) {
1559 optarg += strlen(createdataset_string);
1560 unsigned history_seconds = strtoul(optarg, NULL, 0);
@@ -1901,6 +1906,8 @@ int main(int argc, char **argv) {
1906 netdata_log_info("Netdata agent version \""VERSION"\" is starting");
1907
1908 ieee754_doubles = is_system_ieee754_double();
1909 + if(!ieee754_doubles)
1910 + globally_disabled_capabilities |= STREAM_CAP_IEEE754;
1911
1912 aral_judy_init();
1913
database/rrdhost.c
+4 -11
@@ -1145,13 +1145,10 @@ static void rrdhost_streaming_sender_structures_init(RRDHOST *host)
1145 host->sender->rrdpush_sender_pipe[PIPE_READ] = -1;
1146 host->sender->rrdpush_sender_pipe[PIPE_WRITE] = -1;
1147 host->sender->rrdpush_sender_socket = -1;
1148 + host->sender->disabled_capabilities = STREAM_CAP_NONE;
1149
1149 -#ifdef ENABLE_RRDPUSH_COMPRESSION
1150 - if(default_rrdpush_compression_enabled)
1151 - host->sender->flags |= SENDER_FLAG_COMPRESSION;
1152 - else
1153 - host->sender->flags &= ~SENDER_FLAG_COMPRESSION;
1154 -#endif
1150 + if(!default_rrdpush_compression_enabled)
1151 + host->sender->disabled_capabilities |= STREAM_CAP_COMPRESSIONS_AVAILABLE;
1152
1153 spinlock_init(&host->sender->spinlock);
1154 replication_init_sender(host->sender);
@@ -1167,9 +1164,7 @@ static void rrdhost_streaming_sender_structures_free(RRDHOST *host)
1164 rrdpush_sender_thread_stop(host, STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP, true); // stop a possibly running thread
1165 cbuffer_free(host->sender->buffer);
1166
1170 -#ifdef ENABLE_RRDPUSH_COMPRESSION
1167 rrdpush_compressor_destroy(&host->sender->compressor);
1172 -#endif
1168
1169 replication_cleanup_sender(host->sender);
1170
@@ -1885,9 +1880,7 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) {
1880 else
1881 s->stream.status = RRDHOST_STREAM_STATUS_ONLINE;
1882
1888 -#ifdef ENABLE_RRDPUSH_COMPRESSION
1889 - s->stream.compression = (stream_has_capability(host->sender, STREAM_CAP_COMPRESSION) && host->sender->compressor.initialized);
1890 -#endif
1883 + s->stream.compression = host->sender->compressor.initialized;
1884 }
1885 else {
1886 s->stream.status = RRDHOST_STREAM_STATUS_OFFLINE;
libnetdata/libnetdata.h
+4 -7
@@ -11,10 +11,6 @@ extern "C" {
11 #include <config.h>
12 #endif
13
14 -#ifdef ENABLE_LZ4
15 -#define ENABLE_RRDPUSH_COMPRESSION 1
16 -#endif
17 -
14 #ifdef ENABLE_OPENSSL
15 #define ENABLE_HTTPS 1
16 #endif
@@ -681,9 +677,10 @@ static inline BITMAPX *bitmapX_create(uint32_t bits) {
677 #define bitmap1024_get_bit(ptr, idx) bitmapX_get_bit((BITMAPX *)ptr, idx)
678 #define bitmap1024_set_bit(ptr, idx, value) bitmapX_set_bit((BITMAPX *)ptr, idx, value)
679
684 -
685 -#define COMPRESSION_MAX_MSG_SIZE 0x4000
686 -#define PLUGINSD_LINE_MAX (COMPRESSION_MAX_MSG_SIZE - 1024)
680 +#define COMPRESSION_MAX_CHUNK 0x4000
681 +#define COMPRESSION_MAX_OVERHEAD 128
682 +#define COMPRESSION_MAX_MSG_SIZE (COMPRESSION_MAX_CHUNK - COMPRESSION_MAX_OVERHEAD - 1)
683 +#define PLUGINSD_LINE_MAX (COMPRESSION_MAX_MSG_SIZE - 768)
684 int pluginsd_isspace(char c);
685 int config_isspace(char c);
686 int group_by_label_isspace(char c);
streaming/compression.c
+240 -135
@@ -1,181 +1,286 @@
1 -#include "rrdpush.h"
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#ifdef ENABLE_RRDPUSH_COMPRESSION
4 -#include "lz4.h"
3 +#include "compression.h"
4
6 -#define STREAM_COMPRESSION_MSG "STREAM_COMPRESSION"
5 +#include "compression_gzip.h"
6
8 -/*
9 - * Reset compressor state for a new stream
10 - */
11 -void rrdpush_compressor_reset(struct compressor_state *state) {
12 - if(!state->initialized) {
13 - state->initialized = true;
7 +#ifdef ENABLE_LZ4
8 +#include "compression_lz4.h"
9 +#endif
10
15 - state->stream.lz4_stream = LZ4_createStream();
16 - state->stream.input_ring_buffer_size = LZ4_DECODER_RING_BUFFER_SIZE(COMPRESSION_MAX_MSG_SIZE * 2);
17 - state->stream.input_ring_buffer = callocz(1, state->stream.input_ring_buffer_size);
18 - state->compression_result_buffer_size = 0;
19 - }
11 +#ifdef ENABLE_ZSTD
12 +#include "compression_zstd.h"
13 +#endif
14 +
15 +// ----------------------------------------------------------------------------
16 +// compressor public API
17
21 - LZ4_resetStream_fast(state->stream.lz4_stream);
18 +void rrdpush_compressor_init(struct compressor_state *state) {
19 + switch(state->algorithm) {
20 +#ifdef ENABLE_ZSTD
21 + case COMPRESSION_ALGORITHM_ZSTD:
22 + rrdpush_compressor_init_zstd(state);
23 + break;
24 +#endif
25 +
26 +#ifdef ENABLE_LZ4
27 + case COMPRESSION_ALGORITHM_LZ4:
28 + rrdpush_compressor_init_lz4(state);
29 + break;
30 +#endif
31
23 - state->stream.input_ring_buffer_pos = 0;
32 + default:
33 + case COMPRESSION_ALGORITHM_GZIP:
34 + rrdpush_compressor_init_gzip(state);
35 + break;
36 + }
37 +
38 + simple_ring_buffer_reset(&state->input);
39 + simple_ring_buffer_reset(&state->output);
40 }
41
26 -/*
27 - * Destroy compressor state and all related data
28 - */
42 void rrdpush_compressor_destroy(struct compressor_state *state) {
30 - if (state->stream.lz4_stream) {
31 - LZ4_freeStream(state->stream.lz4_stream);
32 - state->stream.lz4_stream = NULL;
33 - }
43 + switch(state->algorithm) {
44 +#ifdef ENABLE_ZSTD
45 + case COMPRESSION_ALGORITHM_ZSTD:
46 + rrdpush_compressor_destroy_zstd(state);
47 + break;
48 +#endif
49
35 - freez(state->stream.input_ring_buffer);
36 - state->stream.input_ring_buffer = NULL;
50 +#ifdef ENABLE_LZ4
51 + case COMPRESSION_ALGORITHM_LZ4:
52 + rrdpush_compressor_destroy_lz4(state);
53 + break;
54 +#endif
55
38 - freez(state->compression_result_buffer);
39 - state->compression_result_buffer = NULL;
56 + default:
57 + case COMPRESSION_ALGORITHM_GZIP:
58 + rrdpush_compressor_destroy_gzip(state);
59 + break;
60 + }
61
62 state->initialized = false;
63 +
64 + simple_ring_buffer_destroy(&state->input);
65 + simple_ring_buffer_destroy(&state->output);
66 }
67
44 -/*
45 - * Compress the given block of data
46 - * Compressed data will remain in the internal buffer until the next invocation
47 - * Return the size of compressed data block as result and the pointer to internal buffer using the last argument
48 - * or 0 in case of error
49 - */
50 -size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, char **out) {
51 - if(unlikely(!state || !size || !out))
52 - return 0;
68 +size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, const char **out) {
69 + size_t ret = 0;
70
54 - if(unlikely(size > COMPRESSION_MAX_MSG_SIZE)) {
55 - netdata_log_error("RRDPUSH COMPRESS: Compression Failed - Message size %lu above compression buffer limit: %d",
56 - (long unsigned int)size, COMPRESSION_MAX_MSG_SIZE);
57 - return 0;
58 - }
71 + switch(state->algorithm) {
72 +#ifdef ENABLE_ZSTD
73 + case COMPRESSION_ALGORITHM_ZSTD:
74 + ret = rrdpush_compress_zstd(state, data, size, out);
75 + break;
76 +#endif
77
60 - size_t max_dst_size = LZ4_COMPRESSBOUND(size);
61 - size_t data_size = max_dst_size + RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
78 +#ifdef ENABLE_LZ4
79 + case COMPRESSION_ALGORITHM_LZ4:
80 + ret = rrdpush_compress_lz4(state, data, size, out);
81 + break;
82 +#endif
83
63 - if (!state->compression_result_buffer) {
64 - state->compression_result_buffer = mallocz(data_size);
65 - state->compression_result_buffer_size = data_size;
66 - }
67 - else if(unlikely(state->compression_result_buffer_size < data_size)) {
68 - state->compression_result_buffer = reallocz(state->compression_result_buffer, data_size);
69 - state->compression_result_buffer_size = data_size;
84 + default:
85 + case COMPRESSION_ALGORITHM_GZIP:
86 + ret = rrdpush_compress_gzip(state, data, size, out);
87 + break;
88 }
89
72 - // the ring buffer always has space for LZ4_MAX_MSG_SIZE
73 - memcpy(state->stream.input_ring_buffer + state->stream.input_ring_buffer_pos, data, size);
74 -
75 - // this call needs the last 64K of our previous data
76 - // they are available in the ring buffer
77 - long int compressed_data_size = LZ4_compress_fast_continue(
78 - state->stream.lz4_stream,
79 - state->stream.input_ring_buffer + state->stream.input_ring_buffer_pos,
80 - state->compression_result_buffer + RRDPUSH_COMPRESSION_SIGNATURE_SIZE,
81 - (int)size,
82 - (int)max_dst_size,
83 - 1);
84 -
85 - if (compressed_data_size < 0) {
86 - netdata_log_error("Data compression error: %ld", compressed_data_size);
90 + if(unlikely(ret >= COMPRESSION_MAX_CHUNK)) {
91 + netdata_log_error("RRDPUSH_COMPRESS: compressed data is %zu bytes, which is >= than the max chunk size %zu",
92 + ret, COMPRESSION_MAX_CHUNK);
93 return 0;
94 }
95
90 - // update the next writing position of the ring buffer
91 - state->stream.input_ring_buffer_pos += size;
92 - if(unlikely(state->stream.input_ring_buffer_pos >= state->stream.input_ring_buffer_size - COMPRESSION_MAX_MSG_SIZE))
93 - state->stream.input_ring_buffer_pos = 0;
94 -
95 - // update the signature header
96 - uint32_t len = ((compressed_data_size & 0x7f) | 0x80 | (((compressed_data_size & (0x7f << 7)) << 1) | 0x8000)) << 8;
97 - *(uint32_t *)state->compression_result_buffer = len | RRDPUSH_COMPRESSION_SIGNATURE;
98 - *out = state->compression_result_buffer;
99 - netdata_log_debug(D_STREAM, "%s: Compressed data header: %ld", STREAM_COMPRESSION_MSG, compressed_data_size);
100 - return compressed_data_size + RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
96 + return ret;
97 }
98
103 -/*
104 - * Decompress the compressed data in the internal buffer
105 - * Return the size of uncompressed data or 0 for error
106 - */
107 -size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) {
108 - if (unlikely(!state || !compressed_data || !compressed_size))
109 - return 0;
99 +// ----------------------------------------------------------------------------
100 +// decompressor public API
101
111 - if(unlikely(state->stream.read_at != state->stream.write_at))
112 - fatal("RRDPUSH_DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!");
102 +void rrdpush_decompressor_destroy(struct decompressor_state *state) {
103 + if(unlikely(!state->initialized))
104 + return;
105
114 - if (unlikely(state->stream.write_at >= state->stream.size / 2)) {
115 - state->stream.write_at = 0;
116 - state->stream.read_at = 0;
117 - }
106 + switch(state->algorithm) {
107 +#ifdef ENABLE_ZSTD
108 + case COMPRESSION_ALGORITHM_ZSTD:
109 + rrdpush_decompressor_destroy_zstd(state);
110 + break;
111 +#endif
112
119 - long int decompressed_size = LZ4_decompress_safe_continue(
120 - state->stream.lz4_stream
121 - , compressed_data
122 - , state->stream.buffer + state->stream.write_at
123 - , (int)compressed_size
124 - , (int)(state->stream.size - state->stream.write_at)
125 - );
113 +#ifdef ENABLE_LZ4
114 + case COMPRESSION_ALGORITHM_LZ4:
115 + rrdpush_decompressor_destroy_lz4(state);
116 + break;
117 +#endif
118
127 - if (unlikely(decompressed_size < 0)) {
128 - netdata_log_error("RRDPUSH DECOMPRESS: decompressor returned negative decompressed bytes: %ld", decompressed_size);
129 - return 0;
119 + default:
120 + case COMPRESSION_ALGORITHM_GZIP:
121 + rrdpush_decompressor_destroy_gzip(state);
122 + break;
123 }
124
132 - if(unlikely(decompressed_size + state->stream.write_at > state->stream.size))
133 - fatal("RRDPUSH DECOMPRESS: decompressor overflown the stream_buffer. size: %zu, pos: %zu, added: %ld, "
134 - "exceeding the buffer by %zu"
135 - , state->stream.size
136 - , state->stream.write_at
137 - , decompressed_size
138 - , (size_t)(state->stream.write_at + decompressed_size - state->stream.size)
139 - );
125 + simple_ring_buffer_destroy(&state->output);
126
141 - state->stream.write_at += decompressed_size;
127 + state->initialized = false;
128 +}
129
143 - // statistics
144 - state->total_compressed += compressed_size + RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
145 - state->total_uncompressed += decompressed_size;
146 - state->packet_count++;
130 +void rrdpush_decompressor_init(struct decompressor_state *state) {
131 + switch(state->algorithm) {
132 +#ifdef ENABLE_ZSTD
133 + case COMPRESSION_ALGORITHM_ZSTD:
134 + rrdpush_decompressor_init_zstd(state);
135 + break;
136 +#endif
137
148 - return decompressed_size;
149 -}
138 +#ifdef ENABLE_LZ4
139 + case COMPRESSION_ALGORITHM_LZ4:
140 + rrdpush_decompressor_init_lz4(state);
141 + break;
142 +#endif
143
151 -void rrdpush_decompressor_reset(struct decompressor_state *state) {
152 - if(!state->initialized) {
153 - state->initialized = true;
154 - state->stream.lz4_stream = LZ4_createStreamDecode();
155 - state->stream.size = LZ4_decoderRingBufferSize(COMPRESSION_MAX_MSG_SIZE) * 2;
156 - state->stream.buffer = mallocz(state->stream.size);
144 + default:
145 + case COMPRESSION_ALGORITHM_GZIP:
146 + rrdpush_decompressor_init_gzip(state);
147 + break;
148 }
149
159 - LZ4_setStreamDecode(state->stream.lz4_stream, NULL, 0);
160 -
150 state->signature_size = RRDPUSH_COMPRESSION_SIGNATURE_SIZE;
162 - state->stream.write_at = 0;
163 - state->stream.read_at = 0;
151 + simple_ring_buffer_reset(&state->output);
152 }
153
166 -void rrdpush_decompressor_destroy(struct decompressor_state *state) {
167 - if(unlikely(!state->initialized))
168 - return;
154 +size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) {
155 + if (unlikely(state->output.read_pos != state->output.write_pos))
156 + fatal("RRDPUSH_DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!");
157 +
158 + size_t ret = 0;
159
170 - if (state->stream.lz4_stream) {
171 - LZ4_freeStreamDecode(state->stream.lz4_stream);
172 - state->stream.lz4_stream = NULL;
160 + switch(state->algorithm) {
161 +#ifdef ENABLE_ZSTD
162 + case COMPRESSION_ALGORITHM_ZSTD:
163 + ret = rrdpush_decompress_zstd(state, compressed_data, compressed_size);
164 + break;
165 +#endif
166 +
167 +#ifdef ENABLE_LZ4
168 + case COMPRESSION_ALGORITHM_LZ4:
169 + ret = rrdpush_decompress_lz4(state, compressed_data, compressed_size);
170 + break;
171 +#endif
172 +
173 + default:
174 + case COMPRESSION_ALGORITHM_GZIP:
175 + ret = rrdpush_decompress_gzip(state, compressed_data, compressed_size);
176 + break;
177 }
178
175 - freez(state->stream.buffer);
176 - state->stream.buffer = NULL;
179 + // for backwards compatibility we cannot check for COMPRESSION_MAX_MSG_SIZE,
180 + // because old children may send this big payloads.
181 + if(unlikely(ret > COMPRESSION_MAX_CHUNK)) {
182 + netdata_log_error("RRDPUSH_DECOMPRESS: decompressed data is %zu bytes, which is bigger than the max msg size %zu",
183 + ret, COMPRESSION_MAX_CHUNK);
184 + return 0;
185 + }
186
178 - state->initialized = false;
187 + return ret;
188 }
189
181 -#endif
190 +// ----------------------------------------------------------------------------
191 +// unit test
192 +
193 +int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char *name) {
194 + fprintf(stderr, "\nTesting streaming compression with %s\n", name);
195 +
196 + struct compressor_state cctx = {
197 + .initialized = false,
198 + .algorithm = algorithm,
199 + };
200 + struct decompressor_state dctx = {
201 + .initialized = false,
202 + .algorithm = algorithm,
203 + };
204 +
205 + char txt[COMPRESSION_MAX_MSG_SIZE];
206 +
207 + rrdpush_compressor_init(&cctx);
208 + rrdpush_decompressor_init(&dctx);
209 +
210 + int errors = 0;
211 +
212 + memset(txt, '=', COMPRESSION_MAX_MSG_SIZE);
213 +
214 + for(int i = 0; i < COMPRESSION_MAX_MSG_SIZE ;i++) {
215 + txt[i] = 'A' + (i % 26);
216 + size_t txt_len = i + 1;
217 +
218 + const char *out;
219 + size_t size = rrdpush_compress(&cctx, txt, txt_len, &out);
220 +
221 + if(size >= COMPRESSION_MAX_CHUNK) {
222 + fprintf(stderr, "iteration %d: compressed size %zu exceeds max allowed size\n",
223 + i, size);
224 + errors++;
225 + goto cleanup;
226 + }
227 + else {
228 + size_t dtxt_len = rrdpush_decompress(&dctx, out, size);
229 + char *dtxt = (char *) &dctx.output.data[dctx.output.read_pos];
230 +
231 + if(rrdpush_decompressed_bytes_in_buffer(&dctx) != dtxt_len) {
232 + fprintf(stderr, "iteration %d: decompressed size %zu does not rrdpush_decompressed_bytes_in_buffer() %zu\n",
233 + i, dtxt_len, rrdpush_decompressed_bytes_in_buffer(&dctx)
234 + );
235 + errors++;
236 + goto cleanup;
237 + }
238 +
239 + if(dtxt_len != txt_len) {
240 + fprintf(stderr, "iteration %d: decompressed size %zu does not match original size %zu\n",
241 + i, dtxt_len, txt_len
242 + );
243 + errors++;
244 + goto cleanup;
245 + }
246 + else {
247 + if(memcmp(txt, dtxt, txt_len) != 0) {
248 + txt[txt_len] = '\0';
249 + dtxt[txt_len + 5] = '\0';
250 +
251 + fprintf(stderr, "iteration %d: decompressed data '%s' do not match original data '%s' of length %zu\n",
252 + i, dtxt, txt, txt_len);
253 + errors++;
254 + goto cleanup;
255 + }
256 + }
257 + }
258 +
259 + // fill the compressed buffer with garbage
260 + memset((void *)out, 'x', size);
261 +
262 + // here we are supposed to copy the data and advance the position
263 + dctx.output.read_pos += rrdpush_decompressed_bytes_in_buffer(&dctx);
264 + }
265 +
266 +cleanup:
267 + rrdpush_compressor_destroy(&cctx);
268 + rrdpush_decompressor_destroy(&dctx);
269 +
270 + if(errors)
271 + fprintf(stderr, "Compression with %s: FAILED (%d errors)\n", name, errors);
272 + else
273 + fprintf(stderr, "Compression with %s: OK\n", name);
274 +
275 + return errors;
276 +}
277 +
278 +int unittest_rrdpush_compressions(void) {
279 + int ret = 0;
280 +
281 + ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_GZIP, "GZIP");
282 + ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_LZ4, "LZ4");
283 + ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_ZSTD, "ZSTD");
284 +
285 + return ret;
286 +}
streaming/compression.h new
+171
@@ -0,0 +1,171 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "rrdpush.h"
4 +
5 +#ifndef NETDATA_RRDPUSH_COMPRESSION_H
6 +#define NETDATA_RRDPUSH_COMPRESSION_H 1
7 +
8 +// signature MUST end with a newline
9 +
10 +#if COMPRESSION_MAX_MSG_SIZE >= (COMPRESSION_MAX_CHUNK - COMPRESSION_MAX_OVERHEAD)
11 +#error "COMPRESSION_MAX_MSG_SIZE >= (COMPRESSION_MAX_CHUNK - COMPRESSION_MAX_OVERHEAD)"
12 +#endif
13 +
14 +typedef uint32_t rrdpush_signature_t;
15 +#define RRDPUSH_COMPRESSION_SIGNATURE ((rrdpush_signature_t)('z' | 0x80) | (0x80 << 8) | (0x80 << 16) | ('\n' << 24))
16 +#define RRDPUSH_COMPRESSION_SIGNATURE_MASK ((rrdpush_signature_t)0xff | (0x80 << 8) | (0x80 << 16) | (0xff << 24))
17 +#define RRDPUSH_COMPRESSION_SIGNATURE_SIZE sizeof(rrdpush_signature_t)
18 +
19 +static inline rrdpush_signature_t rrdpush_compress_encode_signature(size_t compressed_data_size) {
20 + rrdpush_signature_t len = ((compressed_data_size & 0x7f) | 0x80 | (((compressed_data_size & (0x7f << 7)) << 1) | 0x8000)) << 8;
21 + return len | RRDPUSH_COMPRESSION_SIGNATURE;
22 +}
23 +
24 +typedef enum {
25 + COMPRESSION_ALGORITHM_NONE = 0,
26 + COMPRESSION_ALGORITHM_ZSTD,
27 + COMPRESSION_ALGORITHM_LZ4,
28 + COMPRESSION_ALGORITHM_GZIP,
29 +
30 + // terminator
31 + COMPRESSION_ALGORITHM_MAX,
32 +} compression_algorithm_t;
33 +
34 +extern int rrdpush_compression_levels[COMPRESSION_ALGORITHM_MAX];
35 +
36 +// ----------------------------------------------------------------------------
37 +
38 +typedef struct simple_ring_buffer {
39 + const char *data;
40 + size_t size;
41 + size_t read_pos;
42 + size_t write_pos;
43 +} SIMPLE_RING_BUFFER;
44 +
45 +static inline void simple_ring_buffer_reset(SIMPLE_RING_BUFFER *b) {
46 + b->read_pos = b->write_pos = 0;
47 +}
48 +
49 +static inline void simple_ring_buffer_make_room(SIMPLE_RING_BUFFER *b, size_t size) {
50 + if(b->write_pos + size > b->size) {
51 + if(!b->size)
52 + b->size = COMPRESSION_MAX_CHUNK;
53 + else
54 + b->size *= 2;
55 +
56 + if(b->write_pos + size > b->size)
57 + b->size += size;
58 +
59 + b->data = (const char *)reallocz((void *)b->data, b->size);
60 + }
61 +}
62 +
63 +static inline void simple_ring_buffer_append_data(SIMPLE_RING_BUFFER *b, const void *data, size_t size) {
64 + simple_ring_buffer_make_room(b, size);
65 + memcpy((void *)(b->data + b->write_pos), data, size);
66 + b->write_pos += size;
67 +}
68 +
69 +static inline void simple_ring_buffer_destroy(SIMPLE_RING_BUFFER *b) {
70 + freez((void *)b->data);
71 + b->data = NULL;
72 + b->read_pos = b->write_pos = b->size = 0;
73 +}
74 +
75 +// ----------------------------------------------------------------------------
76 +
77 +struct compressor_state {
78 + bool initialized;
79 + compression_algorithm_t algorithm;
80 +
81 + SIMPLE_RING_BUFFER input;
82 + SIMPLE_RING_BUFFER output;
83 +
84 + int level;
85 + void *stream;
86 +
87 + struct {
88 + size_t total_compressed;
89 + size_t total_uncompressed;
90 + size_t total_compressions;
91 + } sender_locked;
92 +};
93 +
94 +void rrdpush_compressor_init(struct compressor_state *state);
95 +void rrdpush_compressor_destroy(struct compressor_state *state);
96 +size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, const char **out);
97 +
98 +// ----------------------------------------------------------------------------
99 +
100 +struct decompressor_state {
101 + bool initialized;
102 + compression_algorithm_t algorithm;
103 + size_t signature_size;
104 +
105 + size_t total_compressed;
106 + size_t total_uncompressed;
107 + size_t total_compressions;
108 +
109 + SIMPLE_RING_BUFFER output;
110 +
111 + void *stream;
112 +};
113 +
114 +void rrdpush_decompressor_destroy(struct decompressor_state *state);
115 +void rrdpush_decompressor_init(struct decompressor_state *state);
116 +size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size);
117 +
118 +static inline size_t rrdpush_decompress_decode_signature(const char *data, size_t data_size) {
119 + if (unlikely(!data || !data_size))
120 + return 0;
121 +
122 + if (unlikely(data_size != RRDPUSH_COMPRESSION_SIGNATURE_SIZE))
123 + return 0;
124 +
125 + rrdpush_signature_t sign = *(rrdpush_signature_t *)data;
126 + if (unlikely((sign & RRDPUSH_COMPRESSION_SIGNATURE_MASK) != RRDPUSH_COMPRESSION_SIGNATURE))
127 + return 0;
128 +
129 + size_t length = ((sign >> 8) & 0x7f) | ((sign >> 9) & (0x7f << 7));
130 + return length;
131 +}
132 +
133 +static inline size_t rrdpush_decompressor_start(struct decompressor_state *state, const char *header, size_t header_size) {
134 + if(unlikely(state->output.read_pos != state->output.write_pos))
135 + fatal("RRDPUSH DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!");
136 +
137 + return rrdpush_decompress_decode_signature(header, header_size);
138 +}
139 +
140 +static inline size_t rrdpush_decompressed_bytes_in_buffer(struct decompressor_state *state) {
141 + if(unlikely(state->output.read_pos > state->output.write_pos))
142 + fatal("RRDPUSH DECOMPRESS: invalid read/write stream positions");
143 +
144 + return state->output.write_pos - state->output.read_pos;
145 +}
146 +
147 +static inline size_t rrdpush_decompressor_get(struct decompressor_state *state, char *dst, size_t size) {
148 + if (unlikely(!state || !size || !dst))
149 + return 0;
150 +
151 + size_t remaining = rrdpush_decompressed_bytes_in_buffer(state);
152 +
153 + if(unlikely(!remaining))
154 + return 0;
155 +
156 + size_t bytes_to_return = size;
157 + if(bytes_to_return > remaining)
158 + bytes_to_return = remaining;
159 +
160 + memcpy(dst, state->output.data + state->output.read_pos, bytes_to_return);
161 + state->output.read_pos += bytes_to_return;
162 +
163 + if(unlikely(state->output.read_pos > state->output.write_pos))
164 + fatal("RRDPUSH DECOMPRESS: invalid read/write stream positions");
165 +
166 + return bytes_to_return;
167 +}
168 +
169 +// ----------------------------------------------------------------------------
170 +
171 +#endif // NETDATA_RRDPUSH_COMPRESSION_H 1
streaming/compression_gzip.c new
+158
@@ -0,0 +1,158 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "compression_gzip.h"
4 +#include <zlib.h>
5 +
6 +void rrdpush_compressor_init_gzip(struct compressor_state *state) {
7 + if (!state->initialized) {
8 + state->initialized = true;
9 +
10 + // Initialize deflate stream
11 + z_stream *strm = state->stream = (z_stream *) mallocz(sizeof(z_stream));
12 + strm->zalloc = Z_NULL;
13 + strm->zfree = Z_NULL;
14 + strm->opaque = Z_NULL;
15 +
16 + if(state->level < Z_BEST_SPEED)
17 + state->level = Z_BEST_SPEED;
18 +
19 + if(state->level > Z_BEST_COMPRESSION)
20 + state->level = Z_BEST_COMPRESSION;
21 +
22 + // int r = deflateInit2(strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
23 + int r = deflateInit2(strm, state->level, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
24 + if (r != Z_OK) {
25 + netdata_log_error("Failed to initialize deflate with error: %d", r);
26 + freez(state->stream);
27 + state->initialized = false;
28 + return;
29 + }
30 +
31 + }
32 +}
33 +
34 +void rrdpush_compressor_destroy_gzip(struct compressor_state *state) {
35 + if (state->stream) {
36 + deflateEnd(state->stream);
37 + free(state->stream);
38 + state->stream = NULL;
39 + }
40 +}
41 +
42 +size_t rrdpush_compress_gzip(struct compressor_state *state, const char *data, size_t size, const char **out) {
43 + if (unlikely(!state || !size || !out))
44 + return 0;
45 +
46 + simple_ring_buffer_make_room(&state->output, deflateBound(state->stream, size));
47 +
48 + z_stream *strm = state->stream;
49 + strm->avail_in = (uInt)size;
50 + strm->next_in = (Bytef *)data;
51 + strm->avail_out = (uInt)state->output.size;
52 + strm->next_out = (Bytef *)state->output.data;
53 +
54 + int ret = deflate(strm, Z_SYNC_FLUSH);
55 + if (ret != Z_OK && ret != Z_STREAM_END) {
56 + netdata_log_error("STREAM: deflate() failed with error %d", ret);
57 + return 0;
58 + }
59 +
60 + if(strm->avail_in != 0) {
61 + netdata_log_error("STREAM: deflate() did not use all the input buffer, %u bytes out of %zu remain",
62 + strm->avail_in, size);
63 + return 0;
64 + }
65 +
66 + if(strm->avail_out == 0) {
67 + netdata_log_error("STREAM: deflate() needs a bigger output buffer than the one we provided "
68 + "(output buffer %zu bytes, compressed payload %zu bytes)",
69 + state->output.size, size);
70 + return 0;
71 + }
72 +
73 + size_t compressed_data_size = state->output.size - strm->avail_out;
74 +
75 + if(compressed_data_size == 0) {
76 + netdata_log_error("STREAM: deflate() did not produce any output "
77 + "(output buffer %zu bytes, compressed payload %zu bytes)",
78 + state->output.size, size);
79 + return 0;
80 + }
81 +
82 + state->sender_locked.total_compressions++;
83 + state->sender_locked.total_uncompressed += size;
84 + state->sender_locked.total_compressed += compressed_data_size;
85 +
86 + *out = state->output.data;
87 + return compressed_data_size;
88 +}
89 +
90 +void rrdpush_decompressor_init_gzip(struct decompressor_state *state) {
91 + if (!state->initialized) {
92 + state->initialized = true;
93 +
94 + // Initialize inflate stream
95 + z_stream *strm = state->stream = (z_stream *) malloc(sizeof(z_stream));
96 + strm->zalloc = Z_NULL;
97 + strm->zfree = Z_NULL;
98 + strm->opaque = Z_NULL;
99 +
100 + inflateInit2(strm, 15 + 16);
101 +
102 + simple_ring_buffer_make_room(&state->output, COMPRESSION_MAX_CHUNK);
103 + }
104 +}
105 +
106 +void rrdpush_decompressor_destroy_gzip(struct decompressor_state *state) {
107 + if (state->stream) {
108 + inflateEnd(state->stream);
109 + free(state->stream);
110 + state->stream = NULL;
111 + }
112 +}
113 +
114 +size_t rrdpush_decompress_gzip(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) {
115 + if (unlikely(!state || !compressed_data || !compressed_size))
116 + return 0;
117 +
118 + // The state.output ring buffer is always EMPTY at this point,
119 + // meaning that (state->output.read_pos == state->output.write_pos)
120 + // However, THEY ARE NOT ZERO.
121 +
122 + z_stream *strm = state->stream;
123 + strm->avail_in = (uInt)compressed_size;
124 + strm->next_in = (Bytef *)compressed_data;
125 + strm->avail_out = (uInt)state->output.size;
126 + strm->next_out = (Bytef *)state->output.data;
127 +
128 + int ret = inflate(strm, Z_SYNC_FLUSH);
129 + if (ret != Z_STREAM_END && ret != Z_OK) {
130 + netdata_log_error("RRDPUSH DECOMPRESS: inflate() failed with error %d", ret);
131 + return 0;
132 + }
133 +
134 + if(strm->avail_in != 0) {
135 + netdata_log_error("RRDPUSH DECOMPRESS: inflate() did not use all compressed data we provided "
136 + "(compressed payload %zu bytes, remaining to be uncompressed %u)"
137 + , compressed_size, strm->avail_in);
138 + return 0;
139 + }
140 +
141 + if(strm->avail_out == 0) {
142 + netdata_log_error("RRDPUSH DECOMPRESS: inflate() needs a bigger output buffer than the one we provided "
143 + "(compressed payload %zu bytes, output buffer size %zu bytes)"
144 + , compressed_size, state->output.size);
145 + return 0;
146 + }
147 +
148 + size_t decompressed_size = state->output.size - strm->avail_out;
149 +
150 + state->output.read_pos = 0;
151 + state->output.write_pos = decompressed_size;
152 +
153 + state->total_compressed += compressed_size;
154 + state->total_uncompressed += decompressed_size;
155 + state->total_compressions++;
156 +
157 + return decompressed_size;
158 +}
streaming/compression_gzip.h new
+15
@@ -0,0 +1,15 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "compression.h"
4 +
5 +#ifndef NETDATA_STREAMING_COMPRESSION_GZIP_H
6 +#define NETDATA_STREAMING_COMPRESSION_GZIP_H
7 +
8 +void rrdpush_compressor_init_gzip(struct compressor_state *state);
9 +void rrdpush_compressor_destroy_gzip(struct compressor_state *state);
10 +size_t rrdpush_compress_gzip(struct compressor_state *state, const char *data, size_t size, const char **out);
11 +size_t rrdpush_decompress_gzip(struct decompressor_state *state, const char *compressed_data, size_t compressed_size);
12 +void rrdpush_decompressor_init_gzip(struct decompressor_state *state);
13 +void rrdpush_decompressor_destroy_gzip(struct decompressor_state *state);
14 +
15 +#endif //NETDATA_STREAMING_COMPRESSION_GZIP_H
streaming/compression_lz4.c new
+143
@@ -0,0 +1,143 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "compression_lz4.h"
4 +
5 +#ifdef ENABLE_LZ4
6 +#include "lz4.h"
7 +
8 +// ----------------------------------------------------------------------------
9 +// compress
10 +
11 +void rrdpush_compressor_init_lz4(struct compressor_state *state) {
12 + if(!state->initialized) {
13 + state->initialized = true;
14 + state->stream = LZ4_createStream();
15 +
16 + // LZ4 needs access to the last 64KB of source data
17 + // so, we keep twice the size of each message
18 + simple_ring_buffer_make_room(&state->input, 65536 + COMPRESSION_MAX_CHUNK * 2);
19 + }
20 +}
21 +
22 +void rrdpush_compressor_destroy_lz4(struct compressor_state *state) {
23 + if (state->stream) {
24 + LZ4_freeStream(state->stream);
25 + state->stream = NULL;
26 + }
27 +}
28 +
29 +/*
30 + * Compress the given block of data
31 + * Compressed data will remain in the internal buffer until the next invocation
32 + * Return the size of compressed data block as result and the pointer to internal buffer using the last argument
33 + * or 0 in case of error
34 + */
35 +size_t rrdpush_compress_lz4(struct compressor_state *state, const char *data, size_t size, const char **out) {
36 + if(unlikely(!state || !size || !out))
37 + return 0;
38 +
39 + // we need to keep the last 64K of our previous source data
40 + // as they were in the ring buffer
41 +
42 + simple_ring_buffer_make_room(&state->output, LZ4_COMPRESSBOUND(size));
43 +
44 + if(state->input.write_pos + size > state->input.size)
45 + // the input buffer cannot fit out data, restart from zero
46 + simple_ring_buffer_reset(&state->input);
47 +
48 + simple_ring_buffer_append_data(&state->input, data, size);
49 +
50 + long int compressed_data_size = LZ4_compress_fast_continue(
51 + state->stream,
52 + state->input.data + state->input.read_pos,
53 + (char *)state->output.data,
54 + (int)(state->input.write_pos - state->input.read_pos),
55 + (int)state->output.size,
56 + state->level);
57 +
58 + if (compressed_data_size <= 0) {
59 + netdata_log_error("STREAM: LZ4_compress_fast_continue() returned %ld "
60 + "(source is %zu bytes, output buffer can fit %zu bytes)",
61 + compressed_data_size, size, state->output.size);
62 + return 0;
63 + }
64 +
65 + state->input.read_pos = state->input.write_pos;
66 +
67 + state->sender_locked.total_compressions++;
68 + state->sender_locked.total_uncompressed += size;
69 + state->sender_locked.total_compressed += compressed_data_size;
70 +
71 + *out = state->output.data;
72 + return compressed_data_size;
73 +}
74 +
75 +// ----------------------------------------------------------------------------
76 +// decompress
77 +
78 +void rrdpush_decompressor_init_lz4(struct decompressor_state *state) {
79 + if(!state->initialized) {
80 + state->initialized = true;
81 + state->stream = LZ4_createStreamDecode();
82 + simple_ring_buffer_make_room(&state->output, 65536 + COMPRESSION_MAX_CHUNK * 2);
83 + }
84 +}
85 +
86 +void rrdpush_decompressor_destroy_lz4(struct decompressor_state *state) {
87 + if (state->stream) {
88 + LZ4_freeStreamDecode(state->stream);
89 + state->stream = NULL;
90 + }
91 +}
92 +
93 +/*
94 + * Decompress the compressed data in the internal buffer
95 + * Return the size of uncompressed data or 0 for error
96 + */
97 +size_t rrdpush_decompress_lz4(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) {
98 + if (unlikely(!state || !compressed_data || !compressed_size))
99 + return 0;
100 +
101 + // The state.output ring buffer is always EMPTY at this point,
102 + // meaning that (state->output.read_pos == state->output.write_pos)
103 + // However, THEY ARE NOT ZERO.
104 +
105 + if (unlikely(state->output.write_pos + COMPRESSION_MAX_CHUNK > state->output.size))
106 + // the input buffer cannot fit out data, restart from zero
107 + simple_ring_buffer_reset(&state->output);
108 +
109 + long int decompressed_size = LZ4_decompress_safe_continue(
110 + state->stream
111 + , compressed_data
112 + , (char *)(state->output.data + state->output.write_pos)
113 + , (int)compressed_size
114 + , (int)(state->output.size - state->output.write_pos)
115 + );
116 +
117 + if (unlikely(decompressed_size < 0)) {
118 + netdata_log_error("RRDPUSH DECOMPRESS: LZ4_decompress_safe_continue() returned negative value: %ld "
119 + "(compressed chunk is %zu bytes)"
120 + , decompressed_size, compressed_size);
121 + return 0;
122 + }
123 +
124 + if(unlikely(decompressed_size + state->output.write_pos > state->output.size))
125 + fatal("RRDPUSH DECOMPRESS: LZ4_decompress_safe_continue() overflown the stream_buffer "
126 + "(size: %zu, pos: %zu, added: %ld, exceeding the buffer by %zu)"
127 + , state->output.size
128 + , state->output.write_pos
129 + , decompressed_size
130 + , (size_t)(state->output.write_pos + decompressed_size - state->output.size)
131 + );
132 +
133 + state->output.write_pos += decompressed_size;
134 +
135 + // statistics
136 + state->total_compressed += compressed_size;
137 + state->total_uncompressed += decompressed_size;
138 + state->total_compressions++;
139 +
140 + return decompressed_size;
141 +}
142 +
143 +#endif // ENABLE_LZ4
streaming/compression_lz4.h new
+19
@@ -0,0 +1,19 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "compression.h"
4 +
5 +#ifndef NETDATA_STREAMING_COMPRESSION_LZ4_H
6 +#define NETDATA_STREAMING_COMPRESSION_LZ4_H
7 +
8 +#ifdef ENABLE_LZ4
9 +
10 +void rrdpush_compressor_init_lz4(struct compressor_state *state);
11 +void rrdpush_compressor_destroy_lz4(struct compressor_state *state);
12 +size_t rrdpush_compress_lz4(struct compressor_state *state, const char *data, size_t size, const char **out);
13 +size_t rrdpush_decompress_lz4(struct decompressor_state *state, const char *compressed_data, size_t compressed_size);
14 +void rrdpush_decompressor_init_lz4(struct decompressor_state *state);
15 +void rrdpush_decompressor_destroy_lz4(struct decompressor_state *state);
16 +
17 +#endif // ENABLE_LZ4
18 +
19 +#endif //NETDATA_STREAMING_COMPRESSION_LZ4_H
streaming/compression_zstd.c new
+163
@@ -0,0 +1,163 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "compression_zstd.h"
4 +
5 +#ifdef ENABLE_ZSTD
6 +#include <zstd.h>
7 +
8 +void rrdpush_compressor_init_zstd(struct compressor_state *state) {
9 + if(!state->initialized) {
10 + state->initialized = true;
11 + state->stream = ZSTD_createCStream();
12 +
13 + if(state->level < 1)
14 + state->level = 1;
15 +
16 + if(state->level > ZSTD_maxCLevel())
17 + state->level = ZSTD_maxCLevel();
18 +
19 + size_t ret = ZSTD_initCStream(state->stream, state->level);
20 + if(ZSTD_isError(ret))
21 + netdata_log_error("STREAM: ZSTD_initCStream() returned error: %s", ZSTD_getErrorName(ret));
22 +
23 + // ZSTD_CCtx_setParameter(state->stream, ZSTD_c_compressionLevel, 1);
24 + // ZSTD_CCtx_setParameter(state->stream, ZSTD_c_strategy, ZSTD_fast);
25 + }
26 +}
27 +
28 +void rrdpush_compressor_destroy_zstd(struct compressor_state *state) {
29 + if(state->stream) {
30 + ZSTD_freeCStream(state->stream);
31 + state->stream = NULL;
32 + }
33 +}
34 +
35 +size_t rrdpush_compress_zstd(struct compressor_state *state, const char *data, size_t size, const char **out) {
36 + if(unlikely(!state || !size || !out))
37 + return 0;
38 +
39 + ZSTD_inBuffer inBuffer = {
40 + .pos = 0,
41 + .size = size,
42 + .src = data,
43 + };
44 +
45 + size_t wanted_size = MAX(ZSTD_compressBound(inBuffer.size - inBuffer.pos), ZSTD_CStreamOutSize());
46 + simple_ring_buffer_make_room(&state->output, wanted_size);
47 +
48 + ZSTD_outBuffer outBuffer = {
49 + .pos = 0,
50 + .size = state->output.size,
51 + .dst = (void *)state->output.data,
52 + };
53 +
54 + // compress
55 + size_t ret = ZSTD_compressStream(state->stream, &outBuffer, &inBuffer);
56 +
57 + // error handling
58 + if(ZSTD_isError(ret)) {
59 + netdata_log_error("STREAM: ZSTD_compressStream() return error: %s", ZSTD_getErrorName(ret));
60 + return 0;
61 + }
62 +
63 + if(inBuffer.pos < inBuffer.size) {
64 + netdata_log_error("STREAM: ZSTD_compressStream() left unprocessed input (source payload %zu bytes, consumed %zu bytes)",
65 + inBuffer.size, inBuffer.pos);
66 + return 0;
67 + }
68 +
69 + if(outBuffer.pos == 0) {
70 + // ZSTD needs more input to flush the output, so let's flush it manually
71 + ret = ZSTD_flushStream(state->stream, &outBuffer);
72 +
73 + if(ZSTD_isError(ret)) {
74 + netdata_log_error("STREAM: ZSTD_flushStream() return error: %s", ZSTD_getErrorName(ret));
75 + return 0;
76 + }
77 +
78 + if(outBuffer.pos == 0) {
79 + netdata_log_error("STREAM: ZSTD_compressStream() returned zero compressed bytes "
80 + "(source is %zu bytes, output buffer can fit %zu bytes) "
81 + , size, outBuffer.size);
82 + return 0;
83 + }
84 + }
85 +
86 + state->sender_locked.total_compressions++;
87 + state->sender_locked.total_uncompressed += size;
88 + state->sender_locked.total_compressed += outBuffer.pos;
89 +
90 + // return values
91 + *out = state->output.data;
92 + return outBuffer.pos;
93 +}
94 +
95 +void rrdpush_decompressor_init_zstd(struct decompressor_state *state) {
96 + if(!state->initialized) {
97 + state->initialized = true;
98 + state->stream = ZSTD_createDStream();
99 +
100 + size_t ret = ZSTD_initDStream(state->stream);
101 + if(ZSTD_isError(ret))
102 + netdata_log_error("STREAM: ZSTD_initDStream() returned error: %s", ZSTD_getErrorName(ret));
103 +
104 + simple_ring_buffer_make_room(&state->output, MAX(COMPRESSION_MAX_CHUNK, ZSTD_DStreamOutSize()));
105 + }
106 +}
107 +
108 +void rrdpush_decompressor_destroy_zstd(struct decompressor_state *state) {
109 + if (state->stream) {
110 + ZSTD_freeDStream(state->stream);
111 + state->stream = NULL;
112 + }
113 +}
114 +
115 +size_t rrdpush_decompress_zstd(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) {
116 + if (unlikely(!state || !compressed_data || !compressed_size))
117 + return 0;
118 +
119 + // The state.output ring buffer is always EMPTY at this point,
120 + // meaning that (state->output.read_pos == state->output.write_pos)
121 + // However, THEY ARE NOT ZERO.
122 +
123 + ZSTD_inBuffer inBuffer = {
124 + .pos = 0,
125 + .size = compressed_size,
126 + .src = compressed_data,
127 + };
128 +
129 + ZSTD_outBuffer outBuffer = {
130 + .pos = 0,
131 + .dst = (char *)state->output.data,
132 + .size = state->output.size,
133 + };
134 +
135 + size_t ret = ZSTD_decompressStream(
136 + state->stream
137 + , &outBuffer
138 + , &inBuffer);
139 +
140 + if(ZSTD_isError(ret)) {
141 + netdata_log_error("STREAM: ZSTD_decompressStream() return error: %s", ZSTD_getErrorName(ret));
142 + return 0;
143 + }
144 +
145 + if(inBuffer.pos < inBuffer.size)
146 + fatal("RRDPUSH DECOMPRESS: ZSTD ZSTD_decompressStream() decompressed %zu bytes, "
147 + "but %zu bytes of compressed data remain",
148 + inBuffer.pos, inBuffer.size);
149 +
150 + size_t decompressed_size = outBuffer.pos;
151 +
152 + state->output.read_pos = 0;
153 + state->output.write_pos = outBuffer.pos;
154 +
155 + // statistics
156 + state->total_compressed += compressed_size;
157 + state->total_uncompressed += decompressed_size;
158 + state->total_compressions++;
159 +
160 + return decompressed_size;
161 +}
162 +
163 +#endif // ENABLE_ZSTD
streaming/compression_zstd.h new
+19
@@ -0,0 +1,19 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "compression.h"
4 +
5 +#ifndef NETDATA_STREAMING_COMPRESSION_ZSTD_H
6 +#define NETDATA_STREAMING_COMPRESSION_ZSTD_H
7 +
8 +#ifdef ENABLE_ZSTD
9 +
10 +void rrdpush_compressor_init_zstd(struct compressor_state *state);
11 +void rrdpush_compressor_destroy_zstd(struct compressor_state *state);
12 +size_t rrdpush_compress_zstd(struct compressor_state *state, const char *data, size_t size, const char **out);
13 +size_t rrdpush_decompress_zstd(struct decompressor_state *state, const char *compressed_data, size_t compressed_size);
14 +void rrdpush_decompressor_init_zstd(struct decompressor_state *state);
15 +void rrdpush_decompressor_destroy_zstd(struct decompressor_state *state);
16 +
17 +#endif // ENABLE_ZSTD
18 +
19 +#endif //NETDATA_STREAMING_COMPRESSION_ZSTD_H
streaming/receiver.c
+90 -32
@@ -28,9 +28,7 @@ void receiver_state_free(struct receiver_state *rpt) {
28 close(rpt->fd);
29 }
30
31 -#ifdef ENABLE_RRDPUSH_COMPRESSION
31 rrdpush_decompressor_destroy(&rpt->decompressor);
33 -#endif
32
33 if(rpt->system_info)
34 rrdhost_system_info_free(rpt->system_info);
@@ -92,15 +90,44 @@ static inline int read_stream(struct receiver_state *r, char* buffer, size_t siz
90 return (int)bytes_read;
91 }
92
95 -static inline bool receiver_read_uncompressed(struct receiver_state *r) {
93 +static inline STREAM_HANDSHAKE read_stream_error_to_reason(int code) {
94 + if(code > 0)
95 + return 0;
96 +
97 + switch(code) {
98 + case 0:
99 + // asked to read zero bytes
100 + return STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_READ_BUFFER;
101 +
102 + case -1:
103 + // EOF
104 + return STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF;
105 +
106 + case -2:
107 + // failed to read
108 + return STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED;
109 +
110 + case -3:
111 + // timeout
112 + return STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT;
113 +
114 + default:
115 + // anything else
116 + return STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR;
117 + }
118 +}
119 +
120 +static inline bool receiver_read_uncompressed(struct receiver_state *r, STREAM_HANDSHAKE *reason) {
121 #ifdef NETDATA_INTERNAL_CHECKS
122 if(r->reader.read_buffer[r->reader.read_len] != '\0')
123 fatal("%s(): read_buffer does not start with zero", __FUNCTION__ );
124 #endif
125
126 int bytes_read = read_stream(r, r->reader.read_buffer + r->reader.read_len, sizeof(r->reader.read_buffer) - r->reader.read_len - 1);
102 - if(unlikely(bytes_read <= 0))
127 + if(unlikely(bytes_read <= 0)) {
128 + *reason = read_stream_error_to_reason(bytes_read);
129 return false;
130 + }
131
132 worker_set_metric(WORKER_RECEIVER_JOB_BYTES_READ, (NETDATA_DOUBLE)bytes_read);
133 worker_set_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes_read);
@@ -111,8 +138,7 @@ static inline bool receiver_read_uncompressed(struct receiver_state *r) {
138 return true;
139 }
140
114 -#ifdef ENABLE_RRDPUSH_COMPRESSION
115 -static inline bool receiver_read_compressed(struct receiver_state *r) {
141 +static inline bool receiver_read_compressed(struct receiver_state *r, STREAM_HANDSHAKE *reason) {
142
143 internal_fatal(r->reader.read_buffer[r->reader.read_len] != '\0',
144 "%s: read_buffer does not start with zero #2", __FUNCTION__ );
@@ -150,8 +176,10 @@ static inline bool receiver_read_compressed(struct receiver_state *r) {
176 int bytes_read = 0;
177 do {
178 int ret = read_stream(r, r->reader.read_buffer + r->reader.read_len + bytes_read, r->decompressor.signature_size - bytes_read);
153 - if (unlikely(ret <= 0))
179 + if (unlikely(ret <= 0)) {
180 + *reason = read_stream_error_to_reason(ret);
181 return false;
182 + }
183
184 bytes_read += ret;
185 } while(unlikely(bytes_read < (int)r->decompressor.signature_size));
@@ -187,7 +215,7 @@ static inline bool receiver_read_compressed(struct receiver_state *r) {
215
216 int last_read_bytes = read_stream(r, &compressed[start], remaining);
217 if (unlikely(last_read_bytes <= 0)) {
190 - internal_error(true, "read_stream() failed #2, with code %d", last_read_bytes);
218 + *reason = read_stream_error_to_reason(last_read_bytes);
219 return false;
220 }
221
@@ -217,11 +245,6 @@ static inline bool receiver_read_compressed(struct receiver_state *r) {
245
246 return true;
247 }
220 -#else // !ENABLE_RRDPUSH_COMPRESSION
221 -static inline bool receiver_read_compressed(struct receiver_state *r) {
222 - return receiver_read_uncompressed(r);
223 -}
224 -#endif // ENABLE_RRDPUSH_COMPRESSION
248
249 /* Produce a full line if one exists, statefully return where we start next time.
250 * When we hit the end of the buffer with a partial line move it to the beginning for the next fill.
@@ -323,16 +346,7 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i
346 // so, parser needs to be allocated before pushing it
347 netdata_thread_cleanup_push(pluginsd_process_thread_cleanup, parser);
348
326 - bool compressed_connection = false;
327 -
328 -#ifdef ENABLE_RRDPUSH_COMPRESSION
329 - if(stream_has_capability(rpt, STREAM_CAP_COMPRESSION)) {
330 - compressed_connection = true;
331 - rrdpush_decompressor_reset(&rpt->decompressor);
332 - }
333 - else
334 - rrdpush_decompressor_destroy(&rpt->decompressor);
335 -#endif
349 + bool compressed_connection = rrdpush_decompression_initialize(rpt);
350
351 buffered_reader_init(&rpt->reader);
352
@@ -340,10 +354,12 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i
354 while(!receiver_should_stop(rpt)) {
355
356 if(!buffered_reader_next_line(&rpt->reader, buffer)) {
343 - bool have_new_data = compressed_connection ? receiver_read_compressed(rpt) : receiver_read_uncompressed(rpt);
357 + STREAM_HANDSHAKE reason = STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR;
358 +
359 + bool have_new_data = compressed_connection ? receiver_read_compressed(rpt, &reason) : receiver_read_uncompressed(rpt, &reason);
360
361 if(unlikely(!have_new_data)) {
346 - receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_ERROR, false);
362 + receiver_set_exit_reason(rpt, reason, false);
363 break;
364 }
365
@@ -543,6 +559,29 @@ void rrdpush_receive_log_status(struct receiver_state *rpt, const char *msg, con
559
560 }
561
562 +static void rrdpush_parse_compression_order(struct receiver_state *rpt, const char *order) {
563 + rpt->config.compression_priorities[0] = STREAM_CAP_ZSTD;
564 + rpt->config.compression_priorities[1] = STREAM_CAP_LZ4;
565 + rpt->config.compression_priorities[2] = STREAM_CAP_GZIP;
566 +
567 + char *s = strdupz(order);
568 +
569 + char *words[COMPRESSION_ALGORITHM_MAX] = { NULL };
570 + size_t num_words = quoted_strings_splitter_pluginsd(s, words, COMPRESSION_ALGORITHM_MAX);
571 + for(size_t i = 0; i < num_words ;i++) {
572 + if(strcasecmp(words[i], "zstd") == 0)
573 + rpt->config.compression_priorities[i] = STREAM_CAP_ZSTD;
574 + else if(strcasecmp(words[i], "lz4") == 0)
575 + rpt->config.compression_priorities[i] = STREAM_CAP_LZ4;
576 + else if(strcasecmp(words[i], "gzip") == 0)
577 + rpt->config.compression_priorities[i] = STREAM_CAP_GZIP;
578 + else
579 + rpt->config.compression_priorities[i] = 0;
580 + }
581 +
582 + freez(s);
583 +}
584 +
585 static void rrdpush_receive(struct receiver_state *rpt)
586 {
587 rpt->config.mode = default_rrd_memory_mode;
@@ -611,11 +650,15 @@ static void rrdpush_receive(struct receiver_state *rpt)
650 rpt->config.rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->key, "seconds per replication step", rpt->config.rrdpush_replication_step);
651 rpt->config.rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->machine_guid, "seconds per replication step", rpt->config.rrdpush_replication_step);
652
614 -#ifdef ENABLE_RRDPUSH_COMPRESSION
653 rpt->config.rrdpush_compression = default_rrdpush_compression_enabled;
654 rpt->config.rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->key, "enable compression", rpt->config.rrdpush_compression);
655 rpt->config.rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->machine_guid, "enable compression", rpt->config.rrdpush_compression);
618 -#endif // ENABLE_RRDPUSH_COMPRESSION
656 +
657 + if(rpt->config.rrdpush_compression) {
658 + char *order = appconfig_get(&stream_config, rpt->key, "compression algorithms order", "zstd lz4 gzip");
659 + order = appconfig_get(&stream_config, rpt->machine_guid, "compression algorithms order", order);
660 + rrdpush_parse_compression_order(rpt, order);
661 + }
662
663 (void)appconfig_set_default(&stream_config, rpt->machine_guid, "host tags", (rpt->tags)?rpt->tags:"");
664
@@ -709,12 +752,27 @@ static void rrdpush_receive(struct receiver_state *rpt)
752 snprintfz(cd.fullfilename, FILENAME_MAX, "%s:%s", rpt->client_ip, rpt->client_port);
753 snprintfz(cd.cmd, PLUGINSD_CMD_MAX, "%s:%s", rpt->client_ip, rpt->client_port);
754
712 -#ifdef ENABLE_RRDPUSH_COMPRESSION
713 - if (stream_has_capability(rpt, STREAM_CAP_COMPRESSION)) {
714 - if (!rpt->config.rrdpush_compression)
715 - rpt->capabilities &= ~STREAM_CAP_COMPRESSION;
755 + if (!rpt->config.rrdpush_compression)
756 + rpt->capabilities &= ~STREAM_CAP_COMPRESSIONS_AVAILABLE;
757 +
758 + // select the right compression before sending our capabilities to the child
759 + if(stream_has_more_than_one_capability_of(rpt->capabilities, STREAM_CAP_COMPRESSIONS_AVAILABLE)) {
760 + STREAM_CAPABILITIES compressions = rpt->capabilities & STREAM_CAP_COMPRESSIONS_AVAILABLE;
761 + for(int i = 0; i < COMPRESSION_ALGORITHM_MAX; i++) {
762 + STREAM_CAPABILITIES c = rpt->config.compression_priorities[i];
763 +
764 + if(!(c & STREAM_CAP_COMPRESSIONS_AVAILABLE))
765 + continue;
766 +
767 + if(compressions & c) {
768 + STREAM_CAPABILITIES exclude = compressions;
769 + exclude &= ~c;
770 +
771 + rpt->capabilities &= ~exclude;
772 + break;
773 + }
774 + }
775 }
717 -#endif // ENABLE_RRDPUSH_COMPRESSION
776
777 {
778 // netdata_log_info("STREAM %s [receive from [%s]:%s]: initializing communication...", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port);
streaming/rrdpush.c
+132 -61
@@ -39,9 +39,9 @@ struct config stream_config = {
39 };
40
41 unsigned int default_rrdpush_enabled = 0;
42 -#ifdef ENABLE_RRDPUSH_COMPRESSION
42 +STREAM_CAPABILITIES globally_disabled_capabilities = STREAM_CAP_NONE;
43 +
44 unsigned int default_rrdpush_compression_enabled = 1;
44 -#endif
45 char *default_rrdpush_destination = NULL;
46 char *default_rrdpush_api_key = NULL;
47 char *default_rrdpush_send_charts_matching = NULL;
@@ -67,43 +67,6 @@ static void load_stream_conf() {
67 freez(filename);
68 }
69
70 -STREAM_CAPABILITIES stream_our_capabilities(RRDHOST *host, bool sender) {
71 -
72 - // we can have DATA_WITH_ML when INTERPOLATED is available
73 - bool ml_capability = true;
74 -
75 - if(host && sender) {
76 - // we have DATA_WITH_ML capability
77 - // we should remove the DATA_WITH_ML capability if our database does not have anomaly info
78 - // this can happen under these conditions: 1. we don't run ML, and 2. we don't receive ML
79 - netdata_mutex_lock(&host->receiver_lock);
80 -
81 - if(!ml_host_running(host) && !stream_has_capability(host->receiver, STREAM_CAP_DATA_WITH_ML))
82 - ml_capability = false;
83 -
84 - netdata_mutex_unlock(&host->receiver_lock);
85 - }
86 -
87 - return STREAM_CAP_V1 |
88 - STREAM_CAP_V2 |
89 - STREAM_CAP_VN |
90 - STREAM_CAP_VCAPS |
91 - STREAM_CAP_HLABELS |
92 - STREAM_CAP_CLAIM |
93 - STREAM_CAP_CLABELS |
94 - STREAM_CAP_FUNCTIONS |
95 - STREAM_CAP_REPLICATION |
96 - STREAM_CAP_BINARY |
97 - STREAM_CAP_INTERPOLATED |
98 - STREAM_HAS_COMPRESSION |
99 -#ifdef NETDATA_TEST_DYNCFG
100 - STREAM_CAP_DYNCFG |
101 -#endif
102 - (ieee754_doubles ? STREAM_CAP_IEEE754 : 0) |
103 - (ml_capability ? STREAM_CAP_DATA_WITH_ML : 0) |
104 - 0;
105 -}
106 -
70 bool rrdpush_receiver_needs_dbengine() {
71 struct section *co;
72
@@ -145,10 +108,20 @@ int rrdpush_init() {
108
109 rrdhost_free_orphan_time_s = config_get_number(CONFIG_SECTION_DB, "cleanup orphan hosts after secs", rrdhost_free_orphan_time_s);
110
148 -#ifdef ENABLE_RRDPUSH_COMPRESSION
111 default_rrdpush_compression_enabled = (unsigned int)appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM,
112 "enable compression", default_rrdpush_compression_enabled);
151 -#endif
113 +
114 + rrdpush_compression_levels[COMPRESSION_ALGORITHM_ZSTD] = (int)appconfig_get_number(
115 + &stream_config, CONFIG_SECTION_STREAM, "zstd compression level",
116 + rrdpush_compression_levels[COMPRESSION_ALGORITHM_ZSTD]);
117 +
118 + rrdpush_compression_levels[COMPRESSION_ALGORITHM_LZ4] = (int)appconfig_get_number(
119 + &stream_config, CONFIG_SECTION_STREAM, "lz4 compression acceleration",
120 + rrdpush_compression_levels[COMPRESSION_ALGORITHM_LZ4]);
121 +
122 + rrdpush_compression_levels[COMPRESSION_ALGORITHM_GZIP] = (int)appconfig_get_number(
123 + &stream_config, CONFIG_SECTION_STREAM, "gzip compression level",
124 + rrdpush_compression_levels[COMPRESSION_ALGORITHM_GZIP]);
125
126 if(default_rrdpush_enabled && (!default_rrdpush_destination || !*default_rrdpush_destination || !default_rrdpush_api_key || !*default_rrdpush_api_key)) {
127 netdata_log_error("STREAM [send]: cannot enable sending thread - information is missing.");
@@ -921,9 +894,10 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_stri
894
895 struct receiver_state *rpt = callocz(1, sizeof(*rpt));
896 rpt->last_msg_t = now_monotonic_sec();
924 - rpt->capabilities = STREAM_CAP_INVALID;
897 rpt->hops = 1;
898
899 + rpt->capabilities = STREAM_CAP_INVALID;
900 +
901 __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_receivers, sizeof(*rpt), __ATOMIC_RELAXED);
902 __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED);
903
@@ -1380,11 +1354,15 @@ static struct {
1354 { STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN, "DISCONNECTED SHUTDOWN REQUESTED" },
1355 { STREAM_HANDSHAKE_DISCONNECT_NETDATA_EXIT, "DISCONNECTED NETDATA EXIT" },
1356 { STREAM_HANDSHAKE_DISCONNECT_PARSER_EXIT, "DISCONNECTED PARSE ENDED" },
1383 - { STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_ERROR, "DISCONNECTED SOCKET READ ERROR" },
1357 + {STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR, "DISCONNECTED UNKNOWN SOCKET READ ERROR" },
1358 { STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, "DISCONNECTED PARSE ERROR" },
1359 { STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT, "DISCONNECTED RECEIVER LEFT" },
1360 { STREAM_HANDSHAKE_DISCONNECT_ORPHAN_HOST, "DISCONNECTED ORPHAN HOST" },
1361 { STREAM_HANDSHAKE_NON_STREAMABLE_HOST, "NON STREAMABLE HOST" },
1362 + { STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_READ_BUFFER, "DISCONNECTED NOT SUFFICIENT READ BUFFER" },
1363 + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF, "DISCONNECTED SOCKET EOF" },
1364 + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED, "DISCONNECTED SOCKET READ FAILED" },
1365 + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT, "DISCONNECTED SOCKET READ TIMEOUT" },
1366 { 0, NULL },
1367 };
1368
@@ -1405,22 +1383,24 @@ static struct {
1383 STREAM_CAPABILITIES cap;
1384 const char *str;
1385 } capability_names[] = {
1408 - { STREAM_CAP_V1, "V1" },
1409 - { STREAM_CAP_V2, "V2" },
1410 - { STREAM_CAP_VN, "VN" },
1411 - { STREAM_CAP_VCAPS, "VCAPS" },
1412 - { STREAM_CAP_HLABELS, "HLABELS" },
1413 - { STREAM_CAP_CLAIM, "CLAIM" },
1414 - { STREAM_CAP_CLABELS, "CLABELS" },
1415 - { STREAM_CAP_COMPRESSION, "COMPRESSION" },
1416 - { STREAM_CAP_FUNCTIONS, "FUNCTIONS" },
1417 - { STREAM_CAP_REPLICATION, "REPLICATION" },
1418 - { STREAM_CAP_BINARY, "BINARY" },
1419 - { STREAM_CAP_INTERPOLATED, "INTERPOLATED" },
1420 - { STREAM_CAP_IEEE754, "IEEE754" },
1421 - { STREAM_CAP_DATA_WITH_ML, "ML" },
1422 - { STREAM_CAP_DYNCFG, "DYN_CFG" },
1423 - { 0 , NULL },
1386 + {STREAM_CAP_V1, "V1" },
1387 + {STREAM_CAP_V2, "V2" },
1388 + {STREAM_CAP_VN, "VN" },
1389 + {STREAM_CAP_VCAPS, "VCAPS" },
1390 + {STREAM_CAP_HLABELS, "HLABELS" },
1391 + {STREAM_CAP_CLAIM, "CLAIM" },
1392 + {STREAM_CAP_CLABELS, "CLABELS" },
1393 + {STREAM_CAP_LZ4, "LZ4" },
1394 + {STREAM_CAP_FUNCTIONS, "FUNCTIONS" },
1395 + {STREAM_CAP_REPLICATION, "REPLICATION" },
1396 + {STREAM_CAP_BINARY, "BINARY" },
1397 + {STREAM_CAP_INTERPOLATED, "INTERPOLATED" },
1398 + {STREAM_CAP_IEEE754, "IEEE754" },
1399 + {STREAM_CAP_DATA_WITH_ML, "ML" },
1400 + {STREAM_CAP_DYNCFG, "DYN_CFG" },
1401 + {STREAM_CAP_ZSTD, "ZSTD" },
1402 + {STREAM_CAP_GZIP, "GZIP" },
1403 + {0 , NULL },
1404 };
1405
1406 static void stream_capabilities_to_string(BUFFER *wb, STREAM_CAPABILITIES caps) {
@@ -1466,6 +1446,44 @@ void log_sender_capabilities(struct sender_state *s) {
1446 buffer_free(wb);
1447 }
1448
1449 +STREAM_CAPABILITIES stream_our_capabilities(RRDHOST *host, bool sender) {
1450 + STREAM_CAPABILITIES disabled_capabilities = globally_disabled_capabilities;
1451 +
1452 + if(host && sender) {
1453 + // we have DATA_WITH_ML capability
1454 + // we should remove the DATA_WITH_ML capability if our database does not have anomaly info
1455 + // this can happen under these conditions: 1. we don't run ML, and 2. we don't receive ML
1456 + netdata_mutex_lock(&host->receiver_lock);
1457 +
1458 + if(!ml_host_running(host) && !stream_has_capability(host->receiver, STREAM_CAP_DATA_WITH_ML))
1459 + disabled_capabilities |= STREAM_CAP_DATA_WITH_ML;
1460 +
1461 + netdata_mutex_unlock(&host->receiver_lock);
1462 +
1463 + if(host->sender)
1464 + disabled_capabilities |= host->sender->disabled_capabilities;
1465 + }
1466 +
1467 + return (STREAM_CAP_V1 |
1468 + STREAM_CAP_V2 |
1469 + STREAM_CAP_VN |
1470 + STREAM_CAP_VCAPS |
1471 + STREAM_CAP_HLABELS |
1472 + STREAM_CAP_CLAIM |
1473 + STREAM_CAP_CLABELS |
1474 + STREAM_CAP_FUNCTIONS |
1475 + STREAM_CAP_REPLICATION |
1476 + STREAM_CAP_BINARY |
1477 + STREAM_CAP_INTERPOLATED |
1478 + STREAM_CAP_COMPRESSIONS_AVAILABLE |
1479 + #ifdef NETDATA_TEST_DYNCFG
1480 + STREAM_CAP_DYNCFG |
1481 + #endif
1482 + STREAM_CAP_IEEE754 |
1483 + STREAM_CAP_DATA_WITH_ML |
1484 + 0) & ~disabled_capabilities;
1485 +}
1486 +
1487 STREAM_CAPABILITIES convert_stream_version_to_capabilities(int32_t version, RRDHOST *host, bool sender) {
1488 STREAM_CAPABILITIES caps = 0;
1489
@@ -1473,7 +1491,7 @@ STREAM_CAPABILITIES convert_stream_version_to_capabilities(int32_t version, RRDH
1491 else if(version < STREAM_OLD_VERSION_CLAIM) caps = STREAM_CAP_V2 | STREAM_CAP_HLABELS;
1492 else if(version <= STREAM_OLD_VERSION_CLAIM) caps = STREAM_CAP_VN | STREAM_CAP_HLABELS | STREAM_CAP_CLAIM;
1493 else if(version <= STREAM_OLD_VERSION_CLABELS) caps = STREAM_CAP_VN | STREAM_CAP_HLABELS | STREAM_CAP_CLAIM | STREAM_CAP_CLABELS;
1476 - else if(version <= STREAM_OLD_VERSION_COMPRESSION) caps = STREAM_CAP_VN | STREAM_CAP_HLABELS | STREAM_CAP_CLAIM | STREAM_CAP_CLABELS | STREAM_HAS_COMPRESSION;
1494 + else if(version <= STREAM_OLD_VERSION_LZ4) caps = STREAM_CAP_VN | STREAM_CAP_HLABELS | STREAM_CAP_CLAIM | STREAM_CAP_CLABELS | STREAM_CAP_LZ4_AVAILABLE;
1495 else caps = version;
1496
1497 if(caps & STREAM_CAP_VCAPS)
@@ -1495,8 +1513,61 @@ STREAM_CAPABILITIES convert_stream_version_to_capabilities(int32_t version, RRDH
1513 }
1514
1515 int32_t stream_capabilities_to_vn(uint32_t caps) {
1498 - if(caps & STREAM_CAP_COMPRESSION) return STREAM_OLD_VERSION_COMPRESSION;
1516 + if(caps & STREAM_CAP_LZ4) return STREAM_OLD_VERSION_LZ4;
1517 if(caps & STREAM_CAP_CLABELS) return STREAM_OLD_VERSION_CLABELS;
1518 return STREAM_OLD_VERSION_CLAIM; // if(caps & STREAM_CAP_CLAIM)
1519 }
1520
1521 +int rrdpush_compression_levels[COMPRESSION_ALGORITHM_MAX] = {
1522 + [COMPRESSION_ALGORITHM_NONE] = 0,
1523 + [COMPRESSION_ALGORITHM_ZSTD] = 3, // 1 (faster) - 22 (best compression),
1524 + [COMPRESSION_ALGORITHM_LZ4] = 1, // 1 (best compression) - 9 (faster)
1525 + [COMPRESSION_ALGORITHM_GZIP] = 1, // 1 (faster) - 9 (best compression)
1526 +};
1527 +
1528 +bool rrdpush_compression_initialize(struct sender_state *s) {
1529 + rrdpush_compressor_destroy(&s->compressor);
1530 +
1531 + // IMPORTANT
1532 + // KEEP THE SAME ORDER IN DECOMPRESSION
1533 +
1534 + if(stream_has_capability(s, STREAM_CAP_ZSTD))
1535 + s->compressor.algorithm = COMPRESSION_ALGORITHM_ZSTD;
1536 + else if(stream_has_capability(s, STREAM_CAP_LZ4))
1537 + s->compressor.algorithm = COMPRESSION_ALGORITHM_LZ4;
1538 + else if(stream_has_capability(s, STREAM_CAP_GZIP))
1539 + s->compressor.algorithm = COMPRESSION_ALGORITHM_GZIP;
1540 + else
1541 + s->compressor.algorithm = COMPRESSION_ALGORITHM_NONE;
1542 +
1543 + if(s->compressor.algorithm != COMPRESSION_ALGORITHM_NONE) {
1544 + s->compressor.level = rrdpush_compression_levels[s->compressor.algorithm];
1545 + rrdpush_compressor_init(&s->compressor);
1546 + return true;
1547 + }
1548 +
1549 + return false;
1550 +}
1551 +
1552 +bool rrdpush_decompression_initialize(struct receiver_state *rpt) {
1553 + rrdpush_decompressor_destroy(&rpt->decompressor);
1554 +
1555 + // IMPORTANT
1556 + // KEEP THE SAME ORDER IN COMPRESSION
1557 +
1558 + if(stream_has_capability(rpt, STREAM_CAP_ZSTD))
1559 + rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_ZSTD;
1560 + else if(stream_has_capability(rpt, STREAM_CAP_LZ4))
1561 + rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_LZ4;
1562 + else if(stream_has_capability(rpt, STREAM_CAP_GZIP))
1563 + rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_GZIP;
1564 + else
1565 + rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_NONE;
1566 +
1567 + if(rpt->decompressor.algorithm != COMPRESSION_ALGORITHM_NONE) {
1568 + rrdpush_decompressor_init(&rpt->decompressor);
1569 + return true;
1570 + }
1571 +
1572 + return false;
1573 +}
streaming/rrdpush.h
+36 -112
@@ -18,12 +18,14 @@
18
19 #define STREAM_OLD_VERSION_CLAIM 3
20 #define STREAM_OLD_VERSION_CLABELS 4
21 -#define STREAM_OLD_VERSION_COMPRESSION 5 // this is production
21 +#define STREAM_OLD_VERSION_LZ4 5
22
23 // ----------------------------------------------------------------------------
24 // capabilities negotiation
25
26 typedef enum {
27 + STREAM_CAP_NONE = 0,
28 +
29 // do not use the first 3 bits
30 // they used to be versions 1, 2 and 3
31 // before we introduce capabilities
@@ -38,7 +40,7 @@ typedef enum {
40 STREAM_CAP_HLABELS = (1 << 7), // host labels supported
41 STREAM_CAP_CLAIM = (1 << 8), // claiming supported
42 STREAM_CAP_CLABELS = (1 << 9), // chart labels supported
41 - STREAM_CAP_COMPRESSION = (1 << 10), // lz4 compression supported
43 + STREAM_CAP_LZ4 = (1 << 10), // lz4 compression supported
44 STREAM_CAP_FUNCTIONS = (1 << 11), // plugin functions supported
45 STREAM_CAP_REPLICATION = (1 << 12), // replication supported
46 STREAM_CAP_BINARY = (1 << 13), // streaming supports binary data
@@ -46,22 +48,39 @@ typedef enum {
48 STREAM_CAP_IEEE754 = (1 << 15), // streaming supports binary/hex transfer of double values
49 STREAM_CAP_DATA_WITH_ML = (1 << 16), // streaming supports transferring anomaly bit
50 STREAM_CAP_DYNCFG = (1 << 17), // dynamic configuration of plugins trough streaming
51 + STREAM_CAP_ZSTD = (1 << 19), // ZSTD compression supported
52 + STREAM_CAP_GZIP = (1 << 20), // GZIP compression supported
53
54 STREAM_CAP_INVALID = (1 << 30), // used as an invalid value for capabilities when this is set
55 // this must be signed int, so don't use the last bit
56 // needed for negotiating errors between parent and child
57 } STREAM_CAPABILITIES;
58
55 -#ifdef ENABLE_RRDPUSH_COMPRESSION
56 -#define STREAM_HAS_COMPRESSION STREAM_CAP_COMPRESSION
59 +#ifdef ENABLE_LZ4
60 +#define STREAM_CAP_LZ4_AVAILABLE STREAM_CAP_LZ4
61 +#else
62 +#define STREAM_CAP_LZ4_AVAILABLE 0
63 +#endif // ENABLE_LZ4
64 +
65 +#ifdef ENABLE_ZSTD
66 +#define STREAM_CAP_ZSTD_AVAILABLE STREAM_CAP_ZSTD
67 #else
58 -#define STREAM_HAS_COMPRESSION 0
59 -#endif // ENABLE_RRDPUSH_COMPRESSION
68 +#define STREAM_CAP_ZSTD_AVAILABLE 0
69 +#endif // ENABLE_ZSTD
70 +
71 +#define STREAM_CAP_COMPRESSIONS_AVAILABLE (STREAM_CAP_LZ4_AVAILABLE|STREAM_CAP_ZSTD_AVAILABLE|STREAM_CAP_GZIP)
72 +
73 +extern STREAM_CAPABILITIES globally_disabled_capabilities;
74
75 STREAM_CAPABILITIES stream_our_capabilities(RRDHOST *host, bool sender);
76
77 #define stream_has_capability(rpt, capability) ((rpt) && ((rpt)->capabilities & (capability)) == (capability))
78
79 +static inline bool stream_has_more_than_one_capability_of(STREAM_CAPABILITIES caps, STREAM_CAPABILITIES mask) {
80 + STREAM_CAPABILITIES common = (STREAM_CAPABILITIES)(caps & mask);
81 + return (common & (common - 1)) != 0 && common != 0;
82 +}
83 +
84 // ----------------------------------------------------------------------------
85 // stream handshake
86
@@ -101,11 +120,15 @@ typedef enum {
120 STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN = -15,
121 STREAM_HANDSHAKE_DISCONNECT_NETDATA_EXIT = -16,
122 STREAM_HANDSHAKE_DISCONNECT_PARSER_EXIT = -17,
104 - STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_ERROR = -18,
123 + STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR = -18,
124 STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED = -19,
125 STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT = -20,
126 STREAM_HANDSHAKE_DISCONNECT_ORPHAN_HOST = -21,
127 STREAM_HANDSHAKE_NON_STREAMABLE_HOST = -22,
128 + STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_READ_BUFFER = -23,
129 + STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF = -24,
130 + STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED = -25,
131 + STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT = -26,
132
133 } STREAM_HANDSHAKE;
134
@@ -120,100 +143,7 @@ typedef struct {
143 char *kernel_version;
144 } stream_encoded_t;
145
123 -#ifdef ENABLE_RRDPUSH_COMPRESSION
124 -// signature MUST end with a newline
125 -#define RRDPUSH_COMPRESSION_SIGNATURE ((uint32_t)('z' | 0x80) | (0x80 << 8) | (0x80 << 16) | ('\n' << 24))
126 -#define RRDPUSH_COMPRESSION_SIGNATURE_MASK ((uint32_t)0xff | (0x80 << 8) | (0x80 << 16) | (0xff << 24))
127 -#define RRDPUSH_COMPRESSION_SIGNATURE_SIZE 4
128 -
129 -struct compressor_state {
130 - bool initialized;
131 - char *compression_result_buffer;
132 - size_t compression_result_buffer_size;
133 - struct {
134 - void *lz4_stream;
135 - char *input_ring_buffer;
136 - size_t input_ring_buffer_size;
137 - size_t input_ring_buffer_pos;
138 - } stream;
139 - size_t (*compress)(struct compressor_state *state, const char *data, size_t size, char **buffer);
140 - void (*destroy)(struct compressor_state **state);
141 -};
142 -
143 -void rrdpush_compressor_reset(struct compressor_state *state);
144 -void rrdpush_compressor_destroy(struct compressor_state *state);
145 -size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, char **out);
146 -
147 -struct decompressor_state {
148 - bool initialized;
149 - size_t signature_size;
150 - size_t total_compressed;
151 - size_t total_uncompressed;
152 - size_t packet_count;
153 - struct {
154 - void *lz4_stream;
155 - char *buffer;
156 - size_t size;
157 - size_t write_at;
158 - size_t read_at;
159 - } stream;
160 -};
161 -
162 -void rrdpush_decompressor_destroy(struct decompressor_state *state);
163 -void rrdpush_decompressor_reset(struct decompressor_state *state);
164 -size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size);
165 -
166 -static inline size_t rrdpush_decompress_decode_header(const char *data, size_t data_size) {
167 - if (unlikely(!data || !data_size))
168 - return 0;
169 -
170 - if (unlikely(data_size != RRDPUSH_COMPRESSION_SIGNATURE_SIZE))
171 - return 0;
172 -
173 - uint32_t sign = *(uint32_t *)data;
174 - if (unlikely((sign & RRDPUSH_COMPRESSION_SIGNATURE_MASK) != RRDPUSH_COMPRESSION_SIGNATURE))
175 - return 0;
176 -
177 - size_t length = ((sign >> 8) & 0x7f) | ((sign >> 9) & (0x7f << 7));
178 - return length;
179 -}
180 -
181 -static inline size_t rrdpush_decompressor_start(struct decompressor_state *state, const char *header, size_t header_size) {
182 - if(unlikely(state->stream.read_at != state->stream.write_at))
183 - fatal("RRDPUSH DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!");
184 -
185 - return rrdpush_decompress_decode_header(header, header_size);
186 -}
187 -
188 -static inline size_t rrdpush_decompressed_bytes_in_buffer(struct decompressor_state *state) {
189 - if(unlikely(state->stream.read_at > state->stream.write_at))
190 - fatal("RRDPUSH DECOMPRESS: invalid read/write stream positions");
191 -
192 - return state->stream.write_at - state->stream.read_at;
193 -}
194 -
195 -static inline size_t rrdpush_decompressor_get(struct decompressor_state *state, char *dst, size_t size) {
196 - if (unlikely(!state || !size || !dst))
197 - return 0;
198 -
199 - size_t remaining = rrdpush_decompressed_bytes_in_buffer(state);
200 -
201 - if(unlikely(!remaining))
202 - return 0;
203 -
204 - size_t bytes_to_return = size;
205 - if(bytes_to_return > remaining)
206 - bytes_to_return = remaining;
207 -
208 - memcpy(dst, state->stream.buffer + state->stream.read_at, bytes_to_return);
209 - state->stream.read_at += bytes_to_return;
210 -
211 - if(unlikely(state->stream.read_at > state->stream.write_at))
212 - fatal("RRDPUSH DECOMPRESS: invalid read/write stream positions");
213 -
214 - return bytes_to_return;
215 -}
216 -#endif
146 +#include "compression.h"
147
148 // Thread-local storage
149 // Metric transmission: collector threads asynchronously fill the buffer, sender thread uses it.
@@ -230,7 +160,6 @@ typedef enum __attribute__((packed)) {
160
161 typedef enum __attribute__((packed)) {
162 SENDER_FLAG_OVERFLOW = (1 << 0), // The buffer has been overflown
233 - SENDER_FLAG_COMPRESSION = (1 << 1), // The stream needs to have and has compression
163 } SENDER_FLAGS;
164
165 struct function_payload_state {
@@ -263,6 +192,7 @@ struct sender_state {
192 char read_buffer[PLUGINSD_LINE_MAX + 1];
193 ssize_t read_len;
194 STREAM_CAPABILITIES capabilities;
195 + STREAM_CAPABILITIES disabled_capabilities;
196
197 size_t sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_MAX];
198
@@ -274,9 +204,7 @@ struct sender_state {
204
205 uint16_t hops;
206
277 -#ifdef ENABLE_RRDPUSH_COMPRESSION
207 struct compressor_state compressor;
279 -#endif // ENABLE_RRDPUSH_COMPRESSION
208
209 #ifdef ENABLE_HTTPS
210 NETDATA_SSL ssl; // structure used to encrypt the connection
@@ -421,6 +349,7 @@ struct receiver_state {
349 time_t rrdpush_replication_step;
350 char *rrdpush_destination; // DONT FREE - it is allocated in appconfig
351 unsigned int rrdpush_compression;
352 + STREAM_CAPABILITIES compression_priorities[COMPRESSION_ALGORITHM_MAX];
353 } config;
354
355 #ifdef ENABLE_HTTPS
@@ -429,9 +358,7 @@ struct receiver_state {
358
359 time_t replication_first_time_t;
360
432 -#ifdef ENABLE_RRDPUSH_COMPRESSION
361 struct decompressor_state decompressor;
434 -#endif // ENABLE_RRDPUSH_COMPRESSION
362 /*
363 struct {
364 uint32_t count;
@@ -453,9 +380,7 @@ struct rrdpush_destinations {
380 };
381
382 extern unsigned int default_rrdpush_enabled;
456 -#ifdef ENABLE_RRDPUSH_COMPRESSION
383 extern unsigned int default_rrdpush_compression_enabled;
458 -#endif // ENABLE_RRDPUSH_COMPRESSION
384 extern char *default_rrdpush_destination;
385 extern char *default_rrdpush_api_key;
386 extern char *default_rrdpush_send_charts_matching;
@@ -514,10 +439,6 @@ int connect_to_one_of_destinations(
439
440 void rrdpush_signal_sender_to_wake_up(struct sender_state *s);
441
517 -#ifdef ENABLE_RRDPUSH_COMPRESSION
518 -struct compressor_state *create_compressor();
519 -#endif // ENABLE_RRDPUSH_COMPRESSION
520 -
442 void rrdpush_reset_destinations_postpone_time(RRDHOST *host);
443 const char *stream_handshake_error_to_string(STREAM_HANDSHAKE handshake_error);
444 void stream_capabilities_to_json_array(BUFFER *wb, STREAM_CAPABILITIES caps, const char *key);
@@ -784,4 +705,7 @@ void rrdpush_send_dyncfg_reg_module(RRDHOST *host, const char *plugin_name, cons
705 void rrdpush_send_dyncfg_reg_job(RRDHOST *host, const char *plugin_name, const char *module_name, const char *job_name, enum job_type type, uint32_t flags);
706 void rrdpush_send_dyncfg_reset(RRDHOST *host, const char *plugin_name);
707
708 +bool rrdpush_compression_initialize(struct sender_state *s);
709 +bool rrdpush_decompression_initialize(struct receiver_state *rpt);
710 +
711 #endif //NETDATA_RRDPUSH_H
streaming/sender.c
+67 -33
@@ -20,9 +20,12 @@
20 #define WORKER_SENDER_JOB_BUFFER_RATIO 15
21 #define WORKER_SENDER_JOB_BYTES_RECEIVED 16
22 #define WORKER_SENDER_JOB_BYTES_SENT 17
23 -#define WORKER_SENDER_JOB_REPLAY_REQUEST 18
24 -#define WORKER_SENDER_JOB_FUNCTION_REQUEST 19
25 -#define WORKER_SENDER_JOB_REPLAY_DICT_SIZE 20
23 +#define WORKER_SENDER_JOB_BYTES_COMPRESSED 18
24 +#define WORKER_SENDER_JOB_BYTES_UNCOMPRESSED 19
25 +#define WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO 20
26 +#define WORKER_SENDER_JOB_REPLAY_REQUEST 21
27 +#define WORKER_SENDER_JOB_FUNCTION_REQUEST 22
28 +#define WORKER_SENDER_JOB_REPLAY_DICT_SIZE 23
29
30 #if WORKER_UTILIZATION_MAX_JOB_TYPES < 21
31 #error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 21
@@ -66,7 +69,6 @@ BUFFER *sender_start(struct sender_state *s) {
69
70 static inline void rrdpush_sender_thread_close_socket(RRDHOST *host);
71
69 -#ifdef ENABLE_RRDPUSH_COMPRESSION
72 /*
73 * In case of stream compression buffer overflow
74 * Inform the user through the error log file and
@@ -74,12 +76,35 @@ static inline void rrdpush_sender_thread_close_socket(RRDHOST *host);
76 */
77 static inline void deactivate_compression(struct sender_state *s) {
78 worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_NO_COMPRESSION);
77 - netdata_log_error("STREAM_COMPRESSION: Compression returned error, disabling it.");
78 - s->flags &= ~SENDER_FLAG_COMPRESSION;
79 - netdata_log_error("STREAM %s [send to %s]: Restarting connection without compression.", rrdhost_hostname(s->host), s->connected_to);
79 +
80 + switch(s->compressor.algorithm) {
81 + case COMPRESSION_ALGORITHM_MAX:
82 + case COMPRESSION_ALGORITHM_NONE:
83 + netdata_log_error("STREAM_COMPRESSION: compression error on 'host:%s' without any compression enabled. Ignoring error.",
84 + rrdhost_hostname(s->host));
85 + break;
86 +
87 + case COMPRESSION_ALGORITHM_GZIP:
88 + netdata_log_error("STREAM_COMPRESSION: GZIP compression error on 'host:%s'. Disabling GZIP for this node.",
89 + rrdhost_hostname(s->host));
90 + s->disabled_capabilities |= STREAM_CAP_GZIP;
91 + break;
92 +
93 + case COMPRESSION_ALGORITHM_LZ4:
94 + netdata_log_error("STREAM_COMPRESSION: LZ4 compression error on 'host:%s'. Disabling ZSTD for this node.",
95 + rrdhost_hostname(s->host));
96 + s->disabled_capabilities |= STREAM_CAP_LZ4;
97 + break;
98 +
99 + case COMPRESSION_ALGORITHM_ZSTD:
100 + netdata_log_error("STREAM_COMPRESSION: ZSTD compression error on 'host:%s'. Disabling ZSTD for this node.",
101 + rrdhost_hostname(s->host));
102 + s->disabled_capabilities |= STREAM_CAP_ZSTD;
103 + break;
104 + }
105 +
106 rrdpush_sender_thread_close_socket(s->host);
107 }
82 -#endif
108
109 #define SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE 3
110
@@ -117,8 +142,7 @@ void sender_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type)
142 s->buffer->max_size = (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE;
143 }
144
120 -#ifdef ENABLE_RRDPUSH_COMPRESSION
121 - if (stream_has_capability(s, STREAM_CAP_COMPRESSION) && s->compressor.initialized) {
145 + if (s->compressor.initialized) {
146 while(src_len) {
147 size_t size_to_compress = src_len;
148
@@ -143,13 +167,13 @@ void sender_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type)
167 }
168 }
169
146 - char *dst;
170 + const char *dst;
171 size_t dst_len = rrdpush_compress(&s->compressor, src, size_to_compress, &dst);
172 if (!dst_len) {
173 netdata_log_error("STREAM %s [send to %s]: COMPRESSION failed. Resetting compressor and re-trying",
174 rrdhost_hostname(s->host), s->connected_to);
175
152 - rrdpush_compressor_reset(&s->compressor);
176 + rrdpush_compression_initialize(s);
177 dst_len = rrdpush_compress(&s->compressor, src, size_to_compress, &dst);
178 if(!dst_len) {
179 netdata_log_error("STREAM %s [send to %s]: COMPRESSION failed again. Deactivating compression",
@@ -161,10 +185,25 @@ void sender_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type)
185 }
186 }
187
164 - if(cbuffer_add_unsafe(s->buffer, dst, dst_len))
188 + rrdpush_signature_t signature = rrdpush_compress_encode_signature(dst_len);
189 +
190 +#ifdef NETDATA_INTERNAL_CHECKS
191 + // check if reversing the signature provides the same length
192 + size_t decoded_dst_len = rrdpush_decompress_decode_signature((const char *)&signature, sizeof(signature));
193 + if(decoded_dst_len != dst_len)
194 + fatal("RRDPUSH COMPRESSION: invalid signature, original payload %zu bytes, "
195 + "compressed payload length %zu bytes, but signature says payload is %zu bytes",
196 + size_to_compress, dst_len, decoded_dst_len);
197 +#endif
198 +
199 + if(cbuffer_add_unsafe(s->buffer, (const char *)&signature, sizeof(signature)))
200 s->flags |= SENDER_FLAG_OVERFLOW;
166 - else
167 - s->sent_bytes_on_this_connection_per_type[type] += dst_len;
201 + else {
202 + if(cbuffer_add_unsafe(s->buffer, dst, dst_len))
203 + s->flags |= SENDER_FLAG_OVERFLOW;
204 + else
205 + s->sent_bytes_on_this_connection_per_type[type] += dst_len + sizeof(signature);
206 + }
207
208 src = src + size_to_compress;
209 src_len -= size_to_compress;
@@ -174,12 +213,6 @@ void sender_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type)
213 s->flags |= SENDER_FLAG_OVERFLOW;
214 else
215 s->sent_bytes_on_this_connection_per_type[type] += src_len;
177 -#else
178 - if(cbuffer_add_unsafe(s->buffer, src, src_len))
179 - s->flags |= SENDER_FLAG_OVERFLOW;
180 - else
181 - s->sent_bytes_on_this_connection_per_type[type] += src_len;
182 -#endif
216
217 replication_recalculate_buffer_used_ratio_unsafe(s);
218
@@ -600,12 +633,6 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
633 // reset our capabilities to default
634 s->capabilities = stream_our_capabilities(host, true);
635
603 -#ifdef ENABLE_RRDPUSH_COMPRESSION
604 - // If we don't want compression, remove it from our capabilities
605 - if(!(s->flags & SENDER_FLAG_COMPRESSION))
606 - s->capabilities &= ~STREAM_CAP_COMPRESSION;
607 -#endif // ENABLE_RRDPUSH_COMPRESSION
608 -
636 /* TODO: During the implementation of #7265 switch the set of variables to HOST_* and CONTAINER_* if the
637 version negotiation resulted in a high enough version.
638 */
@@ -766,12 +793,7 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
793 if(!rrdpush_sender_validate_response(host, s, http, bytes))
794 return false;
795
769 -#ifdef ENABLE_RRDPUSH_COMPRESSION
770 - if(stream_has_capability(s, STREAM_CAP_COMPRESSION))
771 - rrdpush_compressor_reset(&s->compressor);
772 - else
773 - rrdpush_compressor_destroy(&s->compressor);
774 -#endif // ENABLE_RRDPUSH_COMPRESSION
796 + rrdpush_compression_initialize(s);
797
798 log_sender_capabilities(s);
799
@@ -1303,6 +1325,9 @@ void *rrdpush_sender_thread(void *ptr) {
1325 worker_register_job_custom_metric(WORKER_SENDER_JOB_BUFFER_RATIO, "used buffer ratio", "%", WORKER_METRIC_ABSOLUTE);
1326 worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_RECEIVED, "bytes received", "bytes/s", WORKER_METRIC_INCREMENT);
1327 worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_SENT, "bytes sent", "bytes/s", WORKER_METRIC_INCREMENT);
1328 + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_COMPRESSED, "bytes compressed", "bytes/s", WORKER_METRIC_INCREMENTAL_TOTAL);
1329 + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_UNCOMPRESSED, "bytes uncompressed", "bytes/s", WORKER_METRIC_INCREMENTAL_TOTAL);
1330 + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO, "cumulative compression savings ratio", "%", WORKER_METRIC_ABSOLUTE);
1331 worker_register_job_custom_metric(WORKER_SENDER_JOB_REPLAY_DICT_SIZE, "replication dict entries", "entries", WORKER_METRIC_ABSOLUTE);
1332
1333 struct sender_state *s = ptr;
@@ -1423,6 +1448,15 @@ void *rrdpush_sender_thread(void *ptr) {
1448 rrdpush_sender_pipe_clear_pending_data(s);
1449 rrdpush_sender_cbuffer_recreate_timed(s, now_s, true, false);
1450 }
1451 +
1452 + if(s->compressor.initialized) {
1453 + size_t bytes_uncompressed = s->compressor.sender_locked.total_uncompressed;
1454 + size_t bytes_compressed = s->compressor.sender_locked.total_compressed + s->compressor.sender_locked.total_compressions * sizeof(rrdpush_signature_t);
1455 + NETDATA_DOUBLE ratio = 100.0 - ((NETDATA_DOUBLE)bytes_compressed * 100.0 / (NETDATA_DOUBLE)bytes_uncompressed);
1456 + worker_set_metric(WORKER_SENDER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes_uncompressed);
1457 + worker_set_metric(WORKER_SENDER_JOB_BYTES_COMPRESSED, (NETDATA_DOUBLE)bytes_compressed);
1458 + worker_set_metric(WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO, ratio);
1459 + }
1460 sender_unlock(s);
1461
1462 worker_set_metric(WORKER_SENDER_JOB_BUFFER_RATIO, (NETDATA_DOUBLE)(s->buffer->max_size - available) * 100.0 / (NETDATA_DOUBLE)s->buffer->max_size);
streaming/stream.conf
+5 -2
@@ -168,11 +168,14 @@
168 # Stream Compression
169 # By default it is enabled.
170 # You can control stream compression in this parent agent stream with options: yes | no
171 - #enable compression = yes
171 + enable compression = yes
172 +
173 + # select the order the compression algorithms will be used, when multiple are offered by the child
174 + compression algorithms order = zstd lz4 gzip
175
176 # Replication
177 # Enable replication for all hosts using this api key. Default: enabled
175 - #enable replication = yes
178 + enable replication = yes
179
180 # How many seconds to replicate from each child. Default: a day
181 #seconds to replicate = 86400
web/api/web_api_v1.c
+1 -5
@@ -1272,12 +1272,8 @@ inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb)
1272 buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE);
1273 buffer_json_member_add_boolean(wb, "stream-enabled", default_rrdpush_enabled);
1274
1275 -#ifdef ENABLE_RRDPUSH_COMPRESSION
1275 buffer_json_member_add_boolean(wb, "stream-compression",
1277 - host->sender && stream_has_capability(host->sender, STREAM_CAP_COMPRESSION));
1278 -#else // ! ENABLE_RRDPUSH_COMPRESSION
1279 - buffer_json_member_add_boolean(wb, "stream-compression", false);
1280 -#endif // ENABLE_RRDPUSH_COMPRESSION
1276 + host->sender && host->sender->compressor.initialized);
1277
1278 #ifdef ENABLE_HTTPS
1279 buffer_json_member_add_boolean(wb, "https-enabled", true);