diff options
author | Vladimir Kobal <vlad@prokk.net> | 2020-11-05 19:08:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-05 19:08:17 +0200 |
commit | 943ee2482b16a81afd54b426f4fb0952f99c48e7 (patch) | |
tree | 5603b2bbd3cba974f665699a2b132256b2dfe912 | |
parent | edd6d02dec1c65593acb9d7b98dbb3f594d58c3a (diff) |
Add HTTP and HTTPS support to the simple exporting connector (#9911)
35 files changed, 846 insertions, 398 deletions
diff --git a/.gitignore b/.gitignore index 79851707cf..07dd9f0088 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,8 @@ cmake-build-release/ CMakeCache.txt CMakeFiles/ cmake_install.cmake +.cmake +compile_commands.json # jetbrains IDE .jetbrains* diff --git a/CMakeLists.txt b/CMakeLists.txt index ac72ec44fb..00f9f12b97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1270,6 +1270,7 @@ endif() -Wl,--wrap=connect_to_one_of -Wl,--wrap=create_main_rusage_chart -Wl,--wrap=send_main_rusage + -Wl,--wrap=simple_connector_end_batch ${PROMETHEUS_REMOTE_WRITE_LINK_OPTIONS} ${KINESIS_LINK_OPTIONS} ${PUBSUB_LINK_OPTIONS} diff --git a/Makefile.am b/Makefile.am index 24de58e66f..d5daff71d3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -955,6 +955,7 @@ if ENABLE_UNITTESTS -Wl,--wrap=connect_to_one_of \ -Wl,--wrap=create_main_rusage_chart \ -Wl,--wrap=send_main_rusage \ + -Wl,--wrap=simple_connector_end_batch \ $(TEST_LDFLAGS) \ $(NULL) exporting_tests_exporting_engine_testdriver_LDADD = $(NETDATA_COMMON_LIBS) $(TEST_LIBS) diff --git a/backends/backends.c b/backends/backends.c index 34df05f26c..074e18679d 100644 --- a/backends/backends.c +++ b/backends/backends.c @@ -551,7 +551,7 @@ void *backends_main(void *ptr) { case BACKEND_TYPE_OPENTSDB_USING_HTTP: { #ifdef ENABLE_HTTPS if (!strcmp(type, "opentsdb:https")) { - security_start_ssl(NETDATA_SSL_CONTEXT_OPENTSDB); + security_start_ssl(NETDATA_SSL_CONTEXT_EXPORTING); } #endif backend_set_opentsdb_http_variables(&default_port,&backend_response_checker,&backend_request_formatter); @@ -1001,9 +1001,9 @@ void *backends_main(void *ptr) { sock = connect_to_one_of(destination, default_port, &timeout, &reconnects, NULL, 0); #ifdef ENABLE_HTTPS if(sock != -1) { - if(netdata_opentsdb_ctx) { + if(netdata_exporting_ctx) { if(!opentsdb_ssl.conn) { - opentsdb_ssl.conn = SSL_new(netdata_opentsdb_ctx); + opentsdb_ssl.conn = SSL_new(netdata_exporting_ctx); if(!opentsdb_ssl.conn) { error("Failed to allocate SSL structure %d.", sock); opentsdb_ssl.flags = NETDATA_SSL_NO_HANDSHAKE; @@ -1229,7 +1229,7 @@ cleanup: buffer_free(response); #ifdef ENABLE_HTTPS - if(netdata_opentsdb_ctx) { + if(netdata_exporting_ctx) { if(opentsdb_ssl.conn) { SSL_free(opentsdb_ssl.conn); } diff --git a/exporting/README.md b/exporting/README.md index 1a04e4085d..8f490cc2ff 100644 --- a/exporting/README.md +++ b/exporting/README.md @@ -44,7 +44,7 @@ X seconds (though, it can send them per second if you need it to). also be configured). Learn more in our guide to [export and visualize Netdata metrics in Graphite](/docs/guides/export/export-netdata-metrics-graphite.md). - [**JSON** document databases](/exporting/json/README.md) - - [**OpenTSDB**](/exporting/opentsdb/README.md): Use a plaintext, HTTP, or HTTPS interfaces. Metrics are sent to + - [**OpenTSDB**](/exporting/opentsdb/README.md): Use a plaintext or HTTP interfaces. Metrics are sent to OpenTSDB as `prefix.chart.dimension` with tag `host=hostname`. - [**MongoDB**](/exporting/mongodb/README.md): Metrics are sent to the database in `JSON` format. - [**Prometheus**](/exporting/prometheus/README.md): Use an existing Prometheus installation to scrape metrics @@ -173,8 +173,10 @@ You can configure each connector individually using the available [options](#opt - `[prometheus:exporter]` defines settings for Prometheus exporter API queries (e.g.: `http://NODE:19999/api/v1/allmetrics?format=prometheus&help=yes&source=as-collected`). - `[<type>:<name>]` keeps settings for a particular exporting connector instance, where: - - `type` selects the exporting connector type: graphite | opentsdb:telnet | opentsdb:http | opentsdb:https | - prometheus_remote_write | json | kinesis | pubsub | mongodb + - `type` selects the exporting connector type: graphite | opentsdb:telnet | opentsdb:http | + prometheus_remote_write | json | kinesis | pubsub | mongodb. For graphite, opentsdb, + json, and prometheus_remote_write connectors you can also use `:http` or `:https` modifiers + (e.g.: `opentsdb:https`). - `name` can be arbitrary instance name you chose. ### Options @@ -270,6 +272,13 @@ Configure individual connectors and override any global settings with the follow > You can check how the host tags were parsed using the /api/v1/info API call. But, keep in mind that backends subsystem > is deprecated and will be deleted soon. Please move your existing tags to the `[host labels]` section. +## HTTPS + +Netdata can send metrics to external databases using the TLS/SSL protocol. Unfortunately, some of +them does not support encrypted connections, so you will have to configure a reverse proxy to enable +HTTPS communication between Netdata and an external database. You can set up a reverse proxy with +[Nginx](/docs/Running-behind-nginx.md). + ## Exporting engine monitoring Netdata creates five charts in the dashboard, under the **Netdata Monitoring** section, to help you monitor the health diff --git a/exporting/aws_kinesis/aws_kinesis.c b/exporting/aws_kinesis/aws_kinesis.c index 00600fa9bb..036afb49fc 100644 --- a/exporting/aws_kinesis/aws_kinesis.c +++ b/exporting/aws_kinesis/aws_kinesis.c @@ -48,7 +48,7 @@ int init_aws_kinesis_instance(struct instance *instance) instance->end_host_formatting = flush_host_labels; instance->end_batch_formatting = NULL; - instance->send_header = NULL; + instance->prepare_header = NULL; instance->check_response = NULL; instance->buffer = (void *)buffer_create(0); diff --git a/exporting/clean_connectors.c b/exporting/clean_connectors.c index 4ad644d69c..459777f0fd 100644 --- a/exporting/clean_connectors.c +++ b/exporting/clean_connectors.c @@ -35,3 +35,46 @@ void clean_instance(struct instance *instance) uv_cond_destroy(&instance->cond_var); // uv_mutex_destroy(&instance->mutex); } + +/** + * Clean up a simple connector instance on Netdata exit + * + * @param instance an instance data structure. + */ +void simple_connector_cleanup(struct instance *instance) +{ + info("EXPORTING: cleaning up instance %s ...", instance->config.name); + + struct simple_connector_data *simple_connector_data = + (struct simple_connector_data *)instance->connector_specific_data; + + buffer_free(instance->buffer); + buffer_free(simple_connector_data->buffer); + buffer_free(simple_connector_data->header); + + struct simple_connector_buffer *next_buffer = simple_connector_data->first_buffer; + for (int i = 0; i < instance->config.buffer_on_failures; i++) { + struct simple_connector_buffer *current_buffer = next_buffer; + next_buffer = next_buffer->next; + + buffer_free(current_buffer->header); + buffer_free(current_buffer->buffer); + freez(current_buffer); + } + +#ifdef ENABLE_HTTPS + if (simple_connector_data->conn) + SSL_free(simple_connector_data->conn); +#endif + + freez(simple_connector_data); + + struct simple_connector_config *simple_connector_config = + (struct simple_connector_config *)instance->config.connector_specific_config; + freez(simple_connector_config); + + info("EXPORTING: instance %s exited", instance->config.name); + instance->exited = 1; + + return; +} diff --git a/exporting/exporting_engine.h b/exporting/exporting_engine.h index b436f5d809..e0993c98f8 100644 --- a/exporting/exporting_engine.h +++ b/exporting/exporting_engine.h @@ -46,10 +46,12 @@ typedef enum exporting_options { typedef enum exporting_connector_types { EXPORTING_CONNECTOR_TYPE_UNKNOWN, // Invalid type EXPORTING_CONNECTOR_TYPE_GRAPHITE, // Send plain text to Graphite - EXPORTING_CONNECTOR_TYPE_OPENTSDB_USING_TELNET, // Send data to OpenTSDB using telnet API - EXPORTING_CONNECTOR_TYPE_OPENTSDB_USING_HTTP, // Send data to OpenTSDB using HTTP API - EXPORTING_CONNECTOR_TYPE_JSON, // Stores the data using JSON. - EXPORTING_CONNECTOR_TYPE_PROMETHEUS_REMOTE_WRITE, // The user selected to use Prometheus backend + EXPORTING_CONNECTOR_TYPE_GRAPHITE_HTTP, // Send data to Graphite using HTTP API + EXPORTING_CONNECTOR_TYPE_JSON, // Send data in JSON format + EXPORTING_CONNECTOR_TYPE_JSON_HTTP, // Send data in JSON format using HTTP API + EXPORTING_CONNECTOR_TYPE_OPENTSDB, // Send data to OpenTSDB using telnet API + EXPORTING_CONNECTOR_TYPE_OPENTSDB_HTTP, // Send data to OpenTSDB using HTTP API + EXPORTING_CONNECTOR_TYPE_PROMETHEUS_REMOTE_WRITE, // User selected to use Prometheus backend EXPORTING_CONNECTOR_TYPE_KINESIS, // Send message to AWS Kinesis EXPORTING_CONNECTOR_TYPE_PUBSUB, // Send message to Google Cloud Pub/Sub EXPORTING_CONNECTOR_TYPE_MONGODB, // Send data to MongoDB collection @@ -81,6 +83,38 @@ struct simple_connector_config { int default_port; }; +struct simple_connector_buffer { + BUFFER *header; + BUFFER *buffer; + + size_t buffered_metrics; + size_t buffered_bytes; + + int used; + + struct simple_connector_buffer *next; +}; + +struct simple_connector_data { + void *connector_specific_data; + + size_t total_buffered_metrics; + + BUFFER *header; + BUFFER *buffer; + size_t buffered_metrics; + size_t buffered_bytes; + + struct simple_connector_buffer *previous_buffer; + struct simple_connector_buffer *first_buffer; + struct simple_connector_buffer *last_buffer; + +#ifdef ENABLE_HTTPS + SSL *conn; //SSL connection + int flags; //The flags for SSL connection +#endif +}; + struct prometheus_remote_write_specific_config { char *remote_write_path; }; @@ -175,7 +209,7 @@ struct instance { int (*end_host_formatting)(struct instance *instance, RRDHOST *host); int (*end_batch_formatting)(struct instance *instance); - int (*send_header)(int *sock, struct instance *instance); + void (*prepare_header)(struct instance *instance); int (*check_response)(BUFFER *buffer, struct instance *instance); void *connector_specific_data; @@ -210,6 +244,7 @@ struct engine *read_exporting_config(); EXPORTING_CONNECTOR_TYPE exporting_select_type(const char *type); int init_connectors(struct engine *engine); +void simple_connector_init(struct instance *instance); int mark_scheduled_instances(struct engine *engine); void prepare_buffers(struct engine *engine); @@ -232,11 +267,12 @@ void end_chart_formatting(struct engine *engine, RRDSET *st); void end_host_formatting(struct engine *engine, RRDHOST *host); void end_batch_formatting(struct engine *engine); int flush_host_labels(struct instance *instance, RRDHOST *host); -int simple_connector_update_buffered_bytes(struct instance *instance); +int simple_connector_end_batch(struct instance *instance); int exporting_discard_response(BUFFER *buffer, struct instance *instance); void simple_connector_receive_response(int *sock, struct instance *instance); -void simple_connector_send_buffer(int *sock, int *failures, struct instance *instance); +void simple_connector_send_buffer( + int *sock, int *failures, struct instance *instance, BUFFER *header, BUFFER *buffer, size_t buffered_metrics); void simple_connector_worker(void *instance_p); void create_main_rusage_chart(RRDSET **st_rusage, RRDDIM **rd_user, RRDDIM **rd_system); @@ -244,6 +280,7 @@ void send_main_rusage(RRDSET *st_rusage, RRDDIM *rd_user, RRDDIM *rd_system); void send_internal_metrics(struct instance *instance); extern void clean_instance(struct instance *ptr); +void simple_connector_cleanup(struct instance *instance); static inline void disable_instance(struct instance *instance) { diff --git a/exporting/graphite/README.md b/exporting/graphite/README.md index 95b8ef954d..2ac7adc7af 100644 --- a/exporting/graphite/README.md +++ b/exporting/graphite/README.md @@ -3,6 +3,7 @@ title: "Export metrics to Graphite providers" sidebar_label: Graphite description: "Archive your Agent's metrics to a any Graphite database provider for long-term storage, further analysis, or correlation with data from other sources." custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/graphite/README.md +sidebar_label: Graphite --> # Export metrics to Graphite providers @@ -21,6 +22,9 @@ directory and set the following options: destination = localhost:2003 ``` +Add `:http` or `:https` modifiers to the connector type if you need to use other than a plaintext protocol. For example: `graphite:http:my_graphite_instance`, +`graphite:https:my_graphite_instance`. + The Graphite connector is further configurable using additional settings. See the [exporting reference doc](/exporting/README.md#options) for details. diff --git a/exporting/graphite/graphite.c b/exporting/graphite/graphite.c index 6aad2054db..23e40c369e 100644 --- a/exporting/graphite/graphite.c +++ b/exporting/graphite/graphite.c @@ -16,6 +16,17 @@ int init_graphite_instance(struct instance *instance) instance->config.connector_specific_config = (void *)connector_specific_config; connector_specific_config->default_port = 2003; + struct simple_connector_data *connector_specific_data = callocz(1, sizeof(struct simple_connector_data)); + instance->connector_specific_data = connector_specific_data; + +#ifdef ENABLE_HTTPS + connector_specific_data->flags = NETDATA_SSL_START; + connector_specific_data->conn = NULL; + if (instance->config.options & EXPORTING_OPTION_USE_TLS) { + security_start_ssl(NETDATA_SSL_CONTEXT_EXPORTING); + } +#endif + instance->start_batch_formatting = NULL; instance->start_host_formatting = format_host_labels_graphite_plaintext; instance->start_chart_formatting = NULL; @@ -27,9 +38,13 @@ int init_graphite_instance(struct instance *instance) instance->end_chart_formatting = NULL; instance->end_host_formatting = flush_host_labels; - instance->end_batch_formatting = simple_connector_update_buffered_bytes; + instance->end_batch_formatting = simple_connector_end_batch; + + if (instance->config.type == EXPORTING_CONNECTOR_TYPE_GRAPHITE_HTTP) + instance->prepare_header = graphite_http_prepare_header; + else + instance->prepare_header = NULL; - instance->send_header = NULL; instance->check_response = exporting_discard_response; instance->buffer = (void *)buffer_create(0); @@ -37,6 +52,9 @@ int init_graphite_instance(struct instance *instance) error("EXPORTING: cannot create buffer for graphite exporting connector instance %s", instance->config.name); return 1; } + + simple_connector_init(instance); + if (uv_mutex_init(&instance->mutex)) return 1; if (uv_cond_init(&instance->cond_var)) @@ -187,3 +205,26 @@ int format_dimension_stored_graphite_plaintext(struct instance *instance, RRDDIM return 0; } + +/** + * Ppepare HTTP header + * + * @param instance an instance data structure. + * @return Returns 0 on success, 1 on failure. + */ +void graphite_http_prepare_header(struct instance *instance) +{ + struct simple_connector_data *simple_connector_data = instance->connector_specific_data; + + buffer_sprintf( + simple_connector_data->last_buffer->header, + "POST /api/put HTTP/1.1\r\n" + "Host: %s\r\n" + "Content-Type: application/graphite\r\n" + "Content-Length: %lu\r\n" + "\r\n", + instance->config.destination, + buffer_strlen(simple_connector_data->last_buffer->buffer)); + + return; +} diff --git a/exporting/graphite/graphite.h b/exporting/graphite/graphite.h index edda498e85..993c12e57a 100644 --- a/exporting/graphite/graphite.h +++ b/exporting/graphite/graphite.h @@ -13,4 +13,6 @@ int format_host_labels_graphite_plaintext(struct instance *instance, RRDHOST *ho int format_dimension_collected_graphite_plaintext(struct instance *instance, RRDDIM *rd); int format_dimension_stored_graphite_plaintext(struct instance *instance, RRDDIM *rd); +void graphite_http_prepare_header(struct instance *instance); + #endif //NETDATA_EXPORTING_GRAPHITE_H diff --git a/exporting/init_connectors.c b/exporting/init_connectors.c index 4a68118970..57ac030d9a 100644 --- a/exporting/init_connectors.c +++ b/exporting/init_connectors.c @@ -40,15 +40,23 @@ int init_connectors(struct engine *engine) if (init_graphite_instance(instance) != 0) return 1; break; + case EXPORTING_CONNECTOR_TYPE_GRAPHITE_HTTP: + if (init_graphite_instance(instance) != 0) + return 1; + break; case EXPORTING_CONNECTOR_TYPE_JSON: if (init_json_instance(instance) != 0) return 1; break; - case EXPORTING_CONNECTOR_TYPE_OPENTSDB_USING_TELNET: + case EXPORTING_CONNECTOR_TYPE_JSON_HTTP: + if (init_json_http_instance(instance) != 0) + return 1; + break; + case EXPORTING_CONNECTOR_TYPE_OPENTSDB: if (init_opentsdb_telnet_instance(instance) != 0) return 1; break; - case EXPORTING_CONNECTOR_TYPE_OPENTSDB_USING_HTTP: + case EXPORTING_CONNECTOR_TYPE_OPENTSDB_HTTP: if (init_opentsdb_http_instance(instance) != 0) return 1; break; @@ -96,3 +104,36 @@ int init_connectors(struct engine *engine) return 0; } + +/** + * Initialize a ring buffer for a simple connector + * + * @param instance an instance data structure. + */ +void simple_connector_init(struct instance *instance) +{ + struct simple_connector_data *connector_specific_data = + (struct simple_connector_data *)instance->connector_specific_data; + + // create a ring buffer + struct simple_connector_buffer *first_buffer = NULL; + + if (instance->config.buffer_on_failures < 1) + instance->config.buffer_on_failures = 1; + + for (int i = 0; i < instance->config.buffer_on_failures; i++) { + struct simple_connector_buffer *current_buffer = callocz(1, sizeof(struct simple_connector_buffer)); + + if (!connector_specific_data->first_buffer) + first_buffer = current_buffer; + else + current_buffer->next = connector_specific_data->first_buffer; + + connector_specific_data->first_buffer = current_buffer; + } + + first_buffer->next = connector_specific_data->first_buffer; + connector_specific_data->last_buffer = connector_specific_data->first_buffer; + + return; +} diff --git a/exporting/json/README.md b/exporting/json/README.md index 8ef084cf22..1dd41e738e 100644 --- a/exporting/json/README.md +++ b/exporting/json/README.md @@ -3,6 +3,7 @@ title: "Export metrics to JSON document databases" sidebar_label: JSON description: "Archive your Agent's metrics to a JSON document database for long-term storage, further analysis, or correlation with data from other sources." custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/json/README.md +sidebar_label: JSON Document Databases --> # Export metrics to JSON document databases @@ -21,6 +22,9 @@ directory and set the following options: destination = localhost:5448 ``` +Add `:http` or `:https` modifiers to the connector type if you need to use other than a plaintext protocol. For example: `json:http:my_json_instance`, +`json:https:my_json_instance`. + The JSON connector is further configurable using additional settings. See the [exporting reference doc](/exporting/README.md#options) for details. diff --git a/exporting/json/json.c b/exporting/json/json.c index cea7eff07c..458e7e8cce 100644 --- a/exporting/json/json.c +++ b/exporting/json/json.c @@ -16,6 +16,9 @@ int init_json_instance(struct instance *instance) instance->config.connector_specific_config = (void *)connector_specific_config; connector_specific_config->default_port = 5448; + struct simple_connector_data *connector_specific_data = callocz(1, sizeof(struct simple_connector_data)); + instance->connector_specific_data = connector_specific_data; + instance->start_batch_formatting = NULL; instance->start_host_formatting = format_host_labels_json_plaintext; instance->start_chart_formatting = NULL; @@ -27,9 +30,10 @@ int init_json_instance(struct instance *instance) instance->end_chart_formatting = NULL; instance->end_host_formatting = flush_host_labels; - instance->end_batch_formatting = simple_connector_update_buffered_bytes; + instance->end_batch_formatting = simple_connector_end_batch; + + instance->prepare_header = NULL; - instance->send_header = NULL; instance->check_response = exporting_discard_response; instance->buffer = (void *)buffer_create(0); @@ -37,6 +41,63 @@ int init_json_instance(struct instance *instance) error("EXPORTING: cannot create buffer for json exporting connector instance %s", instance->config.name); return 1; } + + simple_connector_init(instance); + + if (uv_mutex_init(&instance->mutex)) + return 1; + if (uv_cond_init(&instance->cond_var)) + return 1; + + return 0; +} + +/** + * Initialize JSON connector instance for HTTP protocol + * + * @param instance an instance data structure. + * @return Returns 0 on success, 1 on failure. + */ +int init_json_http_instance(struct instance *instance) +{ + instance->worker = simple_connector_worker; + + struct simple_connector_config *connector_specific_config = callocz(1, sizeof(struct simple_connector_config)); + instance->config.connector_specific_config = (void *)connector_specific_config; + connector_specific_config->default_port = 5448; + + struct simple_connector_data *connector_specific_data = callocz(1, sizeof(struct simple_connector_data)); + instance->connector_specific_data = connector_specific_data; + +#ifdef ENABLE_HTTPS + connector_specific_data->flags = NETDATA_SSL_START; + connector_specific_data->conn = NULL; + if (instance->config.options & EXPORTING_OPTION_USE_TLS) { + security_start_ssl(NETDATA_SSL_CONTEXT_EXPORTING); + } +#endif + + instance->start_batch_formatting = open_batch_json_http; + instance->start_host_formatting = format_host_labels_json_plaintext; + instance->start_chart_formatting = NULL; + + if (EXPORTING_OPTIONS_DATA_SOURCE(instance->config.options) == EXPORTING_SOURCE_DATA_AS_COLLECTED) + instance->metric_formatting = format_dimension_collected_json_plaintext; + else + instance->metric_formatting = format_dimension_stored_json_plaintext; + + instance->end_chart_formatting = NULL; + instance->end_host_formatting = flush_host_labels; + instance->end_batch_formatting = close_batch_json_http; + + instance->prepare_header = json_http_prepare_header; + + instance->check_response = exporting_discard_response; + + instance->buffer = (void *)buffer_create(0); + + simple_connector_init(instance); + if (uv_mutex_init(&instance->mutex)) return 1; if (uv_cond_init(&instance->cond_var)) @@ -111,6 +172,11 @@ int format_dimension_collected_json_plaintext(struct instance *instance, RRDDIM } } + if (instance->config.type == EXPORTING_CONNECTOR_TYPE_JSON_HTTP) { + if (buffer_strlen((BUFFER *)instance->buffer) > 2) + buffer_strcat(instance->buffer, ",\n"); + } + buffer_sprintf( instance->buffer, @@ -131,7 +197,7 @@ int format_dimension_collected_json_plaintext(struct instance *instance, RRDDIM "\"name\":\"%s\"," "\"value\":" COLLECTED_NUMBER_FORMAT "," - "\"timestamp\":%llu}\n", + "\"timestamp\":%llu}", instance->config.prefix, (host == localhost) ? engine->config.hostname : host->hostname, @@ -153,6 +219,10 @@ int format_dimension_collected_json_plaintext(struct instance *instance, RRDDIM (unsigned long long)rd->last_collected_time.tv_sec); + if (instance->config.type != EXPORTING_CONNECTOR_TYPE_JSON_HTTP) { + buffer_strcat(instance->buffer, "\n"); + } + return 0; } @@ -189,6 +259,11 @@ int format_dimension_stored_json_plaintext(struct instance *instance, RRDD |