diff --git a/CMakeLists.txt b/CMakeLists.txt index dc5a11848a3e0f..ebe892d54dbf1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -438,6 +438,7 @@ check_function_exists(backtrace HAVE_BACKTRACE) check_function_exists(arc4random_buf HAVE_ARC4RANDOM_BUF) check_function_exists(arc4random_uniform HAVE_ARC4RANDOM_UNIFORM) check_function_exists(getrandom HAVE_GETRANDOM) +check_function_exists(sysinfo HAVE_SYSINFO) # # check source compilation @@ -475,6 +476,14 @@ int main() { } " HAVE_C_MALLOPT) +check_c_source_compiles(" +#include +int main() { + malloc_trim(0); + return 0; +} +" HAVE_C_MALLOC_TRIM) + check_c_source_compiles(" #define _GNU_SOURCE #include @@ -920,6 +929,21 @@ set(LIBNETDATA_FILES src/libnetdata/xxHash/xxhash.h src/libnetdata/os/random.c src/libnetdata/os/random.h + src/libnetdata/socket/nd-sock.c + src/libnetdata/socket/nd-sock.h + src/libnetdata/socket/listen-sockets.c + src/libnetdata/socket/listen-sockets.h + src/libnetdata/socket/poll-events.c + src/libnetdata/socket/poll-events.h + src/libnetdata/socket/connect-to.c + src/libnetdata/socket/connect-to.h + src/libnetdata/socket/socket-peers.c + src/libnetdata/socket/socket-peers.h + src/libnetdata/libjudy/judyl-typed.h + src/libnetdata/os/system_memory.c + src/libnetdata/os/system_memory.h + src/libnetdata/socket/nd-poll.c + src/libnetdata/socket/nd-poll.h ) set(LIBH2O_FILES @@ -1013,8 +1037,8 @@ set(DAEMON_FILES src/daemon/daemon.h src/daemon/libuv_workers.c src/daemon/libuv_workers.h - src/daemon/global_statistics.c - src/daemon/global_statistics.h + src/daemon/telemetry/telemetry.c + src/daemon/telemetry/telemetry.h src/daemon/analytics.c src/daemon/analytics.h src/daemon/main.c @@ -1035,15 +1059,59 @@ set(DAEMON_FILES src/daemon/pipename.h src/daemon/unit_test.c src/daemon/unit_test.h - src/daemon/config/dyncfg.c - src/daemon/config/dyncfg.h - src/daemon/config/dyncfg-files.c - src/daemon/config/dyncfg-unittest.c - src/daemon/config/dyncfg-inline.c - src/daemon/config/dyncfg-echo.c - src/daemon/config/dyncfg-internals.h - src/daemon/config/dyncfg-intercept.c - src/daemon/config/dyncfg-tree.c + src/daemon/dyncfg/dyncfg.c + src/daemon/dyncfg/dyncfg.h + src/daemon/dyncfg/dyncfg-files.c + src/daemon/dyncfg/dyncfg-unittest.c + src/daemon/dyncfg/dyncfg-inline.c + src/daemon/dyncfg/dyncfg-echo.c + src/daemon/dyncfg/dyncfg-internals.h + src/daemon/dyncfg/dyncfg-intercept.c + src/daemon/dyncfg/dyncfg-tree.c + src/daemon/telemetry/telemetry-http-api.c + src/daemon/telemetry/telemetry-http-api.h + src/daemon/telemetry/telemetry-queries.c + src/daemon/telemetry/telemetry-queries.h + src/daemon/telemetry/telemetry-ingestion.c + src/daemon/telemetry/telemetry-ingestion.h + src/daemon/telemetry/telemetry-ml.c + src/daemon/telemetry/telemetry-ml.h + src/daemon/telemetry/telemetry-gorilla.c + src/daemon/telemetry/telemetry-gorilla.h + src/daemon/telemetry/telemetry-daemon.c + src/daemon/telemetry/telemetry-daemon.h + src/daemon/telemetry/telemetry-daemon-memory.c + src/daemon/telemetry/telemetry-daemon-memory.h + src/daemon/telemetry/telemetry-sqlite3.c + src/daemon/telemetry/telemetry-sqlite3.h + src/daemon/telemetry/telemetry-dbengine.c + src/daemon/telemetry/telemetry-dbengine.h + src/daemon/telemetry/telemetry-string.c + src/daemon/telemetry/telemetry-string.h + src/daemon/telemetry/telemetry-heartbeat.c + src/daemon/telemetry/telemetry-heartbeat.h + src/daemon/telemetry/telemetry-dictionary.c + src/daemon/telemetry/telemetry-dictionary.h + src/daemon/telemetry/telemetry-workers.c + src/daemon/telemetry/telemetry-workers.h + src/daemon/telemetry/telemetry-trace-allocations.c + src/daemon/telemetry/telemetry-trace-allocations.h + src/daemon/telemetry/telemetry-aral.c + src/daemon/telemetry/telemetry-aral.h + src/daemon/config/netdata-conf-db.c + src/daemon/config/netdata-conf-db.h + src/daemon/config/netdata-conf.h + src/daemon/config/netdata-conf-backwards-compatibility.c + src/daemon/config/netdata-conf-backwards-compatibility.h + src/daemon/config/netdata-conf-web.c + src/daemon/config/netdata-conf-web.h + src/daemon/config/netdata-conf-directories.c + src/daemon/config/netdata-conf-directories.h + src/daemon/config/netdata-conf-logs.c + src/daemon/config/netdata-conf-logs.h + src/daemon/config/netdata-conf-global.c + src/daemon/config/netdata-conf-global.h + src/daemon/config/netdata-conf.c ) set(H2O_FILES @@ -1227,15 +1295,34 @@ if(ENABLE_ML) set(ML_FILES src/ml/ad_charts.h src/ml/ad_charts.cc - src/ml/Config.cc src/ml/dlib/dlib/all/source.cpp - src/ml/ml.h src/ml/ml.cc - src/ml/ml-private.h + src/ml/ml_calculated_number.h + src/ml/ml_host.h + src/ml/ml_config.h + src/ml/ml_config.cc + src/ml/ml_dimension.h + src/ml/ml_enums.h + src/ml/ml_enums.cc + src/ml/ml_features.h + src/ml/ml_features.cc + src/ml/ml_kmeans.h + src/ml/ml_kmeans.cc + src/ml/ml_queue.h + src/ml/ml_worker.h + src/ml/ml_string_wrapper.h + src/ml/ml_queue.cc + src/ml/ml_private.h + src/ml/ml_public.h + src/ml/ml_public.cc ) + + if(NOT ENABLE_MIMALLOC) + list(APPEND ML_FILES src/ml/ml_memory.cc) + endif() else() set(ML_FILES - src/ml/ml.h + src/ml/ml_public.h src/ml/ml-dummy.c ) endif() @@ -1338,6 +1425,8 @@ set(RRD_PLUGIN_FILES src/database/rrdfunctions-exporters.h src/database/rrdfunctions-internals.h src/database/rrdcollector-internals.h + src/database/rrd-database-mode.h + src/database/rrd-database-mode.c ) if(ENABLE_DBENGINE) @@ -1405,7 +1494,7 @@ set(SYSTEMD_JOURNAL_PLUGIN_FILES ) set(STREAMING_PLUGIN_FILES - src/streaming/rrdpush.h + src/streaming/stream.h src/streaming/stream-compression/compression.c src/streaming/stream-compression/compression.h src/streaming/stream-compression/brotli.c @@ -1416,8 +1505,8 @@ set(STREAMING_PLUGIN_FILES src/streaming/stream-compression/lz4.h src/streaming/stream-compression/zstd.c src/streaming/stream-compression/zstd.h - src/streaming/receiver.c - src/streaming/sender.c + src/streaming/stream-receiver.c + src/streaming/stream-sender.c src/streaming/replication.c src/streaming/replication.h src/streaming/h2o-common.h @@ -1429,11 +1518,11 @@ set(STREAMING_PLUGIN_FILES src/streaming/stream-path.h src/streaming/stream-capabilities.c src/streaming/stream-capabilities.h - src/streaming/sender-connect.c - src/streaming/sender-internals.h - src/streaming/sender-execute.c - src/streaming/sender-commit.c - src/streaming/sender-destinations.c + src/streaming/stream-connector.c + src/streaming/stream-sender-internals.h + src/streaming/stream-sender-execute.c + src/streaming/stream-sender-commit.c + src/streaming/stream-parents.c src/streaming/stream-handshake.c src/streaming/protocol/command-function.c src/streaming/protocol/command-host-labels.c @@ -1443,11 +1532,17 @@ set(STREAMING_PLUGIN_FILES src/streaming/stream-conf.c src/streaming/stream-conf.h src/streaming/stream-handshake.h - src/streaming/sender.h - src/streaming/sender-destinations.h + src/streaming/stream-parents.h src/streaming/rrdhost-status.c src/streaming/rrdhost-status.h - src/streaming/receiver.h + src/streaming/stream-sender-api.c + src/streaming/stream-receiver-internals.h + src/streaming/stream-receiver-api.c + src/streaming/stream-thread.c + src/streaming/stream-thread.h + src/streaming/stream-receiver-connection.c + src/streaming/stream-sender-commit.h + src/streaming/stream-traffic-types.h ) set(WEB_PLUGIN_FILES @@ -1459,6 +1554,7 @@ set(WEB_PLUGIN_FILES src/web/server/static/static-threaded.h src/web/server/web_client_cache.c src/web/server/web_client_cache.h + src/web/api/v3/api_v3_stream_info.c src/web/api/v3/api_v3_stream_path.c ) diff --git a/docs/developer-and-contributor-corner/python-collector.txt b/docs/developer-and-contributor-corner/python-collector.txt index f846b347b680c9..9f75a030d6eb51 100644 --- a/docs/developer-and-contributor-corner/python-collector.txt +++ b/docs/developer-and-contributor-corner/python-collector.txt @@ -115,7 +115,7 @@ context, charttype]`, where: - `family`: An identifier used to group charts together (can be null). - `context`: An identifier used to group contextually similar charts together. The best practice is to provide a context that is `A.B`, with `A` being the name of the collector, and `B` being the name of the specific metric. -- `charttype`: Either `line`, `area`, or `stacked`. If null line is the default value. +- `charttype`: Either `line`, `area`, `stacked` or `heatmap`. If null line is the default value. You can read more about `family` and `context` in the [Netdata Charts](/docs/dashboards-and-charts/netdata-charts.md) doc. diff --git a/docs/diagrams/data_structures/web.svg b/docs/diagrams/data_structures/web.svg index bf05698a2ef42b..1f5f2b68cffe48 100644 --- a/docs/diagrams/data_structures/web.svg +++ b/docs/diagrams/data_structures/web.svg @@ -1,2 +1,2 @@ -web_clientunsigned long long idWEB_CLIENT_FLAGS flagsWEB_CLIENT_MODE modeWEB_CLIENT_ACL aclsize_t header_parse_triessize_t header_parse_last_sizeint tcp_corkint ifdint ofdchar client_ip[NI_MAXHOST+1}char client_port[NI_MAXSERV+1]char decoded_url[NETDATA_WEB_REQUEST_URL_SIZE+1char last_url[NETDATA_WEB_REQUEST_URL_SIZE+1]struct timeval tv_in, tv_readychar cookie1[NETDATA_WEB_REQUEST_COOKIE_SIZE+1]char cookie2[NETDATA_WEB_REQUEST_COOKIE_SIZE+1]char origin[NETDATA_WEB_REQUEST_ORIGIN_HEADER_SIZE+1]char *user_agentstruct response responsesize_t stats_received_bytessize_t stats_sent_bytesstruct web_client *prev
double linked list of
double linked list of
struct web_client *nextnetdata_thread_t threadvolatile int runningsize_t pollinfo_slotsize_t pollinfo_filecopy_slotresponseBUFFER *headerBUFFER *header_outputBUFFER *dataint codesize_t rlensize_t sentint zoutputz_stream zstreamBytef zbuffer[NETDATA_WEB_RESPONSE_ZLIB_CHUNK_SIZE]size_t zsentsize_t zhaveunsigned int zinitializedclients_cachepid_t pidstruct web_client *usedsize_t used_countstruct web_client *availsize_t avail_countsize_t reusedsize_t allocated
linked list of
linked list of
linked list of
linked list of
listen_socketsstruct config *configconst char *config_sectionconst char *default_bind_touint16_t default_portint backlogsize_t openedsize_t failedint fds[MAX_LISTEN_FDS]int *fds_names[MAX_LISTEN_FDS]int fds_types[MAX_LISTEN_FDS]int fds_families[MAX_LISTEN_FDS]POLLINFOPOLLJOB *psize_t slotint fdint socktypechar *client_ipchar *client_porttime_t connected_ttime_t last_received_ttime_t last_sent_tsize_t recv_countsize_t send_countuint32_t flagsvoid (*del_callback)int (*rcv_callback)int (*snd_callback)void *datastruct pollinfo *nextPOLLJOBsize_t slotssize_t usedsize_t minsize_t maxsize_t limittime_t complete_request_timeouttime_t idle_timeouttime_t check_everytime_t timer_millisecondsvoid *timer_datastruct pollfd *fdsstruct pollinfo *infstruct pollinfo *first_freeSIMPLE_PATTERN *access_listvoid *(*add_callback)void (*dell_callback)int (*rcv_callback)int (*snd_callback)void (*tmr_callback)
\ No newline at end of file +web_clientunsigned long long idWEB_CLIENT_FLAGS flagsWEB_CLIENT_MODE modeWEB_CLIENT_ACL aclsize_t header_parse_triessize_t header_parse_last_sizeint tcp_corkint ifdint ofdchar client_ip[NI_MAXHOST+1}char client_port[NI_MAXSERV+1]char decoded_url[NETDATA_WEB_REQUEST_URL_SIZE+1char last_url[NETDATA_WEB_REQUEST_URL_SIZE+1]struct timeval tv_in, tv_readychar cookie1[NETDATA_WEB_REQUEST_COOKIE_SIZE+1]char cookie2[NETDATA_WEB_REQUEST_COOKIE_SIZE+1]char origin[NETDATA_WEB_REQUEST_ORIGIN_HEADER_SIZE+1]char *user_agentstruct response responsesize_t stats_received_bytessize_t stats_sent_bytesstruct web_client *prev
double linked list of
double linked list of
struct web_client *nextnetdata_thread_t threadvolatile int runningsize_t pollinfo_slotsize_t pollinfo_filecopy_slotresponseBUFFER *headerBUFFER *header_outputBUFFER *dataint codesize_t rlensize_t sentint zoutputz_stream zstreamBytef zbuffer[NETDATA_WEB_RESPONSE_ZLIB_CHUNK_SIZE]size_t zsentsize_t zhaveunsigned int zinitializedclients_cachepid_t pidstruct web_client *usedsize_t used_countstruct web_client *availsize_t avail_countsize_t reusedsize_t allocated
linked list of
linked list of
linked list of
linked list of
listen_socketsstruct config *configconst char *config_sectionconst char *default_bind_touint16_t default_portint backlogsize_t openedsize_t failedint fds[MAX_LISTEN_FDS]int *fds_names[MAX_LISTEN_FDS]int fds_types[MAX_LISTEN_FDS]int fds_families[MAX_LISTEN_FDS]POLLINFOPOLLJOB *psize_t slotint fdint socktypechar *client_ipchar *client_porttime_t connected_ttime_t last_received_ttime_t last_sent_tsize_t recv_countsize_t send_countuint32_t flagsvoid (*del_callback)int (*rcv_callback)int (*snd_callback)void *datastruct pollinfo *nextPOLLJOBsize_t slotssize_t usedsize_t minsize_t maxsize_t limittime_t complete_request_timeouttime_t idle_timeouttime_t check_everytime_t timer_millisecondsvoid *timer_datastruct run *fdsstruct pollinfo *infstruct pollinfo *first_freeSIMPLE_PATTERN *access_listvoid *(*add_callback)void (*dell_callback)int (*rcv_callback)int (*snd_callback)void (*tmr_callback)
\ No newline at end of file diff --git a/packaging/cmake/config.cmake.h.in b/packaging/cmake/config.cmake.h.in index f9b3e86c438545..d82cf52ad7defc 100644 --- a/packaging/cmake/config.cmake.h.in +++ b/packaging/cmake/config.cmake.h.in @@ -73,6 +73,7 @@ #cmakedefine HAVE_ARC4RANDOM_UNIFORM #cmakedefine HAVE_RAND_S #cmakedefine HAVE_GETRANDOM +#cmakedefine HAVE_SYSINFO #cmakedefine HAVE_BACKTRACE #cmakedefine HAVE_CLOSE_RANGE @@ -95,6 +96,7 @@ #cmakedefine STRERROR_R_CHAR_P #cmakedefine HAVE_C__GENERIC #cmakedefine HAVE_C_MALLOPT +#cmakedefine HAVE_C_MALLOC_TRIM #cmakedefine HAVE_SETNS #cmakedefine HAVE_STRNDUP #cmakedefine SSL_HAS_PENDING diff --git a/src/aclk/aclk.c b/src/aclk/aclk.c index 7bc620a61348a4..f2dc8d91ac0d81 100644 --- a/src/aclk/aclk.c +++ b/src/aclk/aclk.c @@ -201,7 +201,7 @@ static int wait_till_agent_claim_ready() // We trap the impossible NULL here to keep the linter happy without using a fatal() in the code. const char *cloud_base_url = cloud_config_url_get(); if (cloud_base_url == NULL) { - netdata_log_error("Do not move the \"url\" out of post_conf_load!!"); + netdata_log_error("Do not move the \"url\" out of netdata_conf_section_global_run_as_user!!"); return 1; } @@ -559,7 +559,7 @@ static int aclk_attempt_to_connect(mqtt_wss_client client) while (service_running(SERVICE_ACLK)) { aclk_cloud_base_url = cloud_config_url_get(); if (aclk_cloud_base_url == NULL) { - error_report("Do not move the \"url\" out of post_conf_load!!"); + error_report("Do not move the \"url\" out of netdata_conf_section_global_run_as_user!!"); aclk_status = ACLK_STATUS_NO_CLOUD_URL; return -1; } @@ -868,7 +868,7 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable) create_query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE; create_query->data.bin_payload.msg_name = "CreateNodeInstance"; nd_log(NDLS_DAEMON, NDLP_DEBUG, - "Registering host=%s, hops=%u", host->machine_guid, host->system_info->hops); + "Registering host=%s, hops=%d", host->machine_guid, host->system_info->hops); aclk_execute_query(create_query); return; @@ -892,7 +892,7 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable) query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update); nd_log(NDLS_DAEMON, NDLP_DEBUG, - "Queuing status update for node=%s, live=%d, hops=%u, queryable=%d", + "Queuing status update for node=%s, live=%d, hops=%d, queryable=%d", (char*)node_state_update.node_id, cmd, host->system_info->hops, queryable); freez((void*)node_state_update.node_id); query->data.bin_payload.msg_name = "UpdateNodeInstanceConnection"; diff --git a/src/aclk/aclk_capas.c b/src/aclk/aclk_capas.c index dee6bf0c5eb6c3..a93d709999118a 100644 --- a/src/aclk/aclk_capas.c +++ b/src/aclk/aclk_capas.c @@ -2,7 +2,7 @@ #include "aclk_capas.h" -#include "ml/ml.h" +#include "ml/ml_public.h" #define HTTP_API_V2_VERSION 7 @@ -31,14 +31,14 @@ const struct capability *aclk_get_agent_capas() agent_capabilities[3].version = metric_correlations_version; agent_capabilities[3].enabled = 1; - agent_capabilities[7].enabled = localhost->health.health_enabled; + agent_capabilities[7].enabled = localhost->health.enabled; return agent_capabilities; } struct capability *aclk_get_node_instance_capas(RRDHOST *host) { - bool functions = (host == localhost || (host->receiver && stream_has_capability(host->receiver, STREAM_CAP_FUNCTIONS))); + bool functions = (host == localhost || receiver_has_capability(host, STREAM_CAP_FUNCTIONS)); bool dyncfg = (host == localhost || dyncfg_available_for_rrdhost(host)); struct capability ni_caps[] = { @@ -48,7 +48,7 @@ struct capability *aclk_get_node_instance_capas(RRDHOST *host) { .name = "ctx", .version = 1, .enabled = 1 }, { .name = "funcs", .version = functions ? 1 : 0, .enabled = functions ? 1 : 0 }, { .name = "http_api_v2", .version = HTTP_API_V2_VERSION, .enabled = 1 }, - { .name = "health", .version = 2, .enabled = host->health.health_enabled }, + { .name = "health", .version = 2, .enabled = host->health.enabled}, { .name = "req_cancel", .version = 1, .enabled = 1 }, { .name = "dyncfg", .version = 2, .enabled = dyncfg }, { .name = NULL, .version = 0, .enabled = 0 } diff --git a/src/aclk/https_client.c b/src/aclk/https_client.c index f144eaf15d5942..f04683be0ce985 100644 --- a/src/aclk/https_client.c +++ b/src/aclk/https_client.c @@ -6,7 +6,7 @@ #include "aclk_util.h" -#include "daemon/global_statistics.h" +#include "daemon/telemetry/telemetry.h" static const char *http_req_type_to_str(http_req_type_t req) { switch (req) { diff --git a/src/claim/claim.c b/src/claim/claim.c index 24e4e1c3c557c8..f815dbf5d60ee5 100644 --- a/src/claim/claim.c +++ b/src/claim/claim.c @@ -189,7 +189,7 @@ CLOUD_STATUS claim_reload_and_wait_online(void) { cloud_conf_load(0); bool claimed = load_claiming_state(); registry_update_cloud_base_url(); - rrdpush_sender_send_claimed_id(localhost); + stream_sender_send_claimed_id(localhost); nd_log_limits_reset(); CLOUD_STATUS status = cloud_status(); diff --git a/src/claim/cloud-status.c b/src/claim/cloud-status.c index 45db177e916301..52f238128165df 100644 --- a/src/claim/cloud-status.c +++ b/src/claim/cloud-status.c @@ -30,8 +30,8 @@ CLOUD_STATUS cloud_status(void) { return CLOUD_STATUS_ONLINE; if(localhost->sender && - rrdhost_flag_check(localhost, RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS) && - stream_has_capability(localhost->sender, STREAM_CAP_NODE_ID) && + rrdhost_flag_check(localhost, RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS) && + stream_sender_has_capabilities(localhost, STREAM_CAP_NODE_ID) && !UUIDiszero(localhost->node_id) && !UUIDiszero(localhost->aclk.claim_id_of_parent)) return CLOUD_STATUS_INDIRECT; diff --git a/src/collectors/apps.plugin/apps_pid.c b/src/collectors/apps.plugin/apps_pid.c index 0dcee7ccead753..e9d331ee4acb17 100644 --- a/src/collectors/apps.plugin/apps_pid.c +++ b/src/collectors/apps.plugin/apps_pid.c @@ -53,7 +53,7 @@ size_t all_pids_count(void) { } void apps_pids_init(void) { - pids.all_pids.aral = aral_create("pid_stat", sizeof(struct pid_stat), 1, 65536, NULL, NULL, NULL, false, true); + pids.all_pids.aral = aral_create("pid_stat", sizeof(struct pid_stat), 1, 0, NULL, NULL, NULL, false, true); simple_hashtable_init_PID(&pids.all_pids.ht, 1024); } diff --git a/src/collectors/ebpf.plugin/ebpf.c b/src/collectors/ebpf.plugin/ebpf.c index 70a311feb4a234..88f6439c4075de 100644 --- a/src/collectors/ebpf.plugin/ebpf.c +++ b/src/collectors/ebpf.plugin/ebpf.c @@ -739,7 +739,7 @@ ARAL *ebpf_allocate_pid_aral(char *name, size_t size) } return aral_create(name, size, - 0, max_elements, + 0, 0, NULL, NULL, NULL, false, false); } diff --git a/src/collectors/statsd.plugin/statsd.c b/src/collectors/statsd.plugin/statsd.c index 11a6ac968a79b1..1d48f0c9127165 100644 --- a/src/collectors/statsd.plugin/statsd.c +++ b/src/collectors/statsd.plugin/statsd.c @@ -2654,7 +2654,7 @@ void *statsd_main(void *ptr) { RRDSET *st_pcharts = NULL; RRDDIM *rd_pcharts = NULL; - if(global_statistics_enabled) { + if(telemetry_enabled) { st_metrics = rrdset_create_localhost( "netdata", "statsd_metrics", @@ -2851,7 +2851,7 @@ void *statsd_main(void *ptr) { if(unlikely(!service_running(SERVICE_COLLECTORS))) break; - if(global_statistics_enabled) { + if(telemetry_enabled) { rrddim_set_by_pointer(st_metrics, rd_metrics_gauge, (collected_number)statsd.gauges.metrics); rrddim_set_by_pointer(st_metrics, rd_metrics_counter, (collected_number)statsd.counters.metrics); rrddim_set_by_pointer(st_metrics, rd_metrics_timer, (collected_number)statsd.timers.metrics); diff --git a/src/daemon/analytics.c b/src/daemon/analytics.c index cebfdeb704052d..75e52a592a3757 100644 --- a/src/daemon/analytics.c +++ b/src/daemon/analytics.c @@ -377,10 +377,7 @@ void analytics_https(void) BUFFER *b = buffer_create(30, NULL); analytics_exporting_connectors_ssl(b); - buffer_strcat(b, netdata_ssl_streaming_sender_ctx && - rrdhost_flag_check(localhost, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED) && - SSL_connection(&localhost->sender->ssl) ? "streaming|" : "|"); - + buffer_strcat(b, stream_sender_is_connected_with_ssl(localhost) ? "streaming|" : "|"); buffer_strcat(b, netdata_ssl_web_server_ctx ? "web" : ""); analytics_set_data_str(&analytics_data.netdata_config_https_available, (char *)buffer_tostring(b)); @@ -619,7 +616,7 @@ void *analytics_main(void *ptr) */ void set_late_analytics_variables(struct rrdhost_system_info *system_info) { - analytics_set_data(&analytics_data.netdata_config_stream_enabled, stream_conf_send_enabled ? "true" : "false"); + analytics_set_data(&analytics_data.netdata_config_stream_enabled, stream_send.enabled ? "true" : "false"); analytics_set_data_str(&analytics_data.netdata_config_memory_mode, (char *)rrd_memory_mode_name(default_rrd_memory_mode)); analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "true"); diff --git a/src/daemon/common.h b/src/daemon/common.h index 9f6efa3efba79a..0fde3f0384ce36 100644 --- a/src/daemon/common.h +++ b/src/daemon/common.h @@ -3,7 +3,12 @@ #ifndef NETDATA_COMMON_H #define NETDATA_COMMON_H 1 +#ifdef __cplusplus +extern "C" { +#endif + #include "libnetdata/libnetdata.h" +#include "config/netdata-conf.h" #include "libuv_workers.h" // ---------------------------------------------------------------------------- @@ -11,9 +16,10 @@ #include "web/api/maps/maps.h" -#include "daemon/config/dyncfg.h" +#include "daemon/config/netdata-conf.h" +#include "daemon/dyncfg/dyncfg.h" -#include "global_statistics.h" +#include "daemon/telemetry/telemetry.h" // health monitoring and alarm notifications #include "health/health.h" @@ -30,10 +36,10 @@ #endif // streaming metrics between netdata servers -#include "streaming/rrdpush.h" +#include "streaming/stream.h" // anomaly detection -#include "ml/ml.h" +#include "ml/ml_public.h" // the netdata registry // the registry is actually an API feature @@ -94,4 +100,8 @@ long get_netdata_cpus(void); void set_environment_for_plugins_and_scripts(void); +#ifdef __cplusplus +} +#endif + #endif /* NETDATA_COMMON_H */ diff --git a/src/daemon/config/README.md b/src/daemon/config/README.md index ae67c48c32f8a9..3dd095b65df368 100644 --- a/src/daemon/config/README.md +++ b/src/daemon/config/README.md @@ -18,7 +18,7 @@ The configuration file uses an INI-style format with `[SECTION]` headers: | [[health]](#health-section-options) | [Health monitoring](/src/health/README.md) | | `[web]` | [Web Server](/src/web/server/README.md) | | `[registry]` | [Registry](/src/registry/README.md) | -| `[global statistics]` | Internal monitoring | +| `[telemetry]` | Internal monitoring | | `[statsd]` | [StatsD plugin](/src/collectors/statsd.plugin/README.md) | | [`[plugins]`](#plugins-section-options) | Data collection Plugins (Collectors) | | [[plugin:NAME]](#per-plugin-configuration) | Individual [Plugins](#per-plugin-configuration) | diff --git a/src/daemon/config/netdata-conf-backwards-compatibility.c b/src/daemon/config/netdata-conf-backwards-compatibility.c new file mode 100644 index 00000000000000..b1dbb35954bd70 --- /dev/null +++ b/src/daemon/config/netdata-conf-backwards-compatibility.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "netdata-conf-backwards-compatibility.h" +#include "database/engine/rrdengineapi.h" + +void netdata_conf_backwards_compatibility(void) { + static bool run = false; + if(run) return; + run = true; + + // move [global] options to the [web] section + + config_move(CONFIG_SECTION_GLOBAL, "http port listen backlog", + CONFIG_SECTION_WEB, "listen backlog"); + + config_move(CONFIG_SECTION_GLOBAL, "bind socket to IP", + CONFIG_SECTION_WEB, "bind to"); + + config_move(CONFIG_SECTION_GLOBAL, "bind to", + CONFIG_SECTION_WEB, "bind to"); + + config_move(CONFIG_SECTION_GLOBAL, "port", + CONFIG_SECTION_WEB, "default port"); + + config_move(CONFIG_SECTION_GLOBAL, "default port", + CONFIG_SECTION_WEB, "default port"); + + config_move(CONFIG_SECTION_GLOBAL, "disconnect idle web clients after seconds", + CONFIG_SECTION_WEB, "disconnect idle clients after seconds"); + + config_move(CONFIG_SECTION_GLOBAL, "respect web browser do not track policy", + CONFIG_SECTION_WEB, "respect do not track policy"); + + config_move(CONFIG_SECTION_GLOBAL, "web x-frame-options header", + CONFIG_SECTION_WEB, "x-frame-options response header"); + + config_move(CONFIG_SECTION_GLOBAL, "enable web responses gzip compression", + CONFIG_SECTION_WEB, "enable gzip compression"); + + config_move(CONFIG_SECTION_GLOBAL, "web compression strategy", + CONFIG_SECTION_WEB, "gzip compression strategy"); + + config_move(CONFIG_SECTION_GLOBAL, "web compression level", + CONFIG_SECTION_WEB, "gzip compression level"); + + config_move(CONFIG_SECTION_GLOBAL, "config directory", + CONFIG_SECTION_DIRECTORIES, "config"); + + config_move(CONFIG_SECTION_GLOBAL, "stock config directory", + CONFIG_SECTION_DIRECTORIES, "stock config"); + + config_move(CONFIG_SECTION_GLOBAL, "log directory", + CONFIG_SECTION_DIRECTORIES, "log"); + + config_move(CONFIG_SECTION_GLOBAL, "web files directory", + CONFIG_SECTION_DIRECTORIES, "web"); + + config_move(CONFIG_SECTION_GLOBAL, "cache directory", + CONFIG_SECTION_DIRECTORIES, "cache"); + + config_move(CONFIG_SECTION_GLOBAL, "lib directory", + CONFIG_SECTION_DIRECTORIES, "lib"); + + config_move(CONFIG_SECTION_GLOBAL, "home directory", + CONFIG_SECTION_DIRECTORIES, "home"); + + config_move(CONFIG_SECTION_GLOBAL, "lock directory", + CONFIG_SECTION_DIRECTORIES, "lock"); + + config_move(CONFIG_SECTION_GLOBAL, "plugins directory", + CONFIG_SECTION_DIRECTORIES, "plugins"); + + config_move(CONFIG_SECTION_HEALTH, "health configuration directory", + CONFIG_SECTION_DIRECTORIES, "health config"); + + config_move(CONFIG_SECTION_HEALTH, "stock health configuration directory", + CONFIG_SECTION_DIRECTORIES, "stock health config"); + + config_move(CONFIG_SECTION_REGISTRY, "registry db directory", + CONFIG_SECTION_DIRECTORIES, "registry"); + + config_move(CONFIG_SECTION_GLOBAL, "debug log", + CONFIG_SECTION_LOGS, "debug"); + + config_move(CONFIG_SECTION_GLOBAL, "error log", + CONFIG_SECTION_LOGS, "error"); + + config_move(CONFIG_SECTION_GLOBAL, "access log", + CONFIG_SECTION_LOGS, "access"); + + config_move(CONFIG_SECTION_GLOBAL, "facility log", + CONFIG_SECTION_LOGS, "facility"); + + config_move(CONFIG_SECTION_GLOBAL, "errors flood protection period", + CONFIG_SECTION_LOGS, "errors flood protection period"); + + config_move(CONFIG_SECTION_GLOBAL, "errors to trigger flood protection", + CONFIG_SECTION_LOGS, "errors to trigger flood protection"); + + config_move(CONFIG_SECTION_GLOBAL, "debug flags", + CONFIG_SECTION_LOGS, "debug flags"); + + config_move(CONFIG_SECTION_GLOBAL, "TZ environment variable", + CONFIG_SECTION_ENV_VARS, "TZ"); + + config_move(CONFIG_SECTION_PLUGINS, "PATH environment variable", + CONFIG_SECTION_ENV_VARS, "PATH"); + + config_move(CONFIG_SECTION_PLUGINS, "PYTHONPATH environment variable", + CONFIG_SECTION_ENV_VARS, "PYTHONPATH"); + + config_move(CONFIG_SECTION_STATSD, "enabled", + CONFIG_SECTION_PLUGINS, "statsd"); + + config_move(CONFIG_SECTION_GLOBAL, "memory mode", + CONFIG_SECTION_DB, "db"); + + config_move(CONFIG_SECTION_DB, "mode", + CONFIG_SECTION_DB, "db"); + + config_move(CONFIG_SECTION_GLOBAL, "history", + CONFIG_SECTION_DB, "retention"); + + config_move(CONFIG_SECTION_GLOBAL, "update every", + CONFIG_SECTION_DB, "update every"); + + config_move(CONFIG_SECTION_GLOBAL, "page cache size", + CONFIG_SECTION_DB, "dbengine page cache size"); + + config_move(CONFIG_SECTION_DB, "dbengine page cache size MB", + CONFIG_SECTION_DB, "dbengine page cache size"); + + config_move(CONFIG_SECTION_DB, "dbengine extent cache size MB", + CONFIG_SECTION_DB, "dbengine extent cache size"); + + config_move(CONFIG_SECTION_DB, "page cache size", + CONFIG_SECTION_DB, "dbengine page cache size MB"); + + config_move(CONFIG_SECTION_GLOBAL, "page cache uses malloc", + CONFIG_SECTION_DB, "dbengine page cache with malloc"); + + config_move(CONFIG_SECTION_DB, "page cache with malloc", + CONFIG_SECTION_DB, "dbengine page cache with malloc"); + + config_move(CONFIG_SECTION_GLOBAL, "memory deduplication (ksm)", + CONFIG_SECTION_DB, "memory deduplication (ksm)"); + + config_move(CONFIG_SECTION_GLOBAL, "dbengine page fetch timeout", + CONFIG_SECTION_DB, "dbengine page fetch timeout secs"); + + config_move(CONFIG_SECTION_GLOBAL, "dbengine page fetch retries", + CONFIG_SECTION_DB, "dbengine page fetch retries"); + + config_move(CONFIG_SECTION_GLOBAL, "dbengine extent pages", + CONFIG_SECTION_DB, "dbengine pages per extent"); + + config_move(CONFIG_SECTION_GLOBAL, "cleanup obsolete charts after seconds", + CONFIG_SECTION_DB, "cleanup obsolete charts after"); + + config_move(CONFIG_SECTION_DB, "cleanup obsolete charts after secs", + CONFIG_SECTION_DB, "cleanup obsolete charts after"); + + config_move(CONFIG_SECTION_GLOBAL, "gap when lost iterations above", + CONFIG_SECTION_DB, "gap when lost iterations above"); + + config_move(CONFIG_SECTION_GLOBAL, "cleanup orphan hosts after seconds", + CONFIG_SECTION_DB, "cleanup orphan hosts after"); + + config_move(CONFIG_SECTION_DB, "cleanup orphan hosts after secs", + CONFIG_SECTION_DB, "cleanup orphan hosts after"); + + config_move(CONFIG_SECTION_DB, "cleanup ephemeral hosts after secs", + CONFIG_SECTION_DB, "cleanup ephemeral hosts after"); + + config_move(CONFIG_SECTION_DB, "seconds to replicate", + CONFIG_SECTION_DB, "replication period"); + + config_move(CONFIG_SECTION_DB, "seconds per replication step", + CONFIG_SECTION_DB, "replication step"); + + config_move(CONFIG_SECTION_GLOBAL, "enable zero metrics", + CONFIG_SECTION_DB, "enable zero metrics"); + + config_move("global statistics", "update every", + CONFIG_SECTION_TELEMETRY, "update every"); + + config_move(CONFIG_SECTION_PLUGINS, "netdata monitoring", + CONFIG_SECTION_PLUGINS, "netdata telemetry"); + + config_move(CONFIG_SECTION_PLUGINS, "netdata monitoring extended", + CONFIG_SECTION_TELEMETRY, "extended telemetry"); + + + // ---------------------------------------------------------------------------------------------------------------- + + bool found_old_config = false; + + if(config_move(CONFIG_SECTION_GLOBAL, "dbengine disk space", + CONFIG_SECTION_DB, "dbengine tier 0 retention size") != -1) + found_old_config = true; + + if(config_move(CONFIG_SECTION_GLOBAL, "dbengine multihost disk space", + CONFIG_SECTION_DB, "dbengine tier 0 retention size") != -1) + found_old_config = true; + + if(config_move(CONFIG_SECTION_DB, "dbengine disk space MB", + CONFIG_SECTION_DB, "dbengine tier 0 retention size") != -1) + found_old_config = true; + + for(size_t tier = 0; tier < RRD_STORAGE_TIERS ;tier++) { + char old_config[128], new_config[128]; + + snprintfz(old_config, sizeof(old_config), "dbengine tier %zu retention days", tier); + snprintfz(new_config, sizeof(new_config), "dbengine tier %zu retention time", tier); + config_move(CONFIG_SECTION_DB, old_config, + CONFIG_SECTION_DB, new_config); + + if(tier == 0) + snprintfz(old_config, sizeof(old_config), "dbengine multihost disk space MB"); + else + snprintfz(old_config, sizeof(old_config), "dbengine tier %zu multihost disk space MB", tier); + snprintfz(new_config, sizeof(new_config), "dbengine tier %zu retention size", tier); + if(config_move(CONFIG_SECTION_DB, old_config, + CONFIG_SECTION_DB, new_config) != -1 && tier == 0) + found_old_config = true; + + snprintfz(old_config, sizeof(old_config), "dbengine tier %zu disk space MB", tier); + snprintfz(new_config, sizeof(new_config), "dbengine tier %zu retention size", tier); + if(config_move(CONFIG_SECTION_DB, old_config, + CONFIG_SECTION_DB, new_config) != -1 && tier == 0) + found_old_config = true; + } + + legacy_multihost_db_space = found_old_config; + + // ---------------------------------------------------------------------------------------------------------------- + + config_move(CONFIG_SECTION_LOGS, "error", + CONFIG_SECTION_LOGS, "daemon"); + + config_move(CONFIG_SECTION_LOGS, "severity level", + CONFIG_SECTION_LOGS, "level"); + + config_move(CONFIG_SECTION_LOGS, "errors to trigger flood protection", + CONFIG_SECTION_LOGS, "logs to trigger flood protection"); + + config_move(CONFIG_SECTION_LOGS, "errors flood protection period", + CONFIG_SECTION_LOGS, "logs flood protection period"); + + config_move(CONFIG_SECTION_HEALTH, "is ephemeral", + CONFIG_SECTION_GLOBAL, "is ephemeral node"); + + config_move(CONFIG_SECTION_HEALTH, "has unstable connection", + CONFIG_SECTION_GLOBAL, "has unstable connection"); + + config_move(CONFIG_SECTION_HEALTH, "run at least every seconds", + CONFIG_SECTION_HEALTH, "run at least every"); + + config_move(CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for seconds", + CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for"); + + config_move(CONFIG_SECTION_HEALTH, "health log history", + CONFIG_SECTION_HEALTH, "health log retention"); + + config_move(CONFIG_SECTION_REGISTRY, "registry expire idle persons days", + CONFIG_SECTION_REGISTRY, "registry expire idle persons"); + + config_move(CONFIG_SECTION_WEB, "disconnect idle clients after seconds", + CONFIG_SECTION_WEB, "disconnect idle clients after"); + + config_move(CONFIG_SECTION_WEB, "accept a streaming request every seconds", + CONFIG_SECTION_WEB, "accept a streaming request every"); + + config_move(CONFIG_SECTION_STATSD, "set charts as obsolete after secs", + CONFIG_SECTION_STATSD, "set charts as obsolete after"); + + config_move(CONFIG_SECTION_STATSD, "disconnect idle tcp clients after seconds", + CONFIG_SECTION_STATSD, "disconnect idle tcp clients after"); + + config_move("plugin:idlejitter", "loop time in ms", + "plugin:idlejitter", "loop time"); + + config_move("plugin:proc:/sys/class/infiniband", "refresh ports state every seconds", + "plugin:proc:/sys/class/infiniband", "refresh ports state every"); +} diff --git a/src/daemon/config/netdata-conf-backwards-compatibility.h b/src/daemon/config/netdata-conf-backwards-compatibility.h new file mode 100644 index 00000000000000..0abe2ba73330a0 --- /dev/null +++ b/src/daemon/config/netdata-conf-backwards-compatibility.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_DAEMON_NETDATA_CONF_BACKWARDS_COMPATIBILITY_H +#define NETDATA_DAEMON_NETDATA_CONF_BACKWARDS_COMPATIBILITY_H + +#include "config.h" + +void netdata_conf_backwards_compatibility(void); + +#endif //NETDATA_DAEMON_NETDATA_CONF_BACKWARDS_COMPATIBILITY_H diff --git a/src/daemon/config/netdata-conf-db.c b/src/daemon/config/netdata-conf-db.c new file mode 100644 index 00000000000000..d2b9c83b89d3ae --- /dev/null +++ b/src/daemon/config/netdata-conf-db.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "netdata-conf-db.h" + +int default_rrd_update_every = UPDATE_EVERY; +int default_rrd_history_entries = RRD_DEFAULT_HISTORY_ENTRIES; + +bool dbengine_enabled = false; // will become true if and when dbengine is initialized +size_t storage_tiers = 3; +bool dbengine_use_direct_io = true; +static size_t storage_tiers_grouping_iterations[RRD_STORAGE_TIERS] = {1, 60, 60, 60, 60}; +static double storage_tiers_retention_days[RRD_STORAGE_TIERS] = {14, 90, 2 * 365, 2 * 365, 2 * 365}; + +time_t rrdset_free_obsolete_time_s = 3600; +time_t rrdhost_free_orphan_time_s = 3600; +time_t rrdhost_free_ephemeral_time_s = 86400; + +size_t get_tier_grouping(size_t tier) { + if(unlikely(tier >= storage_tiers)) tier = storage_tiers - 1; + + size_t grouping = 1; + // first tier is always 1 iteration of whatever update every the chart has + for(size_t i = 1; i <= tier ;i++) + grouping *= storage_tiers_grouping_iterations[i]; + + return grouping; +} + +static void netdata_conf_dbengine_pre_logs(void) { + static bool run = false; + if(run) return; + run = true; + + errno_clear(); + +#ifdef ENABLE_DBENGINE + // this is required for dbegnine to work, so call it here (it is ok, it won't run twice) + netdata_conf_section_directories(); + + // ------------------------------------------------------------------------ + // get default Database Engine page type + + const char *page_type = config_get(CONFIG_SECTION_DB, "dbengine page type", "gorilla"); + if (strcmp(page_type, "gorilla") == 0) + tier_page_type[0] = RRDENG_PAGE_TYPE_GORILLA_32BIT; + else if (strcmp(page_type, "raw") == 0) + tier_page_type[0] = RRDENG_PAGE_TYPE_ARRAY_32BIT; + else { + tier_page_type[0] = RRDENG_PAGE_TYPE_ARRAY_32BIT; + netdata_log_error("Invalid dbengine page type ''%s' given. Defaulting to 'raw'.", page_type); + } + + // ------------------------------------------------------------------------ + // get default Database Engine page cache size in MiB + + default_rrdeng_page_cache_mb = (int) config_get_size_mb(CONFIG_SECTION_DB, "dbengine page cache size", default_rrdeng_page_cache_mb); + default_rrdeng_extent_cache_mb = (int) config_get_size_mb(CONFIG_SECTION_DB, "dbengine extent cache size", default_rrdeng_extent_cache_mb); + db_engine_journal_check = config_get_boolean(CONFIG_SECTION_DB, "dbengine enable journal integrity check", CONFIG_BOOLEAN_NO); + + if(default_rrdeng_extent_cache_mb < 0) { + default_rrdeng_extent_cache_mb = 0; + config_set_size_mb(CONFIG_SECTION_DB, "dbengine extent cache size", default_rrdeng_extent_cache_mb); + } + + if(default_rrdeng_page_cache_mb < RRDENG_MIN_PAGE_CACHE_SIZE_MB) { + netdata_log_error("Invalid page cache size %d given. Defaulting to %d.", default_rrdeng_page_cache_mb, RRDENG_MIN_PAGE_CACHE_SIZE_MB); + default_rrdeng_page_cache_mb = RRDENG_MIN_PAGE_CACHE_SIZE_MB; + config_set_size_mb(CONFIG_SECTION_DB, "dbengine page cache size", default_rrdeng_page_cache_mb); + } + + // ------------------------------------------------------------------------ + // get default Database Engine disk space quota in MiB + // + // // if (!config_exists(CONFIG_SECTION_DB, "dbengine disk space MB") && !config_exists(CONFIG_SECTION_DB, "dbengine multihost disk space MB")) + // + // default_rrdeng_disk_quota_mb = (int) config_get_number(CONFIG_SECTION_DB, "dbengine disk space MB", default_rrdeng_disk_quota_mb); + // if(default_rrdeng_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) { + // netdata_log_error("Invalid dbengine disk space %d given. Defaulting to %d.", default_rrdeng_disk_quota_mb, RRDENG_MIN_DISK_SPACE_MB); + // default_rrdeng_disk_quota_mb = RRDENG_MIN_DISK_SPACE_MB; + // config_set_number(CONFIG_SECTION_DB, "dbengine disk space MB", default_rrdeng_disk_quota_mb); + // } + // + // default_multidb_disk_quota_mb = (int) config_get_number(CONFIG_SECTION_DB, "dbengine multihost disk space MB", compute_multidb_diskspace()); + // if(default_multidb_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) { + // netdata_log_error("Invalid multidb disk space %d given. Defaulting to %d.", default_multidb_disk_quota_mb, default_rrdeng_disk_quota_mb); + // default_multidb_disk_quota_mb = default_rrdeng_disk_quota_mb; + // config_set_number(CONFIG_SECTION_DB, "dbengine multihost disk space MB", default_multidb_disk_quota_mb); + // } + +#else + if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { + error_report("RRD_MEMORY_MODE_DBENGINE is not supported in this platform. The agent will use db mode 'save' instead."); + default_rrd_memory_mode = RRD_MEMORY_MODE_RAM; + } +#endif +} + +#ifdef ENABLE_DBENGINE +struct dbengine_initialization { + ND_THREAD *thread; + char path[FILENAME_MAX + 1]; + int disk_space_mb; + size_t retention_seconds; + size_t tier; + int ret; +}; + +void *dbengine_tier_init(void *ptr) { + struct dbengine_initialization *dbi = ptr; + dbi->ret = rrdeng_init(NULL, dbi->path, dbi->disk_space_mb, dbi->tier, dbi->retention_seconds); + return ptr; +} + +RRD_BACKFILL get_dbengine_backfill(RRD_BACKFILL backfill) +{ + const char *bf = config_get( + CONFIG_SECTION_DB, + "dbengine tier backfill", + backfill == RRD_BACKFILL_NEW ? "new" : + backfill == RRD_BACKFILL_FULL ? "full" : + "none"); + + if (strcmp(bf, "new") == 0) + backfill = RRD_BACKFILL_NEW; + else if (strcmp(bf, "full") == 0) + backfill = RRD_BACKFILL_FULL; + else if (strcmp(bf, "none") == 0) + backfill = RRD_BACKFILL_NONE; + else { + nd_log(NDLS_DAEMON, NDLP_WARNING, "DBENGINE: unknown backfill value '%s', assuming 'new'", bf); + config_set(CONFIG_SECTION_DB, "dbengine tier backfill", "new"); + backfill = RRD_BACKFILL_NEW; + } + return backfill; +} +#endif + +void netdata_conf_dbengine_init(const char *hostname) { +#ifdef ENABLE_DBENGINE + + // ---------------------------------------------------------------------------------------------------------------- + // out of memory protection and use all ram for caches + + dbengine_out_of_memory_protection = 0; // will be calculated below + OS_SYSTEM_MEMORY sm = os_system_memory(true); + if(sm.ram_total_bytes && sm.ram_available_bytes && sm.ram_total_bytes > sm.ram_available_bytes) { + // calculate the default out of memory protection size + char buf[64]; + size_snprintf(buf, sizeof(buf), sm.ram_total_bytes / 10, "B", false); + size_parse(buf, &dbengine_out_of_memory_protection, "B"); + } + + if(dbengine_out_of_memory_protection) { + dbengine_use_all_ram_for_caches = config_get_boolean(CONFIG_SECTION_DB, "dbengine use all ram for caches", dbengine_use_all_ram_for_caches); + dbengine_out_of_memory_protection = config_get_size_bytes(CONFIG_SECTION_DB, "dbengine out of memory protection", dbengine_out_of_memory_protection); + + char buf_total[64], buf_avail[64], buf_oom[64]; + size_snprintf(buf_total, sizeof(buf_total), sm.ram_total_bytes, "B", false); + size_snprintf(buf_avail, sizeof(buf_avail), sm.ram_available_bytes, "B", false); + size_snprintf(buf_oom, sizeof(buf_oom), dbengine_out_of_memory_protection, "B", false); + + nd_log(NDLS_DAEMON, NDLP_NOTICE, + "DBENGINE Out of Memory Protection. " + "System Memory Total: %s, Currently Available: %s, Out of Memory Protection: %s, Use All RAM: %s", + buf_total, buf_avail, buf_oom, dbengine_use_all_ram_for_caches ? "enabled" : "disabled"); + } + else { + dbengine_out_of_memory_protection = 0; + dbengine_use_all_ram_for_caches = false; + + nd_log(NDLS_DAEMON, NDLP_WARNING, + "DBENGINE Out of Memory Protection and Use All Ram cannot be enabled. " + "Failed to detect memory size on this system."); + } + + // ---------------------------------------------------------------------------------------------------------------- + + dbengine_use_direct_io = config_get_boolean(CONFIG_SECTION_DB, "dbengine use direct io", dbengine_use_direct_io); + + unsigned read_num = (unsigned)config_get_number(CONFIG_SECTION_DB, "dbengine pages per extent", DEFAULT_PAGES_PER_EXTENT); + if (read_num > 0 && read_num <= DEFAULT_PAGES_PER_EXTENT) + rrdeng_pages_per_extent = read_num; + else { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "Invalid dbengine pages per extent %u given. Using %u.", + read_num, rrdeng_pages_per_extent); + + config_set_number(CONFIG_SECTION_DB, "dbengine pages per extent", rrdeng_pages_per_extent); + } + + storage_tiers = config_get_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); + if(storage_tiers < 1) { + nd_log(NDLS_DAEMON, NDLP_WARNING, "At least 1 storage tier is required. Assuming 1."); + + storage_tiers = 1; + config_set_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); + } + if(storage_tiers > RRD_STORAGE_TIERS) { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "Up to %d storage tier are supported. Assuming %d.", + RRD_STORAGE_TIERS, RRD_STORAGE_TIERS); + + storage_tiers = RRD_STORAGE_TIERS; + config_set_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); + } + + new_dbengine_defaults = + (!legacy_multihost_db_space && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 1 update every iterations") && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 2 update every iterations") && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 3 update every iterations") && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 4 update every iterations") && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 1 retention size") && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 2 retention size") && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 3 retention size") && + !config_exists(CONFIG_SECTION_DB, "dbengine tier 4 retention size")); + + default_backfill = get_dbengine_backfill(RRD_BACKFILL_NEW); + char dbengineconfig[200 + 1]; + + size_t grouping_iterations = default_rrd_update_every; + storage_tiers_grouping_iterations[0] = default_rrd_update_every; + + for (size_t tier = 1; tier < storage_tiers; tier++) { + grouping_iterations = storage_tiers_grouping_iterations[tier]; + snprintfz(dbengineconfig, sizeof(dbengineconfig) - 1, "dbengine tier %zu update every iterations", tier); + grouping_iterations = config_get_number(CONFIG_SECTION_DB, dbengineconfig, grouping_iterations); + if(grouping_iterations < 2) { + grouping_iterations = 2; + config_set_number(CONFIG_SECTION_DB, dbengineconfig, grouping_iterations); + nd_log(NDLS_DAEMON, NDLP_WARNING, + "DBENGINE on '%s': 'dbegnine tier %zu update every iterations' cannot be less than 2. Assuming 2.", + hostname, tier); + } + storage_tiers_grouping_iterations[tier] = grouping_iterations; + } + + default_multidb_disk_quota_mb = (int) config_get_size_mb(CONFIG_SECTION_DB, "dbengine tier 0 retention size", RRDENG_DEFAULT_TIER_DISK_SPACE_MB); + if(default_multidb_disk_quota_mb && default_multidb_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) { + netdata_log_error("Invalid disk space %d for tier 0 given. Defaulting to %d.", default_multidb_disk_quota_mb, RRDENG_MIN_DISK_SPACE_MB); + default_multidb_disk_quota_mb = RRDENG_MIN_DISK_SPACE_MB; + config_set_size_mb(CONFIG_SECTION_DB, "dbengine tier 0 retention size", default_multidb_disk_quota_mb); + } + +#ifdef OS_WINDOWS + // FIXME: for whatever reason joining the initialization threads + // fails on Windows. + bool parallel_initialization = false; +#else + bool parallel_initialization = (storage_tiers <= (size_t)get_netdata_cpus()) ? true : false; +#endif + + struct dbengine_initialization tiers_init[RRD_STORAGE_TIERS] = {}; + + size_t created_tiers = 0; + char dbenginepath[FILENAME_MAX + 1]; + + for (size_t tier = 0; tier < storage_tiers; tier++) { + + if (tier == 0) + snprintfz(dbenginepath, FILENAME_MAX, "%s/dbengine", netdata_configured_cache_dir); + else + snprintfz(dbenginepath, FILENAME_MAX, "%s/dbengine-tier%zu", netdata_configured_cache_dir, tier); + + int ret = mkdir(dbenginepath, 0775); + if (ret != 0 && errno != EEXIST) { + nd_log(NDLS_DAEMON, NDLP_CRIT, "DBENGINE on '%s': cannot create directory '%s'", hostname, dbenginepath); + continue; + } + + int disk_space_mb = tier ? RRDENG_DEFAULT_TIER_DISK_SPACE_MB : default_multidb_disk_quota_mb; + snprintfz(dbengineconfig, sizeof(dbengineconfig) - 1, "dbengine tier %zu retention size", tier); + disk_space_mb = config_get_size_mb(CONFIG_SECTION_DB, dbengineconfig, disk_space_mb); + + snprintfz(dbengineconfig, sizeof(dbengineconfig) - 1, "dbengine tier %zu retention time", tier); + storage_tiers_retention_days[tier] = config_get_duration_days( + CONFIG_SECTION_DB, dbengineconfig, new_dbengine_defaults ? storage_tiers_retention_days[tier] : 0); + + tiers_init[tier].disk_space_mb = (int) disk_space_mb; + tiers_init[tier].tier = tier; + tiers_init[tier].retention_seconds = (size_t) (86400.0 * storage_tiers_retention_days[tier]); + strncpyz(tiers_init[tier].path, dbenginepath, FILENAME_MAX); + tiers_init[tier].ret = 0; + + if(parallel_initialization) { + char tag[NETDATA_THREAD_TAG_MAX + 1]; + snprintfz(tag, NETDATA_THREAD_TAG_MAX, "DBENGINIT[%zu]", tier); + tiers_init[tier].thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE, dbengine_tier_init, &tiers_init[tier]); + } + else + dbengine_tier_init(&tiers_init[tier]); + } + + for(size_t tier = 0; tier < storage_tiers ;tier++) { + if(parallel_initialization) + nd_thread_join(tiers_init[tier].thread); + + if(tiers_init[tier].ret != 0) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "DBENGINE on '%s': Failed to initialize multi-host database tier %zu on path '%s'", + hostname, tiers_init[tier].tier, tiers_init[tier].path); + } + else if(created_tiers == tier) + created_tiers++; + } + + if(created_tiers && created_tiers < storage_tiers) { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "DBENGINE on '%s': Managed to create %zu tiers instead of %zu. Continuing with %zu available.", + hostname, created_tiers, storage_tiers, created_tiers); + + storage_tiers = created_tiers; + } + else if(!created_tiers) + fatal("DBENGINE on '%s', failed to initialize databases at '%s'.", hostname, netdata_configured_cache_dir); + + for(size_t tier = 0; tier < storage_tiers ;tier++) + rrdeng_readiness_wait(multidb_ctx[tier]); + + calculate_tier_disk_space_percentage(); + + dbengine_enabled = true; +#else + storage_tiers = config_get_number(CONFIG_SECTION_DB, "storage tiers", 1); + if(storage_tiers != 1) { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "DBENGINE is not available on '%s', so only 1 database tier can be supported.", + hostname); + + storage_tiers = 1; + config_set_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); + } + dbengine_enabled = false; +#endif +} + +void netdata_conf_section_db(void) { + static bool run = false; + if(run) return; + run = true; + + // ------------------------------------------------------------------------ + + rrdhost_free_orphan_time_s = + config_get_duration_seconds(CONFIG_SECTION_DB, "cleanup orphan hosts after", rrdhost_free_orphan_time_s); + + // ------------------------------------------------------------------------ + // get default database update frequency + + default_rrd_update_every = (int) config_get_duration_seconds(CONFIG_SECTION_DB, "update every", UPDATE_EVERY); + if(default_rrd_update_every < 1 || default_rrd_update_every > 600) { + netdata_log_error("Invalid data collection frequency (update every) %d given. Defaulting to %d.", default_rrd_update_every, UPDATE_EVERY); + default_rrd_update_every = UPDATE_EVERY; + config_set_duration_seconds(CONFIG_SECTION_DB, "update every", default_rrd_update_every); + } + + // ------------------------------------------------------------------------ + // get the database selection + + { + const char *mode = config_get(CONFIG_SECTION_DB, "db", rrd_memory_mode_name(default_rrd_memory_mode)); + default_rrd_memory_mode = rrd_memory_mode_id(mode); + if(strcmp(mode, rrd_memory_mode_name(default_rrd_memory_mode)) != 0) { + netdata_log_error("Invalid memory mode '%s' given. Using '%s'", mode, rrd_memory_mode_name(default_rrd_memory_mode)); + config_set(CONFIG_SECTION_DB, "db", rrd_memory_mode_name(default_rrd_memory_mode)); + } + } + + // ------------------------------------------------------------------------ + // get default database size + + if(default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE && default_rrd_memory_mode != RRD_MEMORY_MODE_NONE) { + default_rrd_history_entries = (int)config_get_number( + CONFIG_SECTION_DB, "retention", + align_entries_to_pagesize(default_rrd_memory_mode, RRD_DEFAULT_HISTORY_ENTRIES)); + + long h = align_entries_to_pagesize(default_rrd_memory_mode, default_rrd_history_entries); + if (h != default_rrd_history_entries) { + config_set_number(CONFIG_SECTION_DB, "retention", h); + default_rrd_history_entries = (int)h; + } + } + + // -------------------------------------------------------------------- + // get KSM settings + +#ifdef MADV_MERGEABLE + enable_ksm = config_get_boolean_ondemand(CONFIG_SECTION_DB, "memory deduplication (ksm)", enable_ksm); +#endif + + // -------------------------------------------------------------------- + + rrdhost_free_ephemeral_time_s = + config_get_duration_seconds(CONFIG_SECTION_DB, "cleanup ephemeral hosts after", rrdhost_free_ephemeral_time_s); + + rrdset_free_obsolete_time_s = + config_get_duration_seconds(CONFIG_SECTION_DB, "cleanup obsolete charts after", rrdset_free_obsolete_time_s); + + // Current chart locking and invalidation scheme doesn't prevent Netdata from segmentation faults if a short + // cleanup delay is set. Extensive stress tests showed that 10 seconds is quite a safe delay. Look at + // https://github.com/netdata/netdata/pull/11222#issuecomment-868367920 for more information. + if (rrdset_free_obsolete_time_s < 10) { + rrdset_free_obsolete_time_s = 10; + netdata_log_info("The \"cleanup obsolete charts after\" option was set to 10 seconds."); + config_set_duration_seconds(CONFIG_SECTION_DB, "cleanup obsolete charts after", rrdset_free_obsolete_time_s); + } + + gap_when_lost_iterations_above = (int)config_get_number(CONFIG_SECTION_DB, "gap when lost iterations above", gap_when_lost_iterations_above); + if (gap_when_lost_iterations_above < 1) { + gap_when_lost_iterations_above = 1; + config_set_number(CONFIG_SECTION_DB, "gap when lost iterations above", gap_when_lost_iterations_above); + } + gap_when_lost_iterations_above += 2; + + // ------------------------------------------------------------------------ + + netdata_conf_dbengine_pre_logs(); +} diff --git a/src/daemon/config/netdata-conf-db.h b/src/daemon/config/netdata-conf-db.h new file mode 100644 index 00000000000000..2a623251e26dfc --- /dev/null +++ b/src/daemon/config/netdata-conf-db.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_DAEMON_NETDATA_CONF_DBENGINE_H +#define NETDATA_DAEMON_NETDATA_CONF_DBENGINE_H + +#include "libnetdata/libnetdata.h" + +extern bool dbengine_enabled; +extern size_t storage_tiers; +extern bool dbengine_use_direct_io; + +extern int default_rrd_update_every; +extern int default_rrd_history_entries; +extern int gap_when_lost_iterations_above; +extern time_t rrdset_free_obsolete_time_s; + +size_t get_tier_grouping(size_t tier); + +void netdata_conf_section_db(void); +void netdata_conf_dbengine_init(const char *hostname); + +#include "netdata-conf.h" + +#endif //NETDATA_DAEMON_NETDATA_CONF_DBENGINE_H diff --git a/src/daemon/config/netdata-conf-directories.c b/src/daemon/config/netdata-conf-directories.c new file mode 100644 index 00000000000000..562ce2da1f4814 --- /dev/null +++ b/src/daemon/config/netdata-conf-directories.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "netdata-conf-directories.h" + +static const char *get_varlib_subdir_from_config(const char *prefix, const char *dir) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/%s", prefix, dir); + return config_get(CONFIG_SECTION_DIRECTORIES, dir, filename); +} + +void netdata_conf_section_directories(void) { + static bool run = false; + if(run) return; + run = true; + + // ------------------------------------------------------------------------ + // get system paths + + netdata_configured_user_config_dir = config_get(CONFIG_SECTION_DIRECTORIES, "config", netdata_configured_user_config_dir); + netdata_configured_stock_config_dir = config_get(CONFIG_SECTION_DIRECTORIES, "stock config", netdata_configured_stock_config_dir); + netdata_configured_log_dir = config_get(CONFIG_SECTION_DIRECTORIES, "log", netdata_configured_log_dir); + netdata_configured_web_dir = config_get(CONFIG_SECTION_DIRECTORIES, "web", netdata_configured_web_dir); + netdata_configured_cache_dir = config_get(CONFIG_SECTION_DIRECTORIES, "cache", netdata_configured_cache_dir); + netdata_configured_varlib_dir = config_get(CONFIG_SECTION_DIRECTORIES, "lib", netdata_configured_varlib_dir); + + netdata_configured_lock_dir = get_varlib_subdir_from_config(netdata_configured_varlib_dir, "lock"); + netdata_configured_cloud_dir = get_varlib_subdir_from_config(netdata_configured_varlib_dir, "cloud.d"); + + pluginsd_initialize_plugin_directories(); + netdata_configured_primary_plugins_dir = plugin_directories[PLUGINSD_STOCK_PLUGINS_DIRECTORY_PATH]; +} diff --git a/src/daemon/config/netdata-conf-directories.h b/src/daemon/config/netdata-conf-directories.h new file mode 100644 index 00000000000000..52b6ae33a2ffa6 --- /dev/null +++ b/src/daemon/config/netdata-conf-directories.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_NETDATA_CONF_DIRECTORIES_H +#define NETDATA_NETDATA_CONF_DIRECTORIES_H + +#include "netdata-conf.h" + +void netdata_conf_section_directories(void); + +#endif //NETDATA_NETDATA_CONF_DIRECTORIES_H diff --git a/src/daemon/config/netdata-conf-global.c b/src/daemon/config/netdata-conf-global.c new file mode 100644 index 00000000000000..89473cc28efb7b --- /dev/null +++ b/src/daemon/config/netdata-conf-global.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "netdata-conf-global.h" + +static int get_hostname(char *buf, size_t buf_size) { + if (netdata_configured_host_prefix && *netdata_configured_host_prefix) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/etc/hostname", netdata_configured_host_prefix); + + if (!read_txt_file(filename, buf, buf_size)) { + trim(buf); + return 0; + } + } + + return gethostname(buf, buf_size); +} + +void netdata_conf_section_global(void) { + netdata_conf_backwards_compatibility(); + + // ------------------------------------------------------------------------ + // get the hostname + + netdata_configured_host_prefix = config_get(CONFIG_SECTION_GLOBAL, "host access prefix", ""); + (void) verify_netdata_host_prefix(true); + + char buf[HOSTNAME_MAX + 1]; + if (get_hostname(buf, HOSTNAME_MAX)) + netdata_log_error("Cannot get machine hostname."); + + netdata_configured_hostname = config_get(CONFIG_SECTION_GLOBAL, "hostname", buf); + netdata_log_debug(D_OPTIONS, "hostname set to '%s'", netdata_configured_hostname); + + netdata_conf_section_directories(); + netdata_conf_section_db(); + + // -------------------------------------------------------------------- + // get various system parameters + + os_get_system_cpus_uncached(); + os_get_system_pid_max(); +} + +void netdata_conf_section_global_run_as_user(const char **user) { + // -------------------------------------------------------------------- + // get the user we should run + + // IMPORTANT: this is required before web_files_uid() + if(getuid() == 0) { + *user = config_get(CONFIG_SECTION_GLOBAL, "run as user", NETDATA_USER); + } + else { + struct passwd *passwd = getpwuid(getuid()); + *user = config_get(CONFIG_SECTION_GLOBAL, "run as user", (passwd && passwd->pw_name)?passwd->pw_name:""); + } +} diff --git a/src/daemon/config/netdata-conf-global.h b/src/daemon/config/netdata-conf-global.h new file mode 100644 index 00000000000000..a87d90e1d39d49 --- /dev/null +++ b/src/daemon/config/netdata-conf-global.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_NETDATA_CONF_GLOBAL_H +#define NETDATA_NETDATA_CONF_GLOBAL_H + +#include "libnetdata/libnetdata.h" + +void netdata_conf_section_global(void); +void netdata_conf_section_global_run_as_user(const char **user); + +#include "netdata-conf.h" + +#endif //NETDATA_NETDATA_CONF_GLOBAL_H diff --git a/src/daemon/config/netdata-conf-logs.c b/src/daemon/config/netdata-conf-logs.c new file mode 100644 index 00000000000000..989bfbfd86a034 --- /dev/null +++ b/src/daemon/config/netdata-conf-logs.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "netdata-conf-logs.h" + +void netdata_conf_section_logs(void) { + static bool run = false; + if(run) return; + run = true; + + nd_log_set_facility(config_get(CONFIG_SECTION_LOGS, "facility", "daemon")); + + time_t period = ND_LOG_DEFAULT_THROTTLE_PERIOD; + size_t logs = ND_LOG_DEFAULT_THROTTLE_LOGS; + period = config_get_duration_seconds(CONFIG_SECTION_LOGS, "logs flood protection period", period); + logs = (unsigned long)config_get_number(CONFIG_SECTION_LOGS, "logs to trigger flood protection", (long long int)logs); + nd_log_set_flood_protection(logs, period); + + const char *netdata_log_level = getenv("NETDATA_LOG_LEVEL"); + netdata_log_level = netdata_log_level ? nd_log_id2priority(nd_log_priority2id(netdata_log_level)) : NDLP_INFO_STR; + + nd_log_set_priority_level(config_get(CONFIG_SECTION_LOGS, "level", netdata_log_level)); + + char filename[FILENAME_MAX + 1]; + char* os_default_method = NULL; +#if defined(OS_LINUX) + os_default_method = is_stderr_connected_to_journal() /* || nd_log_journal_socket_available() */ ? "journal" : NULL; +#elif defined(OS_WINDOWS) +#if defined(HAVE_ETW) + os_default_method = "etw"; +#elif defined(HAVE_WEL) + os_default_method = "wel"; +#endif +#endif + +#if defined(OS_WINDOWS) + // on windows, debug log goes to windows events + snprintfz(filename, FILENAME_MAX, "%s", os_default_method); +#else + snprintfz(filename, FILENAME_MAX, "%s/debug.log", netdata_configured_log_dir); +#endif + + nd_log_set_user_settings(NDLS_DEBUG, config_get(CONFIG_SECTION_LOGS, "debug", filename)); + + if(os_default_method) + snprintfz(filename, FILENAME_MAX, "%s", os_default_method); + else + snprintfz(filename, FILENAME_MAX, "%s/daemon.log", netdata_configured_log_dir); + nd_log_set_user_settings(NDLS_DAEMON, config_get(CONFIG_SECTION_LOGS, "daemon", filename)); + + if(os_default_method) + snprintfz(filename, FILENAME_MAX, "%s", os_default_method); + else + snprintfz(filename, FILENAME_MAX, "%s/collector.log", netdata_configured_log_dir); + nd_log_set_user_settings(NDLS_COLLECTORS, config_get(CONFIG_SECTION_LOGS, "collector", filename)); + +#if defined(OS_WINDOWS) + // on windows, access log goes to windows events + snprintfz(filename, FILENAME_MAX, "%s", os_default_method); +#else + snprintfz(filename, FILENAME_MAX, "%s/access.log", netdata_configured_log_dir); +#endif + nd_log_set_user_settings(NDLS_ACCESS, config_get(CONFIG_SECTION_LOGS, "access", filename)); + + if(os_default_method) + snprintfz(filename, FILENAME_MAX, "%s", os_default_method); + else + snprintfz(filename, FILENAME_MAX, "%s/health.log", netdata_configured_log_dir); + nd_log_set_user_settings(NDLS_HEALTH, config_get(CONFIG_SECTION_LOGS, "health", filename)); + + aclklog_enabled = config_get_boolean(CONFIG_SECTION_CLOUD, "conversation log", CONFIG_BOOLEAN_NO); + if (aclklog_enabled) { +#if defined(OS_WINDOWS) + // on windows, aclk log goes to windows events + snprintfz(filename, FILENAME_MAX, "%s", os_default_method); +#else + snprintfz(filename, FILENAME_MAX, "%s/aclk.log", netdata_configured_log_dir); +#endif + nd_log_set_user_settings(NDLS_ACLK, config_get(CONFIG_SECTION_CLOUD, "conversation log file", filename)); + } + + aclk_config_get_query_scope(); +} diff --git a/src/daemon/config/netdata-conf-logs.h b/src/daemon/config/netdata-conf-logs.h new file mode 100644 index 00000000000000..08c984a8935298 --- /dev/null +++ b/src/daemon/config/netdata-conf-logs.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_NETDATA_CONF_LOGS_H +#define NETDATA_NETDATA_CONF_LOGS_H + +#include "netdata-conf.h" + +void netdata_conf_section_logs(void); + +#endif //NETDATA_NETDATA_CONF_LOGS_H diff --git a/src/daemon/config/netdata-conf-web.c b/src/daemon/config/netdata-conf-web.c new file mode 100644 index 00000000000000..2bb9e7337a5223 --- /dev/null +++ b/src/daemon/config/netdata-conf-web.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "netdata-conf-web.h" +#include "daemon/static_threads.h" + +static int make_dns_decision(const char *section_name, const char *config_name, const char *default_value, SIMPLE_PATTERN *p) { + const char *value = config_get(section_name,config_name,default_value); + + if(!strcmp("yes",value)) + return 1; + + if(!strcmp("no",value)) + return 0; + + if(strcmp("heuristic",value) != 0) + netdata_log_error("Invalid configuration option '%s' for '%s'/'%s'. Valid options are 'yes', 'no' and 'heuristic'. Proceeding with 'heuristic'", + value, section_name, config_name); + + return simple_pattern_is_potential_name(p); +} + +extern struct netdata_static_thread *static_threads; +void web_server_threading_selection(void) { + static bool run = false; + if(run) return; + run = true; + + web_server_mode = web_server_mode_id(config_get(CONFIG_SECTION_WEB, "mode", web_server_mode_name(web_server_mode))); + + int static_threaded = (web_server_mode == WEB_SERVER_MODE_STATIC_THREADED); + + int i; + for (i = 0; static_threads[i].name; i++) { + if (static_threads[i].start_routine == socket_listen_main_static_threaded) + static_threads[i].enabled = static_threaded; + } +} + +void netdata_conf_section_web(void) { + static bool run = false; + if(run) return; + run = true; + + web_client_timeout = + (int)config_get_duration_seconds(CONFIG_SECTION_WEB, "disconnect idle clients after", web_client_timeout); + + web_client_first_request_timeout = + (int)config_get_duration_seconds(CONFIG_SECTION_WEB, "timeout for first request", web_client_first_request_timeout); + + web_client_streaming_rate_t = + config_get_duration_seconds(CONFIG_SECTION_WEB, "accept a streaming request every", web_client_streaming_rate_t); + + respect_web_browser_do_not_track_policy = + config_get_boolean(CONFIG_SECTION_WEB, "respect do not track policy", respect_web_browser_do_not_track_policy); + web_x_frame_options = config_get(CONFIG_SECTION_WEB, "x-frame-options response header", ""); + if(!*web_x_frame_options) + web_x_frame_options = NULL; + + web_allow_connections_from = + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow connections from", "localhost *"), + NULL, SIMPLE_PATTERN_EXACT, true); + web_allow_connections_dns = + make_dns_decision(CONFIG_SECTION_WEB, "allow connections by dns", "heuristic", web_allow_connections_from); + web_allow_dashboard_from = + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"), + NULL, SIMPLE_PATTERN_EXACT, true); + web_allow_dashboard_dns = + make_dns_decision(CONFIG_SECTION_WEB, "allow dashboard by dns", "heuristic", web_allow_dashboard_from); + web_allow_badges_from = + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), NULL, SIMPLE_PATTERN_EXACT, + true); + web_allow_badges_dns = + make_dns_decision(CONFIG_SECTION_WEB, "allow badges by dns", "heuristic", web_allow_badges_from); + web_allow_registry_from = + simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT, + true); + web_allow_registry_dns = make_dns_decision(CONFIG_SECTION_REGISTRY, "allow by dns", "heuristic", + web_allow_registry_from); + web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), + NULL, SIMPLE_PATTERN_EXACT, true); + web_allow_streaming_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow streaming by dns", "heuristic", + web_allow_streaming_from); + // Note the default is not heuristic, the wildcards could match DNS but the intent is ip-addresses. + web_allow_netdataconf_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow netdata.conf from", + "localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.*" + " 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.*" + " 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.*" + " 172.31.* UNKNOWN"), NULL, SIMPLE_PATTERN_EXACT, + true); + web_allow_netdataconf_dns = + make_dns_decision(CONFIG_SECTION_WEB, "allow netdata.conf by dns", "no", web_allow_netdataconf_from); + web_allow_mgmt_from = + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow management from", "localhost"), + NULL, SIMPLE_PATTERN_EXACT, true); + web_allow_mgmt_dns = + make_dns_decision(CONFIG_SECTION_WEB, "allow management by dns","heuristic",web_allow_mgmt_from); + + web_enable_gzip = config_get_boolean(CONFIG_SECTION_WEB, "enable gzip compression", web_enable_gzip); + + const char *s = config_get(CONFIG_SECTION_WEB, "gzip compression strategy", "default"); + if(!strcmp(s, "default")) + web_gzip_strategy = Z_DEFAULT_STRATEGY; + else if(!strcmp(s, "filtered")) + web_gzip_strategy = Z_FILTERED; + else if(!strcmp(s, "huffman only")) + web_gzip_strategy = Z_HUFFMAN_ONLY; + else if(!strcmp(s, "rle")) + web_gzip_strategy = Z_RLE; + else if(!strcmp(s, "fixed")) + web_gzip_strategy = Z_FIXED; + else { + netdata_log_error("Invalid compression strategy '%s'. Valid strategies are 'default', 'filtered', 'huffman only', 'rle' and 'fixed'. Proceeding with 'default'.", s); + web_gzip_strategy = Z_DEFAULT_STRATEGY; + } + + web_gzip_level = (int)config_get_number(CONFIG_SECTION_WEB, "gzip compression level", 3); + if(web_gzip_level < 1) { + netdata_log_error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 1 (fastest compression).", web_gzip_level); + web_gzip_level = 1; + } + else if(web_gzip_level > 9) { + netdata_log_error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 9 (best compression).", web_gzip_level); + web_gzip_level = 9; + } +} + +void netdata_conf_web_security_init(void) { + static bool run = false; + if(run) return; + run = true; + + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/ssl/key.pem",netdata_configured_user_config_dir); + netdata_ssl_security_key = config_get(CONFIG_SECTION_WEB, "ssl key", filename); + + snprintfz(filename, FILENAME_MAX, "%s/ssl/cert.pem",netdata_configured_user_config_dir); + netdata_ssl_security_cert = config_get(CONFIG_SECTION_WEB, "ssl certificate", filename); + + tls_version = config_get(CONFIG_SECTION_WEB, "tls version", "1.3"); + tls_ciphers = config_get(CONFIG_SECTION_WEB, "tls ciphers", "none"); + + netdata_ssl_initialize_openssl(); +} diff --git a/src/daemon/config/netdata-conf-web.h b/src/daemon/config/netdata-conf-web.h new file mode 100644 index 00000000000000..bc95a8202df4e7 --- /dev/null +++ b/src/daemon/config/netdata-conf-web.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_NETDATA_CONF_WEB_H +#define NETDATA_NETDATA_CONF_WEB_H + +#include "netdata-conf.h" + +void netdata_conf_section_web(void); +void web_server_threading_selection(void); +void netdata_conf_web_security_init(void); + +#endif //NETDATA_NETDATA_CONF_WEB_H diff --git a/src/daemon/config/netdata-conf.c b/src/daemon/config/netdata-conf.c new file mode 100644 index 00000000000000..d5d6105ff19996 --- /dev/null +++ b/src/daemon/config/netdata-conf.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "netdata-conf.h" + +bool netdata_conf_load(char *filename, char overwrite_used, const char **user) { + errno_clear(); + + int ret = 0; + + if(filename && *filename) { + ret = config_load(filename, overwrite_used, NULL); + if(!ret) + netdata_log_error("CONFIG: cannot load config file '%s'.", filename); + } + else { + filename = filename_from_path_entry_strdupz(netdata_configured_user_config_dir, "netdata.conf"); + + ret = config_load(filename, overwrite_used, NULL); + if(!ret) { + netdata_log_info("CONFIG: cannot load user config '%s'. Will try the stock version.", filename); + freez(filename); + + filename = filename_from_path_entry_strdupz(netdata_configured_stock_config_dir, "netdata.conf"); + ret = config_load(filename, overwrite_used, NULL); + if(!ret) + netdata_log_info("CONFIG: cannot load stock config '%s'. Running with internal defaults.", filename); + } + + freez(filename); + } + + netdata_conf_section_global_run_as_user(user); + return ret; +} diff --git a/src/daemon/config/netdata-conf.h b/src/daemon/config/netdata-conf.h new file mode 100644 index 00000000000000..d69f26ee14d5d4 --- /dev/null +++ b/src/daemon/config/netdata-conf.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_DAEMON_NETDATA_CONF_H +#define NETDATA_DAEMON_NETDATA_CONF_H + +#include "libnetdata/libnetdata.h" + +bool netdata_conf_load(char *filename, char overwrite_used, const char **user); + +#include "netdata-conf-backwards-compatibility.h" +#include "netdata-conf-db.h" +#include "netdata-conf-directories.h" +#include "netdata-conf-global.h" +#include "netdata-conf-logs.h" +#include "netdata-conf-web.h" + +#include "daemon/common.h" + +#endif //NETDATA_DAEMON_NETDATA_CONF_H diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c index 9d80917d65942c..d388ad538a8038 100644 --- a/src/daemon/daemon.c +++ b/src/daemon/daemon.c @@ -6,6 +6,45 @@ char *pidfile = NULL; char *netdata_exe_path = NULL; +long get_netdata_cpus(void) { + static long processors = 0; + + if(processors) + return processors; + + long cores_proc_stat = os_get_system_cpus_cached(false, true); + long cores_cpuset_v1 = (long)os_read_cpuset_cpus("/sys/fs/cgroup/cpuset/cpuset.cpus", cores_proc_stat); + long cores_cpuset_v2 = (long)os_read_cpuset_cpus("/sys/fs/cgroup/cpuset.cpus", cores_proc_stat); + + if(cores_cpuset_v2) + processors = cores_cpuset_v2; + else if(cores_cpuset_v1) + processors = cores_cpuset_v1; + else + processors = cores_proc_stat; + + long cores_user_configured = config_get_number(CONFIG_SECTION_GLOBAL, "cpu cores", processors); + + errno_clear(); + internal_error(true, + "System CPUs: %ld, (" + "system: %ld, cgroups cpuset v1: %ld, cgroups cpuset v2: %ld, netdata.conf: %ld" + ")" + , processors + , cores_proc_stat + , cores_cpuset_v1 + , cores_cpuset_v2 + , cores_user_configured + ); + + processors = cores_user_configured; + + if(processors < 1) + processors = 1; + + return processors; +} + void get_netdata_execution_path(void) { struct passwd *passwd = getpwuid(getuid()); char *user = (passwd && passwd->pw_name) ? passwd->pw_name : ""; diff --git a/src/daemon/config/dyncfg-echo.c b/src/daemon/dyncfg/dyncfg-echo.c similarity index 100% rename from src/daemon/config/dyncfg-echo.c rename to src/daemon/dyncfg/dyncfg-echo.c diff --git a/src/daemon/config/dyncfg-files.c b/src/daemon/dyncfg/dyncfg-files.c similarity index 100% rename from src/daemon/config/dyncfg-files.c rename to src/daemon/dyncfg/dyncfg-files.c diff --git a/src/daemon/config/dyncfg-inline.c b/src/daemon/dyncfg/dyncfg-inline.c similarity index 100% rename from src/daemon/config/dyncfg-inline.c rename to src/daemon/dyncfg/dyncfg-inline.c diff --git a/src/daemon/config/dyncfg-intercept.c b/src/daemon/dyncfg/dyncfg-intercept.c similarity index 100% rename from src/daemon/config/dyncfg-intercept.c rename to src/daemon/dyncfg/dyncfg-intercept.c diff --git a/src/daemon/config/dyncfg-internals.h b/src/daemon/dyncfg/dyncfg-internals.h similarity index 100% rename from src/daemon/config/dyncfg-internals.h rename to src/daemon/dyncfg/dyncfg-internals.h diff --git a/src/daemon/config/dyncfg-tree.c b/src/daemon/dyncfg/dyncfg-tree.c similarity index 100% rename from src/daemon/config/dyncfg-tree.c rename to src/daemon/dyncfg/dyncfg-tree.c diff --git a/src/daemon/config/dyncfg-unittest.c b/src/daemon/dyncfg/dyncfg-unittest.c similarity index 100% rename from src/daemon/config/dyncfg-unittest.c rename to src/daemon/dyncfg/dyncfg-unittest.c diff --git a/src/daemon/config/dyncfg.c b/src/daemon/dyncfg/dyncfg.c similarity index 100% rename from src/daemon/config/dyncfg.c rename to src/daemon/dyncfg/dyncfg.c diff --git a/src/daemon/config/dyncfg.h b/src/daemon/dyncfg/dyncfg.h similarity index 100% rename from src/daemon/config/dyncfg.h rename to src/daemon/dyncfg/dyncfg.h diff --git a/src/daemon/global_statistics.c b/src/daemon/global_statistics.c deleted file mode 100644 index 1bdaa4db40eaf1..00000000000000 --- a/src/daemon/global_statistics.c +++ /dev/null @@ -1,4339 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "common.h" - -#define GLOBAL_STATS_RESET_WEB_USEC_MAX 0x01 - -#define WORKER_JOB_GLOBAL 0 -#define WORKER_JOB_GLOBAL_EXT 1 -#define WORKER_JOB_REGISTRY 2 -#define WORKER_JOB_WORKERS 3 -#define WORKER_JOB_DBENGINE 4 -#define WORKER_JOB_HEARTBEAT 5 -#define WORKER_JOB_STRINGS 6 -#define WORKER_JOB_DICTIONARIES 7 -#define WORKER_JOB_MALLOC_TRACE 8 -#define WORKER_JOB_SQLITE3 9 - -#if WORKER_UTILIZATION_MAX_JOB_TYPES < 10 -#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 10 -#endif - -bool global_statistics_enabled = true; - -struct netdata_buffers_statistics netdata_buffers_statistics = {}; - -static size_t dbengine_total_memory = 0; -size_t rrddim_db_memory_size = 0; - -static struct global_statistics { - uint16_t connected_clients; - - uint64_t web_requests; - uint64_t web_usec; - uint64_t web_usec_max; - uint64_t bytes_received; - uint64_t bytes_sent; - uint64_t content_size; - uint64_t compressed_content_size; - - uint64_t web_client_count; - - uint64_t api_data_queries_made; - uint64_t api_data_db_points_read; - uint64_t api_data_result_points_generated; - - uint64_t api_weights_queries_made; - uint64_t api_weights_db_points_read; - uint64_t api_weights_result_points_generated; - - uint64_t api_badges_queries_made; - uint64_t api_badges_db_points_read; - uint64_t api_badges_result_points_generated; - - uint64_t health_queries_made; - uint64_t health_db_points_read; - uint64_t health_result_points_generated; - - uint64_t ml_queries_made; - uint64_t ml_db_points_read; - uint64_t ml_result_points_generated; - uint64_t ml_models_consulted; - - uint64_t exporters_queries_made; - uint64_t exporters_db_points_read; - - uint64_t backfill_queries_made; - uint64_t backfill_db_points_read; - - uint64_t tier0_hot_gorilla_buffers; - - uint64_t tier0_disk_compressed_bytes; - uint64_t tier0_disk_uncompressed_bytes; - - uint64_t db_points_stored_per_tier[RRD_STORAGE_TIERS]; - -} global_statistics = { - .connected_clients = 0, - .web_requests = 0, - .web_usec = 0, - .bytes_received = 0, - .bytes_sent = 0, - .content_size = 0, - .compressed_content_size = 0, - .web_client_count = 1, - - .api_data_queries_made = 0, - .api_data_db_points_read = 0, - .api_data_result_points_generated = 0, - - .tier0_hot_gorilla_buffers = 0, - .tier0_disk_compressed_bytes = 0, - .tier0_disk_uncompressed_bytes = 0, -}; - -void global_statistics_rrdset_done_chart_collection_completed(size_t *points_read_per_tier_array) { - for(size_t tier = 0; tier < storage_tiers ;tier++) { - __atomic_fetch_add(&global_statistics.db_points_stored_per_tier[tier], points_read_per_tier_array[tier], __ATOMIC_RELAXED); - points_read_per_tier_array[tier] = 0; - } -} - -void global_statistics_ml_query_completed(size_t points_read) { - __atomic_fetch_add(&global_statistics.ml_queries_made, 1, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.ml_db_points_read, points_read, __ATOMIC_RELAXED); -} - -void global_statistics_ml_models_consulted(size_t models_consulted) { - __atomic_fetch_add(&global_statistics.ml_models_consulted, models_consulted, __ATOMIC_RELAXED); -} - -void global_statistics_exporters_query_completed(size_t points_read) { - __atomic_fetch_add(&global_statistics.exporters_queries_made, 1, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.exporters_db_points_read, points_read, __ATOMIC_RELAXED); -} - -void global_statistics_backfill_query_completed(size_t points_read) { - __atomic_fetch_add(&global_statistics.backfill_queries_made, 1, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.backfill_db_points_read, points_read, __ATOMIC_RELAXED); -} - -void global_statistics_gorilla_buffer_add_hot() { - __atomic_fetch_add(&global_statistics.tier0_hot_gorilla_buffers, 1, __ATOMIC_RELAXED); -} - -void global_statistics_tier0_disk_compressed_bytes(uint32_t size) { - __atomic_fetch_add(&global_statistics.tier0_disk_compressed_bytes, size, __ATOMIC_RELAXED); -} - -void global_statistics_tier0_disk_uncompressed_bytes(uint32_t size) { - __atomic_fetch_add(&global_statistics.tier0_disk_uncompressed_bytes, size, __ATOMIC_RELAXED); -} - -void global_statistics_rrdr_query_completed(size_t queries, uint64_t db_points_read, uint64_t result_points_generated, QUERY_SOURCE query_source) { - switch(query_source) { - case QUERY_SOURCE_API_DATA: - __atomic_fetch_add(&global_statistics.api_data_queries_made, queries, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.api_data_db_points_read, db_points_read, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.api_data_result_points_generated, result_points_generated, __ATOMIC_RELAXED); - break; - - case QUERY_SOURCE_ML: - __atomic_fetch_add(&global_statistics.ml_queries_made, queries, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.ml_db_points_read, db_points_read, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.ml_result_points_generated, result_points_generated, __ATOMIC_RELAXED); - break; - - case QUERY_SOURCE_API_WEIGHTS: - __atomic_fetch_add(&global_statistics.api_weights_queries_made, queries, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.api_weights_db_points_read, db_points_read, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.api_weights_result_points_generated, result_points_generated, __ATOMIC_RELAXED); - break; - - case QUERY_SOURCE_API_BADGE: - __atomic_fetch_add(&global_statistics.api_badges_queries_made, queries, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.api_badges_db_points_read, db_points_read, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.api_badges_result_points_generated, result_points_generated, __ATOMIC_RELAXED); - break; - - case QUERY_SOURCE_HEALTH: - __atomic_fetch_add(&global_statistics.health_queries_made, queries, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.health_db_points_read, db_points_read, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.health_result_points_generated, result_points_generated, __ATOMIC_RELAXED); - break; - - default: - case QUERY_SOURCE_UNITTEST: - case QUERY_SOURCE_UNKNOWN: - break; - } -} - -void global_statistics_web_request_completed(uint64_t dt, - uint64_t bytes_received, - uint64_t bytes_sent, - uint64_t content_size, - uint64_t compressed_content_size) { - uint64_t old_web_usec_max = global_statistics.web_usec_max; - while(dt > old_web_usec_max) - __atomic_compare_exchange(&global_statistics.web_usec_max, &old_web_usec_max, &dt, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED); - - __atomic_fetch_add(&global_statistics.web_requests, 1, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.web_usec, dt, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.bytes_received, bytes_received, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.bytes_sent, bytes_sent, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.content_size, content_size, __ATOMIC_RELAXED); - __atomic_fetch_add(&global_statistics.compressed_content_size, compressed_content_size, __ATOMIC_RELAXED); -} - -uint64_t global_statistics_web_client_connected(void) { - __atomic_fetch_add(&global_statistics.connected_clients, 1, __ATOMIC_RELAXED); - return __atomic_fetch_add(&global_statistics.web_client_count, 1, __ATOMIC_RELAXED); -} - -void global_statistics_web_client_disconnected(void) { - __atomic_fetch_sub(&global_statistics.connected_clients, 1, __ATOMIC_RELAXED); -} - -static inline void global_statistics_copy(struct global_statistics *gs, uint8_t options) { - gs->connected_clients = __atomic_load_n(&global_statistics.connected_clients, __ATOMIC_RELAXED); - gs->web_requests = __atomic_load_n(&global_statistics.web_requests, __ATOMIC_RELAXED); - gs->web_usec = __atomic_load_n(&global_statistics.web_usec, __ATOMIC_RELAXED); - gs->web_usec_max = __atomic_load_n(&global_statistics.web_usec_max, __ATOMIC_RELAXED); - gs->bytes_received = __atomic_load_n(&global_statistics.bytes_received, __ATOMIC_RELAXED); - gs->bytes_sent = __atomic_load_n(&global_statistics.bytes_sent, __ATOMIC_RELAXED); - gs->content_size = __atomic_load_n(&global_statistics.content_size, __ATOMIC_RELAXED); - gs->compressed_content_size = __atomic_load_n(&global_statistics.compressed_content_size, __ATOMIC_RELAXED); - gs->web_client_count = __atomic_load_n(&global_statistics.web_client_count, __ATOMIC_RELAXED); - - gs->api_data_queries_made = __atomic_load_n(&global_statistics.api_data_queries_made, __ATOMIC_RELAXED); - gs->api_data_db_points_read = __atomic_load_n(&global_statistics.api_data_db_points_read, __ATOMIC_RELAXED); - gs->api_data_result_points_generated = __atomic_load_n(&global_statistics.api_data_result_points_generated, __ATOMIC_RELAXED); - - gs->api_weights_queries_made = __atomic_load_n(&global_statistics.api_weights_queries_made, __ATOMIC_RELAXED); - gs->api_weights_db_points_read = __atomic_load_n(&global_statistics.api_weights_db_points_read, __ATOMIC_RELAXED); - gs->api_weights_result_points_generated = __atomic_load_n(&global_statistics.api_weights_result_points_generated, __ATOMIC_RELAXED); - - gs->api_badges_queries_made = __atomic_load_n(&global_statistics.api_badges_queries_made, __ATOMIC_RELAXED); - gs->api_badges_db_points_read = __atomic_load_n(&global_statistics.api_badges_db_points_read, __ATOMIC_RELAXED); - gs->api_badges_result_points_generated = __atomic_load_n(&global_statistics.api_badges_result_points_generated, __ATOMIC_RELAXED); - - gs->health_queries_made = __atomic_load_n(&global_statistics.health_queries_made, __ATOMIC_RELAXED); - gs->health_db_points_read = __atomic_load_n(&global_statistics.health_db_points_read, __ATOMIC_RELAXED); - gs->health_result_points_generated = __atomic_load_n(&global_statistics.health_result_points_generated, __ATOMIC_RELAXED); - - gs->ml_queries_made = __atomic_load_n(&global_statistics.ml_queries_made, __ATOMIC_RELAXED); - gs->ml_db_points_read = __atomic_load_n(&global_statistics.ml_db_points_read, __ATOMIC_RELAXED); - gs->ml_result_points_generated = __atomic_load_n(&global_statistics.ml_result_points_generated, __ATOMIC_RELAXED); - gs->ml_models_consulted = __atomic_load_n(&global_statistics.ml_models_consulted, __ATOMIC_RELAXED); - - gs->exporters_queries_made = __atomic_load_n(&global_statistics.exporters_queries_made, __ATOMIC_RELAXED); - gs->exporters_db_points_read = __atomic_load_n(&global_statistics.exporters_db_points_read, __ATOMIC_RELAXED); - gs->backfill_queries_made = __atomic_load_n(&global_statistics.backfill_queries_made, __ATOMIC_RELAXED); - gs->backfill_db_points_read = __atomic_load_n(&global_statistics.backfill_db_points_read, __ATOMIC_RELAXED); - - gs->tier0_hot_gorilla_buffers = __atomic_load_n(&global_statistics.tier0_hot_gorilla_buffers, __ATOMIC_RELAXED); - - gs->tier0_disk_compressed_bytes = __atomic_load_n(&global_statistics.tier0_disk_compressed_bytes, __ATOMIC_RELAXED); - gs->tier0_disk_uncompressed_bytes = __atomic_load_n(&global_statistics.tier0_disk_uncompressed_bytes, __ATOMIC_RELAXED); - - for(size_t tier = 0; tier < storage_tiers ;tier++) - gs->db_points_stored_per_tier[tier] = __atomic_load_n(&global_statistics.db_points_stored_per_tier[tier], __ATOMIC_RELAXED); - - if(options & GLOBAL_STATS_RESET_WEB_USEC_MAX) { - uint64_t n = 0; - __atomic_compare_exchange(&global_statistics.web_usec_max, (uint64_t *) &gs->web_usec_max, &n, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED); - } -} - -#define dictionary_stats_memory_total(stats) \ - ((stats).memory.dict + (stats).memory.values + (stats).memory.index) - -static void global_statistics_charts(void) { - static unsigned long long old_web_requests = 0, old_web_usec = 0; - - static collected_number average_response_time = -1; - - static time_t netdata_boottime_time = 0; - if (!netdata_boottime_time) - netdata_boottime_time = now_boottime_sec(); - time_t netdata_uptime = now_boottime_sec() - netdata_boottime_time; - - struct global_statistics gs; - struct rusage me; - - global_statistics_copy(&gs, GLOBAL_STATS_RESET_WEB_USEC_MAX); - getrusage(RUSAGE_SELF, &me); - - // ---------------------------------------------------------------- - - { - static RRDSET *st_cpu = NULL; - static RRDDIM *rd_cpu_user = NULL, - *rd_cpu_system = NULL; - - if (unlikely(!st_cpu)) { - st_cpu = rrdset_create_localhost( - "netdata" - , "server_cpu" - , NULL - , "netdata" - , NULL - , "Netdata CPU usage" - , "milliseconds/s" - , "netdata" - , "stats" - , 130000 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - - rd_cpu_user = rrddim_add(st_cpu, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL); - rd_cpu_system = rrddim_add(st_cpu, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_cpu, rd_cpu_user, me.ru_utime.tv_sec * 1000000ULL + me.ru_utime.tv_usec); - rrddim_set_by_pointer(st_cpu, rd_cpu_system, me.ru_stime.tv_sec * 1000000ULL + me.ru_stime.tv_usec); - rrdset_done(st_cpu); - } - - { - static RRDSET *st_uptime = NULL; - static RRDDIM *rd_uptime = NULL; - - if (unlikely(!st_uptime)) { - st_uptime = rrdset_create_localhost( - "netdata", - "uptime", - NULL, - "netdata", - NULL, - "Netdata uptime", - "seconds", - "netdata", - "stats", - 130150, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_uptime = rrddim_add(st_uptime, "uptime", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st_uptime, rd_uptime, netdata_uptime); - rrdset_done(st_uptime); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_clients = NULL; - static RRDDIM *rd_clients = NULL; - - if (unlikely(!st_clients)) { - st_clients = rrdset_create_localhost( - "netdata" - , "clients" - , NULL - , "api" - , NULL - , "Netdata Web Clients" - , "connected clients" - , "netdata" - , "stats" - , 130200 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_clients = rrddim_add(st_clients, "clients", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st_clients, rd_clients, gs.connected_clients); - rrdset_done(st_clients); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_reqs = NULL; - static RRDDIM *rd_requests = NULL; - - if (unlikely(!st_reqs)) { - st_reqs = rrdset_create_localhost( - "netdata" - , "requests" - , NULL - , "api" - , NULL - , "Netdata Web Requests" - , "requests/s" - , "netdata" - , "stats" - , 130300 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_requests = rrddim_add(st_reqs, "requests", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_reqs, rd_requests, (collected_number) gs.web_requests); - rrdset_done(st_reqs); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_bytes = NULL; - static RRDDIM *rd_in = NULL, - *rd_out = NULL; - - if (unlikely(!st_bytes)) { - st_bytes = rrdset_create_localhost( - "netdata" - , "net" - , NULL - , "api" - , NULL - , "Netdata Network Traffic" - , "kilobits/s" - , "netdata" - , "stats" - , 130400 - , localhost->rrd_update_every - , RRDSET_TYPE_AREA - ); - - rd_in = rrddim_add(st_bytes, "in", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL); - rd_out = rrddim_add(st_bytes, "out", NULL, -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_bytes, rd_in, (collected_number) gs.bytes_received); - rrddim_set_by_pointer(st_bytes, rd_out, (collected_number) gs.bytes_sent); - rrdset_done(st_bytes); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_duration = NULL; - static RRDDIM *rd_average = NULL, - *rd_max = NULL; - - if (unlikely(!st_duration)) { - st_duration = rrdset_create_localhost( - "netdata" - , "response_time" - , NULL - , "api" - , NULL - , "Netdata API Response Time" - , "milliseconds/request" - , "netdata" - , "stats" - , 130500 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_average = rrddim_add(st_duration, "average", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); - rd_max = rrddim_add(st_duration, "max", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); - } - - uint64_t gweb_usec = gs.web_usec; - uint64_t gweb_requests = gs.web_requests; - - uint64_t web_usec = (gweb_usec >= old_web_usec) ? gweb_usec - old_web_usec : 0; - uint64_t web_requests = (gweb_requests >= old_web_requests) ? gweb_requests - old_web_requests : 0; - - old_web_usec = gweb_usec; - old_web_requests = gweb_requests; - - if (web_requests) - average_response_time = (collected_number) (web_usec / web_requests); - - if (unlikely(average_response_time != -1)) - rrddim_set_by_pointer(st_duration, rd_average, average_response_time); - else - rrddim_set_by_pointer(st_duration, rd_average, 0); - - rrddim_set_by_pointer(st_duration, rd_max, ((gs.web_usec_max)?(collected_number)gs.web_usec_max:average_response_time)); - rrdset_done(st_duration); - } -} - -static void global_statistics_extended_charts(void) { - static unsigned long long old_content_size = 0, old_compressed_content_size = 0; - static collected_number compression_ratio = -1; - - struct global_statistics gs; - struct replication_query_statistics replication = replication_get_query_statistics(); - global_statistics_copy(&gs, GLOBAL_STATS_RESET_WEB_USEC_MAX); - - { - static RRDSET *st_memory = NULL; - static RRDDIM *rd_database = NULL; - static RRDDIM *rd_collectors = NULL; - static RRDDIM *rd_hosts = NULL; - static RRDDIM *rd_rrd = NULL; - static RRDDIM *rd_contexts = NULL; - static RRDDIM *rd_health = NULL; - static RRDDIM *rd_functions = NULL; - static RRDDIM *rd_labels = NULL; - static RRDDIM *rd_strings = NULL; - static RRDDIM *rd_streaming = NULL; - static RRDDIM *rd_replication = NULL; - static RRDDIM *rd_buffers = NULL; - static RRDDIM *rd_workers = NULL; - static RRDDIM *rd_aral = NULL; - static RRDDIM *rd_judy = NULL; - static RRDDIM *rd_other = NULL; - - if (unlikely(!st_memory)) { - st_memory = rrdset_create_localhost( - "netdata", - "memory", - NULL, - "netdata", - NULL, - "Netdata Memory", - "bytes", - "netdata", - "stats", - 130100, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_database = rrddim_add(st_memory, "db", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_collectors = rrddim_add(st_memory, "collectors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_hosts = rrddim_add(st_memory, "hosts", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_rrd = rrddim_add(st_memory, "rrdset rrddim", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_contexts = rrddim_add(st_memory, "contexts", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_health = rrddim_add(st_memory, "health", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_functions = rrddim_add(st_memory, "functions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_labels = rrddim_add(st_memory, "labels", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_strings = rrddim_add(st_memory, "strings", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_streaming = rrddim_add(st_memory, "streaming", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_replication = rrddim_add(st_memory, "replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers = rrddim_add(st_memory, "buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_workers = rrddim_add(st_memory, "workers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_aral = rrddim_add(st_memory, "aral", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_judy = rrddim_add(st_memory, "judy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_other = rrddim_add(st_memory, "other", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - size_t buffers = - netdata_buffers_statistics.query_targets_size + - netdata_buffers_statistics.rrdset_done_rda_size + - netdata_buffers_statistics.buffers_aclk + - netdata_buffers_statistics.buffers_api + - netdata_buffers_statistics.buffers_functions + - netdata_buffers_statistics.buffers_sqlite + - netdata_buffers_statistics.buffers_exporters + - netdata_buffers_statistics.buffers_health + - netdata_buffers_statistics.buffers_streaming + - netdata_buffers_statistics.cbuffers_streaming + - netdata_buffers_statistics.buffers_web + - replication_allocated_buffers() + - aral_by_size_overhead() + - judy_aral_overhead(); - - size_t strings = 0; - string_statistics(NULL, NULL, NULL, NULL, NULL, &strings, NULL, NULL); - - rrddim_set_by_pointer(st_memory, rd_database, (collected_number)dbengine_total_memory + (collected_number)rrddim_db_memory_size); - rrddim_set_by_pointer(st_memory, rd_collectors, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_collectors)); - rrddim_set_by_pointer(st_memory, rd_hosts, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdhost) + (collected_number)netdata_buffers_statistics.rrdhost_allocations_size); - rrddim_set_by_pointer(st_memory, rd_rrd, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdset_rrddim)); - rrddim_set_by_pointer(st_memory, rd_contexts, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdcontext)); - rrddim_set_by_pointer(st_memory, rd_health, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdhealth)); - rrddim_set_by_pointer(st_memory, rd_functions, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_functions)); - rrddim_set_by_pointer(st_memory, rd_labels, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdlabels)); - rrddim_set_by_pointer(st_memory, rd_strings, (collected_number)strings); - rrddim_set_by_pointer(st_memory, rd_streaming, (collected_number)netdata_buffers_statistics.rrdhost_senders + (collected_number)netdata_buffers_statistics.rrdhost_receivers); - rrddim_set_by_pointer(st_memory, rd_replication, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_replication) + (collected_number)replication_allocated_memory()); - rrddim_set_by_pointer(st_memory, rd_buffers, (collected_number)buffers); - rrddim_set_by_pointer(st_memory, rd_workers, (collected_number) workers_allocated_memory()); - rrddim_set_by_pointer(st_memory, rd_aral, (collected_number) aral_by_size_structures()); - rrddim_set_by_pointer(st_memory, rd_judy, (collected_number) judy_aral_structures()); - rrddim_set_by_pointer(st_memory, rd_other, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_other)); - - rrdset_done(st_memory); - } - - { - static RRDSET *st_memory_buffers = NULL; - static RRDDIM *rd_queries = NULL; - static RRDDIM *rd_collectors = NULL; - static RRDDIM *rd_buffers_aclk = NULL; - static RRDDIM *rd_buffers_api = NULL; - static RRDDIM *rd_buffers_functions = NULL; - static RRDDIM *rd_buffers_sqlite = NULL; - static RRDDIM *rd_buffers_exporters = NULL; - static RRDDIM *rd_buffers_health = NULL; - static RRDDIM *rd_buffers_streaming = NULL; - static RRDDIM *rd_cbuffers_streaming = NULL; - static RRDDIM *rd_buffers_replication = NULL; - static RRDDIM *rd_buffers_web = NULL; - static RRDDIM *rd_buffers_aral = NULL; - static RRDDIM *rd_buffers_judy = NULL; - - if (unlikely(!st_memory_buffers)) { - st_memory_buffers = rrdset_create_localhost( - "netdata", - "memory_buffers", - NULL, - "netdata", - NULL, - "Netdata Memory Buffers", - "bytes", - "netdata", - "stats", - 130101, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_queries = rrddim_add(st_memory_buffers, "queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_collectors = rrddim_add(st_memory_buffers, "collection", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_aclk = rrddim_add(st_memory_buffers, "aclk", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_api = rrddim_add(st_memory_buffers, "api", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_functions = rrddim_add(st_memory_buffers, "functions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_sqlite = rrddim_add(st_memory_buffers, "sqlite", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_exporters = rrddim_add(st_memory_buffers, "exporters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_health = rrddim_add(st_memory_buffers, "health", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_streaming = rrddim_add(st_memory_buffers, "streaming", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_cbuffers_streaming = rrddim_add(st_memory_buffers, "streaming cbuf", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_replication = rrddim_add(st_memory_buffers, "replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_web = rrddim_add(st_memory_buffers, "web", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_aral = rrddim_add(st_memory_buffers, "aral", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_buffers_judy = rrddim_add(st_memory_buffers, "judy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st_memory_buffers, rd_queries, (collected_number)netdata_buffers_statistics.query_targets_size + (collected_number) onewayalloc_allocated_memory()); - rrddim_set_by_pointer(st_memory_buffers, rd_collectors, (collected_number)netdata_buffers_statistics.rrdset_done_rda_size); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_aclk, (collected_number)netdata_buffers_statistics.buffers_aclk); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_api, (collected_number)netdata_buffers_statistics.buffers_api); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_functions, (collected_number)netdata_buffers_statistics.buffers_functions); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_sqlite, (collected_number)netdata_buffers_statistics.buffers_sqlite); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_exporters, (collected_number)netdata_buffers_statistics.buffers_exporters); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_health, (collected_number)netdata_buffers_statistics.buffers_health); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_streaming, (collected_number)netdata_buffers_statistics.buffers_streaming); - rrddim_set_by_pointer(st_memory_buffers, rd_cbuffers_streaming, (collected_number)netdata_buffers_statistics.cbuffers_streaming); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_replication, (collected_number)replication_allocated_buffers()); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_web, (collected_number)netdata_buffers_statistics.buffers_web); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_aral, (collected_number)aral_by_size_overhead()); - rrddim_set_by_pointer(st_memory_buffers, rd_buffers_judy, (collected_number)judy_aral_overhead()); - - rrdset_done(st_memory_buffers); - } - - - { - static RRDSET *st_compression = NULL; - static RRDDIM *rd_savings = NULL; - - if (unlikely(!st_compression)) { - st_compression = rrdset_create_localhost( - "netdata" - , "compression_ratio" - , NULL - , "api" - , NULL - , "Netdata API Responses Compression Savings Ratio" - , "percentage" - , "netdata" - , "stats" - , 130600 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_savings = rrddim_add(st_compression, "savings", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); - } - - // since we don't lock here to read the global statistics - // read the smaller value first - unsigned long long gcompressed_content_size = gs.compressed_content_size; - unsigned long long gcontent_size = gs.content_size; - - unsigned long long compressed_content_size = gcompressed_content_size - old_compressed_content_size; - unsigned long long content_size = gcontent_size - old_content_size; - - old_compressed_content_size = gcompressed_content_size; - old_content_size = gcontent_size; - - if (content_size && content_size >= compressed_content_size) - compression_ratio = ((content_size - compressed_content_size) * 100 * 1000) / content_size; - - if (compression_ratio != -1) - rrddim_set_by_pointer(st_compression, rd_savings, compression_ratio); - - rrdset_done(st_compression); - } - - { - static RRDSET *st_queries = NULL; - static RRDDIM *rd_api_data_queries = NULL; - static RRDDIM *rd_api_weights_queries = NULL; - static RRDDIM *rd_api_badges_queries = NULL; - static RRDDIM *rd_health_queries = NULL; - static RRDDIM *rd_ml_queries = NULL; - static RRDDIM *rd_exporters_queries = NULL; - static RRDDIM *rd_backfill_queries = NULL; - static RRDDIM *rd_replication_queries = NULL; - - if (unlikely(!st_queries)) { - st_queries = rrdset_create_localhost( - "netdata" - , "queries" - , NULL - , "queries" - , NULL - , "Netdata DB Queries" - , "queries/s" - , "netdata" - , "stats" - , 131000 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - - rd_api_data_queries = rrddim_add(st_queries, "/api/v1/data", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_api_weights_queries = rrddim_add(st_queries, "/api/v1/weights", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_api_badges_queries = rrddim_add(st_queries, "/api/v1/badge", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_health_queries = rrddim_add(st_queries, "health", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_ml_queries = rrddim_add(st_queries, "ml", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_exporters_queries = rrddim_add(st_queries, "exporters", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_backfill_queries = rrddim_add(st_queries, "backfill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_replication_queries = rrddim_add(st_queries, "replication", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_queries, rd_api_data_queries, (collected_number)gs.api_data_queries_made); - rrddim_set_by_pointer(st_queries, rd_api_weights_queries, (collected_number)gs.api_weights_queries_made); - rrddim_set_by_pointer(st_queries, rd_api_badges_queries, (collected_number)gs.api_badges_queries_made); - rrddim_set_by_pointer(st_queries, rd_health_queries, (collected_number)gs.health_queries_made); - rrddim_set_by_pointer(st_queries, rd_ml_queries, (collected_number)gs.ml_queries_made); - rrddim_set_by_pointer(st_queries, rd_exporters_queries, (collected_number)gs.exporters_queries_made); - rrddim_set_by_pointer(st_queries, rd_backfill_queries, (collected_number)gs.backfill_queries_made); - rrddim_set_by_pointer(st_queries, rd_replication_queries, (collected_number)replication.queries_finished); - - rrdset_done(st_queries); - } - - { - static RRDSET *st_points_read = NULL; - static RRDDIM *rd_api_data_points_read = NULL; - static RRDDIM *rd_api_weights_points_read = NULL; - static RRDDIM *rd_api_badges_points_read = NULL; - static RRDDIM *rd_health_points_read = NULL; - static RRDDIM *rd_ml_points_read = NULL; - static RRDDIM *rd_exporters_points_read = NULL; - static RRDDIM *rd_backfill_points_read = NULL; - static RRDDIM *rd_replication_points_read = NULL; - - if (unlikely(!st_points_read)) { - st_points_read = rrdset_create_localhost( - "netdata" - , "db_points_read" - , NULL - , "queries" - , NULL - , "Netdata DB Points Query Read" - , "points/s" - , "netdata" - , "stats" - , 131001 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - - rd_api_data_points_read = rrddim_add(st_points_read, "/api/v1/data", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_api_weights_points_read = rrddim_add(st_points_read, "/api/v1/weights", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_api_badges_points_read = rrddim_add(st_points_read, "/api/v1/badge", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_health_points_read = rrddim_add(st_points_read, "health", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_ml_points_read = rrddim_add(st_points_read, "ml", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_exporters_points_read = rrddim_add(st_points_read, "exporters", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_backfill_points_read = rrddim_add(st_points_read, "backfill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_replication_points_read = rrddim_add(st_points_read, "replication", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_points_read, rd_api_data_points_read, (collected_number)gs.api_data_db_points_read); - rrddim_set_by_pointer(st_points_read, rd_api_weights_points_read, (collected_number)gs.api_weights_db_points_read); - rrddim_set_by_pointer(st_points_read, rd_api_badges_points_read, (collected_number)gs.api_badges_db_points_read); - rrddim_set_by_pointer(st_points_read, rd_health_points_read, (collected_number)gs.health_db_points_read); - rrddim_set_by_pointer(st_points_read, rd_ml_points_read, (collected_number)gs.ml_db_points_read); - rrddim_set_by_pointer(st_points_read, rd_exporters_points_read, (collected_number)gs.exporters_db_points_read); - rrddim_set_by_pointer(st_points_read, rd_backfill_points_read, (collected_number)gs.backfill_db_points_read); - rrddim_set_by_pointer(st_points_read, rd_replication_points_read, (collected_number)replication.points_read); - - rrdset_done(st_points_read); - } - - if(gs.api_data_result_points_generated || replication.points_generated) { - static RRDSET *st_points_generated = NULL; - static RRDDIM *rd_api_data_points_generated = NULL; - static RRDDIM *rd_api_weights_points_generated = NULL; - static RRDDIM *rd_api_badges_points_generated = NULL; - static RRDDIM *rd_health_points_generated = NULL; - static RRDDIM *rd_ml_points_generated = NULL; - static RRDDIM *rd_replication_points_generated = NULL; - - if (unlikely(!st_points_generated)) { - st_points_generated = rrdset_create_localhost( - "netdata" - , "db_points_results" - , NULL - , "queries" - , NULL - , "Netdata Points in Query Results" - , "points/s" - , "netdata" - , "stats" - , 131002 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - - rd_api_data_points_generated = rrddim_add(st_points_generated, "/api/v1/data", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_api_weights_points_generated = rrddim_add(st_points_generated, "/api/v1/weights", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_api_badges_points_generated = rrddim_add(st_points_generated, "/api/v1/badge", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_health_points_generated = rrddim_add(st_points_generated, "health", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_ml_points_generated = rrddim_add(st_points_generated, "ml", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_replication_points_generated = rrddim_add(st_points_generated, "replication", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_points_generated, rd_api_data_points_generated, (collected_number)gs.api_data_result_points_generated); - rrddim_set_by_pointer(st_points_generated, rd_api_weights_points_generated, (collected_number)gs.api_weights_result_points_generated); - rrddim_set_by_pointer(st_points_generated, rd_api_badges_points_generated, (collected_number)gs.api_badges_result_points_generated); - rrddim_set_by_pointer(st_points_generated, rd_health_points_generated, (collected_number)gs.health_result_points_generated); - rrddim_set_by_pointer(st_points_generated, rd_ml_points_generated, (collected_number)gs.ml_result_points_generated); - rrddim_set_by_pointer(st_points_generated, rd_replication_points_generated, (collected_number)replication.points_generated); - - rrdset_done(st_points_generated); - } - - ml_update_global_statistics_charts(gs.ml_models_consulted); - - { - static RRDSET *st_points_stored = NULL; - static RRDDIM *rds[RRD_STORAGE_TIERS] = {}; - - if (unlikely(!st_points_stored)) { - st_points_stored = rrdset_create_localhost( - "netdata" - , "db_points_stored" - , NULL - , "queries" - , NULL - , "Netdata DB Points Stored" - , "points/s" - , "netdata" - , "stats" - , 131003 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - - for(size_t tier = 0; tier < storage_tiers ;tier++) { - char buf[30 + 1]; - snprintfz(buf, sizeof(buf) - 1, "tier%zu", tier); - rds[tier] = rrddim_add(st_points_stored, buf, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - } - - for(size_t tier = 0; tier < storage_tiers ;tier++) - rrddim_set_by_pointer(st_points_stored, rds[tier], (collected_number)gs.db_points_stored_per_tier[tier]); - - rrdset_done(st_points_stored); - } - -#ifdef ENABLE_DBENGINE - if (tier_page_type[0] == RRDENG_PAGE_TYPE_GORILLA_32BIT) - { - static RRDSET *st_tier0_gorilla_pages = NULL; - static RRDDIM *rd_num_gorilla_pages = NULL; - - if (unlikely(!st_tier0_gorilla_pages)) { - st_tier0_gorilla_pages = rrdset_create_localhost( - "netdata" - , "tier0_gorilla_pages" - , NULL - , "dbengine gorilla" - , NULL - , "Number of gorilla_pages" - , "count" - , "netdata" - , "stats" - , 131004 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_num_gorilla_pages = rrddim_add(st_tier0_gorilla_pages, "count", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_tier0_gorilla_pages, rd_num_gorilla_pages, (collected_number)gs.tier0_hot_gorilla_buffers); - - rrdset_done(st_tier0_gorilla_pages); - } - - if (tier_page_type[0] == RRDENG_PAGE_TYPE_GORILLA_32BIT) - { - static RRDSET *st_tier0_compression_info = NULL; - - static RRDDIM *rd_compressed_bytes = NULL; - static RRDDIM *rd_uncompressed_bytes = NULL; - - if (unlikely(!st_tier0_compression_info)) { - st_tier0_compression_info = rrdset_create_localhost( - "netdata" - , "tier0_compression_info" - , NULL - , "dbengine gorilla" - , NULL - , "Tier 0 compression info" - , "bytes" - , "netdata" - , "stats" - , 131005 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_compressed_bytes = rrddim_add(st_tier0_compression_info, "compressed", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_uncompressed_bytes = rrddim_add(st_tier0_compression_info, "uncompressed", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st_tier0_compression_info, rd_compressed_bytes, (collected_number)gs.tier0_disk_compressed_bytes); - rrddim_set_by_pointer(st_tier0_compression_info, rd_uncompressed_bytes, (collected_number)gs.tier0_disk_uncompressed_bytes); - - rrdset_done(st_tier0_compression_info); - } -#endif -} -// ---------------------------------------------------------------------------- -// sqlite3 statistics - -struct sqlite3_statistics { - uint64_t sqlite3_queries_made; - uint64_t sqlite3_queries_ok; - uint64_t sqlite3_queries_failed; - uint64_t sqlite3_queries_failed_busy; - uint64_t sqlite3_queries_failed_locked; - uint64_t sqlite3_rows; - uint64_t sqlite3_metadata_cache_hit; - uint64_t sqlite3_context_cache_hit; - uint64_t sqlite3_metadata_cache_miss; - uint64_t sqlite3_context_cache_miss; - uint64_t sqlite3_metadata_cache_spill; - uint64_t sqlite3_context_cache_spill; - uint64_t sqlite3_metadata_cache_write; - uint64_t sqlite3_context_cache_write; - -} sqlite3_statistics = { }; - -void global_statistics_sqlite3_query_completed(bool success, bool busy, bool locked) { - __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_made, 1, __ATOMIC_RELAXED); - - if(success) { - __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_ok, 1, __ATOMIC_RELAXED); - } - else { - __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_failed, 1, __ATOMIC_RELAXED); - - if(busy) - __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_failed_busy, 1, __ATOMIC_RELAXED); - - if(locked) - __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_failed_locked, 1, __ATOMIC_RELAXED); - } -} - -void global_statistics_sqlite3_row_completed(void) { - __atomic_fetch_add(&sqlite3_statistics.sqlite3_rows, 1, __ATOMIC_RELAXED); -} - -static inline void sqlite3_statistics_copy(struct sqlite3_statistics *gs) { - static usec_t last_run = 0; - - gs->sqlite3_queries_made = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_made, __ATOMIC_RELAXED); - gs->sqlite3_queries_ok = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_ok, __ATOMIC_RELAXED); - gs->sqlite3_queries_failed = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_failed, __ATOMIC_RELAXED); - gs->sqlite3_queries_failed_busy = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_failed_busy, __ATOMIC_RELAXED); - gs->sqlite3_queries_failed_locked = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_failed_locked, __ATOMIC_RELAXED); - gs->sqlite3_rows = __atomic_load_n(&sqlite3_statistics.sqlite3_rows, __ATOMIC_RELAXED); - - usec_t timeout = default_rrd_update_every * USEC_PER_SEC + default_rrd_update_every * USEC_PER_SEC / 3; - usec_t now = now_monotonic_usec(); - if(!last_run) - last_run = now; - usec_t delta = now - last_run; - bool query_sqlite3 = delta < timeout; - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_metadata_cache_hit = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_HIT); - else { - gs->sqlite3_metadata_cache_hit = UINT64_MAX; - query_sqlite3 = false; - } - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_context_cache_hit = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_HIT); - else { - gs->sqlite3_context_cache_hit = UINT64_MAX; - query_sqlite3 = false; - } - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_metadata_cache_miss = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_MISS); - else { - gs->sqlite3_metadata_cache_miss = UINT64_MAX; - query_sqlite3 = false; - } - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_context_cache_miss = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_MISS); - else { - gs->sqlite3_context_cache_miss = UINT64_MAX; - query_sqlite3 = false; - } - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_metadata_cache_spill = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_SPILL); - else { - gs->sqlite3_metadata_cache_spill = UINT64_MAX; - query_sqlite3 = false; - } - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_context_cache_spill = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_SPILL); - else { - gs->sqlite3_context_cache_spill = UINT64_MAX; - query_sqlite3 = false; - } - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_metadata_cache_write = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_WRITE); - else { - gs->sqlite3_metadata_cache_write = UINT64_MAX; - query_sqlite3 = false; - } - - if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) - gs->sqlite3_context_cache_write = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_WRITE); - else { - gs->sqlite3_context_cache_write = UINT64_MAX; - query_sqlite3 = false; - } - - last_run = now_monotonic_usec(); -} - -static void sqlite3_statistics_charts(void) { - struct sqlite3_statistics gs; - sqlite3_statistics_copy(&gs); - - if(gs.sqlite3_queries_made) { - static RRDSET *st_sqlite3_queries = NULL; - static RRDDIM *rd_queries = NULL; - - if (unlikely(!st_sqlite3_queries)) { - st_sqlite3_queries = rrdset_create_localhost( - "netdata" - , "sqlite3_queries" - , NULL - , "sqlite3" - , NULL - , "Netdata SQLite3 Queries" - , "queries/s" - , "netdata" - , "stats" - , 131100 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_queries = rrddim_add(st_sqlite3_queries, "queries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_sqlite3_queries, rd_queries, (collected_number)gs.sqlite3_queries_made); - - rrdset_done(st_sqlite3_queries); - } - - // ---------------------------------------------------------------- - - if(gs.sqlite3_queries_ok || gs.sqlite3_queries_failed) { - static RRDSET *st_sqlite3_queries_by_status = NULL; - static RRDDIM *rd_ok = NULL, *rd_failed = NULL, *rd_busy = NULL, *rd_locked = NULL; - - if (unlikely(!st_sqlite3_queries_by_status)) { - st_sqlite3_queries_by_status = rrdset_create_localhost( - "netdata" - , "sqlite3_queries_by_status" - , NULL - , "sqlite3" - , NULL - , "Netdata SQLite3 Queries by status" - , "queries/s" - , "netdata" - , "stats" - , 131101 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_ok = rrddim_add(st_sqlite3_queries_by_status, "ok", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_failed = rrddim_add(st_sqlite3_queries_by_status, "failed", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_busy = rrddim_add(st_sqlite3_queries_by_status, "busy", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_locked = rrddim_add(st_sqlite3_queries_by_status, "locked", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_ok, (collected_number)gs.sqlite3_queries_made); - rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_failed, (collected_number)gs.sqlite3_queries_failed); - rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_busy, (collected_number)gs.sqlite3_queries_failed_busy); - rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_locked, (collected_number)gs.sqlite3_queries_failed_locked); - - rrdset_done(st_sqlite3_queries_by_status); - } - - // ---------------------------------------------------------------- - - if(gs.sqlite3_rows) { - static RRDSET *st_sqlite3_rows = NULL; - static RRDDIM *rd_rows = NULL; - - if (unlikely(!st_sqlite3_rows)) { - st_sqlite3_rows = rrdset_create_localhost( - "netdata" - , "sqlite3_rows" - , NULL - , "sqlite3" - , NULL - , "Netdata SQLite3 Rows" - , "rows/s" - , "netdata" - , "stats" - , 131102 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_rows = rrddim_add(st_sqlite3_rows, "ok", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - rrddim_set_by_pointer(st_sqlite3_rows, rd_rows, (collected_number)gs.sqlite3_rows); - - rrdset_done(st_sqlite3_rows); - } - - if(gs.sqlite3_metadata_cache_hit) { - static RRDSET *st_sqlite3_cache = NULL; - static RRDDIM *rd_cache_hit = NULL; - static RRDDIM *rd_cache_miss= NULL; - static RRDDIM *rd_cache_spill= NULL; - static RRDDIM *rd_cache_write= NULL; - - if (unlikely(!st_sqlite3_cache)) { - st_sqlite3_cache = rrdset_create_localhost( - "netdata" - , "sqlite3_metatada_cache" - , NULL - , "sqlite3" - , NULL - , "Netdata SQLite3 metadata cache" - , "ops/s" - , "netdata" - , "stats" - , 131103 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_cache_hit = rrddim_add(st_sqlite3_cache, "cache_hit", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_cache_miss = rrddim_add(st_sqlite3_cache, "cache_miss", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_cache_spill = rrddim_add(st_sqlite3_cache, "cache_spill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_cache_write = rrddim_add(st_sqlite3_cache, "cache_write", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - if(gs.sqlite3_metadata_cache_hit != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_hit, (collected_number)gs.sqlite3_metadata_cache_hit); - - if(gs.sqlite3_metadata_cache_miss != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_miss, (collected_number)gs.sqlite3_metadata_cache_miss); - - if(gs.sqlite3_metadata_cache_spill != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_spill, (collected_number)gs.sqlite3_metadata_cache_spill); - - if(gs.sqlite3_metadata_cache_write != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_write, (collected_number)gs.sqlite3_metadata_cache_write); - - rrdset_done(st_sqlite3_cache); - } - - if(gs.sqlite3_context_cache_hit) { - static RRDSET *st_sqlite3_cache = NULL; - static RRDDIM *rd_cache_hit = NULL; - static RRDDIM *rd_cache_miss= NULL; - static RRDDIM *rd_cache_spill= NULL; - static RRDDIM *rd_cache_write= NULL; - - if (unlikely(!st_sqlite3_cache)) { - st_sqlite3_cache = rrdset_create_localhost( - "netdata" - , "sqlite3_context_cache" - , NULL - , "sqlite3" - , NULL - , "Netdata SQLite3 context cache" - , "ops/s" - , "netdata" - , "stats" - , 131104 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - rd_cache_hit = rrddim_add(st_sqlite3_cache, "cache_hit", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_cache_miss = rrddim_add(st_sqlite3_cache, "cache_miss", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_cache_spill = rrddim_add(st_sqlite3_cache, "cache_spill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_cache_write = rrddim_add(st_sqlite3_cache, "cache_write", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - if(gs.sqlite3_context_cache_hit != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_hit, (collected_number)gs.sqlite3_context_cache_hit); - - if(gs.sqlite3_context_cache_miss != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_miss, (collected_number)gs.sqlite3_context_cache_miss); - - if(gs.sqlite3_context_cache_spill != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_spill, (collected_number)gs.sqlite3_context_cache_spill); - - if(gs.sqlite3_context_cache_write != UINT64_MAX) - rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_write, (collected_number)gs.sqlite3_context_cache_write); - - rrdset_done(st_sqlite3_cache); - } - - // ---------------------------------------------------------------- -} - -#ifdef ENABLE_DBENGINE - -struct dbengine2_cache_pointers { - RRDSET *st_cache_hit_ratio; - RRDDIM *rd_hit_ratio_closest; - RRDDIM *rd_hit_ratio_exact; - - RRDSET *st_operations; - RRDDIM *rd_searches_closest; - RRDDIM *rd_searches_exact; - RRDDIM *rd_add_hot; - RRDDIM *rd_add_clean; - RRDDIM *rd_evictions; - RRDDIM *rd_flushes; - RRDDIM *rd_acquires; - RRDDIM *rd_releases; - RRDDIM *rd_acquires_for_deletion; - - RRDSET *st_pgc_memory; - RRDDIM *rd_pgc_memory_free; - RRDDIM *rd_pgc_memory_clean; - RRDDIM *rd_pgc_memory_hot; - RRDDIM *rd_pgc_memory_dirty; - RRDDIM *rd_pgc_memory_index; - RRDDIM *rd_pgc_memory_evicting; - RRDDIM *rd_pgc_memory_flushing; - - RRDSET *st_pgc_tm; - RRDDIM *rd_pgc_tm_current; - RRDDIM *rd_pgc_tm_wanted; - RRDDIM *rd_pgc_tm_hot_max; - RRDDIM *rd_pgc_tm_dirty_max; - RRDDIM *rd_pgc_tm_hot; - RRDDIM *rd_pgc_tm_dirty; - RRDDIM *rd_pgc_tm_referenced; - - RRDSET *st_pgc_pages; - RRDDIM *rd_pgc_pages_clean; - RRDDIM *rd_pgc_pages_hot; - RRDDIM *rd_pgc_pages_dirty; - RRDDIM *rd_pgc_pages_referenced; - - RRDSET *st_pgc_memory_changes; - RRDDIM *rd_pgc_memory_new_hot; - RRDDIM *rd_pgc_memory_new_clean; - RRDDIM *rd_pgc_memory_clean_evictions; - - RRDSET *st_pgc_memory_migrations; - RRDDIM *rd_pgc_memory_hot_to_dirty; - RRDDIM *rd_pgc_memory_dirty_to_clean; - - RRDSET *st_pgc_workers; - RRDDIM *rd_pgc_workers_evictors; - RRDDIM *rd_pgc_workers_flushers; - RRDDIM *rd_pgc_workers_adders; - RRDDIM *rd_pgc_workers_searchers; - RRDDIM *rd_pgc_workers_jv2_flushers; - RRDDIM *rd_pgc_workers_hot2dirty; - - RRDSET *st_pgc_memory_events; - RRDDIM *rd_pgc_memory_evictions_critical; - RRDDIM *rd_pgc_memory_evictions_aggressive; - RRDDIM *rd_pgc_memory_flushes_critical; - - RRDSET *st_pgc_waste; - RRDDIM *rd_pgc_waste_evictions_skipped; - RRDDIM *rd_pgc_waste_flushes_cancelled; - RRDDIM *rd_pgc_waste_insert_spins; - RRDDIM *rd_pgc_waste_evict_spins; - RRDDIM *rd_pgc_waste_release_spins; - RRDDIM *rd_pgc_waste_acquire_spins; - RRDDIM *rd_pgc_waste_delete_spins; - RRDDIM *rd_pgc_waste_flush_spins; - -}; - -static void dbengine2_cache_statistics_charts(struct dbengine2_cache_pointers *ptrs, struct pgc_statistics *pgc_stats, struct pgc_statistics *pgc_stats_old __maybe_unused, const char *name, int priority) { - - { - if (unlikely(!ptrs->st_cache_hit_ratio)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_hit_ratio", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Hit Ratio", name); - - ptrs->st_cache_hit_ratio = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "%", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - ptrs->rd_hit_ratio_closest = rrddim_add(ptrs->st_cache_hit_ratio, "closest", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_hit_ratio_exact = rrddim_add(ptrs->st_cache_hit_ratio, "exact", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - size_t closest_percent = 100 * 10000; - if(pgc_stats->searches_closest > pgc_stats_old->searches_closest) - closest_percent = (pgc_stats->searches_closest_hits - pgc_stats_old->searches_closest_hits) * 100 * 10000 / (pgc_stats->searches_closest - pgc_stats_old->searches_closest); - - size_t exact_percent = 100 * 10000; - if(pgc_stats->searches_exact > pgc_stats_old->searches_exact) - exact_percent = (pgc_stats->searches_exact_hits - pgc_stats_old->searches_exact_hits) * 100 * 10000 / (pgc_stats->searches_exact - pgc_stats_old->searches_exact); - - rrddim_set_by_pointer(ptrs->st_cache_hit_ratio, ptrs->rd_hit_ratio_closest, (collected_number)closest_percent); - rrddim_set_by_pointer(ptrs->st_cache_hit_ratio, ptrs->rd_hit_ratio_exact, (collected_number)exact_percent); - - rrdset_done(ptrs->st_cache_hit_ratio); - } - - { - if (unlikely(!ptrs->st_operations)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_operations", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Operations", name); - - ptrs->st_operations = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "ops/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - ptrs->rd_searches_closest = rrddim_add(ptrs->st_operations, "search closest", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_searches_exact = rrddim_add(ptrs->st_operations, "search exact", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_add_hot = rrddim_add(ptrs->st_operations, "add hot", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_add_clean = rrddim_add(ptrs->st_operations, "add clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_evictions = rrddim_add(ptrs->st_operations, "evictions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_flushes = rrddim_add(ptrs->st_operations, "flushes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_acquires = rrddim_add(ptrs->st_operations, "acquires", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_releases = rrddim_add(ptrs->st_operations, "releases", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_acquires_for_deletion = rrddim_add(ptrs->st_operations, "del acquires", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_searches_closest, (collected_number)pgc_stats->searches_closest); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_searches_exact, (collected_number)pgc_stats->searches_exact); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_add_hot, (collected_number)pgc_stats->queues.hot.added_entries); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_add_clean, (collected_number)(pgc_stats->added_entries - pgc_stats->queues.hot.added_entries)); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_evictions, (collected_number)pgc_stats->queues.clean.removed_entries); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_flushes, (collected_number)pgc_stats->flushes_completed); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_acquires, (collected_number)pgc_stats->acquires); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_releases, (collected_number)pgc_stats->releases); - rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_acquires_for_deletion, (collected_number)pgc_stats->acquires_for_deletion); - - rrdset_done(ptrs->st_operations); - } - - { - if (unlikely(!ptrs->st_pgc_memory)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_memory", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Memory", name); - - ptrs->st_pgc_memory = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "bytes", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - ptrs->rd_pgc_memory_free = rrddim_add(ptrs->st_pgc_memory, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_memory_hot = rrddim_add(ptrs->st_pgc_memory, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_memory_dirty = rrddim_add(ptrs->st_pgc_memory, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_memory_clean = rrddim_add(ptrs->st_pgc_memory, "clean", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_memory_index = rrddim_add(ptrs->st_pgc_memory, "index", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_memory_evicting = rrddim_add(ptrs->st_pgc_memory, "evicting", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_memory_flushing = rrddim_add(ptrs->st_pgc_memory, "flushing", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - collected_number free = (pgc_stats->current_cache_size > pgc_stats->wanted_cache_size) ? 0 : - (collected_number)(pgc_stats->wanted_cache_size - pgc_stats->current_cache_size); - - rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_free, free); - rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_hot, (collected_number)pgc_stats->queues.hot.size); - rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_dirty, (collected_number)pgc_stats->queues.dirty.size); - rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_clean, (collected_number)pgc_stats->queues.clean.size); - rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_evicting, (collected_number)pgc_stats->evicting_size); - rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_flushing, (collected_number)pgc_stats->flushing_size); - rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_index, - (collected_number)(pgc_stats->size - pgc_stats->queues.clean.size - pgc_stats->queues.hot.size - pgc_stats->queues.dirty.size - pgc_stats->evicting_size - pgc_stats->flushing_size)); - - rrdset_done(ptrs->st_pgc_memory); - } - - { - if (unlikely(!ptrs->st_pgc_tm)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_target_memory", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Target Cache Memory", name); - - ptrs->st_pgc_tm = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "bytes", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - ptrs->rd_pgc_tm_current = rrddim_add(ptrs->st_pgc_tm, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_tm_wanted = rrddim_add(ptrs->st_pgc_tm, "wanted", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_tm_referenced = rrddim_add(ptrs->st_pgc_tm, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_tm_hot_max = rrddim_add(ptrs->st_pgc_tm, "hot max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_tm_dirty_max = rrddim_add(ptrs->st_pgc_tm, "dirty max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_tm_hot = rrddim_add(ptrs->st_pgc_tm, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_tm_dirty = rrddim_add(ptrs->st_pgc_tm, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_current, (collected_number)pgc_stats->current_cache_size); - rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_wanted, (collected_number)pgc_stats->wanted_cache_size); - rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_referenced, (collected_number)pgc_stats->referenced_size); - rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_hot_max, (collected_number)pgc_stats->queues.hot.max_size); - rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_dirty_max, (collected_number)pgc_stats->queues.dirty.max_size); - rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_hot, (collected_number)pgc_stats->queues.hot.size); - rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_dirty, (collected_number)pgc_stats->queues.dirty.size); - - rrdset_done(ptrs->st_pgc_tm); - } - - { - if (unlikely(!ptrs->st_pgc_pages)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_pages", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Pages", name); - - ptrs->st_pgc_pages = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "pages", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - ptrs->rd_pgc_pages_clean = rrddim_add(ptrs->st_pgc_pages, "clean", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_pages_hot = rrddim_add(ptrs->st_pgc_pages, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_pages_dirty = rrddim_add(ptrs->st_pgc_pages, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_pages_referenced = rrddim_add(ptrs->st_pgc_pages, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_clean, (collected_number)pgc_stats->queues.clean.entries); - rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_hot, (collected_number)pgc_stats->queues.hot.entries); - rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_dirty, (collected_number)pgc_stats->queues.dirty.entries); - rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_referenced, (collected_number)pgc_stats->referenced_entries); - - rrdset_done(ptrs->st_pgc_pages); - } - - { - if (unlikely(!ptrs->st_pgc_memory_changes)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_memory_changes", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Memory Changes", name); - - ptrs->st_pgc_memory_changes = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "bytes/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_AREA); - - ptrs->rd_pgc_memory_new_clean = rrddim_add(ptrs->st_pgc_memory_changes, "new clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_memory_clean_evictions = rrddim_add(ptrs->st_pgc_memory_changes, "evictions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_memory_new_hot = rrddim_add(ptrs->st_pgc_memory_changes, "new hot", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_new_clean, (collected_number)(pgc_stats->added_size - pgc_stats->queues.hot.added_size)); - rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_clean_evictions, (collected_number)pgc_stats->queues.clean.removed_size); - rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_new_hot, (collected_number)pgc_stats->queues.hot.added_size); - - rrdset_done(ptrs->st_pgc_memory_changes); - } - - { - if (unlikely(!ptrs->st_pgc_memory_migrations)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_memory_migrations", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Memory Migrations", name); - - ptrs->st_pgc_memory_migrations = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "bytes/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_AREA); - - ptrs->rd_pgc_memory_dirty_to_clean = rrddim_add(ptrs->st_pgc_memory_migrations, "dirty to clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_memory_hot_to_dirty = rrddim_add(ptrs->st_pgc_memory_migrations, "hot to dirty", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_pgc_memory_migrations, ptrs->rd_pgc_memory_dirty_to_clean, (collected_number)pgc_stats->queues.dirty.removed_size); - rrddim_set_by_pointer(ptrs->st_pgc_memory_migrations, ptrs->rd_pgc_memory_hot_to_dirty, (collected_number)pgc_stats->queues.dirty.added_size); - - rrdset_done(ptrs->st_pgc_memory_migrations); - } - - { - if (unlikely(!ptrs->st_pgc_memory_events)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_events", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Events", name); - - ptrs->st_pgc_memory_events = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "events/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_AREA); - - ptrs->rd_pgc_memory_evictions_aggressive = rrddim_add(ptrs->st_pgc_memory_events, "evictions aggressive", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_memory_evictions_critical = rrddim_add(ptrs->st_pgc_memory_events, "evictions critical", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_memory_flushes_critical = rrddim_add(ptrs->st_pgc_memory_events, "flushes critical", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_evictions_aggressive, (collected_number)pgc_stats->events_cache_needs_space_aggressively); - rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_evictions_critical, (collected_number)pgc_stats->events_cache_under_severe_pressure); - rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_flushes_critical, (collected_number)pgc_stats->events_flush_critical); - - rrdset_done(ptrs->st_pgc_memory_events); - } - - { - if (unlikely(!ptrs->st_pgc_waste)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_waste_events", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Waste Events", name); - - ptrs->st_pgc_waste = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "events/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - ptrs->rd_pgc_waste_evictions_skipped = rrddim_add(ptrs->st_pgc_waste, "evictions skipped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_waste_flushes_cancelled = rrddim_add(ptrs->st_pgc_waste, "flushes cancelled", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_waste_acquire_spins = rrddim_add(ptrs->st_pgc_waste, "acquire spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_waste_release_spins = rrddim_add(ptrs->st_pgc_waste, "release spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_waste_insert_spins = rrddim_add(ptrs->st_pgc_waste, "insert spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_waste_delete_spins = rrddim_add(ptrs->st_pgc_waste, "delete spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_waste_evict_spins = rrddim_add(ptrs->st_pgc_waste, "evict spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - ptrs->rd_pgc_waste_flush_spins = rrddim_add(ptrs->st_pgc_waste, "flush spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evictions_skipped, (collected_number)pgc_stats->evict_skipped); - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_flushes_cancelled, (collected_number)pgc_stats->flushes_cancelled); - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_acquire_spins, (collected_number)pgc_stats->acquire_spins); - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_release_spins, (collected_number)pgc_stats->release_spins); - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_insert_spins, (collected_number)pgc_stats->insert_spins); - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_delete_spins, (collected_number)pgc_stats->delete_spins); - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evict_spins, (collected_number)pgc_stats->evict_spins); - rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_flush_spins, (collected_number)pgc_stats->flush_spins); - - rrdset_done(ptrs->st_pgc_waste); - } - - { - if (unlikely(!ptrs->st_pgc_workers)) { - BUFFER *id = buffer_create(100, NULL); - buffer_sprintf(id, "dbengine_%s_cache_workers", name); - - BUFFER *family = buffer_create(100, NULL); - buffer_sprintf(family, "dbengine %s cache", name); - - BUFFER *title = buffer_create(100, NULL); - buffer_sprintf(title, "Netdata %s Cache Workers", name); - - ptrs->st_pgc_workers = rrdset_create_localhost( - "netdata", - buffer_tostring(id), - NULL, - buffer_tostring(family), - NULL, - buffer_tostring(title), - "workers", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - ptrs->rd_pgc_workers_searchers = rrddim_add(ptrs->st_pgc_workers, "searchers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_workers_adders = rrddim_add(ptrs->st_pgc_workers, "adders", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_workers_evictors = rrddim_add(ptrs->st_pgc_workers, "evictors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_workers_flushers = rrddim_add(ptrs->st_pgc_workers, "flushers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_workers_hot2dirty = rrddim_add(ptrs->st_pgc_workers, "hot2dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - ptrs->rd_pgc_workers_jv2_flushers = rrddim_add(ptrs->st_pgc_workers, "jv2 flushers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - buffer_free(id); - buffer_free(family); - buffer_free(title); - priority++; - } - - rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_searchers, (collected_number)pgc_stats->workers_search); - rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_adders, (collected_number)pgc_stats->workers_add); - rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_evictors, (collected_number)pgc_stats->workers_evict); - rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_flushers, (collected_number)pgc_stats->workers_flush); - rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_hot2dirty, (collected_number)pgc_stats->workers_hot2dirty); - rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_jv2_flushers, (collected_number)pgc_stats->workers_jv2_flush); - - rrdset_done(ptrs->st_pgc_workers); - } -} - - -static void dbengine2_statistics_charts(void) { - if(!main_cache || !main_mrg) - return; - - static struct dbengine2_cache_pointers main_cache_ptrs = {}, open_cache_ptrs = {}, extent_cache_ptrs = {}; - static struct rrdeng_cache_efficiency_stats cache_efficiency_stats = {}, cache_efficiency_stats_old = {}; - static struct pgc_statistics pgc_main_stats = {}, pgc_main_stats_old = {}; (void)pgc_main_stats_old; - static struct pgc_statistics pgc_open_stats = {}, pgc_open_stats_old = {}; (void)pgc_open_stats_old; - static struct pgc_statistics pgc_extent_stats = {}, pgc_extent_stats_old = {}; (void)pgc_extent_stats_old; - static struct mrg_statistics mrg_stats = {}, mrg_stats_old = {}; (void)mrg_stats_old; - - pgc_main_stats_old = pgc_main_stats; - pgc_main_stats = pgc_get_statistics(main_cache); - dbengine2_cache_statistics_charts(&main_cache_ptrs, &pgc_main_stats, &pgc_main_stats_old, "main", 135100); - - pgc_open_stats_old = pgc_open_stats; - pgc_open_stats = pgc_get_statistics(open_cache); - dbengine2_cache_statistics_charts(&open_cache_ptrs, &pgc_open_stats, &pgc_open_stats_old, "open", 135200); - - pgc_extent_stats_old = pgc_extent_stats; - pgc_extent_stats = pgc_get_statistics(extent_cache); - dbengine2_cache_statistics_charts(&extent_cache_ptrs, &pgc_extent_stats, &pgc_extent_stats_old, "extent", 135300); - - cache_efficiency_stats_old = cache_efficiency_stats; - cache_efficiency_stats = rrdeng_get_cache_efficiency_stats(); - - mrg_stats_old = mrg_stats; - mrg_get_statistics(main_mrg, &mrg_stats); - - struct rrdeng_buffer_sizes buffers = rrdeng_get_buffer_sizes(); - size_t buffers_total_size = buffers.handles + buffers.xt_buf + buffers.xt_io + buffers.pdc + buffers.descriptors + - buffers.opcodes + buffers.wal + buffers.workers + buffers.epdl + buffers.deol + buffers.pd + buffers.pgc + buffers.mrg; - -#ifdef PDC_USE_JULYL - buffers_total_size += buffers.julyl; -#endif - - dbengine_total_memory = pgc_main_stats.size + pgc_open_stats.size + pgc_extent_stats.size + mrg_stats.size + buffers_total_size; - - size_t priority = 135000; - - { - static RRDSET *st_pgc_memory = NULL; - static RRDDIM *rd_pgc_memory_main = NULL; - static RRDDIM *rd_pgc_memory_open = NULL; // open journal memory - static RRDDIM *rd_pgc_memory_extent = NULL; // extent compresses cache memory - static RRDDIM *rd_pgc_memory_metrics = NULL; // metric registry memory - static RRDDIM *rd_pgc_memory_buffers = NULL; - - if (unlikely(!st_pgc_memory)) { - st_pgc_memory = rrdset_create_localhost( - "netdata", - "dbengine_memory", - NULL, - "dbengine memory", - NULL, - "Netdata DB Memory", - "bytes", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_pgc_memory_main = rrddim_add(st_pgc_memory, "main cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_memory_open = rrddim_add(st_pgc_memory, "open cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_memory_extent = rrddim_add(st_pgc_memory, "extent cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_memory_metrics = rrddim_add(st_pgc_memory, "metrics registry", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_memory_buffers = rrddim_add(st_pgc_memory, "buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - priority++; - - - rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_main, (collected_number)pgc_main_stats.size); - rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_open, (collected_number)pgc_open_stats.size); - rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_extent, (collected_number)pgc_extent_stats.size); - rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_metrics, (collected_number)mrg_stats.size); - rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_buffers, (collected_number)buffers_total_size); - - rrdset_done(st_pgc_memory); - } - - { - static RRDSET *st_pgc_buffers = NULL; - static RRDDIM *rd_pgc_buffers_pgc = NULL; - static RRDDIM *rd_pgc_buffers_mrg = NULL; - static RRDDIM *rd_pgc_buffers_opcodes = NULL; - static RRDDIM *rd_pgc_buffers_handles = NULL; - static RRDDIM *rd_pgc_buffers_descriptors = NULL; - static RRDDIM *rd_pgc_buffers_wal = NULL; - static RRDDIM *rd_pgc_buffers_workers = NULL; - static RRDDIM *rd_pgc_buffers_pdc = NULL; - static RRDDIM *rd_pgc_buffers_xt_io = NULL; - static RRDDIM *rd_pgc_buffers_xt_buf = NULL; - static RRDDIM *rd_pgc_buffers_epdl = NULL; - static RRDDIM *rd_pgc_buffers_deol = NULL; - static RRDDIM *rd_pgc_buffers_pd = NULL; -#ifdef PDC_USE_JULYL - static RRDDIM *rd_pgc_buffers_julyl = NULL; -#endif - - if (unlikely(!st_pgc_buffers)) { - st_pgc_buffers = rrdset_create_localhost( - "netdata", - "dbengine_buffers", - NULL, - "dbengine memory", - NULL, - "Netdata DB Buffers", - "bytes", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_pgc_buffers_pgc = rrddim_add(st_pgc_buffers, "pgc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_mrg = rrddim_add(st_pgc_buffers, "mrg", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_opcodes = rrddim_add(st_pgc_buffers, "opcodes", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_handles = rrddim_add(st_pgc_buffers, "query handles", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_descriptors = rrddim_add(st_pgc_buffers, "descriptors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_wal = rrddim_add(st_pgc_buffers, "wal", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_workers = rrddim_add(st_pgc_buffers, "workers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_pdc = rrddim_add(st_pgc_buffers, "pdc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_pd = rrddim_add(st_pgc_buffers, "pd", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_xt_io = rrddim_add(st_pgc_buffers, "extent io", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_xt_buf = rrddim_add(st_pgc_buffers, "extent buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_epdl = rrddim_add(st_pgc_buffers, "epdl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_pgc_buffers_deol = rrddim_add(st_pgc_buffers, "deol", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); -#ifdef PDC_USE_JULYL - rd_pgc_buffers_julyl = rrddim_add(st_pgc_buffers, "julyl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); -#endif - } - priority++; - - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pgc, (collected_number)buffers.pgc); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_mrg, (collected_number)buffers.mrg); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_opcodes, (collected_number)buffers.opcodes); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_handles, (collected_number)buffers.handles); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_descriptors, (collected_number)buffers.descriptors); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_wal, (collected_number)buffers.wal); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_workers, (collected_number)buffers.workers); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pdc, (collected_number)buffers.pdc); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pd, (collected_number)buffers.pd); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_xt_io, (collected_number)buffers.xt_io); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_xt_buf, (collected_number)buffers.xt_buf); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_epdl, (collected_number)buffers.epdl); - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_deol, (collected_number)buffers.deol); -#ifdef PDC_USE_JULYL - rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_julyl, (collected_number)buffers.julyl); -#endif - - rrdset_done(st_pgc_buffers); - } - -#ifdef PDC_USE_JULYL - { - static RRDSET *st_julyl_moved = NULL; - static RRDDIM *rd_julyl_moved = NULL; - - if (unlikely(!st_julyl_moved)) { - st_julyl_moved = rrdset_create_localhost( - "netdata", - "dbengine_julyl_moved", - NULL, - "dbengine memory", - NULL, - "Netdata JulyL Memory Moved", - "bytes/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_AREA); - - rd_julyl_moved = rrddim_add(st_julyl_moved, "moved", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_julyl_moved, rd_julyl_moved, (collected_number)julyl_bytes_moved()); - - rrdset_done(st_julyl_moved); - } -#endif - - { - static RRDSET *st_mrg_metrics = NULL; - static RRDDIM *rd_mrg_metrics = NULL; - static RRDDIM *rd_mrg_acquired = NULL; - static RRDDIM *rd_mrg_collected = NULL; - static RRDDIM *rd_mrg_multiple_writers = NULL; - - if (unlikely(!st_mrg_metrics)) { - st_mrg_metrics = rrdset_create_localhost( - "netdata", - "dbengine_metrics", - NULL, - "dbengine metrics", - NULL, - "Netdata Metrics in Metrics Registry", - "metrics", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_mrg_metrics = rrddim_add(st_mrg_metrics, "all", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_mrg_acquired = rrddim_add(st_mrg_metrics, "acquired", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_mrg_collected = rrddim_add(st_mrg_metrics, "collected", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_mrg_multiple_writers = rrddim_add(st_mrg_metrics, "multi-collected", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - priority++; - - rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_metrics, (collected_number)mrg_stats.entries); - rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_acquired, (collected_number)mrg_stats.entries_referenced); - rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_collected, (collected_number)mrg_stats.writers); - rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_multiple_writers, (collected_number)mrg_stats.writers_conflicts); - - rrdset_done(st_mrg_metrics); - } - - { - static RRDSET *st_mrg_ops = NULL; - static RRDDIM *rd_mrg_add = NULL; - static RRDDIM *rd_mrg_del = NULL; - static RRDDIM *rd_mrg_search = NULL; - - if (unlikely(!st_mrg_ops)) { - st_mrg_ops = rrdset_create_localhost( - "netdata", - "dbengine_metrics_registry_operations", - NULL, - "dbengine metrics", - NULL, - "Netdata Metrics Registry Operations", - "metrics", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_mrg_add = rrddim_add(st_mrg_ops, "add", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_mrg_del = rrddim_add(st_mrg_ops, "delete", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_mrg_search = rrddim_add(st_mrg_ops, "search", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_mrg_ops, rd_mrg_add, (collected_number)mrg_stats.additions); - rrddim_set_by_pointer(st_mrg_ops, rd_mrg_del, (collected_number)mrg_stats.deletions); - rrddim_set_by_pointer(st_mrg_ops, rd_mrg_search, (collected_number)mrg_stats.search_hits + (collected_number)mrg_stats.search_misses); - - rrdset_done(st_mrg_ops); - } - - { - static RRDSET *st_mrg_references = NULL; - static RRDDIM *rd_mrg_references = NULL; - - if (unlikely(!st_mrg_references)) { - st_mrg_references = rrdset_create_localhost( - "netdata", - "dbengine_metrics_registry_references", - NULL, - "dbengine metrics", - NULL, - "Netdata Metrics Registry References", - "references", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_mrg_references = rrddim_add(st_mrg_references, "references", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - priority++; - - rrddim_set_by_pointer(st_mrg_references, rd_mrg_references, (collected_number)mrg_stats.current_references); - - rrdset_done(st_mrg_references); - } - - { - static RRDSET *st_cache_hit_ratio = NULL; - static RRDDIM *rd_hit_ratio = NULL; - static RRDDIM *rd_main_cache_hit_ratio = NULL; - static RRDDIM *rd_extent_cache_hit_ratio = NULL; - static RRDDIM *rd_extent_merge_hit_ratio = NULL; - - if (unlikely(!st_cache_hit_ratio)) { - st_cache_hit_ratio = rrdset_create_localhost( - "netdata", - "dbengine_cache_hit_ratio", - NULL, - "dbengine query router", - NULL, - "Netdata Queries Cache Hit Ratio", - "%", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_hit_ratio = rrddim_add(st_cache_hit_ratio, "overall", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); - rd_main_cache_hit_ratio = rrddim_add(st_cache_hit_ratio, "main cache", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); - rd_extent_cache_hit_ratio = rrddim_add(st_cache_hit_ratio, "extent cache", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); - rd_extent_merge_hit_ratio = rrddim_add(st_cache_hit_ratio, "extent merge", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); - } - priority++; - - size_t delta_pages_total = cache_efficiency_stats.pages_total - cache_efficiency_stats_old.pages_total; - size_t delta_pages_to_load_from_disk = cache_efficiency_stats.pages_to_load_from_disk - cache_efficiency_stats_old.pages_to_load_from_disk; - size_t delta_extents_loaded_from_disk = cache_efficiency_stats.extents_loaded_from_disk - cache_efficiency_stats_old.extents_loaded_from_disk; - - size_t delta_pages_data_source_main_cache = cache_efficiency_stats.pages_data_source_main_cache - cache_efficiency_stats_old.pages_data_source_main_cache; - size_t delta_pages_pending_found_in_cache_at_pass4 = cache_efficiency_stats.pages_data_source_main_cache_at_pass4 - cache_efficiency_stats_old.pages_data_source_main_cache_at_pass4; - - size_t delta_pages_data_source_extent_cache = cache_efficiency_stats.pages_data_source_extent_cache - cache_efficiency_stats_old.pages_data_source_extent_cache; - size_t delta_pages_load_extent_merged = cache_efficiency_stats.pages_load_extent_merged - cache_efficiency_stats_old.pages_load_extent_merged; - - size_t pages_total_hit = delta_pages_total - delta_extents_loaded_from_disk; - - static size_t overall_hit_ratio = 100; - size_t main_cache_hit_ratio = 0, extent_cache_hit_ratio = 0, extent_merge_hit_ratio = 0; - if(delta_pages_total) { - if(pages_total_hit > delta_pages_total) - pages_total_hit = delta_pages_total; - - overall_hit_ratio = pages_total_hit * 100 * 10000 / delta_pages_total; - - size_t delta_pages_main_cache = delta_pages_data_source_main_cache + delta_pages_pending_found_in_cache_at_pass4; - if(delta_pages_main_cache > delta_pages_total) - delta_pages_main_cache = delta_pages_total; - - main_cache_hit_ratio = delta_pages_main_cache * 100 * 10000 / delta_pages_total; - } - - if(delta_pages_to_load_from_disk) { - if(delta_pages_data_source_extent_cache > delta_pages_to_load_from_disk) - delta_pages_data_source_extent_cache = delta_pages_to_load_from_disk; - - extent_cache_hit_ratio = delta_pages_data_source_extent_cache * 100 * 10000 / delta_pages_to_load_from_disk; - - if(delta_pages_load_extent_merged > delta_pages_to_load_from_disk) - delta_pages_load_extent_merged = delta_pages_to_load_from_disk; - - extent_merge_hit_ratio = delta_pages_load_extent_merged * 100 * 10000 / delta_pages_to_load_from_disk; - } - - rrddim_set_by_pointer(st_cache_hit_ratio, rd_hit_ratio, (collected_number)overall_hit_ratio); - rrddim_set_by_pointer(st_cache_hit_ratio, rd_main_cache_hit_ratio, (collected_number)main_cache_hit_ratio); - rrddim_set_by_pointer(st_cache_hit_ratio, rd_extent_cache_hit_ratio, (collected_number)extent_cache_hit_ratio); - rrddim_set_by_pointer(st_cache_hit_ratio, rd_extent_merge_hit_ratio, (collected_number)extent_merge_hit_ratio); - - rrdset_done(st_cache_hit_ratio); - } - - { - static RRDSET *st_queries = NULL; - static RRDDIM *rd_total = NULL; - static RRDDIM *rd_open = NULL; - static RRDDIM *rd_jv2 = NULL; - static RRDDIM *rd_planned_with_gaps = NULL; - static RRDDIM *rd_executed_with_gaps = NULL; - - if (unlikely(!st_queries)) { - st_queries = rrdset_create_localhost( - "netdata", - "dbengine_queries", - NULL, - "dbengine query router", - NULL, - "Netdata Queries", - "queries/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_total = rrddim_add(st_queries, "total", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_open = rrddim_add(st_queries, "open cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_jv2 = rrddim_add(st_queries, "journal v2", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_planned_with_gaps = rrddim_add(st_queries, "planned with gaps", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_executed_with_gaps = rrddim_add(st_queries, "executed with gaps", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_queries, rd_total, (collected_number)cache_efficiency_stats.queries); - rrddim_set_by_pointer(st_queries, rd_open, (collected_number)cache_efficiency_stats.queries_open); - rrddim_set_by_pointer(st_queries, rd_jv2, (collected_number)cache_efficiency_stats.queries_journal_v2); - rrddim_set_by_pointer(st_queries, rd_planned_with_gaps, (collected_number)cache_efficiency_stats.queries_planned_with_gaps); - rrddim_set_by_pointer(st_queries, rd_executed_with_gaps, (collected_number)cache_efficiency_stats.queries_executed_with_gaps); - - rrdset_done(st_queries); - } - - { - static RRDSET *st_queries_running = NULL; - static RRDDIM *rd_queries = NULL; - - if (unlikely(!st_queries_running)) { - st_queries_running = rrdset_create_localhost( - "netdata", - "dbengine_queries_running", - NULL, - "dbengine query router", - NULL, - "Netdata Queries Running", - "queries", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_queries = rrddim_add(st_queries_running, "queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - priority++; - - rrddim_set_by_pointer(st_queries_running, rd_queries, (collected_number)cache_efficiency_stats.currently_running_queries); - - rrdset_done(st_queries_running); - } - - { - static RRDSET *st_query_pages_metadata_source = NULL; - static RRDDIM *rd_cache = NULL; - static RRDDIM *rd_open = NULL; - static RRDDIM *rd_jv2 = NULL; - - if (unlikely(!st_query_pages_metadata_source)) { - st_query_pages_metadata_source = rrdset_create_localhost( - "netdata", - "dbengine_query_pages_metadata_source", - NULL, - "dbengine query router", - NULL, - "Netdata Query Pages Metadata Source", - "pages/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_cache = rrddim_add(st_query_pages_metadata_source, "cache hit", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_jv2 = rrddim_add(st_query_pages_metadata_source, "journal v2 scan", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_open = rrddim_add(st_query_pages_metadata_source, "open journal", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_query_pages_metadata_source, rd_cache, (collected_number)cache_efficiency_stats.pages_meta_source_main_cache); - rrddim_set_by_pointer(st_query_pages_metadata_source, rd_jv2, (collected_number)cache_efficiency_stats.pages_meta_source_journal_v2); - rrddim_set_by_pointer(st_query_pages_metadata_source, rd_open, (collected_number)cache_efficiency_stats.pages_meta_source_open_cache); - - rrdset_done(st_query_pages_metadata_source); - } - - { - static RRDSET *st_query_pages_data_source = NULL; - static RRDDIM *rd_pages_main_cache = NULL; - static RRDDIM *rd_pages_disk = NULL; - static RRDDIM *rd_pages_extent_cache = NULL; - - if (unlikely(!st_query_pages_data_source)) { - st_query_pages_data_source = rrdset_create_localhost( - "netdata", - "dbengine_query_pages_data_source", - NULL, - "dbengine query router", - NULL, - "Netdata Query Pages to Data Source", - "pages/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_pages_main_cache = rrddim_add(st_query_pages_data_source, "main cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pages_disk = rrddim_add(st_query_pages_data_source, "disk", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pages_extent_cache = rrddim_add(st_query_pages_data_source, "extent cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_main_cache, (collected_number)cache_efficiency_stats.pages_data_source_main_cache + (collected_number)cache_efficiency_stats.pages_data_source_main_cache_at_pass4); - rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_disk, (collected_number)cache_efficiency_stats.pages_to_load_from_disk); - rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_extent_cache, (collected_number)cache_efficiency_stats.pages_data_source_extent_cache); - - rrdset_done(st_query_pages_data_source); - } - - { - static RRDSET *st_query_next_page = NULL; - static RRDDIM *rd_pass4 = NULL; - static RRDDIM *rd_nowait_failed = NULL; - static RRDDIM *rd_wait_failed = NULL; - static RRDDIM *rd_wait_loaded = NULL; - static RRDDIM *rd_nowait_loaded = NULL; - - if (unlikely(!st_query_next_page)) { - st_query_next_page = rrdset_create_localhost( - "netdata", - "dbengine_query_next_page", - NULL, - "dbengine query router", - NULL, - "Netdata Query Next Page", - "pages/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_pass4 = rrddim_add(st_query_next_page, "pass4", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_wait_failed = rrddim_add(st_query_next_page, "failed slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_nowait_failed = rrddim_add(st_query_next_page, "failed fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_wait_loaded = rrddim_add(st_query_next_page, "loaded slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_nowait_loaded = rrddim_add(st_query_next_page, "loaded fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_query_next_page, rd_pass4, (collected_number)cache_efficiency_stats.pages_data_source_main_cache_at_pass4); - rrddim_set_by_pointer(st_query_next_page, rd_wait_failed, (collected_number)cache_efficiency_stats.page_next_wait_failed); - rrddim_set_by_pointer(st_query_next_page, rd_nowait_failed, (collected_number)cache_efficiency_stats.page_next_nowait_failed); - rrddim_set_by_pointer(st_query_next_page, rd_wait_loaded, (collected_number)cache_efficiency_stats.page_next_wait_loaded); - rrddim_set_by_pointer(st_query_next_page, rd_nowait_loaded, (collected_number)cache_efficiency_stats.page_next_nowait_loaded); - - rrdset_done(st_query_next_page); - } - - { - static RRDSET *st_query_page_issues = NULL; - static RRDDIM *rd_pages_zero_time = NULL; - static RRDDIM *rd_pages_past_time = NULL; - static RRDDIM *rd_pages_invalid_size = NULL; - static RRDDIM *rd_pages_fixed_update_every = NULL; - static RRDDIM *rd_pages_fixed_entries = NULL; - static RRDDIM *rd_pages_overlapping = NULL; - - if (unlikely(!st_query_page_issues)) { - st_query_page_issues = rrdset_create_localhost( - "netdata", - "dbengine_query_next_page_issues", - NULL, - "dbengine query router", - NULL, - "Netdata Query Next Page Issues", - "pages/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_pages_zero_time = rrddim_add(st_query_page_issues, "zero timestamp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pages_invalid_size = rrddim_add(st_query_page_issues, "invalid size", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pages_past_time = rrddim_add(st_query_page_issues, "past time", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pages_overlapping = rrddim_add(st_query_page_issues, "overlapping", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pages_fixed_update_every = rrddim_add(st_query_page_issues, "update every fixed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pages_fixed_entries = rrddim_add(st_query_page_issues, "entries fixed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_query_page_issues, rd_pages_zero_time, (collected_number)cache_efficiency_stats.pages_zero_time_skipped); - rrddim_set_by_pointer(st_query_page_issues, rd_pages_invalid_size, (collected_number)cache_efficiency_stats.pages_invalid_size_skipped); - rrddim_set_by_pointer(st_query_page_issues, rd_pages_past_time, (collected_number)cache_efficiency_stats.pages_past_time_skipped); - rrddim_set_by_pointer(st_query_page_issues, rd_pages_overlapping, (collected_number)cache_efficiency_stats.pages_overlapping_skipped); - rrddim_set_by_pointer(st_query_page_issues, rd_pages_fixed_update_every, (collected_number)cache_efficiency_stats.pages_invalid_update_every_fixed); - rrddim_set_by_pointer(st_query_page_issues, rd_pages_fixed_entries, (collected_number)cache_efficiency_stats.pages_invalid_entries_fixed); - - rrdset_done(st_query_page_issues); - } - - { - static RRDSET *st_query_pages_from_disk = NULL; - static RRDDIM *rd_compressed = NULL; - static RRDDIM *rd_invalid = NULL; - static RRDDIM *rd_uncompressed = NULL; - static RRDDIM *rd_mmap_failed = NULL; - static RRDDIM *rd_unavailable = NULL; - static RRDDIM *rd_unroutable = NULL; - static RRDDIM *rd_not_found = NULL; - static RRDDIM *rd_cancelled = NULL; - static RRDDIM *rd_invalid_extent = NULL; - static RRDDIM *rd_extent_merged = NULL; - - if (unlikely(!st_query_pages_from_disk)) { - st_query_pages_from_disk = rrdset_create_localhost( - "netdata", - "dbengine_query_pages_disk_load", - NULL, - "dbengine query router", - NULL, - "Netdata Query Pages Loaded from Disk", - "pages/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_compressed = rrddim_add(st_query_pages_from_disk, "ok compressed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_invalid = rrddim_add(st_query_pages_from_disk, "fail invalid page", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_uncompressed = rrddim_add(st_query_pages_from_disk, "ok uncompressed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_mmap_failed = rrddim_add(st_query_pages_from_disk, "fail cant mmap", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_unavailable = rrddim_add(st_query_pages_from_disk, "fail unavailable", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_unroutable = rrddim_add(st_query_pages_from_disk, "fail unroutable", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_not_found = rrddim_add(st_query_pages_from_disk, "fail not found", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_invalid_extent = rrddim_add(st_query_pages_from_disk, "fail invalid extent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_extent_merged = rrddim_add(st_query_pages_from_disk, "extent merged", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_cancelled = rrddim_add(st_query_pages_from_disk, "cancelled", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_query_pages_from_disk, rd_compressed, (collected_number)cache_efficiency_stats.pages_load_ok_compressed); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_invalid, (collected_number)cache_efficiency_stats.pages_load_fail_invalid_page_in_extent); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_uncompressed, (collected_number)cache_efficiency_stats.pages_load_ok_uncompressed); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_mmap_failed, (collected_number)cache_efficiency_stats.pages_load_fail_cant_mmap_extent); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_unavailable, (collected_number)cache_efficiency_stats.pages_load_fail_datafile_not_available); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_unroutable, (collected_number)cache_efficiency_stats.pages_load_fail_unroutable); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_not_found, (collected_number)cache_efficiency_stats.pages_load_fail_not_found); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_cancelled, (collected_number)cache_efficiency_stats.pages_load_fail_cancelled); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_invalid_extent, (collected_number)cache_efficiency_stats.pages_load_fail_invalid_extent); - rrddim_set_by_pointer(st_query_pages_from_disk, rd_extent_merged, (collected_number)cache_efficiency_stats.pages_load_extent_merged); - - rrdset_done(st_query_pages_from_disk); - } - - { - static RRDSET *st_events = NULL; - static RRDDIM *rd_journal_v2_mapped = NULL; - static RRDDIM *rd_journal_v2_unmapped = NULL; - static RRDDIM *rd_datafile_creation = NULL; - static RRDDIM *rd_datafile_deletion = NULL; - static RRDDIM *rd_datafile_deletion_spin = NULL; - static RRDDIM *rd_jv2_indexing = NULL; - static RRDDIM *rd_retention = NULL; - - if (unlikely(!st_events)) { - st_events = rrdset_create_localhost( - "netdata", - "dbengine_events", - NULL, - "dbengine query router", - NULL, - "Netdata Database Events", - "events/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_journal_v2_mapped = rrddim_add(st_events, "journal v2 mapped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_journal_v2_unmapped = rrddim_add(st_events, "journal v2 unmapped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_datafile_creation = rrddim_add(st_events, "datafile creation", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_datafile_deletion = rrddim_add(st_events, "datafile deletion", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_datafile_deletion_spin = rrddim_add(st_events, "datafile deletion spin", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_jv2_indexing = rrddim_add(st_events, "journal v2 indexing", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_retention = rrddim_add(st_events, "retention", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_events, rd_journal_v2_mapped, (collected_number)cache_efficiency_stats.journal_v2_mapped); - rrddim_set_by_pointer(st_events, rd_journal_v2_unmapped, (collected_number)cache_efficiency_stats.journal_v2_unmapped); - rrddim_set_by_pointer(st_events, rd_datafile_creation, (collected_number)cache_efficiency_stats.datafile_creation_started); - rrddim_set_by_pointer(st_events, rd_datafile_deletion, (collected_number)cache_efficiency_stats.datafile_deletion_started); - rrddim_set_by_pointer(st_events, rd_datafile_deletion_spin, (collected_number)cache_efficiency_stats.datafile_deletion_spin); - rrddim_set_by_pointer(st_events, rd_jv2_indexing, (collected_number)cache_efficiency_stats.journal_v2_indexing_started); - rrddim_set_by_pointer(st_events, rd_retention, (collected_number)cache_efficiency_stats.metrics_retention_started); - - rrdset_done(st_events); - } - - { - static RRDSET *st_prep_timings = NULL; - static RRDDIM *rd_routing = NULL; - static RRDDIM *rd_main_cache = NULL; - static RRDDIM *rd_open_cache = NULL; - static RRDDIM *rd_journal_v2 = NULL; - static RRDDIM *rd_pass4 = NULL; - - if (unlikely(!st_prep_timings)) { - st_prep_timings = rrdset_create_localhost( - "netdata", - "dbengine_prep_timings", - NULL, - "dbengine query router", - NULL, - "Netdata Query Preparation Timings", - "usec/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_routing = rrddim_add(st_prep_timings, "routing", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_main_cache = rrddim_add(st_prep_timings, "main cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_open_cache = rrddim_add(st_prep_timings, "open cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_journal_v2 = rrddim_add(st_prep_timings, "journal v2", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_pass4 = rrddim_add(st_prep_timings, "pass4", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_prep_timings, rd_routing, (collected_number)cache_efficiency_stats.prep_time_to_route); - rrddim_set_by_pointer(st_prep_timings, rd_main_cache, (collected_number)cache_efficiency_stats.prep_time_in_main_cache_lookup); - rrddim_set_by_pointer(st_prep_timings, rd_open_cache, (collected_number)cache_efficiency_stats.prep_time_in_open_cache_lookup); - rrddim_set_by_pointer(st_prep_timings, rd_journal_v2, (collected_number)cache_efficiency_stats.prep_time_in_journal_v2_lookup); - rrddim_set_by_pointer(st_prep_timings, rd_pass4, (collected_number)cache_efficiency_stats.prep_time_in_pass4_lookup); - - rrdset_done(st_prep_timings); - } - - { - static RRDSET *st_query_timings = NULL; - static RRDDIM *rd_init = NULL; - static RRDDIM *rd_prep_wait = NULL; - static RRDDIM *rd_next_page_disk_fast = NULL; - static RRDDIM *rd_next_page_disk_slow = NULL; - static RRDDIM *rd_next_page_preload_fast = NULL; - static RRDDIM *rd_next_page_preload_slow = NULL; - - if (unlikely(!st_query_timings)) { - st_query_timings = rrdset_create_localhost( - "netdata", - "dbengine_query_timings", - NULL, - "dbengine query router", - NULL, - "Netdata Query Timings", - "usec/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - - rd_init = rrddim_add(st_query_timings, "init", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_prep_wait = rrddim_add(st_query_timings, "prep wait", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_next_page_disk_fast = rrddim_add(st_query_timings, "next page disk fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_next_page_disk_slow = rrddim_add(st_query_timings, "next page disk slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_next_page_preload_fast = rrddim_add(st_query_timings, "next page preload fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_next_page_preload_slow = rrddim_add(st_query_timings, "next page preload slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_query_timings, rd_init, (collected_number)cache_efficiency_stats.query_time_init); - rrddim_set_by_pointer(st_query_timings, rd_prep_wait, (collected_number)cache_efficiency_stats.query_time_wait_for_prep); - rrddim_set_by_pointer(st_query_timings, rd_next_page_disk_fast, (collected_number)cache_efficiency_stats.query_time_to_fast_disk_next_page); - rrddim_set_by_pointer(st_query_timings, rd_next_page_disk_slow, (collected_number)cache_efficiency_stats.query_time_to_slow_disk_next_page); - rrddim_set_by_pointer(st_query_timings, rd_next_page_preload_fast, (collected_number)cache_efficiency_stats.query_time_to_fast_preload_next_page); - rrddim_set_by_pointer(st_query_timings, rd_next_page_preload_slow, (collected_number)cache_efficiency_stats.query_time_to_slow_preload_next_page); - - rrdset_done(st_query_timings); - } - - if(netdata_rwlock_tryrdlock(&rrd_rwlock) == 0) { - priority = 135400; - - RRDHOST *host; - unsigned long long stats_array[RRDENG_NR_STATS] = {0}; - unsigned long long local_stats_array[RRDENG_NR_STATS]; - unsigned dbengine_contexts = 0, counted_multihost_db[RRD_STORAGE_TIERS] = { 0 }, i; - - rrdhost_foreach_read(host) { - if (!rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) { - - /* get localhost's DB engine's statistics for each tier */ - for(size_t tier = 0; tier < storage_tiers ;tier++) { - if(host->db[tier].mode != RRD_MEMORY_MODE_DBENGINE) continue; - if(!host->db[tier].si) continue; - - if(counted_multihost_db[tier]) - continue; - else - counted_multihost_db[tier] = 1; - - ++dbengine_contexts; - rrdeng_get_37_statistics((struct rrdengine_instance *)host->db[tier].si, local_stats_array); - for (i = 0; i < RRDENG_NR_STATS; ++i) { - /* aggregate statistics across hosts */ - stats_array[i] += local_stats_array[i]; - } - } - } - } - rrd_rdunlock(); - - if (dbengine_contexts) { - /* deduplicate global statistics by getting the ones from the last context */ - stats_array[30] = local_stats_array[30]; - stats_array[31] = local_stats_array[31]; - stats_array[32] = local_stats_array[32]; - stats_array[34] = local_stats_array[34]; - stats_array[36] = local_stats_array[36]; - - // ---------------------------------------------------------------- - - { - static RRDSET *st_compression = NULL; - static RRDDIM *rd_savings = NULL; - - if (unlikely(!st_compression)) { - st_compression = rrdset_create_localhost( - "netdata", - "dbengine_compression_ratio", - NULL, - "dbengine io", - NULL, - "Netdata DB engine data extents' compression savings ratio", - "percentage", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_savings = rrddim_add(st_compression, "savings", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); - } - priority++; - - unsigned long long ratio; - unsigned long long compressed_content_size = stats_array[12]; - unsigned long long content_size = stats_array[11]; - - if (content_size) { - // allow negative savings - ratio = ((content_size - compressed_content_size) * 100 * 1000) / content_size; - } else { - ratio = 0; - } - rrddim_set_by_pointer(st_compression, rd_savings, ratio); - - rrdset_done(st_compression); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_io_stats = NULL; - static RRDDIM *rd_reads = NULL; - static RRDDIM *rd_writes = NULL; - - if (unlikely(!st_io_stats)) { - st_io_stats = rrdset_create_localhost( - "netdata", - "dbengine_io_throughput", - NULL, - "dbengine io", - NULL, - "Netdata DB engine I/O throughput", - "MiB/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); - rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[17]); - rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[15]); - rrdset_done(st_io_stats); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_io_stats = NULL; - static RRDDIM *rd_reads = NULL; - static RRDDIM *rd_writes = NULL; - - if (unlikely(!st_io_stats)) { - st_io_stats = rrdset_create_localhost( - "netdata", - "dbengine_io_operations", - NULL, - "dbengine io", - NULL, - "Netdata DB engine I/O operations", - "operations/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[18]); - rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[16]); - rrdset_done(st_io_stats); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_errors = NULL; - static RRDDIM *rd_fs_errors = NULL; - static RRDDIM *rd_io_errors = NULL; - static RRDDIM *pg_cache_over_half_dirty_events = NULL; - - if (unlikely(!st_errors)) { - st_errors = rrdset_create_localhost( - "netdata", - "dbengine_global_errors", - NULL, - "dbengine io", - NULL, - "Netdata DB engine errors", - "errors/s", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_io_errors = rrddim_add(st_errors, "io_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_fs_errors = rrddim_add(st_errors, "fs_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - pg_cache_over_half_dirty_events = - rrddim_add(st_errors, "pg_cache_over_half_dirty_events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - priority++; - - rrddim_set_by_pointer(st_errors, rd_io_errors, (collected_number)stats_array[30]); - rrddim_set_by_pointer(st_errors, rd_fs_errors, (collected_number)stats_array[31]); - rrddim_set_by_pointer(st_errors, pg_cache_over_half_dirty_events, (collected_number)stats_array[34]); - rrdset_done(st_errors); - } - - // ---------------------------------------------------------------- - - { - static RRDSET *st_fd = NULL; - static RRDDIM *rd_fd_current = NULL; - static RRDDIM *rd_fd_max = NULL; - - if (unlikely(!st_fd)) { - st_fd = rrdset_create_localhost( - "netdata", - "dbengine_global_file_descriptors", - NULL, - "dbengine io", - NULL, - "Netdata DB engine File Descriptors", - "descriptors", - "netdata", - "stats", - priority, - localhost->rrd_update_every, - RRDSET_TYPE_LINE); - - rd_fd_current = rrddim_add(st_fd, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_fd_max = rrddim_add(st_fd, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - priority++; - - rrddim_set_by_pointer(st_fd, rd_fd_current, (collected_number)stats_array[32]); - /* Careful here, modify this accordingly if the File-Descriptor budget ever changes */ - rrddim_set_by_pointer(st_fd, rd_fd_max, (collected_number)rlimit_nofile.rlim_cur / 4); - rrdset_done(st_fd); - } - } - } -} -#endif // ENABLE_DBENGINE - -static void update_strings_charts() { - static RRDSET *st_ops = NULL, *st_entries = NULL, *st_mem = NULL; - static RRDDIM *rd_ops_inserts = NULL, *rd_ops_deletes = NULL; - static RRDDIM *rd_entries_entries = NULL; - static RRDDIM *rd_mem = NULL; -#ifdef NETDATA_INTERNAL_CHECKS - static RRDDIM *rd_entries_refs = NULL, *rd_ops_releases = NULL, *rd_ops_duplications = NULL, *rd_ops_searches = NULL; -#endif - - size_t inserts, deletes, searches, entries, references, memory, duplications, releases; - - string_statistics(&inserts, &deletes, &searches, &entries, &references, &memory, &duplications, &releases); - - if (unlikely(!st_ops)) { - st_ops = rrdset_create_localhost( - "netdata" - , "strings_ops" - , NULL - , "strings" - , NULL - , "Strings operations" - , "ops/s" - , "netdata" - , "stats" - , 910000 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE); - - rd_ops_inserts = rrddim_add(st_ops, "inserts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_ops_deletes = rrddim_add(st_ops, "deletes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); -#ifdef NETDATA_INTERNAL_CHECKS - rd_ops_searches = rrddim_add(st_ops, "searches", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_ops_duplications = rrddim_add(st_ops, "duplications", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_ops_releases = rrddim_add(st_ops, "releases", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); -#endif - } - - rrddim_set_by_pointer(st_ops, rd_ops_inserts, (collected_number)inserts); - rrddim_set_by_pointer(st_ops, rd_ops_deletes, (collected_number)deletes); -#ifdef NETDATA_INTERNAL_CHECKS - rrddim_set_by_pointer(st_ops, rd_ops_searches, (collected_number)searches); - rrddim_set_by_pointer(st_ops, rd_ops_duplications, (collected_number)duplications); - rrddim_set_by_pointer(st_ops, rd_ops_releases, (collected_number)releases); -#endif - rrdset_done(st_ops); - - if (unlikely(!st_entries)) { - st_entries = rrdset_create_localhost( - "netdata" - , "strings_entries" - , NULL - , "strings" - , NULL - , "Strings entries" - , "entries" - , "netdata" - , "stats" - , 910001 - , localhost->rrd_update_every - , RRDSET_TYPE_AREA); - - rd_entries_entries = rrddim_add(st_entries, "entries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); -#ifdef NETDATA_INTERNAL_CHECKS - rd_entries_refs = rrddim_add(st_entries, "references", NULL, 1, -1, RRD_ALGORITHM_ABSOLUTE); -#endif - } - - rrddim_set_by_pointer(st_entries, rd_entries_entries, (collected_number)entries); -#ifdef NETDATA_INTERNAL_CHECKS - rrddim_set_by_pointer(st_entries, rd_entries_refs, (collected_number)references); -#endif - rrdset_done(st_entries); - - if (unlikely(!st_mem)) { - st_mem = rrdset_create_localhost( - "netdata" - , "strings_memory" - , NULL - , "strings" - , NULL - , "Strings memory" - , "bytes" - , "netdata" - , "stats" - , 910001 - , localhost->rrd_update_every - , RRDSET_TYPE_AREA); - - rd_mem = rrddim_add(st_mem, "memory", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st_mem, rd_mem, (collected_number)memory); - rrdset_done(st_mem); -} - -static void update_heartbeat_charts() { - static RRDSET *st_heartbeat = NULL; - static RRDDIM *rd_heartbeat_min = NULL; - static RRDDIM *rd_heartbeat_max = NULL; - static RRDDIM *rd_heartbeat_avg = NULL; - - if (unlikely(!st_heartbeat)) { - st_heartbeat = rrdset_create_localhost( - "netdata" - , "heartbeat" - , NULL - , "heartbeat" - , NULL - , "System clock jitter" - , "microseconds" - , "netdata" - , "stats" - , 900000 - , localhost->rrd_update_every - , RRDSET_TYPE_AREA); - - rd_heartbeat_min = rrddim_add(st_heartbeat, "min", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_heartbeat_max = rrddim_add(st_heartbeat, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_heartbeat_avg = rrddim_add(st_heartbeat, "average", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - usec_t min, max, average; - size_t count; - - heartbeat_statistics(&min, &max, &average, &count); - - rrddim_set_by_pointer(st_heartbeat, rd_heartbeat_min, (collected_number)min); - rrddim_set_by_pointer(st_heartbeat, rd_heartbeat_max, (collected_number)max); - rrddim_set_by_pointer(st_heartbeat, rd_heartbeat_avg, (collected_number)average); - - rrdset_done(st_heartbeat); -} - -// --------------------------------------------------------------------------------------------------------------------- -// dictionary statistics - -struct dictionary_stats dictionary_stats_category_collectors = { .name = "collectors" }; -struct dictionary_stats dictionary_stats_category_rrdhost = { .name = "rrdhost" }; -struct dictionary_stats dictionary_stats_category_rrdset_rrddim = { .name = "rrdset_rrddim" }; -struct dictionary_stats dictionary_stats_category_rrdcontext = { .name = "context" }; -struct dictionary_stats dictionary_stats_category_rrdlabels = { .name = "labels" }; -struct dictionary_stats dictionary_stats_category_rrdhealth = { .name = "health" }; -struct dictionary_stats dictionary_stats_category_functions = { .name = "functions" }; -struct dictionary_stats dictionary_stats_category_replication = { .name = "replication" }; - -#ifdef DICT_WITH_STATS -struct dictionary_categories { - struct dictionary_stats *stats; - const char *family; - const char *context_prefix; - int priority; - - RRDSET *st_dicts; - RRDDIM *rd_dicts_active; - RRDDIM *rd_dicts_deleted; - - RRDSET *st_items; - RRDDIM *rd_items_entries; - RRDDIM *rd_items_referenced; - RRDDIM *rd_items_pending_deletion; - - RRDSET *st_ops; - RRDDIM *rd_ops_creations; - RRDDIM *rd_ops_destructions; - RRDDIM *rd_ops_flushes; - RRDDIM *rd_ops_traversals; - RRDDIM *rd_ops_walkthroughs; - RRDDIM *rd_ops_garbage_collections; - RRDDIM *rd_ops_searches; - RRDDIM *rd_ops_inserts; - RRDDIM *rd_ops_resets; - RRDDIM *rd_ops_deletes; - - RRDSET *st_callbacks; - RRDDIM *rd_callbacks_inserts; - RRDDIM *rd_callbacks_conflicts; - RRDDIM *rd_callbacks_reacts; - RRDDIM *rd_callbacks_deletes; - - RRDSET *st_memory; - RRDDIM *rd_memory_indexed; - RRDDIM *rd_memory_values; - RRDDIM *rd_memory_dict; - - RRDSET *st_spins; - RRDDIM *rd_spins_use; - RRDDIM *rd_spins_search; - RRDDIM *rd_spins_insert; - RRDDIM *rd_spins_delete; - -} dictionary_categories[] = { - { .stats = &dictionary_stats_category_collectors, "dictionaries collectors", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_rrdhost, "dictionaries hosts", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_rrdset_rrddim, "dictionaries rrd", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_rrdcontext, "dictionaries contexts", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_rrdlabels, "dictionaries labels", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_rrdhealth, "dictionaries health", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_functions, "dictionaries functions", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_replication, "dictionaries replication", "dictionaries", 900000 }, - { .stats = &dictionary_stats_category_other, "dictionaries other", "dictionaries", 900000 }, - - // terminator - { .stats = NULL, NULL, NULL, 0 }, -}; - -#define load_dictionary_stats_entry(x) total += (size_t)(stats.x = __atomic_load_n(&c->stats->x, __ATOMIC_RELAXED)) - -static void update_dictionary_category_charts(struct dictionary_categories *c) { - struct dictionary_stats stats; - stats.name = c->stats->name; - - // ------------------------------------------------------------------------ - - size_t total = 0; - load_dictionary_stats_entry(dictionaries.active); - load_dictionary_stats_entry(dictionaries.deleted); - - if(c->st_dicts || total != 0) { - if (unlikely(!c->st_dicts)) { - char id[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.dictionaries", c->context_prefix, stats.name); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.dictionaries", c->context_prefix); - - c->st_dicts = rrdset_create_localhost( - "netdata" - , id - , NULL - , c->family - , context - , "Dictionaries" - , "dictionaries" - , "netdata" - , "stats" - , c->priority + 0 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - c->rd_dicts_active = rrddim_add(c->st_dicts, "active", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - c->rd_dicts_deleted = rrddim_add(c->st_dicts, "deleted", NULL, -1, 1, RRD_ALGORITHM_ABSOLUTE); - - rrdlabels_add(c->st_dicts->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); - } - - rrddim_set_by_pointer(c->st_dicts, c->rd_dicts_active, (collected_number)stats.dictionaries.active); - rrddim_set_by_pointer(c->st_dicts, c->rd_dicts_deleted, (collected_number)stats.dictionaries.deleted); - rrdset_done(c->st_dicts); - } - - // ------------------------------------------------------------------------ - - total = 0; - load_dictionary_stats_entry(items.entries); - load_dictionary_stats_entry(items.referenced); - load_dictionary_stats_entry(items.pending_deletion); - - if(c->st_items || total != 0) { - if (unlikely(!c->st_items)) { - char id[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.items", c->context_prefix, stats.name); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.items", c->context_prefix); - - c->st_items = rrdset_create_localhost( - "netdata" - , id - , NULL - , c->family - , context - , "Dictionary Items" - , "items" - , "netdata" - , "stats" - , c->priority + 1 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - c->rd_items_entries = rrddim_add(c->st_items, "active", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - c->rd_items_pending_deletion = rrddim_add(c->st_items, "deleted", NULL, -1, 1, RRD_ALGORITHM_ABSOLUTE); - c->rd_items_referenced = rrddim_add(c->st_items, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - rrdlabels_add(c->st_items->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); - } - - rrddim_set_by_pointer(c->st_items, c->rd_items_entries, stats.items.entries); - rrddim_set_by_pointer(c->st_items, c->rd_items_pending_deletion, stats.items.pending_deletion); - rrddim_set_by_pointer(c->st_items, c->rd_items_referenced, stats.items.referenced); - rrdset_done(c->st_items); - } - - // ------------------------------------------------------------------------ - - total = 0; - load_dictionary_stats_entry(ops.creations); - load_dictionary_stats_entry(ops.destructions); - load_dictionary_stats_entry(ops.flushes); - load_dictionary_stats_entry(ops.traversals); - load_dictionary_stats_entry(ops.walkthroughs); - load_dictionary_stats_entry(ops.garbage_collections); - load_dictionary_stats_entry(ops.searches); - load_dictionary_stats_entry(ops.inserts); - load_dictionary_stats_entry(ops.resets); - load_dictionary_stats_entry(ops.deletes); - - if(c->st_ops || total != 0) { - if (unlikely(!c->st_ops)) { - char id[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.ops", c->context_prefix, stats.name); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.ops", c->context_prefix); - - c->st_ops = rrdset_create_localhost( - "netdata" - , id - , NULL - , c->family - , context - , "Dictionary Operations" - , "ops/s" - , "netdata" - , "stats" - , c->priority + 2 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - c->rd_ops_creations = rrddim_add(c->st_ops, "creations", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_destructions = rrddim_add(c->st_ops, "destructions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_flushes = rrddim_add(c->st_ops, "flushes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_traversals = rrddim_add(c->st_ops, "traversals", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_walkthroughs = rrddim_add(c->st_ops, "walkthroughs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_garbage_collections = rrddim_add(c->st_ops, "garbage_collections", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_searches = rrddim_add(c->st_ops, "searches", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_inserts = rrddim_add(c->st_ops, "inserts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_resets = rrddim_add(c->st_ops, "resets", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_ops_deletes = rrddim_add(c->st_ops, "deletes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - rrdlabels_add(c->st_ops->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); - } - - rrddim_set_by_pointer(c->st_ops, c->rd_ops_creations, (collected_number)stats.ops.creations); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_destructions, (collected_number)stats.ops.destructions); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_flushes, (collected_number)stats.ops.flushes); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_traversals, (collected_number)stats.ops.traversals); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_walkthroughs, (collected_number)stats.ops.walkthroughs); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_garbage_collections, (collected_number)stats.ops.garbage_collections); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_searches, (collected_number)stats.ops.searches); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_inserts, (collected_number)stats.ops.inserts); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_resets, (collected_number)stats.ops.resets); - rrddim_set_by_pointer(c->st_ops, c->rd_ops_deletes, (collected_number)stats.ops.deletes); - - rrdset_done(c->st_ops); - } - - // ------------------------------------------------------------------------ - - total = 0; - load_dictionary_stats_entry(callbacks.inserts); - load_dictionary_stats_entry(callbacks.conflicts); - load_dictionary_stats_entry(callbacks.reacts); - load_dictionary_stats_entry(callbacks.deletes); - - if(c->st_callbacks || total != 0) { - if (unlikely(!c->st_callbacks)) { - char id[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.callbacks", c->context_prefix, stats.name); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.callbacks", c->context_prefix); - - c->st_callbacks = rrdset_create_localhost( - "netdata" - , id - , NULL - , c->family - , context - , "Dictionary Callbacks" - , "callbacks/s" - , "netdata" - , "stats" - , c->priority + 3 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - c->rd_callbacks_inserts = rrddim_add(c->st_callbacks, "inserts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_callbacks_deletes = rrddim_add(c->st_callbacks, "deletes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_callbacks_conflicts = rrddim_add(c->st_callbacks, "conflicts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_callbacks_reacts = rrddim_add(c->st_callbacks, "reacts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - rrdlabels_add(c->st_callbacks->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); - } - - rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_inserts, (collected_number)stats.callbacks.inserts); - rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_conflicts, (collected_number)stats.callbacks.conflicts); - rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_reacts, (collected_number)stats.callbacks.reacts); - rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_deletes, (collected_number)stats.callbacks.deletes); - - rrdset_done(c->st_callbacks); - } - - // ------------------------------------------------------------------------ - - total = 0; - load_dictionary_stats_entry(memory.index); - load_dictionary_stats_entry(memory.values); - load_dictionary_stats_entry(memory.dict); - - if(c->st_memory || total != 0) { - if (unlikely(!c->st_memory)) { - char id[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.memory", c->context_prefix, stats.name); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.memory", c->context_prefix); - - c->st_memory = rrdset_create_localhost( - "netdata" - , id - , NULL - , c->family - , context - , "Dictionary Memory" - , "bytes" - , "netdata" - , "stats" - , c->priority + 4 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - - c->rd_memory_indexed = rrddim_add(c->st_memory, "index", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - c->rd_memory_values = rrddim_add(c->st_memory, "data", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - c->rd_memory_dict = rrddim_add(c->st_memory, "structures", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - rrdlabels_add(c->st_memory->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); - } - - rrddim_set_by_pointer(c->st_memory, c->rd_memory_indexed, (collected_number)stats.memory.index); - rrddim_set_by_pointer(c->st_memory, c->rd_memory_values, (collected_number)stats.memory.values); - rrddim_set_by_pointer(c->st_memory, c->rd_memory_dict, (collected_number)stats.memory.dict); - - rrdset_done(c->st_memory); - } - - // ------------------------------------------------------------------------ - - total = 0; - load_dictionary_stats_entry(spin_locks.use_spins); - load_dictionary_stats_entry(spin_locks.search_spins); - load_dictionary_stats_entry(spin_locks.insert_spins); - load_dictionary_stats_entry(spin_locks.delete_spins); - - if(c->st_spins || total != 0) { - if (unlikely(!c->st_spins)) { - char id[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.spins", c->context_prefix, stats.name); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.spins", c->context_prefix); - - c->st_spins = rrdset_create_localhost( - "netdata" - , id - , NULL - , c->family - , context - , "Dictionary Spins" - , "count" - , "netdata" - , "stats" - , c->priority + 5 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - c->rd_spins_use = rrddim_add(c->st_spins, "use", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_spins_search = rrddim_add(c->st_spins, "search", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_spins_insert = rrddim_add(c->st_spins, "insert", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - c->rd_spins_delete = rrddim_add(c->st_spins, "delete", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - rrdlabels_add(c->st_spins->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); - } - - rrddim_set_by_pointer(c->st_spins, c->rd_spins_use, (collected_number)stats.spin_locks.use_spins); - rrddim_set_by_pointer(c->st_spins, c->rd_spins_search, (collected_number)stats.spin_locks.search_spins); - rrddim_set_by_pointer(c->st_spins, c->rd_spins_insert, (collected_number)stats.spin_locks.insert_spins); - rrddim_set_by_pointer(c->st_spins, c->rd_spins_delete, (collected_number)stats.spin_locks.delete_spins); - - rrdset_done(c->st_spins); - } -} - -static void dictionary_statistics(void) { - for(int i = 0; dictionary_categories[i].stats ;i++) { - update_dictionary_category_charts(&dictionary_categories[i]); - } -} -#endif // DICT_WITH_STATS - -#ifdef NETDATA_TRACE_ALLOCATIONS - -struct memory_trace_data { - RRDSET *st_memory; - RRDSET *st_allocations; - RRDSET *st_avg_alloc; - RRDSET *st_ops; -}; - -static int do_memory_trace_item(void *item, void *data) { - struct memory_trace_data *tmp = data; - struct malloc_trace *p = item; - - // ------------------------------------------------------------------------ - - if(!p->rd_bytes) - p->rd_bytes = rrddim_add(tmp->st_memory, p->function, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - collected_number bytes = (collected_number)__atomic_load_n(&p->bytes, __ATOMIC_RELAXED); - rrddim_set_by_pointer(tmp->st_memory, p->rd_bytes, bytes); - - // ------------------------------------------------------------------------ - - if(!p->rd_allocations) - p->rd_allocations = rrddim_add(tmp->st_allocations, p->function, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - collected_number allocs = (collected_number)__atomic_load_n(&p->allocations, __ATOMIC_RELAXED); - rrddim_set_by_pointer(tmp->st_allocations, p->rd_allocations, allocs); - - // ------------------------------------------------------------------------ - - if(!p->rd_avg_alloc) - p->rd_avg_alloc = rrddim_add(tmp->st_avg_alloc, p->function, NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); - - collected_number avg_alloc = (allocs)?(bytes * 100 / allocs):0; - rrddim_set_by_pointer(tmp->st_avg_alloc, p->rd_avg_alloc, avg_alloc); - - // ------------------------------------------------------------------------ - - if(!p->rd_ops) - p->rd_ops = rrddim_add(tmp->st_ops, p->function, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - - collected_number ops = 0; - ops += (collected_number)__atomic_load_n(&p->malloc_calls, __ATOMIC_RELAXED); - ops += (collected_number)__atomic_load_n(&p->calloc_calls, __ATOMIC_RELAXED); - ops += (collected_number)__atomic_load_n(&p->realloc_calls, __ATOMIC_RELAXED); - ops += (collected_number)__atomic_load_n(&p->strdup_calls, __ATOMIC_RELAXED); - ops += (collected_number)__atomic_load_n(&p->free_calls, __ATOMIC_RELAXED); - rrddim_set_by_pointer(tmp->st_ops, p->rd_ops, ops); - - // ------------------------------------------------------------------------ - - return 1; -} -static void malloc_trace_statistics(void) { - static struct memory_trace_data tmp = { - .st_memory = NULL, - .st_allocations = NULL, - .st_avg_alloc = NULL, - .st_ops = NULL, - }; - - if(!tmp.st_memory) { - tmp.st_memory = rrdset_create_localhost( - "netdata" - , "memory_size" - , NULL - , "memory" - , "netdata.memory.size" - , "Netdata Memory Used by Function" - , "bytes" - , "netdata" - , "stats" - , 900000 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - } - - if(!tmp.st_ops) { - tmp.st_ops = rrdset_create_localhost( - "netdata" - , "memory_operations" - , NULL - , "memory" - , "netdata.memory.operations" - , "Netdata Memory Operations by Function" - , "ops/s" - , "netdata" - , "stats" - , 900001 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - } - - if(!tmp.st_allocations) { - tmp.st_allocations = rrdset_create_localhost( - "netdata" - , "memory_allocations" - , NULL - , "memory" - , "netdata.memory.allocations" - , "Netdata Memory Allocations by Function" - , "allocations" - , "netdata" - , "stats" - , 900002 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - } - - if(!tmp.st_avg_alloc) { - tmp.st_avg_alloc = rrdset_create_localhost( - "netdata" - , "memory_avg_alloc" - , NULL - , "memory" - , "netdata.memory.avg_alloc" - , "Netdata Average Allocation Size by Function" - , "bytes" - , "netdata" - , "stats" - , 900003 - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - } - - malloc_trace_walkthrough(do_memory_trace_item, &tmp); - - rrdset_done(tmp.st_memory); - rrdset_done(tmp.st_ops); - rrdset_done(tmp.st_allocations); - rrdset_done(tmp.st_avg_alloc); -} -#endif - -// --------------------------------------------------------------------------------------------------------------------- -// worker utilization - -#define WORKERS_MIN_PERCENT_DEFAULT 10000.0 - -struct worker_job_type_gs { - STRING *name; - STRING *units; - - size_t jobs_started; - usec_t busy_time; - - RRDDIM *rd_jobs_started; - RRDDIM *rd_busy_time; - - WORKER_METRIC_TYPE type; - NETDATA_DOUBLE min_value; - NETDATA_DOUBLE max_value; - NETDATA_DOUBLE sum_value; - size_t count_value; - - RRDSET *st; - RRDDIM *rd_min; - RRDDIM *rd_max; - RRDDIM *rd_avg; -}; - -struct worker_thread { - pid_t pid; - bool enabled; - - bool cpu_enabled; - double cpu; - - kernel_uint_t utime; - kernel_uint_t stime; - - kernel_uint_t utime_old; - kernel_uint_t stime_old; - - usec_t collected_time; - usec_t collected_time_old; - - size_t jobs_started; - usec_t busy_time; - - struct worker_thread *next; - struct worker_thread *prev; -}; - -struct worker_utilization { - const char *name; - const char *family; - size_t priority; - uint32_t flags; - - char *name_lowercase; - - struct worker_job_type_gs per_job_type[WORKER_UTILIZATION_MAX_JOB_TYPES]; - - size_t workers_max_job_id; - size_t workers_registered; - size_t workers_busy; - usec_t workers_total_busy_time; - usec_t workers_total_duration; - size_t workers_total_jobs_started; - double workers_min_busy_time; - double workers_max_busy_time; - - size_t workers_cpu_registered; - double workers_cpu_min; - double workers_cpu_max; - double workers_cpu_total; - - struct worker_thread *threads; - - RRDSET *st_workers_time; - RRDDIM *rd_workers_time_avg; - RRDDIM *rd_workers_time_min; - RRDDIM *rd_workers_time_max; - - RRDSET *st_workers_cpu; - RRDDIM *rd_workers_cpu_avg; - RRDDIM *rd_workers_cpu_min; - RRDDIM *rd_workers_cpu_max; - - RRDSET *st_workers_threads; - RRDDIM *rd_workers_threads_free; - RRDDIM *rd_workers_threads_busy; - - RRDSET *st_workers_jobs_per_job_type; - RRDSET *st_workers_busy_per_job_type; - - RRDDIM *rd_total_cpu_utilizaton; -}; - -static struct worker_utilization all_workers_utilization[] = { - { .name = "STATS", .family = "workers global statistics", .priority = 1000000 }, - { .name = "HEALTH", .family = "workers health alarms", .priority = 1000000 }, - { .name = "MLTRAIN", .family = "workers ML training", .priority = 1000000 }, - { .name = "MLDETECT", .family = "workers ML detection", .priority = 1000000 }, - { .name = "STREAMRCV", .family = "workers streaming receive", .priority = 1000000 }, - { .name = "STREAMSND", .family = "workers streaming send", .priority = 1000000 }, - { .name = "DBENGINE", .family = "workers dbengine instances", .priority = 1000000 }, - { .name = "LIBUV", .family = "workers libuv threadpool", .priority = 1000000 }, - { .name = "WEB", .family = "workers web server", .priority = 1000000 }, - { .name = "ACLKSYNC", .family = "workers aclk sync", .priority = 1000000 }, - { .name = "METASYNC", .family = "workers metadata sync", .priority = 1000000 }, - { .name = "PLUGINSD", .family = "workers plugins.d", .priority = 1000000 }, - { .name = "STATSD", .family = "workers plugin statsd", .priority = 1000000 }, - { .name = "STATSDFLUSH", .family = "workers plugin statsd flush", .priority = 1000000 }, - { .name = "PROC", .family = "workers plugin proc", .priority = 1000000 }, - { .name = "WIN", .family = "workers plugin windows", .priority = 1000000 }, - { .name = "NETDEV", .family = "workers plugin proc netdev", .priority = 1000000 }, - { .name = "FREEBSD", .family = "workers plugin freebsd", .priority = 1000000 }, - { .name = "MACOS", .family = "workers plugin macos", .priority = 1000000 }, - { .name = "CGROUPS", .family = "workers plugin cgroups", .priority = 1000000 }, - { .name = "CGROUPSDISC", .family = "workers plugin cgroups find", .priority = 1000000 }, - { .name = "DISKSPACE", .family = "workers plugin diskspace", .priority = 1000000 }, - { .name = "TC", .family = "workers plugin tc", .priority = 1000000 }, - { .name = "TIMEX", .family = "workers plugin timex", .priority = 1000000 }, - { .name = "IDLEJITTER", .family = "workers plugin idlejitter", .priority = 1000000 }, - { .name = "LOGSMANAGPLG",.family = "workers plugin logs management", .priority = 1000000 }, - { .name = "RRDCONTEXT", .family = "workers contexts", .priority = 1000000 }, - { .name = "REPLICATION", .family = "workers replication sender", .priority = 1000000 }, - { .name = "SERVICE", .family = "workers service", .priority = 1000000 }, - { .name = "PROFILER", .family = "workers profile", .priority = 1000000 }, - - // has to be terminated with a NULL - { .name = NULL, .family = NULL } -}; - -static void workers_total_cpu_utilization_chart(void) { - size_t i, cpu_enabled = 0; - for(i = 0; all_workers_utilization[i].name ;i++) - if(all_workers_utilization[i].workers_cpu_registered) cpu_enabled++; - - if(!cpu_enabled) return; - - static RRDSET *st = NULL; - - if(!st) { - st = rrdset_create_localhost( - "netdata", - "workers_cpu", - NULL, - "workers", - "netdata.workers.cpu_total", - "Netdata Workers CPU Utilization (100% = 1 core)", - "%", - "netdata", - "stats", - 999000, - localhost->rrd_update_every, - RRDSET_TYPE_STACKED); - } - - for(i = 0; all_workers_utilization[i].name ;i++) { - struct worker_utilization *wu = &all_workers_utilization[i]; - if(!wu->workers_cpu_registered) continue; - - if(!wu->rd_total_cpu_utilizaton) - wu->rd_total_cpu_utilizaton = rrddim_add(st, wu->name_lowercase, NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); - - rrddim_set_by_pointer(st, wu->rd_total_cpu_utilizaton, (collected_number)((double)wu->workers_cpu_total * 100.0)); - } - - rrdset_done(st); -} - -#define WORKER_CHART_DECIMAL_PRECISION 100 - -static void workers_utilization_update_chart(struct worker_utilization *wu) { - if(!wu->workers_registered) return; - - //fprintf(stderr, "%-12s WORKER UTILIZATION: %-3.2f%%, %zu jobs done, %zu running, on %zu workers, min %-3.02f%%, max %-3.02f%%.\n", - // wu->name, - // (double)wu->workers_total_busy_time * 100.0 / (double)wu->workers_total_duration, - // wu->workers_total_jobs_started, wu->workers_busy, wu->workers_registered, - // wu->workers_min_busy_time, wu->workers_max_busy_time); - - // ---------------------------------------------------------------------- - - if(unlikely(!wu->st_workers_time)) { - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(name, RRD_ID_LENGTH_MAX, "workers_time_%s", wu->name_lowercase); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.time", wu->name_lowercase); - - wu->st_workers_time = rrdset_create_localhost( - "netdata" - , name - , NULL - , wu->family - , context - , "Netdata Workers Busy Time (100% = all workers busy)" - , "%" - , "netdata" - , "stats" - , wu->priority - , localhost->rrd_update_every - , RRDSET_TYPE_AREA - ); - } - - // we add the min and max dimensions only when we have multiple workers - - if(unlikely(!wu->rd_workers_time_min && wu->workers_registered > 1)) - wu->rd_workers_time_min = rrddim_add(wu->st_workers_time, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - - if(unlikely(!wu->rd_workers_time_max && wu->workers_registered > 1)) - wu->rd_workers_time_max = rrddim_add(wu->st_workers_time, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - - if(unlikely(!wu->rd_workers_time_avg)) - wu->rd_workers_time_avg = rrddim_add(wu->st_workers_time, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - - if(unlikely(wu->workers_min_busy_time == WORKERS_MIN_PERCENT_DEFAULT)) wu->workers_min_busy_time = 0.0; - - if(wu->rd_workers_time_min) - rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_min, (collected_number)((double)wu->workers_min_busy_time * WORKER_CHART_DECIMAL_PRECISION)); - - if(wu->rd_workers_time_max) - rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_max, (collected_number)((double)wu->workers_max_busy_time * WORKER_CHART_DECIMAL_PRECISION)); - - if(wu->workers_total_duration == 0) - rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_avg, 0); - else - rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_avg, (collected_number)((double)wu->workers_total_busy_time * 100.0 * WORKER_CHART_DECIMAL_PRECISION / (double)wu->workers_total_duration)); - - rrdset_done(wu->st_workers_time); - - // ---------------------------------------------------------------------- - -#ifdef __linux__ - if(wu->workers_cpu_registered || wu->st_workers_cpu) { - if(unlikely(!wu->st_workers_cpu)) { - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(name, RRD_ID_LENGTH_MAX, "workers_cpu_%s", wu->name_lowercase); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.cpu", wu->name_lowercase); - - wu->st_workers_cpu = rrdset_create_localhost( - "netdata" - , name - , NULL - , wu->family - , context - , "Netdata Workers CPU Utilization (100% = all workers busy)" - , "%" - , "netdata" - , "stats" - , wu->priority + 1 - , localhost->rrd_update_every - , RRDSET_TYPE_AREA - ); - } - - if (unlikely(!wu->rd_workers_cpu_min && wu->workers_registered > 1)) - wu->rd_workers_cpu_min = rrddim_add(wu->st_workers_cpu, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - - if (unlikely(!wu->rd_workers_cpu_max && wu->workers_registered > 1)) - wu->rd_workers_cpu_max = rrddim_add(wu->st_workers_cpu, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - - if(unlikely(!wu->rd_workers_cpu_avg)) - wu->rd_workers_cpu_avg = rrddim_add(wu->st_workers_cpu, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - - if(unlikely(wu->workers_cpu_min == WORKERS_MIN_PERCENT_DEFAULT)) wu->workers_cpu_min = 0.0; - - if(wu->rd_workers_cpu_min) - rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_min, (collected_number)(wu->workers_cpu_min * WORKER_CHART_DECIMAL_PRECISION)); - - if(wu->rd_workers_cpu_max) - rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_max, (collected_number)(wu->workers_cpu_max * WORKER_CHART_DECIMAL_PRECISION)); - - if(wu->workers_cpu_registered == 0) - rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_avg, 0); - else - rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_avg, (collected_number)( wu->workers_cpu_total * WORKER_CHART_DECIMAL_PRECISION / (NETDATA_DOUBLE)wu->workers_cpu_registered )); - - rrdset_done(wu->st_workers_cpu); - } -#endif - - // ---------------------------------------------------------------------- - - if(unlikely(!wu->st_workers_jobs_per_job_type)) { - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(name, RRD_ID_LENGTH_MAX, "workers_jobs_by_type_%s", wu->name_lowercase); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.jobs_started_by_type", wu->name_lowercase); - - wu->st_workers_jobs_per_job_type = rrdset_create_localhost( - "netdata" - , name - , NULL - , wu->family - , context - , "Netdata Workers Jobs Started by Type" - , "jobs" - , "netdata" - , "stats" - , wu->priority + 2 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - } - - { - size_t i; - for(i = 0; i <= wu->workers_max_job_id ;i++) { - if(unlikely(wu->per_job_type[i].type != WORKER_METRIC_IDLE_BUSY)) - continue; - - if (wu->per_job_type[i].name) { - - if(unlikely(!wu->per_job_type[i].rd_jobs_started)) - wu->per_job_type[i].rd_jobs_started = rrddim_add(wu->st_workers_jobs_per_job_type, string2str(wu->per_job_type[i].name), NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - - rrddim_set_by_pointer(wu->st_workers_jobs_per_job_type, wu->per_job_type[i].rd_jobs_started, (collected_number)(wu->per_job_type[i].jobs_started)); - } - } - } - - rrdset_done(wu->st_workers_jobs_per_job_type); - - // ---------------------------------------------------------------------- - - if(unlikely(!wu->st_workers_busy_per_job_type)) { - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(name, RRD_ID_LENGTH_MAX, "workers_busy_time_by_type_%s", wu->name_lowercase); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.time_by_type", wu->name_lowercase); - - wu->st_workers_busy_per_job_type = rrdset_create_localhost( - "netdata" - , name - , NULL - , wu->family - , context - , "Netdata Workers Busy Time by Type" - , "ms" - , "netdata" - , "stats" - , wu->priority + 3 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - } - - { - size_t i; - for(i = 0; i <= wu->workers_max_job_id ;i++) { - if(unlikely(wu->per_job_type[i].type != WORKER_METRIC_IDLE_BUSY)) - continue; - - if (wu->per_job_type[i].name) { - - if(unlikely(!wu->per_job_type[i].rd_busy_time)) - wu->per_job_type[i].rd_busy_time = rrddim_add(wu->st_workers_busy_per_job_type, string2str(wu->per_job_type[i].name), NULL, 1, USEC_PER_MS, RRD_ALGORITHM_ABSOLUTE); - - rrddim_set_by_pointer(wu->st_workers_busy_per_job_type, wu->per_job_type[i].rd_busy_time, (collected_number)(wu->per_job_type[i].busy_time)); - } - } - } - - rrdset_done(wu->st_workers_busy_per_job_type); - - // ---------------------------------------------------------------------- - - if(wu->st_workers_threads || wu->workers_registered > 1) { - if(unlikely(!wu->st_workers_threads)) { - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(name, RRD_ID_LENGTH_MAX, "workers_threads_%s", wu->name_lowercase); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.threads", wu->name_lowercase); - - wu->st_workers_threads = rrdset_create_localhost( - "netdata" - , name - , NULL - , wu->family - , context - , "Netdata Workers Threads" - , "threads" - , "netdata" - , "stats" - , wu->priority + 4 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); - - wu->rd_workers_threads_free = rrddim_add(wu->st_workers_threads, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - wu->rd_workers_threads_busy = rrddim_add(wu->st_workers_threads, "busy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(wu->st_workers_threads, wu->rd_workers_threads_free, (collected_number)(wu->workers_registered - wu->workers_busy)); - rrddim_set_by_pointer(wu->st_workers_threads, wu->rd_workers_threads_busy, (collected_number)(wu->workers_busy)); - rrdset_done(wu->st_workers_threads); - } - - // ---------------------------------------------------------------------- - // custom metric types WORKER_METRIC_ABSOLUTE - - { - size_t i; - for (i = 0; i <= wu->workers_max_job_id; i++) { - if(wu->per_job_type[i].type != WORKER_METRIC_ABSOLUTE) - continue; - - if(!wu->per_job_type[i].count_value) - continue; - - if(!wu->per_job_type[i].st) { - size_t job_name_len = string_strlen(wu->per_job_type[i].name); - if(job_name_len > RRD_ID_LENGTH_MAX) job_name_len = RRD_ID_LENGTH_MAX; - - char job_name_sanitized[job_name_len + 1]; - rrdset_strncpyz_name(job_name_sanitized, string2str(wu->per_job_type[i].name), job_name_len); - - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(name, RRD_ID_LENGTH_MAX, "workers_%s_value_%s", wu->name_lowercase, job_name_sanitized); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.value.%s", wu->name_lowercase, job_name_sanitized); - - char title[1000 + 1]; - snprintf(title, 1000, "Netdata Workers %s value of %s", wu->name_lowercase, string2str(wu->per_job_type[i].name)); - - wu->per_job_type[i].st = rrdset_create_localhost( - "netdata" - , name - , NULL - , wu->family - , context - , title - , (wu->per_job_type[i].units)?string2str(wu->per_job_type[i].units):"value" - , "netdata" - , "stats" - , wu->priority + 5 + i - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - wu->per_job_type[i].rd_min = rrddim_add(wu->per_job_type[i].st, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - wu->per_job_type[i].rd_max = rrddim_add(wu->per_job_type[i].st, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - wu->per_job_type[i].rd_avg = rrddim_add(wu->per_job_type[i].st, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_min, (collected_number)(wu->per_job_type[i].min_value * WORKER_CHART_DECIMAL_PRECISION)); - rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_max, (collected_number)(wu->per_job_type[i].max_value * WORKER_CHART_DECIMAL_PRECISION)); - rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_avg, (collected_number)(wu->per_job_type[i].sum_value / wu->per_job_type[i].count_value * WORKER_CHART_DECIMAL_PRECISION)); - - rrdset_done(wu->per_job_type[i].st); - } - } - - // ---------------------------------------------------------------------- - // custom metric types WORKER_METRIC_INCREMENTAL - - { - size_t i; - for (i = 0; i <= wu->workers_max_job_id ; i++) { - if(wu->per_job_type[i].type != WORKER_METRIC_INCREMENT && wu->per_job_type[i].type != WORKER_METRIC_INCREMENTAL_TOTAL) - continue; - - if(!wu->per_job_type[i].count_value) - continue; - - if(!wu->per_job_type[i].st) { - size_t job_name_len = string_strlen(wu->per_job_type[i].name); - if(job_name_len > RRD_ID_LENGTH_MAX) job_name_len = RRD_ID_LENGTH_MAX; - - char job_name_sanitized[job_name_len + 1]; - rrdset_strncpyz_name(job_name_sanitized, string2str(wu->per_job_type[i].name), job_name_len); - - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(name, RRD_ID_LENGTH_MAX, "workers_%s_rate_%s", wu->name_lowercase, job_name_sanitized); - - char context[RRD_ID_LENGTH_MAX + 1]; - snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.rate.%s", wu->name_lowercase, job_name_sanitized); - - char title[1000 + 1]; - snprintf(title, 1000, "Netdata Workers %s rate of %s", wu->name_lowercase, string2str(wu->per_job_type[i].name)); - - wu->per_job_type[i].st = rrdset_create_localhost( - "netdata" - , name - , NULL - , wu->family - , context - , title - , (wu->per_job_type[i].units)?string2str(wu->per_job_type[i].units):"rate" - , "netdata" - , "stats" - , wu->priority + 5 + i - , localhost->rrd_update_every - , RRDSET_TYPE_LINE - ); - - wu->per_job_type[i].rd_min = rrddim_add(wu->per_job_type[i].st, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - wu->per_job_type[i].rd_max = rrddim_add(wu->per_job_type[i].st, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - wu->per_job_type[i].rd_avg = rrddim_add(wu->per_job_type[i].st, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_min, (collected_number)(wu->per_job_type[i].min_value * WORKER_CHART_DECIMAL_PRECISION)); - rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_max, (collected_number)(wu->per_job_type[i].max_value * WORKER_CHART_DECIMAL_PRECISION)); - rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_avg, (collected_number)(wu->per_job_type[i].sum_value / wu->per_job_type[i].count_value * WORKER_CHART_DECIMAL_PRECISION)); - - rrdset_done(wu->per_job_type[i].st); - } - } -} - -static void workers_utilization_reset_statistics(struct worker_utilization *wu) { - wu->workers_registered = 0; - wu->workers_busy = 0; - wu->workers_total_busy_time = 0; - wu->workers_total_duration = 0; - wu->workers_total_jobs_started = 0; - wu->workers_min_busy_time = WORKERS_MIN_PERCENT_DEFAULT; - wu->workers_max_busy_time = 0; - - wu->workers_cpu_registered = 0; - wu->workers_cpu_min = WORKERS_MIN_PERCENT_DEFAULT; - wu->workers_cpu_max = 0; - wu->workers_cpu_total = 0; - - size_t i; - for(i = 0; i < WORKER_UTILIZATION_MAX_JOB_TYPES ;i++) { - if(unlikely(!wu->name_lowercase)) { - wu->name_lowercase = strdupz(wu->name); - char *s = wu->name_lowercase; - for( ; *s ; s++) *s = tolower(*s); - } - - wu->per_job_type[i].jobs_started = 0; - wu->per_job_type[i].busy_time = 0; - - wu->per_job_type[i].min_value = NAN; - wu->per_job_type[i].max_value = NAN; - wu->per_job_type[i].sum_value = NAN; - wu->per_job_type[i].count_value = 0; - } - - struct worker_thread *wt; - for(wt = wu->threads; wt ; wt = wt->next) { - wt->enabled = false; - wt->cpu_enabled = false; - } -} - -#define TASK_STAT_PREFIX "/proc/self/task/" -#define TASK_STAT_SUFFIX "/stat" - -static int read_thread_cpu_time_from_proc_stat(pid_t pid __maybe_unused, kernel_uint_t *utime __maybe_unused, kernel_uint_t *stime __maybe_unused) { -#ifdef __linux__ - static char filename[sizeof(TASK_STAT_PREFIX) + sizeof(TASK_STAT_SUFFIX) + 20] = TASK_STAT_PREFIX; - static size_t start_pos = sizeof(TASK_STAT_PREFIX) - 1; - static procfile *ff = NULL; - - // construct the filename - size_t end_pos = snprintfz(&filename[start_pos], 20, "%d", pid); - strcpy(&filename[start_pos + end_pos], TASK_STAT_SUFFIX); - - // (re)open the procfile to the new filename - bool set_quotes = (ff == NULL) ? true : false; - ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_ERROR_ON_ERROR_LOG); - if(unlikely(!ff)) return -1; - - if(set_quotes) - procfile_set_open_close(ff, "(", ")"); - - // read the entire file and split it to lines and words - ff = procfile_readall(ff); - if(unlikely(!ff)) return -1; - - // parse the numbers we are interested - *utime = str2kernel_uint_t(procfile_lineword(ff, 0, 13)); - *stime = str2kernel_uint_t(procfile_lineword(ff, 0, 14)); - - // leave the file open for the next iteration - - return 0; -#else - // TODO: add here cpu time detection per thread, for FreeBSD and MacOS - *utime = 0; - *stime = 0; - return 1; -#endif -} - -static Pvoid_t workers_by_pid_JudyL_array = NULL; - -static void workers_threads_cleanup(struct worker_utilization *wu) { - struct worker_thread *t = wu->threads; - while(t) { - struct worker_thread *next = t->next; - - if(!t->enabled) { - JudyLDel(&workers_by_pid_JudyL_array, t->pid, PJE0); - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wu->threads, t, prev, next); - freez(t); - } - t = next; - } - } - -static struct worker_thread *worker_thread_find(struct worker_utilization *wu __maybe_unused, pid_t pid) { - struct worker_thread *wt = NULL; - - Pvoid_t *PValue = JudyLGet(workers_by_pid_JudyL_array, pid, PJE0); - if(PValue) - wt = *PValue; - - return wt; -} - -static struct worker_thread *worker_thread_create(struct worker_utilization *wu, pid_t pid) { - struct worker_thread *wt; - - wt = (struct worker_thread *)callocz(1, sizeof(struct worker_thread)); - wt->pid = pid; - - Pvoid_t *PValue = JudyLIns(&workers_by_pid_JudyL_array, pid, PJE0); - *PValue = wt; - - // link it - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wu->threads, wt, prev, next); - - return wt; -} - -static struct worker_thread *worker_thread_find_or_create(struct worker_utilization *wu, pid_t pid) { - struct worker_thread *wt; - wt = worker_thread_find(wu, pid); - if(!wt) wt = worker_thread_create(wu, pid); - - return wt; -} - -static void worker_utilization_charts_callback(void *ptr - , pid_t pid __maybe_unused - , const char *thread_tag __maybe_unused - , size_t max_job_id __maybe_unused - , size_t utilization_usec __maybe_unused - , size_t duration_usec __maybe_unused - , size_t jobs_started __maybe_unused - , size_t is_running __maybe_unused - , STRING **job_types_names __maybe_unused - , STRING **job_types_units __maybe_unused - , WORKER_METRIC_TYPE *job_types_metric_types __maybe_unused - , size_t *job_types_jobs_started __maybe_unused - , usec_t *job_types_busy_time __maybe_unused - , NETDATA_DOUBLE *job_types_custom_metrics __maybe_unused - ) { - struct worker_utilization *wu = (struct worker_utilization *)ptr; - - // find the worker_thread in the list - struct worker_thread *wt = worker_thread_find_or_create(wu, pid); - - if(utilization_usec > duration_usec) - utilization_usec = duration_usec; - - wt->enabled = true; - wt->busy_time = utilization_usec; - wt->jobs_started = jobs_started; - - wt->utime_old = wt->utime; - wt->stime_old = wt->stime; - wt->collected_time_old = wt->collected_time; - - if(max_job_id > wu->workers_max_job_id) - wu->workers_max_job_id = max_job_id; - - wu->workers_total_busy_time += utilization_usec; - wu->workers_total_duration += duration_usec; - wu->workers_total_jobs_started += jobs_started; - wu->workers_busy += is_running; - wu->workers_registered++; - - double util = (double)utilization_usec * 100.0 / (double)duration_usec; - if(util > wu->workers_max_busy_time) - wu->workers_max_busy_time = util; - - if(util < wu->workers_min_busy_time) - wu->workers_min_busy_time = util; - - // accumulate per job type statistics - size_t i; - for(i = 0; i <= max_job_id ;i++) { - if(!wu->per_job_type[i].name && job_types_names[i]) - wu->per_job_type[i].name = string_dup(job_types_names[i]); - - if(!wu->per_job_type[i].units && job_types_units[i]) - wu->per_job_type[i].units = string_dup(job_types_units[i]); - - wu->per_job_type[i].type = job_types_metric_types[i]; - - wu->per_job_type[i].jobs_started += job_types_jobs_started[i]; - wu->per_job_type[i].busy_time += job_types_busy_time[i]; - - NETDATA_DOUBLE value = job_types_custom_metrics[i]; - if(netdata_double_isnumber(value)) { - if(!wu->per_job_type[i].count_value) { - wu->per_job_type[i].count_value = 1; - wu->per_job_type[i].min_value = value; - wu->per_job_type[i].max_value = value; - wu->per_job_type[i].sum_value = value; - } - else { - wu->per_job_type[i].count_value++; - wu->per_job_type[i].sum_value += value; - if(value < wu->per_job_type[i].min_value) wu->per_job_type[i].min_value = value; - if(value > wu->per_job_type[i].max_value) wu->per_job_type[i].max_value = value; - } - } - } - - // find its CPU utilization - if((!read_thread_cpu_time_from_proc_stat(pid, &wt->utime, &wt->stime))) { - wt->collected_time = now_realtime_usec(); - usec_t delta = wt->collected_time - wt->collected_time_old; - - double utime = (double)(wt->utime - wt->utime_old) / (double)system_hz * 100.0 * (double)USEC_PER_SEC / (double)delta; - double stime = (double)(wt->stime - wt->stime_old) / (double)system_hz * 100.0 * (double)USEC_PER_SEC / (double)delta; - double cpu = utime + stime; - wt->cpu = cpu; - wt->cpu_enabled = true; - - wu->workers_cpu_total += cpu; - if(cpu < wu->workers_cpu_min) wu->workers_cpu_min = cpu; - if(cpu > wu->workers_cpu_max) wu->workers_cpu_max = cpu; - } - wu->workers_cpu_registered += (wt->cpu_enabled) ? 1 : 0; -} - -static void worker_utilization_charts(void) { - static size_t iterations = 0; - iterations++; - - for(int i = 0; all_workers_utilization[i].name ;i++) { - workers_utilization_reset_statistics(&all_workers_utilization[i]); - - workers_foreach(all_workers_utilization[i].name, worker_utilization_charts_callback, &all_workers_utilization[i]); - - // skip the first iteration, so that we don't accumulate startup utilization to our charts - if(likely(iterations > 1)) - workers_utilization_update_chart(&all_workers_utilization[i]); - - workers_threads_cleanup(&all_workers_utilization[i]); - } - - workers_total_cpu_utilization_chart(); -} - -static void worker_utilization_finish(void) { - int i, j; - for(i = 0; all_workers_utilization[i].name ;i++) { - struct worker_utilization *wu = &all_workers_utilization[i]; - - if(wu->name_lowercase) { - freez(wu->name_lowercase); - wu->name_lowercase = NULL; - } - - for(j = 0; j < WORKER_UTILIZATION_MAX_JOB_TYPES ;j++) { - string_freez(wu->per_job_type[j].name); - wu->per_job_type[j].name = NULL; - - string_freez(wu->per_job_type[j].units); - wu->per_job_type[j].units = NULL; - } - - // mark all threads as not enabled - struct worker_thread *t; - for(t = wu->threads; t ; t = t->next) - t->enabled = false; - - // let the cleanup job free them - workers_threads_cleanup(wu); - } -} - -// --------------------------------------------------------------------------------------------------------------------- -// global statistics thread - - -static void global_statistics_register_workers(void) { - worker_register("STATS"); - worker_register_job_name(WORKER_JOB_GLOBAL, "global"); - worker_register_job_name(WORKER_JOB_GLOBAL_EXT, "global_ext"); - worker_register_job_name(WORKER_JOB_REGISTRY, "registry"); - worker_register_job_name(WORKER_JOB_DBENGINE, "dbengine"); - worker_register_job_name(WORKER_JOB_STRINGS, "strings"); - worker_register_job_name(WORKER_JOB_DICTIONARIES, "dictionaries"); - worker_register_job_name(WORKER_JOB_MALLOC_TRACE, "malloc_trace"); - worker_register_job_name(WORKER_JOB_WORKERS, "workers"); - worker_register_job_name(WORKER_JOB_SQLITE3, "sqlite3"); -} - -static void global_statistics_cleanup(void *pptr) -{ - struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr); - if(!static_thread) return; - - static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; - - worker_unregister(); - netdata_log_info("cleaning up..."); - - static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; -} - -void *global_statistics_main(void *ptr) -{ - CLEANUP_FUNCTION_REGISTER(global_statistics_cleanup) cleanup_ptr = ptr; - - global_statistics_register_workers(); - - int update_every = - (int)config_get_duration_seconds(CONFIG_SECTION_GLOBAL_STATISTICS, "update every", localhost->rrd_update_every); - if (update_every < localhost->rrd_update_every) { - update_every = localhost->rrd_update_every; - config_set_duration_seconds(CONFIG_SECTION_GLOBAL_STATISTICS, "update every", update_every); - } - - usec_t step = update_every * USEC_PER_SEC; - heartbeat_t hb; - heartbeat_init(&hb, USEC_PER_SEC); - usec_t real_step = USEC_PER_SEC; - - // keep the randomness at zero - // to make sure we are not close to any other thread - hb.randomness = 0; - - while (service_running(SERVICE_COLLECTORS)) { - worker_is_idle(); - heartbeat_next(&hb); - if (real_step < step) { - real_step += USEC_PER_SEC; - continue; - } - real_step = USEC_PER_SEC; - - worker_is_busy(WORKER_JOB_GLOBAL); - global_statistics_charts(); - } - - return NULL; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// global statistics extended thread - -static void global_statistics_extended_cleanup(void *pptr) -{ - struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr); - if (!static_thread) - return; - - static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; - - netdata_log_info("cleaning up..."); - - worker_unregister(); - worker_utilization_finish(); - - static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; -} - -void *global_statistics_extended_main(void *ptr) -{ - CLEANUP_FUNCTION_REGISTER(global_statistics_extended_cleanup) cleanup_ptr = ptr; - - global_statistics_register_workers(); - - int update_every = - (int)config_get_duration_seconds(CONFIG_SECTION_GLOBAL_STATISTICS, "update every", localhost->rrd_update_every); - if (update_every < localhost->rrd_update_every) { - update_every = localhost->rrd_update_every; - config_set_duration_seconds(CONFIG_SECTION_GLOBAL_STATISTICS, "update every", update_every); - } - - usec_t step = update_every * USEC_PER_SEC; - heartbeat_t hb; - heartbeat_init(&hb, USEC_PER_SEC); - usec_t real_step = USEC_PER_SEC; - - while (service_running(SERVICE_COLLECTORS)) { - worker_is_idle(); - heartbeat_next(&hb); - if (real_step < step) { - real_step += USEC_PER_SEC; - continue; - } - real_step = USEC_PER_SEC; - - worker_is_busy(WORKER_JOB_HEARTBEAT); - update_heartbeat_charts(); - - worker_is_busy(WORKER_JOB_GLOBAL_EXT); - global_statistics_extended_charts(); - -#ifdef ENABLE_DBENGINE - if(dbengine_enabled) { - worker_is_busy(WORKER_JOB_DBENGINE); - dbengine2_statistics_charts(); - } -#endif - - worker_is_busy(WORKER_JOB_REGISTRY); - registry_statistics(); - - worker_is_busy(WORKER_JOB_STRINGS); - update_strings_charts(); - -#ifdef DICT_WITH_STATS - worker_is_busy(WORKER_JOB_DICTIONARIES); - dictionary_statistics(); -#endif - -#ifdef NETDATA_TRACE_ALLOCATIONS - worker_is_busy(WORKER_JOB_MALLOC_TRACE); - malloc_trace_statistics(); -#endif - - worker_is_busy(WORKER_JOB_WORKERS); - worker_utilization_charts(); - - worker_is_busy(WORKER_JOB_SQLITE3); - sqlite3_statistics_charts(); - } - - return NULL; -} diff --git a/src/daemon/global_statistics.h b/src/daemon/global_statistics.h deleted file mode 100644 index 44717c6cf4efed..00000000000000 --- a/src/daemon/global_statistics.h +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_GLOBAL_STATISTICS_H -#define NETDATA_GLOBAL_STATISTICS_H 1 - -#include "database/rrd.h" - -extern struct netdata_buffers_statistics { - size_t rrdhost_allocations_size; - size_t rrdhost_senders; - size_t rrdhost_receivers; - size_t query_targets_size; - size_t rrdset_done_rda_size; - size_t buffers_aclk; - size_t buffers_api; - size_t buffers_functions; - size_t buffers_sqlite; - size_t buffers_exporters; - size_t buffers_health; - size_t buffers_streaming; - size_t cbuffers_streaming; - size_t buffers_web; -} netdata_buffers_statistics; - -extern struct dictionary_stats dictionary_stats_category_collectors; -extern struct dictionary_stats dictionary_stats_category_rrdhost; -extern struct dictionary_stats dictionary_stats_category_rrdset_rrddim; -extern struct dictionary_stats dictionary_stats_category_rrdcontext; -extern struct dictionary_stats dictionary_stats_category_rrdlabels; -extern struct dictionary_stats dictionary_stats_category_rrdhealth; -extern struct dictionary_stats dictionary_stats_category_functions; -extern struct dictionary_stats dictionary_stats_category_replication; - -extern size_t rrddim_db_memory_size; - -// ---------------------------------------------------------------------------- -// global statistics - -void global_statistics_ml_query_completed(size_t points_read); -void global_statistics_ml_models_consulted(size_t models_consulted); -void global_statistics_exporters_query_completed(size_t points_read); -void global_statistics_backfill_query_completed(size_t points_read); -void global_statistics_rrdr_query_completed(size_t queries, uint64_t db_points_read, uint64_t result_points_generated, QUERY_SOURCE query_source); -void global_statistics_sqlite3_query_completed(bool success, bool busy, bool locked); -void global_statistics_sqlite3_row_completed(void); -void global_statistics_rrdset_done_chart_collection_completed(size_t *points_read_per_tier_array); - -void global_statistics_gorilla_buffer_add_hot(); - -void global_statistics_tier0_disk_compressed_bytes(uint32_t size); -void global_statistics_tier0_disk_uncompressed_bytes(uint32_t size); - -void global_statistics_web_request_completed(uint64_t dt, - uint64_t bytes_received, - uint64_t bytes_sent, - uint64_t content_size, - uint64_t compressed_content_size); - -uint64_t global_statistics_web_client_connected(void); -void global_statistics_web_client_disconnected(void); - -extern bool global_statistics_enabled; - -#endif /* NETDATA_GLOBAL_STATISTICS_H */ diff --git a/src/daemon/h2o-common.c b/src/daemon/h2o-common.c index aa7a3c581191be..ddfabce8ab8af8 100644 --- a/src/daemon/h2o-common.c +++ b/src/daemon/h2o-common.c @@ -19,42 +19,3 @@ const char *netdata_configured_abbrev_timezone = NULL; int32_t netdata_configured_utc_offset = 0; bool netdata_ready = false; - -long get_netdata_cpus(void) { - static long processors = 0; - - if(processors) - return processors; - - long cores_proc_stat = os_get_system_cpus_cached(false, true); - long cores_cpuset_v1 = (long)os_read_cpuset_cpus("/sys/fs/cgroup/cpuset/cpuset.cpus", cores_proc_stat); - long cores_cpuset_v2 = (long)os_read_cpuset_cpus("/sys/fs/cgroup/cpuset.cpus", cores_proc_stat); - - if(cores_cpuset_v2) - processors = cores_cpuset_v2; - else if(cores_cpuset_v1) - processors = cores_cpuset_v1; - else - processors = cores_proc_stat; - - long cores_user_configured = config_get_number(CONFIG_SECTION_GLOBAL, "cpu cores", processors); - - errno_clear(); - internal_error(true, - "System CPUs: %ld, (" - "system: %ld, cgroups cpuset v1: %ld, cgroups cpuset v2: %ld, netdata.conf: %ld" - ")" - , processors - , cores_proc_stat - , cores_cpuset_v1 - , cores_cpuset_v2 - , cores_user_configured - ); - - processors = cores_user_configured; - - if(processors < 1) - processors = 1; - - return processors; -} diff --git a/src/daemon/libuv_workers.c b/src/daemon/libuv_workers.c index 441002d068d53d..ac31ad5d5665c3 100644 --- a/src/daemon/libuv_workers.c +++ b/src/daemon/libuv_workers.c @@ -44,6 +44,8 @@ void register_libuv_worker_jobs() { // other dbengine events worker_register_job_name(UV_EVENT_DBENGINE_EVICT_MAIN_CACHE, "evict main"); + worker_register_job_name(UV_EVENT_DBENGINE_EVICT_OPEN_CACHE, "evict open"); + worker_register_job_name(UV_EVENT_DBENGINE_EVICT_EXTENT_CACHE, "evict extent"); worker_register_job_name(UV_EVENT_DBENGINE_BUFFERS_CLEANUP, "dbengine buffers cleanup"); worker_register_job_name(UV_EVENT_DBENGINE_QUIESCE, "dbengine quiesce"); worker_register_job_name(UV_EVENT_DBENGINE_SHUTDOWN, "dbengine shutdown"); diff --git a/src/daemon/libuv_workers.h b/src/daemon/libuv_workers.h index c1821c64617cc8..faa6b1771c8fce 100644 --- a/src/daemon/libuv_workers.h +++ b/src/daemon/libuv_workers.h @@ -36,6 +36,8 @@ enum event_loop_job { // other dbengine events UV_EVENT_DBENGINE_EVICT_MAIN_CACHE, + UV_EVENT_DBENGINE_EVICT_OPEN_CACHE, + UV_EVENT_DBENGINE_EVICT_EXTENT_CACHE, UV_EVENT_DBENGINE_BUFFERS_CLEANUP, UV_EVENT_DBENGINE_QUIESCE, UV_EVENT_DBENGINE_SHUTDOWN, diff --git a/src/daemon/main.c b/src/daemon/main.c index 33d3b04192d83c..65a99afabf2167 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -361,6 +361,7 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re service_wait_exit(SERVICE_EXPORTERS | SERVICE_HEALTH | SERVICE_WEB_SERVER | SERVICE_HTTPD, 3 * USEC_PER_SEC); watcher_step_complete(WATCHER_STEP_ID_STOP_EXPORTERS_HEALTH_AND_WEB_SERVERS_THREADS); + stream_threads_cancel(); service_wait_exit(SERVICE_COLLECTORS | SERVICE_STREAMING, 3 * USEC_PER_SEC); watcher_step_complete(WATCHER_STEP_ID_STOP_COLLECTORS_AND_STREAMING_THREADS); @@ -501,117 +502,6 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re exit(ret); } -void web_server_threading_selection(void) { - web_server_mode = web_server_mode_id(config_get(CONFIG_SECTION_WEB, "mode", web_server_mode_name(web_server_mode))); - - int static_threaded = (web_server_mode == WEB_SERVER_MODE_STATIC_THREADED); - - int i; - for (i = 0; static_threads[i].name; i++) { - if (static_threads[i].start_routine == socket_listen_main_static_threaded) - static_threads[i].enabled = static_threaded; - } -} - -int make_dns_decision(const char *section_name, const char *config_name, const char *default_value, SIMPLE_PATTERN *p) -{ - const char *value = config_get(section_name,config_name,default_value); - if(!strcmp("yes",value)) - return 1; - if(!strcmp("no",value)) - return 0; - if(strcmp("heuristic",value) != 0) - netdata_log_error("Invalid configuration option '%s' for '%s'/'%s'. Valid options are 'yes', 'no' and 'heuristic'. Proceeding with 'heuristic'", - value, section_name, config_name); - - return simple_pattern_is_potential_name(p); -} - -void web_server_config_options(void) -{ - web_client_timeout = - (int)config_get_duration_seconds(CONFIG_SECTION_WEB, "disconnect idle clients after", web_client_timeout); - - web_client_first_request_timeout = - (int)config_get_duration_seconds(CONFIG_SECTION_WEB, "timeout for first request", web_client_first_request_timeout); - - web_client_streaming_rate_t = - config_get_duration_seconds(CONFIG_SECTION_WEB, "accept a streaming request every", web_client_streaming_rate_t); - - respect_web_browser_do_not_track_policy = - config_get_boolean(CONFIG_SECTION_WEB, "respect do not track policy", respect_web_browser_do_not_track_policy); - web_x_frame_options = config_get(CONFIG_SECTION_WEB, "x-frame-options response header", ""); - if(!*web_x_frame_options) - web_x_frame_options = NULL; - - web_allow_connections_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow connections from", "localhost *"), - NULL, SIMPLE_PATTERN_EXACT, true); - web_allow_connections_dns = - make_dns_decision(CONFIG_SECTION_WEB, "allow connections by dns", "heuristic", web_allow_connections_from); - web_allow_dashboard_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"), - NULL, SIMPLE_PATTERN_EXACT, true); - web_allow_dashboard_dns = - make_dns_decision(CONFIG_SECTION_WEB, "allow dashboard by dns", "heuristic", web_allow_dashboard_from); - web_allow_badges_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), NULL, SIMPLE_PATTERN_EXACT, - true); - web_allow_badges_dns = - make_dns_decision(CONFIG_SECTION_WEB, "allow badges by dns", "heuristic", web_allow_badges_from); - web_allow_registry_from = - simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT, - true); - web_allow_registry_dns = make_dns_decision(CONFIG_SECTION_REGISTRY, "allow by dns", "heuristic", - web_allow_registry_from); - web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), - NULL, SIMPLE_PATTERN_EXACT, true); - web_allow_streaming_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow streaming by dns", "heuristic", - web_allow_streaming_from); - // Note the default is not heuristic, the wildcards could match DNS but the intent is ip-addresses. - web_allow_netdataconf_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow netdata.conf from", - "localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.*" - " 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.*" - " 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.*" - " 172.31.* UNKNOWN"), NULL, SIMPLE_PATTERN_EXACT, - true); - web_allow_netdataconf_dns = - make_dns_decision(CONFIG_SECTION_WEB, "allow netdata.conf by dns", "no", web_allow_netdataconf_from); - web_allow_mgmt_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow management from", "localhost"), - NULL, SIMPLE_PATTERN_EXACT, true); - web_allow_mgmt_dns = - make_dns_decision(CONFIG_SECTION_WEB, "allow management by dns","heuristic",web_allow_mgmt_from); - - web_enable_gzip = config_get_boolean(CONFIG_SECTION_WEB, "enable gzip compression", web_enable_gzip); - - const char *s = config_get(CONFIG_SECTION_WEB, "gzip compression strategy", "default"); - if(!strcmp(s, "default")) - web_gzip_strategy = Z_DEFAULT_STRATEGY; - else if(!strcmp(s, "filtered")) - web_gzip_strategy = Z_FILTERED; - else if(!strcmp(s, "huffman only")) - web_gzip_strategy = Z_HUFFMAN_ONLY; - else if(!strcmp(s, "rle")) - web_gzip_strategy = Z_RLE; - else if(!strcmp(s, "fixed")) - web_gzip_strategy = Z_FIXED; - else { - netdata_log_error("Invalid compression strategy '%s'. Valid strategies are 'default', 'filtered', 'huffman only', 'rle' and 'fixed'. Proceeding with 'default'.", s); - web_gzip_strategy = Z_DEFAULT_STRATEGY; - } - - web_gzip_level = (int)config_get_number(CONFIG_SECTION_WEB, "gzip compression level", 3); - if(web_gzip_level < 1) { - netdata_log_error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 1 (fastest compression).", web_gzip_level); - web_gzip_level = 1; - } - else if(web_gzip_level > 9) { - netdata_log_error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 9 (best compression).", web_gzip_level); - web_gzip_level = 9; - } -} - static void set_nofile_limit(struct rlimit *rl) { // get the num files allowed if(getrlimit(RLIMIT_NOFILE, rl) != 0) { @@ -813,597 +703,6 @@ int help(int exitcode) { return exitcode; } -static void security_init(){ - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "%s/ssl/key.pem",netdata_configured_user_config_dir); - netdata_ssl_security_key = config_get(CONFIG_SECTION_WEB, "ssl key", filename); - - snprintfz(filename, FILENAME_MAX, "%s/ssl/cert.pem",netdata_configured_user_config_dir); - netdata_ssl_security_cert = config_get(CONFIG_SECTION_WEB, "ssl certificate", filename); - - tls_version = config_get(CONFIG_SECTION_WEB, "tls version", "1.3"); - tls_ciphers = config_get(CONFIG_SECTION_WEB, "tls ciphers", "none"); - - netdata_ssl_initialize_openssl(); -} - -static void log_init(void) { - nd_log_set_facility(config_get(CONFIG_SECTION_LOGS, "facility", "daemon")); - - time_t period = ND_LOG_DEFAULT_THROTTLE_PERIOD; - size_t logs = ND_LOG_DEFAULT_THROTTLE_LOGS; - period = config_get_duration_seconds(CONFIG_SECTION_LOGS, "logs flood protection period", period); - logs = (unsigned long)config_get_number(CONFIG_SECTION_LOGS, "logs to trigger flood protection", (long long int)logs); - nd_log_set_flood_protection(logs, period); - - const char *netdata_log_level = getenv("NETDATA_LOG_LEVEL"); - netdata_log_level = netdata_log_level ? nd_log_id2priority(nd_log_priority2id(netdata_log_level)) : NDLP_INFO_STR; - - nd_log_set_priority_level(config_get(CONFIG_SECTION_LOGS, "level", netdata_log_level)); - - char filename[FILENAME_MAX + 1]; - char* os_default_method = NULL; -#if defined(OS_LINUX) - os_default_method = is_stderr_connected_to_journal() /* || nd_log_journal_socket_available() */ ? "journal" : NULL; -#elif defined(OS_WINDOWS) -#if defined(HAVE_ETW) - os_default_method = "etw"; -#elif defined(HAVE_WEL) - os_default_method = "wel"; -#endif -#endif - -#if defined(OS_WINDOWS) - // on windows, debug log goes to windows events - snprintfz(filename, FILENAME_MAX, "%s", os_default_method); -#else - snprintfz(filename, FILENAME_MAX, "%s/debug.log", netdata_configured_log_dir); -#endif - - nd_log_set_user_settings(NDLS_DEBUG, config_get(CONFIG_SECTION_LOGS, "debug", filename)); - - if(os_default_method) - snprintfz(filename, FILENAME_MAX, "%s", os_default_method); - else - snprintfz(filename, FILENAME_MAX, "%s/daemon.log", netdata_configured_log_dir); - nd_log_set_user_settings(NDLS_DAEMON, config_get(CONFIG_SECTION_LOGS, "daemon", filename)); - - if(os_default_method) - snprintfz(filename, FILENAME_MAX, "%s", os_default_method); - else - snprintfz(filename, FILENAME_MAX, "%s/collector.log", netdata_configured_log_dir); - nd_log_set_user_settings(NDLS_COLLECTORS, config_get(CONFIG_SECTION_LOGS, "collector", filename)); - -#if defined(OS_WINDOWS) - // on windows, access log goes to windows events - snprintfz(filename, FILENAME_MAX, "%s", os_default_method); -#else - snprintfz(filename, FILENAME_MAX, "%s/access.log", netdata_configured_log_dir); -#endif - nd_log_set_user_settings(NDLS_ACCESS, config_get(CONFIG_SECTION_LOGS, "access", filename)); - - if(os_default_method) - snprintfz(filename, FILENAME_MAX, "%s", os_default_method); - else - snprintfz(filename, FILENAME_MAX, "%s/health.log", netdata_configured_log_dir); - nd_log_set_user_settings(NDLS_HEALTH, config_get(CONFIG_SECTION_LOGS, "health", filename)); - - aclklog_enabled = config_get_boolean(CONFIG_SECTION_CLOUD, "conversation log", CONFIG_BOOLEAN_NO); - if (aclklog_enabled) { -#if defined(OS_WINDOWS) - // on windows, aclk log goes to windows events - snprintfz(filename, FILENAME_MAX, "%s", os_default_method); -#else - snprintfz(filename, FILENAME_MAX, "%s/aclk.log", netdata_configured_log_dir); -#endif - nd_log_set_user_settings(NDLS_ACLK, config_get(CONFIG_SECTION_CLOUD, "conversation log file", filename)); - } - - aclk_config_get_query_scope(); -} - -static const char *get_varlib_subdir_from_config(const char *prefix, const char *dir) { - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "%s/%s", prefix, dir); - return config_get(CONFIG_SECTION_DIRECTORIES, dir, filename); -} - -static void backwards_compatible_config() { - // move [global] options to the [web] section - - config_move(CONFIG_SECTION_GLOBAL, "http port listen backlog", - CONFIG_SECTION_WEB, "listen backlog"); - - config_move(CONFIG_SECTION_GLOBAL, "bind socket to IP", - CONFIG_SECTION_WEB, "bind to"); - - config_move(CONFIG_SECTION_GLOBAL, "bind to", - CONFIG_SECTION_WEB, "bind to"); - - config_move(CONFIG_SECTION_GLOBAL, "port", - CONFIG_SECTION_WEB, "default port"); - - config_move(CONFIG_SECTION_GLOBAL, "default port", - CONFIG_SECTION_WEB, "default port"); - - config_move(CONFIG_SECTION_GLOBAL, "disconnect idle web clients after seconds", - CONFIG_SECTION_WEB, "disconnect idle clients after seconds"); - - config_move(CONFIG_SECTION_GLOBAL, "respect web browser do not track policy", - CONFIG_SECTION_WEB, "respect do not track policy"); - - config_move(CONFIG_SECTION_GLOBAL, "web x-frame-options header", - CONFIG_SECTION_WEB, "x-frame-options response header"); - - config_move(CONFIG_SECTION_GLOBAL, "enable web responses gzip compression", - CONFIG_SECTION_WEB, "enable gzip compression"); - - config_move(CONFIG_SECTION_GLOBAL, "web compression strategy", - CONFIG_SECTION_WEB, "gzip compression strategy"); - - config_move(CONFIG_SECTION_GLOBAL, "web compression level", - CONFIG_SECTION_WEB, "gzip compression level"); - - config_move(CONFIG_SECTION_GLOBAL, "config directory", - CONFIG_SECTION_DIRECTORIES, "config"); - - config_move(CONFIG_SECTION_GLOBAL, "stock config directory", - CONFIG_SECTION_DIRECTORIES, "stock config"); - - config_move(CONFIG_SECTION_GLOBAL, "log directory", - CONFIG_SECTION_DIRECTORIES, "log"); - - config_move(CONFIG_SECTION_GLOBAL, "web files directory", - CONFIG_SECTION_DIRECTORIES, "web"); - - config_move(CONFIG_SECTION_GLOBAL, "cache directory", - CONFIG_SECTION_DIRECTORIES, "cache"); - - config_move(CONFIG_SECTION_GLOBAL, "lib directory", - CONFIG_SECTION_DIRECTORIES, "lib"); - - config_move(CONFIG_SECTION_GLOBAL, "home directory", - CONFIG_SECTION_DIRECTORIES, "home"); - - config_move(CONFIG_SECTION_GLOBAL, "lock directory", - CONFIG_SECTION_DIRECTORIES, "lock"); - - config_move(CONFIG_SECTION_GLOBAL, "plugins directory", - CONFIG_SECTION_DIRECTORIES, "plugins"); - - config_move(CONFIG_SECTION_HEALTH, "health configuration directory", - CONFIG_SECTION_DIRECTORIES, "health config"); - - config_move(CONFIG_SECTION_HEALTH, "stock health configuration directory", - CONFIG_SECTION_DIRECTORIES, "stock health config"); - - config_move(CONFIG_SECTION_REGISTRY, "registry db directory", - CONFIG_SECTION_DIRECTORIES, "registry"); - - config_move(CONFIG_SECTION_GLOBAL, "debug log", - CONFIG_SECTION_LOGS, "debug"); - - config_move(CONFIG_SECTION_GLOBAL, "error log", - CONFIG_SECTION_LOGS, "error"); - - config_move(CONFIG_SECTION_GLOBAL, "access log", - CONFIG_SECTION_LOGS, "access"); - - config_move(CONFIG_SECTION_GLOBAL, "facility log", - CONFIG_SECTION_LOGS, "facility"); - - config_move(CONFIG_SECTION_GLOBAL, "errors flood protection period", - CONFIG_SECTION_LOGS, "errors flood protection period"); - - config_move(CONFIG_SECTION_GLOBAL, "errors to trigger flood protection", - CONFIG_SECTION_LOGS, "errors to trigger flood protection"); - - config_move(CONFIG_SECTION_GLOBAL, "debug flags", - CONFIG_SECTION_LOGS, "debug flags"); - - config_move(CONFIG_SECTION_GLOBAL, "TZ environment variable", - CONFIG_SECTION_ENV_VARS, "TZ"); - - config_move(CONFIG_SECTION_PLUGINS, "PATH environment variable", - CONFIG_SECTION_ENV_VARS, "PATH"); - - config_move(CONFIG_SECTION_PLUGINS, "PYTHONPATH environment variable", - CONFIG_SECTION_ENV_VARS, "PYTHONPATH"); - - config_move(CONFIG_SECTION_STATSD, "enabled", - CONFIG_SECTION_PLUGINS, "statsd"); - - config_move(CONFIG_SECTION_GLOBAL, "memory mode", - CONFIG_SECTION_DB, "db"); - - config_move(CONFIG_SECTION_DB, "mode", - CONFIG_SECTION_DB, "db"); - - config_move(CONFIG_SECTION_GLOBAL, "history", - CONFIG_SECTION_DB, "retention"); - - config_move(CONFIG_SECTION_GLOBAL, "update every", - CONFIG_SECTION_DB, "update every"); - - config_move(CONFIG_SECTION_GLOBAL, "page cache size", - CONFIG_SECTION_DB, "dbengine page cache size"); - - config_move(CONFIG_SECTION_DB, "dbengine page cache size MB", - CONFIG_SECTION_DB, "dbengine page cache size"); - - config_move(CONFIG_SECTION_DB, "dbengine extent cache size MB", - CONFIG_SECTION_DB, "dbengine extent cache size"); - - config_move(CONFIG_SECTION_DB, "page cache size", - CONFIG_SECTION_DB, "dbengine page cache size MB"); - - config_move(CONFIG_SECTION_GLOBAL, "page cache uses malloc", - CONFIG_SECTION_DB, "dbengine page cache with malloc"); - - config_move(CONFIG_SECTION_DB, "page cache with malloc", - CONFIG_SECTION_DB, "dbengine page cache with malloc"); - - config_move(CONFIG_SECTION_GLOBAL, "memory deduplication (ksm)", - CONFIG_SECTION_DB, "memory deduplication (ksm)"); - - config_move(CONFIG_SECTION_GLOBAL, "dbengine page fetch timeout", - CONFIG_SECTION_DB, "dbengine page fetch timeout secs"); - - config_move(CONFIG_SECTION_GLOBAL, "dbengine page fetch retries", - CONFIG_SECTION_DB, "dbengine page fetch retries"); - - config_move(CONFIG_SECTION_GLOBAL, "dbengine extent pages", - CONFIG_SECTION_DB, "dbengine pages per extent"); - - config_move(CONFIG_SECTION_GLOBAL, "cleanup obsolete charts after seconds", - CONFIG_SECTION_DB, "cleanup obsolete charts after"); - - config_move(CONFIG_SECTION_DB, "cleanup obsolete charts after secs", - CONFIG_SECTION_DB, "cleanup obsolete charts after"); - - config_move(CONFIG_SECTION_GLOBAL, "gap when lost iterations above", - CONFIG_SECTION_DB, "gap when lost iterations above"); - - config_move(CONFIG_SECTION_GLOBAL, "cleanup orphan hosts after seconds", - CONFIG_SECTION_DB, "cleanup orphan hosts after"); - - config_move(CONFIG_SECTION_DB, "cleanup orphan hosts after secs", - CONFIG_SECTION_DB, "cleanup orphan hosts after"); - - config_move(CONFIG_SECTION_DB, "cleanup ephemeral hosts after secs", - CONFIG_SECTION_DB, "cleanup ephemeral hosts after"); - - config_move(CONFIG_SECTION_DB, "seconds to replicate", - CONFIG_SECTION_DB, "replication period"); - - config_move(CONFIG_SECTION_DB, "seconds per replication step", - CONFIG_SECTION_DB, "replication step"); - - config_move(CONFIG_SECTION_GLOBAL, "enable zero metrics", - CONFIG_SECTION_DB, "enable zero metrics"); - - // ---------------------------------------------------------------------------------------------------------------- - - config_move(CONFIG_SECTION_GLOBAL, "dbengine disk space", - CONFIG_SECTION_DB, "dbengine tier 0 retention size"); - - config_move(CONFIG_SECTION_GLOBAL, "dbengine multihost disk space", - CONFIG_SECTION_DB, "dbengine tier 0 retention size"); - - config_move(CONFIG_SECTION_DB, "dbengine disk space MB", - CONFIG_SECTION_DB, "dbengine tier 0 retention size"); - - for(size_t tier = 0; tier < RRD_STORAGE_TIERS ;tier++) { - char old_config[128], new_config[128]; - - snprintfz(old_config, sizeof(old_config), "dbengine tier %zu retention days", tier); - snprintfz(new_config, sizeof(new_config), "dbengine tier %zu retention time", tier); - config_move(CONFIG_SECTION_DB, old_config, - CONFIG_SECTION_DB, new_config); - - if(tier == 0) - snprintfz(old_config, sizeof(old_config), "dbengine multihost disk space MB"); - else - snprintfz(old_config, sizeof(old_config), "dbengine tier %zu multihost disk space MB", tier); - snprintfz(new_config, sizeof(new_config), "dbengine tier %zu retention size", tier); - config_move(CONFIG_SECTION_DB, old_config, - CONFIG_SECTION_DB, new_config); - - snprintfz(old_config, sizeof(old_config), "dbengine tier %zu disk space MB", tier); - snprintfz(new_config, sizeof(new_config), "dbengine tier %zu retention size", tier); - config_move(CONFIG_SECTION_DB, old_config, - CONFIG_SECTION_DB, new_config); - } - - // ---------------------------------------------------------------------------------------------------------------- - - config_move(CONFIG_SECTION_LOGS, "error", - CONFIG_SECTION_LOGS, "daemon"); - - config_move(CONFIG_SECTION_LOGS, "severity level", - CONFIG_SECTION_LOGS, "level"); - - config_move(CONFIG_SECTION_LOGS, "errors to trigger flood protection", - CONFIG_SECTION_LOGS, "logs to trigger flood protection"); - - config_move(CONFIG_SECTION_LOGS, "errors flood protection period", - CONFIG_SECTION_LOGS, "logs flood protection period"); - - config_move(CONFIG_SECTION_HEALTH, "is ephemeral", - CONFIG_SECTION_GLOBAL, "is ephemeral node"); - - config_move(CONFIG_SECTION_HEALTH, "has unstable connection", - CONFIG_SECTION_GLOBAL, "has unstable connection"); - - config_move(CONFIG_SECTION_HEALTH, "run at least every seconds", - CONFIG_SECTION_HEALTH, "run at least every"); - - config_move(CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for seconds", - CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for"); - - config_move(CONFIG_SECTION_HEALTH, "health log history", - CONFIG_SECTION_HEALTH, "health log retention"); - - config_move(CONFIG_SECTION_REGISTRY, "registry expire idle persons days", - CONFIG_SECTION_REGISTRY, "registry expire idle persons"); - - config_move(CONFIG_SECTION_WEB, "disconnect idle clients after seconds", - CONFIG_SECTION_WEB, "disconnect idle clients after"); - - config_move(CONFIG_SECTION_WEB, "accept a streaming request every seconds", - CONFIG_SECTION_WEB, "accept a streaming request every"); - - config_move(CONFIG_SECTION_STATSD, "set charts as obsolete after secs", - CONFIG_SECTION_STATSD, "set charts as obsolete after"); - - config_move(CONFIG_SECTION_STATSD, "disconnect idle tcp clients after seconds", - CONFIG_SECTION_STATSD, "disconnect idle tcp clients after"); - - config_move("plugin:idlejitter", "loop time in ms", - "plugin:idlejitter", "loop time"); - - config_move("plugin:proc:/sys/class/infiniband", "refresh ports state every seconds", - "plugin:proc:/sys/class/infiniband", "refresh ports state every"); -} - -static int get_hostname(char *buf, size_t buf_size) { - if (netdata_configured_host_prefix && *netdata_configured_host_prefix) { - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "%s/etc/hostname", netdata_configured_host_prefix); - - if (!read_txt_file(filename, buf, buf_size)) { - trim(buf); - return 0; - } - } - - return gethostname(buf, buf_size); -} - -static void get_netdata_configured_variables() -{ -#ifdef ENABLE_DBENGINE - legacy_multihost_db_space = config_exists(CONFIG_SECTION_DB, "dbengine multihost disk space MB"); - if (!legacy_multihost_db_space) - legacy_multihost_db_space = config_exists(CONFIG_SECTION_GLOBAL, "dbengine multihost disk space"); - if (!legacy_multihost_db_space) - legacy_multihost_db_space = config_exists(CONFIG_SECTION_GLOBAL, "dbengine disk space"); -#endif - - backwards_compatible_config(); - - // ------------------------------------------------------------------------ - // get the hostname - - netdata_configured_host_prefix = config_get(CONFIG_SECTION_GLOBAL, "host access prefix", ""); - (void) verify_netdata_host_prefix(true); - - char buf[HOSTNAME_MAX + 1]; - if (get_hostname(buf, HOSTNAME_MAX)) - netdata_log_error("Cannot get machine hostname."); - - netdata_configured_hostname = config_get(CONFIG_SECTION_GLOBAL, "hostname", buf); - netdata_log_debug(D_OPTIONS, "hostname set to '%s'", netdata_configured_hostname); - - // ------------------------------------------------------------------------ - // get default database update frequency - - default_rrd_update_every = (int) config_get_duration_seconds(CONFIG_SECTION_DB, "update every", UPDATE_EVERY); - if(default_rrd_update_every < 1 || default_rrd_update_every > 600) { - netdata_log_error("Invalid data collection frequency (update every) %d given. Defaulting to %d.", default_rrd_update_every, UPDATE_EVERY); - default_rrd_update_every = UPDATE_EVERY; - config_set_duration_seconds(CONFIG_SECTION_DB, "update every", default_rrd_update_every); - } - - // ------------------------------------------------------------------------ - // get the database selection - - { - const char *mode = config_get(CONFIG_SECTION_DB, "db", rrd_memory_mode_name(default_rrd_memory_mode)); - default_rrd_memory_mode = rrd_memory_mode_id(mode); - if(strcmp(mode, rrd_memory_mode_name(default_rrd_memory_mode)) != 0) { - netdata_log_error("Invalid memory mode '%s' given. Using '%s'", mode, rrd_memory_mode_name(default_rrd_memory_mode)); - config_set(CONFIG_SECTION_DB, "db", rrd_memory_mode_name(default_rrd_memory_mode)); - } - } - - // ------------------------------------------------------------------------ - // get default database size - - if(default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE && default_rrd_memory_mode != RRD_MEMORY_MODE_NONE) { - default_rrd_history_entries = (int)config_get_number( - CONFIG_SECTION_DB, "retention", - align_entries_to_pagesize(default_rrd_memory_mode, RRD_DEFAULT_HISTORY_ENTRIES)); - - long h = align_entries_to_pagesize(default_rrd_memory_mode, default_rrd_history_entries); - if (h != default_rrd_history_entries) { - config_set_number(CONFIG_SECTION_DB, "retention", h); - default_rrd_history_entries = (int)h; - } - } - - // ------------------------------------------------------------------------ - // get system paths - - netdata_configured_user_config_dir = config_get(CONFIG_SECTION_DIRECTORIES, "config", netdata_configured_user_config_dir); - netdata_configured_stock_config_dir = config_get(CONFIG_SECTION_DIRECTORIES, "stock config", netdata_configured_stock_config_dir); - netdata_configured_log_dir = config_get(CONFIG_SECTION_DIRECTORIES, "log", netdata_configured_log_dir); - netdata_configured_web_dir = config_get(CONFIG_SECTION_DIRECTORIES, "web", netdata_configured_web_dir); - netdata_configured_cache_dir = config_get(CONFIG_SECTION_DIRECTORIES, "cache", netdata_configured_cache_dir); - netdata_configured_varlib_dir = config_get(CONFIG_SECTION_DIRECTORIES, "lib", netdata_configured_varlib_dir); - - netdata_configured_lock_dir = get_varlib_subdir_from_config(netdata_configured_varlib_dir, "lock"); - netdata_configured_cloud_dir = get_varlib_subdir_from_config(netdata_configured_varlib_dir, "cloud.d"); - - { - pluginsd_initialize_plugin_directories(); - netdata_configured_primary_plugins_dir = plugin_directories[PLUGINSD_STOCK_PLUGINS_DIRECTORY_PATH]; - } - -#ifdef ENABLE_DBENGINE - // ------------------------------------------------------------------------ - // get default Database Engine page type - - const char *page_type = config_get(CONFIG_SECTION_DB, "dbengine page type", "gorilla"); - if (strcmp(page_type, "gorilla") == 0) - tier_page_type[0] = RRDENG_PAGE_TYPE_GORILLA_32BIT; - else if (strcmp(page_type, "raw") == 0) - tier_page_type[0] = RRDENG_PAGE_TYPE_ARRAY_32BIT; - else { - tier_page_type[0] = RRDENG_PAGE_TYPE_ARRAY_32BIT; - netdata_log_error("Invalid dbengine page type ''%s' given. Defaulting to 'raw'.", page_type); - } - - // ------------------------------------------------------------------------ - // get default Database Engine page cache size in MiB - - default_rrdeng_page_cache_mb = (int) config_get_size_mb(CONFIG_SECTION_DB, "dbengine page cache size", default_rrdeng_page_cache_mb); - default_rrdeng_extent_cache_mb = (int) config_get_size_mb(CONFIG_SECTION_DB, "dbengine extent cache size", default_rrdeng_extent_cache_mb); - db_engine_journal_check = config_get_boolean(CONFIG_SECTION_DB, "dbengine enable journal integrity check", CONFIG_BOOLEAN_NO); - - if(default_rrdeng_extent_cache_mb < 0) { - default_rrdeng_extent_cache_mb = 0; - config_set_size_mb(CONFIG_SECTION_DB, "dbengine extent cache size", default_rrdeng_extent_cache_mb); - } - - if(default_rrdeng_page_cache_mb < RRDENG_MIN_PAGE_CACHE_SIZE_MB) { - netdata_log_error("Invalid page cache size %d given. Defaulting to %d.", default_rrdeng_page_cache_mb, RRDENG_MIN_PAGE_CACHE_SIZE_MB); - default_rrdeng_page_cache_mb = RRDENG_MIN_PAGE_CACHE_SIZE_MB; - config_set_size_mb(CONFIG_SECTION_DB, "dbengine page cache size", default_rrdeng_page_cache_mb); - } - - // ------------------------------------------------------------------------ - // get default Database Engine disk space quota in MiB -// -// // if (!config_exists(CONFIG_SECTION_DB, "dbengine disk space MB") && !config_exists(CONFIG_SECTION_DB, "dbengine multihost disk space MB")) -// -// default_rrdeng_disk_quota_mb = (int) config_get_number(CONFIG_SECTION_DB, "dbengine disk space MB", default_rrdeng_disk_quota_mb); -// if(default_rrdeng_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) { -// netdata_log_error("Invalid dbengine disk space %d given. Defaulting to %d.", default_rrdeng_disk_quota_mb, RRDENG_MIN_DISK_SPACE_MB); -// default_rrdeng_disk_quota_mb = RRDENG_MIN_DISK_SPACE_MB; -// config_set_number(CONFIG_SECTION_DB, "dbengine disk space MB", default_rrdeng_disk_quota_mb); -// } -// -// default_multidb_disk_quota_mb = (int) config_get_number(CONFIG_SECTION_DB, "dbengine multihost disk space MB", compute_multidb_diskspace()); -// if(default_multidb_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) { -// netdata_log_error("Invalid multidb disk space %d given. Defaulting to %d.", default_multidb_disk_quota_mb, default_rrdeng_disk_quota_mb); -// default_multidb_disk_quota_mb = default_rrdeng_disk_quota_mb; -// config_set_number(CONFIG_SECTION_DB, "dbengine multihost disk space MB", default_multidb_disk_quota_mb); -// } - -#else - if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { - error_report("RRD_MEMORY_MODE_DBENGINE is not supported in this platform. The agent will use db mode 'save' instead."); - default_rrd_memory_mode = RRD_MEMORY_MODE_RAM; - } -#endif - - // -------------------------------------------------------------------- - // get KSM settings - -#ifdef MADV_MERGEABLE - enable_ksm = config_get_boolean_ondemand(CONFIG_SECTION_DB, "memory deduplication (ksm)", enable_ksm); -#endif - - // -------------------------------------------------------------------- - - rrdhost_free_ephemeral_time_s = - config_get_duration_seconds(CONFIG_SECTION_DB, "cleanup ephemeral hosts after", rrdhost_free_ephemeral_time_s); - - rrdset_free_obsolete_time_s = - config_get_duration_seconds(CONFIG_SECTION_DB, "cleanup obsolete charts after", rrdset_free_obsolete_time_s); - - // Current chart locking and invalidation scheme doesn't prevent Netdata from segmentation faults if a short - // cleanup delay is set. Extensive stress tests showed that 10 seconds is quite a safe delay. Look at - // https://github.com/netdata/netdata/pull/11222#issuecomment-868367920 for more information. - if (rrdset_free_obsolete_time_s < 10) { - rrdset_free_obsolete_time_s = 10; - netdata_log_info("The \"cleanup obsolete charts after\" option was set to 10 seconds."); - config_set_duration_seconds(CONFIG_SECTION_DB, "cleanup obsolete charts after", rrdset_free_obsolete_time_s); - } - - gap_when_lost_iterations_above = (int)config_get_number(CONFIG_SECTION_DB, "gap when lost iterations above", gap_when_lost_iterations_above); - if (gap_when_lost_iterations_above < 1) { - gap_when_lost_iterations_above = 1; - config_set_number(CONFIG_SECTION_DB, "gap when lost iterations above", gap_when_lost_iterations_above); - } - gap_when_lost_iterations_above += 2; - - // -------------------------------------------------------------------- - // get various system parameters - - os_get_system_cpus_uncached(); - os_get_system_pid_max(); - - -} - -static void post_conf_load(const char **user) -{ - // -------------------------------------------------------------------- - // get the user we should run - - // IMPORTANT: this is required before web_files_uid() - if(getuid() == 0) { - *user = config_get(CONFIG_SECTION_GLOBAL, "run as user", NETDATA_USER); - } - else { - struct passwd *passwd = getpwuid(getuid()); - *user = config_get(CONFIG_SECTION_GLOBAL, "run as user", (passwd && passwd->pw_name)?passwd->pw_name:""); - } -} - -static bool load_netdata_conf(char *filename, char overwrite_used, const char **user) { - errno_clear(); - - int ret = 0; - - if(filename && *filename) { - ret = config_load(filename, overwrite_used, NULL); - if(!ret) - netdata_log_error("CONFIG: cannot load config file '%s'.", filename); - } - else { - filename = filename_from_path_entry_strdupz(netdata_configured_user_config_dir, "netdata.conf"); - - ret = config_load(filename, overwrite_used, NULL); - if(!ret) { - netdata_log_info("CONFIG: cannot load user config '%s'. Will try the stock version.", filename); - freez(filename); - - filename = filename_from_path_entry_strdupz(netdata_configured_stock_config_dir, "netdata.conf"); - ret = config_load(filename, overwrite_used, NULL); - if(!ret) - netdata_log_info("CONFIG: cannot load stock config '%s'. Running with internal defaults.", filename); - } - - freez(filename); - } - - post_conf_load(user); - return ret; -} - // coverity[ +tainted_string_sanitize_content : arg-0 ] static inline void coverity_remove_taint(char *s) { @@ -1476,7 +775,7 @@ int julytest(void); int pluginsd_parser_unittest(void); void replication_initialize(void); void bearer_tokens_init(void); -int unittest_rrdpush_compressions(void); +int unittest_stream_compressions(void); int uuid_unittest(void); int progress_unittest(void); int dyncfg_unittest(void); @@ -1487,8 +786,8 @@ int windows_perflib_dump(const char *key); #endif int unittest_prepare_rrd(const char **user) { - post_conf_load(user); - get_netdata_configured_variables(); + netdata_conf_section_global_run_as_user(user); + netdata_conf_section_global(); default_rrd_update_every = 1; default_rrd_memory_mode = RRD_MEMORY_MODE_RAM; health_plugin_disable(); @@ -1498,7 +797,7 @@ int unittest_prepare_rrd(const char **user) { fprintf(stderr, "rrd_init failed for unittest\n"); return 1; } - stream_conf_send_enabled = 0; + stream_send.enabled = false; return 0; } @@ -1554,7 +853,7 @@ int netdata_main(int argc, char **argv) { while( (opt = getopt(argc, argv, optstring)) != -1 ) { switch(opt) { case 'c': - if(!load_netdata_conf(optarg, 1, &user)) { + if(!netdata_conf_load(optarg, 1, &user)) { netdata_log_error("Cannot load configuration file %s.", optarg); return 1; } @@ -1725,9 +1024,9 @@ int netdata_main(int argc, char **argv) { unittest_running = true; return pluginsd_parser_unittest(); } - else if(strcmp(optarg, "rrdpush_compressions_test") == 0) { + else if(strcmp(optarg, "stream_compressions_test") == 0) { unittest_running = true; - return unittest_rrdpush_compressions(); + return unittest_stream_compressions(); } else if(strcmp(optarg, "progresstest") == 0) { unittest_running = true; @@ -1742,8 +1041,8 @@ int netdata_main(int argc, char **argv) { else if(strncmp(optarg, createdataset_string, strlen(createdataset_string)) == 0) { optarg += strlen(createdataset_string); unsigned history_seconds = strtoul(optarg, NULL, 0); - post_conf_load(&user); - get_netdata_configured_variables(); + netdata_conf_section_global_run_as_user(&user); + netdata_conf_section_global(); default_rrd_update_every = 1; registry_init(); if(rrd_init("dbengine-dataset", NULL, true)) { @@ -1917,10 +1216,10 @@ int netdata_main(int argc, char **argv) { if(!config_loaded) { fprintf(stderr, "warning: no configuration file has been loaded. Use -c CONFIG_FILE, before -W get. Using default config.\n"); - load_netdata_conf(NULL, 0, &user); + netdata_conf_load(NULL, 0, &user); } - get_netdata_configured_variables(); + netdata_conf_section_global(); const char *section = argv[optind]; const char *key = argv[optind + 1]; @@ -1944,11 +1243,11 @@ int netdata_main(int argc, char **argv) { if(!config_loaded) { fprintf(stderr, "warning: no configuration file has been loaded. Use -c CONFIG_FILE, before -W get. Using default config.\n"); - load_netdata_conf(NULL, 0, &user); + netdata_conf_load(NULL, 0, &user); cloud_conf_load(1); } - get_netdata_configured_variables(); + netdata_conf_section_global(); const char *conf_file = argv[optind]; /* "cloud" is cloud.conf, otherwise netdata.conf */ struct config *tmp_config = strcmp(conf_file, "cloud") ? &netdata_config : &cloud_config; @@ -1994,7 +1293,7 @@ int netdata_main(int argc, char **argv) { } if(!config_loaded) { - load_netdata_conf(NULL, 0, &user); + netdata_conf_load(NULL, 0, &user); cloud_conf_load(0); } @@ -2040,7 +1339,7 @@ int netdata_main(int argc, char **argv) { } // prepare configuration environment variables for the plugins - get_netdata_configured_variables(); + netdata_conf_section_global(); set_environment_for_plugins_and_scripts(); analytics_reset(); @@ -2078,7 +1377,7 @@ int netdata_main(int argc, char **argv) { // -------------------------------------------------------------------- // get log filenames and settings - log_init(); + netdata_conf_section_logs(); nd_log_limits_unlimited(); // initialize the log files @@ -2096,7 +1395,7 @@ int netdata_main(int argc, char **argv) { // -------------------------------------------------------------------- // get the certificate and start security - security_init(); + netdata_conf_web_security_init(); // -------------------------------------------------------------------- // This is the safest place to start the SILENCERS structure @@ -2125,11 +1424,14 @@ int netdata_main(int argc, char **argv) { default_stacksize = 1 * 1024 * 1024; #ifdef NETDATA_INTERNAL_CHECKS - config_set_boolean(CONFIG_SECTION_PLUGINS, "netdata monitoring", true); - config_set_boolean(CONFIG_SECTION_PLUGINS, "netdata monitoring extended", true); + telemetry_enabled = true; + telemetry_extended_enabled = true; #endif - if(config_get_boolean(CONFIG_SECTION_PLUGINS, "netdata monitoring extended", false)) + telemetry_extended_enabled = + config_get_boolean(CONFIG_SECTION_TELEMETRY, "extended telemetry", telemetry_extended_enabled); + + if(telemetry_extended_enabled) // this has to run before starting any other threads that use workers workers_utilization_enable(); @@ -2239,7 +1541,7 @@ int netdata_main(int argc, char **argv) { netdata_random_session_id_generate(); // ------------------------------------------------------------------------ - // initialize rrd, registry, health, rrdpush, etc. + // initialize rrd, registry, health, streaming, etc. delta_startup_time("collecting system info"); @@ -2295,11 +1597,12 @@ int netdata_main(int argc, char **argv) { // ------------------------------------------------------------------------ // spawn the threads + get_agent_event_time_median_init(); bearer_tokens_init(); delta_startup_time("start the static threads"); - web_server_config_options(); + netdata_conf_section_web(); set_late_analytics_variables(system_info); for (i = 0; static_threads[i].name != NULL ; i++) { diff --git a/src/daemon/service.c b/src/daemon/service.c index f209cb470126e2..b015213f043bb9 100644 --- a/src/daemon/service.c +++ b/src/daemon/service.c @@ -166,7 +166,7 @@ static void svc_rrdhost_detect_obsolete_charts(RRDHOST *host) { time_t last_entry_t; RRDSET *st; - time_t child_connect_time = host->child_connect_time; + time_t child_connect_time = host->stream.rcv.status.last_connected; rrdset_foreach_read(st, host) { if(rrdset_is_replicating(st)) @@ -203,19 +203,19 @@ static void svc_rrd_cleanup_obsolete_charts_from_all_hosts() { if (host == localhost) continue; - spinlock_lock(&host->receiver_lock); + rrdhost_receiver_lock(host); time_t now = now_realtime_sec(); - if (host->trigger_chart_obsoletion_check && - ((host->child_last_chart_command && - host->child_last_chart_command + host->health.health_delay_up_to < now) || - (host->child_connect_time + TIME_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT < now))) { + if (host->stream.rcv.status.check_obsolete && + ((host->stream.rcv.status.last_chart && + host->stream.rcv.status.last_chart + host->health.delay_up_to < now) || + (host->stream.rcv.status.last_connected + TIME_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT < now))) { svc_rrdhost_detect_obsolete_charts(host); - host->trigger_chart_obsoletion_check = 0; + host->stream.rcv.status.check_obsolete = false; } - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); } rrd_rdunlock(); @@ -235,7 +235,8 @@ static void svc_rrdhost_cleanup_orphan_hosts(RRDHOST *protected_host) { continue; bool force = false; - if (rrdhost_option_check(host, RRDHOST_OPTION_EPHEMERAL_HOST) && now - host->last_connected > rrdhost_free_ephemeral_time_s) + if (rrdhost_option_check(host, RRDHOST_OPTION_EPHEMERAL_HOST) && + now - host->stream.snd.status.last_connected > rrdhost_free_ephemeral_time_s) force = true; bool is_archived = rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED); diff --git a/src/daemon/static_threads.c b/src/daemon/static_threads.c index 3e5b7e3502e867..91a20a85a33fa1 100644 --- a/src/daemon/static_threads.c +++ b/src/daemon/static_threads.c @@ -5,8 +5,6 @@ void *aclk_main(void *ptr); void *analytics_main(void *ptr); void *cpuidlejitter_main(void *ptr); -void *global_statistics_main(void *ptr); -void *global_statistics_extended_main(void *ptr); void *health_main(void *ptr); void *pluginsd_main(void *ptr); void *service_main(void *ptr); @@ -14,7 +12,7 @@ void *statsd_main(void *ptr); void *profile_main(void *ptr); void *replication_thread_main(void *ptr); -extern bool global_statistics_enabled; +extern bool telemetry_enabled; const struct netdata_static_thread static_threads_common[] = { { @@ -45,26 +43,26 @@ const struct netdata_static_thread static_threads_common[] = { .start_routine = analytics_main }, { - .name = "STATS_GLOBAL", + .name = "TELEMETRY", .config_section = CONFIG_SECTION_PLUGINS, - .config_name = "netdata monitoring", + .config_name = "netdata telemetry", .env_name = "NETDATA_INTERNALS_MONITORING", - .global_variable = &global_statistics_enabled, + .global_variable = &telemetry_enabled, .enabled = 1, .thread = NULL, .init_routine = NULL, - .start_routine = global_statistics_main + .start_routine = telemetry_thread_main }, { - .name = "STATS_GLOBAL_EXT", - .config_section = CONFIG_SECTION_PLUGINS, - .config_name = "netdata monitoring extended", - .env_name = "NETDATA_INTERNALS_EXTENDED_MONITORING", - .global_variable = &global_statistics_enabled, - .enabled = 0, // this is ignored - check main() for "netdata monitoring extended" + .name = "TLMTRY-SQLITE3", + .config_section = CONFIG_SECTION_TELEMETRY, + .config_name = "extended telemetry", + .env_name = NULL, + .global_variable = &telemetry_extended_enabled, + .enabled = 0, // the default value - it uses netdata.conf for users to enable it .thread = NULL, .init_routine = NULL, - .start_routine = global_statistics_extended_main + .start_routine = telemetry_thread_sqlite3_main }, { .name = "PLUGINSD", @@ -109,8 +107,7 @@ const struct netdata_static_thread static_threads_common[] = { .enabled = 0, .thread = NULL, .init_routine = NULL, - .start_routine = rrdpush_sender_thread - }, + .start_routine = stream_sender_start_localhost}, { .name = "WEB[1]", .config_section = NULL, diff --git a/src/daemon/telemetry/telemetry-aral.c b/src/daemon/telemetry/telemetry-aral.c new file mode 100644 index 00000000000000..b5b90d0bf99775 --- /dev/null +++ b/src/daemon/telemetry/telemetry-aral.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-aral.h" + +struct aral_info { + const char *name; + RRDSET *st_memory; + RRDDIM *rd_used, *rd_free, *rd_structures; + + RRDSET *st_utilization; + RRDDIM *rd_utilization; +}; + +DEFINE_JUDYL_TYPED(ARAL_STATS, struct aral_info *); + +static struct { + SPINLOCK spinlock; + ARAL_STATS_JudyLSet idx; +} globals = { 0 }; + +static void telemetry_aral_register_statistics(struct aral_statistics *stats, const char *name) { + if(!name || !stats) + return; + + spinlock_lock(&globals.spinlock); + struct aral_info *ai = ARAL_STATS_GET(&globals.idx, (Word_t)stats); + if(!ai) { + ai = callocz(1, sizeof(struct aral_info)); + ai->name = strdupz(name); + ARAL_STATS_SET(&globals.idx, (Word_t)stats, ai); + } + spinlock_unlock(&globals.spinlock); +} + +void telemetry_aral_register(ARAL *ar, const char *name) { + if(!ar) return; + + if(!name) + name = aral_name(ar); + + struct aral_statistics *stats = aral_get_statistics(ar); + + telemetry_aral_register_statistics(stats, name); +} + +void telemetry_aral_unregister(ARAL *ar) { + if(!ar) return; + struct aral_statistics *stats = aral_get_statistics(ar); + + spinlock_lock(&globals.spinlock); + struct aral_info *ai = ARAL_STATS_GET(&globals.idx, (Word_t)stats); + if(ai) { + ARAL_STATS_DEL(&globals.idx, (Word_t)stats); + freez((void *)ai->name); + freez(ai); + } + spinlock_unlock(&globals.spinlock); +} + +void telemerty_aral_init(void) { + telemetry_aral_register_statistics(aral_by_size_statistics(), "by-size"); +} + +void telemetry_aral_do(bool extended) { + if(!extended) return; + + spinlock_lock(&globals.spinlock); + Word_t s = 0; + for(struct aral_info *ai = ARAL_STATS_FIRST(&globals.idx, &s); + ai; + ai = ARAL_STATS_NEXT(&globals.idx, &s)) { + struct aral_statistics *stats = (void *)(uintptr_t)s; + if (!stats) + continue; + + size_t allocated_bytes = __atomic_load_n(&stats->malloc.allocated_bytes, __ATOMIC_RELAXED) + + __atomic_load_n(&stats->mmap.allocated_bytes, __ATOMIC_RELAXED); + + size_t used_bytes = __atomic_load_n(&stats->malloc.used_bytes, __ATOMIC_RELAXED) + + __atomic_load_n(&stats->mmap.used_bytes, __ATOMIC_RELAXED); + + // slight difference may exist, due to the time needed to get these values + // fix the obvious discrepancies + if(used_bytes > allocated_bytes) + used_bytes = allocated_bytes; + + size_t structures_bytes = __atomic_load_n(&stats->structures.allocated_bytes, __ATOMIC_RELAXED); + + size_t free_bytes = allocated_bytes - used_bytes; + + NETDATA_DOUBLE utilization; + if(used_bytes && allocated_bytes) + utilization = 100.0 * (NETDATA_DOUBLE)used_bytes / (NETDATA_DOUBLE)allocated_bytes; + else + utilization = 100.0; + + { + if (unlikely(!ai->st_memory)) { + char id[256]; + + snprintfz(id, sizeof(id), "aral_%s_memory", ai->name); + netdata_fix_chart_id(id); + + ai->st_memory = rrdset_create_localhost( + "netdata", + id, + NULL, + "ARAL", + "netdata.aral_memory", + "Array Allocator Memory Utilization", + "bytes", + "netdata", + "telemetry", + 910000, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rrdlabels_add(ai->st_memory->rrdlabels, "ARAL", ai->name, RRDLABEL_SRC_AUTO); + + ai->rd_free = rrddim_add(ai->st_memory, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ai->rd_used = rrddim_add(ai->st_memory, "used", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ai->rd_structures = rrddim_add(ai->st_memory, "structures", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(ai->st_memory, ai->rd_used, (collected_number)allocated_bytes); + rrddim_set_by_pointer(ai->st_memory, ai->rd_free, (collected_number)free_bytes); + rrddim_set_by_pointer(ai->st_memory, ai->rd_structures, (collected_number)structures_bytes); + rrdset_done(ai->st_memory); + } + + { + if (unlikely(!ai->st_utilization)) { + char id[256]; + + snprintfz(id, sizeof(id), "aral_%s_utilization", ai->name); + netdata_fix_chart_id(id); + + ai->st_utilization = rrdset_create_localhost( + "netdata", + id, + NULL, + "ARAL", + "netdata.aral_utilization", + "Array Allocator Memory Utilization", + "%", + "netdata", + "telemetry", + 910001, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rrdlabels_add(ai->st_utilization->rrdlabels, "ARAL", ai->name, RRDLABEL_SRC_AUTO); + + ai->rd_utilization = rrddim_add(ai->st_utilization, "utilization", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(ai->st_utilization, ai->rd_utilization, (collected_number)(utilization * 10000.0)); + rrdset_done(ai->st_utilization); + } + } + + spinlock_unlock(&globals.spinlock); +} diff --git a/src/daemon/telemetry/telemetry-aral.h b/src/daemon/telemetry/telemetry-aral.h new file mode 100644 index 00000000000000..26c08ffda4de0b --- /dev/null +++ b/src/daemon/telemetry/telemetry-aral.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_ARAL_H +#define NETDATA_TELEMETRY_ARAL_H + +#include "daemon/common.h" + +void telemetry_aral_register(ARAL *ar, const char *name); +void telemetry_aral_unregister(ARAL *ar); + +#if defined(TELEMETRY_INTERNALS) +void telemerty_aral_init(void); +void telemetry_aral_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_ARAL_H diff --git a/src/daemon/telemetry/telemetry-daemon-memory.c b/src/daemon/telemetry/telemetry-daemon-memory.c new file mode 100644 index 00000000000000..19cfbc5e1b3d52 --- /dev/null +++ b/src/daemon/telemetry/telemetry-daemon-memory.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-daemon-memory.h" + +#define dictionary_stats_memory_total(stats) \ + ((stats).memory.dict + (stats).memory.values + (stats).memory.index) + +struct netdata_buffers_statistics netdata_buffers_statistics = {}; + +void telemetry_daemon_memory_do(bool extended) { + { + static RRDSET *st_memory = NULL; + static RRDDIM *rd_database = NULL; +#ifdef DICT_WITH_STATS + static RRDDIM *rd_collectors = NULL; + static RRDDIM *rd_rrdhosts = NULL; + static RRDDIM *rd_rrdsets = NULL; + static RRDDIM *rd_rrddims = NULL; + static RRDDIM *rd_contexts = NULL; + static RRDDIM *rd_health = NULL; + static RRDDIM *rd_functions = NULL; + static RRDDIM *rd_replication = NULL; +#else + static RRDDIM *rd_metadata = NULL; +#endif + static RRDDIM *rd_labels = NULL; // labels use dictionary like statistics, but it is not ARAL based dictionary + static RRDDIM *rd_ml = NULL; + static RRDDIM *rd_strings = NULL; + static RRDDIM *rd_streaming = NULL; + static RRDDIM *rd_buffers = NULL; + static RRDDIM *rd_workers = NULL; + static RRDDIM *rd_aral = NULL; + static RRDDIM *rd_judy = NULL; + static RRDDIM *rd_other = NULL; + + if (unlikely(!st_memory)) { + st_memory = rrdset_create_localhost( + "netdata", + "memory", + NULL, + "Memory Usage", + NULL, + "Netdata Memory", + "bytes", + "netdata", + "stats", + 130100, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_database = rrddim_add(st_memory, "db", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + +#ifdef DICT_WITH_STATS + rd_collectors = rrddim_add(st_memory, "collectors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_rrdhosts = rrddim_add(st_memory, "hosts", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_rrdsets = rrddim_add(st_memory, "rrdset", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_rrddims = rrddim_add(st_memory, "rrddim", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_contexts = rrddim_add(st_memory, "contexts", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_health = rrddim_add(st_memory, "health", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_functions = rrddim_add(st_memory, "functions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_replication = rrddim_add(st_memory, "replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); +#else + rd_metadata = rrddim_add(st_memory, "metadata", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); +#endif + rd_labels = rrddim_add(st_memory, "labels", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_ml = rrddim_add(st_memory, "ML", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_strings = rrddim_add(st_memory, "strings", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_streaming = rrddim_add(st_memory, "streaming", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers = rrddim_add(st_memory, "buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_workers = rrddim_add(st_memory, "workers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_aral = rrddim_add(st_memory, "aral", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_judy = rrddim_add(st_memory, "judy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_other = rrddim_add(st_memory, "other", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + size_t buffers = + netdata_buffers_statistics.query_targets_size + + netdata_buffers_statistics.rrdset_done_rda_size + + netdata_buffers_statistics.buffers_aclk + + netdata_buffers_statistics.buffers_api + + netdata_buffers_statistics.buffers_functions + + netdata_buffers_statistics.buffers_sqlite + + netdata_buffers_statistics.buffers_exporters + + netdata_buffers_statistics.buffers_health + + netdata_buffers_statistics.buffers_streaming + + netdata_buffers_statistics.cbuffers_streaming + + netdata_buffers_statistics.buffers_web + + replication_allocated_buffers() + + aral_by_size_overhead() + + judy_aral_overhead(); + + size_t strings = 0; + string_statistics(NULL, NULL, NULL, NULL, NULL, &strings, NULL, NULL); + + rrddim_set_by_pointer(st_memory, rd_database, + (collected_number)telemetry_dbengine_total_memory + (collected_number)rrddim_db_memory_size); + +#ifdef DICT_WITH_STATS + rrddim_set_by_pointer(st_memory, rd_collectors, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_collectors)); + + rrddim_set_by_pointer(st_memory, + rd_rrdhosts, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdhost) + (collected_number)netdata_buffers_statistics.rrdhost_allocations_size); + + rrddim_set_by_pointer(st_memory, rd_rrdsets, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdset)); + + rrddim_set_by_pointer(st_memory, rd_rrddims, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrddim)); + + rrddim_set_by_pointer(st_memory, rd_contexts, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdcontext)); + + rrddim_set_by_pointer(st_memory, rd_health, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdhealth)); + + rrddim_set_by_pointer(st_memory, rd_functions, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_functions)); + + rrddim_set_by_pointer(st_memory, rd_replication, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_replication) + (collected_number)replication_allocated_memory()); +#else + uint64_t metadata = + aral_by_size_used_bytes() + + dictionary_stats_category_rrdhost.memory.dict + + dictionary_stats_category_rrdset.memory.dict + + dictionary_stats_category_rrddim.memory.dict + + dictionary_stats_category_rrdcontext.memory.dict + + dictionary_stats_category_rrdhealth.memory.dict + + dictionary_stats_category_functions.memory.dict + + dictionary_stats_category_replication.memory.dict + + replication_allocated_memory(); + + rrddim_set_by_pointer(st_memory, rd_metadata, (collected_number)metadata); +#endif + + // labels use dictionary like statistics, but it is not ARAL based dictionary + rrddim_set_by_pointer(st_memory, rd_labels, + (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdlabels)); + + rrddim_set_by_pointer(st_memory, rd_ml, + (collected_number)telemetry_ml_get_current_memory_usage()); + + rrddim_set_by_pointer(st_memory, rd_strings, + (collected_number)strings); + + rrddim_set_by_pointer(st_memory, rd_streaming, + (collected_number)netdata_buffers_statistics.rrdhost_senders + (collected_number)netdata_buffers_statistics.rrdhost_receivers); + + rrddim_set_by_pointer(st_memory, rd_buffers, + (collected_number)buffers); + + rrddim_set_by_pointer(st_memory, rd_workers, + (collected_number) workers_allocated_memory()); + + rrddim_set_by_pointer(st_memory, rd_aral, + (collected_number) aral_by_size_structures()); + + rrddim_set_by_pointer(st_memory, + rd_judy, (collected_number) judy_aral_structures()); + + rrddim_set_by_pointer(st_memory, + rd_other, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_other)); + + rrdset_done(st_memory); + } + + { + static RRDSET *st_memory_buffers = NULL; + static RRDDIM *rd_queries = NULL; + static RRDDIM *rd_collectors = NULL; + static RRDDIM *rd_buffers_aclk = NULL; + static RRDDIM *rd_buffers_api = NULL; + static RRDDIM *rd_buffers_functions = NULL; + static RRDDIM *rd_buffers_sqlite = NULL; + static RRDDIM *rd_buffers_exporters = NULL; + static RRDDIM *rd_buffers_health = NULL; + static RRDDIM *rd_buffers_streaming = NULL; + static RRDDIM *rd_cbuffers_streaming = NULL; + static RRDDIM *rd_buffers_replication = NULL; + static RRDDIM *rd_buffers_web = NULL; + static RRDDIM *rd_buffers_aral = NULL; + static RRDDIM *rd_buffers_judy = NULL; + + if (unlikely(!st_memory_buffers)) { + st_memory_buffers = rrdset_create_localhost( + "netdata", + "memory_buffers", + NULL, + "Memory Usage", + NULL, + "Netdata Memory Buffers", + "bytes", + "netdata", + "stats", + 130101, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_queries = rrddim_add(st_memory_buffers, "queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_collectors = rrddim_add(st_memory_buffers, "collection", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_aclk = rrddim_add(st_memory_buffers, "aclk", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_api = rrddim_add(st_memory_buffers, "api", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_functions = rrddim_add(st_memory_buffers, "functions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_sqlite = rrddim_add(st_memory_buffers, "sqlite", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_exporters = rrddim_add(st_memory_buffers, "exporters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_health = rrddim_add(st_memory_buffers, "health", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_streaming = rrddim_add(st_memory_buffers, "streaming", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_cbuffers_streaming = rrddim_add(st_memory_buffers, "streaming cbuf", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_replication = rrddim_add(st_memory_buffers, "replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_web = rrddim_add(st_memory_buffers, "web", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_aral = rrddim_add(st_memory_buffers, "aral", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_buffers_judy = rrddim_add(st_memory_buffers, "judy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(st_memory_buffers, rd_queries, (collected_number)netdata_buffers_statistics.query_targets_size + (collected_number) onewayalloc_allocated_memory()); + rrddim_set_by_pointer(st_memory_buffers, rd_collectors, (collected_number)netdata_buffers_statistics.rrdset_done_rda_size); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_aclk, (collected_number)netdata_buffers_statistics.buffers_aclk); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_api, (collected_number)netdata_buffers_statistics.buffers_api); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_functions, (collected_number)netdata_buffers_statistics.buffers_functions); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_sqlite, (collected_number)netdata_buffers_statistics.buffers_sqlite); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_exporters, (collected_number)netdata_buffers_statistics.buffers_exporters); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_health, (collected_number)netdata_buffers_statistics.buffers_health); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_streaming, (collected_number)netdata_buffers_statistics.buffers_streaming); + rrddim_set_by_pointer(st_memory_buffers, rd_cbuffers_streaming, (collected_number)netdata_buffers_statistics.cbuffers_streaming); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_replication, (collected_number)replication_allocated_buffers()); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_web, (collected_number)netdata_buffers_statistics.buffers_web); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_aral, (collected_number)aral_by_size_overhead()); + rrddim_set_by_pointer(st_memory_buffers, rd_buffers_judy, (collected_number)judy_aral_overhead()); + + rrdset_done(st_memory_buffers); + } + + // ---------------------------------------------------------------------------------------------------------------- + + if(!extended) + return; + + // ---------------------------------------------------------------------------------------------------------------- + +} diff --git a/src/daemon/telemetry/telemetry-daemon-memory.h b/src/daemon/telemetry/telemetry-daemon-memory.h new file mode 100644 index 00000000000000..f33b4d4a88219c --- /dev/null +++ b/src/daemon/telemetry/telemetry-daemon-memory.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_DAEMON_MEMORY_H +#define NETDATA_TELEMETRY_DAEMON_MEMORY_H + +#include "daemon/common.h" + +extern struct netdata_buffers_statistics { + size_t rrdhost_allocations_size; + size_t rrdhost_senders; + size_t rrdhost_receivers; + size_t query_targets_size; + size_t rrdset_done_rda_size; + size_t buffers_aclk; + size_t buffers_api; + size_t buffers_functions; + size_t buffers_sqlite; + size_t buffers_exporters; + size_t buffers_health; + size_t buffers_streaming; + size_t cbuffers_streaming; + size_t buffers_web; +} netdata_buffers_statistics; + +#if defined(TELEMETRY_INTERNALS) +void telemetry_daemon_memory_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_DAEMON_MEMORY_H diff --git a/src/daemon/telemetry/telemetry-daemon.c b/src/daemon/telemetry/telemetry-daemon.c new file mode 100644 index 00000000000000..7478520a222e51 --- /dev/null +++ b/src/daemon/telemetry/telemetry-daemon.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-daemon.h" + +static void telemetry_daemon_cpu_usage_do(bool extended __maybe_unused) { + struct rusage me; + getrusage(RUSAGE_SELF, &me); + + { + static RRDSET *st_cpu = NULL; + static RRDDIM *rd_cpu_user = NULL, + *rd_cpu_system = NULL; + + if (unlikely(!st_cpu)) { + st_cpu = rrdset_create_localhost( + "netdata" + , "server_cpu" + , NULL + , "CPU usage" + , NULL + , "Netdata CPU usage" + , "milliseconds/s" + , "netdata" + , "stats" + , 130000 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + + rd_cpu_user = rrddim_add(st_cpu, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL); + rd_cpu_system = rrddim_add(st_cpu, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_cpu, rd_cpu_user, (collected_number )(me.ru_utime.tv_sec * 1000000ULL + me.ru_utime.tv_usec)); + rrddim_set_by_pointer(st_cpu, rd_cpu_system, (collected_number )(me.ru_stime.tv_sec * 1000000ULL + me.ru_stime.tv_usec)); + rrdset_done(st_cpu); + } +} + +static void telemetry_daemon_uptime_do(bool extended __maybe_unused) { + { + static time_t netdata_boottime_time = 0; + if (!netdata_boottime_time) + netdata_boottime_time = now_boottime_sec(); + + time_t netdata_uptime = now_boottime_sec() - netdata_boottime_time; + + static RRDSET *st_uptime = NULL; + static RRDDIM *rd_uptime = NULL; + + if (unlikely(!st_uptime)) { + st_uptime = rrdset_create_localhost( + "netdata", + "uptime", + NULL, + "Uptime", + NULL, + "Netdata uptime", + "seconds", + "netdata", + "stats", + 130150, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_uptime = rrddim_add(st_uptime, "uptime", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(st_uptime, rd_uptime, netdata_uptime); + rrdset_done(st_uptime); + } +} + +void telemetry_daemon_do(bool extended) { + telemetry_daemon_cpu_usage_do(extended); + telemetry_daemon_uptime_do(extended); + telemetry_daemon_memory_do(extended); +} diff --git a/src/daemon/telemetry/telemetry-daemon.h b/src/daemon/telemetry/telemetry-daemon.h new file mode 100644 index 00000000000000..44023312e39acf --- /dev/null +++ b/src/daemon/telemetry/telemetry-daemon.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_DAEMON_H +#define NETDATA_TELEMETRY_DAEMON_H + +#include "daemon/common.h" + +#if defined(TELEMETRY_INTERNALS) +void telemetry_daemon_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_DAEMON_H diff --git a/src/daemon/telemetry/telemetry-dbengine.c b/src/daemon/telemetry/telemetry-dbengine.c new file mode 100644 index 00000000000000..74e72d2f11e64b --- /dev/null +++ b/src/daemon/telemetry/telemetry-dbengine.c @@ -0,0 +1,1624 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-dbengine.h" + +size_t telemetry_dbengine_total_memory = 0; + +#if defined(ENABLE_DBENGINE) + +struct dbengine2_cache_pointers { + RRDSET *st_cache_hit_ratio; + RRDDIM *rd_hit_ratio_closest; + RRDDIM *rd_hit_ratio_exact; + + RRDSET *st_operations; + RRDDIM *rd_searches_closest; + RRDDIM *rd_searches_exact; + RRDDIM *rd_add_hot; + RRDDIM *rd_add_clean; + RRDDIM *rd_evictions; + RRDDIM *rd_flushes; + RRDDIM *rd_acquires; + RRDDIM *rd_releases; + RRDDIM *rd_acquires_for_deletion; + + RRDSET *st_pgc_memory; + RRDDIM *rd_pgc_memory_free; + RRDDIM *rd_pgc_memory_clean; + RRDDIM *rd_pgc_memory_hot; + RRDDIM *rd_pgc_memory_dirty; + RRDDIM *rd_pgc_memory_index; + RRDDIM *rd_pgc_memory_evicting; + RRDDIM *rd_pgc_memory_flushing; + + struct { + RRDSET *st_pgc_page_size_heatmap; + RRDDIM *rd_pgc_page_size_x[PGC_SIZE_HISTOGRAM_ENTRIES]; + } queues[3]; + + RRDSET *st_pgc_tm; + RRDDIM *rd_pgc_tm_current; + RRDDIM *rd_pgc_tm_wanted; + RRDDIM *rd_pgc_tm_hot_max; + RRDDIM *rd_pgc_tm_dirty_max; + RRDDIM *rd_pgc_tm_hot; + RRDDIM *rd_pgc_tm_dirty; + RRDDIM *rd_pgc_tm_referenced; + + RRDSET *st_pgc_pages; + RRDDIM *rd_pgc_pages_clean; + RRDDIM *rd_pgc_pages_hot; + RRDDIM *rd_pgc_pages_dirty; + RRDDIM *rd_pgc_pages_referenced; + + RRDSET *st_pgc_memory_changes; + RRDDIM *rd_pgc_memory_new_hot; + RRDDIM *rd_pgc_memory_new_clean; + RRDDIM *rd_pgc_memory_clean_evictions; + + RRDSET *st_pgc_memory_migrations; + RRDDIM *rd_pgc_memory_hot_to_dirty; + RRDDIM *rd_pgc_memory_dirty_to_clean; + + RRDSET *st_pgc_workers; + RRDDIM *rd_pgc_workers_evictors; + RRDDIM *rd_pgc_workers_flushers; + RRDDIM *rd_pgc_workers_adders; + RRDDIM *rd_pgc_workers_searchers; + RRDDIM *rd_pgc_workers_jv2_flushers; + RRDDIM *rd_pgc_workers_hot2dirty; + + RRDSET *st_pgc_memory_events; + RRDDIM *rd_pgc_memory_evictions_critical; + RRDDIM *rd_pgc_memory_evictions_aggressive; + RRDDIM *rd_pgc_memory_flushes_critical; + RRDDIM *rd_pgc_waste_evict_thread_signals; + RRDDIM *rd_pgc_waste_evict_inline_on_add; + RRDDIM *rd_pgc_waste_evict_inline_on_release; + RRDDIM *rd_pgc_waste_flush_inline_on_add; + RRDDIM *rd_pgc_waste_flush_inline_on_release; + + RRDSET *st_pgc_waste; + RRDDIM *rd_pgc_waste_evict_relocated; + RRDDIM *rd_pgc_waste_flushes_cancelled; + RRDDIM *rd_pgc_waste_insert_spins; + RRDDIM *rd_pgc_waste_evict_spins; + RRDDIM *rd_pgc_waste_release_spins; + RRDDIM *rd_pgc_waste_acquire_spins; + RRDDIM *rd_pgc_waste_delete_spins; +}; + +static void dbengine2_cache_statistics_charts(struct dbengine2_cache_pointers *ptrs, struct pgc_statistics *pgc_stats, struct pgc_statistics *pgc_stats_old __maybe_unused, const char *name, int priority) { + + { + if (unlikely(!ptrs->st_cache_hit_ratio)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_hit_ratio", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Hit Ratio", name); + + ptrs->st_cache_hit_ratio = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "%", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + ptrs->rd_hit_ratio_closest = rrddim_add(ptrs->st_cache_hit_ratio, "closest", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_hit_ratio_exact = rrddim_add(ptrs->st_cache_hit_ratio, "exact", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + size_t closest_percent = 100 * 10000; + if(pgc_stats->searches_closest > pgc_stats_old->searches_closest) + closest_percent = (pgc_stats->searches_closest_hits - pgc_stats_old->searches_closest_hits) * 100 * 10000 / (pgc_stats->searches_closest - pgc_stats_old->searches_closest); + + size_t exact_percent = 100 * 10000; + if(pgc_stats->searches_exact > pgc_stats_old->searches_exact) + exact_percent = (pgc_stats->searches_exact_hits - pgc_stats_old->searches_exact_hits) * 100 * 10000 / (pgc_stats->searches_exact - pgc_stats_old->searches_exact); + + rrddim_set_by_pointer(ptrs->st_cache_hit_ratio, ptrs->rd_hit_ratio_closest, (collected_number)closest_percent); + rrddim_set_by_pointer(ptrs->st_cache_hit_ratio, ptrs->rd_hit_ratio_exact, (collected_number)exact_percent); + + rrdset_done(ptrs->st_cache_hit_ratio); + } + + { + if (unlikely(!ptrs->st_operations)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_operations", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Operations", name); + + ptrs->st_operations = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "ops/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + ptrs->rd_searches_closest = rrddim_add(ptrs->st_operations, "search closest", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_searches_exact = rrddim_add(ptrs->st_operations, "search exact", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_add_hot = rrddim_add(ptrs->st_operations, "add hot", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_add_clean = rrddim_add(ptrs->st_operations, "add clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_evictions = rrddim_add(ptrs->st_operations, "evictions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_flushes = rrddim_add(ptrs->st_operations, "flushes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_acquires = rrddim_add(ptrs->st_operations, "acquires", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_releases = rrddim_add(ptrs->st_operations, "releases", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_acquires_for_deletion = rrddim_add(ptrs->st_operations, "del acquires", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_searches_closest, (collected_number)pgc_stats->searches_closest); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_searches_exact, (collected_number)pgc_stats->searches_exact); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_add_hot, (collected_number)pgc_stats->queues[PGC_QUEUE_HOT].added_entries); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_add_clean, (collected_number)(pgc_stats->added_entries - pgc_stats->queues[PGC_QUEUE_HOT].added_entries)); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_evictions, (collected_number)pgc_stats->queues[PGC_QUEUE_CLEAN].removed_entries); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_flushes, (collected_number)pgc_stats->flushes_completed); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_acquires, (collected_number)pgc_stats->acquires); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_releases, (collected_number)pgc_stats->releases); + rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_acquires_for_deletion, (collected_number)pgc_stats->acquires_for_deletion); + + rrdset_done(ptrs->st_operations); + } + + { + if (unlikely(!ptrs->st_pgc_memory)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_memory", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Memory", name); + + ptrs->st_pgc_memory = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "bytes", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + ptrs->rd_pgc_memory_free = rrddim_add(ptrs->st_pgc_memory, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_memory_hot = rrddim_add(ptrs->st_pgc_memory, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_memory_dirty = rrddim_add(ptrs->st_pgc_memory, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_memory_clean = rrddim_add(ptrs->st_pgc_memory, "clean", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_memory_index = rrddim_add(ptrs->st_pgc_memory, "index", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_memory_evicting = rrddim_add(ptrs->st_pgc_memory, "evicting", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_memory_flushing = rrddim_add(ptrs->st_pgc_memory, "flushing", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + collected_number free = (pgc_stats->current_cache_size > pgc_stats->wanted_cache_size) ? 0 : + (collected_number)(pgc_stats->wanted_cache_size - pgc_stats->current_cache_size); + + rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_free, free); + rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_hot, (collected_number)pgc_stats->queues[PGC_QUEUE_HOT].size); + rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_dirty, (collected_number)pgc_stats->queues[PGC_QUEUE_DIRTY].size); + rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_clean, (collected_number)pgc_stats->queues[PGC_QUEUE_CLEAN].size); + rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_evicting, (collected_number)pgc_stats->evicting_size); + rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_flushing, (collected_number)pgc_stats->flushing_size); + rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_index,(collected_number)(pgc_stats->size - pgc_stats->queues[PGC_QUEUE_CLEAN].size - pgc_stats->queues[PGC_QUEUE_HOT].size - pgc_stats->queues[PGC_QUEUE_DIRTY].size - pgc_stats->evicting_size - pgc_stats->flushing_size)); + + rrdset_done(ptrs->st_pgc_memory); + } + + for(size_t q = 0; q < 3 ;q++) { + const char *queue; + switch(q) { + case PGC_QUEUE_HOT: + queue = "hot"; + break; + + case PGC_QUEUE_DIRTY: + queue = "dirty"; + break; + + default: + case PGC_QUEUE_CLEAN: + queue = "clean"; + break; + } + + if (unlikely(!ptrs->queues[q].st_pgc_page_size_heatmap)) { + CLEAN_BUFFER *ctx = buffer_create(100, NULL); + buffer_sprintf(ctx, "netdata.dbengine_%s_page_sizes", name); + + CLEAN_BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_%s_page_sizes", name, queue); + + CLEAN_BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + CLEAN_BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Nominal Page Sizes (without overheads)", name); + + ptrs->queues[q].st_pgc_page_size_heatmap = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + buffer_tostring(ctx), + buffer_tostring(title), + "pages", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_HEATMAP); + + ptrs->queues[q].rd_pgc_page_size_x[0] = rrddim_add(ptrs->queues[q].st_pgc_page_size_heatmap, "empty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + for(size_t i = 1; i < _countof(ptrs->queues[q].rd_pgc_page_size_x) - 1 ;i++) { + char buf[64]; + snprintfz(buf, sizeof(buf), "%zu", pgc_stats->queues[q].size_histogram.array[i].upto); + // size_snprintf(&buf[1], sizeof(buf) - 1, pgc_stats->size_histogram.array[i].upto, "B", true); + ptrs->queues[q].rd_pgc_page_size_x[i] = rrddim_add(ptrs->queues[q].st_pgc_page_size_heatmap, buf, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + ptrs->queues[q].rd_pgc_page_size_x[_countof(ptrs->queues[q].rd_pgc_page_size_x) - 1] = rrddim_add(ptrs->queues[q].st_pgc_page_size_heatmap, "+inf", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + rrdlabels_add(ptrs->queues[q].st_pgc_page_size_heatmap->rrdlabels, "Cache", name, RRDLABEL_SRC_AUTO); + rrdlabels_add(ptrs->queues[q].st_pgc_page_size_heatmap->rrdlabels, "Queue", queue, RRDLABEL_SRC_AUTO); + + priority++; + } + + for(size_t i = 0; i < _countof(ptrs->queues[q].rd_pgc_page_size_x) - 1 ;i++) + rrddim_set_by_pointer(ptrs->queues[q].st_pgc_page_size_heatmap, ptrs->queues[q].rd_pgc_page_size_x[i], (collected_number)pgc_stats->queues[q].size_histogram.array[i].count); + + rrdset_done(ptrs->queues[q].st_pgc_page_size_heatmap); + } + + { + if (unlikely(!ptrs->st_pgc_tm)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_target_memory", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Target Cache Memory", name); + + ptrs->st_pgc_tm = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "bytes", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + ptrs->rd_pgc_tm_current = rrddim_add(ptrs->st_pgc_tm, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_tm_wanted = rrddim_add(ptrs->st_pgc_tm, "wanted", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_tm_referenced = rrddim_add(ptrs->st_pgc_tm, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_tm_hot_max = rrddim_add(ptrs->st_pgc_tm, "hot max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_tm_dirty_max = rrddim_add(ptrs->st_pgc_tm, "dirty max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_tm_hot = rrddim_add(ptrs->st_pgc_tm, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_tm_dirty = rrddim_add(ptrs->st_pgc_tm, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_current, (collected_number)pgc_stats->current_cache_size); + rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_wanted, (collected_number)pgc_stats->wanted_cache_size); + rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_referenced, (collected_number)pgc_stats->referenced_size); + rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_hot_max, (collected_number)pgc_stats->queues[PGC_QUEUE_HOT].max_size); + rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_dirty_max, (collected_number)pgc_stats->queues[PGC_QUEUE_DIRTY].max_size); + rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_hot, (collected_number)pgc_stats->queues[PGC_QUEUE_HOT].size); + rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_dirty, (collected_number)pgc_stats->queues[PGC_QUEUE_DIRTY].size); + + rrdset_done(ptrs->st_pgc_tm); + } + + { + if (unlikely(!ptrs->st_pgc_pages)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_pages", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Pages", name); + + ptrs->st_pgc_pages = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "pages", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + ptrs->rd_pgc_pages_clean = rrddim_add(ptrs->st_pgc_pages, "clean", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_pages_hot = rrddim_add(ptrs->st_pgc_pages, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_pages_dirty = rrddim_add(ptrs->st_pgc_pages, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_pages_referenced = rrddim_add(ptrs->st_pgc_pages, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_clean, (collected_number)pgc_stats->queues[PGC_QUEUE_CLEAN].entries); + rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_hot, (collected_number)pgc_stats->queues[PGC_QUEUE_HOT].entries); + rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_dirty, (collected_number)pgc_stats->queues[PGC_QUEUE_DIRTY].entries); + rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_referenced, (collected_number)pgc_stats->referenced_entries); + + rrdset_done(ptrs->st_pgc_pages); + } + + { + if (unlikely(!ptrs->st_pgc_memory_changes)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_memory_changes", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Memory Changes", name); + + ptrs->st_pgc_memory_changes = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "bytes/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_AREA); + + ptrs->rd_pgc_memory_new_clean = rrddim_add(ptrs->st_pgc_memory_changes, "new clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_memory_clean_evictions = rrddim_add(ptrs->st_pgc_memory_changes, "evictions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_memory_new_hot = rrddim_add(ptrs->st_pgc_memory_changes, "new hot", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_new_clean, (collected_number)(pgc_stats->added_size - pgc_stats->queues[PGC_QUEUE_HOT].added_size)); + rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_clean_evictions, (collected_number)pgc_stats->queues[PGC_QUEUE_CLEAN].removed_size); + rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_new_hot, (collected_number)pgc_stats->queues[PGC_QUEUE_HOT].added_size); + + rrdset_done(ptrs->st_pgc_memory_changes); + } + + { + if (unlikely(!ptrs->st_pgc_memory_migrations)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_memory_migrations", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Memory Migrations", name); + + ptrs->st_pgc_memory_migrations = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "bytes/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_AREA); + + ptrs->rd_pgc_memory_dirty_to_clean = rrddim_add(ptrs->st_pgc_memory_migrations, "dirty to clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_memory_hot_to_dirty = rrddim_add(ptrs->st_pgc_memory_migrations, "hot to dirty", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_pgc_memory_migrations, ptrs->rd_pgc_memory_dirty_to_clean, (collected_number)pgc_stats->queues[PGC_QUEUE_DIRTY].removed_size); + rrddim_set_by_pointer(ptrs->st_pgc_memory_migrations, ptrs->rd_pgc_memory_hot_to_dirty, (collected_number)pgc_stats->queues[PGC_QUEUE_DIRTY].added_size); + + rrdset_done(ptrs->st_pgc_memory_migrations); + } + + { + if (unlikely(!ptrs->st_pgc_memory_events)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_events", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Events", name); + + ptrs->st_pgc_memory_events = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "events/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_AREA); + + ptrs->rd_pgc_memory_evictions_aggressive = rrddim_add(ptrs->st_pgc_memory_events, "evictions aggressive", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_memory_evictions_critical = rrddim_add(ptrs->st_pgc_memory_events, "evictions critical", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_memory_flushes_critical = rrddim_add(ptrs->st_pgc_memory_events, "flushes critical", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_evictions_aggressive, (collected_number)pgc_stats->events_cache_needs_space_aggressively); + rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_evictions_critical, (collected_number)pgc_stats->events_cache_under_severe_pressure); + rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_flushes_critical, (collected_number)pgc_stats->events_flush_critical); + + rrdset_done(ptrs->st_pgc_memory_events); + } + + { + if (unlikely(!ptrs->st_pgc_waste)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_waste_events", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Waste Events", name); + + ptrs->st_pgc_waste = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "events/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + ptrs->rd_pgc_waste_evict_relocated = rrddim_add(ptrs->st_pgc_waste, "evict relocated", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_flushes_cancelled = rrddim_add(ptrs->st_pgc_waste, "flushes cancelled", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_acquire_spins = rrddim_add(ptrs->st_pgc_waste, "acquire spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_release_spins = rrddim_add(ptrs->st_pgc_waste, "release spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_insert_spins = rrddim_add(ptrs->st_pgc_waste, "insert spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_delete_spins = rrddim_add(ptrs->st_pgc_waste, "delete spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_evict_spins = rrddim_add(ptrs->st_pgc_waste, "evict useless spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_evict_thread_signals = rrddim_add(ptrs->st_pgc_waste, "evict thread signals", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_evict_inline_on_add = rrddim_add(ptrs->st_pgc_waste, "evict inline on add", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_evict_inline_on_release = rrddim_add(ptrs->st_pgc_waste, "evict inline on rel", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_flush_inline_on_add = rrddim_add(ptrs->st_pgc_waste, "flush inline on add", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + ptrs->rd_pgc_waste_flush_inline_on_release = rrddim_add(ptrs->st_pgc_waste, "flush inline on rel", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evict_relocated, (collected_number)pgc_stats->waste_evict_relocated); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_flushes_cancelled, (collected_number)pgc_stats->waste_flushes_cancelled); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_acquire_spins, (collected_number)pgc_stats->waste_acquire_spins); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_release_spins, (collected_number)pgc_stats->waste_release_spins); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_insert_spins, (collected_number)pgc_stats->waste_insert_spins); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_delete_spins, (collected_number)pgc_stats->waste_delete_spins); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evict_spins, (collected_number)pgc_stats->waste_evict_useless_spins); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evict_thread_signals, (collected_number)pgc_stats->waste_evict_thread_signals); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evict_inline_on_add, (collected_number)pgc_stats->waste_evictions_inline_on_add); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evict_inline_on_release, (collected_number)pgc_stats->waste_evictions_inline_on_release); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_flush_inline_on_add, (collected_number)pgc_stats->waste_flush_on_add); + rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_flush_inline_on_release, (collected_number)pgc_stats->waste_flush_on_release); + + rrdset_done(ptrs->st_pgc_waste); + } + + { + if (unlikely(!ptrs->st_pgc_workers)) { + BUFFER *id = buffer_create(100, NULL); + buffer_sprintf(id, "dbengine_%s_cache_workers", name); + + BUFFER *family = buffer_create(100, NULL); + buffer_sprintf(family, "dbengine %s cache", name); + + BUFFER *title = buffer_create(100, NULL); + buffer_sprintf(title, "Netdata %s Cache Workers", name); + + ptrs->st_pgc_workers = rrdset_create_localhost( + "netdata", + buffer_tostring(id), + NULL, + buffer_tostring(family), + NULL, + buffer_tostring(title), + "workers", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + ptrs->rd_pgc_workers_searchers = rrddim_add(ptrs->st_pgc_workers, "searchers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_workers_adders = rrddim_add(ptrs->st_pgc_workers, "adders", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_workers_evictors = rrddim_add(ptrs->st_pgc_workers, "evictors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_workers_flushers = rrddim_add(ptrs->st_pgc_workers, "flushers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_workers_hot2dirty = rrddim_add(ptrs->st_pgc_workers, "hot2dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + ptrs->rd_pgc_workers_jv2_flushers = rrddim_add(ptrs->st_pgc_workers, "jv2 flushers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + buffer_free(id); + buffer_free(family); + buffer_free(title); + priority++; + } + + rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_searchers, (collected_number)pgc_stats->workers_search); + rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_adders, (collected_number)pgc_stats->workers_add); + rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_evictors, (collected_number)pgc_stats->workers_evict); + rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_flushers, (collected_number)pgc_stats->workers_flush); + rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_hot2dirty, (collected_number)pgc_stats->workers_hot2dirty); + rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_jv2_flushers, (collected_number)pgc_stats->workers_jv2_flush); + + rrdset_done(ptrs->st_pgc_workers); + } +} + + +void telemetry_dbengine_do(bool extended) { + if(!main_cache || !main_mrg || !extended) + return; + + static struct dbengine2_cache_pointers main_cache_ptrs = {}, open_cache_ptrs = {}, extent_cache_ptrs = {}; + static struct rrdeng_cache_efficiency_stats cache_efficiency_stats = {}, cache_efficiency_stats_old = {}; + static struct pgc_statistics pgc_main_stats = {}, pgc_main_stats_old = {}; (void)pgc_main_stats_old; + static struct pgc_statistics pgc_open_stats = {}, pgc_open_stats_old = {}; (void)pgc_open_stats_old; + static struct pgc_statistics pgc_extent_stats = {}, pgc_extent_stats_old = {}; (void)pgc_extent_stats_old; + static struct mrg_statistics mrg_stats = {}, mrg_stats_old = {}; (void)mrg_stats_old; + + pgc_main_stats_old = pgc_main_stats; + pgc_main_stats = pgc_get_statistics(main_cache); + dbengine2_cache_statistics_charts(&main_cache_ptrs, &pgc_main_stats, &pgc_main_stats_old, "main", 135100); + + pgc_open_stats_old = pgc_open_stats; + pgc_open_stats = pgc_get_statistics(open_cache); + dbengine2_cache_statistics_charts(&open_cache_ptrs, &pgc_open_stats, &pgc_open_stats_old, "open", 135200); + + pgc_extent_stats_old = pgc_extent_stats; + pgc_extent_stats = pgc_get_statistics(extent_cache); + dbengine2_cache_statistics_charts(&extent_cache_ptrs, &pgc_extent_stats, &pgc_extent_stats_old, "extent", 135300); + + cache_efficiency_stats_old = cache_efficiency_stats; + cache_efficiency_stats = rrdeng_get_cache_efficiency_stats(); + + mrg_stats_old = mrg_stats; + mrg_get_statistics(main_mrg, &mrg_stats); + + struct rrdeng_buffer_sizes buffers = rrdeng_get_buffer_sizes(); + size_t buffers_total_size = buffers.handles + buffers.xt_buf + buffers.xt_io + buffers.pdc + buffers.descriptors + + buffers.opcodes + buffers.wal + buffers.workers + buffers.epdl + buffers.deol + buffers.pd + buffers.pgc + buffers.pgd + buffers.mrg; + +#ifdef PDC_USE_JULYL + buffers_total_size += buffers.julyl; +#endif + + telemetry_dbengine_total_memory = pgc_main_stats.size + pgc_open_stats.size + pgc_extent_stats.size + mrg_stats.size + buffers_total_size; + + size_t priority = 135000; + + { + static RRDSET *st_pgc_memory = NULL; + static RRDDIM *rd_pgc_memory_main = NULL; + static RRDDIM *rd_pgc_memory_open = NULL; // open journal memory + static RRDDIM *rd_pgc_memory_extent = NULL; // extent compresses cache memory + static RRDDIM *rd_pgc_memory_metrics = NULL; // metric registry memory + static RRDDIM *rd_pgc_memory_buffers = NULL; + + if (unlikely(!st_pgc_memory)) { + st_pgc_memory = rrdset_create_localhost( + "netdata", + "dbengine_memory", + NULL, + "dbengine memory", + NULL, + "Netdata DB Memory", + "bytes", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_pgc_memory_main = rrddim_add(st_pgc_memory, "main cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_memory_open = rrddim_add(st_pgc_memory, "open cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_memory_extent = rrddim_add(st_pgc_memory, "extent cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_memory_metrics = rrddim_add(st_pgc_memory, "metrics registry", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_memory_buffers = rrddim_add(st_pgc_memory, "buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + priority++; + + + rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_main, (collected_number)pgc_main_stats.size); + rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_open, (collected_number)pgc_open_stats.size); + rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_extent, (collected_number)pgc_extent_stats.size); + rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_metrics, (collected_number)mrg_stats.size); + rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_buffers, (collected_number)buffers_total_size); + + rrdset_done(st_pgc_memory); + } + + { + static RRDSET *st_pgc_buffers = NULL; + static RRDDIM *rd_pgc_buffers_pgc = NULL; + static RRDDIM *rd_pgc_buffers_pgd = NULL; + static RRDDIM *rd_pgc_buffers_mrg = NULL; + static RRDDIM *rd_pgc_buffers_opcodes = NULL; + static RRDDIM *rd_pgc_buffers_handles = NULL; + static RRDDIM *rd_pgc_buffers_descriptors = NULL; + static RRDDIM *rd_pgc_buffers_wal = NULL; + static RRDDIM *rd_pgc_buffers_workers = NULL; + static RRDDIM *rd_pgc_buffers_pdc = NULL; + static RRDDIM *rd_pgc_buffers_xt_io = NULL; + static RRDDIM *rd_pgc_buffers_xt_buf = NULL; + static RRDDIM *rd_pgc_buffers_epdl = NULL; + static RRDDIM *rd_pgc_buffers_deol = NULL; + static RRDDIM *rd_pgc_buffers_pd = NULL; +#ifdef PDC_USE_JULYL + static RRDDIM *rd_pgc_buffers_julyl = NULL; +#endif + + if (unlikely(!st_pgc_buffers)) { + st_pgc_buffers = rrdset_create_localhost( + "netdata", + "dbengine_buffers", + NULL, + "dbengine memory", + NULL, + "Netdata DB Buffers", + "bytes", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_pgc_buffers_pgc = rrddim_add(st_pgc_buffers, "pgc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_pgd = rrddim_add(st_pgc_buffers, "pgd", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_mrg = rrddim_add(st_pgc_buffers, "mrg", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_opcodes = rrddim_add(st_pgc_buffers, "opcodes", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_handles = rrddim_add(st_pgc_buffers, "query handles", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_descriptors = rrddim_add(st_pgc_buffers, "descriptors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_wal = rrddim_add(st_pgc_buffers, "wal", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_workers = rrddim_add(st_pgc_buffers, "workers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_pdc = rrddim_add(st_pgc_buffers, "pdc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_pd = rrddim_add(st_pgc_buffers, "pd", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_xt_io = rrddim_add(st_pgc_buffers, "extent io", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_xt_buf = rrddim_add(st_pgc_buffers, "extent buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_epdl = rrddim_add(st_pgc_buffers, "epdl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_pgc_buffers_deol = rrddim_add(st_pgc_buffers, "deol", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); +#ifdef PDC_USE_JULYL + rd_pgc_buffers_julyl = rrddim_add(st_pgc_buffers, "julyl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); +#endif + } + priority++; + + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pgc, (collected_number)buffers.pgc); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pgd, (collected_number)buffers.pgd); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_mrg, (collected_number)buffers.mrg); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_opcodes, (collected_number)buffers.opcodes); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_handles, (collected_number)buffers.handles); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_descriptors, (collected_number)buffers.descriptors); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_wal, (collected_number)buffers.wal); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_workers, (collected_number)buffers.workers); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pdc, (collected_number)buffers.pdc); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pd, (collected_number)buffers.pd); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_xt_io, (collected_number)buffers.xt_io); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_xt_buf, (collected_number)buffers.xt_buf); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_epdl, (collected_number)buffers.epdl); + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_deol, (collected_number)buffers.deol); +#ifdef PDC_USE_JULYL + rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_julyl, (collected_number)buffers.julyl); +#endif + + rrdset_done(st_pgc_buffers); + } + +#ifdef PDC_USE_JULYL + { + static RRDSET *st_julyl_moved = NULL; + static RRDDIM *rd_julyl_moved = NULL; + + if (unlikely(!st_julyl_moved)) { + st_julyl_moved = rrdset_create_localhost( + "netdata", + "dbengine_julyl_moved", + NULL, + "dbengine memory", + NULL, + "Netdata JulyL Memory Moved", + "bytes/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_AREA); + + rd_julyl_moved = rrddim_add(st_julyl_moved, "moved", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_julyl_moved, rd_julyl_moved, (collected_number)julyl_bytes_moved()); + + rrdset_done(st_julyl_moved); + } +#endif + + { + static RRDSET *st_mrg_metrics = NULL; + static RRDDIM *rd_mrg_metrics = NULL; + static RRDDIM *rd_mrg_acquired = NULL; + static RRDDIM *rd_mrg_collected = NULL; + static RRDDIM *rd_mrg_multiple_writers = NULL; + + if (unlikely(!st_mrg_metrics)) { + st_mrg_metrics = rrdset_create_localhost( + "netdata", + "dbengine_metrics", + NULL, + "dbengine metrics", + NULL, + "Netdata Metrics in Metrics Registry", + "metrics", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_mrg_metrics = rrddim_add(st_mrg_metrics, "all", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_mrg_acquired = rrddim_add(st_mrg_metrics, "acquired", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_mrg_collected = rrddim_add(st_mrg_metrics, "collected", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_mrg_multiple_writers = rrddim_add(st_mrg_metrics, "multi-collected", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + priority++; + + rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_metrics, (collected_number)mrg_stats.entries); + rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_acquired, (collected_number)mrg_stats.entries_referenced); + rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_collected, (collected_number)mrg_stats.writers); + rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_multiple_writers, (collected_number)mrg_stats.writers_conflicts); + + rrdset_done(st_mrg_metrics); + } + + { + static RRDSET *st_mrg_ops = NULL; + static RRDDIM *rd_mrg_add = NULL; + static RRDDIM *rd_mrg_del = NULL; + static RRDDIM *rd_mrg_search = NULL; + + if (unlikely(!st_mrg_ops)) { + st_mrg_ops = rrdset_create_localhost( + "netdata", + "dbengine_metrics_registry_operations", + NULL, + "dbengine metrics", + NULL, + "Netdata Metrics Registry Operations", + "metrics", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_mrg_add = rrddim_add(st_mrg_ops, "add", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_mrg_del = rrddim_add(st_mrg_ops, "delete", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_mrg_search = rrddim_add(st_mrg_ops, "search", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_mrg_ops, rd_mrg_add, (collected_number)mrg_stats.additions); + rrddim_set_by_pointer(st_mrg_ops, rd_mrg_del, (collected_number)mrg_stats.deletions); + rrddim_set_by_pointer(st_mrg_ops, rd_mrg_search, (collected_number)mrg_stats.search_hits + (collected_number)mrg_stats.search_misses); + + rrdset_done(st_mrg_ops); + } + + { + static RRDSET *st_mrg_references = NULL; + static RRDDIM *rd_mrg_references = NULL; + + if (unlikely(!st_mrg_references)) { + st_mrg_references = rrdset_create_localhost( + "netdata", + "dbengine_metrics_registry_references", + NULL, + "dbengine metrics", + NULL, + "Netdata Metrics Registry References", + "references", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_mrg_references = rrddim_add(st_mrg_references, "references", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + priority++; + + rrddim_set_by_pointer(st_mrg_references, rd_mrg_references, (collected_number)mrg_stats.current_references); + + rrdset_done(st_mrg_references); + } + + { + static RRDSET *st_cache_hit_ratio = NULL; + static RRDDIM *rd_hit_ratio = NULL; + static RRDDIM *rd_main_cache_hit_ratio = NULL; + static RRDDIM *rd_extent_cache_hit_ratio = NULL; + static RRDDIM *rd_extent_merge_hit_ratio = NULL; + + if (unlikely(!st_cache_hit_ratio)) { + st_cache_hit_ratio = rrdset_create_localhost( + "netdata", + "dbengine_cache_hit_ratio", + NULL, + "dbengine query router", + NULL, + "Netdata Queries Cache Hit Ratio", + "%", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_hit_ratio = rrddim_add(st_cache_hit_ratio, "overall", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); + rd_main_cache_hit_ratio = rrddim_add(st_cache_hit_ratio, "main cache", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); + rd_extent_cache_hit_ratio = rrddim_add(st_cache_hit_ratio, "extent cache", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); + rd_extent_merge_hit_ratio = rrddim_add(st_cache_hit_ratio, "extent merge", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); + } + priority++; + + size_t delta_pages_total = cache_efficiency_stats.pages_total - cache_efficiency_stats_old.pages_total; + size_t delta_pages_to_load_from_disk = cache_efficiency_stats.pages_to_load_from_disk - cache_efficiency_stats_old.pages_to_load_from_disk; + size_t delta_extents_loaded_from_disk = cache_efficiency_stats.extents_loaded_from_disk - cache_efficiency_stats_old.extents_loaded_from_disk; + + size_t delta_pages_data_source_main_cache = cache_efficiency_stats.pages_data_source_main_cache - cache_efficiency_stats_old.pages_data_source_main_cache; + size_t delta_pages_pending_found_in_cache_at_pass4 = cache_efficiency_stats.pages_data_source_main_cache_at_pass4 - cache_efficiency_stats_old.pages_data_source_main_cache_at_pass4; + + size_t delta_pages_data_source_extent_cache = cache_efficiency_stats.pages_data_source_extent_cache - cache_efficiency_stats_old.pages_data_source_extent_cache; + size_t delta_pages_load_extent_merged = cache_efficiency_stats.pages_load_extent_merged - cache_efficiency_stats_old.pages_load_extent_merged; + + size_t pages_total_hit = delta_pages_total - delta_extents_loaded_from_disk; + + static size_t overall_hit_ratio = 100; + size_t main_cache_hit_ratio = 0, extent_cache_hit_ratio = 0, extent_merge_hit_ratio = 0; + if(delta_pages_total) { + if(pages_total_hit > delta_pages_total) + pages_total_hit = delta_pages_total; + + overall_hit_ratio = pages_total_hit * 100 * 10000 / delta_pages_total; + + size_t delta_pages_main_cache = delta_pages_data_source_main_cache + delta_pages_pending_found_in_cache_at_pass4; + if(delta_pages_main_cache > delta_pages_total) + delta_pages_main_cache = delta_pages_total; + + main_cache_hit_ratio = delta_pages_main_cache * 100 * 10000 / delta_pages_total; + } + + if(delta_pages_to_load_from_disk) { + if(delta_pages_data_source_extent_cache > delta_pages_to_load_from_disk) + delta_pages_data_source_extent_cache = delta_pages_to_load_from_disk; + + extent_cache_hit_ratio = delta_pages_data_source_extent_cache * 100 * 10000 / delta_pages_to_load_from_disk; + + if(delta_pages_load_extent_merged > delta_pages_to_load_from_disk) + delta_pages_load_extent_merged = delta_pages_to_load_from_disk; + + extent_merge_hit_ratio = delta_pages_load_extent_merged * 100 * 10000 / delta_pages_to_load_from_disk; + } + + rrddim_set_by_pointer(st_cache_hit_ratio, rd_hit_ratio, (collected_number)overall_hit_ratio); + rrddim_set_by_pointer(st_cache_hit_ratio, rd_main_cache_hit_ratio, (collected_number)main_cache_hit_ratio); + rrddim_set_by_pointer(st_cache_hit_ratio, rd_extent_cache_hit_ratio, (collected_number)extent_cache_hit_ratio); + rrddim_set_by_pointer(st_cache_hit_ratio, rd_extent_merge_hit_ratio, (collected_number)extent_merge_hit_ratio); + + rrdset_done(st_cache_hit_ratio); + } + + { + static RRDSET *st_queries = NULL; + static RRDDIM *rd_total = NULL; + static RRDDIM *rd_open = NULL; + static RRDDIM *rd_jv2 = NULL; + static RRDDIM *rd_planned_with_gaps = NULL; + static RRDDIM *rd_executed_with_gaps = NULL; + + if (unlikely(!st_queries)) { + st_queries = rrdset_create_localhost( + "netdata", + "dbengine_queries", + NULL, + "dbengine query router", + NULL, + "Netdata Queries", + "queries/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_total = rrddim_add(st_queries, "total", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_open = rrddim_add(st_queries, "open cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_jv2 = rrddim_add(st_queries, "journal v2", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_planned_with_gaps = rrddim_add(st_queries, "planned with gaps", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_executed_with_gaps = rrddim_add(st_queries, "executed with gaps", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_queries, rd_total, (collected_number)cache_efficiency_stats.queries); + rrddim_set_by_pointer(st_queries, rd_open, (collected_number)cache_efficiency_stats.queries_open); + rrddim_set_by_pointer(st_queries, rd_jv2, (collected_number)cache_efficiency_stats.queries_journal_v2); + rrddim_set_by_pointer(st_queries, rd_planned_with_gaps, (collected_number)cache_efficiency_stats.queries_planned_with_gaps); + rrddim_set_by_pointer(st_queries, rd_executed_with_gaps, (collected_number)cache_efficiency_stats.queries_executed_with_gaps); + + rrdset_done(st_queries); + } + + { + static RRDSET *st_queries_running = NULL; + static RRDDIM *rd_queries = NULL; + + if (unlikely(!st_queries_running)) { + st_queries_running = rrdset_create_localhost( + "netdata", + "dbengine_queries_running", + NULL, + "dbengine query router", + NULL, + "Netdata Queries Running", + "queries", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_queries = rrddim_add(st_queries_running, "queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + priority++; + + rrddim_set_by_pointer(st_queries_running, rd_queries, (collected_number)cache_efficiency_stats.currently_running_queries); + + rrdset_done(st_queries_running); + } + + { + static RRDSET *st_query_pages_metadata_source = NULL; + static RRDDIM *rd_cache = NULL; + static RRDDIM *rd_open = NULL; + static RRDDIM *rd_jv2 = NULL; + + if (unlikely(!st_query_pages_metadata_source)) { + st_query_pages_metadata_source = rrdset_create_localhost( + "netdata", + "dbengine_query_pages_metadata_source", + NULL, + "dbengine query router", + NULL, + "Netdata Query Pages Metadata Source", + "pages/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_cache = rrddim_add(st_query_pages_metadata_source, "cache hit", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_jv2 = rrddim_add(st_query_pages_metadata_source, "journal v2 scan", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_open = rrddim_add(st_query_pages_metadata_source, "open journal", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_query_pages_metadata_source, rd_cache, (collected_number)cache_efficiency_stats.pages_meta_source_main_cache); + rrddim_set_by_pointer(st_query_pages_metadata_source, rd_jv2, (collected_number)cache_efficiency_stats.pages_meta_source_journal_v2); + rrddim_set_by_pointer(st_query_pages_metadata_source, rd_open, (collected_number)cache_efficiency_stats.pages_meta_source_open_cache); + + rrdset_done(st_query_pages_metadata_source); + } + + { + static RRDSET *st_query_pages_data_source = NULL; + static RRDDIM *rd_pages_main_cache = NULL; + static RRDDIM *rd_pages_disk = NULL; + static RRDDIM *rd_pages_extent_cache = NULL; + + if (unlikely(!st_query_pages_data_source)) { + st_query_pages_data_source = rrdset_create_localhost( + "netdata", + "dbengine_query_pages_data_source", + NULL, + "dbengine query router", + NULL, + "Netdata Query Pages to Data Source", + "pages/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_pages_main_cache = rrddim_add(st_query_pages_data_source, "main cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pages_disk = rrddim_add(st_query_pages_data_source, "disk", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pages_extent_cache = rrddim_add(st_query_pages_data_source, "extent cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_main_cache, (collected_number)cache_efficiency_stats.pages_data_source_main_cache + (collected_number)cache_efficiency_stats.pages_data_source_main_cache_at_pass4); + rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_disk, (collected_number)cache_efficiency_stats.pages_to_load_from_disk); + rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_extent_cache, (collected_number)cache_efficiency_stats.pages_data_source_extent_cache); + + rrdset_done(st_query_pages_data_source); + } + + { + static RRDSET *st_query_next_page = NULL; + static RRDDIM *rd_pass4 = NULL; + static RRDDIM *rd_nowait_failed = NULL; + static RRDDIM *rd_wait_failed = NULL; + static RRDDIM *rd_wait_loaded = NULL; + static RRDDIM *rd_nowait_loaded = NULL; + + if (unlikely(!st_query_next_page)) { + st_query_next_page = rrdset_create_localhost( + "netdata", + "dbengine_query_next_page", + NULL, + "dbengine query router", + NULL, + "Netdata Query Next Page", + "pages/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_pass4 = rrddim_add(st_query_next_page, "pass4", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_wait_failed = rrddim_add(st_query_next_page, "failed slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_nowait_failed = rrddim_add(st_query_next_page, "failed fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_wait_loaded = rrddim_add(st_query_next_page, "loaded slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_nowait_loaded = rrddim_add(st_query_next_page, "loaded fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_query_next_page, rd_pass4, (collected_number)cache_efficiency_stats.pages_data_source_main_cache_at_pass4); + rrddim_set_by_pointer(st_query_next_page, rd_wait_failed, (collected_number)cache_efficiency_stats.page_next_wait_failed); + rrddim_set_by_pointer(st_query_next_page, rd_nowait_failed, (collected_number)cache_efficiency_stats.page_next_nowait_failed); + rrddim_set_by_pointer(st_query_next_page, rd_wait_loaded, (collected_number)cache_efficiency_stats.page_next_wait_loaded); + rrddim_set_by_pointer(st_query_next_page, rd_nowait_loaded, (collected_number)cache_efficiency_stats.page_next_nowait_loaded); + + rrdset_done(st_query_next_page); + } + + { + static RRDSET *st_query_page_issues = NULL; + static RRDDIM *rd_pages_zero_time = NULL; + static RRDDIM *rd_pages_past_time = NULL; + static RRDDIM *rd_pages_invalid_size = NULL; + static RRDDIM *rd_pages_fixed_update_every = NULL; + static RRDDIM *rd_pages_fixed_entries = NULL; + static RRDDIM *rd_pages_overlapping = NULL; + + if (unlikely(!st_query_page_issues)) { + st_query_page_issues = rrdset_create_localhost( + "netdata", + "dbengine_query_next_page_issues", + NULL, + "dbengine query router", + NULL, + "Netdata Query Next Page Issues", + "pages/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_pages_zero_time = rrddim_add(st_query_page_issues, "zero timestamp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pages_invalid_size = rrddim_add(st_query_page_issues, "invalid size", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pages_past_time = rrddim_add(st_query_page_issues, "past time", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pages_overlapping = rrddim_add(st_query_page_issues, "overlapping", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pages_fixed_update_every = rrddim_add(st_query_page_issues, "update every fixed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pages_fixed_entries = rrddim_add(st_query_page_issues, "entries fixed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_query_page_issues, rd_pages_zero_time, (collected_number)cache_efficiency_stats.pages_zero_time_skipped); + rrddim_set_by_pointer(st_query_page_issues, rd_pages_invalid_size, (collected_number)cache_efficiency_stats.pages_invalid_size_skipped); + rrddim_set_by_pointer(st_query_page_issues, rd_pages_past_time, (collected_number)cache_efficiency_stats.pages_past_time_skipped); + rrddim_set_by_pointer(st_query_page_issues, rd_pages_overlapping, (collected_number)cache_efficiency_stats.pages_overlapping_skipped); + rrddim_set_by_pointer(st_query_page_issues, rd_pages_fixed_update_every, (collected_number)cache_efficiency_stats.pages_invalid_update_every_fixed); + rrddim_set_by_pointer(st_query_page_issues, rd_pages_fixed_entries, (collected_number)cache_efficiency_stats.pages_invalid_entries_fixed); + + rrdset_done(st_query_page_issues); + } + + { + static RRDSET *st_query_pages_from_disk = NULL; + static RRDDIM *rd_compressed = NULL; + static RRDDIM *rd_invalid = NULL; + static RRDDIM *rd_uncompressed = NULL; + static RRDDIM *rd_mmap_failed = NULL; + static RRDDIM *rd_unavailable = NULL; + static RRDDIM *rd_unroutable = NULL; + static RRDDIM *rd_not_found = NULL; + static RRDDIM *rd_cancelled = NULL; + static RRDDIM *rd_invalid_extent = NULL; + static RRDDIM *rd_extent_merged = NULL; + + if (unlikely(!st_query_pages_from_disk)) { + st_query_pages_from_disk = rrdset_create_localhost( + "netdata", + "dbengine_query_pages_disk_load", + NULL, + "dbengine query router", + NULL, + "Netdata Query Pages Loaded from Disk", + "pages/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_compressed = rrddim_add(st_query_pages_from_disk, "ok compressed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_invalid = rrddim_add(st_query_pages_from_disk, "fail invalid page", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_uncompressed = rrddim_add(st_query_pages_from_disk, "ok uncompressed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_mmap_failed = rrddim_add(st_query_pages_from_disk, "fail cant mmap", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_unavailable = rrddim_add(st_query_pages_from_disk, "fail unavailable", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_unroutable = rrddim_add(st_query_pages_from_disk, "fail unroutable", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_not_found = rrddim_add(st_query_pages_from_disk, "fail not found", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_invalid_extent = rrddim_add(st_query_pages_from_disk, "fail invalid extent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_extent_merged = rrddim_add(st_query_pages_from_disk, "extent merged", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_cancelled = rrddim_add(st_query_pages_from_disk, "cancelled", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_query_pages_from_disk, rd_compressed, (collected_number)cache_efficiency_stats.pages_load_ok_compressed); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_invalid, (collected_number)cache_efficiency_stats.pages_load_fail_invalid_page_in_extent); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_uncompressed, (collected_number)cache_efficiency_stats.pages_load_ok_uncompressed); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_mmap_failed, (collected_number)cache_efficiency_stats.pages_load_fail_cant_mmap_extent); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_unavailable, (collected_number)cache_efficiency_stats.pages_load_fail_datafile_not_available); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_unroutable, (collected_number)cache_efficiency_stats.pages_load_fail_unroutable); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_not_found, (collected_number)cache_efficiency_stats.pages_load_fail_not_found); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_cancelled, (collected_number)cache_efficiency_stats.pages_load_fail_cancelled); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_invalid_extent, (collected_number)cache_efficiency_stats.pages_load_fail_invalid_extent); + rrddim_set_by_pointer(st_query_pages_from_disk, rd_extent_merged, (collected_number)cache_efficiency_stats.pages_load_extent_merged); + + rrdset_done(st_query_pages_from_disk); + } + + { + static RRDSET *st_events = NULL; + static RRDDIM *rd_journal_v2_mapped = NULL; + static RRDDIM *rd_journal_v2_unmapped = NULL; + static RRDDIM *rd_datafile_creation = NULL; + static RRDDIM *rd_datafile_deletion = NULL; + static RRDDIM *rd_datafile_deletion_spin = NULL; + static RRDDIM *rd_jv2_indexing = NULL; + static RRDDIM *rd_retention = NULL; + + if (unlikely(!st_events)) { + st_events = rrdset_create_localhost( + "netdata", + "dbengine_events", + NULL, + "dbengine query router", + NULL, + "Netdata Database Events", + "events/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_journal_v2_mapped = rrddim_add(st_events, "journal v2 mapped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_journal_v2_unmapped = rrddim_add(st_events, "journal v2 unmapped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_datafile_creation = rrddim_add(st_events, "datafile creation", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_datafile_deletion = rrddim_add(st_events, "datafile deletion", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_datafile_deletion_spin = rrddim_add(st_events, "datafile deletion spin", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_jv2_indexing = rrddim_add(st_events, "journal v2 indexing", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_retention = rrddim_add(st_events, "retention", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_events, rd_journal_v2_mapped, (collected_number)cache_efficiency_stats.journal_v2_mapped); + rrddim_set_by_pointer(st_events, rd_journal_v2_unmapped, (collected_number)cache_efficiency_stats.journal_v2_unmapped); + rrddim_set_by_pointer(st_events, rd_datafile_creation, (collected_number)cache_efficiency_stats.datafile_creation_started); + rrddim_set_by_pointer(st_events, rd_datafile_deletion, (collected_number)cache_efficiency_stats.datafile_deletion_started); + rrddim_set_by_pointer(st_events, rd_datafile_deletion_spin, (collected_number)cache_efficiency_stats.datafile_deletion_spin); + rrddim_set_by_pointer(st_events, rd_jv2_indexing, (collected_number)cache_efficiency_stats.journal_v2_indexing_started); + rrddim_set_by_pointer(st_events, rd_retention, (collected_number)cache_efficiency_stats.metrics_retention_started); + + rrdset_done(st_events); + } + + { + static RRDSET *st_prep_timings = NULL; + static RRDDIM *rd_routing = NULL; + static RRDDIM *rd_main_cache = NULL; + static RRDDIM *rd_open_cache = NULL; + static RRDDIM *rd_journal_v2 = NULL; + static RRDDIM *rd_pass4 = NULL; + + if (unlikely(!st_prep_timings)) { + st_prep_timings = rrdset_create_localhost( + "netdata", + "dbengine_prep_timings", + NULL, + "dbengine query router", + NULL, + "Netdata Query Preparation Timings", + "usec/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_routing = rrddim_add(st_prep_timings, "routing", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_main_cache = rrddim_add(st_prep_timings, "main cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_open_cache = rrddim_add(st_prep_timings, "open cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_journal_v2 = rrddim_add(st_prep_timings, "journal v2", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_pass4 = rrddim_add(st_prep_timings, "pass4", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_prep_timings, rd_routing, (collected_number)cache_efficiency_stats.prep_time_to_route); + rrddim_set_by_pointer(st_prep_timings, rd_main_cache, (collected_number)cache_efficiency_stats.prep_time_in_main_cache_lookup); + rrddim_set_by_pointer(st_prep_timings, rd_open_cache, (collected_number)cache_efficiency_stats.prep_time_in_open_cache_lookup); + rrddim_set_by_pointer(st_prep_timings, rd_journal_v2, (collected_number)cache_efficiency_stats.prep_time_in_journal_v2_lookup); + rrddim_set_by_pointer(st_prep_timings, rd_pass4, (collected_number)cache_efficiency_stats.prep_time_in_pass4_lookup); + + rrdset_done(st_prep_timings); + } + + { + static RRDSET *st_query_timings = NULL; + static RRDDIM *rd_init = NULL; + static RRDDIM *rd_prep_wait = NULL; + static RRDDIM *rd_next_page_disk_fast = NULL; + static RRDDIM *rd_next_page_disk_slow = NULL; + static RRDDIM *rd_next_page_preload_fast = NULL; + static RRDDIM *rd_next_page_preload_slow = NULL; + + if (unlikely(!st_query_timings)) { + st_query_timings = rrdset_create_localhost( + "netdata", + "dbengine_query_timings", + NULL, + "dbengine query router", + NULL, + "Netdata Query Timings", + "usec/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + + rd_init = rrddim_add(st_query_timings, "init", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_prep_wait = rrddim_add(st_query_timings, "prep wait", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_next_page_disk_fast = rrddim_add(st_query_timings, "next page disk fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_next_page_disk_slow = rrddim_add(st_query_timings, "next page disk slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_next_page_preload_fast = rrddim_add(st_query_timings, "next page preload fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_next_page_preload_slow = rrddim_add(st_query_timings, "next page preload slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_query_timings, rd_init, (collected_number)cache_efficiency_stats.query_time_init); + rrddim_set_by_pointer(st_query_timings, rd_prep_wait, (collected_number)cache_efficiency_stats.query_time_wait_for_prep); + rrddim_set_by_pointer(st_query_timings, rd_next_page_disk_fast, (collected_number)cache_efficiency_stats.query_time_to_fast_disk_next_page); + rrddim_set_by_pointer(st_query_timings, rd_next_page_disk_slow, (collected_number)cache_efficiency_stats.query_time_to_slow_disk_next_page); + rrddim_set_by_pointer(st_query_timings, rd_next_page_preload_fast, (collected_number)cache_efficiency_stats.query_time_to_fast_preload_next_page); + rrddim_set_by_pointer(st_query_timings, rd_next_page_preload_slow, (collected_number)cache_efficiency_stats.query_time_to_slow_preload_next_page); + + rrdset_done(st_query_timings); + } + + if(netdata_rwlock_tryrdlock(&rrd_rwlock) == 0) { + priority = 135400; + + RRDHOST *host; + unsigned long long stats_array[RRDENG_NR_STATS] = {0}; + unsigned long long local_stats_array[RRDENG_NR_STATS]; + unsigned dbengine_contexts = 0, counted_multihost_db[RRD_STORAGE_TIERS] = { 0 }, i; + + rrdhost_foreach_read(host) { + if (!rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) { + + /* get localhost's DB engine's statistics for each tier */ + for(size_t tier = 0; tier < storage_tiers ;tier++) { + if(host->db[tier].mode != RRD_MEMORY_MODE_DBENGINE) continue; + if(!host->db[tier].si) continue; + + if(counted_multihost_db[tier]) + continue; + else + counted_multihost_db[tier] = 1; + + ++dbengine_contexts; + rrdeng_get_37_statistics((struct rrdengine_instance *)host->db[tier].si, local_stats_array); + for (i = 0; i < RRDENG_NR_STATS; ++i) { + /* aggregate statistics across hosts */ + stats_array[i] += local_stats_array[i]; + } + } + } + } + rrd_rdunlock(); + + if (dbengine_contexts) { + /* deduplicate telemetry by getting the ones from the last context */ + stats_array[30] = local_stats_array[30]; + stats_array[31] = local_stats_array[31]; + stats_array[32] = local_stats_array[32]; + stats_array[34] = local_stats_array[34]; + stats_array[36] = local_stats_array[36]; + + // ---------------------------------------------------------------- + + { + static RRDSET *st_compression = NULL; + static RRDDIM *rd_savings = NULL; + + if (unlikely(!st_compression)) { + st_compression = rrdset_create_localhost( + "netdata", + "dbengine_compression_ratio", + NULL, + "dbengine io", + NULL, + "Netdata DB engine data extents' compression savings ratio", + "percentage", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_savings = rrddim_add(st_compression, "savings", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + } + priority++; + + unsigned long long ratio; + unsigned long long compressed_content_size = stats_array[12]; + unsigned long long content_size = stats_array[11]; + + if (content_size) { + // allow negative savings + ratio = ((content_size - compressed_content_size) * 100 * 1000) / content_size; + } else { + ratio = 0; + } + rrddim_set_by_pointer(st_compression, rd_savings, ratio); + + rrdset_done(st_compression); + } + + // ---------------------------------------------------------------- + + { + static RRDSET *st_io_stats = NULL; + static RRDDIM *rd_reads = NULL; + static RRDDIM *rd_writes = NULL; + + if (unlikely(!st_io_stats)) { + st_io_stats = rrdset_create_localhost( + "netdata", + "dbengine_io_throughput", + NULL, + "dbengine io", + NULL, + "Netdata DB engine I/O throughput", + "MiB/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); + rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[17]); + rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[15]); + rrdset_done(st_io_stats); + } + + // ---------------------------------------------------------------- + + { + static RRDSET *st_io_stats = NULL; + static RRDDIM *rd_reads = NULL; + static RRDDIM *rd_writes = NULL; + + if (unlikely(!st_io_stats)) { + st_io_stats = rrdset_create_localhost( + "netdata", + "dbengine_io_operations", + NULL, + "dbengine io", + NULL, + "Netdata DB engine I/O operations", + "operations/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[18]); + rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[16]); + rrdset_done(st_io_stats); + } + + // ---------------------------------------------------------------- + + { + static RRDSET *st_errors = NULL; + static RRDDIM *rd_fs_errors = NULL; + static RRDDIM *rd_io_errors = NULL; + static RRDDIM *pg_cache_over_half_dirty_events = NULL; + + if (unlikely(!st_errors)) { + st_errors = rrdset_create_localhost( + "netdata", + "dbengine_global_errors", + NULL, + "dbengine io", + NULL, + "Netdata DB engine errors", + "errors/s", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_io_errors = rrddim_add(st_errors, "io_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_fs_errors = rrddim_add(st_errors, "fs_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + pg_cache_over_half_dirty_events = + rrddim_add(st_errors, "pg_cache_over_half_dirty_events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + priority++; + + rrddim_set_by_pointer(st_errors, rd_io_errors, (collected_number)stats_array[30]); + rrddim_set_by_pointer(st_errors, rd_fs_errors, (collected_number)stats_array[31]); + rrddim_set_by_pointer(st_errors, pg_cache_over_half_dirty_events, (collected_number)stats_array[34]); + rrdset_done(st_errors); + } + + // ---------------------------------------------------------------- + + { + static RRDSET *st_fd = NULL; + static RRDDIM *rd_fd_current = NULL; + static RRDDIM *rd_fd_max = NULL; + + if (unlikely(!st_fd)) { + st_fd = rrdset_create_localhost( + "netdata", + "dbengine_global_file_descriptors", + NULL, + "dbengine io", + NULL, + "Netdata DB engine File Descriptors", + "descriptors", + "netdata", + "stats", + priority, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_fd_current = rrddim_add(st_fd, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_fd_max = rrddim_add(st_fd, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + priority++; + + rrddim_set_by_pointer(st_fd, rd_fd_current, (collected_number)stats_array[32]); + /* Careful here, modify this accordingly if the File-Descriptor budget ever changes */ + rrddim_set_by_pointer(st_fd, rd_fd_max, (collected_number)rlimit_nofile.rlim_cur / 4); + rrdset_done(st_fd); + } + } + } +} + +#endif diff --git a/src/daemon/telemetry/telemetry-dbengine.h b/src/daemon/telemetry/telemetry-dbengine.h new file mode 100644 index 00000000000000..af120501e1e980 --- /dev/null +++ b/src/daemon/telemetry/telemetry-dbengine.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_DBENGINE_H +#define NETDATA_TELEMETRY_DBENGINE_H + +#include "daemon/common.h" + +#if defined(TELEMETRY_INTERNALS) +extern size_t telemetry_dbengine_total_memory; + +#if defined(ENABLE_DBENGINE) +void telemetry_dbengine_do(bool extended); +#endif + +#endif + +#endif //NETDATA_TELEMETRY_DBENGINE_H diff --git a/src/daemon/telemetry/telemetry-dictionary.c b/src/daemon/telemetry/telemetry-dictionary.c new file mode 100644 index 00000000000000..75424cb0702865 --- /dev/null +++ b/src/daemon/telemetry/telemetry-dictionary.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-dictionary.h" + +struct dictionary_stats dictionary_stats_category_collectors = { .name = "collectors" }; +struct dictionary_stats dictionary_stats_category_rrdhost = { .name = "rrdhost" }; +struct dictionary_stats dictionary_stats_category_rrdset = { .name = "rrdset" }; +struct dictionary_stats dictionary_stats_category_rrddim = { .name = "rrddim" }; +struct dictionary_stats dictionary_stats_category_rrdcontext = { .name = "context" }; +struct dictionary_stats dictionary_stats_category_rrdlabels = { .name = "labels" }; +struct dictionary_stats dictionary_stats_category_rrdhealth = { .name = "health" }; +struct dictionary_stats dictionary_stats_category_functions = { .name = "functions" }; +struct dictionary_stats dictionary_stats_category_replication = { .name = "replication" }; + +size_t rrddim_db_memory_size = 0; + +#ifdef DICT_WITH_STATS +struct dictionary_categories { + struct dictionary_stats *stats; + + RRDSET *st_dicts; + RRDDIM *rd_dicts_active; + RRDDIM *rd_dicts_deleted; + + RRDSET *st_items; + RRDDIM *rd_items_entries; + RRDDIM *rd_items_referenced; + RRDDIM *rd_items_pending_deletion; + + RRDSET *st_ops; + RRDDIM *rd_ops_creations; + RRDDIM *rd_ops_destructions; + RRDDIM *rd_ops_flushes; + RRDDIM *rd_ops_traversals; + RRDDIM *rd_ops_walkthroughs; + RRDDIM *rd_ops_garbage_collections; + RRDDIM *rd_ops_searches; + RRDDIM *rd_ops_inserts; + RRDDIM *rd_ops_resets; + RRDDIM *rd_ops_deletes; + + RRDSET *st_callbacks; + RRDDIM *rd_callbacks_inserts; + RRDDIM *rd_callbacks_conflicts; + RRDDIM *rd_callbacks_reacts; + RRDDIM *rd_callbacks_deletes; + + RRDSET *st_memory; + RRDDIM *rd_memory_indexed; + RRDDIM *rd_memory_values; + RRDDIM *rd_memory_dict; + + RRDSET *st_spins; + RRDDIM *rd_spins_use; + RRDDIM *rd_spins_search; + RRDDIM *rd_spins_insert; + RRDDIM *rd_spins_delete; + +} dictionary_categories[] = { + { .stats = &dictionary_stats_category_collectors, }, + { .stats = &dictionary_stats_category_rrdhost, }, + { .stats = &dictionary_stats_category_rrdset, }, + { .stats = &dictionary_stats_category_rrdcontext, }, + { .stats = &dictionary_stats_category_rrdlabels, }, + { .stats = &dictionary_stats_category_rrdhealth, }, + { .stats = &dictionary_stats_category_functions, }, + { .stats = &dictionary_stats_category_replication, }, + { .stats = &dictionary_stats_category_other, }, + + // terminator + { .stats = NULL, NULL, NULL, 0 }, +}; + +#define load_dictionary_stats_entry(x) total += (size_t)(stats.x = __atomic_load_n(&c->stats->x, __ATOMIC_RELAXED)) + +static void update_dictionary_category_charts(struct dictionary_categories *c) { + struct dictionary_stats stats; + stats.name = c->stats->name; + int priority = 900000; + const char *family = "dictionaries"; + const char *context_prefix = "dictionaries"; + + // ------------------------------------------------------------------------ + + size_t total = 0; + load_dictionary_stats_entry(dictionaries.active); + load_dictionary_stats_entry(dictionaries.deleted); + + if(c->st_dicts || total != 0) { + if (unlikely(!c->st_dicts)) { + char id[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.dictionaries", context_prefix, stats.name); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.dictionaries", context_prefix); + + c->st_dicts = rrdset_create_localhost( + "netdata" + , id + , NULL + , family + , context + , "Dictionaries" + , "dictionaries" + , "netdata" + , "stats" + , priority + 0 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + c->rd_dicts_active = rrddim_add(c->st_dicts, "active", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + c->rd_dicts_deleted = rrddim_add(c->st_dicts, "deleted", NULL, -1, 1, RRD_ALGORITHM_ABSOLUTE); + + rrdlabels_add(c->st_dicts->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); + } + + rrddim_set_by_pointer(c->st_dicts, c->rd_dicts_active, (collected_number)stats.dictionaries.active); + rrddim_set_by_pointer(c->st_dicts, c->rd_dicts_deleted, (collected_number)stats.dictionaries.deleted); + rrdset_done(c->st_dicts); + } + + // ------------------------------------------------------------------------ + + total = 0; + load_dictionary_stats_entry(items.entries); + load_dictionary_stats_entry(items.referenced); + load_dictionary_stats_entry(items.pending_deletion); + + if(c->st_items || total != 0) { + if (unlikely(!c->st_items)) { + char id[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.items", context_prefix, stats.name); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.items", context_prefix); + + c->st_items = rrdset_create_localhost( + "netdata" + , id + , NULL + , family + , context + , "Dictionary Items" + , "items" + , "netdata" + , "stats" + , priority + 1 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + c->rd_items_entries = rrddim_add(c->st_items, "active", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + c->rd_items_pending_deletion = rrddim_add(c->st_items, "deleted", NULL, -1, 1, RRD_ALGORITHM_ABSOLUTE); + c->rd_items_referenced = rrddim_add(c->st_items, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + rrdlabels_add(c->st_items->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); + } + + rrddim_set_by_pointer(c->st_items, c->rd_items_entries, stats.items.entries); + rrddim_set_by_pointer(c->st_items, c->rd_items_pending_deletion, stats.items.pending_deletion); + rrddim_set_by_pointer(c->st_items, c->rd_items_referenced, stats.items.referenced); + rrdset_done(c->st_items); + } + + // ------------------------------------------------------------------------ + + total = 0; + load_dictionary_stats_entry(ops.creations); + load_dictionary_stats_entry(ops.destructions); + load_dictionary_stats_entry(ops.flushes); + load_dictionary_stats_entry(ops.traversals); + load_dictionary_stats_entry(ops.walkthroughs); + load_dictionary_stats_entry(ops.garbage_collections); + load_dictionary_stats_entry(ops.searches); + load_dictionary_stats_entry(ops.inserts); + load_dictionary_stats_entry(ops.resets); + load_dictionary_stats_entry(ops.deletes); + + if(c->st_ops || total != 0) { + if (unlikely(!c->st_ops)) { + char id[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.ops", context_prefix, stats.name); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.ops", context_prefix); + + c->st_ops = rrdset_create_localhost( + "netdata" + , id + , NULL + , family + , context + , "Dictionary Operations" + , "ops/s" + , "netdata" + , "stats" + , priority + 2 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + c->rd_ops_creations = rrddim_add(c->st_ops, "creations", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_destructions = rrddim_add(c->st_ops, "destructions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_flushes = rrddim_add(c->st_ops, "flushes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_traversals = rrddim_add(c->st_ops, "traversals", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_walkthroughs = rrddim_add(c->st_ops, "walkthroughs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_garbage_collections = rrddim_add(c->st_ops, "garbage_collections", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_searches = rrddim_add(c->st_ops, "searches", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_inserts = rrddim_add(c->st_ops, "inserts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_resets = rrddim_add(c->st_ops, "resets", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_ops_deletes = rrddim_add(c->st_ops, "deletes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + rrdlabels_add(c->st_ops->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); + } + + rrddim_set_by_pointer(c->st_ops, c->rd_ops_creations, (collected_number)stats.ops.creations); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_destructions, (collected_number)stats.ops.destructions); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_flushes, (collected_number)stats.ops.flushes); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_traversals, (collected_number)stats.ops.traversals); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_walkthroughs, (collected_number)stats.ops.walkthroughs); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_garbage_collections, (collected_number)stats.ops.garbage_collections); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_searches, (collected_number)stats.ops.searches); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_inserts, (collected_number)stats.ops.inserts); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_resets, (collected_number)stats.ops.resets); + rrddim_set_by_pointer(c->st_ops, c->rd_ops_deletes, (collected_number)stats.ops.deletes); + + rrdset_done(c->st_ops); + } + + // ------------------------------------------------------------------------ + + total = 0; + load_dictionary_stats_entry(callbacks.inserts); + load_dictionary_stats_entry(callbacks.conflicts); + load_dictionary_stats_entry(callbacks.reacts); + load_dictionary_stats_entry(callbacks.deletes); + + if(c->st_callbacks || total != 0) { + if (unlikely(!c->st_callbacks)) { + char id[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.callbacks", context_prefix, stats.name); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.callbacks", context_prefix); + + c->st_callbacks = rrdset_create_localhost( + "netdata" + , id + , NULL + , family + , context + , "Dictionary Callbacks" + , "callbacks/s" + , "netdata" + , "stats" + , priority + 3 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + c->rd_callbacks_inserts = rrddim_add(c->st_callbacks, "inserts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_callbacks_deletes = rrddim_add(c->st_callbacks, "deletes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_callbacks_conflicts = rrddim_add(c->st_callbacks, "conflicts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_callbacks_reacts = rrddim_add(c->st_callbacks, "reacts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + rrdlabels_add(c->st_callbacks->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); + } + + rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_inserts, (collected_number)stats.callbacks.inserts); + rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_conflicts, (collected_number)stats.callbacks.conflicts); + rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_reacts, (collected_number)stats.callbacks.reacts); + rrddim_set_by_pointer(c->st_callbacks, c->rd_callbacks_deletes, (collected_number)stats.callbacks.deletes); + + rrdset_done(c->st_callbacks); + } + + // ------------------------------------------------------------------------ + + total = 0; + load_dictionary_stats_entry(memory.index); + load_dictionary_stats_entry(memory.values); + load_dictionary_stats_entry(memory.dict); + + if(c->st_memory || total != 0) { + if (unlikely(!c->st_memory)) { + char id[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.memory", context_prefix, stats.name); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.memory", context_prefix); + + c->st_memory = rrdset_create_localhost( + "netdata" + , id + , NULL + , family + , context + , "Dictionary Memory" + , "bytes" + , "netdata" + , "stats" + , priority + 4 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + + c->rd_memory_indexed = rrddim_add(c->st_memory, "index", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + c->rd_memory_values = rrddim_add(c->st_memory, "data", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + c->rd_memory_dict = rrddim_add(c->st_memory, "structures", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + rrdlabels_add(c->st_memory->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); + } + + rrddim_set_by_pointer(c->st_memory, c->rd_memory_indexed, (collected_number)stats.memory.index); + rrddim_set_by_pointer(c->st_memory, c->rd_memory_values, (collected_number)stats.memory.values); + rrddim_set_by_pointer(c->st_memory, c->rd_memory_dict, (collected_number)stats.memory.dict); + + rrdset_done(c->st_memory); + } + + // ------------------------------------------------------------------------ + + total = 0; + load_dictionary_stats_entry(spin_locks.use_spins); + load_dictionary_stats_entry(spin_locks.search_spins); + load_dictionary_stats_entry(spin_locks.insert_spins); + load_dictionary_stats_entry(spin_locks.delete_spins); + + if(c->st_spins || total != 0) { + if (unlikely(!c->st_spins)) { + char id[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s.%s.spins", context_prefix, stats.name); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(context, RRD_ID_LENGTH_MAX, "netdata.%s.category.spins", context_prefix); + + c->st_spins = rrdset_create_localhost( + "netdata" + , id + , NULL + , family + , context + , "Dictionary Spins" + , "count" + , "netdata" + , "stats" + , priority + 5 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + c->rd_spins_use = rrddim_add(c->st_spins, "use", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_spins_search = rrddim_add(c->st_spins, "search", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_spins_insert = rrddim_add(c->st_spins, "insert", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + c->rd_spins_delete = rrddim_add(c->st_spins, "delete", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + rrdlabels_add(c->st_spins->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO); + } + + rrddim_set_by_pointer(c->st_spins, c->rd_spins_use, (collected_number)stats.spin_locks.use_spins); + rrddim_set_by_pointer(c->st_spins, c->rd_spins_search, (collected_number)stats.spin_locks.search_spins); + rrddim_set_by_pointer(c->st_spins, c->rd_spins_insert, (collected_number)stats.spin_locks.insert_spins); + rrddim_set_by_pointer(c->st_spins, c->rd_spins_delete, (collected_number)stats.spin_locks.delete_spins); + + rrdset_done(c->st_spins); + } +} + +void telemetry_dictionary_do(bool extended) { + if(!extended) return; + + for(int i = 0; dictionary_categories[i].stats ;i++) { + update_dictionary_category_charts(&dictionary_categories[i]); + } +} +#endif // DICT_WITH_STATS diff --git a/src/daemon/telemetry/telemetry-dictionary.h b/src/daemon/telemetry/telemetry-dictionary.h new file mode 100644 index 00000000000000..2e9ab8201bdba4 --- /dev/null +++ b/src/daemon/telemetry/telemetry-dictionary.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_DICTIONARY_H +#define NETDATA_TELEMETRY_DICTIONARY_H + +#include "daemon/common.h" + +extern struct dictionary_stats dictionary_stats_category_collectors; +extern struct dictionary_stats dictionary_stats_category_rrdhost; +extern struct dictionary_stats dictionary_stats_category_rrdset; +extern struct dictionary_stats dictionary_stats_category_rrddim; +extern struct dictionary_stats dictionary_stats_category_rrdcontext; +extern struct dictionary_stats dictionary_stats_category_rrdlabels; +extern struct dictionary_stats dictionary_stats_category_rrdhealth; +extern struct dictionary_stats dictionary_stats_category_functions; +extern struct dictionary_stats dictionary_stats_category_replication; + +extern size_t rrddim_db_memory_size; + +#if defined(TELEMETRY_INTERNALS) +void telemetry_dictionary_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_DICTIONARY_H diff --git a/src/daemon/telemetry/telemetry-gorilla.c b/src/daemon/telemetry/telemetry-gorilla.c new file mode 100644 index 00000000000000..441dcffe504faf --- /dev/null +++ b/src/daemon/telemetry/telemetry-gorilla.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-gorilla.h" + +static struct gorilla_statistics { + bool enabled; + + alignas(64) uint64_t tier0_hot_gorilla_buffers; + + alignas(64) uint64_t gorilla_tier0_disk_actual_bytes; + alignas(64) uint64_t gorilla_tier0_disk_optimal_bytes; + alignas(64) uint64_t gorilla_tier0_disk_original_bytes; +} gorilla_statistics = { 0 }; + +void telemetry_gorilla_hot_buffer_added() { + if(!gorilla_statistics.enabled) return; + + __atomic_fetch_add(&gorilla_statistics.tier0_hot_gorilla_buffers, 1, __ATOMIC_RELAXED); +} + +void telemetry_gorilla_tier0_page_flush(uint32_t actual, uint32_t optimal, uint32_t original) { + if(!gorilla_statistics.enabled) return; + + __atomic_fetch_add(&gorilla_statistics.gorilla_tier0_disk_actual_bytes, actual, __ATOMIC_RELAXED); + __atomic_fetch_add(&gorilla_statistics.gorilla_tier0_disk_optimal_bytes, optimal, __ATOMIC_RELAXED); + __atomic_fetch_add(&gorilla_statistics.gorilla_tier0_disk_original_bytes, original, __ATOMIC_RELAXED); +} + +static inline void global_statistics_copy(struct gorilla_statistics *gs) { + gs->tier0_hot_gorilla_buffers = __atomic_load_n(&gorilla_statistics.tier0_hot_gorilla_buffers, __ATOMIC_RELAXED); + gs->gorilla_tier0_disk_actual_bytes = __atomic_load_n(&gorilla_statistics.gorilla_tier0_disk_actual_bytes, __ATOMIC_RELAXED); + gs->gorilla_tier0_disk_optimal_bytes = __atomic_load_n(&gorilla_statistics.gorilla_tier0_disk_optimal_bytes, __ATOMIC_RELAXED); + gs->gorilla_tier0_disk_original_bytes = __atomic_load_n(&gorilla_statistics.gorilla_tier0_disk_original_bytes, __ATOMIC_RELAXED); +} + +void telemetry_gorilla_do(bool extended __maybe_unused) { +#ifdef ENABLE_DBENGINE + if(!extended) return; + gorilla_statistics.enabled = true; + + struct gorilla_statistics gs; + global_statistics_copy(&gs); + + if (tier_page_type[0] == RRDENG_PAGE_TYPE_GORILLA_32BIT) + { + static RRDSET *st_tier0_gorilla_pages = NULL; + static RRDDIM *rd_num_gorilla_pages = NULL; + + if (unlikely(!st_tier0_gorilla_pages)) { + st_tier0_gorilla_pages = rrdset_create_localhost( + "netdata" + , "tier0_gorilla_pages" + , NULL + , "dbengine gorilla" + , NULL + , "Number of gorilla_pages" + , "count" + , "netdata" + , "stats" + , 131004 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_num_gorilla_pages = rrddim_add(st_tier0_gorilla_pages, "count", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_tier0_gorilla_pages, rd_num_gorilla_pages, (collected_number)gs.tier0_hot_gorilla_buffers); + + rrdset_done(st_tier0_gorilla_pages); + } + + if (tier_page_type[0] == RRDENG_PAGE_TYPE_GORILLA_32BIT) + { + static RRDSET *st_tier0_compression_info = NULL; + + static RRDDIM *rd_actual_bytes = NULL; + static RRDDIM *rd_optimal_bytes = NULL; + static RRDDIM *rd_uncompressed_bytes = NULL; + + if (unlikely(!st_tier0_compression_info)) { + st_tier0_compression_info = rrdset_create_localhost( + "netdata" + , "tier0_gorilla_efficiency" + , NULL + , "dbengine gorilla" + , NULL + , "DBENGINE Gorilla Compression Efficiency on Tier 0" + , "bytes" + , "netdata" + , "stats" + , 131005 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_actual_bytes = rrddim_add(st_tier0_compression_info, "actual", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_optimal_bytes = rrddim_add(st_tier0_compression_info, "optimal", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_uncompressed_bytes = rrddim_add(st_tier0_compression_info, "uncompressed", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(st_tier0_compression_info, rd_actual_bytes, (collected_number)gs.gorilla_tier0_disk_actual_bytes); + rrddim_set_by_pointer(st_tier0_compression_info, rd_optimal_bytes, (collected_number)gs.gorilla_tier0_disk_optimal_bytes); + rrddim_set_by_pointer(st_tier0_compression_info, rd_uncompressed_bytes, (collected_number)gs.gorilla_tier0_disk_original_bytes); + + rrdset_done(st_tier0_compression_info); + } +#endif +} diff --git a/src/daemon/telemetry/telemetry-gorilla.h b/src/daemon/telemetry/telemetry-gorilla.h new file mode 100644 index 00000000000000..845e2c42868d7c --- /dev/null +++ b/src/daemon/telemetry/telemetry-gorilla.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_GORILLA_H +#define NETDATA_TELEMETRY_GORILLA_H + +#include "daemon/common.h" + +void telemetry_gorilla_hot_buffer_added(); +void telemetry_gorilla_tier0_page_flush(uint32_t actual, uint32_t optimal, uint32_t original); + +#if defined(TELEMETRY_INTERNALS) +void telemetry_gorilla_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_GORILLA_H diff --git a/src/daemon/telemetry/telemetry-heartbeat.c b/src/daemon/telemetry/telemetry-heartbeat.c new file mode 100644 index 00000000000000..c66c9a04065ab9 --- /dev/null +++ b/src/daemon/telemetry/telemetry-heartbeat.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-heartbeat.h" + +void telemetry_heartbeat_do(bool extended) { + if(!extended) return; + + static RRDSET *st_heartbeat = NULL; + static RRDDIM *rd_heartbeat_min = NULL; + static RRDDIM *rd_heartbeat_max = NULL; + static RRDDIM *rd_heartbeat_avg = NULL; + + if (unlikely(!st_heartbeat)) { + st_heartbeat = rrdset_create_localhost( + "netdata" + , "heartbeat" + , NULL + , "heartbeat" + , NULL + , "System clock jitter" + , "microseconds" + , "netdata" + , "stats" + , 900000 + , localhost->rrd_update_every + , RRDSET_TYPE_AREA); + + rd_heartbeat_min = rrddim_add(st_heartbeat, "min", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_heartbeat_max = rrddim_add(st_heartbeat, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rd_heartbeat_avg = rrddim_add(st_heartbeat, "average", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + usec_t min, max, average; + size_t count; + + heartbeat_statistics(&min, &max, &average, &count); + + rrddim_set_by_pointer(st_heartbeat, rd_heartbeat_min, (collected_number)min); + rrddim_set_by_pointer(st_heartbeat, rd_heartbeat_max, (collected_number)max); + rrddim_set_by_pointer(st_heartbeat, rd_heartbeat_avg, (collected_number)average); + + rrdset_done(st_heartbeat); +} diff --git a/src/daemon/telemetry/telemetry-heartbeat.h b/src/daemon/telemetry/telemetry-heartbeat.h new file mode 100644 index 00000000000000..c8a021a7ee7243 --- /dev/null +++ b/src/daemon/telemetry/telemetry-heartbeat.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_HEARTBEAT_H +#define NETDATA_TELEMETRY_HEARTBEAT_H + +#include "daemon/common.h" + +#if defined(TELEMETRY_INTERNALS) +void telemetry_heartbeat_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_HEARTBEAT_H diff --git a/src/daemon/telemetry/telemetry-http-api.c b/src/daemon/telemetry/telemetry-http-api.c new file mode 100644 index 00000000000000..4211050df4c6a7 --- /dev/null +++ b/src/daemon/telemetry/telemetry-http-api.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-http-api.h" + +#define GLOBAL_STATS_RESET_WEB_USEC_MAX 0x01 + +static struct web_statistics { + bool extended; + + uint16_t connected_clients; + uint64_t web_client_count; // oops! this is used for giving unique IDs to web_clients! + + uint64_t web_requests; + uint64_t web_usec; + uint64_t web_usec_max; + uint64_t bytes_received; + uint64_t bytes_sent; + + uint64_t content_size_uncompressed; + uint64_t content_size_compressed; +} web_statistics; + +uint64_t telemetry_web_client_connected(void) { + __atomic_fetch_add(&web_statistics.connected_clients, 1, __ATOMIC_RELAXED); + return __atomic_fetch_add(&web_statistics.web_client_count, 1, __ATOMIC_RELAXED); +} + +void telemetry_web_client_disconnected(void) { + __atomic_fetch_sub(&web_statistics.connected_clients, 1, __ATOMIC_RELAXED); +} + +void telemetry_web_request_completed(uint64_t dt, + uint64_t bytes_received, + uint64_t bytes_sent, + uint64_t content_size, + uint64_t compressed_content_size) { + uint64_t old_web_usec_max = web_statistics.web_usec_max; + while(dt > old_web_usec_max) + __atomic_compare_exchange(&web_statistics.web_usec_max, &old_web_usec_max, &dt, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + + __atomic_fetch_add(&web_statistics.web_requests, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&web_statistics.web_usec, dt, __ATOMIC_RELAXED); + __atomic_fetch_add(&web_statistics.bytes_received, bytes_received, __ATOMIC_RELAXED); + __atomic_fetch_add(&web_statistics.bytes_sent, bytes_sent, __ATOMIC_RELAXED); + __atomic_fetch_add(&web_statistics.content_size_uncompressed, content_size, __ATOMIC_RELAXED); + __atomic_fetch_add(&web_statistics.content_size_compressed, compressed_content_size, __ATOMIC_RELAXED); +} + +static inline void telemetry_web_copy(struct web_statistics *gs, uint8_t options) { + gs->connected_clients = __atomic_load_n(&web_statistics.connected_clients, __ATOMIC_RELAXED); + gs->web_requests = __atomic_load_n(&web_statistics.web_requests, __ATOMIC_RELAXED); + gs->web_usec = __atomic_load_n(&web_statistics.web_usec, __ATOMIC_RELAXED); + gs->web_usec_max = __atomic_load_n(&web_statistics.web_usec_max, __ATOMIC_RELAXED); + gs->bytes_received = __atomic_load_n(&web_statistics.bytes_received, __ATOMIC_RELAXED); + gs->bytes_sent = __atomic_load_n(&web_statistics.bytes_sent, __ATOMIC_RELAXED); + gs->content_size_uncompressed = __atomic_load_n(&web_statistics.content_size_uncompressed, __ATOMIC_RELAXED); + gs->content_size_compressed = __atomic_load_n(&web_statistics.content_size_compressed, __ATOMIC_RELAXED); + gs->web_client_count = __atomic_load_n(&web_statistics.web_client_count, __ATOMIC_RELAXED); + + if(options & GLOBAL_STATS_RESET_WEB_USEC_MAX) { + uint64_t n = 0; + __atomic_compare_exchange(&web_statistics.web_usec_max, (uint64_t *) &gs->web_usec_max, &n, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + } +} + +void telemetry_web_do(bool extended) { + static struct web_statistics gs; + telemetry_web_copy(&gs, GLOBAL_STATS_RESET_WEB_USEC_MAX); + + // ---------------------------------------------------------------- + + { + static RRDSET *st_clients = NULL; + static RRDDIM *rd_clients = NULL; + + if (unlikely(!st_clients)) { + st_clients = rrdset_create_localhost( + "netdata" + , "clients" + , NULL + , "HTTP API" + , NULL + , "Netdata Web API Clients" + , "connected clients" + , "netdata" + , "stats" + , 130200 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_clients = rrddim_add(st_clients, "clients", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(st_clients, rd_clients, gs.connected_clients); + rrdset_done(st_clients); + } + + // ---------------------------------------------------------------- + + { + static RRDSET *st_reqs = NULL; + static RRDDIM *rd_requests = NULL; + + if (unlikely(!st_reqs)) { + st_reqs = rrdset_create_localhost( + "netdata" + , "requests" + , NULL + , "HTTP API" + , NULL + , "Netdata Web API Requests Received" + , "requests/s" + , "netdata" + , "stats" + , 130300 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_requests = rrddim_add(st_reqs, "requests", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_reqs, rd_requests, (collected_number) gs.web_requests); + rrdset_done(st_reqs); + } + + // ---------------------------------------------------------------- + + { + static RRDSET *st_bytes = NULL; + static RRDDIM *rd_in = NULL, + *rd_out = NULL; + + if (unlikely(!st_bytes)) { + st_bytes = rrdset_create_localhost( + "netdata" + , "net" + , NULL + , "HTTP API" + , NULL + , "Netdata Web API Network Traffic" + , "kilobits/s" + , "netdata" + , "stats" + , 130400 + , localhost->rrd_update_every + , RRDSET_TYPE_AREA + ); + + rd_in = rrddim_add(st_bytes, "in", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL); + rd_out = rrddim_add(st_bytes, "out", NULL, -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_bytes, rd_in, (collected_number) gs.bytes_received); + rrddim_set_by_pointer(st_bytes, rd_out, (collected_number) gs.bytes_sent); + rrdset_done(st_bytes); + } + + // ---------------------------------------------------------------- + + { + static unsigned long long old_web_requests = 0, old_web_usec = 0; + static collected_number average_response_time = -1; + + static RRDSET *st_duration = NULL; + static RRDDIM *rd_average = NULL, + *rd_max = NULL; + + if (unlikely(!st_duration)) { + st_duration = rrdset_create_localhost( + "netdata" + , "response_time" + , NULL + , "HTTP API" + , NULL + , "Netdata Web API Response Time" + , "milliseconds/request" + , "netdata" + , "stats" + , 130500 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_average = rrddim_add(st_duration, "average", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + rd_max = rrddim_add(st_duration, "max", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + } + + uint64_t gweb_usec = gs.web_usec; + uint64_t gweb_requests = gs.web_requests; + + uint64_t web_usec = (gweb_usec >= old_web_usec) ? gweb_usec - old_web_usec : 0; + uint64_t web_requests = (gweb_requests >= old_web_requests) ? gweb_requests - old_web_requests : 0; + + old_web_usec = gweb_usec; + old_web_requests = gweb_requests; + + if (web_requests) + average_response_time = (collected_number) (web_usec / web_requests); + + if (unlikely(average_response_time != -1)) + rrddim_set_by_pointer(st_duration, rd_average, average_response_time); + else + rrddim_set_by_pointer(st_duration, rd_average, 0); + + rrddim_set_by_pointer(st_duration, rd_max, ((gs.web_usec_max)?(collected_number)gs.web_usec_max:average_response_time)); + rrdset_done(st_duration); + } + + // ---------------------------------------------------------------- + + if(!extended) return; + + // ---------------------------------------------------------------- + + { + static unsigned long long old_content_size = 0, old_compressed_content_size = 0; + static collected_number compression_ratio = -1; + + static RRDSET *st_compression = NULL; + static RRDDIM *rd_savings = NULL; + + if (unlikely(!st_compression)) { + st_compression = rrdset_create_localhost( + "netdata" + , "compression_ratio" + , NULL + , "HTTP API" + , NULL + , "Netdata Web API Responses Compression Savings Ratio" + , "percentage" + , "netdata" + , "stats" + , 130600 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_savings = rrddim_add(st_compression, "savings", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + } + + // since we don't lock here to read the telemetry + // read the smaller value first + unsigned long long gcompressed_content_size = gs.content_size_compressed; + unsigned long long gcontent_size = gs.content_size_uncompressed; + + unsigned long long compressed_content_size = gcompressed_content_size - old_compressed_content_size; + unsigned long long content_size = gcontent_size - old_content_size; + + old_compressed_content_size = gcompressed_content_size; + old_content_size = gcontent_size; + + if (content_size && content_size >= compressed_content_size) + compression_ratio = ((content_size - compressed_content_size) * 100 * 1000) / content_size; + + if (compression_ratio != -1) + rrddim_set_by_pointer(st_compression, rd_savings, compression_ratio); + + rrdset_done(st_compression); + } +} \ No newline at end of file diff --git a/src/daemon/telemetry/telemetry-http-api.h b/src/daemon/telemetry/telemetry-http-api.h new file mode 100644 index 00000000000000..2b1ad38d1259a9 --- /dev/null +++ b/src/daemon/telemetry/telemetry-http-api.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_HTTP_API_H +#define NETDATA_TELEMETRY_HTTP_API_H + +#include "daemon/common.h" + +uint64_t telemetry_web_client_connected(void); +void telemetry_web_client_disconnected(void); + +void telemetry_web_request_completed(uint64_t dt, + uint64_t bytes_received, + uint64_t bytes_sent, + uint64_t content_size, + uint64_t compressed_content_size); + +#if defined(TELEMETRY_INTERNALS) +void telemetry_web_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_HTTP_API_H diff --git a/src/daemon/telemetry/telemetry-ingestion.c b/src/daemon/telemetry/telemetry-ingestion.c new file mode 100644 index 00000000000000..d1b2d03ec351b1 --- /dev/null +++ b/src/daemon/telemetry/telemetry-ingestion.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-ingestion.h" + +static struct ingest_statistics { + uint64_t db_points_stored_per_tier[RRD_STORAGE_TIERS]; +} ingest_statistics; + +void telemetry_queries_rrdset_collection_completed(size_t *points_read_per_tier_array) { + for(size_t tier = 0; tier < storage_tiers ;tier++) { + __atomic_fetch_add(&ingest_statistics.db_points_stored_per_tier[tier], points_read_per_tier_array[tier], __ATOMIC_RELAXED); + points_read_per_tier_array[tier] = 0; + } +} + +static inline void telemetry_ingestion_copy(struct ingest_statistics *gs) { + for(size_t tier = 0; tier < storage_tiers ;tier++) + gs->db_points_stored_per_tier[tier] = __atomic_load_n(&ingest_statistics.db_points_stored_per_tier[tier], __ATOMIC_RELAXED); +} + +void telemetry_ingestion_do(bool extended __maybe_unused) { + static struct ingest_statistics gs; + telemetry_ingestion_copy(&gs); + + { + static RRDSET *st_points_stored = NULL; + static RRDDIM *rds[RRD_STORAGE_TIERS] = {}; + + if (unlikely(!st_points_stored)) { + st_points_stored = rrdset_create_localhost( + "netdata" + , "db_samples_collected" + , NULL + , "Data Collection Samples" + , NULL + , "Netdata Time-Series Collected Samples" + , "samples/s" + , "netdata" + , "stats" + , 131003 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + + for(size_t tier = 0; tier < storage_tiers ;tier++) { + char buf[30 + 1]; + snprintfz(buf, sizeof(buf) - 1, "tier%zu", tier); + rds[tier] = rrddim_add(st_points_stored, buf, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + } + + for(size_t tier = 0; tier < storage_tiers ;tier++) + rrddim_set_by_pointer(st_points_stored, rds[tier], (collected_number)gs.db_points_stored_per_tier[tier]); + + rrdset_done(st_points_stored); + } +} diff --git a/src/daemon/telemetry/telemetry-ingestion.h b/src/daemon/telemetry/telemetry-ingestion.h new file mode 100644 index 00000000000000..ab72ea0e8a0c4e --- /dev/null +++ b/src/daemon/telemetry/telemetry-ingestion.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_INGESTION_H +#define NETDATA_TELEMETRY_INGESTION_H + +#include "daemon/common.h" + +void telemetry_queries_rrdset_collection_completed(size_t *points_read_per_tier_array); + +#if defined(TELEMETRY_INTERNALS) +void telemetry_ingestion_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_INGESTION_H diff --git a/src/daemon/telemetry/telemetry-ml.c b/src/daemon/telemetry/telemetry-ml.c new file mode 100644 index 00000000000000..e127850a90fd3c --- /dev/null +++ b/src/daemon/telemetry/telemetry-ml.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-ml.h" + +static struct ml_statistics { + alignas(64) uint64_t ml_models_consulted; + alignas(64) uint64_t ml_models_received; + alignas(64) uint64_t ml_models_ignored; + alignas(64) uint64_t ml_models_sent; + alignas(64) uint64_t ml_models_deserialization_failures; + alignas(64) uint64_t ml_memory_consumption; + alignas(64) uint64_t ml_memory_new; + alignas(64) uint64_t ml_memory_delete; +} ml_statistics = {0}; + +void telemetry_ml_models_received() +{ + __atomic_fetch_add(&ml_statistics.ml_models_received, 1, __ATOMIC_RELAXED); +} + +void telemetry_ml_models_ignored() +{ + __atomic_fetch_add(&ml_statistics.ml_models_ignored, 1, __ATOMIC_RELAXED); +} + +void telemetry_ml_models_sent() +{ + __atomic_fetch_add(&ml_statistics.ml_models_sent, 1, __ATOMIC_RELAXED); +} + +void global_statistics_ml_models_deserialization_failures() +{ + __atomic_fetch_add(&ml_statistics.ml_models_deserialization_failures, 1, __ATOMIC_RELAXED); +} + +void telemetry_ml_models_consulted(size_t models_consulted) +{ + __atomic_fetch_add(&ml_statistics.ml_models_consulted, models_consulted, __ATOMIC_RELAXED); +} + +void telemetry_ml_memory_allocated(size_t n) +{ + __atomic_fetch_add(&ml_statistics.ml_memory_consumption, n, __ATOMIC_RELAXED); + __atomic_fetch_add(&ml_statistics.ml_memory_new, 1, __ATOMIC_RELAXED); +} + +void telemetry_ml_memory_freed(size_t n) +{ + __atomic_fetch_sub(&ml_statistics.ml_memory_consumption, n, __ATOMIC_RELAXED); + __atomic_fetch_add(&ml_statistics.ml_memory_delete, 1, __ATOMIC_RELAXED); +} + +uint64_t telemetry_ml_get_current_memory_usage(void) { + return __atomic_load_n(&ml_statistics.ml_memory_consumption, __ATOMIC_RELAXED); +} + +static inline void ml_statistics_copy(struct ml_statistics *gs) +{ + gs->ml_models_consulted = __atomic_load_n(&ml_statistics.ml_models_consulted, __ATOMIC_RELAXED); + gs->ml_models_received = __atomic_load_n(&ml_statistics.ml_models_received, __ATOMIC_RELAXED); + gs->ml_models_sent = __atomic_load_n(&ml_statistics.ml_models_sent, __ATOMIC_RELAXED); + gs->ml_models_ignored = __atomic_load_n(&ml_statistics.ml_models_ignored, __ATOMIC_RELAXED); + gs->ml_models_deserialization_failures = + __atomic_load_n(&ml_statistics.ml_models_deserialization_failures, __ATOMIC_RELAXED); + + gs->ml_memory_consumption = __atomic_load_n(&ml_statistics.ml_memory_consumption, __ATOMIC_RELAXED); + gs->ml_memory_new = __atomic_load_n(&ml_statistics.ml_memory_new, __ATOMIC_RELAXED); + gs->ml_memory_delete = __atomic_load_n(&ml_statistics.ml_memory_delete, __ATOMIC_RELAXED); +} + +void telemetry_ml_do(bool extended) +{ + if (!extended) + return; + + struct ml_statistics gs; + ml_statistics_copy(&gs); + + ml_update_global_statistics_charts( + gs.ml_models_consulted, + gs.ml_models_received, + gs.ml_models_sent, + gs.ml_models_ignored, + gs.ml_models_deserialization_failures, + gs.ml_memory_consumption, + gs.ml_memory_new, + gs.ml_memory_delete); +} diff --git a/src/daemon/telemetry/telemetry-ml.h b/src/daemon/telemetry/telemetry-ml.h new file mode 100644 index 00000000000000..ff992cbe2ffdf3 --- /dev/null +++ b/src/daemon/telemetry/telemetry-ml.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_ML_H +#define NETDATA_TELEMETRY_ML_H + +#include "daemon/common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void telemetry_ml_models_consulted(size_t models_consulted); +void telemetry_ml_models_received(); +void telemetry_ml_models_ignored(); +void telemetry_ml_models_sent(); + +void telemetry_ml_memory_allocated(size_t n); +void telemetry_ml_memory_freed(size_t n); + +void global_statistics_ml_models_deserialization_failures(); + +uint64_t telemetry_ml_get_current_memory_usage(void); + +#if defined(TELEMETRY_INTERNALS) +void telemetry_ml_do(bool extended); +#endif + +#ifdef __cplusplus +} +#endif + + +#endif //NETDATA_TELEMETRY_ML_H diff --git a/src/daemon/telemetry/telemetry-queries.c b/src/daemon/telemetry/telemetry-queries.c new file mode 100644 index 00000000000000..c4ec819c65854f --- /dev/null +++ b/src/daemon/telemetry/telemetry-queries.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-queries.h" + +static struct query_statistics { + uint64_t api_data_queries_made; + uint64_t api_data_db_points_read; + uint64_t api_data_result_points_generated; + + uint64_t api_weights_queries_made; + uint64_t api_weights_db_points_read; + uint64_t api_weights_result_points_generated; + + uint64_t api_badges_queries_made; + uint64_t api_badges_db_points_read; + uint64_t api_badges_result_points_generated; + + uint64_t health_queries_made; + uint64_t health_db_points_read; + uint64_t health_result_points_generated; + + uint64_t ml_queries_made; + uint64_t ml_db_points_read; + uint64_t ml_result_points_generated; + + uint64_t backfill_queries_made; + uint64_t backfill_db_points_read; + + uint64_t exporters_queries_made; + uint64_t exporters_db_points_read; +} query_statistics; + +void telemetry_queries_ml_query_completed(size_t points_read) { + __atomic_fetch_add(&query_statistics.ml_queries_made, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.ml_db_points_read, points_read, __ATOMIC_RELAXED); +} + +void telemetry_queries_exporters_query_completed(size_t points_read) { + __atomic_fetch_add(&query_statistics.exporters_queries_made, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.exporters_db_points_read, points_read, __ATOMIC_RELAXED); +} + +void telemetry_queries_backfill_query_completed(size_t points_read) { + __atomic_fetch_add(&query_statistics.backfill_queries_made, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.backfill_db_points_read, points_read, __ATOMIC_RELAXED); +} + +void telemetry_queries_rrdr_query_completed(size_t queries, uint64_t db_points_read, uint64_t result_points_generated, QUERY_SOURCE query_source) { + switch(query_source) { + case QUERY_SOURCE_API_DATA: + __atomic_fetch_add(&query_statistics.api_data_queries_made, queries, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.api_data_db_points_read, db_points_read, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.api_data_result_points_generated, result_points_generated, __ATOMIC_RELAXED); + break; + + case QUERY_SOURCE_ML: + __atomic_fetch_add(&query_statistics.ml_queries_made, queries, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.ml_db_points_read, db_points_read, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.ml_result_points_generated, result_points_generated, __ATOMIC_RELAXED); + break; + + case QUERY_SOURCE_API_WEIGHTS: + __atomic_fetch_add(&query_statistics.api_weights_queries_made, queries, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.api_weights_db_points_read, db_points_read, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.api_weights_result_points_generated, result_points_generated, __ATOMIC_RELAXED); + break; + + case QUERY_SOURCE_API_BADGE: + __atomic_fetch_add(&query_statistics.api_badges_queries_made, queries, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.api_badges_db_points_read, db_points_read, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.api_badges_result_points_generated, result_points_generated, __ATOMIC_RELAXED); + break; + + case QUERY_SOURCE_HEALTH: + __atomic_fetch_add(&query_statistics.health_queries_made, queries, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.health_db_points_read, db_points_read, __ATOMIC_RELAXED); + __atomic_fetch_add(&query_statistics.health_result_points_generated, result_points_generated, __ATOMIC_RELAXED); + break; + + default: + case QUERY_SOURCE_UNITTEST: + case QUERY_SOURCE_UNKNOWN: + break; + } +} + +static inline void telemetry_queries_copy(struct query_statistics *gs) { + gs->api_data_queries_made = __atomic_load_n(&query_statistics.api_data_queries_made, __ATOMIC_RELAXED); + gs->api_data_db_points_read = __atomic_load_n(&query_statistics.api_data_db_points_read, __ATOMIC_RELAXED); + gs->api_data_result_points_generated = __atomic_load_n(&query_statistics.api_data_result_points_generated, __ATOMIC_RELAXED); + + gs->api_weights_queries_made = __atomic_load_n(&query_statistics.api_weights_queries_made, __ATOMIC_RELAXED); + gs->api_weights_db_points_read = __atomic_load_n(&query_statistics.api_weights_db_points_read, __ATOMIC_RELAXED); + gs->api_weights_result_points_generated = __atomic_load_n(&query_statistics.api_weights_result_points_generated, __ATOMIC_RELAXED); + + gs->api_badges_queries_made = __atomic_load_n(&query_statistics.api_badges_queries_made, __ATOMIC_RELAXED); + gs->api_badges_db_points_read = __atomic_load_n(&query_statistics.api_badges_db_points_read, __ATOMIC_RELAXED); + gs->api_badges_result_points_generated = __atomic_load_n(&query_statistics.api_badges_result_points_generated, __ATOMIC_RELAXED); + + gs->health_queries_made = __atomic_load_n(&query_statistics.health_queries_made, __ATOMIC_RELAXED); + gs->health_db_points_read = __atomic_load_n(&query_statistics.health_db_points_read, __ATOMIC_RELAXED); + gs->health_result_points_generated = __atomic_load_n(&query_statistics.health_result_points_generated, __ATOMIC_RELAXED); + + gs->ml_queries_made = __atomic_load_n(&query_statistics.ml_queries_made, __ATOMIC_RELAXED); + gs->ml_db_points_read = __atomic_load_n(&query_statistics.ml_db_points_read, __ATOMIC_RELAXED); + gs->ml_result_points_generated = __atomic_load_n(&query_statistics.ml_result_points_generated, __ATOMIC_RELAXED); + + gs->exporters_queries_made = __atomic_load_n(&query_statistics.exporters_queries_made, __ATOMIC_RELAXED); + gs->exporters_db_points_read = __atomic_load_n(&query_statistics.exporters_db_points_read, __ATOMIC_RELAXED); + gs->backfill_queries_made = __atomic_load_n(&query_statistics.backfill_queries_made, __ATOMIC_RELAXED); + gs->backfill_db_points_read = __atomic_load_n(&query_statistics.backfill_db_points_read, __ATOMIC_RELAXED); +} + +void telemetry_queries_do(bool extended __maybe_unused) { + static struct query_statistics gs; + telemetry_queries_copy(&gs); + + struct replication_query_statistics replication = replication_get_query_statistics(); + + { + static RRDSET *st_queries = NULL; + static RRDDIM *rd_api_data_queries = NULL; + static RRDDIM *rd_api_weights_queries = NULL; + static RRDDIM *rd_api_badges_queries = NULL; + static RRDDIM *rd_health_queries = NULL; + static RRDDIM *rd_ml_queries = NULL; + static RRDDIM *rd_exporters_queries = NULL; + static RRDDIM *rd_backfill_queries = NULL; + static RRDDIM *rd_replication_queries = NULL; + + if (unlikely(!st_queries)) { + st_queries = rrdset_create_localhost( + "netdata" + , "queries" + , NULL + , "Time-Series Queries" + , NULL + , "Netdata Time-Series DB Queries" + , "queries/s" + , "netdata" + , "stats" + , 131000 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + + rd_api_data_queries = rrddim_add(st_queries, "/api/vX/data", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_api_weights_queries = rrddim_add(st_queries, "/api/vX/weights", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_api_badges_queries = rrddim_add(st_queries, "/api/vX/badge", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_health_queries = rrddim_add(st_queries, "health", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_ml_queries = rrddim_add(st_queries, "ml", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_exporters_queries = rrddim_add(st_queries, "exporters", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_backfill_queries = rrddim_add(st_queries, "backfill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_replication_queries = rrddim_add(st_queries, "replication", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_queries, rd_api_data_queries, (collected_number)gs.api_data_queries_made); + rrddim_set_by_pointer(st_queries, rd_api_weights_queries, (collected_number)gs.api_weights_queries_made); + rrddim_set_by_pointer(st_queries, rd_api_badges_queries, (collected_number)gs.api_badges_queries_made); + rrddim_set_by_pointer(st_queries, rd_health_queries, (collected_number)gs.health_queries_made); + rrddim_set_by_pointer(st_queries, rd_ml_queries, (collected_number)gs.ml_queries_made); + rrddim_set_by_pointer(st_queries, rd_exporters_queries, (collected_number)gs.exporters_queries_made); + rrddim_set_by_pointer(st_queries, rd_backfill_queries, (collected_number)gs.backfill_queries_made); + rrddim_set_by_pointer(st_queries, rd_replication_queries, (collected_number)replication.queries_finished); + + rrdset_done(st_queries); + } + + { + static RRDSET *st_points_read = NULL; + static RRDDIM *rd_api_data_points_read = NULL; + static RRDDIM *rd_api_weights_points_read = NULL; + static RRDDIM *rd_api_badges_points_read = NULL; + static RRDDIM *rd_health_points_read = NULL; + static RRDDIM *rd_ml_points_read = NULL; + static RRDDIM *rd_exporters_points_read = NULL; + static RRDDIM *rd_backfill_points_read = NULL; + static RRDDIM *rd_replication_points_read = NULL; + + if (unlikely(!st_points_read)) { + st_points_read = rrdset_create_localhost( + "netdata" + , "db_points_read" + , NULL + , "Time-Series Queries" + , NULL + , "Netdata Time-Series DB Samples Read" + , "points/s" + , "netdata" + , "stats" + , 131001 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + + rd_api_data_points_read = rrddim_add(st_points_read, "/api/vX/data", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_api_weights_points_read = rrddim_add(st_points_read, "/api/vX/weights", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_api_badges_points_read = rrddim_add(st_points_read, "/api/vX/badge", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_health_points_read = rrddim_add(st_points_read, "health", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_ml_points_read = rrddim_add(st_points_read, "ml", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_exporters_points_read = rrddim_add(st_points_read, "exporters", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_backfill_points_read = rrddim_add(st_points_read, "backfill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_replication_points_read = rrddim_add(st_points_read, "replication", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_points_read, rd_api_data_points_read, (collected_number)gs.api_data_db_points_read); + rrddim_set_by_pointer(st_points_read, rd_api_weights_points_read, (collected_number)gs.api_weights_db_points_read); + rrddim_set_by_pointer(st_points_read, rd_api_badges_points_read, (collected_number)gs.api_badges_db_points_read); + rrddim_set_by_pointer(st_points_read, rd_health_points_read, (collected_number)gs.health_db_points_read); + rrddim_set_by_pointer(st_points_read, rd_ml_points_read, (collected_number)gs.ml_db_points_read); + rrddim_set_by_pointer(st_points_read, rd_exporters_points_read, (collected_number)gs.exporters_db_points_read); + rrddim_set_by_pointer(st_points_read, rd_backfill_points_read, (collected_number)gs.backfill_db_points_read); + rrddim_set_by_pointer(st_points_read, rd_replication_points_read, (collected_number)replication.points_read); + + rrdset_done(st_points_read); + } + + if(gs.api_data_result_points_generated || replication.points_generated) { + static RRDSET *st_points_generated = NULL; + static RRDDIM *rd_api_data_points_generated = NULL; + static RRDDIM *rd_api_weights_points_generated = NULL; + static RRDDIM *rd_api_badges_points_generated = NULL; + static RRDDIM *rd_health_points_generated = NULL; + static RRDDIM *rd_ml_points_generated = NULL; + static RRDDIM *rd_replication_points_generated = NULL; + + if (unlikely(!st_points_generated)) { + st_points_generated = rrdset_create_localhost( + "netdata" + , "db_points_results" + , NULL + , "Time-Series Queries" + , NULL + , "Netdata Time-Series Samples Generated" + , "points/s" + , "netdata" + , "stats" + , 131002 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + + rd_api_data_points_generated = rrddim_add(st_points_generated, "/api/vX/data", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_api_weights_points_generated = rrddim_add(st_points_generated, "/api/vX/weights", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_api_badges_points_generated = rrddim_add(st_points_generated, "/api/vX/badge", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_health_points_generated = rrddim_add(st_points_generated, "health", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_ml_points_generated = rrddim_add(st_points_generated, "ml", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_replication_points_generated = rrddim_add(st_points_generated, "replication", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_points_generated, rd_api_data_points_generated, (collected_number)gs.api_data_result_points_generated); + rrddim_set_by_pointer(st_points_generated, rd_api_weights_points_generated, (collected_number)gs.api_weights_result_points_generated); + rrddim_set_by_pointer(st_points_generated, rd_api_badges_points_generated, (collected_number)gs.api_badges_result_points_generated); + rrddim_set_by_pointer(st_points_generated, rd_health_points_generated, (collected_number)gs.health_result_points_generated); + rrddim_set_by_pointer(st_points_generated, rd_ml_points_generated, (collected_number)gs.ml_result_points_generated); + rrddim_set_by_pointer(st_points_generated, rd_replication_points_generated, (collected_number)replication.points_generated); + + rrdset_done(st_points_generated); + } +} diff --git a/src/daemon/telemetry/telemetry-queries.h b/src/daemon/telemetry/telemetry-queries.h new file mode 100644 index 00000000000000..67c0a3679ca480 --- /dev/null +++ b/src/daemon/telemetry/telemetry-queries.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_QUERIES_H +#define NETDATA_TELEMETRY_QUERIES_H + +#include "daemon/common.h" + +void telemetry_queries_ml_query_completed(size_t points_read); +void telemetry_queries_exporters_query_completed(size_t points_read); +void telemetry_queries_backfill_query_completed(size_t points_read); +void telemetry_queries_rrdr_query_completed(size_t queries, uint64_t db_points_read, uint64_t result_points_generated, QUERY_SOURCE query_source); + +#if defined(TELEMETRY_INTERNALS) +void telemetry_queries_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_QUERIES_H diff --git a/src/daemon/telemetry/telemetry-sqlite3.c b/src/daemon/telemetry/telemetry-sqlite3.c new file mode 100644 index 00000000000000..da160067e07cd2 --- /dev/null +++ b/src/daemon/telemetry/telemetry-sqlite3.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-sqlite3.h" + +static struct sqlite3_statistics { + bool enabled; + + alignas(64) uint64_t sqlite3_queries_made; + alignas(64) uint64_t sqlite3_queries_ok; + alignas(64) uint64_t sqlite3_queries_failed; + alignas(64) uint64_t sqlite3_queries_failed_busy; + alignas(64) uint64_t sqlite3_queries_failed_locked; + alignas(64) uint64_t sqlite3_rows; + alignas(64) uint64_t sqlite3_metadata_cache_hit; + alignas(64) uint64_t sqlite3_context_cache_hit; + alignas(64) uint64_t sqlite3_metadata_cache_miss; + alignas(64) uint64_t sqlite3_context_cache_miss; + alignas(64) uint64_t sqlite3_metadata_cache_spill; + alignas(64) uint64_t sqlite3_context_cache_spill; + alignas(64) uint64_t sqlite3_metadata_cache_write; + alignas(64) uint64_t sqlite3_context_cache_write; +} sqlite3_statistics = { }; + +void telemetry_sqlite3_query_completed(bool success, bool busy, bool locked) { + if(!sqlite3_statistics.enabled) return; + + __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_made, 1, __ATOMIC_RELAXED); + + if(success) { + __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_ok, 1, __ATOMIC_RELAXED); + } + else { + __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_failed, 1, __ATOMIC_RELAXED); + + if(busy) + __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_failed_busy, 1, __ATOMIC_RELAXED); + + if(locked) + __atomic_fetch_add(&sqlite3_statistics.sqlite3_queries_failed_locked, 1, __ATOMIC_RELAXED); + } +} + +void telemetry_sqlite3_row_completed(void) { + if(!sqlite3_statistics.enabled) return; + + __atomic_fetch_add(&sqlite3_statistics.sqlite3_rows, 1, __ATOMIC_RELAXED); +} + +static inline void sqlite3_statistics_copy(struct sqlite3_statistics *gs) { + static usec_t last_run = 0; + + gs->sqlite3_queries_made = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_made, __ATOMIC_RELAXED); + gs->sqlite3_queries_ok = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_ok, __ATOMIC_RELAXED); + gs->sqlite3_queries_failed = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_failed, __ATOMIC_RELAXED); + gs->sqlite3_queries_failed_busy = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_failed_busy, __ATOMIC_RELAXED); + gs->sqlite3_queries_failed_locked = __atomic_load_n(&sqlite3_statistics.sqlite3_queries_failed_locked, __ATOMIC_RELAXED); + gs->sqlite3_rows = __atomic_load_n(&sqlite3_statistics.sqlite3_rows, __ATOMIC_RELAXED); + + usec_t timeout = default_rrd_update_every * USEC_PER_SEC + default_rrd_update_every * USEC_PER_SEC / 3; + usec_t now = now_monotonic_usec(); + if(!last_run) + last_run = now; + usec_t delta = now - last_run; + bool query_sqlite3 = delta < timeout; + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_metadata_cache_hit = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_HIT); + else { + gs->sqlite3_metadata_cache_hit = UINT64_MAX; + query_sqlite3 = false; + } + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_context_cache_hit = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_HIT); + else { + gs->sqlite3_context_cache_hit = UINT64_MAX; + query_sqlite3 = false; + } + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_metadata_cache_miss = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_MISS); + else { + gs->sqlite3_metadata_cache_miss = UINT64_MAX; + query_sqlite3 = false; + } + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_context_cache_miss = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_MISS); + else { + gs->sqlite3_context_cache_miss = UINT64_MAX; + query_sqlite3 = false; + } + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_metadata_cache_spill = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_SPILL); + else { + gs->sqlite3_metadata_cache_spill = UINT64_MAX; + query_sqlite3 = false; + } + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_context_cache_spill = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_SPILL); + else { + gs->sqlite3_context_cache_spill = UINT64_MAX; + query_sqlite3 = false; + } + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_metadata_cache_write = (uint64_t) sql_metadata_cache_stats(SQLITE_DBSTATUS_CACHE_WRITE); + else { + gs->sqlite3_metadata_cache_write = UINT64_MAX; + query_sqlite3 = false; + } + + if(query_sqlite3 && now_monotonic_usec() - last_run < timeout) + gs->sqlite3_context_cache_write = (uint64_t) sql_context_cache_stats(SQLITE_DBSTATUS_CACHE_WRITE); + else { + gs->sqlite3_context_cache_write = UINT64_MAX; + query_sqlite3 = false; + } + + last_run = now_monotonic_usec(); +} + +void telemetry_sqlite3_do(bool extended) { + if(!extended) return; + sqlite3_statistics.enabled = true; + + struct sqlite3_statistics gs; + sqlite3_statistics_copy(&gs); + + if(gs.sqlite3_queries_made) { + static RRDSET *st_sqlite3_queries = NULL; + static RRDDIM *rd_queries = NULL; + + if (unlikely(!st_sqlite3_queries)) { + st_sqlite3_queries = rrdset_create_localhost( + "netdata" + , "sqlite3_queries" + , NULL + , "sqlite3" + , NULL + , "Netdata SQLite3 Queries" + , "queries/s" + , "netdata" + , "stats" + , 131100 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_queries = rrddim_add(st_sqlite3_queries, "queries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_sqlite3_queries, rd_queries, (collected_number)gs.sqlite3_queries_made); + + rrdset_done(st_sqlite3_queries); + } + + // ---------------------------------------------------------------- + + if(gs.sqlite3_queries_ok || gs.sqlite3_queries_failed) { + static RRDSET *st_sqlite3_queries_by_status = NULL; + static RRDDIM *rd_ok = NULL, *rd_failed = NULL, *rd_busy = NULL, *rd_locked = NULL; + + if (unlikely(!st_sqlite3_queries_by_status)) { + st_sqlite3_queries_by_status = rrdset_create_localhost( + "netdata" + , "sqlite3_queries_by_status" + , NULL + , "sqlite3" + , NULL + , "Netdata SQLite3 Queries by status" + , "queries/s" + , "netdata" + , "stats" + , 131101 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_ok = rrddim_add(st_sqlite3_queries_by_status, "ok", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_failed = rrddim_add(st_sqlite3_queries_by_status, "failed", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_busy = rrddim_add(st_sqlite3_queries_by_status, "busy", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_locked = rrddim_add(st_sqlite3_queries_by_status, "locked", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_ok, (collected_number)gs.sqlite3_queries_made); + rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_failed, (collected_number)gs.sqlite3_queries_failed); + rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_busy, (collected_number)gs.sqlite3_queries_failed_busy); + rrddim_set_by_pointer(st_sqlite3_queries_by_status, rd_locked, (collected_number)gs.sqlite3_queries_failed_locked); + + rrdset_done(st_sqlite3_queries_by_status); + } + + // ---------------------------------------------------------------- + + if(gs.sqlite3_rows) { + static RRDSET *st_sqlite3_rows = NULL; + static RRDDIM *rd_rows = NULL; + + if (unlikely(!st_sqlite3_rows)) { + st_sqlite3_rows = rrdset_create_localhost( + "netdata" + , "sqlite3_rows" + , NULL + , "sqlite3" + , NULL + , "Netdata SQLite3 Rows" + , "rows/s" + , "netdata" + , "stats" + , 131102 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_rows = rrddim_add(st_sqlite3_rows, "ok", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_sqlite3_rows, rd_rows, (collected_number)gs.sqlite3_rows); + + rrdset_done(st_sqlite3_rows); + } + + if(gs.sqlite3_metadata_cache_hit) { + static RRDSET *st_sqlite3_cache = NULL; + static RRDDIM *rd_cache_hit = NULL; + static RRDDIM *rd_cache_miss= NULL; + static RRDDIM *rd_cache_spill= NULL; + static RRDDIM *rd_cache_write= NULL; + + if (unlikely(!st_sqlite3_cache)) { + st_sqlite3_cache = rrdset_create_localhost( + "netdata" + , "sqlite3_metatada_cache" + , NULL + , "sqlite3" + , NULL + , "Netdata SQLite3 metadata cache" + , "ops/s" + , "netdata" + , "stats" + , 131103 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_cache_hit = rrddim_add(st_sqlite3_cache, "cache_hit", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_cache_miss = rrddim_add(st_sqlite3_cache, "cache_miss", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_cache_spill = rrddim_add(st_sqlite3_cache, "cache_spill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_cache_write = rrddim_add(st_sqlite3_cache, "cache_write", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + if(gs.sqlite3_metadata_cache_hit != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_hit, (collected_number)gs.sqlite3_metadata_cache_hit); + + if(gs.sqlite3_metadata_cache_miss != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_miss, (collected_number)gs.sqlite3_metadata_cache_miss); + + if(gs.sqlite3_metadata_cache_spill != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_spill, (collected_number)gs.sqlite3_metadata_cache_spill); + + if(gs.sqlite3_metadata_cache_write != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_write, (collected_number)gs.sqlite3_metadata_cache_write); + + rrdset_done(st_sqlite3_cache); + } + + if(gs.sqlite3_context_cache_hit) { + static RRDSET *st_sqlite3_cache = NULL; + static RRDDIM *rd_cache_hit = NULL; + static RRDDIM *rd_cache_miss= NULL; + static RRDDIM *rd_cache_spill= NULL; + static RRDDIM *rd_cache_write= NULL; + + if (unlikely(!st_sqlite3_cache)) { + st_sqlite3_cache = rrdset_create_localhost( + "netdata" + , "sqlite3_context_cache" + , NULL + , "sqlite3" + , NULL + , "Netdata SQLite3 context cache" + , "ops/s" + , "netdata" + , "stats" + , 131104 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + rd_cache_hit = rrddim_add(st_sqlite3_cache, "cache_hit", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_cache_miss = rrddim_add(st_sqlite3_cache, "cache_miss", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_cache_spill = rrddim_add(st_sqlite3_cache, "cache_spill", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_cache_write = rrddim_add(st_sqlite3_cache, "cache_write", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + if(gs.sqlite3_context_cache_hit != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_hit, (collected_number)gs.sqlite3_context_cache_hit); + + if(gs.sqlite3_context_cache_miss != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_miss, (collected_number)gs.sqlite3_context_cache_miss); + + if(gs.sqlite3_context_cache_spill != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_spill, (collected_number)gs.sqlite3_context_cache_spill); + + if(gs.sqlite3_context_cache_write != UINT64_MAX) + rrddim_set_by_pointer(st_sqlite3_cache, rd_cache_write, (collected_number)gs.sqlite3_context_cache_write); + + rrdset_done(st_sqlite3_cache); + } +} diff --git a/src/daemon/telemetry/telemetry-sqlite3.h b/src/daemon/telemetry/telemetry-sqlite3.h new file mode 100644 index 00000000000000..1c124dfa15cf97 --- /dev/null +++ b/src/daemon/telemetry/telemetry-sqlite3.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_SQLITE3_H +#define NETDATA_TELEMETRY_SQLITE3_H + +#include "daemon/common.h" + +void telemetry_sqlite3_query_completed(bool success, bool busy, bool locked); +void telemetry_sqlite3_row_completed(void); + +#if defined(TELEMETRY_INTERNALS) +void telemetry_sqlite3_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_SQLITE3_H diff --git a/src/daemon/telemetry/telemetry-string.c b/src/daemon/telemetry/telemetry-string.c new file mode 100644 index 00000000000000..33922ae9a30fec --- /dev/null +++ b/src/daemon/telemetry/telemetry-string.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-string.h" + +void telemetry_string_do(bool extended) { + if(!extended) return; + + static RRDSET *st_ops = NULL, *st_entries = NULL, *st_mem = NULL; + static RRDDIM *rd_ops_inserts = NULL, *rd_ops_deletes = NULL; + static RRDDIM *rd_entries_entries = NULL; + static RRDDIM *rd_mem = NULL; +#ifdef NETDATA_INTERNAL_CHECKS + static RRDDIM *rd_entries_refs = NULL, *rd_ops_releases = NULL, *rd_ops_duplications = NULL, *rd_ops_searches = NULL; +#endif + + size_t inserts, deletes, searches, entries, references, memory, duplications, releases; + + string_statistics(&inserts, &deletes, &searches, &entries, &references, &memory, &duplications, &releases); + + if (unlikely(!st_ops)) { + st_ops = rrdset_create_localhost( + "netdata" + , "strings_ops" + , NULL + , "strings" + , NULL + , "Strings operations" + , "ops/s" + , "netdata" + , "stats" + , 910000 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE); + + rd_ops_inserts = rrddim_add(st_ops, "inserts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_ops_deletes = rrddim_add(st_ops, "deletes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); +#ifdef NETDATA_INTERNAL_CHECKS + rd_ops_searches = rrddim_add(st_ops, "searches", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_ops_duplications = rrddim_add(st_ops, "duplications", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_ops_releases = rrddim_add(st_ops, "releases", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); +#endif + } + + rrddim_set_by_pointer(st_ops, rd_ops_inserts, (collected_number)inserts); + rrddim_set_by_pointer(st_ops, rd_ops_deletes, (collected_number)deletes); +#ifdef NETDATA_INTERNAL_CHECKS + rrddim_set_by_pointer(st_ops, rd_ops_searches, (collected_number)searches); + rrddim_set_by_pointer(st_ops, rd_ops_duplications, (collected_number)duplications); + rrddim_set_by_pointer(st_ops, rd_ops_releases, (collected_number)releases); +#endif + rrdset_done(st_ops); + + if (unlikely(!st_entries)) { + st_entries = rrdset_create_localhost( + "netdata" + , "strings_entries" + , NULL + , "strings" + , NULL + , "Strings entries" + , "entries" + , "netdata" + , "stats" + , 910001 + , localhost->rrd_update_every + , RRDSET_TYPE_AREA); + + rd_entries_entries = rrddim_add(st_entries, "entries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); +#ifdef NETDATA_INTERNAL_CHECKS + rd_entries_refs = rrddim_add(st_entries, "references", NULL, 1, -1, RRD_ALGORITHM_ABSOLUTE); +#endif + } + + rrddim_set_by_pointer(st_entries, rd_entries_entries, (collected_number)entries); +#ifdef NETDATA_INTERNAL_CHECKS + rrddim_set_by_pointer(st_entries, rd_entries_refs, (collected_number)references); +#endif + rrdset_done(st_entries); + + if (unlikely(!st_mem)) { + st_mem = rrdset_create_localhost( + "netdata" + , "strings_memory" + , NULL + , "strings" + , NULL + , "Strings memory" + , "bytes" + , "netdata" + , "stats" + , 910001 + , localhost->rrd_update_every + , RRDSET_TYPE_AREA); + + rd_mem = rrddim_add(st_mem, "memory", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(st_mem, rd_mem, (collected_number)memory); + rrdset_done(st_mem); +} diff --git a/src/daemon/telemetry/telemetry-string.h b/src/daemon/telemetry/telemetry-string.h new file mode 100644 index 00000000000000..21fd08127deba7 --- /dev/null +++ b/src/daemon/telemetry/telemetry-string.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_STRING_H +#define NETDATA_TELEMETRY_STRING_H + +#include "daemon/common.h" + +#if defined(TELEMETRY_INTERNALS) +void telemetry_string_do(bool extended); +#endif + +#endif //NETDATA_TELEMETRY_STRING_H diff --git a/src/daemon/telemetry/telemetry-trace-allocations.c b/src/daemon/telemetry/telemetry-trace-allocations.c new file mode 100644 index 00000000000000..a0ee095aebff4c --- /dev/null +++ b/src/daemon/telemetry/telemetry-trace-allocations.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-trace-allocations.h" + +#ifdef NETDATA_TRACE_ALLOCATIONS + +struct memory_trace_data { + RRDSET *st_memory; + RRDSET *st_allocations; + RRDSET *st_avg_alloc; + RRDSET *st_ops; +}; + +static int do_memory_trace_item(void *item, void *data) { + struct memory_trace_data *tmp = data; + struct malloc_trace *p = item; + + // ------------------------------------------------------------------------ + + if(!p->rd_bytes) + p->rd_bytes = rrddim_add(tmp->st_memory, p->function, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + collected_number bytes = (collected_number)__atomic_load_n(&p->bytes, __ATOMIC_RELAXED); + rrddim_set_by_pointer(tmp->st_memory, p->rd_bytes, bytes); + + // ------------------------------------------------------------------------ + + if(!p->rd_allocations) + p->rd_allocations = rrddim_add(tmp->st_allocations, p->function, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + collected_number allocs = (collected_number)__atomic_load_n(&p->allocations, __ATOMIC_RELAXED); + rrddim_set_by_pointer(tmp->st_allocations, p->rd_allocations, allocs); + + // ------------------------------------------------------------------------ + + if(!p->rd_avg_alloc) + p->rd_avg_alloc = rrddim_add(tmp->st_avg_alloc, p->function, NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); + + collected_number avg_alloc = (allocs)?(bytes * 100 / allocs):0; + rrddim_set_by_pointer(tmp->st_avg_alloc, p->rd_avg_alloc, avg_alloc); + + // ------------------------------------------------------------------------ + + if(!p->rd_ops) + p->rd_ops = rrddim_add(tmp->st_ops, p->function, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + + collected_number ops = 0; + ops += (collected_number)__atomic_load_n(&p->malloc_calls, __ATOMIC_RELAXED); + ops += (collected_number)__atomic_load_n(&p->calloc_calls, __ATOMIC_RELAXED); + ops += (collected_number)__atomic_load_n(&p->realloc_calls, __ATOMIC_RELAXED); + ops += (collected_number)__atomic_load_n(&p->strdup_calls, __ATOMIC_RELAXED); + ops += (collected_number)__atomic_load_n(&p->free_calls, __ATOMIC_RELAXED); + rrddim_set_by_pointer(tmp->st_ops, p->rd_ops, ops); + + // ------------------------------------------------------------------------ + + return 1; +} + +void telemetry_trace_allocations_do(bool extended) { + if(!extended) return; + + static struct memory_trace_data tmp = { + .st_memory = NULL, + .st_allocations = NULL, + .st_avg_alloc = NULL, + .st_ops = NULL, + }; + + if(!tmp.st_memory) { + tmp.st_memory = rrdset_create_localhost( + "netdata" + , "memory_size" + , NULL + , "memory" + , "netdata.memory.size" + , "Netdata Memory Used by Function" + , "bytes" + , "netdata" + , "stats" + , 900000 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + } + + if(!tmp.st_ops) { + tmp.st_ops = rrdset_create_localhost( + "netdata" + , "memory_operations" + , NULL + , "memory" + , "netdata.memory.operations" + , "Netdata Memory Operations by Function" + , "ops/s" + , "netdata" + , "stats" + , 900001 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + } + + if(!tmp.st_allocations) { + tmp.st_allocations = rrdset_create_localhost( + "netdata" + , "memory_allocations" + , NULL + , "memory" + , "netdata.memory.allocations" + , "Netdata Memory Allocations by Function" + , "allocations" + , "netdata" + , "stats" + , 900002 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + } + + if(!tmp.st_avg_alloc) { + tmp.st_avg_alloc = rrdset_create_localhost( + "netdata" + , "memory_avg_alloc" + , NULL + , "memory" + , "netdata.memory.avg_alloc" + , "Netdata Average Allocation Size by Function" + , "bytes" + , "netdata" + , "stats" + , 900003 + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + } + + malloc_trace_walkthrough(do_memory_trace_item, &tmp); + + rrdset_done(tmp.st_memory); + rrdset_done(tmp.st_ops); + rrdset_done(tmp.st_allocations); + rrdset_done(tmp.st_avg_alloc); +} + +#endif diff --git a/src/daemon/telemetry/telemetry-trace-allocations.h b/src/daemon/telemetry/telemetry-trace-allocations.h new file mode 100644 index 00000000000000..c44bf2e3ebb31b --- /dev/null +++ b/src/daemon/telemetry/telemetry-trace-allocations.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_TRACE_ALLOCATIONS_H +#define NETDATA_TELEMETRY_TRACE_ALLOCATIONS_H + +#include "daemon/common.h" + +#if defined(TELEMETRY_INTERNALS) +#ifdef NETDATA_TRACE_ALLOCATIONS +void telemetry_trace_allocations_do(bool extended); +#endif +#endif + +#endif //NETDATA_TELEMETRY_TRACE_ALLOCATIONS_H diff --git a/src/daemon/telemetry/telemetry-workers.c b/src/daemon/telemetry/telemetry-workers.c new file mode 100644 index 00000000000000..fd01ab1225f89f --- /dev/null +++ b/src/daemon/telemetry/telemetry-workers.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "telemetry-workers.h" + +#define WORKERS_MIN_PERCENT_DEFAULT 10000.0 + +struct worker_job_type_gs { + STRING *name; + STRING *units; + + size_t jobs_started; + usec_t busy_time; + + RRDDIM *rd_jobs_started; + RRDDIM *rd_busy_time; + + WORKER_METRIC_TYPE type; + NETDATA_DOUBLE min_value; + NETDATA_DOUBLE max_value; + NETDATA_DOUBLE sum_value; + size_t count_value; + + RRDSET *st; + RRDDIM *rd_min; + RRDDIM *rd_max; + RRDDIM *rd_avg; +}; + +struct worker_thread { + pid_t pid; + bool enabled; + + bool cpu_enabled; + double cpu; + + kernel_uint_t utime; + kernel_uint_t stime; + + kernel_uint_t utime_old; + kernel_uint_t stime_old; + + usec_t collected_time; + usec_t collected_time_old; + + size_t jobs_started; + usec_t busy_time; + + struct worker_thread *next; + struct worker_thread *prev; +}; + +struct worker_utilization { + const char *name; + const char *family; + size_t priority; + uint32_t flags; + + char *name_lowercase; + + struct worker_job_type_gs per_job_type[WORKER_UTILIZATION_MAX_JOB_TYPES]; + + size_t workers_max_job_id; + size_t workers_registered; + size_t workers_busy; + usec_t workers_total_busy_time; + usec_t workers_total_duration; + size_t workers_total_jobs_started; + double workers_min_busy_time; + double workers_max_busy_time; + + size_t workers_cpu_registered; + double workers_cpu_min; + double workers_cpu_max; + double workers_cpu_total; + + struct worker_thread *threads; + + RRDSET *st_workers_time; + RRDDIM *rd_workers_time_avg; + RRDDIM *rd_workers_time_min; + RRDDIM *rd_workers_time_max; + + RRDSET *st_workers_cpu; + RRDDIM *rd_workers_cpu_avg; + RRDDIM *rd_workers_cpu_min; + RRDDIM *rd_workers_cpu_max; + + RRDSET *st_workers_threads; + RRDDIM *rd_workers_threads_free; + RRDDIM *rd_workers_threads_busy; + + RRDSET *st_workers_jobs_per_job_type; + RRDSET *st_workers_busy_per_job_type; + + RRDDIM *rd_total_cpu_utilizaton; +}; + +static struct worker_utilization all_workers_utilization[] = { + { .name = "STATS", .family = "workers telemetry", .priority = 1000000 }, + { .name = "HEALTH", .family = "workers health alarms", .priority = 1000000 }, + { .name = "MLTRAIN", .family = "workers ML training", .priority = 1000000 }, + { .name = "MLDETECT", .family = "workers ML detection", .priority = 1000000 }, + { .name = "STREAM", .family = "workers streaming", .priority = 1000000 }, + { .name = "STREAMCNT", .family = "workers streaming connect", .priority = 1000000 }, + { .name = "DBENGINE", .family = "workers dbengine instances", .priority = 1000000 }, + { .name = "LIBUV", .family = "workers libuv threadpool", .priority = 1000000 }, + { .name = "WEB", .family = "workers web server", .priority = 1000000 }, + { .name = "ACLKSYNC", .family = "workers aclk sync", .priority = 1000000 }, + { .name = "METASYNC", .family = "workers metadata sync", .priority = 1000000 }, + { .name = "PLUGINSD", .family = "workers plugins.d", .priority = 1000000 }, + { .name = "STATSD", .family = "workers plugin statsd", .priority = 1000000 }, + { .name = "STATSDFLUSH", .family = "workers plugin statsd flush", .priority = 1000000 }, + { .name = "PROC", .family = "workers plugin proc", .priority = 1000000 }, + { .name = "WIN", .family = "workers plugin windows", .priority = 1000000 }, + { .name = "NETDEV", .family = "workers plugin proc netdev", .priority = 1000000 }, + { .name = "FREEBSD", .family = "workers plugin freebsd", .priority = 1000000 }, + { .name = "MACOS", .family = "workers plugin macos", .priority = 1000000 }, + { .name = "CGROUPS", .family = "workers plugin cgroups", .priority = 1000000 }, + { .name = "CGROUPSDISC", .family = "workers plugin cgroups find", .priority = 1000000 }, + { .name = "DISKSPACE", .family = "workers plugin diskspace", .priority = 1000000 }, + { .name = "TC", .family = "workers plugin tc", .priority = 1000000 }, + { .name = "TIMEX", .family = "workers plugin timex", .priority = 1000000 }, + { .name = "IDLEJITTER", .family = "workers plugin idlejitter", .priority = 1000000 }, + { .name = "RRDCONTEXT", .family = "workers contexts", .priority = 1000000 }, + { .name = "REPLICATION", .family = "workers replication sender", .priority = 1000000 }, + { .name = "SERVICE", .family = "workers service", .priority = 1000000 }, + { .name = "PROFILER", .family = "workers profile", .priority = 1000000 }, + { .name = "PGCEVICT", .family = "workers dbengine eviction", .priority = 1000000 }, + + // has to be terminated with a NULL + { .name = NULL, .family = NULL } +}; + +static void workers_total_cpu_utilization_chart(void) { + size_t i, cpu_enabled = 0; + for(i = 0; all_workers_utilization[i].name ;i++) + if(all_workers_utilization[i].workers_cpu_registered) cpu_enabled++; + + if(!cpu_enabled) return; + + static RRDSET *st = NULL; + + if(!st) { + st = rrdset_create_localhost( + "netdata", + "workers_cpu", + NULL, + "workers", + "netdata.workers.cpu_total", + "Netdata Workers CPU Utilization (100% = 1 core)", + "%", + "netdata", + "stats", + 999000, + localhost->rrd_update_every, + RRDSET_TYPE_STACKED); + } + + for(i = 0; all_workers_utilization[i].name ;i++) { + struct worker_utilization *wu = &all_workers_utilization[i]; + if(!wu->workers_cpu_registered) continue; + + if(!wu->rd_total_cpu_utilizaton) + wu->rd_total_cpu_utilizaton = rrddim_add(st, wu->name_lowercase, NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); + + rrddim_set_by_pointer(st, wu->rd_total_cpu_utilizaton, (collected_number)((double)wu->workers_cpu_total * 100.0)); + } + + rrdset_done(st); +} + +#define WORKER_CHART_DECIMAL_PRECISION 100 + +static void workers_utilization_update_chart(struct worker_utilization *wu) { + if(!wu->workers_registered) return; + + //fprintf(stderr, "%-12s WORKER UTILIZATION: %-3.2f%%, %zu jobs done, %zu running, on %zu workers, min %-3.02f%%, max %-3.02f%%.\n", + // wu->name, + // (double)wu->workers_total_busy_time * 100.0 / (double)wu->workers_total_duration, + // wu->workers_total_jobs_started, wu->workers_busy, wu->workers_registered, + // wu->workers_min_busy_time, wu->workers_max_busy_time); + + // ---------------------------------------------------------------------- + + if(unlikely(!wu->st_workers_time)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "workers_time_%s", wu->name_lowercase); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.time", wu->name_lowercase); + + wu->st_workers_time = rrdset_create_localhost( + "netdata" + , name + , NULL + , wu->family + , context + , "Netdata Workers Busy Time (100% = all workers busy)" + , "%" + , "netdata" + , "stats" + , wu->priority + , localhost->rrd_update_every + , RRDSET_TYPE_AREA + ); + } + + // we add the min and max dimensions only when we have multiple workers + + if(unlikely(!wu->rd_workers_time_min && wu->workers_registered > 1)) + wu->rd_workers_time_min = rrddim_add(wu->st_workers_time, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + + if(unlikely(!wu->rd_workers_time_max && wu->workers_registered > 1)) + wu->rd_workers_time_max = rrddim_add(wu->st_workers_time, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + + if(unlikely(!wu->rd_workers_time_avg)) + wu->rd_workers_time_avg = rrddim_add(wu->st_workers_time, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + + if(unlikely(wu->workers_min_busy_time == WORKERS_MIN_PERCENT_DEFAULT)) wu->workers_min_busy_time = 0.0; + + if(wu->rd_workers_time_min) + rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_min, (collected_number)((double)wu->workers_min_busy_time * WORKER_CHART_DECIMAL_PRECISION)); + + if(wu->rd_workers_time_max) + rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_max, (collected_number)((double)wu->workers_max_busy_time * WORKER_CHART_DECIMAL_PRECISION)); + + if(wu->workers_total_duration == 0) + rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_avg, 0); + else + rrddim_set_by_pointer(wu->st_workers_time, wu->rd_workers_time_avg, (collected_number)((double)wu->workers_total_busy_time * 100.0 * WORKER_CHART_DECIMAL_PRECISION / (double)wu->workers_total_duration)); + + rrdset_done(wu->st_workers_time); + + // ---------------------------------------------------------------------- + +#ifdef __linux__ + if(wu->workers_cpu_registered || wu->st_workers_cpu) { + if(unlikely(!wu->st_workers_cpu)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "workers_cpu_%s", wu->name_lowercase); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.cpu", wu->name_lowercase); + + wu->st_workers_cpu = rrdset_create_localhost( + "netdata" + , name + , NULL + , wu->family + , context + , "Netdata Workers CPU Utilization (100% = all workers busy)" + , "%" + , "netdata" + , "stats" + , wu->priority + 1 + , localhost->rrd_update_every + , RRDSET_TYPE_AREA + ); + } + + if (unlikely(!wu->rd_workers_cpu_min && wu->workers_registered > 1)) + wu->rd_workers_cpu_min = rrddim_add(wu->st_workers_cpu, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + + if (unlikely(!wu->rd_workers_cpu_max && wu->workers_registered > 1)) + wu->rd_workers_cpu_max = rrddim_add(wu->st_workers_cpu, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + + if(unlikely(!wu->rd_workers_cpu_avg)) + wu->rd_workers_cpu_avg = rrddim_add(wu->st_workers_cpu, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + + if(unlikely(wu->workers_cpu_min == WORKERS_MIN_PERCENT_DEFAULT)) wu->workers_cpu_min = 0.0; + + if(wu->rd_workers_cpu_min) + rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_min, (collected_number)(wu->workers_cpu_min * WORKER_CHART_DECIMAL_PRECISION)); + + if(wu->rd_workers_cpu_max) + rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_max, (collected_number)(wu->workers_cpu_max * WORKER_CHART_DECIMAL_PRECISION)); + + if(wu->workers_cpu_registered == 0) + rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_avg, 0); + else + rrddim_set_by_pointer(wu->st_workers_cpu, wu->rd_workers_cpu_avg, (collected_number)( wu->workers_cpu_total * WORKER_CHART_DECIMAL_PRECISION / (NETDATA_DOUBLE)wu->workers_cpu_registered )); + + rrdset_done(wu->st_workers_cpu); + } +#endif + + // ---------------------------------------------------------------------- + + if(unlikely(!wu->st_workers_jobs_per_job_type)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "workers_jobs_by_type_%s", wu->name_lowercase); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.jobs_started_by_type", wu->name_lowercase); + + wu->st_workers_jobs_per_job_type = rrdset_create_localhost( + "netdata" + , name + , NULL + , wu->family + , context + , "Netdata Workers Jobs Started by Type" + , "jobs" + , "netdata" + , "stats" + , wu->priority + 2 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + } + + { + size_t i; + for(i = 0; i <= wu->workers_max_job_id ;i++) { + if(unlikely(wu->per_job_type[i].type != WORKER_METRIC_IDLE_BUSY)) + continue; + + if (wu->per_job_type[i].name) { + + if(unlikely(!wu->per_job_type[i].rd_jobs_started)) + wu->per_job_type[i].rd_jobs_started = rrddim_add(wu->st_workers_jobs_per_job_type, string2str(wu->per_job_type[i].name), NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + rrddim_set_by_pointer(wu->st_workers_jobs_per_job_type, wu->per_job_type[i].rd_jobs_started, (collected_number)(wu->per_job_type[i].jobs_started)); + } + } + } + + rrdset_done(wu->st_workers_jobs_per_job_type); + + // ---------------------------------------------------------------------- + + if(unlikely(!wu->st_workers_busy_per_job_type)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "workers_busy_time_by_type_%s", wu->name_lowercase); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.time_by_type", wu->name_lowercase); + + wu->st_workers_busy_per_job_type = rrdset_create_localhost( + "netdata" + , name + , NULL + , wu->family + , context + , "Netdata Workers Busy Time by Type" + , "ms" + , "netdata" + , "stats" + , wu->priority + 3 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + } + + { + size_t i; + for(i = 0; i <= wu->workers_max_job_id ;i++) { + if(unlikely(wu->per_job_type[i].type != WORKER_METRIC_IDLE_BUSY)) + continue; + + if (wu->per_job_type[i].name) { + + if(unlikely(!wu->per_job_type[i].rd_busy_time)) + wu->per_job_type[i].rd_busy_time = rrddim_add(wu->st_workers_busy_per_job_type, string2str(wu->per_job_type[i].name), NULL, 1, USEC_PER_MS, RRD_ALGORITHM_ABSOLUTE); + + rrddim_set_by_pointer(wu->st_workers_busy_per_job_type, wu->per_job_type[i].rd_busy_time, (collected_number)(wu->per_job_type[i].busy_time)); + } + } + } + + rrdset_done(wu->st_workers_busy_per_job_type); + + // ---------------------------------------------------------------------- + + if(wu->st_workers_threads || wu->workers_registered > 1) { + if(unlikely(!wu->st_workers_threads)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "workers_threads_%s", wu->name_lowercase); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.threads", wu->name_lowercase); + + wu->st_workers_threads = rrdset_create_localhost( + "netdata" + , name + , NULL + , wu->family + , context + , "Netdata Workers Threads" + , "threads" + , "netdata" + , "stats" + , wu->priority + 4 + , localhost->rrd_update_every + , RRDSET_TYPE_STACKED + ); + + wu->rd_workers_threads_free = rrddim_add(wu->st_workers_threads, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + wu->rd_workers_threads_busy = rrddim_add(wu->st_workers_threads, "busy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(wu->st_workers_threads, wu->rd_workers_threads_free, (collected_number)(wu->workers_registered - wu->workers_busy)); + rrddim_set_by_pointer(wu->st_workers_threads, wu->rd_workers_threads_busy, (collected_number)(wu->workers_busy)); + rrdset_done(wu->st_workers_threads); + } + + // ---------------------------------------------------------------------- + // custom metric types WORKER_METRIC_ABSOLUTE + + { + size_t i; + for (i = 0; i <= wu->workers_max_job_id; i++) { + if(wu->per_job_type[i].type != WORKER_METRIC_ABSOLUTE) + continue; + + if(!wu->per_job_type[i].count_value) + continue; + + if(!wu->per_job_type[i].st) { + size_t job_name_len = string_strlen(wu->per_job_type[i].name); + if(job_name_len > RRD_ID_LENGTH_MAX) job_name_len = RRD_ID_LENGTH_MAX; + + char job_name_sanitized[job_name_len + 1]; + rrdset_strncpyz_name(job_name_sanitized, string2str(wu->per_job_type[i].name), job_name_len); + + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "workers_%s_value_%s", wu->name_lowercase, job_name_sanitized); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.value.%s", wu->name_lowercase, job_name_sanitized); + + char title[1000 + 1]; + snprintf(title, 1000, "Netdata Workers %s value of %s", wu->name_lowercase, string2str(wu->per_job_type[i].name)); + + wu->per_job_type[i].st = rrdset_create_localhost( + "netdata" + , name + , NULL + , wu->family + , context + , title + , (wu->per_job_type[i].units)?string2str(wu->per_job_type[i].units):"value" + , "netdata" + , "stats" + , wu->priority + 5 + i + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + wu->per_job_type[i].rd_min = rrddim_add(wu->per_job_type[i].st, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + wu->per_job_type[i].rd_max = rrddim_add(wu->per_job_type[i].st, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + wu->per_job_type[i].rd_avg = rrddim_add(wu->per_job_type[i].st, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_min, (collected_number)(wu->per_job_type[i].min_value * WORKER_CHART_DECIMAL_PRECISION)); + rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_max, (collected_number)(wu->per_job_type[i].max_value * WORKER_CHART_DECIMAL_PRECISION)); + rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_avg, (collected_number)(wu->per_job_type[i].sum_value / wu->per_job_type[i].count_value * WORKER_CHART_DECIMAL_PRECISION)); + + rrdset_done(wu->per_job_type[i].st); + } + } + + // ---------------------------------------------------------------------- + // custom metric types WORKER_METRIC_INCREMENTAL + + { + size_t i; + for (i = 0; i <= wu->workers_max_job_id ; i++) { + if(wu->per_job_type[i].type != WORKER_METRIC_INCREMENT && wu->per_job_type[i].type != WORKER_METRIC_INCREMENTAL_TOTAL) + continue; + + if(!wu->per_job_type[i].count_value) + continue; + + if(!wu->per_job_type[i].st) { + size_t job_name_len = string_strlen(wu->per_job_type[i].name); + if(job_name_len > RRD_ID_LENGTH_MAX) job_name_len = RRD_ID_LENGTH_MAX; + + char job_name_sanitized[job_name_len + 1]; + rrdset_strncpyz_name(job_name_sanitized, string2str(wu->per_job_type[i].name), job_name_len); + + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "workers_%s_rate_%s", wu->name_lowercase, job_name_sanitized); + + char context[RRD_ID_LENGTH_MAX + 1]; + snprintf(context, RRD_ID_LENGTH_MAX, "netdata.workers.%s.rate.%s", wu->name_lowercase, job_name_sanitized); + + char title[1000 + 1]; + snprintf(title, 1000, "Netdata Workers %s rate of %s", wu->name_lowercase, string2str(wu->per_job_type[i].name)); + + wu->per_job_type[i].st = rrdset_create_localhost( + "netdata" + , name + , NULL + , wu->family + , context + , title + , (wu->per_job_type[i].units)?string2str(wu->per_job_type[i].units):"rate" + , "netdata" + , "stats" + , wu->priority + 5 + i + , localhost->rrd_update_every + , RRDSET_TYPE_LINE + ); + + wu->per_job_type[i].rd_min = rrddim_add(wu->per_job_type[i].st, "min", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + wu->per_job_type[i].rd_max = rrddim_add(wu->per_job_type[i].st, "max", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + wu->per_job_type[i].rd_avg = rrddim_add(wu->per_job_type[i].st, "average", NULL, 1, WORKER_CHART_DECIMAL_PRECISION, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_min, (collected_number)(wu->per_job_type[i].min_value * WORKER_CHART_DECIMAL_PRECISION)); + rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_max, (collected_number)(wu->per_job_type[i].max_value * WORKER_CHART_DECIMAL_PRECISION)); + rrddim_set_by_pointer(wu->per_job_type[i].st, wu->per_job_type[i].rd_avg, (collected_number)(wu->per_job_type[i].sum_value / wu->per_job_type[i].count_value * WORKER_CHART_DECIMAL_PRECISION)); + + rrdset_done(wu->per_job_type[i].st); + } + } +} + +static void workers_utilization_reset_statistics(struct worker_utilization *wu) { + wu->workers_registered = 0; + wu->workers_busy = 0; + wu->workers_total_busy_time = 0; + wu->workers_total_duration = 0; + wu->workers_total_jobs_started = 0; + wu->workers_min_busy_time = WORKERS_MIN_PERCENT_DEFAULT; + wu->workers_max_busy_time = 0; + + wu->workers_cpu_registered = 0; + wu->workers_cpu_min = WORKERS_MIN_PERCENT_DEFAULT; + wu->workers_cpu_max = 0; + wu->workers_cpu_total = 0; + + size_t i; + for(i = 0; i < WORKER_UTILIZATION_MAX_JOB_TYPES ;i++) { + if(unlikely(!wu->name_lowercase)) { + wu->name_lowercase = strdupz(wu->name); + char *s = wu->name_lowercase; + for( ; *s ; s++) *s = tolower(*s); + } + + wu->per_job_type[i].jobs_started = 0; + wu->per_job_type[i].busy_time = 0; + + wu->per_job_type[i].min_value = NAN; + wu->per_job_type[i].max_value = NAN; + wu->per_job_type[i].sum_value = NAN; + wu->per_job_type[i].count_value = 0; + } + + struct worker_thread *wt; + for(wt = wu->threads; wt ; wt = wt->next) { + wt->enabled = false; + wt->cpu_enabled = false; + } +} + +#define TASK_STAT_PREFIX "/proc/self/task/" +#define TASK_STAT_SUFFIX "/stat" + +static int read_thread_cpu_time_from_proc_stat(pid_t pid __maybe_unused, kernel_uint_t *utime __maybe_unused, kernel_uint_t *stime __maybe_unused) { +#ifdef __linux__ + static char filename[sizeof(TASK_STAT_PREFIX) + sizeof(TASK_STAT_SUFFIX) + 20] = TASK_STAT_PREFIX; + static size_t start_pos = sizeof(TASK_STAT_PREFIX) - 1; + static procfile *ff = NULL; + + // construct the filename + size_t end_pos = snprintfz(&filename[start_pos], 20, "%d", pid); + strcpy(&filename[start_pos + end_pos], TASK_STAT_SUFFIX); + + // (re)open the procfile to the new filename + bool set_quotes = (ff == NULL) ? true : false; + ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_ERROR_ON_ERROR_LOG); + if(unlikely(!ff)) return -1; + + if(set_quotes) + procfile_set_open_close(ff, "(", ")"); + + // read the entire file and split it to lines and words + ff = procfile_readall(ff); + if(unlikely(!ff)) return -1; + + // parse the numbers we are interested + *utime = str2kernel_uint_t(procfile_lineword(ff, 0, 13)); + *stime = str2kernel_uint_t(procfile_lineword(ff, 0, 14)); + + // leave the file open for the next iteration + + return 0; +#else + // TODO: add here cpu time detection per thread, for FreeBSD and MacOS + *utime = 0; + *stime = 0; + return 1; +#endif +} + +static Pvoid_t workers_by_pid_JudyL_array = NULL; + +static void workers_threads_cleanup(struct worker_utilization *wu) { + struct worker_thread *t = wu->threads; + while(t) { + struct worker_thread *next = t->next; + + if(!t->enabled) { + JudyLDel(&workers_by_pid_JudyL_array, t->pid, PJE0); + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wu->threads, t, prev, next); + freez(t); + } + t = next; + } +} + +static struct worker_thread *worker_thread_find(struct worker_utilization *wu __maybe_unused, pid_t pid) { + struct worker_thread *wt = NULL; + + Pvoid_t *PValue = JudyLGet(workers_by_pid_JudyL_array, pid, PJE0); + if(PValue) + wt = *PValue; + + return wt; +} + +static struct worker_thread *worker_thread_create(struct worker_utilization *wu, pid_t pid) { + struct worker_thread *wt; + + wt = (struct worker_thread *)callocz(1, sizeof(struct worker_thread)); + wt->pid = pid; + + Pvoid_t *PValue = JudyLIns(&workers_by_pid_JudyL_array, pid, PJE0); + *PValue = wt; + + // link it + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wu->threads, wt, prev, next); + + return wt; +} + +static struct worker_thread *worker_thread_find_or_create(struct worker_utilization *wu, pid_t pid) { + struct worker_thread *wt; + wt = worker_thread_find(wu, pid); + if(!wt) wt = worker_thread_create(wu, pid); + + return wt; +} + +static void worker_utilization_charts_callback(void *ptr + , pid_t pid __maybe_unused + , const char *thread_tag __maybe_unused + , size_t max_job_id __maybe_unused + , size_t utilization_usec __maybe_unused + , size_t duration_usec __maybe_unused + , size_t jobs_started __maybe_unused + , size_t is_running __maybe_unused + , STRING **job_types_names __maybe_unused + , STRING **job_types_units __maybe_unused + , WORKER_METRIC_TYPE *job_types_metric_types __maybe_unused + , size_t *job_types_jobs_started __maybe_unused + , usec_t *job_types_busy_time __maybe_unused + , NETDATA_DOUBLE *job_types_custom_metrics __maybe_unused +) { + struct worker_utilization *wu = (struct worker_utilization *)ptr; + + // find the worker_thread in the list + struct worker_thread *wt = worker_thread_find_or_create(wu, pid); + + if(utilization_usec > duration_usec) + utilization_usec = duration_usec; + + wt->enabled = true; + wt->busy_time = utilization_usec; + wt->jobs_started = jobs_started; + + wt->utime_old = wt->utime; + wt->stime_old = wt->stime; + wt->collected_time_old = wt->collected_time; + + if(max_job_id > wu->workers_max_job_id) + wu->workers_max_job_id = max_job_id; + + wu->workers_total_busy_time += utilization_usec; + wu->workers_total_duration += duration_usec; + wu->workers_total_jobs_started += jobs_started; + wu->workers_busy += is_running; + wu->workers_registered++; + + double util = (double)utilization_usec * 100.0 / (double)duration_usec; + if(util > wu->workers_max_busy_time) + wu->workers_max_busy_time = util; + + if(util < wu->workers_min_busy_time) + wu->workers_min_busy_time = util; + + // accumulate per job type statistics + size_t i; + for(i = 0; i <= max_job_id ;i++) { + if(!wu->per_job_type[i].name && job_types_names[i]) + wu->per_job_type[i].name = string_dup(job_types_names[i]); + + if(!wu->per_job_type[i].units && job_types_units[i]) + wu->per_job_type[i].units = string_dup(job_types_units[i]); + + wu->per_job_type[i].type = job_types_metric_types[i]; + + wu->per_job_type[i].jobs_started += job_types_jobs_started[i]; + wu->per_job_type[i].busy_time += job_types_busy_time[i]; + + NETDATA_DOUBLE value = job_types_custom_metrics[i]; + if(netdata_double_isnumber(value)) { + if(!wu->per_job_type[i].count_value) { + wu->per_job_type[i].count_value = 1; + wu->per_job_type[i].min_value = value; + wu->per_job_type[i].max_value = value; + wu->per_job_type[i].sum_value = value; + } + else { + wu->per_job_type[i].count_value++; + wu->per_job_type[i].sum_value += value; + if(value < wu->per_job_type[i].min_value) wu->per_job_type[i].min_value = value; + if(value > wu->per_job_type[i].max_value) wu->per_job_type[i].max_value = value; + } + } + } + + // find its CPU utilization + if((!read_thread_cpu_time_from_proc_stat(pid, &wt->utime, &wt->stime))) { + wt->collected_time = now_realtime_usec(); + usec_t delta = wt->collected_time - wt->collected_time_old; + + double utime = (double)(wt->utime - wt->utime_old) / (double)system_hz * 100.0 * (double)USEC_PER_SEC / (double)delta; + double stime = (double)(wt->stime - wt->stime_old) / (double)system_hz * 100.0 * (double)USEC_PER_SEC / (double)delta; + double cpu = utime + stime; + wt->cpu = cpu; + wt->cpu_enabled = true; + + wu->workers_cpu_total += cpu; + if(cpu < wu->workers_cpu_min) wu->workers_cpu_min = cpu; + if(cpu > wu->workers_cpu_max) wu->workers_cpu_max = cpu; + } + wu->workers_cpu_registered += (wt->cpu_enabled) ? 1 : 0; +} + +void telemetry_workers_cleanup(void) { + int i, j; + for(i = 0; all_workers_utilization[i].name ;i++) { + struct worker_utilization *wu = &all_workers_utilization[i]; + + if(wu->name_lowercase) { + freez(wu->name_lowercase); + wu->name_lowercase = NULL; + } + + for(j = 0; j < WORKER_UTILIZATION_MAX_JOB_TYPES ;j++) { + string_freez(wu->per_job_type[j].name); + wu->per_job_type[j].name = NULL; + + string_freez(wu->per_job_type[j].units); + wu->per_job_type[j].units = NULL; + } + + // mark all threads as not enabled + struct worker_thread *t; + for(t = wu->threads; t ; t = t->next) + t->enabled = false; + + // let the cleanup job free them + workers_threads_cleanup(wu); + } +} + +void telemetry_workers_do(bool extended) { + if(!extended) return; + + static size_t iterations = 0; + iterations++; + + for(int i = 0; all_workers_utilization[i].name ;i++) { + workers_utilization_reset_statistics(&all_workers_utilization[i]); + + workers_foreach(all_workers_utilization[i].name, worker_utilization_charts_callback, &all_workers_utilization[i]); + + // skip the first iteration, so that we don't accumulate startup utilization to our charts + if(likely(iterations > 1)) + workers_utilization_update_chart(&all_workers_utilization[i]); + + workers_threads_cleanup(&all_workers_utilization[i]); + } + + workers_total_cpu_utilization_chart(); +} diff --git a/src/daemon/telemetry/telemetry-workers.h b/src/daemon/telemetry/telemetry-workers.h new file mode 100644 index 00000000000000..02acd59a982e44 --- /dev/null +++ b/src/daemon/telemetry/telemetry-workers.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_WORKERS_H +#define NETDATA_TELEMETRY_WORKERS_H + +#include "daemon/common.h" + +#if defined(TELEMETRY_INTERNALS) +void telemetry_workers_do(bool extended); +void telemetry_workers_cleanup(void); +#endif + +#endif //NETDATA_TELEMETRY_WORKERS_H diff --git a/src/daemon/telemetry/telemetry.c b/src/daemon/telemetry/telemetry.c new file mode 100644 index 00000000000000..65bcbce56ca8ff --- /dev/null +++ b/src/daemon/telemetry/telemetry.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define TELEMETRY_INTERNALS 1 +#include "daemon/common.h" + +#define WORKER_JOB_TELEMETRY_DAEMON 0 +#define WORKER_JOB_SQLITE3 1 +#define WORKER_JOB_TELEMETRY_HTTP_API 2 +#define WORKER_JOB_TELEMETRY_QUERIES 3 +#define WORKER_JOB_TELEMETRY_INGESTION 4 +#define WORKER_JOB_DBENGINE 5 +#define WORKER_JOB_STRINGS 6 +#define WORKER_JOB_DICTIONARIES 7 +#define WORKER_JOB_TELEMETRY_ML 8 +#define WORKER_JOB_TELEMETRY_GORILLA 9 +#define WORKER_JOB_HEARTBEAT 10 +#define WORKER_JOB_WORKERS 11 +#define WORKER_JOB_MALLOC_TRACE 12 +#define WORKER_JOB_REGISTRY 13 +#define WORKER_JOB_ARAL 14 + +#if WORKER_UTILIZATION_MAX_JOB_TYPES < 15 +#error "WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 14" +#endif + +bool telemetry_enabled = true; +bool telemetry_extended_enabled = false; + +static void telemetry_register_workers(void) { + worker_register("STATS"); + + worker_register_job_name(WORKER_JOB_TELEMETRY_DAEMON, "daemon"); + worker_register_job_name(WORKER_JOB_SQLITE3, "sqlite3"); + worker_register_job_name(WORKER_JOB_TELEMETRY_HTTP_API, "http-api"); + worker_register_job_name(WORKER_JOB_TELEMETRY_QUERIES, "queries"); + worker_register_job_name(WORKER_JOB_TELEMETRY_INGESTION, "ingestion"); + worker_register_job_name(WORKER_JOB_DBENGINE, "dbengine"); + worker_register_job_name(WORKER_JOB_STRINGS, "strings"); + worker_register_job_name(WORKER_JOB_DICTIONARIES, "dictionaries"); + worker_register_job_name(WORKER_JOB_TELEMETRY_ML, "ML"); + worker_register_job_name(WORKER_JOB_TELEMETRY_GORILLA, "gorilla"); + worker_register_job_name(WORKER_JOB_HEARTBEAT, "heartbeat"); + worker_register_job_name(WORKER_JOB_WORKERS, "workers"); + worker_register_job_name(WORKER_JOB_MALLOC_TRACE, "malloc_trace"); + worker_register_job_name(WORKER_JOB_REGISTRY, "registry"); + worker_register_job_name(WORKER_JOB_ARAL, "aral"); +} + +static void telementry_cleanup(void *pptr) +{ + struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!static_thread) return; + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; + + telemetry_workers_cleanup(); + worker_unregister(); + netdata_log_info("cleaning up..."); + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; +} + +void *telemetry_thread_main(void *ptr) { + CLEANUP_FUNCTION_REGISTER(telementry_cleanup) cleanup_ptr = ptr; + telemetry_register_workers(); + + int update_every = + (int)config_get_duration_seconds(CONFIG_SECTION_TELEMETRY, "update every", localhost->rrd_update_every); + if (update_every < localhost->rrd_update_every) { + update_every = localhost->rrd_update_every; + config_set_duration_seconds(CONFIG_SECTION_TELEMETRY, "update every", update_every); + } + + telemerty_aral_init(); + + usec_t step = update_every * USEC_PER_SEC; + heartbeat_t hb; + heartbeat_init(&hb, USEC_PER_SEC); + usec_t real_step = USEC_PER_SEC; + + // keep the randomness at zero + // to make sure we are not close to any other thread + hb.randomness = 0; + + while (service_running(SERVICE_COLLECTORS)) { + worker_is_idle(); + heartbeat_next(&hb); + if (real_step < step) { + real_step += USEC_PER_SEC; + continue; + } + real_step = USEC_PER_SEC; + + worker_is_busy(WORKER_JOB_TELEMETRY_INGESTION); + telemetry_ingestion_do(telemetry_extended_enabled); + + worker_is_busy(WORKER_JOB_TELEMETRY_HTTP_API); + telemetry_web_do(telemetry_extended_enabled); + + worker_is_busy(WORKER_JOB_TELEMETRY_QUERIES); + telemetry_queries_do(telemetry_extended_enabled); + + worker_is_busy(WORKER_JOB_TELEMETRY_ML); + telemetry_ml_do(telemetry_extended_enabled); + + worker_is_busy(WORKER_JOB_TELEMETRY_GORILLA); + telemetry_gorilla_do(telemetry_extended_enabled); + + worker_is_busy(WORKER_JOB_HEARTBEAT); + telemetry_heartbeat_do(telemetry_extended_enabled); + +#ifdef ENABLE_DBENGINE + if(dbengine_enabled) { + worker_is_busy(WORKER_JOB_DBENGINE); + telemetry_dbengine_do(telemetry_extended_enabled); + } +#endif + + worker_is_busy(WORKER_JOB_REGISTRY); + registry_statistics(); + + worker_is_busy(WORKER_JOB_STRINGS); + telemetry_string_do(telemetry_extended_enabled); + +#ifdef DICT_WITH_STATS + worker_is_busy(WORKER_JOB_DICTIONARIES); + telemetry_dictionary_do(telemetry_extended_enabled); +#endif + +#ifdef NETDATA_TRACE_ALLOCATIONS + worker_is_busy(WORKER_JOB_MALLOC_TRACE); + telemetry_trace_allocations_do(telemetry_extended_enabled); +#endif + + worker_is_busy(WORKER_JOB_WORKERS); + telemetry_workers_do(telemetry_extended_enabled); + + worker_is_busy(WORKER_JOB_ARAL); + telemetry_aral_do(telemetry_extended_enabled); + + // keep this last to have access to the memory counters + // exposed by everyone else + worker_is_busy(WORKER_JOB_TELEMETRY_DAEMON); + telemetry_daemon_do(telemetry_extended_enabled); + } + + return NULL; +} + +// --------------------------------------------------------------------------------------------------------------------- +// telemetry extended thread + +static void telemetry_thread_sqlite3_cleanup(void *pptr) +{ + struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr); + if (!static_thread) + return; + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; + + netdata_log_info("cleaning up..."); + + worker_unregister(); + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; +} + +void *telemetry_thread_sqlite3_main(void *ptr) { + CLEANUP_FUNCTION_REGISTER(telemetry_thread_sqlite3_cleanup) cleanup_ptr = ptr; + telemetry_register_workers(); + + int update_every = + (int)config_get_duration_seconds(CONFIG_SECTION_TELEMETRY, "update every", localhost->rrd_update_every); + if (update_every < localhost->rrd_update_every) { + update_every = localhost->rrd_update_every; + config_set_duration_seconds(CONFIG_SECTION_TELEMETRY, "update every", update_every); + } + + usec_t step = update_every * USEC_PER_SEC; + heartbeat_t hb; + heartbeat_init(&hb, USEC_PER_SEC); + usec_t real_step = USEC_PER_SEC; + + while (service_running(SERVICE_COLLECTORS)) { + worker_is_idle(); + heartbeat_next(&hb); + if (real_step < step) { + real_step += USEC_PER_SEC; + continue; + } + real_step = USEC_PER_SEC; + + worker_is_busy(WORKER_JOB_SQLITE3); + telemetry_sqlite3_do(telemetry_extended_enabled); + } + + return NULL; +} diff --git a/src/daemon/telemetry/telemetry.h b/src/daemon/telemetry/telemetry.h new file mode 100644 index 00000000000000..54f5357ac6c57c --- /dev/null +++ b/src/daemon/telemetry/telemetry.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_TELEMETRY_H +#define NETDATA_TELEMETRY_H 1 + +#include "database/rrd.h" + +extern bool telemetry_enabled; +extern bool telemetry_extended_enabled; + +#include "telemetry-http-api.h" +#include "telemetry-queries.h" +#include "telemetry-ingestion.h" +#include "telemetry-ml.h" +#include "telemetry-gorilla.h" +#include "telemetry-daemon.h" +#include "telemetry-daemon-memory.h" +#include "telemetry-sqlite3.h" +#include "telemetry-dbengine.h" +#include "telemetry-string.h" +#include "telemetry-heartbeat.h" +#include "telemetry-dictionary.h" +#include "telemetry-workers.h" +#include "telemetry-trace-allocations.h" +#include "telemetry-aral.h" + +void *telemetry_thread_main(void *ptr); +void *telemetry_thread_sqlite3_main(void *ptr); + +#endif /* NETDATA_TELEMETRY_H */ diff --git a/src/database/contexts/api_v2_contexts.c b/src/database/contexts/api_v2_contexts.c index bcce23ca02280e..4ec3cf40d1ce35 100644 --- a/src/database/contexts/api_v2_contexts.c +++ b/src/database/contexts/api_v2_contexts.c @@ -215,7 +215,7 @@ static void rrdhost_receiver_to_json(BUFFER *wb, RRDHOST_STATUS *s, const char * buffer_json_member_add_object(wb, key); { buffer_json_member_add_uint64(wb, "id", s->ingest.id); - buffer_json_member_add_uint64(wb, "hops", s->ingest.hops); + buffer_json_member_add_int64(wb, "hops", s->ingest.hops); buffer_json_member_add_string(wb, "type", rrdhost_ingest_type_to_string(s->ingest.type)); buffer_json_member_add_string(wb, "status", rrdhost_ingest_status_to_string(s->ingest.status)); buffer_json_member_add_time_t(wb, "since", s->ingest.since); @@ -272,15 +272,13 @@ static void rrdhost_sender_to_json(BUFFER *wb, RRDHOST_STATUS *s, const char *ke if (s->stream.status == RRDHOST_STREAM_STATUS_OFFLINE) buffer_json_member_add_string(wb, "reason", stream_handshake_error_to_string(s->stream.reason)); - if (s->stream.status == RRDHOST_STREAM_STATUS_REPLICATING) { - buffer_json_member_add_object(wb, "replication"); - { - buffer_json_member_add_boolean(wb, "in_progress", s->stream.replication.in_progress); - buffer_json_member_add_double(wb, "completion", s->stream.replication.completion); - buffer_json_member_add_uint64(wb, "instances", s->stream.replication.instances); - } - buffer_json_object_close(wb); + buffer_json_member_add_object(wb, "replication"); + { + buffer_json_member_add_boolean(wb, "in_progress", s->stream.replication.in_progress); + buffer_json_member_add_double(wb, "completion", s->stream.replication.completion); + buffer_json_member_add_uint64(wb, "instances", s->stream.replication.instances); } + buffer_json_object_close(wb); // replication buffer_json_member_add_object(wb, "destination"); { @@ -300,35 +298,14 @@ static void rrdhost_sender_to_json(BUFFER *wb, RRDHOST_STATUS *s, const char *ke buffer_json_member_add_uint64(wb, "metadata", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_METADATA]); buffer_json_member_add_uint64(wb, "functions", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_FUNCTIONS]); buffer_json_member_add_uint64(wb, "replication", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_REPLICATION]); - buffer_json_member_add_uint64(wb, "dyncfg", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_DYNCFG]); } buffer_json_object_close(wb); // traffic - buffer_json_member_add_array(wb, "candidates"); - struct rrdpush_destinations *d; - for (d = s->host->destinations; d; d = d->next) { - buffer_json_add_array_item_object(wb); - buffer_json_member_add_uint64(wb, "attempts", d->attempts); - { + buffer_json_member_add_array(wb, "parents"); + rrdhost_stream_parents_to_json(wb, s); + buffer_json_array_close(wb); // parents - if (d->ssl) { - snprintfz(buf, sizeof(buf) - 1, "%s:SSL", string2str(d->destination)); - buffer_json_member_add_string(wb, "destination", buf); - } - else - buffer_json_member_add_string(wb, "destination", string2str(d->destination)); - - buffer_json_member_add_time_t(wb, "since", d->since); - buffer_json_member_add_time_t(wb, "age", s->now - d->since); - buffer_json_member_add_string(wb, "last_handshake", stream_handshake_error_to_string(d->reason)); - if(d->postpone_reconnection_until > s->now) { - buffer_json_member_add_time_t(wb, "next_check", d->postpone_reconnection_until); - buffer_json_member_add_time_t(wb, "next_in", d->postpone_reconnection_until - s->now); - } - } - buffer_json_object_close(wb); // each candidate - } - buffer_json_array_close(wb); // candidates + rrdhost_stream_path_to_json(wb, s->host, STREAM_PATH_JSON_MEMBER, false); } buffer_json_object_close(wb); // destination } @@ -365,7 +342,7 @@ static inline void rrdhost_health_to_json_v2(BUFFER *wb, const char *key, RRDHOS buffer_json_member_add_object(wb, key); { buffer_json_member_add_string(wb, "status", rrdhost_health_status_to_string(s->health.status)); - if (s->health.status == RRDHOST_HEALTH_STATUS_RUNNING) { + if (s->health.status == RRDHOST_HEALTH_STATUS_RUNNING || s->health.status == RRDHOST_HEALTH_STATUS_INITIALIZING) { buffer_json_member_add_object(wb, "alerts"); { buffer_json_member_add_uint64(wb, "critical", s->health.alerts.critical); diff --git a/src/database/contexts/api_v2_contexts_agents.c b/src/database/contexts/api_v2_contexts_agents.c index 1bbe31bbd9389a..1c6ddbbabcf410 100644 --- a/src/database/contexts/api_v2_contexts_agents.c +++ b/src/database/contexts/api_v2_contexts_agents.c @@ -49,7 +49,7 @@ void buffer_json_agents_v2(BUFFER *wb, struct query_timings *timings, time_t now available_instances += __atomic_load_n(&host->rrdctx.instances_count, __ATOMIC_RELAXED); available_contexts += __atomic_load_n(&host->rrdctx.contexts_count, __ATOMIC_RELAXED); - if(rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED)) + if(rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_CONNECTED)) sending++; if (rrdhost_is_online(host)) { @@ -103,12 +103,12 @@ void buffer_json_agents_v2(BUFFER *wb, struct query_timings *timings, time_t now buffer_json_object_close(wb); // api buffer_json_member_add_array(wb, "db_size"); - size_t group_seconds = localhost->rrd_update_every; + size_t group_seconds; for (size_t tier = 0; tier < storage_tiers; tier++) { STORAGE_ENGINE *eng = localhost->db[tier].eng; if (!eng) continue; - group_seconds *= storage_tiers_grouping_iterations[tier]; + group_seconds = get_tier_grouping(tier) * localhost->rrd_update_every; uint64_t max = storage_engine_disk_space_max(eng->seb, localhost->db[tier].si); uint64_t used = storage_engine_disk_space_used(eng->seb, localhost->db[tier].si); #ifdef ENABLE_DBENGINE diff --git a/src/database/contexts/api_v2_contexts_alert_config.c b/src/database/contexts/api_v2_contexts_alert_config.c index cd3d8fc143d2de..68015f02737130 100644 --- a/src/database/contexts/api_v2_contexts_alert_config.c +++ b/src/database/contexts/api_v2_contexts_alert_config.c @@ -81,7 +81,7 @@ void contexts_v2_alert_config_to_json_from_sql_alert_config_data(struct sql_aler { buffer_json_member_add_string(wb, "type", "agent"); buffer_json_member_add_string(wb, "exec", t->notification.exec ? t->notification.exec : NULL); - buffer_json_member_add_string(wb, "to", t->notification.to_key ? t->notification.to_key : string2str(localhost->health.health_default_recipient)); + buffer_json_member_add_string(wb, "to", t->notification.to_key ? t->notification.to_key : string2str(localhost->health.default_recipient)); buffer_json_member_add_string(wb, "delay", t->notification.delay); buffer_json_member_add_string(wb, "repeat", t->notification.repeat); buffer_json_member_add_string(wb, "options", t->notification.options); diff --git a/src/database/contexts/api_v2_contexts_alert_transitions.c b/src/database/contexts/api_v2_contexts_alert_transitions.c index 13061f60f07a99..81d84c4909a436 100644 --- a/src/database/contexts/api_v2_contexts_alert_transitions.c +++ b/src/database/contexts/api_v2_contexts_alert_transitions.c @@ -240,7 +240,7 @@ static void contexts_v2_alert_transition_callback(struct sql_alert_transition_da [ATF_CLASS] = t->classification, [ATF_TYPE] = t->type, [ATF_COMPONENT] = t->component, - [ATF_ROLE] = t->recipient && *t->recipient ? t->recipient : string2str(localhost->health.health_default_recipient), + [ATF_ROLE] = t->recipient && *t->recipient ? t->recipient : string2str(localhost->health.default_recipient), [ATF_NODE] = machine_guid, [ATF_ALERT_NAME] = t->alert_name, [ATF_CHART_NAME] = t->chart_name, @@ -411,9 +411,9 @@ void contexts_v2_alert_transitions_to_json(BUFFER *wb, struct rrdcontext_to_json buffer_json_member_add_time_t(wb, "delay", t->delay); buffer_json_member_add_time_t(wb, "delay_up_to_time", t->delay_up_to_timestamp); health_entry_flags_to_json_array(wb, "flags", t->flags); - buffer_json_member_add_string(wb, "exec", *t->exec ? t->exec : string2str(localhost->health.health_default_exec)); + buffer_json_member_add_string(wb, "exec", *t->exec ? t->exec : string2str(localhost->health.default_exec)); buffer_json_member_add_uint64(wb, "exec_code", t->exec_code); - buffer_json_member_add_string(wb, "to", *t->recipient ? t->recipient : string2str(localhost->health.health_default_recipient)); + buffer_json_member_add_string(wb, "to", *t->recipient ? t->recipient : string2str(localhost->health.default_recipient)); } buffer_json_object_close(wb); // notification } diff --git a/src/database/engine/README.md b/src/database/engine/README.md index 3ffee706f840f0..4724ede3e19720 100644 --- a/src/database/engine/README.md +++ b/src/database/engine/README.md @@ -159,7 +159,7 @@ Then `x 2` is the worst case estimate for the dirty queue. If all collected metr The memory we saved with the above is used to improve the LRU cache. So, although we reserved 32MiB for the LRU, in bigger setups (Netdata Parents) the LRU grows a lot more, within the limits of the equation. -In practice, the main cache sizes itself with `hot x 1.5` instead of `host x 2`. The reason is that 5% of the main cache is reserved for expanding open cache, 5% for expanding extent cache, and we need Room for the extensive buffers that are allocated in these setups. When the main cache exceeds `hot x 1.5` it enters a mode of critical evictions, and aggressively frees pages from the LRU to maintain a healthy memory footprint within its design limits. +In practice, the main cache sizes itself with `hot x 1.5` instead of `hot x 2`. The reason is that 5% of the main cache is reserved for expanding open cache, 5% for expanding extent cache, and we need Room for the extensive buffers that are allocated in these setups. When the main cache exceeds `hot x 1.5` it enters a mode of critical evictions, and aggressively frees pages from the LRU to maintain a healthy memory footprint within its design limits. #### Open Cache diff --git a/src/database/engine/cache.c b/src/database/engine/cache.c index f476747648d5eb..1e1e137eaf895b 100644 --- a/src/database/engine/cache.c +++ b/src/database/engine/cache.c @@ -18,7 +18,9 @@ typedef int32_t REFCOUNT; #define REFCOUNT_DELETING (-100) // to use ARAL uncomment the following line: +#if !defined(FSANITIZE_ADDRESS) #define PGC_WITH_ARAL 1 +#endif typedef enum __attribute__ ((__packed__)) { // mutually exclusive flags @@ -68,8 +70,8 @@ struct pgc_page { // THIS STRUCTURE NEEDS TO BE INITIALIZED BY HAND! }; -struct pgc_linked_list { - SPINLOCK spinlock; +struct pgc_queue { + alignas(64) SPINLOCK spinlock; union { PGC_PAGE *base; Pvoid_t sections_judy; @@ -84,6 +86,8 @@ struct pgc_linked_list { struct pgc { struct { char name[PGC_NAME_MAX + 1]; + bool stats; // enable extended statistics + bool use_all_ram; size_t partitions; size_t clean_size; @@ -93,6 +97,7 @@ struct pgc { size_t max_flushes_inline; size_t max_workers_evict_inline; size_t additional_bytes_per_page; + size_t out_of_memory_protection_bytes; free_clean_page_callback pgc_free_clean_cb; save_dirty_page_callback pgc_save_dirty_cb; save_dirty_init_callback pgc_save_init_cb; @@ -104,52 +109,40 @@ struct pgc { size_t evict_low_threshold_per1000; dynamic_target_cache_size_callback dynamic_target_size_cb; + nominal_page_size_callback nominal_page_size_cb; } config; -#ifdef PGC_WITH_ARAL - ARAL **aral; -#endif - - PGC_CACHE_LINE_PADDING(0); + struct { + SPINLOCK spinlock; // when locked, the evict_thread is currently evicting pages + ND_THREAD *thread; // the thread + struct completion completion; // signal the thread to wake up + } evictor; struct pgc_index { - RW_SPINLOCK rw_spinlock; + alignas(64) RW_SPINLOCK rw_spinlock; Pvoid_t sections_judy; - PGC_CACHE_LINE_PADDING(0); } *index; - PGC_CACHE_LINE_PADDING(1); +#ifdef PGC_WITH_ARAL + ARAL *aral; +#endif struct { - SPINLOCK spinlock; + alignas(64) SPINLOCK spinlock; size_t per1000; } usage; - PGC_CACHE_LINE_PADDING(2); - - struct pgc_linked_list clean; // LRU is applied here to free memory from the cache - - PGC_CACHE_LINE_PADDING(3); - - struct pgc_linked_list dirty; // in the dirty list, pages are ordered the way they were marked dirty - - PGC_CACHE_LINE_PADDING(4); - - struct pgc_linked_list hot; // in the hot list, pages are order the way they were marked hot - - PGC_CACHE_LINE_PADDING(5); - + struct pgc_queue clean; // LRU is applied here to free memory from the cache + struct pgc_queue dirty; // in the dirty list, pages are ordered the way they were marked dirty + struct pgc_queue hot; // in the hot list, pages are order the way they were marked hot struct pgc_statistics stats; // statistics #ifdef NETDATA_PGC_POINTER_CHECK - PGC_CACHE_LINE_PADDING(6); - netdata_mutex_t global_pointer_registry_mutex; + alignas(64) netdata_mutex_t global_pointer_registry_mutex; Pvoid_t global_pointer_registry; #endif }; - - // ---------------------------------------------------------------------------- // validate each pointer is indexed once - internal checks only @@ -207,6 +200,17 @@ static inline void pointer_del(PGC *cache __maybe_unused, PGC_PAGE *page __maybe #endif } +// ---------------------------------------------------------------------------- +// helpers + +static inline size_t page_assumed_size(PGC *cache, size_t size) { + return size + (sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page + sizeof(Word_t) * 3); +} + +static inline size_t page_size_from_assumed_size(PGC *cache, size_t assumed_size) { + return assumed_size - (sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page + sizeof(Word_t) * 3); +} + // ---------------------------------------------------------------------------- // locking @@ -235,16 +239,23 @@ static inline void pgc_index_write_lock(PGC *cache, size_t partition) { static inline void pgc_index_write_unlock(PGC *cache, size_t partition) { rw_spinlock_write_unlock(&cache->index[partition].rw_spinlock); } +static inline bool pgc_index_trywrite_lock(PGC *cache, size_t partition, bool force) { + if(force) { + rw_spinlock_write_lock(&cache->index[partition].rw_spinlock); + return true; + } + return rw_spinlock_trywrite_lock(&cache->index[partition].rw_spinlock); +} -static inline bool pgc_ll_trylock(PGC *cache __maybe_unused, struct pgc_linked_list *ll) { +static inline bool pgc_queue_trylock(PGC *cache __maybe_unused, struct pgc_queue *ll) { return spinlock_trylock(&ll->spinlock); } -static inline void pgc_ll_lock(PGC *cache __maybe_unused, struct pgc_linked_list *ll) { +static inline void pgc_queue_lock(PGC *cache __maybe_unused, struct pgc_queue *ll) { spinlock_lock(&ll->spinlock); } -static inline void pgc_ll_unlock(PGC *cache __maybe_unused, struct pgc_linked_list *ll) { +static inline void pgc_queue_unlock(PGC *cache __maybe_unused, struct pgc_queue *ll) { spinlock_unlock(&ll->spinlock); } @@ -260,6 +271,76 @@ static inline void page_transition_unlock(PGC *cache __maybe_unused, PGC_PAGE *p spinlock_unlock(&page->transition_spinlock); } +// ---------------------------------------------------------------------------- +// size histogram + +static void pgc_size_histogram_init(struct pgc_size_histogram *h) { + // the histogram needs to be all-inclusive for the possible sizes + // so, we start from 0, and the last value is SIZE_MAX. + + size_t values[PGC_SIZE_HISTOGRAM_ENTRIES] = { + 0, 32, 64, 128, 256, 512, 1024, 2048, + 4096, 8192, 16384, 32768, 65536, 128 * 1024, SIZE_MAX + }; + + size_t last_value = 0; + for(size_t i = 0; i < PGC_SIZE_HISTOGRAM_ENTRIES; i++) { + if(i > 0 && values[i] == 0) + fatal("only the first value in the array can be zero"); + + if(i > 0 && values[i] <= last_value) + fatal("the values need to be sorted"); + + h->array[i].upto = values[i]; + last_value = values[i]; + } +} + +static inline size_t pgc_size_histogram_slot(struct pgc_size_histogram *h, size_t size) { + if(size <= h->array[0].upto) + return 0; + + if(size >= h->array[_countof(h->array) - 1].upto) + return _countof(h->array) - 1; + + // binary search for the right size + size_t low = 0, high = _countof(h->array) - 1; + while (low < high) { + size_t mid = low + (high - low) / 2; + if (size < h->array[mid].upto) + high = mid; + else + low = mid + 1; + } + return low - 1; +} + +static inline void pgc_size_histogram_add(PGC *cache, struct pgc_size_histogram *h, PGC_PAGE *page) { + size_t size; + if(cache->config.nominal_page_size_cb) + size = cache->config.nominal_page_size_cb(page->data); + else + size = page_size_from_assumed_size(cache, page->assumed_size); + + size_t slot = pgc_size_histogram_slot(h, size); + internal_fatal(slot >= _countof(h->array), "hey!"); + + __atomic_add_fetch(&h->array[slot].count, 1, __ATOMIC_RELAXED); +} + +static inline void pgc_size_histogram_del(PGC *cache, struct pgc_size_histogram *h, PGC_PAGE *page) { + size_t size; + if(cache->config.nominal_page_size_cb) + size = cache->config.nominal_page_size_cb(page->data); + else + size = page_size_from_assumed_size(cache, page->assumed_size); + + size_t slot = pgc_size_histogram_slot(h, size); + internal_fatal(slot >= _countof(h->array), "hey!"); + + __atomic_sub_fetch(&h->array[slot].count, 1, __ATOMIC_RELAXED); +} + // ---------------------------------------------------------------------------- // evictions control @@ -271,48 +352,72 @@ static inline size_t cache_usage_per1000(PGC *cache, size_t *size_to_evict) { else if(!spinlock_trylock(&cache->usage.spinlock)) return __atomic_load_n(&cache->usage.per1000, __ATOMIC_RELAXED); - size_t current_cache_size; size_t wanted_cache_size; - size_t per1000; - size_t dirty = __atomic_load_n(&cache->dirty.stats->size, __ATOMIC_RELAXED); - size_t hot = __atomic_load_n(&cache->hot.stats->size, __ATOMIC_RELAXED); + const size_t dirty = __atomic_load_n(&cache->dirty.stats->size, __ATOMIC_RELAXED); + const size_t hot = __atomic_load_n(&cache->hot.stats->size, __ATOMIC_RELAXED); + const size_t clean = __atomic_load_n(&cache->clean.stats->size, __ATOMIC_RELAXED); + const size_t evicting = __atomic_load_n(&cache->stats.evicting_size, __ATOMIC_RELAXED); + const size_t flushing = __atomic_load_n(&cache->stats.flushing_size, __ATOMIC_RELAXED); + const size_t current_cache_size = __atomic_load_n(&cache->stats.size, __ATOMIC_RELAXED); + const size_t all_pages_size = hot + dirty + clean + evicting + flushing; + const size_t index = current_cache_size > all_pages_size ? current_cache_size - all_pages_size : 0; + const size_t referenced_size = __atomic_load_n(&cache->stats.referenced_size, __ATOMIC_RELAXED); if(cache->config.options & PGC_OPTIONS_AUTOSCALE) { - size_t dirty_max = __atomic_load_n(&cache->dirty.stats->max_size, __ATOMIC_RELAXED); - size_t hot_max = __atomic_load_n(&cache->hot.stats->max_size, __ATOMIC_RELAXED); + const size_t dirty_max = __atomic_load_n(&cache->dirty.stats->max_size, __ATOMIC_RELAXED); + const size_t hot_max = __atomic_load_n(&cache->hot.stats->max_size, __ATOMIC_RELAXED); // our promise to users - size_t max_size1 = MAX(hot_max, hot) * 2; + const size_t max_size1 = MAX(hot_max, hot) * 2; // protection against slow flushing - size_t max_size2 = hot_max + ((dirty_max < hot_max / 2) ? hot_max / 2 : dirty_max * 2); + const size_t max_size2 = hot_max + ((dirty_max * 2 < hot_max * 2 / 3) ? hot_max * 2 / 3 : dirty_max * 2) + index; // the final wanted cache size wanted_cache_size = MIN(max_size1, max_size2); if(cache->config.dynamic_target_size_cb) { - size_t wanted_cache_size_cb = cache->config.dynamic_target_size_cb(); + const size_t wanted_cache_size_cb = cache->config.dynamic_target_size_cb(); if(wanted_cache_size_cb > wanted_cache_size) wanted_cache_size = wanted_cache_size_cb; } - if (wanted_cache_size < hot + dirty + cache->config.clean_size) - wanted_cache_size = hot + dirty + cache->config.clean_size; + if (wanted_cache_size < hot + dirty + index + cache->config.clean_size) + wanted_cache_size = hot + dirty + index + cache->config.clean_size; } else - wanted_cache_size = hot + dirty + cache->config.clean_size; + wanted_cache_size = hot + dirty + index + cache->config.clean_size; - // protection again huge queries + // protection against huge queries // if huge queries are running, or huge amounts need to be saved - // allow the cache to grow more (hot pages in main cache are also referenced) - size_t referenced_size = __atomic_load_n(&cache->stats.referenced_size, __ATOMIC_RELAXED); - if(unlikely(wanted_cache_size < referenced_size * 2 / 3)) - wanted_cache_size = referenced_size * 2 / 3; - - current_cache_size = __atomic_load_n(&cache->stats.size, __ATOMIC_RELAXED); // + pgc_aral_overhead(); + // allow the cache to grow more (hot pages in the main cache are also referenced) + if(unlikely(wanted_cache_size < referenced_size + dirty)) + wanted_cache_size = referenced_size + dirty; + + // if we don't have enough clean pages, there is no reason to be aggressive or critical + if(current_cache_size > wanted_cache_size && wanted_cache_size < current_cache_size - clean) + wanted_cache_size = current_cache_size - clean; + + if(cache->config.out_of_memory_protection_bytes) { + // out of memory protection + OS_SYSTEM_MEMORY sm = os_system_memory(false); + if(sm.ram_total_bytes) { + // when the total exists, ram_available_bytes is also right + + const size_t min_available = cache->config.out_of_memory_protection_bytes; + if (sm.ram_available_bytes < min_available) { + // we must shrink + wanted_cache_size = current_cache_size - (min_available - sm.ram_available_bytes); + } + else if(cache->config.use_all_ram) { + // we can grow + wanted_cache_size = current_cache_size + (sm.ram_available_bytes - min_available); + } + } + } - per1000 = (size_t)((unsigned long long)current_cache_size * 1000ULL / (unsigned long long)wanted_cache_size); + const size_t per1000 = (size_t)((unsigned long long)current_cache_size * 1000ULL / (unsigned long long)wanted_cache_size); __atomic_store_n(&cache->usage.per1000, per1000, __ATOMIC_RELAXED); __atomic_store_n(&cache->stats.wanted_cache_size, wanted_cache_size, __ATOMIC_RELAXED); @@ -321,7 +426,11 @@ static inline size_t cache_usage_per1000(PGC *cache, size_t *size_to_evict) { spinlock_unlock(&cache->usage.spinlock); if(size_to_evict) { - size_t target = (size_t)((unsigned long long)wanted_cache_size * (unsigned long long)cache->config.evict_low_threshold_per1000 / 1000ULL); + size_t target = (size_t)((uint64_t)wanted_cache_size * (uint64_t)cache->config.evict_low_threshold_per1000 / 1000ULL); + + if(target < wanted_cache_size - clean) + target = wanted_cache_size - clean; + if(current_cache_size > target) *size_to_evict = current_cache_size - target; else @@ -349,24 +458,67 @@ typedef bool (*evict_filter)(PGC_PAGE *page, void *data); static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evict, bool wait, bool all_of_them, evict_filter filter, void *data); #define evict_pages(cache, max_skip, max_evict, wait, all_of_them) evict_pages_with_filter(cache, max_skip, max_evict, wait, all_of_them, NULL, NULL) -static inline void evict_on_clean_page_added(PGC *cache __maybe_unused) { - if((cache->config.options & PGC_OPTIONS_EVICT_PAGES_INLINE) || cache_needs_space_aggressively(cache)) { - evict_pages(cache, - cache->config.max_skip_pages_per_inline_eviction, - cache->config.max_pages_per_inline_eviction, - false, false); +static inline bool flushing_critical(PGC *cache); +static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wait, bool all_of_them); + +static void signal_evict_thread_or_evict_inline(PGC *cache, bool on_release) { + const size_t per1000 = cache_usage_per1000(cache, NULL); + + if (per1000 >= cache->config.healthy_size_per1000 && spinlock_trylock(&cache->evictor.spinlock)) { + __atomic_add_fetch(&cache->stats.waste_evict_thread_signals, 1, __ATOMIC_RELAXED); + completion_mark_complete_a_job(&cache->evictor.completion); + spinlock_unlock(&cache->evictor.spinlock); + } + + if(!(cache->config.options & PGC_OPTIONS_EVICT_PAGES_NO_INLINE)) { + if (per1000 > cache->config.aggressive_evict_per1000 && !on_release) { + // the threads that add pages, turn into evictors when the cache needs evictions aggressively + __atomic_add_fetch(&cache->stats.waste_evictions_inline_on_add, 1, __ATOMIC_RELAXED); + evict_pages(cache, + cache->config.max_skip_pages_per_inline_eviction, + cache->config.max_pages_per_inline_eviction, + false, false); + } + else if (per1000 > cache->config.severe_pressure_per1000 && on_release) { + // the threads that are releasing pages, turn into evictors when the cache is critical + __atomic_add_fetch(&cache->stats.waste_evictions_inline_on_release, 1, __ATOMIC_RELAXED); + + evict_pages(cache, + cache->config.max_skip_pages_per_inline_eviction, + cache->config.max_pages_per_inline_eviction, + false, false); + } } } -static inline void evict_on_page_release_when_permitted(PGC *cache __maybe_unused) { - if ((cache->config.options & PGC_OPTIONS_EVICT_PAGES_INLINE) || cache_under_severe_pressure(cache)) { - evict_pages(cache, - cache->config.max_skip_pages_per_inline_eviction, - cache->config.max_pages_per_inline_eviction, - false, false); +static inline void evict_on_clean_page_added(PGC *cache) { + signal_evict_thread_or_evict_inline(cache, false); +} + +static inline void evict_on_page_release_when_permitted(PGC *cache) { + signal_evict_thread_or_evict_inline(cache, true); +} + +static inline void flush_inline(PGC *cache, bool on_release) { + if(!(cache->config.options & PGC_OPTIONS_FLUSH_PAGES_NO_INLINE) && flushing_critical(cache)) { + if (on_release) + __atomic_add_fetch(&cache->stats.waste_flush_on_release, 1, __ATOMIC_RELAXED); + else + __atomic_add_fetch(&cache->stats.waste_flush_on_add, 1, __ATOMIC_RELAXED); + + flush_pages(cache, cache->config.max_flushes_inline, PGC_SECTION_ALL, false, false); } } +static inline void flush_on_page_add(PGC *cache) { + flush_inline(cache, false); +} + +static inline void flush_on_page_hot_release(PGC *cache) { + flush_inline(cache, true); +} + + // ---------------------------------------------------------------------------- // flushing control @@ -381,17 +533,6 @@ static inline bool flushing_critical(PGC *cache) { return false; } -// ---------------------------------------------------------------------------- -// helpers - -static inline size_t page_assumed_size(PGC *cache, size_t size) { - return size + (sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page + sizeof(Word_t) * 3); -} - -static inline size_t page_size_from_assumed_size(PGC *cache, size_t assumed_size) { - return assumed_size - (sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page + sizeof(Word_t) * 3); -} - // ---------------------------------------------------------------------------- // Linked list management @@ -416,27 +557,34 @@ struct section_pages { PGC_PAGE *base; }; -static ARAL *pgc_section_pages_aral = NULL; +static struct aral_statistics aral_statistics_for_pgc = { 0 }; + +static ARAL *pgc_sections_aral = NULL; +static ARAL *pgc_pages_aral = NULL; + static void pgc_section_pages_static_aral_init(void) { static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; - if(unlikely(!pgc_section_pages_aral)) { - spinlock_lock(&spinlock); + spinlock_lock(&spinlock); - // we have to check again - if(!pgc_section_pages_aral) - pgc_section_pages_aral = aral_create( - "pgc_section", - sizeof(struct section_pages), - 0, - 65536, NULL, - NULL, NULL, false, false); + if(!pgc_sections_aral) + pgc_sections_aral = aral_by_size_acquire(sizeof(struct section_pages)); - spinlock_unlock(&spinlock); + if(!pgc_pages_aral) { + pgc_pages_aral = aral_create( + "pgc_pages", + sizeof(PGC_PAGE), + 0, + 0, + &aral_statistics_for_pgc, + NULL, NULL, false, false); } + + spinlock_unlock(&spinlock); } -static inline void pgc_stats_ll_judy_change(PGC *cache, struct pgc_linked_list *ll, size_t mem_before_judyl, size_t mem_after_judyl) { +static inline void +pgc_stats_queue_judy_change(PGC *cache, struct pgc_queue *ll, size_t mem_before_judyl, size_t mem_after_judyl) { if(mem_after_judyl > mem_before_judyl) { __atomic_add_fetch(&ll->stats->size, mem_after_judyl - mem_before_judyl, __ATOMIC_RELAXED); __atomic_add_fetch(&cache->stats.size, mem_after_judyl - mem_before_judyl, __ATOMIC_RELAXED); @@ -456,88 +604,98 @@ static inline void pgc_stats_index_judy_change(PGC *cache, size_t mem_before_jud } } -static void pgc_ll_add(PGC *cache __maybe_unused, struct pgc_linked_list *ll, PGC_PAGE *page, bool having_lock) { +static void pgc_queue_add(PGC *cache __maybe_unused, struct pgc_queue *q, PGC_PAGE *page, bool having_lock) { if(!having_lock) - pgc_ll_lock(cache, ll); + pgc_queue_lock(cache, q); internal_fatal(page_get_status_flags(page) != 0, "DBENGINE CACHE: invalid page flags, the page has %d, but it is should be %d", page_get_status_flags(page), 0); - if(ll->linked_list_in_sections_judy) { + if(q->linked_list_in_sections_judy) { + // HOT and DIRTY pages end up here. + size_t mem_before_judyl, mem_after_judyl; - mem_before_judyl = JudyLMemUsed(ll->sections_judy); - Pvoid_t *section_pages_pptr = JudyLIns(&ll->sections_judy, page->section, PJE0); - mem_after_judyl = JudyLMemUsed(ll->sections_judy); + mem_before_judyl = JudyLMemUsed(q->sections_judy); + Pvoid_t *section_pages_pptr = JudyLIns(&q->sections_judy, page->section, PJE0); + mem_after_judyl = JudyLMemUsed(q->sections_judy); struct section_pages *sp = *section_pages_pptr; if(!sp) { // sp = callocz(1, sizeof(struct section_pages)); - sp = aral_mallocz(pgc_section_pages_aral); + sp = aral_mallocz(pgc_sections_aral); memset(sp, 0, sizeof(struct section_pages)); *section_pages_pptr = sp; mem_after_judyl += sizeof(struct section_pages); } - pgc_stats_ll_judy_change(cache, ll, mem_before_judyl, mem_after_judyl); + pgc_stats_queue_judy_change(cache, q, mem_before_judyl, mem_after_judyl); sp->entries++; sp->size += page->assumed_size; DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(sp->base, page, link.prev, link.next); if((sp->entries % cache->config.max_dirty_pages_per_call) == 0) - ll->version++; + q->version++; } else { // CLEAN pages end up here. // - New pages created as CLEAN, always have 1 access. // - DIRTY pages made CLEAN, depending on their accesses may be appended (accesses > 0) or prepended (accesses = 0). - if(page->accesses || page_flag_check(page, PGC_PAGE_HAS_BEEN_ACCESSED | PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES) == PGC_PAGE_HAS_BEEN_ACCESSED) { - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ll->base, page, link.prev, link.next); + // FIXME - is it better for fragmentation to always append? + +// if(page->accesses || page_flag_check(page, PGC_PAGE_HAS_BEEN_ACCESSED | PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES) == PGC_PAGE_HAS_BEEN_ACCESSED) { + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(q->base, page, link.prev, link.next); page_flag_clear(page, PGC_PAGE_HAS_BEEN_ACCESSED); - } - else - DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ll->base, page, link.prev, link.next); +// } +// else +// DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(q->base, page, link.prev, link.next); - ll->version++; + q->version++; } - page_flag_set(page, ll->flags); + page_flag_set(page, q->flags); if(!having_lock) - pgc_ll_unlock(cache, ll); + pgc_queue_unlock(cache, q); + + size_t entries = __atomic_add_fetch(&q->stats->entries, 1, __ATOMIC_RELAXED); + size_t size = __atomic_add_fetch(&q->stats->size, page->assumed_size, __ATOMIC_RELAXED); + __atomic_add_fetch(&q->stats->added_entries, 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&q->stats->added_size, page->assumed_size, __ATOMIC_RELAXED); - size_t entries = __atomic_add_fetch(&ll->stats->entries, 1, __ATOMIC_RELAXED); - size_t size = __atomic_add_fetch(&ll->stats->size, page->assumed_size, __ATOMIC_RELAXED); - __atomic_add_fetch(&ll->stats->added_entries, 1, __ATOMIC_RELAXED); - __atomic_add_fetch(&ll->stats->added_size, page->assumed_size, __ATOMIC_RELAXED); + atomic_set_max(&q->stats->max_entries, entries); + atomic_set_max(&q->stats->max_size, size); - atomic_set_max(&ll->stats->max_entries, entries); - atomic_set_max(&ll->stats->max_size, size); + if(cache->config.stats) + pgc_size_histogram_add(cache, &q->stats->size_histogram, page); } -static void pgc_ll_del(PGC *cache __maybe_unused, struct pgc_linked_list *ll, PGC_PAGE *page, bool having_lock) { - __atomic_sub_fetch(&ll->stats->entries, 1, __ATOMIC_RELAXED); - __atomic_sub_fetch(&ll->stats->size, page->assumed_size, __ATOMIC_RELAXED); - __atomic_add_fetch(&ll->stats->removed_entries, 1, __ATOMIC_RELAXED); - __atomic_add_fetch(&ll->stats->removed_size, page->assumed_size, __ATOMIC_RELAXED); +static void pgc_queue_del(PGC *cache __maybe_unused, struct pgc_queue *q, PGC_PAGE *page, bool having_lock) { + if(cache->config.stats) + pgc_size_histogram_del(cache, &q->stats->size_histogram, page); + + __atomic_sub_fetch(&q->stats->entries, 1, __ATOMIC_RELAXED); + __atomic_sub_fetch(&q->stats->size, page->assumed_size, __ATOMIC_RELAXED); + __atomic_add_fetch(&q->stats->removed_entries, 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&q->stats->removed_size, page->assumed_size, __ATOMIC_RELAXED); if(!having_lock) - pgc_ll_lock(cache, ll); + pgc_queue_lock(cache, q); - internal_fatal(page_get_status_flags(page) != ll->flags, + internal_fatal(page_get_status_flags(page) != q->flags, "DBENGINE CACHE: invalid page flags, the page has %d, but it is should be %d", page_get_status_flags(page), - ll->flags); + q->flags); - page_flag_clear(page, ll->flags); + page_flag_clear(page, q->flags); - if(ll->linked_list_in_sections_judy) { - Pvoid_t *section_pages_pptr = JudyLGet(ll->sections_judy, page->section, PJE0); + if(q->linked_list_in_sections_judy) { + Pvoid_t *section_pages_pptr = JudyLGet(q->sections_judy, page->section, PJE0); internal_fatal(!section_pages_pptr, "DBENGINE CACHE: page should be in Judy LL, but it is not"); struct section_pages *sp = *section_pages_pptr; @@ -548,26 +706,26 @@ static void pgc_ll_del(PGC *cache __maybe_unused, struct pgc_linked_list *ll, PG if(!sp->base) { size_t mem_before_judyl, mem_after_judyl; - mem_before_judyl = JudyLMemUsed(ll->sections_judy); - int rc = JudyLDel(&ll->sections_judy, page->section, PJE0); - mem_after_judyl = JudyLMemUsed(ll->sections_judy); + mem_before_judyl = JudyLMemUsed(q->sections_judy); + int rc = JudyLDel(&q->sections_judy, page->section, PJE0); + mem_after_judyl = JudyLMemUsed(q->sections_judy); if(!rc) fatal("DBENGINE CACHE: cannot delete section from Judy LL"); // freez(sp); - aral_freez(pgc_section_pages_aral, sp); + aral_freez(pgc_sections_aral, sp); mem_after_judyl -= sizeof(struct section_pages); - pgc_stats_ll_judy_change(cache, ll, mem_before_judyl, mem_after_judyl); + pgc_stats_queue_judy_change(cache, q, mem_before_judyl, mem_after_judyl); } } else { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ll->base, page, link.prev, link.next); - ll->version++; + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(q->base, page, link.prev, link.next); + q->version++; } if(!having_lock) - pgc_ll_unlock(cache, ll); + pgc_queue_unlock(cache, q); } static inline void page_has_been_accessed(PGC *cache, PGC_PAGE *page) { @@ -577,10 +735,10 @@ static inline void page_has_been_accessed(PGC *cache, PGC_PAGE *page) { __atomic_add_fetch(&page->accesses, 1, __ATOMIC_RELAXED); if (flags & PGC_PAGE_CLEAN) { - if(pgc_ll_trylock(cache, &cache->clean)) { + if(pgc_queue_trylock(cache, &cache->clean)) { DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next); DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next); - pgc_ll_unlock(cache, &cache->clean); + pgc_queue_unlock(cache, &cache->clean); page_flag_clear(page, PGC_PAGE_HAS_BEEN_ACCESSED); } else @@ -606,13 +764,13 @@ static inline void page_set_clean(PGC *cache, PGC_PAGE *page, bool having_transi } if(flags & PGC_PAGE_HOT) - pgc_ll_del(cache, &cache->hot, page, false); + pgc_queue_del(cache, &cache->hot, page, false); if(flags & PGC_PAGE_DIRTY) - pgc_ll_del(cache, &cache->dirty, page, false); + pgc_queue_del(cache, &cache->dirty, page, false); // first add to linked list, the set the flag (required for move_page_last()) - pgc_ll_add(cache, &cache->clean, page, having_clean_lock); + pgc_queue_add(cache, &cache->clean, page, having_clean_lock); if(!having_transition_lock) page_transition_unlock(cache, page); @@ -622,7 +780,7 @@ static inline void page_set_dirty(PGC *cache, PGC_PAGE *page, bool having_hot_lo if(!having_hot_lock) // to avoid deadlocks, we have to get the hot lock before the page transition // since this is what all_hot_to_dirty() does - pgc_ll_lock(cache, &cache->hot); + pgc_queue_lock(cache, &cache->hot); page_transition_lock(cache, page); @@ -633,7 +791,7 @@ static inline void page_set_dirty(PGC *cache, PGC_PAGE *page, bool having_hot_lo if(!having_hot_lock) // we don't need the hot lock anymore - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); return; } @@ -642,17 +800,17 @@ static inline void page_set_dirty(PGC *cache, PGC_PAGE *page, bool having_hot_lo __atomic_add_fetch(&cache->stats.hot2dirty_size, page->assumed_size, __ATOMIC_RELAXED); if(likely(flags & PGC_PAGE_HOT)) - pgc_ll_del(cache, &cache->hot, page, true); + pgc_queue_del(cache, &cache->hot, page, true); if(!having_hot_lock) // we don't need the hot lock anymore - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); if(unlikely(flags & PGC_PAGE_CLEAN)) - pgc_ll_del(cache, &cache->clean, page, false); + pgc_queue_del(cache, &cache->clean, page, false); // first add to linked list, the set the flag (required for move_page_last()) - pgc_ll_add(cache, &cache->dirty, page, false); + pgc_queue_add(cache, &cache->dirty, page, false); __atomic_sub_fetch(&cache->stats.hot2dirty_entries, 1, __ATOMIC_RELAXED); __atomic_sub_fetch(&cache->stats.hot2dirty_size, page->assumed_size, __ATOMIC_RELAXED); @@ -671,13 +829,13 @@ static inline void page_set_hot(PGC *cache, PGC_PAGE *page) { } if(flags & PGC_PAGE_DIRTY) - pgc_ll_del(cache, &cache->dirty, page, false); + pgc_queue_del(cache, &cache->dirty, page, false); if(flags & PGC_PAGE_CLEAN) - pgc_ll_del(cache, &cache->clean, page, false); + pgc_queue_del(cache, &cache->clean, page, false); // first add to linked list, the set the flag (required for move_page_last()) - pgc_ll_add(cache, &cache->hot, page, false); + pgc_queue_add(cache, &cache->hot, page, false); page_transition_unlock(cache, page); } @@ -722,7 +880,7 @@ static inline bool page_acquire(PGC *cache, PGC_PAGE *page) { } while(!__atomic_compare_exchange_n(&page->refcount, &expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); if(unlikely(spins > 1)) - __atomic_add_fetch(&cache->stats.acquire_spins, spins - 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.waste_acquire_spins, spins - 1, __ATOMIC_RELAXED); if(desired == 1) PGC_REFERENCED_PAGES_PLUS1(cache, page); @@ -750,7 +908,7 @@ static inline void page_release(PGC *cache, PGC_PAGE *page, bool evict_if_necess } while(!__atomic_compare_exchange_n(&page->refcount, &expected, desired, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED)); if(unlikely(spins > 1)) - __atomic_add_fetch(&cache->stats.release_spins, spins - 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.waste_release_spins, spins - 1, __ATOMIC_RELAXED); if(desired == 0) { PGC_REFERENCED_PAGES_MINUS1(cache, assumed_size); @@ -795,7 +953,7 @@ static inline bool non_acquired_page_get_for_deletion___while_having_clean_locke } if(unlikely(spins > 1)) - __atomic_add_fetch(&cache->stats.delete_spins, spins - 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.waste_delete_spins, spins - 1, __ATOMIC_RELAXED); return delete_it; } @@ -841,7 +999,7 @@ static inline bool acquired_page_get_for_deletion_or_release_it(PGC *cache __may } if(unlikely(spins > 1)) - __atomic_add_fetch(&cache->stats.delete_spins, spins - 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.waste_delete_spins, spins - 1, __ATOMIC_RELAXED); return delete_it; } @@ -851,6 +1009,8 @@ static inline bool acquired_page_get_for_deletion_or_release_it(PGC *cache __may // Indexing static inline void free_this_page(PGC *cache, PGC_PAGE *page, size_t partition __maybe_unused) { + size_t size = page_size_from_assumed_size(cache, page->assumed_size); + // call the callback to free the user supplied memory cache->config.pgc_free_clean_cb(cache, (PGC_ENTRY){ .section = page->section, @@ -858,12 +1018,14 @@ static inline void free_this_page(PGC *cache, PGC_PAGE *page, size_t partition _ .start_time_s = page->start_time_s, .end_time_s = __atomic_load_n(&page->end_time_s, __ATOMIC_RELAXED), .update_every_s = page->update_every_s, - .size = page_size_from_assumed_size(cache, page->assumed_size), + .size = size, .hot = (is_page_hot(page)) ? true : false, .data = page->data, .custom_data = (cache->config.additional_bytes_per_page) ? page->custom_data : NULL, }); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_CB); + // update statistics __atomic_add_fetch(&cache->stats.removed_entries, 1, __ATOMIC_RELAXED); __atomic_add_fetch(&cache->stats.removed_size, page->assumed_size, __ATOMIC_RELAXED); @@ -871,12 +1033,16 @@ static inline void free_this_page(PGC *cache, PGC_PAGE *page, size_t partition _ __atomic_sub_fetch(&cache->stats.entries, 1, __ATOMIC_RELAXED); __atomic_sub_fetch(&cache->stats.size, page->assumed_size, __ATOMIC_RELAXED); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_ATOMICS2); + // free our memory #ifdef PGC_WITH_ARAL - aral_freez(cache->aral[partition], page); + aral_freez(cache->aral, page); #else freez(page); #endif + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_ARAL); } static void remove_this_page_from_index_unsafe(PGC *cache, PGC_PAGE *page, size_t partition) { @@ -948,20 +1114,20 @@ static inline bool make_acquired_page_clean_and_evict_or_page_release(PGC *cache pointer_check(cache, page); page_transition_lock(cache, page); - pgc_ll_lock(cache, &cache->clean); + pgc_queue_lock(cache, &cache->clean); // make it clean - it does not have any accesses, so it will be prepended page_set_clean(cache, page, true, true); if(!acquired_page_get_for_deletion_or_release_it(cache, page)) { - pgc_ll_unlock(cache, &cache->clean); + pgc_queue_unlock(cache, &cache->clean); page_transition_unlock(cache, page); return false; } // remove it from the linked list - pgc_ll_del(cache, &cache->clean, page, true); - pgc_ll_unlock(cache, &cache->clean); + pgc_queue_del(cache, &cache->clean, page, true); + pgc_queue_unlock(cache, &cache->clean); page_transition_unlock(cache, page); remove_and_free_page_not_in_any_queue_and_acquired_for_deletion(cache, page); @@ -969,7 +1135,7 @@ static inline bool make_acquired_page_clean_and_evict_or_page_release(PGC *cache return true; } -// returns true, when there is more work to do +// returns true, when there is potentially more work to do static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evict, bool wait, bool all_of_them, evict_filter filter, void *data) { size_t per1000 = cache_usage_per1000(cache, NULL); @@ -977,8 +1143,9 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic // don't bother - not enough to do anything return false; + bool under_sever_pressure = per1000 >= cache->config.severe_pressure_per1000; size_t workers_running = __atomic_add_fetch(&cache->stats.workers_evict, 1, __ATOMIC_RELAXED); - if(!wait && !all_of_them && workers_running > cache->config.max_workers_evict_inline && per1000 < cache->config.severe_pressure_per1000) { + if(!wait && !all_of_them && workers_running > cache->config.max_workers_evict_inline && !under_sever_pressure) { __atomic_sub_fetch(&cache->stats.workers_evict, 1, __ATOMIC_RELAXED); return false; } @@ -996,31 +1163,46 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic else if(unlikely(max_evict < 2)) max_evict = 2; + size_t this_loop_evicted = 0; size_t total_pages_evicted = 0; - size_t total_pages_skipped = 0; + size_t total_pages_relocated = 0; bool stopped_before_finishing = false; size_t spins = 0; + size_t max_pages_to_evict = 0; do { - if(++spins > 1) - __atomic_add_fetch(&cache->stats.evict_spins, 1, __ATOMIC_RELAXED); - - bool batch; size_t max_size_to_evict = 0; if (unlikely(all_of_them)) { + // evict them all max_size_to_evict = SIZE_MAX; - batch = true; + max_pages_to_evict = SIZE_MAX; + under_sever_pressure = true; } else if(unlikely(wait)) { + // evict as many as necessary for the cache to go at the predefined threshold per1000 = cache_usage_per1000(cache, &max_size_to_evict); - batch = (wait && per1000 > cache->config.severe_pressure_per1000) ? true : false; + if(per1000 >= cache->config.severe_pressure_per1000) { + under_sever_pressure = true; + max_pages_to_evict = max_pages_to_evict ? max_pages_to_evict * 2 : 4096; + // max_pages_to_evict = 1; + } + else if(per1000 >= cache->config.aggressive_evict_per1000) { + under_sever_pressure = false; + max_pages_to_evict = max_pages_to_evict ? max_pages_to_evict * 2 : 128; + // max_pages_to_evict = 1; + } + else { + under_sever_pressure = false; + max_pages_to_evict = 1; + } } else { - batch = false; + // this is an adder, so evict just 1 page max_size_to_evict = (cache_above_healthy_limit(cache)) ? 1 : 0; + max_pages_to_evict = 1; } - if (!max_size_to_evict) + if (!max_size_to_evict || !max_pages_to_evict) break; // check if we have to stop @@ -1029,8 +1211,15 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic break; } + if(++spins > 1 && !this_loop_evicted) + __atomic_add_fetch(&cache->stats.waste_evict_useless_spins, 1, __ATOMIC_RELAXED); + + this_loop_evicted = 0; + + timing_dbengine_evict_init(); + if(!all_of_them && !wait) { - if(!pgc_ll_trylock(cache, &cache->clean)) { + if(!pgc_queue_trylock(cache, &cache->clean)) { stopped_before_finishing = true; goto premature_exit; } @@ -1038,11 +1227,14 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic // at this point we have the clean lock } else - pgc_ll_lock(cache, &cache->clean); + pgc_queue_lock(cache, &cache->clean); + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_LOCK); // find a page to evict PGC_PAGE *pages_to_evict = NULL; size_t pages_to_evict_size = 0; + size_t pages_to_evict_count = 0; for(PGC_PAGE *page = cache->clean.base, *next = NULL, *first_page_we_relocated = NULL; page ; page = next) { next = page->link.next; @@ -1064,7 +1256,7 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic // we can delete this page // remove it from the clean list - pgc_ll_del(cache, &cache->clean, page, true); + pgc_queue_del(cache, &cache->clean, page, true); __atomic_add_fetch(&cache->stats.evicting_entries, 1, __ATOMIC_RELAXED); __atomic_add_fetch(&cache->stats.evicting_size, page->assumed_size, __ATOMIC_RELAXED); @@ -1072,8 +1264,11 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(pages_to_evict, page, link.prev, link.next); pages_to_evict_size += page->assumed_size; + pages_to_evict_count++; - if(unlikely(all_of_them || (batch && pages_to_evict_size < max_size_to_evict))) + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_SELECT_PAGE); + + if((pages_to_evict_count < max_pages_to_evict && pages_to_evict_size < max_size_to_evict) || all_of_them) // get more pages ; else @@ -1089,14 +1284,20 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next); DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next); + total_pages_relocated++; + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_RELOCATE_PAGE); + // check if we have to stop - if(unlikely(++total_pages_skipped >= max_skip && !all_of_them)) { + if(unlikely(total_pages_relocated >= max_skip && !all_of_them)) { stopped_before_finishing = true; break; } } } - pgc_ll_unlock(cache, &cache->clean); + pgc_queue_unlock(cache, &cache->clean); + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_SELECT); if(likely(pages_to_evict)) { // remove them from the index @@ -1107,6 +1308,9 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic PGC_PAGE *pages_per_partition[cache->config.partitions]; memset(pages_per_partition, 0, sizeof(PGC_PAGE *) * cache->config.partitions); + bool partitions_done[cache->config.partitions]; + memset(partitions_done, 0, sizeof(bool) * cache->config.partitions); + // sort them by partition for (PGC_PAGE *page = pages_to_evict, *next = NULL; page; page = next) { next = page->link.next; @@ -1116,18 +1320,37 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(pages_per_partition[partition], page, link.prev, link.next); } + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_SORT); + // remove them from the index - for (size_t partition = 0; partition < cache->config.partitions; partition++) { - if (!pages_per_partition[partition]) continue; + size_t remaining_partitions = cache->config.partitions; + size_t last_remaining_partitions = remaining_partitions + 1; + while(remaining_partitions) { + bool force = remaining_partitions == last_remaining_partitions; + last_remaining_partitions = remaining_partitions; + remaining_partitions = 0; + + for (size_t partition = 0; partition < cache->config.partitions; partition++) { + if (!pages_per_partition[partition] || partitions_done[partition]) + continue; - pgc_index_write_lock(cache, partition); + if(pgc_index_trywrite_lock(cache, partition, force)) { + partitions_done[partition] = true; - for (PGC_PAGE *page = pages_per_partition[partition]; page; page = page->link.next) - remove_this_page_from_index_unsafe(cache, page, partition); + for (PGC_PAGE *page = pages_per_partition[partition]; page; page = page->link.next) + remove_this_page_from_index_unsafe(cache, page, partition); - pgc_index_write_unlock(cache, partition); + pgc_index_write_unlock(cache, partition); + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_DEINDEX_PAGE); + } + else + remaining_partitions++; + } } + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_DEINDEX); + // free them for (size_t partition = 0; partition < cache->config.partitions; partition++) { if (!pages_per_partition[partition]) continue; @@ -1135,15 +1358,24 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic for (PGC_PAGE *page = pages_per_partition[partition], *next = NULL; page; page = next) { next = page->link.next; + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_LOOP); + size_t page_size = page->assumed_size; free_this_page(cache, page, partition); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_PAGE); + __atomic_sub_fetch(&cache->stats.evicting_entries, 1, __ATOMIC_RELAXED); __atomic_sub_fetch(&cache->stats.evicting_size, page_size, __ATOMIC_RELAXED); total_pages_evicted++; + this_loop_evicted++; + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_ATOMICS); } } + + timing_dbengine_evict_report(); } else { // just one page to be evicted @@ -1161,27 +1393,29 @@ static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evic __atomic_sub_fetch(&cache->stats.evicting_size, page_size, __ATOMIC_RELAXED); total_pages_evicted++; + this_loop_evicted++; } } else break; - } while(all_of_them || (total_pages_evicted < max_evict && total_pages_skipped < max_skip)); + } while(all_of_them || (total_pages_evicted < max_evict && total_pages_relocated < max_skip)); if(all_of_them && !filter) { - pgc_ll_lock(cache, &cache->clean); - if(cache->clean.stats->entries) { + pgc_queue_lock(cache, &cache->clean); + size_t entries = __atomic_load_n(&cache->clean.stats->entries, __ATOMIC_RELAXED); + if(entries) { nd_log_limit_static_global_var(erl, 1, 0); nd_log_limit(&erl, NDLS_DAEMON, NDLP_NOTICE, "DBENGINE CACHE: cannot free all clean pages, %zu are still in the clean queue", - cache->clean.stats->entries); + entries); } - pgc_ll_unlock(cache, &cache->clean); + pgc_queue_unlock(cache, &cache->clean); } premature_exit: - if(unlikely(total_pages_skipped)) - __atomic_add_fetch(&cache->stats.evict_skipped, total_pages_skipped, __ATOMIC_RELAXED); + if(unlikely(total_pages_relocated)) + __atomic_add_fetch(&cache->stats.waste_evict_relocated, total_pages_relocated, __ATOMIC_RELAXED); __atomic_sub_fetch(&cache->stats.workers_evict, 1, __ATOMIC_RELAXED); @@ -1197,7 +1431,7 @@ static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) { size_t partition = pgc_indexing_partition(cache, entry->metric_id); #ifdef PGC_WITH_ARAL - PGC_PAGE *allocation = aral_mallocz(cache->aral[partition]); + PGC_PAGE *allocation = aral_mallocz(cache->aral); #endif PGC_PAGE *page; size_t spins = 0; @@ -1210,7 +1444,7 @@ static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) { do { if(++spins > 1) - __atomic_add_fetch(&cache->stats.insert_spins, 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.waste_insert_spins, 1, __ATOMIC_RELAXED); pgc_index_write_lock(cache, partition); @@ -1300,7 +1534,7 @@ static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) { if(unlikely(!page)) { // now that we don't have the lock, // give it some time for the old page to go away - tinysleep(); + yield_the_processor(); } } @@ -1308,7 +1542,7 @@ static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) { #ifdef PGC_WITH_ARAL if(allocation) - aral_freez(cache->aral[partition], allocation); + aral_freez(cache->aral, allocation); #endif __atomic_sub_fetch(&cache->stats.workers_add, 1, __ATOMIC_RELAXED); @@ -1316,10 +1550,7 @@ static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) { if(!entry->hot) evict_on_clean_page_added(cache); - if((cache->config.options & PGC_OPTIONS_FLUSH_PAGES_INLINE) || flushing_critical(cache)) { - flush_pages(cache, cache->config.max_flushes_inline, PGC_SECTION_ALL, - false, false); - } + flush_on_page_add(cache); return page; } @@ -1459,7 +1690,7 @@ static PGC_PAGE *page_find_and_acquire_once(PGC *cache, Word_t section, Word_t m } static void all_hot_pages_to_dirty(PGC *cache, Word_t section) { - pgc_ll_lock(cache, &cache->hot); + pgc_queue_lock(cache, &cache->hot); bool first = true; Word_t last_section = (section == PGC_SECTION_ALL) ? 0 : section; @@ -1483,7 +1714,7 @@ static void all_hot_pages_to_dirty(PGC *cache, Word_t section) { page = next; } } - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); } // returns true when there is more work to do @@ -1495,20 +1726,21 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai // we have been called from a data collection thread // let's not waste its time... - if(!pgc_ll_trylock(cache, &cache->dirty)) { + if(!pgc_queue_trylock(cache, &cache->dirty)) { // we would block, so give up... - return true; + return false; } // we got the lock at this point } else - pgc_ll_lock(cache, &cache->dirty); + pgc_queue_lock(cache, &cache->dirty); size_t optimal_flush_size = cache->config.max_dirty_pages_per_call; size_t dirty_version_at_entry = cache->dirty.version; - if(!all_of_them && (cache->dirty.stats->entries < optimal_flush_size || cache->dirty.last_version_checked == dirty_version_at_entry)) { - pgc_ll_unlock(cache, &cache->dirty); + size_t entries = __atomic_load_n(&cache->dirty.stats->entries, __ATOMIC_RELAXED); + if(!all_of_them && (entries < optimal_flush_size || cache->dirty.last_version_checked == dirty_version_at_entry)) { + pgc_queue_unlock(cache, &cache->dirty); return false; } @@ -1523,7 +1755,6 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai size_t flushes_so_far = 0; Pvoid_t *section_pages_pptr; bool stopped_before_finishing = false; - size_t spins = 0; bool first = true; while (have_dirty_lock && (section_pages_pptr = JudyLFirstThenNext(cache->dirty.sections_judy, &last_section, &first))) { @@ -1539,9 +1770,6 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai break; } - if(++spins > 1) - __atomic_add_fetch(&cache->stats.flush_spins, 1, __ATOMIC_RELAXED); - PGC_ENTRY array[optimal_flush_size]; PGC_PAGE *pages[optimal_flush_size]; size_t pages_added = 0, pages_added_size = 0; @@ -1603,7 +1831,7 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai __atomic_add_fetch(&cache->stats.flushing_size, tpg->assumed_size, __ATOMIC_RELAXED); // remove it from the dirty list - pgc_ll_del(cache, &cache->dirty, tpg, true); + pgc_queue_del(cache, &cache->dirty, tpg, true); pages_removed_dirty_size += tpg->assumed_size; pages_removed_dirty++; @@ -1629,7 +1857,7 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai // page ptr may be invalid now } - __atomic_add_fetch(&cache->stats.flushes_cancelled, pages_cancelled, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.waste_flushes_cancelled, pages_cancelled, __ATOMIC_RELAXED); __atomic_add_fetch(&cache->stats.flushes_cancelled_size, pages_cancelled_size, __ATOMIC_RELAXED); internal_fatal(pages_added != pages_cancelled || pages_added_size != pages_cancelled_size, @@ -1643,7 +1871,7 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai if(cache->config.pgc_save_init_cb) cache->config.pgc_save_init_cb(cache, last_section); - pgc_ll_unlock(cache, &cache->dirty); + pgc_queue_unlock(cache, &cache->dirty); have_dirty_lock = false; // call the callback to save them @@ -1681,7 +1909,7 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai , "DBENGINE CACHE: flushing pages mismatch"); if(!all_of_them && !wait) { - if(pgc_ll_trylock(cache, &cache->dirty)) + if(pgc_queue_trylock(cache, &cache->dirty)) have_dirty_lock = true; else { @@ -1690,7 +1918,7 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai } } else { - pgc_ll_lock(cache, &cache->dirty); + pgc_queue_lock(cache, &cache->dirty); have_dirty_lock = true; } } @@ -1699,7 +1927,7 @@ static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wai if(!stopped_before_finishing && dirty_version_at_entry > cache->dirty.last_version_checked) cache->dirty.last_version_checked = dirty_version_at_entry; - pgc_ll_unlock(cache, &cache->dirty); + pgc_queue_unlock(cache, &cache->dirty); } __atomic_sub_fetch(&cache->stats.workers_flush, 1, __ATOMIC_RELAXED); @@ -1711,21 +1939,75 @@ void free_all_unreferenced_clean_pages(PGC *cache) { evict_pages(cache, 0, 0, true, true); } +static void *pgc_evict_thread(void *ptr) { + PGC *cache = ptr; + + worker_register("PGCEVICT"); + worker_register_job_name(0, "signaled"); + worker_register_job_name(1, "scheduled"); + + unsigned job_id = 0; + + while (true) { + worker_is_idle(); + unsigned new_job_id = completion_wait_for_a_job_with_timeout(&cache->evictor.completion, job_id, 50); + bool was_signaled = new_job_id > job_id; + worker_is_busy(was_signaled ? 1 : 0); + job_id = new_job_id; + + if (nd_thread_signaled_to_cancel()) + return NULL; + + spinlock_lock(&cache->evictor.spinlock); + + size_t at_once = 10; + size_t size_to_evict = 0; + size_t per1000 = cache_usage_per1000(cache, &size_to_evict); + bool was_aggressive = per1000 > cache->config.aggressive_evict_per1000; + + while (size_to_evict && ((--at_once && size_to_evict && per1000 > cache->config.healthy_size_per1000) || (per1000 > cache->config.aggressive_evict_per1000))) { + if (nd_thread_signaled_to_cancel()) { + spinlock_unlock(&cache->evictor.spinlock); + return NULL; + } + + evict_pages(cache, 0, 0, true, false); + + if(was_signaled || was_aggressive) + mallocz_release_as_much_memory_to_the_system(); + + yield_the_processor(); + + size_to_evict = 0; + per1000 = cache_usage_per1000(cache, &size_to_evict); + } + + spinlock_unlock(&cache->evictor.spinlock); + } + + worker_unregister(); + return NULL; +} + // ---------------------------------------------------------------------------- // public API PGC *pgc_create(const char *name, - size_t clean_size_bytes, free_clean_page_callback pgc_free_cb, + size_t clean_size_bytes, + free_clean_page_callback pgc_free_cb, size_t max_dirty_pages_per_flush, save_dirty_init_callback pgc_save_init_cb, save_dirty_page_callback pgc_save_dirty_cb, - size_t max_pages_per_inline_eviction, size_t max_inline_evictors, + size_t max_pages_per_inline_eviction, + size_t max_inline_evictors, size_t max_skip_pages_per_inline_eviction, size_t max_flushes_inline, - PGC_OPTIONS options, size_t partitions, size_t additional_bytes_per_page) { + PGC_OPTIONS options, + size_t partitions, + size_t additional_bytes_per_page) { - if(max_pages_per_inline_eviction < 2) - max_pages_per_inline_eviction = 2; + if(max_pages_per_inline_eviction < 1) + max_pages_per_inline_eviction = 1; if(max_dirty_pages_per_flush < 1) max_dirty_pages_per_flush = 1; @@ -1735,77 +2017,97 @@ PGC *pgc_create(const char *name, PGC *cache = callocz(1, sizeof(PGC)); strncpyz(cache->config.name, name, PGC_NAME_MAX); + cache->config.options = options; - cache->config.clean_size = (clean_size_bytes < 1 * 1024 * 1024) ? 1 * 1024 * 1024 : clean_size_bytes; - cache->config.pgc_free_clean_cb = pgc_free_cb; - cache->config.max_dirty_pages_per_call = max_dirty_pages_per_flush; - cache->config.pgc_save_init_cb = pgc_save_init_cb; - cache->config.pgc_save_dirty_cb = pgc_save_dirty_cb; + cache->config.additional_bytes_per_page = additional_bytes_per_page; + cache->config.stats = telemetry_enabled; + + // flushing + cache->config.max_flushes_inline = (max_flushes_inline == 0) ? 2 : max_flushes_inline; + cache->config.max_dirty_pages_per_call = max_dirty_pages_per_flush; + cache->config.pgc_save_init_cb = pgc_save_init_cb; + cache->config.pgc_save_dirty_cb = pgc_save_dirty_cb; + + // eviction strategy + cache->config.clean_size = (clean_size_bytes < 1 * 1024 * 1024) ? 1 * 1024 * 1024 : clean_size_bytes; + cache->config.pgc_free_clean_cb = pgc_free_cb; + cache->config.max_workers_evict_inline = max_inline_evictors; cache->config.max_pages_per_inline_eviction = max_pages_per_inline_eviction; cache->config.max_skip_pages_per_inline_eviction = (max_skip_pages_per_inline_eviction < 2) ? 2 : max_skip_pages_per_inline_eviction; - cache->config.max_flushes_inline = (max_flushes_inline < 1) ? 1 : max_flushes_inline; - cache->config.partitions = partitions < 1 ? (size_t)get_netdata_cpus() : partitions; - cache->config.additional_bytes_per_page = additional_bytes_per_page; + cache->config.severe_pressure_per1000 = 1010; // INLINE: use releasers to evict pages (up to max_pages_per_inline_eviction) + cache->config.aggressive_evict_per1000 = 990; // INLINE: use adders to evict page (up to max_pages_per_inline_eviction) + cache->config.healthy_size_per1000 = 980; // signal the eviction thread to evict immediately + cache->config.evict_low_threshold_per1000 = 970; // when evicting, bring the size down to this threshold - cache->config.max_workers_evict_inline = max_inline_evictors; - cache->config.severe_pressure_per1000 = 1010; - cache->config.aggressive_evict_per1000 = 990; - cache->config.healthy_size_per1000 = 980; - cache->config.evict_low_threshold_per1000 = 970; + // use all ram and protection from out of memory + cache->config.use_all_ram = dbengine_use_all_ram_for_caches; + cache->config.out_of_memory_protection_bytes = dbengine_out_of_memory_protection; - cache->index = callocz(cache->config.partitions, sizeof(struct pgc_index)); + // partitions + cache->config.partitions = partitions == 0 ? 1ULL + get_netdata_cpus() / 2 : partitions; + cache->index = callocz(cache->config.partitions, sizeof(struct pgc_index)); + + pgc_section_pages_static_aral_init(); for(size_t part = 0; part < cache->config.partitions ; part++) rw_spinlock_init(&cache->index[part].rw_spinlock); +#ifdef PGC_WITH_ARAL + if(cache->config.additional_bytes_per_page) { + char buf[100]; + snprintfz(buf, sizeof(buf), "%s", name); + cache->aral = aral_create( + buf, + sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page, + 0, + 16364, + &aral_statistics_for_pgc, + NULL, NULL, false, false); + } + else + cache->aral = pgc_pages_aral; + + telemetry_aral_register(cache->aral, "pgc"); +#endif + + spinlock_init(&cache->hot.spinlock); spinlock_init(&cache->dirty.spinlock); spinlock_init(&cache->clean.spinlock); cache->hot.flags = PGC_PAGE_HOT; cache->hot.linked_list_in_sections_judy = true; - cache->hot.stats = &cache->stats.queues.hot; + cache->hot.stats = &cache->stats.queues[PGC_QUEUE_HOT]; cache->dirty.flags = PGC_PAGE_DIRTY; cache->dirty.linked_list_in_sections_judy = true; - cache->dirty.stats = &cache->stats.queues.dirty; + cache->dirty.stats = &cache->stats.queues[PGC_QUEUE_DIRTY]; cache->clean.flags = PGC_PAGE_CLEAN; cache->clean.linked_list_in_sections_judy = false; - cache->clean.stats = &cache->stats.queues.clean; + cache->clean.stats = &cache->stats.queues[PGC_QUEUE_CLEAN]; - pgc_section_pages_static_aral_init(); + pointer_index_init(cache); + pgc_size_histogram_init(&cache->hot.stats->size_histogram); + pgc_size_histogram_init(&cache->dirty.stats->size_histogram); + pgc_size_histogram_init(&cache->clean.stats->size_histogram); -#ifdef PGC_WITH_ARAL - cache->aral = callocz(cache->config.partitions, sizeof(ARAL *)); - for(size_t part = 0; part < cache->config.partitions ; part++) { - char buf[100 +1]; - snprintfz(buf, sizeof(buf) - 1, "%s[%zu]", name, part); - cache->aral[part] = aral_create( - buf, - sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page, - 0, - 16384, - aral_get_statistics(pgc_section_pages_aral), - NULL, NULL, false, false); + // last create the eviction thread + { + spinlock_init(&cache->evictor.spinlock); + completion_init(&cache->evictor.completion); + cache->evictor.thread = nd_thread_create(name, NETDATA_THREAD_OPTION_JOINABLE, pgc_evict_thread, cache); } -#endif - - pointer_index_init(cache); return cache; } -struct aral_statistics *pgc_aral_statistics(void) { - return aral_get_statistics(pgc_section_pages_aral); -} - size_t pgc_aral_structures(void) { - return aral_structures(pgc_section_pages_aral); + return aral_structures(pgc_pages_aral); } size_t pgc_aral_overhead(void) { - return aral_overhead(pgc_section_pages_aral); + return aral_overhead(pgc_pages_aral); } void pgc_flush_all_hot_and_dirty_pages(PGC *cache, Word_t section) { @@ -1825,6 +2127,12 @@ void pgc_destroy(PGC *cache) { // free all unreferenced clean pages free_all_unreferenced_clean_pages(cache); + // stop the eviction thread + nd_thread_signal_cancel(cache->evictor.thread); + completion_mark_complete_a_job(&cache->evictor.completion); + nd_thread_join(cache->evictor.thread); + completion_destroy(&cache->evictor.completion); + if(PGC_REFERENCED_PAGES(cache)) netdata_log_error("DBENGINE CACHE: there are %zu referenced cache pages - leaving the cache allocated", PGC_REFERENCED_PAGES(cache)); else { @@ -1834,10 +2142,8 @@ void pgc_destroy(PGC *cache) { // netdata_rwlock_destroy(&cache->index[part].rw_spinlock); #ifdef PGC_WITH_ARAL - for(size_t part = 0; part < cache->config.partitions ; part++) - aral_destroy(cache->aral[part]); - - freez(cache->aral); + if(cache->config.additional_bytes_per_page) + aral_destroy(cache->aral); #endif freez(cache->index); freez(cache); @@ -1878,8 +2184,8 @@ void pgc_page_hot_to_dirty_and_release(PGC *cache, PGC_PAGE *page, bool never_fl __atomic_sub_fetch(&cache->stats.workers_hot2dirty, 1, __ATOMIC_RELAXED); // flush, if we have to - if(!never_flush && ((cache->config.options & PGC_OPTIONS_FLUSH_PAGES_INLINE) || flushing_critical(cache))) - flush_pages(cache, cache->config.max_flushes_inline, PGC_SECTION_ALL, false, false); + if(!never_flush) + flush_on_page_hot_release(cache); } bool pgc_page_to_clean_evict_or_release(PGC *cache, PGC_PAGE *page) { @@ -1981,12 +2287,18 @@ void pgc_reset_hot_max(PGC *cache) { void pgc_set_dynamic_target_cache_size_callback(PGC *cache, dynamic_target_cache_size_callback callback) { cache->config.dynamic_target_size_cb = callback; + cache->config.out_of_memory_protection_bytes = 0; + cache->config.use_all_ram = false; size_t size_to_evict = 0; cache_usage_per1000(cache, &size_to_evict); evict_pages(cache, 0, 0, true, false); } +void pgc_set_nominal_page_size_callback(PGC *cache, nominal_page_size_callback callback) { + cache->config.nominal_page_size_cb = callback; +} + size_t pgc_get_current_cache_size(PGC *cache) { cache_usage_per1000(cache, NULL); return __atomic_load_n(&cache->stats.current_cache_size, __ATOMIC_RELAXED); @@ -2005,12 +2317,11 @@ bool pgc_evict_pages(PGC *cache, size_t max_skip, size_t max_evict) { true, false); } -bool pgc_flush_pages(PGC *cache, size_t max_flushes) { - bool under_pressure = flushing_critical(cache); - return flush_pages(cache, under_pressure ? 0 : max_flushes, PGC_SECTION_ALL, true, false); +bool pgc_flush_pages(PGC *cache) { + return flush_pages(cache, 0, PGC_SECTION_ALL, true, false); } -void pgc_page_hot_set_end_time_s(PGC *cache __maybe_unused, PGC_PAGE *page, time_t end_time_s) { +void pgc_page_hot_set_end_time_s(PGC *cache __maybe_unused, PGC_PAGE *page, time_t end_time_s, size_t additional_bytes) { internal_fatal(!is_page_hot(page), "DBENGINE CACHE: end_time_s update on non-hot page"); @@ -2019,6 +2330,42 @@ void pgc_page_hot_set_end_time_s(PGC *cache __maybe_unused, PGC_PAGE *page, time __atomic_store_n(&page->end_time_s, end_time_s, __ATOMIC_RELAXED); + if(additional_bytes) { + page_transition_lock(cache, page); + + struct pgc_queue_statistics *queue_stats = NULL; + if(page->flags & PGC_PAGE_HOT) + queue_stats = cache->hot.stats; + else if(page->flags & PGC_PAGE_DIRTY) + queue_stats = cache->dirty.stats; + else if(page->flags & PGC_PAGE_CLEAN) + queue_stats = cache->clean.stats; + + if(queue_stats && cache->config.stats) + pgc_size_histogram_del(cache, &queue_stats->size_histogram, page); + + size_t old_assumed_size = page->assumed_size; + + size_t old_size = page_size_from_assumed_size(cache, old_assumed_size); + size_t size = old_size + additional_bytes; + page->assumed_size = page_assumed_size(cache, size); + + size_t delta = page->assumed_size - old_assumed_size; + __atomic_add_fetch(&cache->stats.size, delta, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.added_size, delta, __ATOMIC_RELAXED); + __atomic_add_fetch(&cache->stats.referenced_size, delta, __ATOMIC_RELAXED); + + if(queue_stats) { + __atomic_add_fetch(&queue_stats->size, delta, __ATOMIC_RELAXED); + __atomic_add_fetch(&queue_stats->added_size, delta, __ATOMIC_RELAXED); + + if(cache->config.stats) + pgc_size_histogram_add(cache, &queue_stats->size_histogram, page); + } + + page_transition_unlock(cache, page); + } + #ifdef PGC_COUNT_POINTS_COLLECTED __atomic_add_fetch(&cache->stats.points_collected, 1, __ATOMIC_RELAXED); #endif @@ -2050,7 +2397,7 @@ PGC_PAGE *pgc_page_get_and_acquire(PGC *cache, Word_t section, Word_t metric_id, if(page || !retry) break; - tinysleep(); + yield_the_processor(); } if(page) { @@ -2085,7 +2432,7 @@ void pgc_open_cache_to_journal_v2(PGC *cache, Word_t section, unsigned datafile_ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.journal_v2_indexing_started, 1, __ATOMIC_RELAXED); __atomic_add_fetch(&cache->stats.workers_jv2_flush, 1, __ATOMIC_RELAXED); - pgc_ll_lock(cache, &cache->hot); + pgc_queue_lock(cache, &cache->hot); Pvoid_t JudyL_metrics = NULL; Pvoid_t JudyL_extents_pos = NULL; @@ -2098,14 +2445,14 @@ void pgc_open_cache_to_journal_v2(PGC *cache, Word_t section, unsigned datafile_ Pvoid_t *section_pages_pptr = JudyLGet(cache->hot.sections_judy, section, PJE0); if(!section_pages_pptr) { - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); return; } struct section_pages *sp = *section_pages_pptr; if(!spinlock_trylock(&sp->migration_to_v2_spinlock)) { netdata_log_info("DBENGINE: migration to journal v2 for datafile %u is postponed, another jv2 indexer is already running for this section", datafile_fileno); - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); return; } @@ -2134,7 +2481,7 @@ void pgc_open_cache_to_journal_v2(PGC *cache, Word_t section, unsigned datafile_ page_flag_set(page, PGC_PAGE_IS_BEING_MIGRATED_TO_V2); - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); // update the extents JudyL @@ -2214,11 +2561,11 @@ void pgc_open_cache_to_journal_v2(PGC *cache, Word_t section, unsigned datafile_ page_release(cache, page, false); } - pgc_ll_lock(cache, &cache->hot); + pgc_queue_lock(cache, &cache->hot); } spinlock_unlock(&sp->migration_to_v2_spinlock); - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); // callback cb(section, datafile_fileno, type, JudyL_metrics, JudyL_extents_pos, count_of_unique_extents, count_of_unique_metrics, count_of_unique_pages, data); @@ -2278,10 +2625,10 @@ void pgc_open_evict_clean_pages_of_datafile(PGC *cache, struct rrdengine_datafil size_t pgc_count_clean_pages_having_data_ptr(PGC *cache, Word_t section, void *ptr) { size_t found = 0; - pgc_ll_lock(cache, &cache->clean); + pgc_queue_lock(cache, &cache->clean); for(PGC_PAGE *page = cache->clean.base; page ;page = page->link.next) found += (page->data == ptr && page->section == section) ? 1 : 0; - pgc_ll_unlock(cache, &cache->clean); + pgc_queue_unlock(cache, &cache->clean); return found; } @@ -2289,14 +2636,14 @@ size_t pgc_count_clean_pages_having_data_ptr(PGC *cache, Word_t section, void *p size_t pgc_count_hot_pages_having_data_ptr(PGC *cache, Word_t section, void *ptr) { size_t found = 0; - pgc_ll_lock(cache, &cache->hot); + pgc_queue_lock(cache, &cache->hot); Pvoid_t *section_pages_pptr = JudyLGet(cache->hot.sections_judy, section, PJE0); if(section_pages_pptr) { struct section_pages *sp = *section_pages_pptr; for(PGC_PAGE *page = sp->base; page ;page = page->link.next) found += (page->data == ptr) ? 1 : 0; } - pgc_ll_unlock(cache, &cache->hot); + pgc_queue_unlock(cache, &cache->hot); return found; } @@ -2726,7 +3073,7 @@ int pgc_unittest(void) { .hot = true, }, NULL); - pgc_page_hot_set_end_time_s(cache, page2, 2001); + pgc_page_hot_set_end_time_s(cache, page2, 2001, 0); pgc_page_hot_to_dirty_and_release(cache, page2, false); PGC_PAGE *page3 = pgc_page_add_and_acquire(cache, (PGC_ENTRY){ @@ -2739,7 +3086,7 @@ int pgc_unittest(void) { .hot = true, }, NULL); - pgc_page_hot_set_end_time_s(cache, page3, 2001); + pgc_page_hot_set_end_time_s(cache, page3, 2001, 0); pgc_page_hot_to_dirty_and_release(cache, page3, false); pgc_destroy(cache); diff --git a/src/database/engine/cache.h b/src/database/engine/cache.h index ef96520289e19e..3b541d4459092b 100644 --- a/src/database/engine/cache.h +++ b/src/database/engine/cache.h @@ -14,12 +14,12 @@ typedef struct pgc_page PGC_PAGE; typedef enum __attribute__ ((__packed__)) { PGC_OPTIONS_NONE = 0, - PGC_OPTIONS_EVICT_PAGES_INLINE = (1 << 0), - PGC_OPTIONS_FLUSH_PAGES_INLINE = (1 << 1), - PGC_OPTIONS_AUTOSCALE = (1 << 2), + PGC_OPTIONS_EVICT_PAGES_NO_INLINE = (1 << 0), + PGC_OPTIONS_FLUSH_PAGES_NO_INLINE = (1 << 1), + PGC_OPTIONS_AUTOSCALE = (1 << 2), } PGC_OPTIONS; -#define PGC_OPTIONS_DEFAULT (PGC_OPTIONS_EVICT_PAGES_INLINE | PGC_OPTIONS_FLUSH_PAGES_INLINE | PGC_OPTIONS_AUTOSCALE) +#define PGC_OPTIONS_DEFAULT (PGC_OPTIONS_EVICT_PAGES_NO_INLINE | PGC_OPTIONS_AUTOSCALE) typedef struct pgc_entry { Word_t section; // the section this belongs to @@ -33,136 +33,137 @@ typedef struct pgc_entry { uint8_t *custom_data; } PGC_ENTRY; -#define PGC_CACHE_LINE_PADDING(x) uint8_t padding##x[64] - -struct pgc_queue_statistics { - size_t entries; - size_t size; +struct pgc_size_histogram_entry { + size_t upto; + size_t count; +}; - PGC_CACHE_LINE_PADDING(1); +#define PGC_SIZE_HISTOGRAM_ENTRIES 15 +#define PGC_QUEUE_HOT 0 +#define PGC_QUEUE_DIRTY 1 +#define PGC_QUEUE_CLEAN 2 - size_t max_entries; - size_t max_size; +struct pgc_size_histogram { + struct pgc_size_histogram_entry array[PGC_SIZE_HISTOGRAM_ENTRIES]; +}; - PGC_CACHE_LINE_PADDING(2); +struct pgc_queue_statistics { + struct pgc_size_histogram size_histogram; - size_t added_entries; - size_t added_size; + alignas(64) size_t entries; + alignas(64) size_t size; - PGC_CACHE_LINE_PADDING(3); + alignas(64) size_t max_entries; + alignas(64) size_t max_size; - size_t removed_entries; - size_t removed_size; + alignas(64) size_t added_entries; + alignas(64) size_t added_size; - PGC_CACHE_LINE_PADDING(4); + alignas(64) size_t removed_entries; + alignas(64) size_t removed_size; }; struct pgc_statistics { - size_t wanted_cache_size; - size_t current_cache_size; + alignas(64) size_t wanted_cache_size; + alignas(64) size_t current_cache_size; - PGC_CACHE_LINE_PADDING(1); + // ---------------------------------------------------------------------------------------------------------------- + // volume - size_t added_entries; - size_t added_size; + alignas(64) size_t entries; // all the entries (includes clean, dirty, hot) + alignas(64) size_t size; // all the entries (includes clean, dirty, hot) - PGC_CACHE_LINE_PADDING(2); + alignas(64) size_t referenced_entries; // all the entries currently referenced + alignas(64) size_t referenced_size; // all the entries currently referenced - size_t removed_entries; - size_t removed_size; + alignas(64) size_t added_entries; + alignas(64) size_t added_size; - PGC_CACHE_LINE_PADDING(3); + alignas(64) size_t removed_entries; + alignas(64) size_t removed_size; - size_t entries; // all the entries (includes clean, dirty, hot) - size_t size; // all the entries (includes clean, dirty, hot) +#ifdef PGC_COUNT_POINTS_COLLECTED + alignas(64) size_t points_collected; +#endif - size_t evicting_entries; - size_t evicting_size; + // ---------------------------------------------------------------------------------------------------------------- + // migrations - size_t flushing_entries; - size_t flushing_size; + alignas(64) size_t evicting_entries; + alignas(64) size_t evicting_size; - size_t hot2dirty_entries; - size_t hot2dirty_size; + alignas(64) size_t flushing_entries; + alignas(64) size_t flushing_size; - PGC_CACHE_LINE_PADDING(4); + alignas(64) size_t hot2dirty_entries; + alignas(64) size_t hot2dirty_size; - size_t acquires; - PGC_CACHE_LINE_PADDING(4a); - size_t releases; - PGC_CACHE_LINE_PADDING(4b); - size_t acquires_for_deletion; - PGC_CACHE_LINE_PADDING(4c); + alignas(64) size_t hot_empty_pages_evicted_immediately; + alignas(64) size_t hot_empty_pages_evicted_later; - size_t referenced_entries; // all the entries currently referenced - size_t referenced_size; // all the entries currently referenced + // ---------------------------------------------------------------------------------------------------------------- + // workload - PGC_CACHE_LINE_PADDING(5); + alignas(64) size_t acquires; + alignas(64) size_t releases; - size_t searches_exact; - size_t searches_exact_hits; - size_t searches_exact_misses; + alignas(64) size_t acquires_for_deletion; - PGC_CACHE_LINE_PADDING(6); + alignas(64) size_t searches_exact; + alignas(64) size_t searches_exact_hits; + alignas(64) size_t searches_exact_misses; - size_t searches_closest; - size_t searches_closest_hits; - size_t searches_closest_misses; + alignas(64) size_t searches_closest; + alignas(64) size_t searches_closest_hits; + alignas(64) size_t searches_closest_misses; - PGC_CACHE_LINE_PADDING(7); + alignas(64) size_t flushes_completed; + alignas(64) size_t flushes_completed_size; + alignas(64) size_t flushes_cancelled_size; - size_t flushes_completed; - size_t flushes_completed_size; - size_t flushes_cancelled; - size_t flushes_cancelled_size; + // ---------------------------------------------------------------------------------------------------------------- + // critical events -#ifdef PGC_COUNT_POINTS_COLLECTED - PGC_CACHE_LINE_PADDING(8); - size_t points_collected; -#endif + alignas(64) size_t events_cache_under_severe_pressure; + alignas(64) size_t events_cache_needs_space_aggressively; + alignas(64) size_t events_flush_critical; - PGC_CACHE_LINE_PADDING(9); - - size_t insert_spins; - size_t evict_spins; - size_t release_spins; - size_t acquire_spins; - size_t delete_spins; - size_t flush_spins; - - PGC_CACHE_LINE_PADDING(10); - - size_t workers_search; - size_t workers_add; - size_t workers_evict; - size_t workers_flush; - size_t workers_jv2_flush; - size_t workers_hot2dirty; - - size_t evict_skipped; - size_t hot_empty_pages_evicted_immediately; - size_t hot_empty_pages_evicted_later; - - PGC_CACHE_LINE_PADDING(11); - - // events - size_t events_cache_under_severe_pressure; - size_t events_cache_needs_space_aggressively; - size_t events_flush_critical; - - PGC_CACHE_LINE_PADDING(12); - - struct { - PGC_CACHE_LINE_PADDING(0); - struct pgc_queue_statistics hot; - PGC_CACHE_LINE_PADDING(1); - struct pgc_queue_statistics dirty; - PGC_CACHE_LINE_PADDING(2); - struct pgc_queue_statistics clean; - PGC_CACHE_LINE_PADDING(3); - } queues; -}; + // ---------------------------------------------------------------------------------------------------------------- + // worker threads + + alignas(64) size_t workers_search; + alignas(64) size_t workers_add; + alignas(64) size_t workers_evict; + alignas(64) size_t workers_flush; + alignas(64) size_t workers_jv2_flush; + alignas(64) size_t workers_hot2dirty; + + // ---------------------------------------------------------------------------------------------------------------- + // waste events + + // waste events - spins + alignas(64) size_t waste_insert_spins; + alignas(64) size_t waste_evict_useless_spins; + alignas(64) size_t waste_release_spins; + alignas(64) size_t waste_acquire_spins; + alignas(64) size_t waste_delete_spins; + // waste events - eviction + alignas(64) size_t waste_evict_relocated; + alignas(64) size_t waste_evict_thread_signals; + alignas(64) size_t waste_evictions_inline_on_add; + alignas(64) size_t waste_evictions_inline_on_release; + + // waste events - flushing + alignas(64) size_t waste_flush_on_add; + alignas(64) size_t waste_flush_on_release; + alignas(64) size_t waste_flushes_cancelled; + + // ---------------------------------------------------------------------------------------------------------------- + // per queue statistics + + struct pgc_queue_statistics queues[3]; +}; typedef void (*free_clean_page_callback)(PGC *cache, PGC_ENTRY entry); typedef void (*save_dirty_page_callback)(PGC *cache, PGC_ENTRY *entries_array, PGC_PAGE **pages_array, size_t entries); @@ -225,7 +226,7 @@ size_t pgc_get_current_cache_size(PGC *cache); size_t pgc_get_wanted_cache_size(PGC *cache); // resetting the end time of a hot page -void pgc_page_hot_set_end_time_s(PGC *cache, PGC_PAGE *page, time_t end_time_s); +void pgc_page_hot_set_end_time_s(PGC *cache, PGC_PAGE *page, time_t end_time_s, size_t additional_bytes); bool pgc_page_to_clean_evict_or_release(PGC *cache, PGC_PAGE *page); typedef void (*migrate_to_v2_callback)(Word_t section, unsigned datafile_fileno, uint8_t type, Pvoid_t JudyL_metrics, Pvoid_t JudyL_extents_pos, size_t count_of_unique_extents, size_t count_of_unique_metrics, size_t count_of_unique_pages, void *data); @@ -237,26 +238,33 @@ size_t pgc_count_hot_pages_having_data_ptr(PGC *cache, Word_t section, void *ptr typedef size_t (*dynamic_target_cache_size_callback)(void); void pgc_set_dynamic_target_cache_size_callback(PGC *cache, dynamic_target_cache_size_callback callback); +typedef size_t (*nominal_page_size_callback)(void *); +void pgc_set_nominal_page_size_callback(PGC *cache, nominal_page_size_callback callback); + // return true when there is more work to do bool pgc_evict_pages(PGC *cache, size_t max_skip, size_t max_evict); -bool pgc_flush_pages(PGC *cache, size_t max_flushes); +bool pgc_flush_pages(PGC *cache); struct pgc_statistics pgc_get_statistics(PGC *cache); size_t pgc_hot_and_dirty_entries(PGC *cache); -struct aral_statistics *pgc_aral_statistics(void); size_t pgc_aral_structures(void); size_t pgc_aral_overhead(void); static inline size_t indexing_partition(Word_t ptr, Word_t modulo) __attribute__((const)); static inline size_t indexing_partition(Word_t ptr, Word_t modulo) { -#ifdef ENV64BIT - uint64_t hash = murmur64(ptr); - return hash % modulo; -#else - uint32_t hash = murmur32(ptr); + XXH64_hash_t hash = XXH3_64bits(&ptr, sizeof(ptr)); return hash % modulo; -#endif +} + +long get_netdata_cpus(void); + +static inline size_t pgc_max_evictors(void) { + return 1 + get_netdata_cpus() / 2; +} + +static inline size_t pgc_max_flushers(void) { + return get_netdata_cpus(); } #endif // DBENGINE_CACHE_H diff --git a/src/database/engine/datafile.c b/src/database/engine/datafile.c index 7bf9487f2d9cb3..9e39268b000c4e 100644 --- a/src/database/engine/datafile.c +++ b/src/database/engine/datafile.c @@ -251,7 +251,7 @@ int create_data_file(struct rrdengine_datafile *datafile) char path[RRDENG_PATH_MAX]; generate_datafilepath(datafile, path, sizeof(path)); - fd = open_file_for_io(path, O_CREAT | O_RDWR | O_TRUNC, &file, use_direct_io); + fd = open_file_for_io(path, O_CREAT | O_RDWR | O_TRUNC, &file, dbengine_use_direct_io); if (fd < 0) { ctx_fs_error(ctx); return fd; @@ -334,7 +334,7 @@ static int load_data_file(struct rrdengine_datafile *datafile) char path[RRDENG_PATH_MAX]; generate_datafilepath(datafile, path, sizeof(path)); - fd = open_file_for_io(path, O_RDWR, &file, use_direct_io); + fd = open_file_for_io(path, O_RDWR, &file, dbengine_use_direct_io); if (fd < 0) { ctx_fs_error(ctx); return fd; diff --git a/src/database/engine/dbengine-stresstest.c b/src/database/engine/dbengine-stresstest.c index 0447bcf337cbff..098a4f0eebb6b0 100644 --- a/src/database/engine/dbengine-stresstest.c +++ b/src/database/engine/dbengine-stresstest.c @@ -22,13 +22,13 @@ static RRDHOST *dbengine_rrdhost_find_or_create(char *name) { default_rrd_history_entries, RRD_MEMORY_MODE_DBENGINE, health_plugin_enabled(), - stream_conf_send_enabled, - stream_conf_send_destination, - stream_conf_send_api_key, - stream_conf_send_charts_matching, - stream_conf_replication_enabled, - stream_conf_replication_period, - stream_conf_replication_step, + stream_send.enabled, + stream_send.parents.destination, + stream_send.api_key, + stream_send.send_charts_matching, + stream_receive.replication.enabled, + stream_receive.replication.period, + stream_receive.replication.step, NULL, 0 ); diff --git a/src/database/engine/dbengine-unittest.c b/src/database/engine/dbengine-unittest.c index 75533610162e8d..0dbc05a23ada59 100644 --- a/src/database/engine/dbengine-unittest.c +++ b/src/database/engine/dbengine-unittest.c @@ -108,13 +108,13 @@ static RRDHOST *dbengine_rrdhost_find_or_create(char *name) { default_rrd_history_entries, RRD_MEMORY_MODE_DBENGINE, health_plugin_enabled(), - stream_conf_send_enabled, - stream_conf_send_destination, - stream_conf_send_api_key, - stream_conf_send_charts_matching, - stream_conf_replication_enabled, - stream_conf_replication_period, - stream_conf_replication_step, + stream_send.enabled, + stream_send.parents.destination, + stream_send.api_key, + stream_send.send_charts_matching, + stream_receive.replication.enabled, + stream_receive.replication.period, + stream_receive.replication.step, NULL, 0 ); diff --git a/src/database/engine/journalfile.c b/src/database/engine/journalfile.c index b120ce3c3bcd86..34715ea68a9bf6 100644 --- a/src/database/engine/journalfile.c +++ b/src/database/engine/journalfile.c @@ -577,7 +577,7 @@ int journalfile_create(struct rrdengine_journalfile *journalfile, struct rrdengi char path[RRDENG_PATH_MAX]; journalfile_v1_generate_path(datafile, path, sizeof(path)); - fd = open_file_for_io(path, O_CREAT | O_RDWR | O_TRUNC, &file, use_direct_io); + fd = open_file_for_io(path, O_CREAT | O_RDWR | O_TRUNC, &file, dbengine_use_direct_io); if (fd < 0) { ctx_fs_error(ctx); return fd; @@ -1522,7 +1522,7 @@ int journalfile_load(struct rrdengine_instance *ctx, struct rrdengine_journalfil journalfile_v1_generate_path(datafile, path, sizeof(path)); - fd = open_file_for_io(path, O_RDWR, &file, use_direct_io); + fd = open_file_for_io(path, O_RDWR, &file, dbengine_use_direct_io); if (fd < 0) { ctx_fs_error(ctx); diff --git a/src/database/engine/metric.c b/src/database/engine/metric.c index 9b2ce6374319a8..6e773bbb6fcbd5 100644 --- a/src/database/engine/metric.c +++ b/src/database/engine/metric.c @@ -375,6 +375,7 @@ inline MRG *mrg_create(ssize_t partitions) { mrg->index[i].aral = aral_create(buf, sizeof(METRIC), 0, 16384, &mrg_aral_statistics, NULL, NULL, false, false); } + telemetry_aral_register(mrg->index[0].aral, "mrg"); return mrg; } @@ -394,7 +395,7 @@ inline void mrg_destroy(MRG *mrg __maybe_unused) { // to delete entries, the caller needs to keep pointers to them // and delete them one by one - ; + telemetry_aral_unregister(mrg->index[0].aral); } inline METRIC *mrg_metric_add_and_acquire(MRG *mrg, MRG_ENTRY entry, bool *ret) { diff --git a/src/database/engine/page.c b/src/database/engine/page.c index 5c4ac14e773246..19c94077be873f 100644 --- a/src/database/engine/page.c +++ b/src/database/engine/page.c @@ -6,6 +6,8 @@ typedef enum __attribute__((packed)) { PAGE_OPTION_ALL_VALUES_EMPTY = (1 << 0), + PAGE_OPTION_ARAL_MARKED = (1 << 1), + PAGE_OPTION_ARAL_UNMARKED = (1 << 2), } PAGE_OPTIONS; typedef enum __attribute__((packed)) { @@ -17,31 +19,32 @@ typedef enum __attribute__((packed)) { typedef struct { uint8_t *data; - uint32_t size; + uint16_t size; } page_raw_t; - typedef struct { - size_t num_buffers; gorilla_writer_t *writer; - int aral_index; + uint16_t num_buffers; } page_gorilla_t; struct pgd { + // the used number of slots in the page + uint16_t used; + + // the total number of slots available in the page + uint16_t slots; + // the page type uint8_t type; - // options related to the page + // the partition this pgd was allocated from + uint8_t partition; + + // options related to the page PAGE_OPTIONS options; PGD_STATES states; - // the uses number of slots in the page - uint32_t used; - - // the total number of slots available in the page - uint32_t slots; - union { page_raw_t raw; page_gorilla_t gorilla; @@ -51,118 +54,241 @@ struct pgd { // ---------------------------------------------------------------------------- // memory management +#define ARAL_TOLERANCE_TO_DEDUP 7 // deduplicate aral sizes, if the delta is below this number of bytes +#define PGD_ARAL_PARTITIONS 4 + struct { - ARAL *aral_pgd; - ARAL *aral_data[RRD_STORAGE_TIERS]; - ARAL *aral_gorilla_buffer[4]; - ARAL *aral_gorilla_writer[4]; + size_t sizeof_pgd; + size_t sizeof_gorilla_writer_t; + size_t sizeof_gorilla_buffer_32bit; + + ARAL *aral_pgd[PGD_ARAL_PARTITIONS]; + ARAL *aral_gorilla_buffer[PGD_ARAL_PARTITIONS]; + ARAL *aral_gorilla_writer[PGD_ARAL_PARTITIONS]; } pgd_alloc_globals = {}; -static ARAL *pgd_aral_data_lookup(size_t size) -{ - for (size_t tier = 0; tier < storage_tiers; tier++) - if (size == tier_page_size[tier]) - return pgd_alloc_globals.aral_data[tier]; +#if RRD_STORAGE_TIERS != 5 +#error "You need to update the slots reserved for storage tiers" +#endif + +static struct aral_statistics aral_statistics_for_pgd = { 0 }; - return NULL; +static size_t aral_sizes_delta; +static size_t aral_sizes_count; +static size_t aral_sizes[] = { +// // leave space for the storage tier page sizes + [RRD_STORAGE_TIERS - 5] = 0, + [RRD_STORAGE_TIERS - 4] = 0, + [RRD_STORAGE_TIERS - 3] = 0, + [RRD_STORAGE_TIERS - 2] = 0, + [RRD_STORAGE_TIERS - 1] = 0, + + // gorilla buffer size + RRDENG_GORILLA_32BIT_BUFFER_SIZE, + + // our structures + sizeof(gorilla_writer_t), + sizeof(PGD), +}; +static ARAL **arals = NULL; + +#define arals_slot(slot, partition) ((partition) * aral_sizes_count + (slot)) +static ARAL *pgd_get_aral_by_size_and_partition(size_t size, size_t partition); + +size_t pgd_aral_structures(void) { + return aral_structures(pgd_alloc_globals.aral_pgd[0]); } -void pgd_init_arals(void) -{ - // pgd aral - { - char buf[20 + 1]; - snprintfz(buf, sizeof(buf) - 1, "pgd"); +size_t pgd_aral_overhead(void) { + return aral_overhead(pgd_alloc_globals.aral_pgd[0]); +} - // FIXME: add stats - pgd_alloc_globals.aral_pgd = aral_create( - buf, - sizeof(struct pgd), - 64, - 512 * (sizeof(struct pgd)), - pgc_aral_statistics(), - NULL, NULL, false, false); - } +int aral_size_sort_compare(const void *a, const void *b) { + size_t size_a = *(const size_t *)a; + size_t size_b = *(const size_t *)b; + return (size_a > size_b) - (size_a < size_b); +} - // tier page aral - { - for (size_t i = storage_tiers; i > 0 ;i--) - { - size_t tier = storage_tiers - i; - - char buf[20 + 1]; - snprintfz(buf, sizeof(buf) - 1, "tier%zu-pages", tier); - - pgd_alloc_globals.aral_data[tier] = aral_create( - buf, - tier_page_size[tier], - 64, - 512 * (tier_page_size[tier]), - pgc_aral_statistics(), - NULL, NULL, false, false); - } +void pgd_init_arals(void) { + aral_sizes_count = _countof(aral_sizes); + + for(size_t i = 0; i < RRD_STORAGE_TIERS ;i++) + aral_sizes[i] = tier_page_size[i]; + + size_t max_delta = 0; + for(size_t i = 0; i < aral_sizes_count ;i++) { + size_t wanted = aral_sizes[i]; + size_t usable = aral_sizes[i]; /* aral_allocation_slot_size(wanted, true);*/ + internal_fatal(usable < wanted, "usable cannot be less than wanted"); + if(usable > wanted && usable - wanted > max_delta) + max_delta = usable - wanted; + + aral_sizes[i] = usable; } + aral_sizes_delta = max_delta + ARAL_TOLERANCE_TO_DEDUP; + + // sort the array + qsort(aral_sizes, aral_sizes_count, sizeof(size_t), aral_size_sort_compare); + + // deduplicate (with some tolerance) + size_t unique_count = 1; + for (size_t i = 1; i < aral_sizes_count; ++i) { + if (aral_sizes[i] > aral_sizes[unique_count - 1] + aral_sizes_delta) + aral_sizes[unique_count++] = aral_sizes[i]; + else + aral_sizes[unique_count - 1] = aral_sizes[i]; + } + aral_sizes_count = unique_count; + + // clear the rest + for(size_t i = unique_count; i < _countof(aral_sizes) ;i++) + aral_sizes[i] = 0; + + // allocate all the arals + arals = callocz(aral_sizes_count * PGD_ARAL_PARTITIONS, sizeof(ARAL *)); + for(size_t slot = 0; slot < aral_sizes_count ; slot++) { + for(size_t partition = 0; partition < PGD_ARAL_PARTITIONS; partition++) { + + if(partition > 0 && aral_sizes[slot] > 128) { + // do not create partitions for sizes above 128 bytes + // use the first partition for all of them + arals[arals_slot(slot, partition)] = arals[arals_slot(slot, 0)]; + continue; + } - // gorilla buffers aral - for (size_t i = 0; i != 4; i++) { - char buf[20 + 1]; - snprintfz(buf, sizeof(buf) - 1, "gbuffer-%zu", i); + char buf[32]; + snprintfz(buf, sizeof(buf), "pgd-%zu-%zu", aral_sizes[slot], partition); - // FIXME: add stats - pgd_alloc_globals.aral_gorilla_buffer[i] = aral_create( + arals[arals_slot(slot, partition)] = aral_create( buf, - RRDENG_GORILLA_32BIT_BUFFER_SIZE, - 64, - 512 * RRDENG_GORILLA_32BIT_BUFFER_SIZE, - pgc_aral_statistics(), + aral_sizes[slot], + 0, + 0, + &aral_statistics_for_pgd, NULL, NULL, false, false); + } } - // gorilla writers aral - for (size_t i = 0; i != 4; i++) { - char buf[20 + 1]; - snprintfz(buf, sizeof(buf) - 1, "gwriter-%zu", i); + for(size_t p = 0; p < PGD_ARAL_PARTITIONS ;p++) { + pgd_alloc_globals.aral_pgd[p] = pgd_get_aral_by_size_and_partition(sizeof(PGD), p); + pgd_alloc_globals.aral_gorilla_writer[p] = pgd_get_aral_by_size_and_partition(sizeof(gorilla_writer_t), p); + pgd_alloc_globals.aral_gorilla_buffer[p] = pgd_get_aral_by_size_and_partition(RRDENG_GORILLA_32BIT_BUFFER_SIZE, p); - // FIXME: add stats - pgd_alloc_globals.aral_gorilla_writer[i] = aral_create( - buf, - sizeof(gorilla_writer_t), - 64, - 512 * sizeof(gorilla_writer_t), - pgc_aral_statistics(), - NULL, NULL, false, false); + internal_fatal(!pgd_alloc_globals.aral_pgd[p] || + !pgd_alloc_globals.aral_gorilla_writer[p] || + !pgd_alloc_globals.aral_gorilla_buffer[p] + , "required PGD aral sizes not found"); } + + pgd_alloc_globals.sizeof_pgd = aral_actual_element_size(pgd_alloc_globals.aral_pgd[0]); + pgd_alloc_globals.sizeof_gorilla_writer_t = aral_actual_element_size(pgd_alloc_globals.aral_gorilla_writer[0]); + pgd_alloc_globals.sizeof_gorilla_buffer_32bit = aral_actual_element_size(pgd_alloc_globals.aral_gorilla_buffer[0]); + + telemetry_aral_register(pgd_alloc_globals.aral_pgd[0], "pgd"); } -static void *pgd_data_aral_alloc(size_t size) -{ - ARAL *ar = pgd_aral_data_lookup(size); - if (!ar) - return mallocz(size); +static ARAL *pgd_get_aral_by_size_and_partition(size_t size, size_t partition) { + internal_fatal(partition >= PGD_ARAL_PARTITIONS, "Wrong partition %zu", partition); + + size_t slot; + + if (size <= aral_sizes[0]) + slot = 0; + + else if (size > aral_sizes[aral_sizes_count - 1]) + return NULL; + + else { + // binary search for the smallest size >= requested size + size_t low = 0, high = aral_sizes_count - 1; + while (low < high) { + size_t mid = low + (high - low) / 2; + if (aral_sizes[mid] >= size) + high = mid; + else + low = mid + 1; + } + slot = low; // This is the smallest index where aral_sizes[slot] >= size + } + internal_fatal(slot >= aral_sizes_count || aral_sizes[slot] < size, "Invalid PGD size binary search"); + + ARAL *ar = arals[arals_slot(slot, partition)]; + internal_fatal(!ar || aral_requested_element_size(ar) < size, "Invalid PGD aral lookup"); + return ar; +} + +static inline gorilla_writer_t *pgd_gorilla_writer_alloc(size_t partition) { + internal_fatal(partition >= PGD_ARAL_PARTITIONS, "invalid gorilla writer partition %zu", partition); + return aral_mallocz_marked(pgd_alloc_globals.aral_gorilla_writer[partition]); +} + +static inline gorilla_buffer_t *pgd_gorilla_buffer_alloc(size_t partition) { + internal_fatal(partition >= PGD_ARAL_PARTITIONS, "invalid gorilla buffer partition %zu", partition); + return aral_mallocz_marked(pgd_alloc_globals.aral_gorilla_buffer[partition]); +} + +static inline PGD *pgd_alloc(bool for_collector) { + size_t partition = gettid_cached() % PGD_ARAL_PARTITIONS; + PGD *pgd; + + if(for_collector) + pgd = aral_mallocz_marked(pgd_alloc_globals.aral_pgd[partition]); else - return aral_mallocz(ar); + pgd = aral_mallocz(pgd_alloc_globals.aral_pgd[partition]); + + pgd->partition = partition; + return pgd; } -static void pgd_data_aral_free(void *page, size_t size) -{ - ARAL *ar = pgd_aral_data_lookup(size); - if (!ar) - freez(page); +static inline void *pgd_data_alloc(size_t size, size_t partition, bool for_collector) { + ARAL *ar = pgd_get_aral_by_size_and_partition(size, partition); + if(ar) { + if(for_collector) + return aral_mallocz_marked(ar); + else + return aral_mallocz(ar); + } else + return mallocz(size); +} + +static void pgd_data_free(void *page, size_t size, size_t partition) { + ARAL *ar = pgd_get_aral_by_size_and_partition(size, partition); + if(ar) aral_freez(ar, page); + else + freez(page); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_TIER1_ARAL); +} + +static void pgd_data_unmark(void *page, size_t size, size_t partition) { + if(!page) return; + + ARAL *ar = pgd_get_aral_by_size_and_partition(size, partition); + if(ar) + aral_unmark_allocation(ar, page); +} + +static size_t pgd_data_footprint(size_t size, size_t partition) { + ARAL *ar = pgd_get_aral_by_size_and_partition(size, partition); + if(ar) + return aral_actual_element_size(ar); + else + return size; } // ---------------------------------------------------------------------------- // management api -PGD *pgd_create(uint8_t type, uint32_t slots) -{ - PGD *pg = aral_mallocz(pgd_alloc_globals.aral_pgd); +PGD *pgd_create(uint8_t type, uint32_t slots) { + + PGD *pg = pgd_alloc(true); // this is malloc'd ! pg->type = type; + pg->states = PGD_STATE_CREATED_FROM_COLLECTOR; + pg->options = PAGE_OPTION_ALL_VALUES_EMPTY | PAGE_OPTION_ARAL_MARKED; + pg->used = 0; pg->slots = slots; - pg->options = PAGE_OPTION_ALL_VALUES_EMPTY; - pg->states = PGD_STATE_CREATED_FROM_COLLECTOR; switch (type) { case RRDENG_PAGE_TYPE_ARRAY_32BIT: @@ -173,23 +299,20 @@ PGD *pgd_create(uint8_t type, uint32_t slots) "DBENGINE: invalid number of slots (%u) or page type (%u)", slots, type); pg->raw.size = size; - pg->raw.data = pgd_data_aral_alloc(size); + pg->raw.data = pgd_data_alloc(size, pg->partition, true); break; } case RRDENG_PAGE_TYPE_GORILLA_32BIT: { internal_fatal(slots == 1, "DBENGINE: invalid number of slots (%u) or page type (%u)", slots, type); - pg->slots = 8 * RRDENG_GORILLA_32BIT_BUFFER_SLOTS; - // allocate new gorilla writer - pg->gorilla.aral_index = gettid_cached() % 4; - pg->gorilla.writer = aral_mallocz(pgd_alloc_globals.aral_gorilla_writer[pg->gorilla.aral_index]); + pg->gorilla.writer = pgd_gorilla_writer_alloc(pg->partition); // allocate new gorilla buffer - gorilla_buffer_t *gbuf = aral_mallocz(pgd_alloc_globals.aral_gorilla_buffer[pg->gorilla.aral_index]); + gorilla_buffer_t *gbuf = pgd_gorilla_buffer_alloc(pg->partition); memset(gbuf, 0, RRDENG_GORILLA_32BIT_BUFFER_SIZE); - global_statistics_gorilla_buffer_add_hot(); + telemetry_gorilla_hot_buffer_added(); *pg->gorilla.writer = gorilla_writer_init(gbuf, RRDENG_GORILLA_32BIT_BUFFER_SLOTS); pg->gorilla.num_buffers = 1; @@ -198,7 +321,7 @@ PGD *pgd_create(uint8_t type, uint32_t slots) } default: netdata_log_error("%s() - Unknown page type: %uc", __FUNCTION__, type); - aral_freez(pgd_alloc_globals.aral_pgd, pg); + aral_freez(pgd_alloc_globals.aral_pgd[pg->partition], pg); pg = PGD_EMPTY; break; } @@ -206,52 +329,47 @@ PGD *pgd_create(uint8_t type, uint32_t slots) return pg; } -PGD *pgd_create_from_disk_data(uint8_t type, void *base, uint32_t size) -{ - if (!size) - return PGD_EMPTY; +PGD *pgd_create_from_disk_data(uint8_t type, void *base, uint32_t size) { - if (size < page_type_size[type]) + if (!size || size < page_type_size[type]) return PGD_EMPTY; - PGD *pg = aral_mallocz(pgd_alloc_globals.aral_pgd); - + PGD *pg = pgd_alloc(false); // this is malloc'd ! pg->type = type; pg->states = PGD_STATE_CREATED_FROM_DISK; - pg->options = ~PAGE_OPTION_ALL_VALUES_EMPTY; + pg->options = PAGE_OPTION_ARAL_UNMARKED; switch (type) { case RRDENG_PAGE_TYPE_ARRAY_32BIT: case RRDENG_PAGE_TYPE_ARRAY_TIER1: - pg->raw.size = size; pg->used = size / page_type_size[type]; pg->slots = pg->used; - pg->raw.data = pgd_data_aral_alloc(size); + pg->raw.size = size; + pg->raw.data = pgd_data_alloc(size, pg->partition, false); memcpy(pg->raw.data, base, size); break; + case RRDENG_PAGE_TYPE_GORILLA_32BIT: internal_fatal(size == 0, "Asked to create page with 0 data!!!"); internal_fatal(size % sizeof(uint32_t), "Unaligned gorilla buffer size"); internal_fatal(size % RRDENG_GORILLA_32BIT_BUFFER_SIZE, "Expected size to be a multiple of %zu-bytes", RRDENG_GORILLA_32BIT_BUFFER_SIZE); - pg->raw.data = mallocz(size); + pg->raw.data = (void *)pgd_data_alloc(size, pg->partition, false); pg->raw.size = size; - // TODO: rm this - memset(pg->raw.data, 0, size); - memcpy(pg->raw.data, base, size); + memcpy(pg->raw.data, base, pg->raw.size); uint32_t total_entries = gorilla_buffer_patch((void *) pg->raw.data); - pg->used = total_entries; pg->slots = pg->used; break; + default: netdata_log_error("%s() - Unknown page type: %uc", __FUNCTION__, type); - aral_freez(pgd_alloc_globals.aral_pgd, pg); + aral_freez(pgd_alloc_globals.aral_pgd[pg->partition], pg); pg = PGD_EMPTY; break; } @@ -259,26 +377,30 @@ PGD *pgd_create_from_disk_data(uint8_t type, void *base, uint32_t size) return pg; } -void pgd_free(PGD *pg) -{ - if (!pg) +void pgd_free(PGD *pg) { + if (!pg || pg == PGD_EMPTY) return; - if (pg == PGD_EMPTY) - return; + internal_fatal(pg->partition >= PGD_ARAL_PARTITIONS, + "PGD partition is invalid %u", pg->partition); switch (pg->type) { case RRDENG_PAGE_TYPE_ARRAY_32BIT: case RRDENG_PAGE_TYPE_ARRAY_TIER1: - pgd_data_aral_free(pg->raw.data, pg->raw.size); + pgd_data_free(pg->raw.data, pg->raw.size, pg->partition); break; + case RRDENG_PAGE_TYPE_GORILLA_32BIT: { if (pg->states & PGD_STATE_CREATED_FROM_DISK) { internal_fatal(pg->raw.data == NULL, "Tried to free gorilla PGD loaded from disk with NULL data"); - freez(pg->raw.data); + + pgd_data_free(pg->raw.data, pg->raw.size, pg->partition); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_ARAL); + pg->raw.data = NULL; + pg->raw.size = 0; } else if ((pg->states & PGD_STATE_CREATED_FROM_COLLECTOR) || (pg->states & PGD_STATE_SCHEDULED_FOR_FLUSHING) || @@ -294,15 +416,19 @@ void pgd_free(PGD *pg) gorilla_buffer_t *gbuf = gorilla_writer_drop_head_buffer(pg->gorilla.writer); if (!gbuf) break; - aral_freez(pgd_alloc_globals.aral_gorilla_buffer[pg->gorilla.aral_index], gbuf); + aral_freez(pgd_alloc_globals.aral_gorilla_buffer[pg->partition], gbuf); pg->gorilla.num_buffers -= 1; } + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_GLIVE); + internal_fatal(pg->gorilla.num_buffers != 0, "Could not free all gorilla writer buffers"); - aral_freez(pgd_alloc_globals.aral_gorilla_writer[pg->gorilla.aral_index], pg->gorilla.writer); + aral_freez(pgd_alloc_globals.aral_gorilla_writer[pg->partition], pg->gorilla.writer); pg->gorilla.writer = NULL; + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_GWORKER); } else { fatal("pgd_free() called on gorilla page with unsupported state"); // TODO: should we support any other states? @@ -317,7 +443,62 @@ void pgd_free(PGD *pg) break; } - aral_freez(pgd_alloc_globals.aral_pgd, pg); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_DATA); + + aral_freez(pgd_alloc_globals.aral_pgd[pg->partition], pg); + + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_ARAL); +} + +static void pgd_aral_unmark(PGD *pg) { + if (!pg || + pg == PGD_EMPTY || + (pg->options & PAGE_OPTION_ARAL_UNMARKED) || + !(pg->options & PAGE_OPTION_ARAL_MARKED)) + return; + + internal_fatal(pg->partition >= PGD_ARAL_PARTITIONS, + "PGD partition is invalid %u", pg->partition); + + switch (pg->type) + { + case RRDENG_PAGE_TYPE_ARRAY_32BIT: + case RRDENG_PAGE_TYPE_ARRAY_TIER1: + pgd_data_unmark(pg->raw.data, pg->raw.size, pg->partition); + break; + + case RRDENG_PAGE_TYPE_GORILLA_32BIT: { + if (pg->states & PGD_STATE_CREATED_FROM_DISK) + pgd_data_unmark(pg->raw.data, pg->raw.size, pg->partition); + + else if ((pg->states & PGD_STATE_CREATED_FROM_COLLECTOR) || + (pg->states & PGD_STATE_SCHEDULED_FOR_FLUSHING) || + (pg->states & PGD_STATE_FLUSHED_TO_DISK)) + { + internal_fatal(pg->gorilla.writer == NULL, "PGD does not have an active gorilla writer"); + internal_fatal(pg->gorilla.num_buffers == 0, "PGD does not have any gorilla buffers allocated"); + + gorilla_writer_aral_unmark(pg->gorilla.writer, pgd_alloc_globals.aral_gorilla_buffer[pg->partition]); + aral_unmark_allocation(pgd_alloc_globals.aral_gorilla_writer[pg->partition], pg->gorilla.writer); + } + else { + fatal("pgd_free() called on gorilla page with unsupported state"); + // TODO: should we support any other states? + // if (!(pg->states & PGD_STATE_FLUSHED_TO_DISK)) + // fatal("pgd_free() is not supported yet for pages flushed to disk"); + } + + break; + } + default: + netdata_log_error("%s() - Unknown page type: %uc", __FUNCTION__, pg->type); + break; + } + + aral_unmark_allocation(pgd_alloc_globals.aral_pgd[pg->partition], pg); + + // make sure we will not do this again + pg->options |= PAGE_OPTION_ARAL_UNMARKED; } // ---------------------------------------------------------------------------- @@ -356,7 +537,54 @@ uint32_t pgd_slots_used(PGD *pg) return pg->used; } +uint32_t pgd_capacity(PGD *pg) { + if (!pg) + return 0; + + if (pg == PGD_EMPTY) + return 0; + + return pg->slots; +} + +// return the overall memory footprint of the page, including all its structures and overheads uint32_t pgd_memory_footprint(PGD *pg) +{ + if (!pg) + return 0; + + if (pg == PGD_EMPTY) + return 0; + + size_t footprint = pgd_alloc_globals.sizeof_pgd; + + switch (pg->type) { + case RRDENG_PAGE_TYPE_ARRAY_32BIT: + case RRDENG_PAGE_TYPE_ARRAY_TIER1: + footprint += pgd_data_footprint(pg->raw.size, pg->partition); + break; + + case RRDENG_PAGE_TYPE_GORILLA_32BIT: { + if (pg->states & PGD_STATE_CREATED_FROM_DISK) + footprint += pgd_data_footprint(pg->raw.size, pg->partition); + + else { + footprint += pgd_alloc_globals.sizeof_gorilla_writer_t; + footprint += pg->gorilla.num_buffers * pgd_alloc_globals.sizeof_gorilla_buffer_32bit; + } + break; + } + + default: + netdata_log_error("%s() - Unknown page type: %uc", __FUNCTION__, pg->type); + break; + } + + return footprint; +} + +// return the nominal buffer size depending on the page type - used by the PGC histogram +uint32_t pgd_buffer_memory_footprint(PGD *pg) { if (!pg) return 0; @@ -365,19 +593,22 @@ uint32_t pgd_memory_footprint(PGD *pg) return 0; size_t footprint = 0; + switch (pg->type) { case RRDENG_PAGE_TYPE_ARRAY_32BIT: case RRDENG_PAGE_TYPE_ARRAY_TIER1: - footprint = sizeof(PGD) + pg->raw.size; + footprint = pg->raw.size; break; + case RRDENG_PAGE_TYPE_GORILLA_32BIT: { if (pg->states & PGD_STATE_CREATED_FROM_DISK) - footprint = sizeof(PGD) + pg->raw.size; - else - footprint = sizeof(PGD) + sizeof(gorilla_writer_t) + (pg->gorilla.num_buffers * RRDENG_GORILLA_32BIT_BUFFER_SIZE); + footprint = pg->raw.size; + else + footprint = pg->gorilla.num_buffers * RRDENG_GORILLA_32BIT_BUFFER_SIZE; break; } + default: netdata_log_error("%s() - Unknown page type: %uc", __FUNCTION__, pg->type); break; @@ -393,6 +624,9 @@ uint32_t pgd_disk_footprint(PGD *pg) size_t size = 0; + // since the page is ready for flushing, let's unmark its pages to ARAL + pgd_aral_unmark(pg); + switch (pg->type) { case RRDENG_PAGE_TYPE_ARRAY_32BIT: case RRDENG_PAGE_TYPE_ARRAY_TIER1: { @@ -415,10 +649,12 @@ uint32_t pgd_disk_footprint(PGD *pg) size = pg->gorilla.num_buffers * RRDENG_GORILLA_32BIT_BUFFER_SIZE; - if (pg->states & PGD_STATE_CREATED_FROM_COLLECTOR) { - global_statistics_tier0_disk_compressed_bytes(gorilla_writer_nbytes(pg->gorilla.writer)); - global_statistics_tier0_disk_uncompressed_bytes(gorilla_writer_entries(pg->gorilla.writer) * sizeof(storage_number)); - } + if (pg->states & PGD_STATE_CREATED_FROM_COLLECTOR) + telemetry_gorilla_tier0_page_flush( + gorilla_writer_actual_nbytes(pg->gorilla.writer), + gorilla_writer_optimal_nbytes(pg->gorilla.writer), + tier_page_size[0]); + } else if (pg->states & PGD_STATE_CREATED_FROM_DISK) { size = pg->raw.size; } else { @@ -434,6 +670,7 @@ uint32_t pgd_disk_footprint(PGD *pg) internal_fatal(pg->states & PGD_STATE_CREATED_FROM_DISK, "Disk footprint asked for page created from disk."); + pg->states = PGD_STATE_SCHEDULED_FOR_FLUSHING; return size; } @@ -461,7 +698,7 @@ void pgd_copy_to_extent(PGD *pg, uint8_t *dst, uint32_t dst_size) bool ok = gorilla_writer_serialize(pg->gorilla.writer, dst, dst_size); UNUSED(ok); internal_fatal(!ok, - "pgd_copy_to_extent() tried to serialize pg=%p, gw=%p (with dst_size=%u bytes, num_buffers=%zu)", + "pgd_copy_to_extent() tried to serialize pg=%p, gw=%p (with dst_size=%u bytes, num_buffers=%u)", pg, pg->gorilla.writer, dst_size, pg->gorilla.num_buffers); break; } @@ -476,7 +713,8 @@ void pgd_copy_to_extent(PGD *pg, uint8_t *dst, uint32_t dst_size) // ---------------------------------------------------------------------------- // data collection -void pgd_append_point(PGD *pg, +// returns additional memory that may have been allocated to store this point +size_t pgd_append_point(PGD *pg, usec_t point_in_time_ut __maybe_unused, NETDATA_DOUBLE n, NETDATA_DOUBLE min_value, @@ -535,22 +773,27 @@ void pgd_append_point(PGD *pg, bool ok = gorilla_writer_write(pg->gorilla.writer, t); if (!ok) { - gorilla_buffer_t *new_buffer = aral_mallocz(pgd_alloc_globals.aral_gorilla_buffer[pg->gorilla.aral_index]); + gorilla_buffer_t *new_buffer = pgd_gorilla_buffer_alloc(pg->partition); memset(new_buffer, 0, RRDENG_GORILLA_32BIT_BUFFER_SIZE); gorilla_writer_add_buffer(pg->gorilla.writer, new_buffer, RRDENG_GORILLA_32BIT_BUFFER_SLOTS); pg->gorilla.num_buffers += 1; - global_statistics_gorilla_buffer_add_hot(); + telemetry_gorilla_hot_buffer_added(); ok = gorilla_writer_write(pg->gorilla.writer, t); internal_fatal(ok == false, "Failed to writer value in newly allocated gorilla buffer."); + + return RRDENG_GORILLA_32BIT_BUFFER_SIZE; } + break; } default: netdata_log_error("%s() - Unknown page type: %uc", __FUNCTION__, pg->type); break; } + + return 0; } // ---------------------------------------------------------------------------- @@ -589,7 +832,6 @@ static void pgdc_seek(PGDC *pgdc, uint32_t position) uint32_t value; bool ok = gorilla_reader_read(&pgdc->gr, &value); - if (!ok) { // this is fine, the reader will return empty points break; @@ -664,6 +906,7 @@ bool pgdc_get_next_point(PGDC *pgdc, uint32_t expected_position __maybe_unused, uint32_t n = 666666666; bool ok = gorilla_reader_read(&pgdc->gr, &n); + if (ok) { sp->min = sp->max = sp->sum = unpack_storage_number(n); sp->flags = (SN_FLAGS)(n & SN_USER_FLAGS); diff --git a/src/database/engine/page.h b/src/database/engine/page.h index 32c87c58072272..2b051b4e68b14e 100644 --- a/src/database/engine/page.h +++ b/src/database/engine/page.h @@ -33,12 +33,17 @@ uint32_t pgd_type(PGD *pg); bool pgd_is_empty(PGD *pg); uint32_t pgd_slots_used(PGD *pg); +uint32_t pgd_buffer_memory_footprint(PGD *pg); uint32_t pgd_memory_footprint(PGD *pg); +uint32_t pgd_capacity(PGD *pg); uint32_t pgd_disk_footprint(PGD *pg); +size_t pgd_aral_structures(void); +size_t pgd_aral_overhead(void); + void pgd_copy_to_extent(PGD *pg, uint8_t *dst, uint32_t dst_size); -void pgd_append_point(PGD *pg, +size_t pgd_append_point(PGD *pg, usec_t point_in_time_ut, NETDATA_DOUBLE n, NETDATA_DOUBLE min_value, diff --git a/src/database/engine/pagecache.c b/src/database/engine/pagecache.c index a88992223c91f8..98de9a26ce2574 100644 --- a/src/database/engine/pagecache.c +++ b/src/database/engine/pagecache.c @@ -63,6 +63,7 @@ static void open_cache_free_clean_page_callback(PGC *cache __maybe_unused, PGC_E { struct rrdengine_datafile *datafile = entry.data; datafile_release(datafile, DATAFILE_ACQUIRE_OPEN_CACHE); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_OPEN); } static void open_cache_flush_dirty_page_callback(PGC *cache __maybe_unused, PGC_ENTRY *entries_array __maybe_unused, PGC_PAGE **pages_array __maybe_unused, size_t entries __maybe_unused) @@ -73,6 +74,7 @@ static void open_cache_flush_dirty_page_callback(PGC *cache __maybe_unused, PGC_ static void extent_cache_free_clean_page_callback(PGC *cache __maybe_unused, PGC_ENTRY entry __maybe_unused) { dbengine_extent_free(entry.data, entry.size); + timing_dbengine_evict_step(TIMING_STEP_DBENGINE_EVICT_FREE_EXTENT); } static void extent_cache_flush_dirty_page_callback(PGC *cache __maybe_unused, PGC_ENTRY *entries_array __maybe_unused, PGC_PAGE **pages_array __maybe_unused, size_t entries __maybe_unused) @@ -1030,8 +1032,21 @@ void pgc_open_add_hot_page(Word_t section, Word_t metric_id, time_t start_time_s } size_t dynamic_open_cache_size(void) { - size_t main_cache_size = pgc_get_wanted_cache_size(main_cache); - size_t target_size = main_cache_size / 100 * 5; + size_t main_wanted_cache_size = pgc_get_wanted_cache_size(main_cache); + size_t target_size = main_wanted_cache_size / 100 * 5; // 5% + +// static bool query_current_size = true; +// if(query_current_size) { +// size_t main_current_cache_size = pgc_get_current_cache_size(main_cache); +// +// size_t main_free_cache_size = (main_wanted_cache_size > main_current_cache_size) ? +// main_wanted_cache_size - main_current_cache_size : 0; +// +// if(main_free_cache_size > target_size) +// target_size = main_free_cache_size; +// else +// query_current_size = false; +// } if(target_size < 2 * 1024 * 1024) target_size = 2 * 1024 * 1024; @@ -1040,15 +1055,33 @@ size_t dynamic_open_cache_size(void) { } size_t dynamic_extent_cache_size(void) { - size_t main_cache_size = pgc_get_wanted_cache_size(main_cache); - size_t target_size = main_cache_size / 100 * 5; - - if(target_size < 3 * 1024 * 1024) - target_size = 3 * 1024 * 1024; + size_t main_wanted_cache_size = pgc_get_wanted_cache_size(main_cache); + + size_t target_size = main_wanted_cache_size / 100 * 10; // 10% + +// static bool query_current_size = true; +// if(query_current_size) { +// size_t main_current_cache_size = pgc_get_current_cache_size(main_cache); +// +// size_t main_free_cache_size = (main_wanted_cache_size > main_current_cache_size) ? +// main_wanted_cache_size - main_current_cache_size : 0; +// +// if(main_free_cache_size > target_size) +// target_size = main_free_cache_size; +// else +// query_current_size = false; +// } + + if(target_size < 5 * 1024 * 1024) + target_size = 5 * 1024 * 1024; return target_size; } +size_t pgc_main_nominal_page_size(void *data) { + return pgd_buffer_memory_footprint(data); +} + void pgc_and_mrg_initialize(void) { main_mrg = mrg_create(0); @@ -1066,51 +1099,52 @@ void pgc_and_mrg_initialize(void) extent_cache_size += (size_t)(default_rrdeng_extent_cache_mb * 1024ULL * 1024ULL); main_cache = pgc_create( - "main_cache", + "MAIN_PGC", main_cache_size, main_cache_free_clean_page_callback, (size_t) rrdeng_pages_per_extent, main_cache_flush_dirty_page_init_callback, main_cache_flush_dirty_page_callback, - 10, - 10240, // if there are that many threads, evict so many at once! - 1000, // - 5, // don't delay too much other threads - PGC_OPTIONS_AUTOSCALE, // AUTOSCALE = 2x max hot pages - 0, // 0 = as many as the system cpus + 2, + pgc_max_evictors(), + 1000, + 1, + PGC_OPTIONS_AUTOSCALE, + 0, 0 ); + pgc_set_nominal_page_size_callback(main_cache, pgc_main_nominal_page_size); open_cache = pgc_create( - "open_cache", - open_cache_size, // the default is 1MB + "OPEN_PGC", + open_cache_size, open_cache_free_clean_page_callback, - 1, + 2, NULL, open_cache_flush_dirty_page_callback, - 10, - 10240, // if there are that many threads, evict that many at once! - 1000, // - 3, // don't delay too much other threads - PGC_OPTIONS_AUTOSCALE | PGC_OPTIONS_EVICT_PAGES_INLINE | PGC_OPTIONS_FLUSH_PAGES_INLINE, - 0, // 0 = as many as the system cpus + 1, + pgc_max_evictors(), + 1000, + 1, + PGC_OPTIONS_AUTOSCALE, // flushing inline: all dirty pages are just converted to clean + 0, sizeof(struct extent_io_data) ); pgc_set_dynamic_target_cache_size_callback(open_cache, dynamic_open_cache_size); extent_cache = pgc_create( - "extent_cache", + "EXTENT_PGC", extent_cache_size, extent_cache_free_clean_page_callback, - 1, + 2, NULL, extent_cache_flush_dirty_page_callback, - 5, - 10, // it will lose up to that extents at once! - 100, // - 2, // don't delay too much other threads - PGC_OPTIONS_AUTOSCALE | PGC_OPTIONS_EVICT_PAGES_INLINE | PGC_OPTIONS_FLUSH_PAGES_INLINE, - 0, // 0 = as many as the system cpus + 1, + pgc_max_evictors(), + 1000, + 1, + PGC_OPTIONS_AUTOSCALE | PGC_OPTIONS_FLUSH_PAGES_NO_INLINE, // no flushing needed + 0, 0 ); pgc_set_dynamic_target_cache_size_callback(extent_cache, dynamic_extent_cache_size); diff --git a/src/database/engine/pdc.c b/src/database/engine/pdc.c index da5dbd7d84943f..1354ffd421400c 100644 --- a/src/database/engine/pdc.c +++ b/src/database/engine/pdc.c @@ -53,10 +53,12 @@ void pdc_init(void) { "dbengine-pdc", sizeof(PDC), 0, - 65536, + 0, NULL, NULL, NULL, false, false ); + + telemetry_aral_register(pdc_globals.pdc.ar, "pdc"); } PDC *pdc_get(void) { @@ -81,10 +83,11 @@ void page_details_init(void) { "dbengine-pd", sizeof(struct page_details), 0, - 65536, + 0, NULL, NULL, NULL, false, false ); + telemetry_aral_register(pdc_globals.pd.ar, "pd"); } struct page_details *page_details_get(void) { @@ -109,10 +112,11 @@ void epdl_init(void) { "dbengine-epdl", sizeof(EPDL), 0, - 65536, + 0, NULL, NULL, NULL, false, false ); + telemetry_aral_register(pdc_globals.epdl.ar, "epdl"); } static EPDL *epdl_get(void) { @@ -137,10 +141,12 @@ void deol_init(void) { "dbengine-deol", sizeof(DEOL), 0, - 65536, + 0, NULL, NULL, NULL, false, false ); + + telemetry_aral_register(pdc_globals.deol.ar, "deol"); } static DEOL *deol_get(void) { @@ -1126,6 +1132,7 @@ static bool epdl_populate_pages_from_extent_data( PGC_PAGE *page = pgc_page_add_and_acquire(main_cache, page_entry, &added); if (false == added) { pgd_free(pgd); + pgd = pgc_page_data(page); stats_cache_hit_while_inserting++; stats_data_from_main_cache++; } @@ -1256,9 +1263,12 @@ void epdl_find_extent_and_populate_pages(struct rrdengine_instance *ctx, EPDL *e void *extent_data = datafile_extent_read(ctx, epdl->file, epdl->extent_offset, epdl->extent_size); if(extent_data != NULL) { - void *copied_extent_compressed_data = dbengine_extent_alloc(epdl->extent_size); - memcpy(copied_extent_compressed_data, extent_data, epdl->extent_size); +#if defined(NETDATA_TRACE_ALLOCATIONS) + void *tmp = dbengine_extent_alloc(epdl->extent_size); + memcpy(tmp, extent_data, epdl->extent_size); datafile_extent_read_free(extent_data); + extent_data = tmp; +#endif if(worker) worker_is_busy(UV_EVENT_DBENGINE_EXTENT_CACHE_LOOKUP); @@ -1272,11 +1282,11 @@ void epdl_find_extent_and_populate_pages(struct rrdengine_instance *ctx, EPDL *e .size = epdl->extent_size, .end_time_s = 0, .update_every_s = 0, - .data = copied_extent_compressed_data, + .data = extent_data, }, &added); if (!added) { - dbengine_extent_free(copied_extent_compressed_data, epdl->extent_size); + dbengine_extent_free(extent_data, epdl->extent_size); internal_fatal(epdl->extent_size != pgc_page_data_size(extent_cache, extent_cache_page), "DBENGINE: cache size does not match the expected size"); } diff --git a/src/database/engine/rrdengine.c b/src/database/engine/rrdengine.c index 78ad873f7e9a7c..80e9b898512bd8 100644 --- a/src/database/engine/rrdengine.c +++ b/src/database/engine/rrdengine.c @@ -45,7 +45,9 @@ struct rrdeng_main { bool shutdown; size_t flushes_running; - size_t evictions_running; + size_t evict_main_running; + size_t evict_open_running; + size_t evict_extent_running; size_t cleanup_running; struct { @@ -86,8 +88,9 @@ struct rrdeng_main { .loop = {}, .async = {}, .timer = {}, + .retention_timer = {}, .flushes_running = 0, - .evictions_running = 0, + .evict_main_running = 0, .cleanup_running = 0, .cmd_queue = { @@ -138,12 +141,15 @@ struct rrdeng_work { static void work_request_init(void) { rrdeng_main.work_cmd.ar = aral_create( - "dbengine-work-cmd", - sizeof(struct rrdeng_work), - 0, - 65536, NULL, - NULL, NULL, false, false + "dbengine-work-cmd", + sizeof(struct rrdeng_work), + 0, + 0, + NULL, + NULL, NULL, false, false ); + + telemetry_aral_register(rrdeng_main.work_cmd.ar, "workers"); } enum LIBUV_WORKERS_STATUS { @@ -259,9 +265,11 @@ void page_descriptors_init(void) { "dbengine-descriptors", sizeof(struct page_descr_with_data), 0, - 65536 * 4, + 0, NULL, NULL, NULL, false, false); + + telemetry_aral_register(rrdeng_main.xt_io_descr.ar, "descriptors"); } struct page_descr_with_data *page_descriptor_get(void) { @@ -282,10 +290,12 @@ static void extent_io_descriptor_init(void) { "dbengine-extent-io", sizeof(struct extent_io_descriptor), 0, - 65536, + 0, NULL, NULL, NULL, false, false ); + + telemetry_aral_register(rrdeng_main.xt_io_descr.ar, "extent io"); } static struct extent_io_descriptor *extent_io_descriptor_get(void) { @@ -306,9 +316,11 @@ void rrdeng_query_handle_init(void) { "dbengine-query-handles", sizeof(struct rrdeng_query_handle), 0, - 65536, + 0, NULL, NULL, NULL, false, false); + + telemetry_aral_register(rrdeng_main.handles.ar, "query handles"); } struct rrdeng_query_handle *rrdeng_query_handle_get(void) { @@ -426,9 +438,11 @@ static void rrdeng_cmd_queue_init(void) { rrdeng_main.cmd_queue.ar = aral_create("dbengine-opcodes", sizeof(struct rrdeng_cmd), 0, - 65536, + 0, NULL, NULL, NULL, false, false); + + telemetry_aral_register(rrdeng_main.cmd_queue.ar, "opcodes"); } static inline STORAGE_PRIORITY rrdeng_enq_cmd_map_opcode_to_priority(enum rrdeng_opcode opcode, STORAGE_PRIORITY priority) { @@ -1376,17 +1390,41 @@ static void *cache_flush_tp_worker(struct rrdengine_instance *ctx __maybe_unused return data; worker_is_busy(UV_EVENT_DBENGINE_FLUSH_MAIN_CACHE); - pgc_flush_pages(main_cache, 0); + while (pgc_flush_pages(main_cache)) + yield_the_processor(); return data; } -static void *cache_evict_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { +static void *cache_evict_main_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { if (!main_cache) return data; worker_is_busy(UV_EVENT_DBENGINE_EVICT_MAIN_CACHE); - pgc_evict_pages(main_cache, 0, 0); + while (pgc_evict_pages(main_cache, 0, 0)) + yield_the_processor(); + + return data; +} + +static void *cache_evict_open_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { + if (!open_cache) + return data; + + worker_is_busy(UV_EVENT_DBENGINE_EVICT_OPEN_CACHE); + while (pgc_evict_pages(open_cache, 0, 0)) + yield_the_processor(); + + return data; +} + +static void *cache_evict_extent_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { + if (!extent_cache) + return data; + + worker_is_busy(UV_EVENT_DBENGINE_EVICT_EXTENT_CACHE); + while (pgc_evict_pages(extent_cache, 0, 0)) + yield_the_processor(); return data; } @@ -1532,8 +1570,16 @@ static void after_do_cache_flush(struct rrdengine_instance *ctx __maybe_unused, rrdeng_main.flushes_running--; } -static void after_do_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { - rrdeng_main.evictions_running--; +static void after_do_main_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { + rrdeng_main.evict_main_running--; +} + +static void after_do_open_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { + rrdeng_main.evict_open_running--; +} + +static void after_do_extent_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { + rrdeng_main.evict_extent_running--; } static void after_journal_v2_indexing(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { @@ -1544,6 +1590,7 @@ static void after_journal_v2_indexing(struct rrdengine_instance *ctx __maybe_unu struct rrdeng_buffer_sizes rrdeng_get_buffer_sizes(void) { return (struct rrdeng_buffer_sizes) { .pgc = pgc_aral_overhead() + pgc_aral_structures(), + .pgd = pgd_aral_overhead() + pgd_aral_structures(), .mrg = mrg_aral_overhead() + mrg_aral_structures(), .opcodes = aral_overhead(rrdeng_main.cmd_queue.ar) + aral_structures(rrdeng_main.cmd_queue.ar), .handles = aral_overhead(rrdeng_main.handles.ar) + aral_structures(rrdeng_main.handles.ar), @@ -1642,8 +1689,7 @@ bool rrdeng_ctx_tier_cap_exceeded(struct rrdengine_instance *ctx) return false; } -void retention_timer_cb(uv_timer_t *handle) -{ +static void retention_timer_cb(uv_timer_t *handle) { if (!localhost) return; @@ -1663,7 +1709,7 @@ void retention_timer_cb(uv_timer_t *handle) worker_is_idle(); } -void timer_cb(uv_timer_t* handle) { +static void timer_per_sec_cb(uv_timer_t* handle) { worker_is_busy(RRDENG_TIMER_CB); uv_stop(handle->loop); uv_update_time(handle->loop); @@ -1672,14 +1718,17 @@ void timer_cb(uv_timer_t* handle) { worker_set_metric(RRDENG_WORKS_DISPATCHED, (NETDATA_DOUBLE)__atomic_load_n(&rrdeng_main.work_cmd.atomics.dispatched, __ATOMIC_RELAXED)); worker_set_metric(RRDENG_WORKS_EXECUTING, (NETDATA_DOUBLE)__atomic_load_n(&rrdeng_main.work_cmd.atomics.executing, __ATOMIC_RELAXED)); - rrdeng_enq_cmd(NULL, RRDENG_OPCODE_FLUSH_INIT, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); - rrdeng_enq_cmd(NULL, RRDENG_OPCODE_EVICT_INIT, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); + // rrdeng_enq_cmd(NULL, RRDENG_OPCODE_EVICT_MAIN, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); + // rrdeng_enq_cmd(NULL, RRDENG_OPCODE_EVICT_OPEN, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); + // rrdeng_enq_cmd(NULL, RRDENG_OPCODE_EVICT_EXTENT, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); + rrdeng_enq_cmd(NULL, RRDENG_OPCODE_FLUSH_MAIN, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); rrdeng_enq_cmd(NULL, RRDENG_OPCODE_CLEANUP, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); worker_is_idle(); } static void dbengine_initialize_structures(void) { + pgd_init_arals(); pgc_and_mrg_initialize(); pdc_init(); @@ -1691,7 +1740,6 @@ static void dbengine_initialize_structures(void) { rrdeng_query_handle_init(); page_descriptors_init(); extent_buffer_init(); - pgd_init_arals(); extent_io_descriptor_init(); } @@ -1900,8 +1948,8 @@ void dbengine_event_loop(void* arg) { worker_register_job_name(RRDENG_OPCODE_FLUSHED_TO_OPEN, "flushed to open"); worker_register_job_name(RRDENG_OPCODE_DATABASE_ROTATE, "db rotate"); worker_register_job_name(RRDENG_OPCODE_JOURNAL_INDEX, "journal index"); - worker_register_job_name(RRDENG_OPCODE_FLUSH_INIT, "flush init"); - worker_register_job_name(RRDENG_OPCODE_EVICT_INIT, "evict init"); + worker_register_job_name(RRDENG_OPCODE_FLUSH_MAIN, "flush init"); + worker_register_job_name(RRDENG_OPCODE_EVICT_MAIN, "evict init"); worker_register_job_name(RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown"); worker_register_job_name(RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce"); worker_register_job_name(RRDENG_OPCODE_SHUTDOWN_EVLOOP, "dbengine shutdown"); @@ -1914,8 +1962,8 @@ void dbengine_event_loop(void* arg) { worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_FLUSHED_TO_OPEN, "flushed to open cb"); worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_DATABASE_ROTATE, "db rotate cb"); worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_JOURNAL_INDEX, "journal index cb"); - worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_FLUSH_INIT, "flush init cb"); - worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EVICT_INIT, "evict init cb"); + worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_FLUSH_MAIN, "flush init cb"); + worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EVICT_MAIN, "evict init cb"); worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown cb"); worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce cb"); @@ -1932,7 +1980,7 @@ void dbengine_event_loop(void* arg) { struct rrdeng_cmd cmd; main->tid = gettid_cached(); - fatal_assert(0 == uv_timer_start(&main->timer, timer_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS)); + fatal_assert(0 == uv_timer_start(&main->timer, timer_per_sec_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS)); fatal_assert(0 == uv_timer_start(&main->retention_timer, retention_timer_cb, TIMER_PERIOD_MS * 60, TIMER_PERIOD_MS * 60)); bool shutdown = false; @@ -1974,18 +2022,34 @@ void dbengine_event_loop(void* arg) { break; } - case RRDENG_OPCODE_FLUSH_INIT: { - if(rrdeng_main.flushes_running < (size_t)(libuv_worker_threads / 4)) { + case RRDENG_OPCODE_FLUSH_MAIN: { + if(rrdeng_main.flushes_running < pgc_max_flushers()) { rrdeng_main.flushes_running++; work_dispatch(NULL, NULL, NULL, opcode, cache_flush_tp_worker, after_do_cache_flush); } break; } - case RRDENG_OPCODE_EVICT_INIT: { - if(!rrdeng_main.evictions_running) { - rrdeng_main.evictions_running++; - work_dispatch(NULL, NULL, NULL, opcode, cache_evict_tp_worker, after_do_cache_evict); + case RRDENG_OPCODE_EVICT_MAIN: { + if(rrdeng_main.evict_main_running < pgc_max_evictors()) { + rrdeng_main.evict_main_running++; + work_dispatch(NULL, NULL, NULL, opcode, cache_evict_main_tp_worker, after_do_main_cache_evict); + } + break; + } + + case RRDENG_OPCODE_EVICT_OPEN: { + if(rrdeng_main.evict_open_running < pgc_max_evictors()) { + rrdeng_main.evict_open_running++; + work_dispatch(NULL, NULL, NULL, opcode, cache_evict_open_tp_worker, after_do_open_cache_evict); + } + break; + } + + case RRDENG_OPCODE_EVICT_EXTENT: { + if(rrdeng_main.evict_extent_running < pgc_max_evictors()) { + rrdeng_main.evict_extent_running++; + work_dispatch(NULL, NULL, NULL, opcode, cache_evict_extent_tp_worker, after_do_extent_cache_evict); } break; } @@ -2049,6 +2113,7 @@ void dbengine_event_loop(void* arg) { case RRDENG_OPCODE_SHUTDOWN_EVLOOP: { uv_close((uv_handle_t *)&main->async, NULL); + (void) uv_timer_stop(&main->timer); uv_close((uv_handle_t *)&main->timer, NULL); diff --git a/src/database/engine/rrdengine.h b/src/database/engine/rrdengine.h index 0a22477acfcce1..190585e4d665a5 100644 --- a/src/database/engine/rrdengine.h +++ b/src/database/engine/rrdengine.h @@ -201,7 +201,6 @@ struct rrdeng_collect_handle { struct metric *metric; struct pgc_page *pgc_page; struct pgd *page_data; - size_t page_data_size; struct pg_alignment *alignment; uint32_t page_entries_max; uint32_t page_position; // keep track of the current page size, to make sure we don't exceed it @@ -249,8 +248,10 @@ enum rrdeng_opcode { RRDENG_OPCODE_FLUSHED_TO_OPEN, RRDENG_OPCODE_DATABASE_ROTATE, RRDENG_OPCODE_JOURNAL_INDEX, - RRDENG_OPCODE_FLUSH_INIT, - RRDENG_OPCODE_EVICT_INIT, + RRDENG_OPCODE_FLUSH_MAIN, + RRDENG_OPCODE_EVICT_MAIN, + RRDENG_OPCODE_EVICT_OPEN, + RRDENG_OPCODE_EVICT_EXTENT, RRDENG_OPCODE_CTX_SHUTDOWN, RRDENG_OPCODE_CTX_QUIESCE, RRDENG_OPCODE_CTX_POPULATE_MRG, diff --git a/src/database/engine/rrdengineapi.c b/src/database/engine/rrdengineapi.c index 21d95fafc91957..4951577d95c11b 100755 --- a/src/database/engine/rrdengineapi.c +++ b/src/database/engine/rrdengineapi.c @@ -59,6 +59,8 @@ __attribute__((constructor)) void initialize_multidb_ctx(void) { initialize_single_ctx(multidb_ctx[i]); } +uint64_t dbengine_out_of_memory_protection = 0; +bool dbengine_use_all_ram_for_caches = false; int db_engine_journal_check = 0; bool new_dbengine_defaults = false; bool legacy_multihost_db_space = false; @@ -279,8 +281,7 @@ STORAGE_COLLECT_HANDLE *rrdeng_store_metric_init(STORAGE_METRIC_HANDLE *smh, uin handle->pgc_page = NULL; handle->page_data = NULL; - handle->page_data_size = 0; - + handle->page_position = 0; handle->page_entries_max = 0; handle->update_every_ut = (usec_t)update_every * USEC_PER_SEC; @@ -339,7 +340,6 @@ void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *sch) { handle->page_position = 0; handle->page_entries_max = 0; handle->page_data = NULL; - handle->page_data_size = 0; // important! // we should never zero page end time ut, because this will allow @@ -355,8 +355,7 @@ void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *sch) { static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *handle, struct rrdengine_instance *ctx, usec_t point_in_time_ut, - PGD *data, - size_t data_size) { + PGD *data) { time_t point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC); const uint32_t update_every_s = (uint32_t)(handle->update_every_ut / USEC_PER_SEC); @@ -365,7 +364,7 @@ static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *ha .metric_id = mrg_metric_id(main_mrg, handle->metric), .start_time_s = point_in_time_s, .end_time_s = point_in_time_s, - .size = data_size, + .size = pgd_memory_footprint(data), .data = data, .update_every_s = update_every_s, .hot = true @@ -405,7 +404,7 @@ static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *ha pgc_page = pgc_page_add_and_acquire(main_cache, page_entry, &added); } - handle->page_entries_max = data_size / CTX_POINT_SIZE_BYTES(ctx); + handle->page_entries_max = pgd_capacity(data); handle->page_start_time_ut = point_in_time_ut; handle->page_end_time_ut = point_in_time_ut; handle->page_position = 1; // zero is already in our data @@ -436,7 +435,7 @@ static size_t aligned_allocation_entries(size_t max_slots, size_t target_slot, t return slots; } -static PGD *rrdeng_alloc_new_page_data(struct rrdeng_collect_handle *handle, size_t *data_size, usec_t point_in_time_ut) { +static PGD *rrdeng_alloc_new_page_data(struct rrdeng_collect_handle *handle, usec_t point_in_time_ut) { struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric); PGD *d = NULL; @@ -463,17 +462,11 @@ static PGD *rrdeng_alloc_new_page_data(struct rrdeng_collect_handle *handle, siz internal_fatal(slots < 3 || slots > max_slots, "ooops! wrong distribution of metrics across time"); internal_fatal(size > tier_page_size[ctx->config.tier] || size < CTX_POINT_SIZE_BYTES(ctx) * 2, "ooops! wrong page size"); - *data_size = size; - switch (ctx->config.page_type) { case RRDENG_PAGE_TYPE_ARRAY_32BIT: case RRDENG_PAGE_TYPE_ARRAY_TIER1: - d = pgd_create(ctx->config.page_type, slots); - break; case RRDENG_PAGE_TYPE_GORILLA_32BIT: - // ignore slots, and use the fixed number of slots per gorilla buffer. - // gorilla will automatically add more buffers if needed. - d = pgd_create(ctx->config.page_type, RRDENG_GORILLA_32BIT_BUFFER_SLOTS); + d = pgd_create(ctx->config.page_type, slots); break; default: fatal("Unknown page type: %uc\n", ctx->config.page_type); @@ -496,24 +489,25 @@ static void rrdeng_store_metric_append_point(STORAGE_COLLECT_HANDLE *sch, struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric); if(unlikely(!handle->page_data)) - handle->page_data = rrdeng_alloc_new_page_data(handle, &handle->page_data_size, point_in_time_ut); + handle->page_data = rrdeng_alloc_new_page_data(handle, point_in_time_ut); timing_step(TIMING_STEP_DBENGINE_CHECK_DATA); - pgd_append_point(handle->page_data, - point_in_time_ut, - n, min_value, max_value, count, anomaly_count, flags, - handle->page_position); + size_t additional_bytes = pgd_append_point(handle->page_data, + point_in_time_ut, + n, min_value, max_value, count, anomaly_count, flags, + handle->page_position); timing_step(TIMING_STEP_DBENGINE_PACK); if(unlikely(!handle->pgc_page)) { - rrdeng_store_metric_create_new_page(handle, ctx, point_in_time_ut, handle->page_data, handle->page_data_size); + rrdeng_store_metric_create_new_page(handle, ctx, point_in_time_ut, handle->page_data); // handle->position is set to 1 already } else { // update an existing page - pgc_page_hot_set_end_time_s(main_cache, handle->pgc_page, (time_t) (point_in_time_ut / USEC_PER_SEC)); + pgc_page_hot_set_end_time_s(main_cache, handle->pgc_page, + (time_t) (point_in_time_ut / USEC_PER_SEC), additional_bytes); handle->page_end_time_ut = point_in_time_ut; if(unlikely(++handle->page_position >= handle->page_entries_max)) { diff --git a/src/database/engine/rrdengineapi.h b/src/database/engine/rrdengineapi.h index cf9606255f81d2..1c1e47971e785f 100644 --- a/src/database/engine/rrdengineapi.h +++ b/src/database/engine/rrdengineapi.h @@ -13,6 +13,9 @@ #define RRDENG_FD_BUDGET_PER_INSTANCE (50) +extern uint64_t dbengine_out_of_memory_protection; +extern bool dbengine_use_all_ram_for_caches; + extern int default_rrdeng_page_cache_mb; extern int default_rrdeng_extent_cache_mb; extern int db_engine_journal_check; @@ -218,6 +221,7 @@ struct rrdeng_buffer_sizes { size_t deol; size_t pd; size_t pgc; + size_t pgd; size_t mrg; #ifdef PDC_USE_JULYL size_t julyl; diff --git a/src/database/rrd-database-mode.c b/src/database/rrd-database-mode.c new file mode 100644 index 00000000000000..123b1d021a828f --- /dev/null +++ b/src/database/rrd-database-mode.c @@ -0,0 +1,34 @@ + +#include "rrd.h" + +inline const char *rrd_memory_mode_name(RRD_MEMORY_MODE id) { + switch(id) { + case RRD_MEMORY_MODE_RAM: + return RRD_MEMORY_MODE_RAM_NAME; + + case RRD_MEMORY_MODE_NONE: + return RRD_MEMORY_MODE_NONE_NAME; + + case RRD_MEMORY_MODE_ALLOC: + return RRD_MEMORY_MODE_ALLOC_NAME; + + case RRD_MEMORY_MODE_DBENGINE: + return RRD_MEMORY_MODE_DBENGINE_NAME; + } + + STORAGE_ENGINE* eng = storage_engine_get(id); + if (eng) { + return eng->name; + } + + return RRD_MEMORY_MODE_RAM_NAME; +} + +RRD_MEMORY_MODE rrd_memory_mode_id(const char *name) { + STORAGE_ENGINE* eng = storage_engine_find(name); + if (eng) { + return eng->id; + } + + return RRD_MEMORY_MODE_RAM; +} diff --git a/src/database/rrd-database-mode.h b/src/database/rrd-database-mode.h new file mode 100644 index 00000000000000..fa8238d1996bbc --- /dev/null +++ b/src/database/rrd-database-mode.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_RRD_DATABASE_MODE_H +#define NETDATA_RRD_DATABASE_MODE_H + +typedef enum __attribute__ ((__packed__)) rrd_memory_mode { + RRD_MEMORY_MODE_NONE = 0, + RRD_MEMORY_MODE_RAM = 1, + RRD_MEMORY_MODE_ALLOC = 4, + RRD_MEMORY_MODE_DBENGINE = 5, + + // this is 8-bit +} RRD_MEMORY_MODE; + +#define RRD_MEMORY_MODE_NONE_NAME "none" +#define RRD_MEMORY_MODE_RAM_NAME "ram" +#define RRD_MEMORY_MODE_ALLOC_NAME "alloc" +#define RRD_MEMORY_MODE_DBENGINE_NAME "dbengine" + +extern RRD_MEMORY_MODE default_rrd_memory_mode; + +const char *rrd_memory_mode_name(RRD_MEMORY_MODE id); +RRD_MEMORY_MODE rrd_memory_mode_id(const char *name); + +#endif //NETDATA_RRD_DATABASE_MODE_H diff --git a/src/database/rrd.c b/src/database/rrd.c index b664ad3ae831ed..9039af21ef9fed 100644 --- a/src/database/rrd.c +++ b/src/database/rrd.c @@ -14,8 +14,6 @@ int rrd_delete_unupdated_dimensions = 0; */ -int default_rrd_update_every = UPDATE_EVERY; -int default_rrd_history_entries = RRD_DEFAULT_HISTORY_ENTRIES; #ifdef ENABLE_DBENGINE RRD_MEMORY_MODE default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE; #else @@ -24,42 +22,6 @@ RRD_MEMORY_MODE default_rrd_memory_mode = RRD_MEMORY_MODE_RAM; int gap_when_lost_iterations_above = 1; -// ---------------------------------------------------------------------------- -// RRD - memory modes - -inline const char *rrd_memory_mode_name(RRD_MEMORY_MODE id) { - switch(id) { - case RRD_MEMORY_MODE_RAM: - return RRD_MEMORY_MODE_RAM_NAME; - - case RRD_MEMORY_MODE_NONE: - return RRD_MEMORY_MODE_NONE_NAME; - - case RRD_MEMORY_MODE_ALLOC: - return RRD_MEMORY_MODE_ALLOC_NAME; - - case RRD_MEMORY_MODE_DBENGINE: - return RRD_MEMORY_MODE_DBENGINE_NAME; - } - - STORAGE_ENGINE* eng = storage_engine_get(id); - if (eng) { - return eng->name; - } - - return RRD_MEMORY_MODE_RAM_NAME; -} - -RRD_MEMORY_MODE rrd_memory_mode_id(const char *name) { - STORAGE_ENGINE* eng = storage_engine_find(name); - if (eng) { - return eng->id; - } - - return RRD_MEMORY_MODE_RAM; -} - - // ---------------------------------------------------------------------------- // RRD - algorithms types @@ -108,6 +70,9 @@ inline RRDSET_TYPE rrdset_type_id(const char *name) { else if(unlikely(strcmp(name, RRDSET_TYPE_STACKED_NAME) == 0)) return RRDSET_TYPE_STACKED; + else if(unlikely(strcmp(name, RRDSET_TYPE_HEATMAP_NAME) == 0)) + return RRDSET_TYPE_HEATMAP; + else // if(unlikely(strcmp(name, RRDSET_TYPE_LINE_NAME) == 0)) return RRDSET_TYPE_LINE; } @@ -123,6 +88,9 @@ const char *rrdset_type_name(RRDSET_TYPE chart_type) { case RRDSET_TYPE_STACKED: return RRDSET_TYPE_STACKED_NAME; + + case RRDSET_TYPE_HEATMAP: + return RRDSET_TYPE_HEATMAP_NAME; } } diff --git a/src/database/rrd.h b/src/database/rrd.h index e781b452f2670f..5f0232d0f79eef 100644 --- a/src/database/rrd.h +++ b/src/database/rrd.h @@ -8,6 +8,9 @@ extern "C" { #endif #include "libnetdata/libnetdata.h" +#include "rrd-database-mode.h" +#include "streaming/stream-traffic-types.h" +#include "streaming/stream-sender-commit.h" // non-existing structs instead of voids // to enable type checking at compile time @@ -69,26 +72,6 @@ struct rrdengine_instance; #endif // ---------------------------------------------------------------------------- -// memory mode - -typedef enum __attribute__ ((__packed__)) rrd_memory_mode { - RRD_MEMORY_MODE_NONE = 0, - RRD_MEMORY_MODE_RAM = 1, - RRD_MEMORY_MODE_ALLOC = 4, - RRD_MEMORY_MODE_DBENGINE = 5, - - // this is 8-bit -} RRD_MEMORY_MODE; - -#define RRD_MEMORY_MODE_NONE_NAME "none" -#define RRD_MEMORY_MODE_RAM_NAME "ram" -#define RRD_MEMORY_MODE_ALLOC_NAME "alloc" -#define RRD_MEMORY_MODE_DBENGINE_NAME "dbengine" - -extern RRD_MEMORY_MODE default_rrd_memory_mode; - -const char *rrd_memory_mode_name(RRD_MEMORY_MODE id); -RRD_MEMORY_MODE rrd_memory_mode_id(const char *name); struct ml_metrics_statistics { size_t anomalous; @@ -145,7 +128,7 @@ typedef enum __attribute__ ((__packed__)) rrdset_flags { #include "rrdlabels.h" #include "streaming/stream-capabilities.h" #include "streaming/stream-path.h" -#include "streaming/rrdpush.h" +#include "streaming/stream.h" //#include "aclk/aclk_rrdhost_state.h" #include "sqlite/sqlite_health.h" @@ -174,22 +157,19 @@ typedef enum __attribute__ ((__packed__)) rrdset_type { RRDSET_TYPE_LINE = 0, RRDSET_TYPE_AREA = 1, RRDSET_TYPE_STACKED = 2, + RRDSET_TYPE_HEATMAP = 3, } RRDSET_TYPE; #define RRDSET_TYPE_LINE_NAME "line" #define RRDSET_TYPE_AREA_NAME "area" #define RRDSET_TYPE_STACKED_NAME "stacked" +#define RRDSET_TYPE_HEATMAP_NAME "heatmap" RRDSET_TYPE rrdset_type_id(const char *name); const char *rrdset_type_name(RRDSET_TYPE chart_type); #include "contexts/rrdcontext.h" -extern bool dbengine_enabled; -extern size_t storage_tiers; -extern bool use_direct_io; -extern size_t storage_tiers_grouping_iterations[RRD_STORAGE_TIERS]; - typedef enum __attribute__ ((__packed__)) { RRD_BACKFILL_NONE = 0, RRD_BACKFILL_FULL, @@ -202,11 +182,6 @@ typedef enum __attribute__ ((__packed__)) { #define RRD_DEFAULT_HISTORY_ENTRIES 3600 #define RRD_HISTORY_ENTRIES_MAX (86400*365) -extern int default_rrd_update_every; -extern int default_rrd_history_entries; -extern int gap_when_lost_iterations_above; -extern time_t rrdset_free_obsolete_time_s; - #if defined(ENV32BIT) #define MIN_LIBUV_WORKER_THREADS 8 #define MAX_LIBUV_WORKER_THREADS 128 @@ -358,8 +333,8 @@ struct rrddim { struct { uint32_t sent_version; uint32_t dim_slot; - } sender; - } rrdpush; + } snd; + } stream; // ------------------------------------------------------------------------ // data collection members @@ -797,8 +772,8 @@ struct rrdset { uint32_t dim_last_slot_used; time_t resync_time_s; // the timestamp up to which we should resync clock upstream - } sender; - } rrdpush; + } snd; + } stream; // ------------------------------------------------------------------------ // db mode SAVE, MAP specifics @@ -869,13 +844,13 @@ static inline uint32_t rrdset_metadata_version(RRDSET *st) { } static inline uint32_t rrdset_metadata_upstream_version(RRDSET *st) { - return __atomic_load_n(&st->rrdpush.sender.sent_version, __ATOMIC_RELAXED); + return __atomic_load_n(&st->stream.snd.sent_version, __ATOMIC_RELAXED); } void rrdset_metadata_updated(RRDSET *st); static inline void rrdset_metadata_exposed_upstream(RRDSET *st, uint32_t version) { - __atomic_store_n(&st->rrdpush.sender.sent_version, version, __ATOMIC_RELAXED); + __atomic_store_n(&st->stream.snd.sent_version, version, __ATOMIC_RELAXED); } static inline bool rrdset_check_upstream_exposed(RRDSET *st) { @@ -888,17 +863,17 @@ static inline uint32_t rrddim_metadata_version(RRDDIM *rd) { } static inline uint32_t rrddim_metadata_upstream_version(RRDDIM *rd) { - return __atomic_load_n(&rd->rrdpush.sender.sent_version, __ATOMIC_RELAXED); + return __atomic_load_n(&rd->stream.snd.sent_version, __ATOMIC_RELAXED); } void rrddim_metadata_updated(RRDDIM *rd); static inline void rrddim_metadata_exposed_upstream(RRDDIM *rd, uint32_t version) { - __atomic_store_n(&rd->rrdpush.sender.sent_version, version, __ATOMIC_RELAXED); + __atomic_store_n(&rd->stream.snd.sent_version, version, __ATOMIC_RELAXED); } static inline void rrddim_metadata_exposed_upstream_clear(RRDDIM *rd) { - __atomic_store_n(&rd->rrdpush.sender.sent_version, 0, __ATOMIC_RELAXED); + __atomic_store_n(&rd->stream.snd.sent_version, 0, __ATOMIC_RELAXED); } static inline bool rrddim_check_upstream_exposed(RRDDIM *rd) { @@ -909,7 +884,7 @@ static inline bool rrddim_check_upstream_exposed(RRDDIM *rd) { // still, it can be removed, after the collector has finished // so, it is safe to check it without atomics static inline bool rrddim_check_upstream_exposed_collector(RRDDIM *rd) { - return rd->rrdset->version == rd->rrdpush.sender.sent_version; + return rd->rrdset->version == rd->stream.snd.sent_version; } STRING *rrd_string_strdupz(const char *s); @@ -952,16 +927,15 @@ typedef enum __attribute__ ((__packed__)) rrdhost_flags { RRDHOST_FLAG_PENDING_OBSOLETE_DIMENSIONS = (1 << 11), // the host has pending dimension obsoletions // Streaming sender - RRDHOST_FLAG_RRDPUSH_SENDER_INITIALIZED = (1 << 12), // the host has initialized rrdpush structures - RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN = (1 << 13), // When set, the sender thread is running - RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED = (1 << 14), // When set, the host is connected to a parent - RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS = (1 << 15), // when set, rrdset_done() should push metrics to parent - RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS = (1 << 16), // when set, we have logged the status of metrics streaming + RRDHOST_FLAG_STREAM_SENDER_INITIALIZED = (1 << 12), // the host has initialized streaming sender structures + RRDHOST_FLAG_STREAM_SENDER_ADDED = (1 << 13), // When set, the sender thread is running + RRDHOST_FLAG_STREAM_SENDER_CONNECTED = (1 << 14), // When set, the host is connected to a parent + RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS = (1 << 15), // when set, rrdset_done() should push metrics to parent + RRDHOST_FLAG_STREAM_SENDER_LOGGED_STATUS = (1 << 16), // when set, we have logged the status of metrics streaming // Health RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION = (1 << 17), // contains charts and dims with uninitialized variables RRDHOST_FLAG_INITIALIZED_HEALTH = (1 << 18), // the host has initialized health structures - // Exporting RRDHOST_FLAG_EXPORTING_SEND = (1 << 19), // send it to external databases RRDHOST_FLAG_EXPORTING_DONT_SEND = (1 << 20), // don't send it to external databases @@ -977,7 +951,7 @@ typedef enum __attribute__ ((__packed__)) rrdhost_flags { RRDHOST_FLAG_PENDING_CONTEXT_LOAD = (1 << 26), // Context needs to be loaded RRDHOST_FLAG_METADATA_CLAIMID = (1 << 27), // metadata needs to be stored in the database - RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED = (1 << 28), // set when the receiver part is disconnected + RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED = (1 << 28), // set when the receiver part is disconnected RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED = (1 << 29), // set when the host has updated global functions } RRDHOST_FLAGS; @@ -1011,9 +985,13 @@ typedef enum __attribute__ ((__packed__)) { #define rrdhost_option_set(host, flag) (host)->options |= flag #define rrdhost_option_clear(host, flag) (host)->options &= ~(flag) -#define rrdhost_has_rrdpush_sender_enabled(host) (rrdhost_option_check(host, RRDHOST_OPTION_SENDER_ENABLED) && (host)->sender) +#define rrdhost_has_stream_sender_enabled(host) (rrdhost_option_check(host, RRDHOST_OPTION_SENDER_ENABLED) && (host)->sender) -#define rrdhost_can_send_definitions_to_parent(host) (rrdhost_has_rrdpush_sender_enabled(host) && rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED)) +#define rrdhost_can_stream_metadata_to_parent(host) \ + (rrdhost_has_stream_sender_enabled(host) && \ + rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS) && \ + !rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED) \ + ) // ---------------------------------------------------------------------------- // Health data @@ -1059,6 +1037,7 @@ struct alarm_entry { RRDCALC_STATUS new_status; uint32_t flags; + int32_t pending_save_count; int delay; time_t delay_up_to_timestamp; @@ -1100,11 +1079,12 @@ typedef struct alarm_log { } ALARM_LOG; typedef struct health { - time_t health_delay_up_to; // a timestamp to delay alarms processing up to - STRING *health_default_exec; // the full path of the alarms notifications program - STRING *health_default_recipient; // the default recipient for all alarms - bool health_enabled; // 1 when this host has health enabled - bool use_summary_for_notifications; // whether to use the summary field as a subject for notifications + time_t delay_up_to; // a timestamp to delay alarms processing up to + STRING *default_exec; // the full path of the alarms notifications program + STRING *default_recipient; // the default recipient for all alarms + bool enabled; // 1 when this host has health enabled + bool use_summary_for_notifications; // whether to use the summary field as a subject for notifications + int32_t pending_transitions; // pending alert transitions to store } HEALTH; // ---------------------------------------------------------------------------- @@ -1140,7 +1120,7 @@ struct rrdhost_system_info { char *container; char *container_detection; char *is_k8s_node; - uint16_t hops; + int16_t hops; bool ml_capable; bool ml_enabled; char *install_type; @@ -1151,6 +1131,8 @@ struct rrdhost_system_info { struct rrdhost_system_info *rrdhost_labels_to_system_info(RRDLABELS *labels); +struct stream_thread; + struct rrdhost { char machine_guid[GUID_LEN + 1]; // the unique ID of this host @@ -1189,9 +1171,14 @@ struct rrdhost { struct rrdhost_system_info *system_info; // information collected from the host environment // ------------------------------------------------------------------------ - // streaming of data to remote hosts - rrdpush + // streaming and replication, configuration and status struct { + struct stream_thread *thread; + uint8_t refcount; + + // --- sender --- + struct { struct { struct { @@ -1206,10 +1193,27 @@ struct rrdhost { uint32_t last_used; // the last slot we used for a chart (increments only) } pluginsd_chart_slots; - char *destination; // where to send metrics to - char *api_key; // the api key at the receiving netdata + struct { + pid_t tid; + + time_t last_connected; // last time child connected (stored in db) + uint32_t connections; // the number of times this sender has connected + + struct { + size_t charts; // the number of charts currently being replicated to a parent + } replication; + } status; + + // reserved for the receiver/sender thread - do not use for other purposes + struct sender_buffer commit; + + STRING *destination; // where to send metrics to + STRING *api_key; // the api key at the receiving netdata SIMPLE_PATTERN *charts_matching; // pattern to match the charts to be sent - } send; + RRDHOST_STREAM_PARENTS parents; // the list of parents (extracted from destination) + } snd; + + // --- receiver --- struct { struct { @@ -1217,43 +1221,46 @@ struct rrdhost { uint32_t size; RRDSET **array; } pluginsd_chart_slots; - } receive; - RRDHOST_STREAM_PATH path; - } rrdpush; + struct { + pid_t tid; + + time_t last_connected; // the time the last sender was connected + time_t last_disconnected; // the time the last sender was disconnected + time_t last_chart; // the time of the last CHART streaming command + bool check_obsolete; // set when child connects, will instruct parent to + // trigger a check for obsoleted charts since previous connect - struct rrdpush_destinations *destinations; // a linked list of possible destinations - struct rrdpush_destinations *destination; // the current destination from the above list + uint32_t connections; // the number of times this receiver has connected + STREAM_HANDSHAKE exit_reason; // the last receiver exit reason - int32_t rrdpush_last_receiver_exit_reason; - time_t rrdpush_seconds_to_replicate; // max time we want to replicate from the child - time_t rrdpush_replication_step; // seconds per replication step - size_t rrdpush_receiver_replicating_charts; // the number of charts currently being replicated from a child - NETDATA_DOUBLE rrdpush_receiver_replication_percent; // the % of replication completion + struct { + size_t charts; // the number of charts currently being replicated from a child + NETDATA_DOUBLE percent; // the % of replication completion + } replication; + } status; + } rcv; + + // --- configuration --- + + struct { + time_t period; // max time we want to replicate from the child + time_t step; // seconds per replication step + } replication; + + RRDHOST_STREAM_PATH path; + } stream; // the following are state information for the threading // streaming metrics from this netdata to an upstream netdata struct sender_state *sender; - ND_THREAD *rrdpush_sender_thread; // the sender thread - size_t rrdpush_sender_replicating_charts; // the number of charts currently being replicated to a parent - struct aclk_sync_cfg_t *aclk_config; - uint32_t rrdpush_receiver_connection_counter; // the number of times this receiver has connected - uint32_t rrdpush_sender_connection_counter; // the number of times this sender has connected + struct receiver_state *receiver; + SPINLOCK receiver_lock; // ------------------------------------------------------------------------ - // streaming of data from remote hosts - rrdpush receiver - - time_t last_connected; // last time child connected (stored in db) - time_t child_connect_time; // the time the last sender was connected - time_t child_last_chart_command; // the time of the last CHART streaming command - time_t child_disconnected_time; // the time the last sender was disconnected - int connected_children_count; // number of senders currently streaming - struct receiver_state *receiver; - SPINLOCK receiver_lock; - int trigger_chart_obsoletion_check; // set when child connects, will instruct parent to - // trigger a check for obsoleted charts since previous connect + struct aclk_sync_cfg_t *aclk_config; // ------------------------------------------------------------------------ // health monitoring options @@ -1330,6 +1337,9 @@ struct rrdhost { }; extern RRDHOST *localhost; +#define rrdhost_receiver_lock(host) spinlock_lock(&(host)->receiver_lock) +#define rrdhost_receiver_unlock(host) spinlock_unlock(&(host)->receiver_lock) + #define rrdhost_hostname(host) string2str((host)->hostname) #define rrdhost_registry_hostname(host) string2str((host)->registry_hostname) #define rrdhost_os(host) string2str((host)->os) @@ -1338,17 +1348,17 @@ extern RRDHOST *localhost; #define rrdhost_program_name(host) string2str((host)->program_name) #define rrdhost_program_version(host) string2str((host)->program_version) -#define rrdhost_receiver_replicating_charts(host) (__atomic_load_n(&((host)->rrdpush_receiver_replicating_charts), __ATOMIC_RELAXED)) -#define rrdhost_receiver_replicating_charts_plus_one(host) (__atomic_add_fetch(&((host)->rrdpush_receiver_replicating_charts), 1, __ATOMIC_RELAXED)) -#define rrdhost_receiver_replicating_charts_minus_one(host) (__atomic_sub_fetch(&((host)->rrdpush_receiver_replicating_charts), 1, __ATOMIC_RELAXED)) -#define rrdhost_receiver_replicating_charts_zero(host) (__atomic_store_n(&((host)->rrdpush_receiver_replicating_charts), 0, __ATOMIC_RELAXED)) +#define rrdhost_receiver_replicating_charts(host) (__atomic_load_n(&((host)->stream.rcv.status.replication.charts), __ATOMIC_RELAXED)) +#define rrdhost_receiver_replicating_charts_plus_one(host) (__atomic_add_fetch(&((host)->stream.rcv.status.replication.charts), 1, __ATOMIC_RELAXED)) +#define rrdhost_receiver_replicating_charts_minus_one(host) (__atomic_sub_fetch(&((host)->stream.rcv.status.replication.charts), 1, __ATOMIC_RELAXED)) +#define rrdhost_receiver_replicating_charts_zero(host) (__atomic_store_n(&((host)->stream.rcv.status.replication.charts), 0, __ATOMIC_RELAXED)) -#define rrdhost_sender_replicating_charts(host) (__atomic_load_n(&((host)->rrdpush_sender_replicating_charts), __ATOMIC_RELAXED)) -#define rrdhost_sender_replicating_charts_plus_one(host) (__atomic_add_fetch(&((host)->rrdpush_sender_replicating_charts), 1, __ATOMIC_RELAXED)) -#define rrdhost_sender_replicating_charts_minus_one(host) (__atomic_sub_fetch(&((host)->rrdpush_sender_replicating_charts), 1, __ATOMIC_RELAXED)) -#define rrdhost_sender_replicating_charts_zero(host) (__atomic_store_n(&((host)->rrdpush_sender_replicating_charts), 0, __ATOMIC_RELAXED)) +#define rrdhost_sender_replicating_charts(host) (__atomic_load_n(&((host)->stream.snd.status.replication.charts), __ATOMIC_RELAXED)) +#define rrdhost_sender_replicating_charts_plus_one(host) (__atomic_add_fetch(&((host)->stream.snd.status.replication.charts), 1, __ATOMIC_RELAXED)) +#define rrdhost_sender_replicating_charts_minus_one(host) (__atomic_sub_fetch(&((host)->stream.snd.status.replication.charts), 1, __ATOMIC_RELAXED)) +#define rrdhost_sender_replicating_charts_zero(host) (__atomic_store_n(&((host)->stream.snd.status.replication.charts), 0, __ATOMIC_RELAXED)) -#define rrdhost_is_online(host) ((host) == localhost || rrdhost_option_check(host, RRDHOST_OPTION_VIRTUAL_HOST) || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN | RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED)) +#define rrdhost_is_online(host) ((host) == localhost || rrdhost_option_check(host, RRDHOST_OPTION_VIRTUAL_HOST) || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN | RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED)) bool rrdhost_matches_window(RRDHOST *host, time_t after, time_t before, time_t now); extern DICTIONARY *rrdhost_root_index; @@ -1409,14 +1419,14 @@ RRDHOST *rrdhost_find_or_create( int update_every, long history, RRD_MEMORY_MODE mode, - unsigned int health_enabled, - unsigned int rrdpush_enabled, - const char *rrdpush_destination, - const char *rrdpush_api_key, - const char *rrdpush_send_charts_matching, - bool rrdpush_enable_replication, - time_t rrdpush_seconds_to_replicate, - time_t rrdpush_replication_step, + bool health, + bool stream, + STRING *parents, + STRING *api_key, + STRING *send_charts_matching, + bool replication, + time_t replication_period, + time_t replication_step, struct rrdhost_system_info *system_info, bool is_archived); @@ -1443,11 +1453,41 @@ RRDSET *rrdset_create_custom(RRDHOST *host , RRD_MEMORY_MODE memory_mode , long history_entries); -#define rrdset_create(host, type, id, name, family, context, title, units, plugin, module, priority, update_every, chart_type) \ - rrdset_create_custom(host, type, id, name, family, context, title, units, plugin, module, priority, update_every, chart_type, (host)->rrd_memory_mode, (host)->rrd_history_entries) +static inline +RRDSET *rrdset_create(RRDHOST *host + , const char *type + , const char *id + , const char *name + , const char *family + , const char *context + , const char *title + , const char *units + , const char *plugin + , const char *module + , long priority + , int update_every + , RRDSET_TYPE chart_type) { + return rrdset_create_custom( + host, type, id, name, family, context, title, units, plugin, module, priority, update_every, chart_type, (host)->rrd_memory_mode, (host)->rrd_history_entries); +} -#define rrdset_create_localhost(type, id, name, family, context, title, units, plugin, module, priority, update_every, chart_type) \ - rrdset_create(localhost, type, id, name, family, context, title, units, plugin, module, priority, update_every, chart_type) +static inline +RRDSET *rrdset_create_localhost( + const char *type + , const char *id + , const char *name + , const char *family + , const char *context + , const char *title + , const char *units + , const char *plugin + , const char *module + , long priority + , int update_every + , RRDSET_TYPE chart_type) { + return rrdset_create( + localhost, type, id, name, family, context, title, units, plugin, module, priority, update_every, chart_type); +} void rrdhost_free_all(void); @@ -1597,7 +1637,6 @@ void set_host_properties( const char *os, const char *tzone, const char *abbrev_tzone, int32_t utc_offset, const char *prog_name, const char *prog_version); -size_t get_tier_grouping(size_t tier); void store_metric_collection_completed(void); static inline void rrdhost_retention(RRDHOST *host, time_t now, bool online, time_t *from, time_t *to) { diff --git a/src/database/rrddim.c b/src/database/rrddim.c index 4a16488f004b08..de2c6259c589ec 100644 --- a/src/database/rrddim.c +++ b/src/database/rrddim.c @@ -53,7 +53,7 @@ static void rrddim_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v rd->rrdset = st; - rd->rrdpush.sender.dim_slot = __atomic_add_fetch(&st->rrdpush.sender.dim_last_slot_used, 1, __ATOMIC_RELAXED); + rd->stream.snd.dim_slot = __atomic_add_fetch(&st->stream.snd.dim_last_slot_used, 1, __ATOMIC_RELAXED); if(rrdset_flag_check(st, RRDSET_FLAG_STORE_FIRST)) rd->collector.counter = 1; @@ -289,7 +289,7 @@ size_t rrddim_size(void) { void rrddim_index_init(RRDSET *st) { if(!st->rrddim_root_index) { st->rrddim_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, - &dictionary_stats_category_rrdset_rrddim, rrddim_size()); + &dictionary_stats_category_rrddim, rrddim_size()); dictionary_register_insert_callback(st->rrddim_root_index, rrddim_insert_callback, NULL); dictionary_register_conflict_callback(st->rrddim_root_index, rrddim_conflict_callback, NULL); diff --git a/src/database/rrdfunctions-exporters.c b/src/database/rrdfunctions-exporters.c index 9a1511be15e580..8523639dd01ce4 100644 --- a/src/database/rrdfunctions-exporters.c +++ b/src/database/rrdfunctions-exporters.c @@ -5,7 +5,7 @@ #include "rrdfunctions-internals.h" #include "rrdfunctions-exporters.h" -void rrd_chart_functions_expose_rrdpush(RRDSET *st, BUFFER *wb) { +void stream_sender_send_rrdset_functions(RRDSET *st, BUFFER *wb) { if(!st->functions_view) return; @@ -27,7 +27,7 @@ void rrd_chart_functions_expose_rrdpush(RRDSET *st, BUFFER *wb) { dfe_done(t); } -void rrd_global_functions_expose_rrdpush(RRDHOST *host, BUFFER *wb, bool dyncfg) { +void stream_sender_send_global_rrdhost_functions(RRDHOST *host, BUFFER *wb, bool dyncfg) { rrdhost_flag_clear(host, RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED); size_t configs = 0; diff --git a/src/database/rrdfunctions-exporters.h b/src/database/rrdfunctions-exporters.h index 295e670c95fb9b..db89d0fc003be5 100644 --- a/src/database/rrdfunctions-exporters.h +++ b/src/database/rrdfunctions-exporters.h @@ -7,8 +7,8 @@ #define RRDFUNCTIONS_VERSION_SEPARATOR "|" -void rrd_chart_functions_expose_rrdpush(RRDSET *st, BUFFER *wb); -void rrd_global_functions_expose_rrdpush(RRDHOST *host, BUFFER *wb, bool dyncfg); +void stream_sender_send_rrdset_functions(RRDSET *st, BUFFER *wb); +void stream_sender_send_global_rrdhost_functions(RRDHOST *host, BUFFER *wb, bool dyncfg); void chart_functions2json(RRDSET *st, BUFFER *wb); void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst, void *value, size_t value_size); diff --git a/src/database/rrdhost.c b/src/database/rrdhost.c index 1902746ee51a3e..8a7fe157929885 100644 --- a/src/database/rrdhost.c +++ b/src/database/rrdhost.c @@ -7,33 +7,9 @@ #error RRD_STORAGE_TIERS is not 5 - you need to update the grouping iterations per tier #endif -static void rrdhost_streaming_sender_structures_init(RRDHOST *host); - -bool dbengine_enabled = false; // will become true if and when dbengine is initialized -size_t storage_tiers = 3; -bool use_direct_io = true; -size_t storage_tiers_grouping_iterations[RRD_STORAGE_TIERS] = {1, 60, 60, 60, 60}; -size_t storage_tiers_collection_per_sec[RRD_STORAGE_TIERS] = {1, 60, 3600, 8 * 3600, 24 * 3600}; -double storage_tiers_retention_days[RRD_STORAGE_TIERS] = {14, 90, 2 * 365, 2 * 365, 2 * 365}; - -size_t get_tier_grouping(size_t tier) { - if(unlikely(tier >= storage_tiers)) tier = storage_tiers - 1; - - size_t grouping = 1; - // first tier is always 1 iteration of whatever update every the chart has - for(size_t i = 1; i <= tier ;i++) - grouping *= storage_tiers_grouping_iterations[i]; - - return grouping; -} - RRDHOST *localhost = NULL; netdata_rwlock_t rrd_rwlock = NETDATA_RWLOCK_INITIALIZER; -time_t rrdset_free_obsolete_time_s = 3600; -time_t rrdhost_free_orphan_time_s = 3600; -time_t rrdhost_free_ephemeral_time_s = 86400; - RRDHOST *find_host_by_node_id(char *node_id) { ND_UUID node_uuid; @@ -133,7 +109,8 @@ inline RRDHOST *rrdhost_find_by_hostname(const char *hostname) { if(unlikely(!strcmp(hostname, "localhost"))) return localhost; - return dictionary_get(rrdhost_root_index_hostname, hostname); + RRDHOST *host = dictionary_get(rrdhost_root_index_hostname, hostname); + return host; } static inline void rrdhost_index_del_hostname(RRDHOST *host) { @@ -231,34 +208,6 @@ void set_host_properties(RRDHOST *host, int update_every, RRD_MEMORY_MODE memory // ---------------------------------------------------------------------------- // RRDHOST - add a host -static void rrdhost_initialize_rrdpush_sender(RRDHOST *host, - unsigned int rrdpush_enabled, - const char *rrdpush_destination, - const char *rrdpush_api_key, - const char *rrdpush_send_charts_matching -) { - if(rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_INITIALIZED)) return; - - if(rrdpush_enabled && rrdpush_destination && *rrdpush_destination && rrdpush_api_key && *rrdpush_api_key) { - rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_INITIALIZED); - - rrdhost_streaming_sender_structures_init(host); - - host->sender->ssl = NETDATA_SSL_UNSET_CONNECTION; - - host->rrdpush.send.destination = strdupz(rrdpush_destination); - rrdpush_destinations_init(host); - - host->rrdpush.send.api_key = strdupz(rrdpush_api_key); - host->rrdpush.send.charts_matching = simple_pattern_create(rrdpush_send_charts_matching, NULL, - SIMPLE_PATTERN_EXACT, true); - - rrdhost_option_set(host, RRDHOST_OPTION_SENDER_ENABLED); - } - else - rrdhost_option_clear(host, RRDHOST_OPTION_SENDER_ENABLED); -} - #ifdef ENABLE_DBENGINE // // true on success @@ -326,6 +275,24 @@ static RRDHOST *prepare_host_for_unittest(RRDHOST *host) } #endif +static void rrdhost_set_replication_parameters(RRDHOST *host, RRD_MEMORY_MODE memory_mode, time_t period, time_t step) { + host->stream.replication.period = period; + host->stream.replication.step = step; + host->stream.rcv.status.replication.percent = 100.0; + + switch(memory_mode) { + default: + case RRD_MEMORY_MODE_ALLOC: + case RRD_MEMORY_MODE_RAM: + if(host->stream.replication.period > (time_t) host->rrd_history_entries * (time_t) host->rrd_update_every) + host->stream.replication.period = (time_t) host->rrd_history_entries * (time_t) host->rrd_update_every; + break; + + case RRD_MEMORY_MODE_DBENGINE: + break; + } +} + static RRDHOST *rrdhost_create( const char *hostname, const char *registry_hostname, @@ -339,14 +306,14 @@ static RRDHOST *rrdhost_create( int update_every, long entries, RRD_MEMORY_MODE memory_mode, - unsigned int health_enabled, - unsigned int rrdpush_enabled, - const char *rrdpush_destination, - const char *rrdpush_api_key, - const char *rrdpush_send_charts_matching, - bool rrdpush_enable_replication, - time_t rrdpush_seconds_to_replicate, - time_t rrdpush_replication_step, + bool health, + bool stream, + STRING *parents, + STRING *api_key, + STRING *send_charts_matching, + bool replication, + time_t replication_period, + time_t replication_step, struct rrdhost_system_info *system_info, int is_localhost, bool archived @@ -363,6 +330,8 @@ static RRDHOST *rrdhost_create( __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(RRDHOST), __ATOMIC_RELAXED); strncpyz(host->machine_guid, guid, GUID_LEN + 1); + rrdhost_stream_path_init(host); + rrdhost_stream_parents_init(host); set_host_properties( host, @@ -379,38 +348,23 @@ static RRDHOST *rrdhost_create( rrdhost_init_hostname(host, hostname, false); host->rrd_history_entries = align_entries_to_pagesize(memory_mode, entries); - host->health.health_enabled = ((memory_mode == RRD_MEMORY_MODE_NONE)) ? 0 : health_enabled; + host->health.enabled = ((memory_mode == RRD_MEMORY_MODE_NONE)) ? 0 : health; spinlock_init(&host->receiver_lock); if (likely(!archived)) { rrd_functions_host_init(host); - host->last_connected = now_realtime_sec(); + host->stream.snd.status.last_connected = now_realtime_sec(); host->rrdlabels = rrdlabels_create(); - rrdhost_initialize_rrdpush_sender( - host, rrdpush_enabled, rrdpush_destination, rrdpush_api_key, rrdpush_send_charts_matching); + stream_sender_structures_init(host, stream, parents, api_key, send_charts_matching); } - if(rrdpush_enable_replication) + if(replication) rrdhost_option_set(host, RRDHOST_OPTION_REPLICATION); else rrdhost_option_clear(host, RRDHOST_OPTION_REPLICATION); - host->rrdpush_seconds_to_replicate = rrdpush_seconds_to_replicate; - host->rrdpush_replication_step = rrdpush_replication_step; - host->rrdpush_receiver_replication_percent = 100.0; - - switch(memory_mode) { - default: - case RRD_MEMORY_MODE_ALLOC: - case RRD_MEMORY_MODE_RAM: - if(host->rrdpush_seconds_to_replicate > (time_t) host->rrd_history_entries * (time_t) host->rrd_update_every) - host->rrdpush_seconds_to_replicate = (time_t) host->rrd_history_entries * (time_t) host->rrd_update_every; - break; - - case RRD_MEMORY_MODE_DBENGINE: - break; - } + rrdhost_set_replication_parameters(host, memory_mode, replication_period, replication_step); host->system_info = system_info; @@ -531,13 +485,14 @@ static RRDHOST *rrdhost_create( , host->rrd_update_every , rrd_memory_mode_name(host->rrd_memory_mode) , host->rrd_history_entries - , rrdhost_has_rrdpush_sender_enabled(host)?"enabled":"disabled" - , host->rrdpush.send.destination?host->rrdpush.send.destination:"" - , host->rrdpush.send.api_key?host->rrdpush.send.api_key:"" - , host->health.health_enabled?"enabled":"disabled" + , + rrdhost_has_stream_sender_enabled(host)?"enabled":"disabled" + , string2str(host->stream.snd.destination) + , string2str(host->stream.snd.api_key) + , host->health.enabled ?"enabled":"disabled" , host->cache_dir - , string2str(host->health.health_default_exec) - , string2str(host->health.health_default_recipient) + , string2str(host->health.default_exec) + , string2str(host->health.default_recipient) ); if(!archived) { @@ -564,14 +519,14 @@ static void rrdhost_update(RRDHOST *host , int update_every , long history , RRD_MEMORY_MODE mode - , unsigned int health_enabled - , unsigned int rrdpush_enabled - , const char *rrdpush_destination - , const char *rrdpush_api_key - , const char *rrdpush_send_charts_matching - , bool rrdpush_enable_replication - , time_t rrdpush_seconds_to_replicate - , time_t rrdpush_replication_step + , bool health + , bool stream + , STRING *parents + , STRING *api_key + , STRING *send_charts_matching + , bool replication + , time_t replication_period + , time_t replication_step , struct rrdhost_system_info *system_info ) { @@ -579,7 +534,7 @@ static void rrdhost_update(RRDHOST *host spinlock_lock(&host->rrdhost_update_lock); - host->health.health_enabled = (mode == RRD_MEMORY_MODE_NONE) ? 0 : health_enabled; + host->health.enabled = (mode == RRD_MEMORY_MODE_NONE) ? 0 : health; { struct rrdhost_system_info *old = host->system_info; @@ -651,7 +606,7 @@ static void rrdhost_update(RRDHOST *host if(!host->rrdvars) host->rrdvars = rrdvariables_create(); - host->last_connected = now_realtime_sec(); + host->stream.snd.status.last_connected = now_realtime_sec(); if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) { rrdhost_flag_clear(host, RRDHOST_FLAG_ARCHIVED); @@ -664,21 +619,16 @@ static void rrdhost_update(RRDHOST *host if (!host->rrdset_root_index) rrdset_index_init(host); - rrdhost_initialize_rrdpush_sender(host, - rrdpush_enabled, - rrdpush_destination, - rrdpush_api_key, - rrdpush_send_charts_matching); + stream_sender_structures_init(host, stream, parents, api_key, send_charts_matching); rrdcalc_rrdhost_index_init(host); - if(rrdpush_enable_replication) + if(replication) rrdhost_option_set(host, RRDHOST_OPTION_REPLICATION); else rrdhost_option_clear(host, RRDHOST_OPTION_REPLICATION); - host->rrdpush_seconds_to_replicate = rrdpush_seconds_to_replicate; - host->rrdpush_replication_step = rrdpush_replication_step; + rrdhost_set_replication_parameters(host, host->rrd_memory_mode, replication_period, replication_step); ml_host_new(host); @@ -704,14 +654,14 @@ RRDHOST *rrdhost_find_or_create( , int update_every , long history , RRD_MEMORY_MODE mode - , unsigned int health_enabled - , unsigned int rrdpush_enabled - , const char *rrdpush_destination - , const char *rrdpush_api_key - , const char *rrdpush_send_charts_matching - , bool rrdpush_enable_replication - , time_t rrdpush_seconds_to_replicate - , time_t rrdpush_replication_step + , bool health + , bool stream + , STRING *parents + , STRING *api_key + , STRING *send_charts_matching + , bool replication + , time_t replication_period + , time_t replication_step , struct rrdhost_system_info *system_info , bool archived ) { @@ -748,14 +698,14 @@ RRDHOST *rrdhost_find_or_create( , update_every , history , mode - , health_enabled - , rrdpush_enabled - , rrdpush_destination - , rrdpush_api_key - , rrdpush_send_charts_matching - , rrdpush_enable_replication - , rrdpush_seconds_to_replicate - , rrdpush_replication_step + , health + , stream + , parents + , api_key + , send_charts_matching + , replication + , replication_period + , replication_step , system_info , 0 , archived @@ -763,28 +713,29 @@ RRDHOST *rrdhost_find_or_create( } else { if (likely(!rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))) - rrdhost_update(host - , hostname - , registry_hostname - , guid - , os - , timezone - , abbrev_timezone - , utc_offset - , prog_name - , prog_version - , update_every - , history - , mode - , health_enabled - , rrdpush_enabled - , rrdpush_destination - , rrdpush_api_key - , rrdpush_send_charts_matching - , rrdpush_enable_replication - , rrdpush_seconds_to_replicate - , rrdpush_replication_step - , system_info); + rrdhost_update( + host + , hostname + , registry_hostname + , guid + , os + , timezone + , abbrev_timezone + , utc_offset + , prog_name + , prog_version + , update_every + , history + , mode + , health + , stream + , parents + , api_key + , send_charts_matching + , replication + , replication_period + , replication_step + , system_info); } return host; @@ -798,232 +749,13 @@ inline int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected_host, tim && rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN) && !rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD) && !host->receiver - && host->child_disconnected_time - && host->child_disconnected_time + rrdhost_free_orphan_time_s < now_s) + && host->stream.rcv.status.last_disconnected + && host->stream.rcv.status.last_disconnected + rrdhost_free_orphan_time_s < now_s) return 1; return 0; } -// ---------------------------------------------------------------------------- -// RRDHOST global / startup initialization - -#ifdef ENABLE_DBENGINE -struct dbengine_initialization { - ND_THREAD *thread; - char path[FILENAME_MAX + 1]; - int disk_space_mb; - size_t retention_seconds; - size_t tier; - int ret; -}; - -typedef struct rrd_alert_prototype { - struct rrd_alert_match match; - struct rrd_alert_config config; - - struct { - uint32_t uses; - bool enabled; - bool is_on_disk; - SPINLOCK spinlock; - struct rrd_alert_prototype *prev, *next; - } _internal; -} RRD_ALERT_PROTOTYPE; - -void *dbengine_tier_init(void *ptr) { - struct dbengine_initialization *dbi = ptr; - dbi->ret = rrdeng_init(NULL, dbi->path, dbi->disk_space_mb, dbi->tier, dbi->retention_seconds); - return ptr; -} - -RRD_BACKFILL get_dbengine_backfill(RRD_BACKFILL backfill) -{ - const char *bf = config_get( - CONFIG_SECTION_DB, - "dbengine tier backfill", - backfill == RRD_BACKFILL_NEW ? "new" : - backfill == RRD_BACKFILL_FULL ? "full" : - "none"); - - if (strcmp(bf, "new") == 0) - backfill = RRD_BACKFILL_NEW; - else if (strcmp(bf, "full") == 0) - backfill = RRD_BACKFILL_FULL; - else if (strcmp(bf, "none") == 0) - backfill = RRD_BACKFILL_NONE; - else { - nd_log(NDLS_DAEMON, NDLP_WARNING, "DBENGINE: unknown backfill value '%s', assuming 'new'", bf); - config_set(CONFIG_SECTION_DB, "dbengine tier backfill", "new"); - backfill = RRD_BACKFILL_NEW; - } - return backfill; -} - -#endif - -static void dbengine_init(const char *hostname) { -#ifdef ENABLE_DBENGINE - - use_direct_io = config_get_boolean(CONFIG_SECTION_DB, "dbengine use direct io", use_direct_io); - - unsigned read_num = (unsigned)config_get_number(CONFIG_SECTION_DB, "dbengine pages per extent", DEFAULT_PAGES_PER_EXTENT); - if (read_num > 0 && read_num <= DEFAULT_PAGES_PER_EXTENT) - rrdeng_pages_per_extent = read_num; - else { - nd_log(NDLS_DAEMON, NDLP_WARNING, - "Invalid dbengine pages per extent %u given. Using %u.", - read_num, rrdeng_pages_per_extent); - - config_set_number(CONFIG_SECTION_DB, "dbengine pages per extent", rrdeng_pages_per_extent); - } - - storage_tiers = config_get_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); - if(storage_tiers < 1) { - nd_log(NDLS_DAEMON, NDLP_WARNING, "At least 1 storage tier is required. Assuming 1."); - - storage_tiers = 1; - config_set_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); - } - if(storage_tiers > RRD_STORAGE_TIERS) { - nd_log(NDLS_DAEMON, NDLP_WARNING, - "Up to %d storage tier are supported. Assuming %d.", - RRD_STORAGE_TIERS, RRD_STORAGE_TIERS); - - storage_tiers = RRD_STORAGE_TIERS; - config_set_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); - } - - new_dbengine_defaults = - (!legacy_multihost_db_space && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 1 update every iterations") && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 2 update every iterations") && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 3 update every iterations") && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 4 update every iterations") && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 1 retention size") && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 2 retention size") && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 3 retention size") && - !config_exists(CONFIG_SECTION_DB, "dbengine tier 4 retention size")); - - default_backfill = get_dbengine_backfill(RRD_BACKFILL_NEW); - char dbengineconfig[200 + 1]; - - size_t grouping_iterations = default_rrd_update_every; - storage_tiers_grouping_iterations[0] = default_rrd_update_every; - - for (size_t tier = 1; tier < storage_tiers; tier++) { - grouping_iterations = storage_tiers_grouping_iterations[tier]; - snprintfz(dbengineconfig, sizeof(dbengineconfig) - 1, "dbengine tier %zu update every iterations", tier); - grouping_iterations = config_get_number(CONFIG_SECTION_DB, dbengineconfig, grouping_iterations); - if(grouping_iterations < 2) { - grouping_iterations = 2; - config_set_number(CONFIG_SECTION_DB, dbengineconfig, grouping_iterations); - nd_log(NDLS_DAEMON, NDLP_WARNING, - "DBENGINE on '%s': 'dbegnine tier %zu update every iterations' cannot be less than 2. Assuming 2.", - hostname, tier); - } - storage_tiers_grouping_iterations[tier] = grouping_iterations; - } - - default_multidb_disk_quota_mb = (int) config_get_size_mb(CONFIG_SECTION_DB, "dbengine tier 0 retention size", RRDENG_DEFAULT_TIER_DISK_SPACE_MB); - if(default_multidb_disk_quota_mb && default_multidb_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) { - netdata_log_error("Invalid disk space %d for tier 0 given. Defaulting to %d.", default_multidb_disk_quota_mb, RRDENG_MIN_DISK_SPACE_MB); - default_multidb_disk_quota_mb = RRDENG_MIN_DISK_SPACE_MB; - config_set_size_mb(CONFIG_SECTION_DB, "dbengine tier 0 retention size", default_multidb_disk_quota_mb); - } - -#ifdef OS_WINDOWS - // FIXME: for whatever reason joining the initialization threads - // fails on Windows. - bool parallel_initialization = false; -#else - bool parallel_initialization = (storage_tiers <= (size_t)get_netdata_cpus()) ? true : false; -#endif - - struct dbengine_initialization tiers_init[RRD_STORAGE_TIERS] = {}; - - size_t created_tiers = 0; - char dbenginepath[FILENAME_MAX + 1]; - - for (size_t tier = 0; tier < storage_tiers; tier++) { - - if (tier == 0) - snprintfz(dbenginepath, FILENAME_MAX, "%s/dbengine", netdata_configured_cache_dir); - else - snprintfz(dbenginepath, FILENAME_MAX, "%s/dbengine-tier%zu", netdata_configured_cache_dir, tier); - - int ret = mkdir(dbenginepath, 0775); - if (ret != 0 && errno != EEXIST) { - nd_log(NDLS_DAEMON, NDLP_CRIT, "DBENGINE on '%s': cannot create directory '%s'", hostname, dbenginepath); - continue; - } - - int disk_space_mb = tier ? RRDENG_DEFAULT_TIER_DISK_SPACE_MB : default_multidb_disk_quota_mb; - snprintfz(dbengineconfig, sizeof(dbengineconfig) - 1, "dbengine tier %zu retention size", tier); - disk_space_mb = config_get_size_mb(CONFIG_SECTION_DB, dbengineconfig, disk_space_mb); - - snprintfz(dbengineconfig, sizeof(dbengineconfig) - 1, "dbengine tier %zu retention time", tier); - storage_tiers_retention_days[tier] = config_get_duration_days( - CONFIG_SECTION_DB, dbengineconfig, new_dbengine_defaults ? storage_tiers_retention_days[tier] : 0); - - tiers_init[tier].disk_space_mb = (int) disk_space_mb; - tiers_init[tier].tier = tier; - tiers_init[tier].retention_seconds = (size_t) (86400.0 * storage_tiers_retention_days[tier]); - strncpyz(tiers_init[tier].path, dbenginepath, FILENAME_MAX); - tiers_init[tier].ret = 0; - - if(parallel_initialization) { - char tag[NETDATA_THREAD_TAG_MAX + 1]; - snprintfz(tag, NETDATA_THREAD_TAG_MAX, "DBENGINIT[%zu]", tier); - tiers_init[tier].thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE, dbengine_tier_init, &tiers_init[tier]); - } - else - dbengine_tier_init(&tiers_init[tier]); - } - - for(size_t tier = 0; tier < storage_tiers ;tier++) { - if(parallel_initialization) - nd_thread_join(tiers_init[tier].thread); - - if(tiers_init[tier].ret != 0) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "DBENGINE on '%s': Failed to initialize multi-host database tier %zu on path '%s'", - hostname, tiers_init[tier].tier, tiers_init[tier].path); - } - else if(created_tiers == tier) - created_tiers++; - } - - if(created_tiers && created_tiers < storage_tiers) { - nd_log(NDLS_DAEMON, NDLP_WARNING, - "DBENGINE on '%s': Managed to create %zu tiers instead of %zu. Continuing with %zu available.", - hostname, created_tiers, storage_tiers, created_tiers); - - storage_tiers = created_tiers; - } - else if(!created_tiers) - fatal("DBENGINE on '%s', failed to initialize databases at '%s'.", hostname, netdata_configured_cache_dir); - - for(size_t tier = 0; tier < storage_tiers ;tier++) - rrdeng_readiness_wait(multidb_ctx[tier]); - - calculate_tier_disk_space_percentage(); - - dbengine_enabled = true; -#else - storage_tiers = config_get_number(CONFIG_SECTION_DB, "storage tiers", 1); - if(storage_tiers != 1) { - nd_log(NDLS_DAEMON, NDLP_WARNING, - "DBENGINE is not available on '%s', so only 1 database tier can be supported.", - hostname); - - storage_tiers = 1; - config_set_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers); - } - dbengine_enabled = false; -#endif -} - void api_v1_management_init(void); int rrd_init(const char *hostname, struct rrdhost_system_info *system_info, bool unittest) { @@ -1053,7 +785,7 @@ int rrd_init(const char *hostname, struct rrdhost_system_info *system_info, bool nd_log(NDLS_DAEMON, NDLP_DEBUG, "DBENGINE: Initializing ..."); - dbengine_init(hostname); + netdata_conf_dbengine_init(hostname); } else storage_tiers = 1; @@ -1094,14 +826,14 @@ int rrd_init(const char *hostname, struct rrdhost_system_info *system_info, bool , default_rrd_history_entries , default_rrd_memory_mode , health_plugin_enabled() - , - stream_conf_send_enabled, - stream_conf_send_destination, - stream_conf_send_api_key, - stream_conf_send_charts_matching, - stream_conf_replication_enabled, - stream_conf_replication_period, - stream_conf_replication_step, system_info + , stream_send.enabled + , stream_send.parents.destination + , stream_send.api_key + , stream_send.send_charts_matching + , stream_receive.replication.enabled + , stream_receive.replication.period + , stream_receive.replication.step + , system_info , 1 , 0 ); @@ -1167,51 +899,6 @@ void rrdhost_system_info_free(struct rrdhost_system_info *system_info) { } } -static void rrdhost_streaming_sender_structures_init(RRDHOST *host) -{ - if (host->sender) - return; - - host->sender = callocz(1, sizeof(*host->sender)); - __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(*host->sender), __ATOMIC_RELAXED); - - host->sender->host = host; - host->sender->buffer = cbuffer_new(CBUFFER_INITIAL_SIZE, 1024 * 1024, &netdata_buffers_statistics.cbuffers_streaming); - host->sender->capabilities = stream_our_capabilities(host, true); - - host->sender->rrdpush_sender_pipe[PIPE_READ] = -1; - host->sender->rrdpush_sender_pipe[PIPE_WRITE] = -1; - host->sender->rrdpush_sender_socket = -1; - host->sender->disabled_capabilities = STREAM_CAP_NONE; - - if(!stream_conf_compression_enabled) - host->sender->disabled_capabilities |= STREAM_CAP_COMPRESSIONS_AVAILABLE; - - spinlock_init(&host->sender->spinlock); - replication_init_sender(host->sender); -} - -static void rrdhost_streaming_sender_structures_free(RRDHOST *host) -{ - rrdhost_option_clear(host, RRDHOST_OPTION_SENDER_ENABLED); - - if (unlikely(!host->sender)) - return; - - rrdpush_sender_thread_stop(host, STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP, true); // stop a possibly running thread - cbuffer_free(host->sender->buffer); - - rrdpush_compressor_destroy(&host->sender->compressor); - - replication_cleanup_sender(host->sender); - - __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(*host->sender), __ATOMIC_RELAXED); - - freez(host->sender); - host->sender = NULL; - rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_INITIALIZED); -} - void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) { if(!host) return; @@ -1243,10 +930,10 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) { // ------------------------------------------------------------------------ // clean up streaming - rrdhost_streaming_sender_structures_free(host); + stream_sender_structures_free(host); if (netdata_exit || force) - stop_streaming_receiver(host, STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP); + stream_receiver_signal_to_stop_and_wait(host, STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP); // ------------------------------------------------------------------------ @@ -1285,13 +972,13 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) { string_freez(host->program_version); rrdhost_system_info_free(host->system_info); freez(host->cache_dir); - freez(host->rrdpush.send.api_key); - freez(host->rrdpush.send.destination); - rrdpush_destinations_free(host); - string_freez(host->health.health_default_exec); - string_freez(host->health.health_default_recipient); + string_freez(host->stream.snd.api_key); + string_freez(host->stream.snd.destination); + rrdhost_stream_parents_free(host, false); + string_freez(host->health.default_exec); + string_freez(host->health.default_recipient); string_freez(host->registry_hostname); - simple_pattern_free(host->rrdpush.send.charts_matching); + simple_pattern_free(host->stream.snd.charts_matching); rrd_functions_host_destroy(host); rrdvariables_destroy(host->rrdvars); @@ -1429,23 +1116,23 @@ static void rrdhost_load_auto_labels(void) { int has_unstable_connection = appconfig_get_boolean(&netdata_config, CONFIG_SECTION_GLOBAL, "has unstable connection", CONFIG_BOOLEAN_NO); rrdlabels_add(labels, "_has_unstable_connection", has_unstable_connection ? "true" : "false", RRDLABEL_SRC_AUTO); - rrdlabels_add(labels, "_is_parent", (localhost->connected_children_count > 0) ? "true" : "false", RRDLABEL_SRC_AUTO); + rrdlabels_add(labels, "_is_parent", (stream_receivers_currently_connected() > 0) ? "true" : "false", RRDLABEL_SRC_AUTO); rrdlabels_add(labels, "_hostname", string2str(localhost->hostname), RRDLABEL_SRC_AUTO); rrdlabels_add(labels, "_os", string2str(localhost->os), RRDLABEL_SRC_AUTO); - if (localhost->rrdpush.send.destination) - rrdlabels_add(labels, "_streams_to", localhost->rrdpush.send.destination, RRDLABEL_SRC_AUTO); + if (localhost->stream.snd.destination) + rrdlabels_add(labels, "_streams_to", string2str(localhost->stream.snd.destination), RRDLABEL_SRC_AUTO); } void rrdhost_set_is_parent_label(void) { - int count = __atomic_load_n(&localhost->connected_children_count, __ATOMIC_RELAXED); + uint32_t count = stream_receivers_currently_connected(); if (count == 0 || count == 1) { RRDLABELS *labels = localhost->rrdlabels; rrdlabels_add(labels, "_is_parent", (count) ? "true" : "false", RRDLABEL_SRC_AUTO); - //queue a node info + // queue a node info aclk_queue_node_info(localhost, false); } } @@ -1508,7 +1195,7 @@ void reload_host_labels(void) { rrdhost_flag_set(localhost,RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE); - rrdpush_send_host_labels(localhost); + stream_send_host_labels(localhost); } void rrdhost_finalize_collection(RRDHOST *host) { diff --git a/src/database/rrdset.c b/src/database/rrdset.c index 396f66835bf6d4..ea5e50b00b2dd0 100644 --- a/src/database/rrdset.c +++ b/src/database/rrdset.c @@ -11,58 +11,58 @@ void rrdset_metadata_updated(RRDSET *st) { } // ---------------------------------------------------------------------------- -// RRDSET rrdpush send chart_slots +// RRDSET streaming send chart_slots -static void rrdset_rrdpush_send_chart_slot_assign(RRDSET *st) { +static void rrdset_stream_send_chart_slot_assign(RRDSET *st) { RRDHOST *host = st->rrdhost; - spinlock_lock(&host->rrdpush.send.pluginsd_chart_slots.available.spinlock); + spinlock_lock(&host->stream.snd.pluginsd_chart_slots.available.spinlock); - if(host->rrdpush.send.pluginsd_chart_slots.available.used > 0) - st->rrdpush.sender.chart_slot = - host->rrdpush.send.pluginsd_chart_slots.available.array[--host->rrdpush.send.pluginsd_chart_slots.available.used]; + if(host->stream.snd.pluginsd_chart_slots.available.used > 0) + st->stream.snd.chart_slot = + host->stream.snd.pluginsd_chart_slots.available.array[--host->stream.snd.pluginsd_chart_slots.available.used]; else - st->rrdpush.sender.chart_slot = ++host->rrdpush.send.pluginsd_chart_slots.last_used; + st->stream.snd.chart_slot = ++host->stream.snd.pluginsd_chart_slots.last_used; - spinlock_unlock(&host->rrdpush.send.pluginsd_chart_slots.available.spinlock); + spinlock_unlock(&host->stream.snd.pluginsd_chart_slots.available.spinlock); } -static void rrdset_rrdpush_send_chart_slot_release(RRDSET *st) { - if(!st->rrdpush.sender.chart_slot || st->rrdhost->rrdpush.send.pluginsd_chart_slots.available.ignore) +static void rrdset_stream_send_chart_slot_release(RRDSET *st) { + if(!st->stream.snd.chart_slot || st->rrdhost->stream.snd.pluginsd_chart_slots.available.ignore) return; RRDHOST *host = st->rrdhost; - spinlock_lock(&host->rrdpush.send.pluginsd_chart_slots.available.spinlock); + spinlock_lock(&host->stream.snd.pluginsd_chart_slots.available.spinlock); - if(host->rrdpush.send.pluginsd_chart_slots.available.used >= host->rrdpush.send.pluginsd_chart_slots.available.size) { - uint32_t old_size = host->rrdpush.send.pluginsd_chart_slots.available.size; + if(host->stream.snd.pluginsd_chart_slots.available.used >= host->stream.snd.pluginsd_chart_slots.available.size) { + uint32_t old_size = host->stream.snd.pluginsd_chart_slots.available.size; uint32_t new_size = (old_size > 0) ? (old_size * 2) : 1024; - host->rrdpush.send.pluginsd_chart_slots.available.array = - reallocz(host->rrdpush.send.pluginsd_chart_slots.available.array, new_size * sizeof(uint32_t)); + host->stream.snd.pluginsd_chart_slots.available.array = + reallocz(host->stream.snd.pluginsd_chart_slots.available.array, new_size * sizeof(uint32_t)); - host->rrdpush.send.pluginsd_chart_slots.available.size = new_size; + host->stream.snd.pluginsd_chart_slots.available.size = new_size; } - host->rrdpush.send.pluginsd_chart_slots.available.array[host->rrdpush.send.pluginsd_chart_slots.available.used++] = - st->rrdpush.sender.chart_slot; + host->stream.snd.pluginsd_chart_slots.available.array[host->stream.snd.pluginsd_chart_slots.available.used++] = + st->stream.snd.chart_slot; - st->rrdpush.sender.chart_slot = 0; - spinlock_unlock(&host->rrdpush.send.pluginsd_chart_slots.available.spinlock); + st->stream.snd.chart_slot = 0; + spinlock_unlock(&host->stream.snd.pluginsd_chart_slots.available.spinlock); } void rrdhost_pluginsd_send_chart_slots_free(RRDHOST *host) { - spinlock_lock(&host->rrdpush.send.pluginsd_chart_slots.available.spinlock); - host->rrdpush.send.pluginsd_chart_slots.available.ignore = true; - freez(host->rrdpush.send.pluginsd_chart_slots.available.array); - host->rrdpush.send.pluginsd_chart_slots.available.array = NULL; - host->rrdpush.send.pluginsd_chart_slots.available.used = 0; - host->rrdpush.send.pluginsd_chart_slots.available.size = 0; - spinlock_unlock(&host->rrdpush.send.pluginsd_chart_slots.available.spinlock); + spinlock_lock(&host->stream.snd.pluginsd_chart_slots.available.spinlock); + host->stream.snd.pluginsd_chart_slots.available.ignore = true; + freez(host->stream.snd.pluginsd_chart_slots.available.array); + host->stream.snd.pluginsd_chart_slots.available.array = NULL; + host->stream.snd.pluginsd_chart_slots.available.used = 0; + host->stream.snd.pluginsd_chart_slots.available.size = 0; + spinlock_unlock(&host->stream.snd.pluginsd_chart_slots.available.spinlock); // zero all the slots on all charts, so that they will not attempt to access the array RRDSET *st; rrdset_foreach_read(st, host) { - st->rrdpush.sender.chart_slot = 0; + st->stream.snd.chart_slot = 0; } rrdset_foreach_done(st); } @@ -78,9 +78,9 @@ void rrdset_pluginsd_receive_unslot(RRDSET *st) { RRDHOST *host = st->rrdhost; if(st->pluginsd.last_slot >= 0 && - (uint32_t)st->pluginsd.last_slot < host->rrdpush.receive.pluginsd_chart_slots.size && - host->rrdpush.receive.pluginsd_chart_slots.array[st->pluginsd.last_slot] == st) { - host->rrdpush.receive.pluginsd_chart_slots.array[st->pluginsd.last_slot] = NULL; + (uint32_t)st->pluginsd.last_slot < host->stream.rcv.pluginsd_chart_slots.size && + host->stream.rcv.pluginsd_chart_slots.array[st->pluginsd.last_slot] == st) { + host->stream.rcv.pluginsd_chart_slots.array[st->pluginsd.last_slot] = NULL; } st->pluginsd.last_slot = -1; @@ -113,18 +113,18 @@ static void rrdset_pluginsd_receive_slots_initialize(RRDSET *st) { } void rrdhost_pluginsd_receive_chart_slots_free(RRDHOST *host) { - spinlock_lock(&host->rrdpush.receive.pluginsd_chart_slots.spinlock); + spinlock_lock(&host->stream.rcv.pluginsd_chart_slots.spinlock); - if(host->rrdpush.receive.pluginsd_chart_slots.array) { - for (size_t s = 0; s < host->rrdpush.receive.pluginsd_chart_slots.size; s++) - rrdset_pluginsd_receive_unslot_and_cleanup(host->rrdpush.receive.pluginsd_chart_slots.array[s]); + if(host->stream.rcv.pluginsd_chart_slots.array) { + for (size_t s = 0; s < host->stream.rcv.pluginsd_chart_slots.size; s++) + rrdset_pluginsd_receive_unslot_and_cleanup(host->stream.rcv.pluginsd_chart_slots.array[s]); - freez(host->rrdpush.receive.pluginsd_chart_slots.array); - host->rrdpush.receive.pluginsd_chart_slots.array = NULL; - host->rrdpush.receive.pluginsd_chart_slots.size = 0; + freez(host->stream.rcv.pluginsd_chart_slots.array); + host->stream.rcv.pluginsd_chart_slots.array = NULL; + host->stream.rcv.pluginsd_chart_slots.size = 0; } - spinlock_unlock(&host->rrdpush.receive.pluginsd_chart_slots.spinlock); + spinlock_unlock(&host->stream.rcv.pluginsd_chart_slots.spinlock); } // ---------------------------------------------------------------------------- @@ -257,7 +257,7 @@ static void rrdset_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v st->chart_type = ctr->chart_type; st->rrdhost = host; - rrdset_rrdpush_send_chart_slot_assign(st); + rrdset_stream_send_chart_slot_assign(st); spinlock_init(&st->data_collection_lock); @@ -340,7 +340,7 @@ static void rrdset_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, v rrdset_finalize_collection(st, false); - rrdset_rrdpush_send_chart_slot_release(st); + rrdset_stream_send_chart_slot_release(st); // remove it from the name index rrdset_index_del_name(host, st); @@ -498,7 +498,7 @@ static void rrdset_react_callback(const DICTIONARY_ITEM *item __maybe_unused, vo void rrdset_index_init(RRDHOST *host) { if(!host->rrdset_root_index) { host->rrdset_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, - &dictionary_stats_category_rrdset_rrddim, sizeof(RRDSET)); + &dictionary_stats_category_rrdset, sizeof(RRDSET)); dictionary_register_insert_callback(host->rrdset_root_index, rrdset_insert_callback, NULL); dictionary_register_conflict_callback(host->rrdset_root_index, rrdset_conflict_callback, NULL); @@ -509,7 +509,7 @@ void rrdset_index_init(RRDHOST *host) { if(!host->rrdset_root_index_name) { host->rrdset_root_index_name = dictionary_create_advanced( DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE, - &dictionary_stats_category_rrdset_rrddim, 0); + &dictionary_stats_category_rrdset, 0); dictionary_register_insert_callback(host->rrdset_root_index_name, rrdset_name_insert_callback, host); dictionary_register_delete_callback(host->rrdset_root_index_name, rrdset_name_delete_callback, host); @@ -749,7 +749,7 @@ inline void rrdset_is_obsolete___safe_from_collector_thread(RRDSET *st) { // the chart will not get more updates (data collection) // so, we have to push its definition now - rrdset_push_chart_definition_now(st); + stream_sender_send_rrdset_definition_now(st); rrdcontext_updated_rrdset_flags(st); } } @@ -920,7 +920,7 @@ RRDSET *rrdset_create_custom( , long history_entries ) { if (host != localhost) - host->child_last_chart_command = now_realtime_sec(); + host->stream.rcv.status.last_chart = now_realtime_sec(); if(!type || !type[0]) fatal("Cannot create rrd stats without a type: id '%s', name '%s', family '%s', context '%s', title '%s', units '%s', plugin '%s', module '%s'." @@ -1282,18 +1282,18 @@ void rrddim_store_metric(RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, // we have not collected this tier before // let's fill any gap that may exist rrdr_fill_tier_gap_from_smaller_tiers(rd, tier, now_s); - rrddim_option_set(rd, RRDDIM_OPTION_BACKFILLED_HIGH_TIERS); } store_metric_at_tier(rd, tier, t, sp, point_end_time_ut); } + rrddim_option_set(rd, RRDDIM_OPTION_BACKFILLED_HIGH_TIERS); rrdcontext_collected_rrddim(rd); log_stack_pop(&lgs); } void store_metric_collection_completed() { - global_statistics_rrdset_done_chart_collection_completed(rrdset_done_statistics_points_stored_per_tier); + telemetry_queries_rrdset_collection_completed(rrdset_done_statistics_points_stored_per_tier); } // caching of dimensions rrdset_done() and rrdset_done_interpolate() loop through @@ -1464,7 +1464,7 @@ static inline size_t rrdset_done_interpolate( (void) ml_dimension_is_anomalous(rd, current_time_s, 0, false); if(rsb->wb && rsb->v2) - rrddim_push_metrics_v2(rsb, rd, next_store_ut, NAN, SN_FLAG_NONE); + stream_send_rrddim_metrics_v2(rsb, rd, next_store_ut, NAN, SN_FLAG_NONE); rrddim_store_metric(rd, next_store_ut, NAN, SN_FLAG_NONE); continue; @@ -1479,7 +1479,7 @@ static inline size_t rrdset_done_interpolate( } if(rsb->wb && rsb->v2) - rrddim_push_metrics_v2(rsb, rd, next_store_ut, new_value, dim_storage_flags); + stream_send_rrddim_metrics_v2(rsb, rd, next_store_ut, new_value, dim_storage_flags); rrddim_store_metric(rd, next_store_ut, new_value, dim_storage_flags); rd->collector.last_stored_value = new_value; @@ -1490,7 +1490,7 @@ static inline size_t rrdset_done_interpolate( rrdset_debug(st, "%s: STORE[%ld] = NON EXISTING ", rrddim_name(rd), current_entry); if(rsb->wb && rsb->v2) - rrddim_push_metrics_v2(rsb, rd, next_store_ut, NAN, SN_FLAG_NONE); + stream_send_rrddim_metrics_v2(rsb, rd, next_store_ut, NAN, SN_FLAG_NONE); rrddim_store_metric(rd, next_store_ut, NAN, SN_FLAG_NONE); rd->collector.last_stored_value = NAN; @@ -1534,8 +1534,8 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) if(unlikely(!service_running(SERVICE_COLLECTORS))) return; RRDSET_STREAM_BUFFER stream_buffer = { .wb = NULL, }; - if(unlikely(rrdhost_has_rrdpush_sender_enabled(st->rrdhost))) - stream_buffer = rrdset_push_metric_initialize(st, now.tv_sec); + if(unlikely(rrdhost_has_stream_sender_enabled(st->rrdhost))) + stream_buffer = stream_send_metrics_init(st, now.tv_sec); spinlock_lock(&st->data_collection_lock); @@ -1660,7 +1660,7 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) st->counter_done++; if(stream_buffer.wb && !stream_buffer.v2) - rrdset_push_metrics_v1(&stream_buffer, st); + stream_send_rrdset_metrics_v1(&stream_buffer, st); size_t rda_slots = dictionary_entries(st->rrddim_root_index); struct rda_item *rda_base = rrdset_thread_rda_get(&rda_slots); @@ -1988,7 +1988,7 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) } spinlock_unlock(&st->data_collection_lock); - rrdset_push_metrics_finished(&stream_buffer, st); + stream_send_rrdset_metrics_finished(&stream_buffer, st); // ALL DONE ABOUT THE DATA UPDATE // -------------------------------------------------------------------- diff --git a/src/database/sqlite/sqlite_aclk.c b/src/database/sqlite/sqlite_aclk.c index b3f926e2f1dae2..82a89ee63541d1 100644 --- a/src/database/sqlite/sqlite_aclk.c +++ b/src/database/sqlite/sqlite_aclk.c @@ -116,7 +116,7 @@ static int create_host_callback(void *data, int argc, char **argv, char **column struct rrdhost_system_info *system_info = callocz(1, sizeof(struct rrdhost_system_info)); __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED); - system_info->hops = str2i((const char *) argv[IDX_HOPS]); + system_info->hops = (int16_t)str2i((const char *) argv[IDX_HOPS]); sql_build_host_system_info((nd_uuid_t *)argv[IDX_HOST_ID], system_info); @@ -133,22 +133,14 @@ static int create_host_callback(void *data, int argc, char **argv, char **column argv[IDX_UPDATE_EVERY] ? str2i(argv[IDX_UPDATE_EVERY]) : 1, argv[IDX_ENTRIES] ? str2i(argv[IDX_ENTRIES]) : 0, default_rrd_memory_mode, - 0 // health - , - 0 // rrdpush enabled - , - NULL //destination - , - NULL // api key - , - NULL // send charts matching - , - false // rrdpush_enable_replication - , - 0 // rrdpush_seconds_to_replicate - , - 0 // rrdpush_replication_step - , + 0, // health + 0, // rrdpush enabled + NULL, // destination + NULL, // api key + NULL, // send charts matching + false, // rrdpush_enable_replication + 0, // rrdpush_seconds_to_replicate + 0, // rrdpush_replication_step system_info, 1); @@ -157,10 +149,10 @@ static int create_host_callback(void *data, int argc, char **argv, char **column rrdhost_option_set(host, RRDHOST_OPTION_EPHEMERAL_HOST); if (is_ephemeral) - host->child_disconnected_time = now_realtime_sec(); + host->stream.rcv.status.last_disconnected = now_realtime_sec(); host->rrdlabels = sql_load_host_labels((nd_uuid_t *)argv[IDX_HOST_ID]); - host->last_connected = last_connected; + host->stream.snd.status.last_connected = last_connected; } (*number_of_chidren)++; @@ -361,9 +353,9 @@ static void node_update_timer_cb(uv_timer_t *handle) struct aclk_sync_cfg_t *ahc = handle->data; RRDHOST *host = ahc->host; - spinlock_lock(&host->receiver_lock); + rrdhost_receiver_lock(host); int live = (host == localhost || host->receiver || !(rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) ? 1 : 0; - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); nd_log(NDLS_ACLK, NDLP_DEBUG,"Timer: Sending node update info for %s, LIVE = %d", rrdhost_hostname(host), live); aclk_host_state_update(host, live, 1); } @@ -461,9 +453,9 @@ static void aclk_synchronization(void *arg) } // This is fallback if timer fails - spinlock_lock(&host->receiver_lock); + rrdhost_receiver_lock(host); int live = (host == localhost || host->receiver || !(rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) ? 1 : 0; - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); aclk_host_state_update(host, live, 1); nd_log(NDLS_ACLK, NDLP_DEBUG,"Sending node update info for %s, LIVE = %d", rrdhost_hostname(host), live); break; diff --git a/src/database/sqlite/sqlite_aclk_alert.c b/src/database/sqlite/sqlite_aclk_alert.c index 1777e08a6302d5..7136460f7b591f 100644 --- a/src/database/sqlite/sqlite_aclk_alert.c +++ b/src/database/sqlite/sqlite_aclk_alert.c @@ -18,9 +18,9 @@ static inline bool is_event_from_alert_variable_config(int64_t unique_id, nd_uuid_t *host_id) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_SELECT_VARIABLE_ALERT_BY_UNIQUE_ID, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_SELECT_VARIABLE_ALERT_BY_UNIQUE_ID, &res)) return false; bool ret = false; @@ -34,20 +34,18 @@ static inline bool is_event_from_alert_variable_config(int64_t unique_id, nd_uui done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); return ret; } -#define MAX_REMOVED_PERIOD 604800 //a week - #define SQL_UPDATE_ALERT_VERSION_TRANSITION \ "UPDATE alert_version SET unique_id = @unique_id WHERE health_log_id = @health_log_id" static void update_alert_version_transition(int64_t health_log_id, int64_t unique_id) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_UPDATE_ALERT_VERSION_TRANSITION, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_UPDATE_ALERT_VERSION_TRANSITION, &res)) return; int param = 0; @@ -61,7 +59,7 @@ static void update_alert_version_transition(int64_t health_log_id, int64_t uniqu done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); } //decide if some events should be sent or not @@ -70,9 +68,9 @@ static void update_alert_version_transition(int64_t health_log_id, int64_t uniqu static bool cloud_status_matches(int64_t health_log_id, RRDCALC_STATUS status) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_SELECT_LAST_ALERT_STATUS, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_SELECT_LAST_ALERT_STATUS, &res)) return true; bool send = false; @@ -89,7 +87,7 @@ static bool cloud_status_matches(int64_t health_log_id, RRDCALC_STATUS status) done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); return send; } @@ -108,7 +106,7 @@ static bool cloud_status_matches(int64_t health_log_id, RRDCALC_STATUS status) // static int insert_alert_to_submit_queue(RRDHOST *host, int64_t health_log_id, uint32_t unique_id, RRDCALC_STATUS status) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; if (cloud_status_matches(health_log_id, status)) { update_alert_version_transition(health_log_id, unique_id); @@ -118,7 +116,7 @@ static int insert_alert_to_submit_queue(RRDHOST *host, int64_t health_log_id, ui if (is_event_from_alert_variable_config(unique_id, &host->host_id.uuid)) return 2; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_QUEUE_ALERT_TO_CLOUD, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_QUEUE_ALERT_TO_CLOUD, &res)) return -1; int param = 0; @@ -133,7 +131,7 @@ static int insert_alert_to_submit_queue(RRDHOST *host, int64_t health_log_id, ui done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); return 0; } @@ -145,9 +143,9 @@ static int insert_alert_to_submit_queue(RRDHOST *host, int64_t health_log_id, ui // static int delete_alert_from_submit_queue(RRDHOST *host, int64_t first_seq_id, int64_t last_seq_id) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_DELETE_QUEUE_ALERT_TO_CLOUD, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_DELETE_QUEUE_ALERT_TO_CLOUD, &res)) return -1; int param = 0; @@ -162,7 +160,7 @@ static int delete_alert_from_submit_queue(RRDHOST *host, int64_t first_seq_id, i done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); return 0; } @@ -224,9 +222,9 @@ static inline char *sqlite3_text_strdupz_empty(sqlite3_stmt *res, int iCol) { // static void sql_update_alert_version(int64_t health_log_id, int64_t unique_id, RRDCALC_STATUS status, uint64_t version) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_UPDATE_ALERT_VERSION, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_UPDATE_ALERT_VERSION, &res)) return; int param = 0; @@ -242,7 +240,7 @@ static void sql_update_alert_version(int64_t health_log_id, int64_t unique_id, R done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); } #define SQL_SELECT_ALERT_TO_DUMMY \ @@ -355,7 +353,7 @@ void health_alarm_log_populate( alarm_log->timezone = strdupz(rrdhost_abbrev_timezone(host)); alarm_log->exec_path = sqlite3_column_bytes(res, EXEC) ? strdupz((char *)sqlite3_column_text(res, EXEC)) : - strdupz((char *)string2str(host->health.health_default_exec)); + strdupz((char *)string2str(host->health.default_exec)); alarm_log->conf_source = source ? strdupz(source) : strdupz(""); @@ -422,6 +420,7 @@ void health_alarm_log_populate( " ORDER BY aq.sequence_id ASC LIMIT "ACLK_MAX_ALERT_UPDATES static void aclk_push_alert_event(RRDHOST *host __maybe_unused) + { CLAIM_ID claim_id = claim_id_get(); @@ -487,9 +486,9 @@ static void aclk_push_alert_event(RRDHOST *host __maybe_unused) static void delete_alert_from_pending_queue(RRDHOST *host, int64_t row) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_DELETE_PROCESSED_ROWS, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_DELETE_PROCESSED_ROWS, &res)) return; int param = 0; @@ -503,7 +502,7 @@ static void delete_alert_from_pending_queue(RRDHOST *host, int64_t row) done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); } #define SQL_REBUILD_HOST_ALERT_VERSION_TABLE \ @@ -555,9 +554,9 @@ void rebuild_host_alert_version_table(RRDHOST *host) bool process_alert_pending_queue(RRDHOST *host) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_PROCESS_ALERT_PENDING_QUEUE, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_PROCESS_ALERT_PENDING_QUEUE, &res)) return false; int param = 0; @@ -587,7 +586,7 @@ bool process_alert_pending_queue(RRDHOST *host) nd_log(NDLS_ACCESS, NDLP_NOTICE, "ACLK STA [%s (N/A)]: Processed %d entries, queued %d", rrdhost_hostname(host), count, added); done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); return added > 0; } @@ -766,9 +765,9 @@ void aclk_push_alert_config_event(char *node_id __maybe_unused, char *config_has static uint64_t calculate_node_alert_version(RRDHOST *host) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_ALERT_VERSION_CALC, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_ALERT_VERSION_CALC, &res)) return 0; uint64_t version = 0; @@ -782,7 +781,7 @@ static uint64_t calculate_node_alert_version(RRDHOST *host) done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); return version; } @@ -1008,7 +1007,7 @@ void aclk_start_alert_streaming(char *node_id, uint64_t cloud_version) return; } - if (unlikely(!host->health.health_enabled)) { + if (unlikely(!host->health.enabled)) { nd_log(NDLS_ACCESS, NDLP_NOTICE, "ACLK STA [%s (N/A)]: Ignoring request to stream alert state changes, health is disabled.", node_id); return; } diff --git a/src/database/sqlite/sqlite_aclk_node.c b/src/database/sqlite/sqlite_aclk_node.c index ea5a41cc31d47a..3ceb2baea16a40 100644 --- a/src/database/sqlite/sqlite_aclk_node.c +++ b/src/database/sqlite/sqlite_aclk_node.c @@ -65,13 +65,8 @@ static void build_node_info(RRDHOST *host) now_realtime_timeval(&node_info.updated_at); char *host_version = NULL; - if (host != localhost) { - spinlock_lock(&host->receiver_lock); - host_version = strdupz( - host->receiver && host->receiver->program_version ? host->receiver->program_version : - rrdhost_program_version(host)); - spinlock_unlock(&host->receiver_lock); - } + if (host != localhost) + host_version = stream_receiver_program_version_strdupz(host); node_info.data.name = rrdhost_hostname(host); node_info.data.os = rrdhost_os(host); diff --git a/src/database/sqlite/sqlite_functions.c b/src/database/sqlite/sqlite_functions.c index f48401e33b5c14..d3ca06dc203559 100644 --- a/src/database/sqlite/sqlite_functions.c +++ b/src/database/sqlite/sqlite_functions.c @@ -13,7 +13,7 @@ SQLITE_API int sqlite3_exec_monitored( char **errmsg /* Error msg written here */ ) { int rc = sqlite3_exec(db, sql, callback, data, errmsg); - global_statistics_sqlite3_query_completed(rc == SQLITE_OK, rc == SQLITE_BUSY, rc == SQLITE_LOCKED); + telemetry_sqlite3_query_completed(rc == SQLITE_OK, rc == SQLITE_BUSY, rc == SQLITE_LOCKED); return rc; } @@ -25,14 +25,14 @@ SQLITE_API int sqlite3_step_monitored(sqlite3_stmt *stmt) { rc = sqlite3_step(stmt); switch (rc) { case SQLITE_DONE: - global_statistics_sqlite3_query_completed(1, 0, 0); + telemetry_sqlite3_query_completed(1, 0, 0); break; case SQLITE_ROW: - global_statistics_sqlite3_row_completed(); + telemetry_sqlite3_row_completed(); break; case SQLITE_BUSY: case SQLITE_LOCKED: - global_statistics_sqlite3_query_completed(false, rc == SQLITE_BUSY, rc == SQLITE_LOCKED); + telemetry_sqlite3_query_completed(false, rc == SQLITE_BUSY, rc == SQLITE_LOCKED); usleep(SQLITE_INSERT_DELAY * USEC_PER_MS); continue; default: diff --git a/src/database/sqlite/sqlite_health.c b/src/database/sqlite/sqlite_health.c index 44cd644d87e61f..0719210fa100d8 100644 --- a/src/database/sqlite/sqlite_health.c +++ b/src/database/sqlite/sqlite_health.c @@ -26,12 +26,11 @@ static void sql_health_alarm_log_update(RRDHOST *host, ALARM_ENTRY *ae) { - static __thread sqlite3_stmt *res = NULL; - int rc; - REQUIRE_DB(db_meta); + sqlite3_stmt *res = NULL; + int rc; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_UPDATE_HEALTH_LOG, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_UPDATE_HEALTH_LOG, &res)) return; int param = 0; @@ -51,7 +50,7 @@ static void sql_health_alarm_log_update(RRDHOST *host, ALARM_ENTRY *ae) done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); } /* Health related SQL queries @@ -149,13 +148,13 @@ static void insert_alert_queue( RRDCALC_STATUS old_status, RRDCALC_STATUS new_status) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; int rc; if (!host->aclk_config) return; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_INSERT_ALERT_PENDING_QUEUE, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_INSERT_ALERT_PENDING_QUEUE, &res)) return; int submit_delay = calculate_delay(old_status, new_status); @@ -176,7 +175,7 @@ static void insert_alert_queue( done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); } #define SQL_INSERT_HEALTH_LOG_DETAIL \ @@ -189,10 +188,10 @@ static void insert_alert_queue( static void sql_health_alarm_log_insert_detail(RRDHOST *host, uint64_t health_log_id, ALARM_ENTRY *ae) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; int rc; - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_INSERT_HEALTH_LOG_DETAIL, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_INSERT_HEALTH_LOG_DETAIL, &res)) return; int param = 0; @@ -230,7 +229,7 @@ static void sql_health_alarm_log_insert_detail(RRDHOST *host, uint64_t health_lo done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); } #define SQL_INSERT_HEALTH_LOG \ @@ -243,13 +242,11 @@ static void sql_health_alarm_log_insert_detail(RRDHOST *host, uint64_t health_lo static void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae) { - static __thread sqlite3_stmt *res = NULL; + sqlite3_stmt *res = NULL; int rc; uint64_t health_log_id; - REQUIRE_DB(db_meta); - - if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_INSERT_HEALTH_LOG, &res)) + if (!PREPARE_STATEMENT(db_meta, SQL_INSERT_HEALTH_LOG, &res)) return; int param = 0; @@ -277,7 +274,7 @@ static void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae) done: REPORT_BIND_FAIL(res, param); - SQLITE_RESET(res); + SQLITE_FINALIZE(res); } void sql_health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae) @@ -1020,8 +1017,8 @@ void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, time_t after, const ch buffer_json_member_add_boolean(wb, "updated", (sqlite3_column_int64(stmt_query, 9) & HEALTH_ENTRY_FLAG_UPDATED)); buffer_json_member_add_int64(wb, "exec_run", (int64_t)sqlite3_column_int64(stmt_query, 10)); buffer_json_member_add_boolean(wb, "exec_failed", (sqlite3_column_int64(stmt_query, 9) & HEALTH_ENTRY_FLAG_EXEC_FAILED)); - buffer_json_member_add_string_or_empty(wb, "exec", sqlite3_column_text(stmt_query, 14) ? (const char *) sqlite3_column_text(stmt_query, 14) : string2str(host->health.health_default_exec)); - buffer_json_member_add_string_or_empty(wb, "recipient", sqlite3_column_text(stmt_query, 15) ? (const char *) sqlite3_column_text(stmt_query, 15) : string2str(host->health.health_default_recipient)); + buffer_json_member_add_string_or_empty(wb, "exec", sqlite3_column_text(stmt_query, 14) ? (const char *) sqlite3_column_text(stmt_query, 14) : string2str(host->health.default_exec)); + buffer_json_member_add_string_or_empty(wb, "recipient", sqlite3_column_text(stmt_query, 15) ? (const char *) sqlite3_column_text(stmt_query, 15) : string2str(host->health.default_recipient)); buffer_json_member_add_int64(wb, "exec_code", sqlite3_column_int(stmt_query, 19)); buffer_json_member_add_string_or_empty(wb, "source", sqlite3_column_text(stmt_query, 16) ? (const char *) sqlite3_column_text(stmt_query, 16) : (char *) "Unknown"); buffer_json_member_add_string_or_empty(wb, "command", edit_command); diff --git a/src/database/sqlite/sqlite_metadata.c b/src/database/sqlite/sqlite_metadata.c index d6bb0b8982ff2a..8f5bc8f5918ba5 100644 --- a/src/database/sqlite/sqlite_metadata.c +++ b/src/database/sqlite/sqlite_metadata.c @@ -173,8 +173,7 @@ sqlite3 *db_meta = NULL; #define METADATA_RUNTIME_THRESHOLD (5) // Run time threshold for cleanup task #define METADATA_HOST_CHECK_FIRST_CHECK (5) // First check for pending metadata -#define METADATA_HOST_CHECK_INTERVAL (30) // Repeat check for pending metadata -#define METADATA_HOST_CHECK_IMMEDIATE (5) // Repeat immediate run because we have more metadata to write +#define METADATA_HOST_CHECK_INTERVAL (5) // Repeat check for pending metadata #define MAX_METADATA_CLEANUP (500) // Maximum metadata write operations (e.g deletes before retrying) #define METADATA_MAX_BATCH_SIZE (512) // Maximum commands to execute before running the event loop @@ -190,6 +189,8 @@ enum metadata_opcode { METADATA_SCAN_HOSTS, METADATA_LOAD_HOST_CONTEXT, METADATA_DELETE_HOST_CHART_LABELS, + METADATA_ADD_HOST_AE, + METADATA_DEL_HOST_AE, METADATA_MAINTENANCE, METADATA_SYNC_SHUTDOWN, METADATA_UNITTEST, @@ -217,6 +218,7 @@ struct metadata_wc { uv_async_t async; uv_timer_t timer_req; time_t metadata_check_after; + Pvoid_t ae_DelJudyL; METADATA_FLAG flags; struct completion start_stop_complete; struct completion *scan_complete; @@ -271,7 +273,7 @@ static inline void set_host_node_id(RRDHOST *host, nd_uuid_t *node_id) else uuid_unparse_lower(*node_id, wc->node_id); - rrdpush_receiver_send_node_and_claim_id_to_child(host); + stream_receiver_send_node_and_claim_id_to_child(host); stream_path_node_id_updated(host); } @@ -958,8 +960,8 @@ static int store_host_metadata(RRDHOST *host) SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_program_name(host), 1)); SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_program_version(host), 1)); SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int64(res, ++param, host->rrd_history_entries)); - SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(res, ++param, (int)host->health.health_enabled)); - SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int64(res, ++param, (sqlite3_int64) host->last_connected)); + SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(res, ++param, (int)host->health.enabled)); + SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int64(res, ++param, (sqlite3_int64) host->stream.snd.status.last_connected)); int store_rc = sqlite3_step_monitored(res); @@ -1602,7 +1604,8 @@ void run_metadata_cleanup(struct metadata_wc *wc) struct scan_metadata_payload { uv_work_t request; struct metadata_wc *wc; - void *data; + void *chart_label_cleanup; + void *pending_alert_list; BUFFER *work_buffer; uint32_t max_count; }; @@ -1749,6 +1752,19 @@ static void after_metadata_hosts(uv_work_t *req, int status __maybe_unused) struct scan_metadata_payload *data = req->data; struct metadata_wc *wc = data->wc; + bool first = false; + Word_t Index = 0; + Pvoid_t *Pvalue; + while ((Pvalue = JudyLFirstThenNext(wc->ae_DelJudyL, &Index, &first))) { + ALARM_ENTRY *ae = (ALARM_ENTRY *) Index; + if(!__atomic_load_n(&ae->pending_save_count, __ATOMIC_RELAXED)) { + health_alarm_log_free_one_nochecks_nounlink(ae); + (void) JudyLDel(&wc->ae_DelJudyL, Index, PJE0); + first = false; + Index = 0; + } + } + metadata_flag_clear(wc, METADATA_FLAG_PROCESSING); if (unlikely(wc->scan_complete)) @@ -1855,12 +1871,46 @@ static void store_host_and_system_info(RRDHOST *host, size_t *query_counter) } } -struct host_chart_label_cleanup { +struct judy_list_t { Pvoid_t JudyL; Word_t count; }; -static void do_chart_label_cleanup(struct host_chart_label_cleanup *cl_cleanup_data) +static void store_alert_transitions(struct judy_list_t *pending_alert_list) +{ + if (!pending_alert_list) + return; + + usec_t started_ut = now_monotonic_usec(); (void)started_ut; + + size_t entries = pending_alert_list->count; + Word_t Index = 0; + bool first = true; + Pvoid_t *PValue; + while ((PValue = JudyLFirstThenNext(pending_alert_list->JudyL, &Index, &first))) { + RRDHOST *host = *PValue; + + PValue = JudyLGet(pending_alert_list->JudyL, ++Index, PJE0); + ALARM_ENTRY *ae = *PValue; + + sql_health_alarm_log_save(host, ae); + + __atomic_add_fetch(&ae->pending_save_count, -1, __ATOMIC_RELAXED); + __atomic_add_fetch(&host->health.pending_transitions, -1, __ATOMIC_RELAXED); + } + (void) JudyLFreeArray(&pending_alert_list->JudyL, PJE0); + freez(pending_alert_list); + + usec_t ended_ut = now_monotonic_usec(); (void)ended_ut; + nd_log( + NDLS_DAEMON, + NDLP_DEBUG, + "Stored and processed %zu alert transitions in %0.2f ms", + entries, + (double)(ended_ut - started_ut) / USEC_PER_MS); +} + +static void do_chart_label_cleanup(struct judy_list_t *cl_cleanup_data) { if (!cl_cleanup_data) return; @@ -1900,7 +1950,8 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused) nd_log(NDLS_DAEMON, NDLP_DEBUG, "Checking all hosts started"); usec_t started_ut = now_monotonic_usec(); (void)started_ut; - do_chart_label_cleanup((struct host_chart_label_cleanup *) data->data); + store_alert_transitions((struct judy_list_t *)data->pending_alert_list); + do_chart_label_cleanup((struct judy_list_t *)data->chart_label_cleanup); bool run_again = false; worker_is_busy(UV_EVENT_METADATA_STORE); @@ -1909,6 +1960,7 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused) transaction_started = !db_execute(db_meta, "BEGIN TRANSACTION"); dfe_start_reentrant(rrdhost_root_index, host) { + if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED) || !rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_UPDATE)) continue; @@ -1988,12 +2040,10 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused) "Checking all hosts completed in %0.2f ms", (double)(all_ended_ut - all_started_ut) / USEC_PER_MS); - if (unlikely(run_again)) - wc->metadata_check_after = now_realtime_sec() + METADATA_HOST_CHECK_IMMEDIATE; - else { - wc->metadata_check_after = now_realtime_sec() + METADATA_HOST_CHECK_INTERVAL; + if (likely(!run_again)) run_metadata_cleanup(wc); - } + + wc->metadata_check_after = now_realtime_sec() + METADATA_HOST_CHECK_INTERVAL; worker_is_idle(); } @@ -2049,11 +2099,15 @@ static void metadata_event_loop(void *arg) completion_mark_complete(&wc->start_stop_complete); BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); struct scan_metadata_payload *data; - struct host_chart_label_cleanup *cl_cleanup_data = NULL; + struct judy_list_t *cl_cleanup_data = NULL; + Pvoid_t *PValue; + struct judy_list_t *pending_ae_list = NULL; while (shutdown == 0 || (wc->flags & METADATA_FLAG_PROCESSING)) { nd_uuid_t *uuid; RRDHOST *host = NULL; + ALARM_ENTRY *ae = NULL; +// struct aclk_sync_cfg_t *host_aclk_sync; worker_is_idle(); uv_run(loop, UV_RUN_DEFAULT); @@ -2106,9 +2160,11 @@ static void metadata_event_loop(void *arg) data = mallocz(sizeof(*data)); data->request.data = data; data->wc = wc; - data->data = cl_cleanup_data; + data->chart_label_cleanup = cl_cleanup_data; + data->pending_alert_list = pending_ae_list; data->work_buffer = work_buffer; cl_cleanup_data = NULL; + pending_ae_list = NULL; if (unlikely(cmd.completion)) { data->max_count = 0; // 0 will process all pending updates @@ -2118,13 +2174,11 @@ static void metadata_event_loop(void *arg) data->max_count = 5000; metadata_flag_set(wc, METADATA_FLAG_PROCESSING); - if (unlikely( - uv_queue_work(loop,&data->request, - start_metadata_hosts, - after_metadata_hosts))) { + if (uv_queue_work(loop, &data->request, start_metadata_hosts, after_metadata_hosts)) { // Failed to launch worker -- let the event loop handle completion cmd.completion = wc->scan_complete; - cl_cleanup_data = data->data; + cl_cleanup_data = data->chart_label_cleanup; + pending_ae_list = data->pending_alert_list; freez(data); metadata_flag_clear(wc, METADATA_FLAG_PROCESSING); } @@ -2136,9 +2190,7 @@ static void metadata_event_loop(void *arg) data = callocz(1,sizeof(*data)); data->request.data = data; data->wc = wc; - if (unlikely( - uv_queue_work(loop,&data->request, start_all_host_load_context, - after_start_host_load_context))) { + if (uv_queue_work(loop, &data->request, start_all_host_load_context, after_start_host_load_context)) { freez(data); } break; @@ -2146,10 +2198,28 @@ static void metadata_event_loop(void *arg) if (!cl_cleanup_data) cl_cleanup_data = callocz(1,sizeof(*cl_cleanup_data)); - Pvoid_t *PValue = JudyLIns(&cl_cleanup_data->JudyL, (Word_t) ++cl_cleanup_data->count, PJE0); + PValue = JudyLIns(&cl_cleanup_data->JudyL, (Word_t) ++cl_cleanup_data->count, PJE0); if (PValue) *PValue = (void *) cmd.param[0]; + break; + case METADATA_ADD_HOST_AE: + host = (RRDHOST *) cmd.param[0]; + ae = (ALARM_ENTRY *) cmd.param[1]; + + if (!pending_ae_list) + pending_ae_list = callocz(1, sizeof(*pending_ae_list)); + + PValue = JudyLIns(&pending_ae_list->JudyL, ++pending_ae_list->count, PJE0); + if (PValue) + *PValue = (void *)host; + + PValue = JudyLIns(&pending_ae_list->JudyL, ++pending_ae_list->count, PJE0); + if (PValue) + *PValue = (void *)ae; + break; + case METADATA_DEL_HOST_AE:; + (void) JudyLIns(&wc->ae_DelJudyL, (Word_t) (void *) cmd.param[0], PJE0); break; case METADATA_UNITTEST:; struct thread_unittest *tu = (struct thread_unittest *) cmd.param[0]; @@ -2335,6 +2405,31 @@ void metadata_delete_host_chart_labels(char *machine_guid) nd_log(NDLS_DAEMON, NDLP_DEBUG, "Queued command delete chart labels for host %s", machine_guid); } +void metadata_queue_ae_save(RRDHOST *host, ALARM_ENTRY *ae) +{ + if (unlikely(!metasync_worker.loop)) + return; + __atomic_add_fetch(&host->health.pending_transitions, 1, __ATOMIC_RELAXED); + __atomic_add_fetch(&ae->pending_save_count, 1, __ATOMIC_RELAXED); + queue_metadata_cmd(METADATA_ADD_HOST_AE, host, ae); +} + +void metadata_queue_ae_deletion(ALARM_ENTRY *ae) +{ + if (unlikely(!metasync_worker.loop)) + return; + + queue_metadata_cmd(METADATA_DEL_HOST_AE, ae, NULL); +} + +void commit_alert_transitions(RRDHOST *host __maybe_unused) +{ + if (unlikely(!metasync_worker.loop)) + return; + + queue_metadata_cmd(METADATA_SCAN_HOSTS, NULL, NULL); +} + uint64_t sqlite_get_meta_space(void) { return sqlite_get_db_space(db_meta); @@ -2378,6 +2473,15 @@ void cleanup_agent_event_log(void) usec_t get_agent_event_time_median(event_log_type_t event_id) { + static bool initialized[EVENT_AGENT_MAX] = { 0 }; + static usec_t median[EVENT_AGENT_MAX] = { 0 }; + + if(event_id >= EVENT_AGENT_MAX) + return 0; + + if(initialized[event_id]) + return median[event_id]; + sqlite3_stmt *res = NULL; if (!PREPARE_STATEMENT(db_meta, SQL_GET_AGENT_EVENT_TYPE_MEDIAN, &res)) return 0; @@ -2393,9 +2497,17 @@ usec_t get_agent_event_time_median(event_log_type_t event_id) done: REPORT_BIND_FAIL(res, param); SQLITE_FINALIZE(res); + + median[event_id] = avg_time; + initialized[event_id] = true; return avg_time; } +void get_agent_event_time_median_init(void) { + for(event_log_type_t event_id = 1; event_id < EVENT_AGENT_MAX; event_id++) + get_agent_event_time_median(event_id); +} + // // unitests // diff --git a/src/database/sqlite/sqlite_metadata.h b/src/database/sqlite/sqlite_metadata.h index a5e68eb8cfda08..ae5c840057befa 100644 --- a/src/database/sqlite/sqlite_metadata.h +++ b/src/database/sqlite/sqlite_metadata.h @@ -9,7 +9,11 @@ typedef enum event_log_type { EVENT_AGENT_START_TIME = 1, EVENT_AGENT_SHUTDOWN_TIME, + + // terminator + EVENT_AGENT_MAX, } event_log_type_t; +void get_agent_event_time_median_init(void); // return a node list struct node_instance_list { @@ -62,6 +66,9 @@ int sql_init_meta_database(db_check_action_type_t rebuild, int memory); void cleanup_agent_event_log(void); void add_agent_event(event_log_type_t event_id, int64_t value); usec_t get_agent_event_time_median(event_log_type_t event_id); +void metadata_queue_ae_save(RRDHOST *host, ALARM_ENTRY *ae); +void metadata_queue_ae_deletion(ALARM_ENTRY *ae); +void commit_alert_transitions(RRDHOST *host); // UNIT TEST int metadata_unittest(void); diff --git a/src/exporting/process_data.c b/src/exporting/process_data.c index 1c7eaa192a95e2..aa60eab5faed39 100644 --- a/src/exporting/process_data.c +++ b/src/exporting/process_data.c @@ -139,7 +139,7 @@ NETDATA_DOUBLE exporting_calculate_value_from_stored_data( counter += sp.count; } storage_engine_query_finalize(&handle); - global_statistics_exporters_query_completed(points_read); + telemetry_queries_exporters_query_completed(points_read); if (unlikely(!counter)) { netdata_log_debug( diff --git a/src/exporting/send_internal_metrics.c b/src/exporting/send_internal_metrics.c index 677a57bbbe6889..f447e6b5185e00 100644 --- a/src/exporting/send_internal_metrics.c +++ b/src/exporting/send_internal_metrics.c @@ -11,7 +11,7 @@ */ void create_main_rusage_chart(RRDSET **st_rusage, RRDDIM **rd_user, RRDDIM **rd_system) { - if (!global_statistics_enabled) + if (!telemetry_enabled) return; if (*st_rusage && *rd_user && *rd_system) @@ -44,7 +44,7 @@ void create_main_rusage_chart(RRDSET **st_rusage, RRDDIM **rd_user, RRDDIM **rd_ */ void send_main_rusage(RRDSET *st_rusage, RRDDIM *rd_user, RRDDIM *rd_system) { - if (!global_statistics_enabled) + if (!telemetry_enabled) return; struct rusage thread; @@ -65,7 +65,7 @@ void send_main_rusage(RRDSET *st_rusage, RRDDIM *rd_user, RRDDIM *rd_system) */ void send_internal_metrics(struct instance *instance) { - if (!global_statistics_enabled) + if (!telemetry_enabled) return; struct stats *stats = &instance->stats; diff --git a/src/health/health_dyncfg.c b/src/health/health_dyncfg.c index 48346f662a3485..7bdc578c496f70 100644 --- a/src/health/health_dyncfg.c +++ b/src/health/health_dyncfg.c @@ -509,7 +509,7 @@ int dyncfg_health_prototype_to_conf(BUFFER *wb, RRD_ALERT_PROTOTYPE *ap, const c if(nap->config.info) buffer_sprintf(wb, "%13s: %s\n", "info", string2str(nap->config.info)); - if(nap->config.exec && nap->config.exec != localhost->health.health_default_exec) + if(nap->config.exec && nap->config.exec != localhost->health.default_exec) buffer_sprintf(wb, "%13s: %s\n", "exec", string2str(nap->config.exec)); if(nap->config.recipient) diff --git a/src/health/health_event_loop.c b/src/health/health_event_loop.c index 0bf6892dd8140d..144e4929be1508 100644 --- a/src/health/health_event_loop.c +++ b/src/health/health_event_loop.c @@ -125,7 +125,7 @@ static void health_execute_delayed_initializations(RRDHOST *host) { static void health_initialize_rrdhost(RRDHOST *host) { health_plugin_init(); - if(!host->health.health_enabled || + if(!host->health.enabled || rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH) || !service_running(SERVICE_HEALTH)) return; @@ -134,8 +134,8 @@ static void health_initialize_rrdhost(RRDHOST *host) { host->health_log.max = health_globals.config.health_log_entries_max; host->health_log.health_log_retention_s = health_globals.config.health_log_retention_s; - host->health.health_default_exec = string_dup(health_globals.config.default_exec); - host->health.health_default_recipient = string_dup(health_globals.config.default_recipient); + host->health.default_exec = string_dup(health_globals.config.default_exec); + host->health.default_recipient = string_dup(health_globals.config.default_recipient); host->health.use_summary_for_notifications = health_globals.config.use_summary_for_notifications; host->health_log.next_log_id = get_uint32_id(); @@ -247,9 +247,20 @@ static void health_event_loop(void) { if(unlikely(!service_running(SERVICE_HEALTH))) break; - if (unlikely(!host->health.health_enabled)) + if (unlikely(!host->health.enabled)) continue; +//#define rrdhost_pending_alert_transitions(host) (__atomic_load_n(&((host)->aclk_config.alert_transition.pending), __ATOMIC_RELAXED)) + + if (unlikely(__atomic_load_n(&host->health.pending_transitions, __ATOMIC_RELAXED))) { + nd_log( + NDLS_DAEMON, + NDLP_DEBUG, + "Host \"%s\" has pending alert transitions to save, postponing health checks", + rrdhost_hostname(host)); + continue; + } + if (unlikely(!rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH))) health_initialize_rrdhost(host); @@ -261,12 +272,12 @@ static void health_event_loop(void) { rrdhost_hostname(host), health_globals.config.postpone_alarms_during_hibernation_for_seconds); - host->health.health_delay_up_to = + host->health.delay_up_to = now + health_globals.config.postpone_alarms_during_hibernation_for_seconds; } - if (unlikely(host->health.health_delay_up_to)) { - if (unlikely(now < host->health.health_delay_up_to)) { + if (unlikely(host->health.delay_up_to)) { + if (unlikely(now < host->health.delay_up_to)) { continue; } @@ -274,12 +285,12 @@ static void health_event_loop(void) { "[%s]: Resuming health checks after delay.", rrdhost_hostname(host)); - host->health.health_delay_up_to = 0; + host->health.delay_up_to = 0; } // wait until cleanup of obsolete charts on children is complete if (host != localhost) { - if (unlikely(host->trigger_chart_obsoletion_check == 1)) { + if (unlikely(host->stream.rcv.status.check_obsolete)) { nd_log(NDLS_DAEMON, NDLP_DEBUG, "[%s]: Waiting for chart obsoletion check.", @@ -649,14 +660,21 @@ static void health_event_loop(void) { break; } } - struct aclk_sync_cfg_t *wc = host->aclk_config; - if (wc && wc->send_snapshot == 1) { - wc->send_snapshot = 2; - rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); - } - else - if (process_alert_pending_queue(host)) + + int32_t pending = __atomic_load_n(&host->health.pending_transitions, __ATOMIC_RELAXED); + if (pending) + commit_alert_transitions(host); + + if (!__atomic_load_n(&host->health.pending_transitions, __ATOMIC_RELAXED)) { + struct aclk_sync_cfg_t *wc = host->aclk_config; + if (wc && wc->send_snapshot == 1) { + wc->send_snapshot = 2; rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); + } else { + if (process_alert_pending_queue(host)) + rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); + } + } dfe_done(host); diff --git a/src/health/health_json.c b/src/health/health_json.c index 68bfb5229a29de..8cd007ac7acad6 100644 --- a/src/health/health_json.c +++ b/src/health/health_json.c @@ -88,8 +88,8 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC , (rc->rrdset)?"true":"false" , (rc->run_flags & RRDCALC_FLAG_DISABLED)?"true":"false" , (rc->run_flags & RRDCALC_FLAG_SILENCED)?"true":"false" - , rc->config.exec?rrdcalc_exec(rc):string2str(host->health.health_default_exec) - , rc->config.recipient?rrdcalc_recipient(rc):string2str(host->health.health_default_recipient) + , rc->config.exec?rrdcalc_exec(rc):string2str(host->health.default_exec) + , rc->config.recipient?rrdcalc_recipient(rc):string2str(host->health.default_recipient) , rrdcalc_source(rc) , rrdcalc_units(rc) , string2str(rc->summary) @@ -238,7 +238,7 @@ void health_alarms2json(RRDHOST *host, BUFFER *wb, int all) { "\n\t\"alarms\": {\n", rrdhost_hostname(host), (host->health_log.next_log_id > 0)?(host->health_log.next_log_id - 1):0, - host->health.health_enabled?"true":"false", + host->health.enabled ?"true":"false", (unsigned long)now_realtime_sec()); health_alarms2json_fill_alarms(host, wb, all, health_rrdcalc2json_nolock); diff --git a/src/health/health_log.c b/src/health/health_log.c index 143b741bffb2ac..363e1a63a1de4a 100644 --- a/src/health/health_log.c +++ b/src/health/health_log.c @@ -6,10 +6,9 @@ inline void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae) { - sql_health_alarm_log_save(host, ae); + metadata_queue_ae_save(host, ae); } - void health_log_alert_transition_with_trace(RRDHOST *host, ALARM_ENTRY *ae, int line, const char *file, const char *function) { if(!host || !ae) return; @@ -160,6 +159,7 @@ inline ALARM_ENTRY* health_create_alarm_entry( ae->flags |= flags; ae->last_repeat = 0; + ae->pending_save_count = 0; if(ae->old_status == RRDCALC_STATUS_WARNING || ae->old_status == RRDCALC_STATUS_CRITICAL) ae->non_clear_duration += ae->duration; @@ -167,10 +167,8 @@ inline ALARM_ENTRY* health_create_alarm_entry( return ae; } -inline void health_alarm_log_add_entry( - RRDHOST *host, - ALARM_ENTRY *ae -) { +inline void health_alarm_log_add_entry(RRDHOST *host, ALARM_ENTRY *ae) +{ netdata_log_debug(D_HEALTH, "Health adding alarm log entry with id: %u", ae->unique_id); __atomic_add_fetch(&host->health_transitions, 1, __ATOMIC_RELAXED); @@ -209,20 +207,24 @@ inline void health_alarm_log_add_entry( } inline void health_alarm_log_free_one_nochecks_nounlink(ALARM_ENTRY *ae) { - string_freez(ae->name); - string_freez(ae->chart); - string_freez(ae->chart_context); - string_freez(ae->classification); - string_freez(ae->component); - string_freez(ae->type); - string_freez(ae->exec); - string_freez(ae->recipient); - string_freez(ae->source); - string_freez(ae->units); - string_freez(ae->info); - string_freez(ae->old_value_string); - string_freez(ae->new_value_string); - freez(ae); + if(__atomic_load_n(&ae->pending_save_count, __ATOMIC_RELAXED)) + metadata_queue_ae_deletion(ae); + else { + string_freez(ae->name); + string_freez(ae->chart); + string_freez(ae->chart_context); + string_freez(ae->classification); + string_freez(ae->component); + string_freez(ae->type); + string_freez(ae->exec); + string_freez(ae->recipient); + string_freez(ae->source); + string_freez(ae->units); + string_freez(ae->info); + string_freez(ae->old_value_string); + string_freez(ae->new_value_string); + freez(ae); + } } inline void health_alarm_log_free(RRDHOST *host) { diff --git a/src/health/health_notifications.c b/src/health/health_notifications.c index 443c0246f06fc3..d78e4f938bd2e3 100644 --- a/src/health/health_notifications.c +++ b/src/health/health_notifications.c @@ -421,8 +421,8 @@ void health_send_notification(RRDHOST *host, ALARM_ENTRY *ae, struct health_rais "[%s]: Sending notification for alarm '%s.%s' status %s.", rrdhost_hostname(host), ae_chart_id(ae), ae_name(ae), rrdcalc_status2string(ae->new_status)); - const char *exec = (ae->exec) ? ae_exec(ae) : string2str(host->health.health_default_exec); - const char *recipient = (ae->recipient) ? ae_recipient(ae) : string2str(host->health.health_default_recipient); + const char *exec = (ae->exec) ? ae_exec(ae) : string2str(host->health.default_exec); + const char *recipient = (ae->recipient) ? ae_recipient(ae) : string2str(host->health.default_recipient); char *edit_command = ae->source ? health_edit_command_from_source(ae_source(ae)) : strdupz("UNKNOWN=0=UNKNOWN"); diff --git a/src/health/health_prototypes.c b/src/health/health_prototypes.c index a8681a4530ba08..c553913d6ba291 100644 --- a/src/health/health_prototypes.c +++ b/src/health/health_prototypes.c @@ -642,7 +642,7 @@ void health_apply_prototype_to_host(RRDHOST *host, RRD_ALERT_PROTOTYPE *ap) { if(!ap->_internal.enabled) return; - if(unlikely(!host->health.health_enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) + if(unlikely(!host->health.enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) return; RRDSET *st; @@ -666,7 +666,7 @@ void health_prototype_apply_to_all_hosts(RRD_ALERT_PROTOTYPE *ap) { // --------------------------------------------------------------------------------------------------------------------- void health_apply_prototypes_to_host(RRDHOST *host) { - if(unlikely(!host->health.health_enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) + if(unlikely(!host->health.enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) return; // free all running alarms diff --git a/src/health/rrdvar.c b/src/health/rrdvar.c index 5d6e3cf84bd875..718c117c9d1d97 100644 --- a/src/health/rrdvar.c +++ b/src/health/rrdvar.c @@ -85,7 +85,7 @@ void rrdvar_host_variable_set(RRDHOST *host, const RRDVAR_ACQUIRED *rva, NETDATA rv->value = value; // if the host is streaming, send this variable upstream immediately - rrdpush_sender_send_this_host_variable_now(host, rva); + stream_sender_send_this_host_variable_now(host, rva); } } diff --git a/src/libnetdata/aral/aral.c b/src/libnetdata/aral/aral.c index 2ec27b3dfc5eaf..3331cd4ede3909 100644 --- a/src/libnetdata/aral/aral.c +++ b/src/libnetdata/aral/aral.c @@ -1,6 +1,8 @@ #include "../libnetdata.h" #include "aral.h" +// #define NETDATA_ARAL_INTERNAL_CHECKS 1 + #ifdef NETDATA_TRACE_ALLOCATIONS #define TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS , const char *file, const char *function, size_t line #define TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS , file, function, line @@ -9,15 +11,18 @@ #define TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS #endif -#define ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST 5 - // max file size -#define ARAL_MAX_PAGE_SIZE_MMAP (1*1024*1024*1024) +#define ARAL_MAX_PAGE_SIZE_MMAP (1ULL * 1024 * 1024 * 1024) // max malloc size // optimal at current versions of libc is up to 256k // ideal to have the same overhead as libc is 4k -#define ARAL_MAX_PAGE_SIZE_MALLOC (65*1024) +#define ARAL_MAX_PAGE_SIZE_MALLOC (256ULL * 1024) + +// we don't need alignof(max_align_t) for normal C structures +// alignof(uintptr_r) is sufficient for our use cases +// #define SYSTEM_REQUIRED_ALIGNMENT (alignof(max_align_t)) +#define SYSTEM_REQUIRED_ALIGNMENT (alignof(uintptr_t)) typedef struct aral_free { size_t size; @@ -25,19 +30,20 @@ typedef struct aral_free { } ARAL_FREE; typedef struct aral_page { - size_t size; // the allocation size of the page + bool marked; + uint32_t size; // the allocation size of the page const char *filename; uint8_t *data; - uint32_t free_elements_to_move_first; - uint32_t max_elements; // the number of elements that can fit on this page + uint32_t max_elements; // the number of elements that can fit on this page struct { uint32_t used_elements; // the number of used elements on this page uint32_t free_elements; // the number of free elements on this page + uint32_t marked_elements; - struct aral_page *prev; // the prev page on the list - struct aral_page *next; // the next page on the list + struct aral_page *prev; // the prev page on the list + struct aral_page *next; // the next page on the list } aral_lock; struct { @@ -48,12 +54,25 @@ typedef struct aral_page { } ARAL_PAGE; typedef enum { - ARAL_LOCKLESS = (1 << 0), - ARAL_DEFRAGMENT = (1 << 1), - ARAL_ALLOCATED_STATS = (1 << 2), + ARAL_LOCKLESS = (1 << 0), + ARAL_ALLOCATED_STATS = (1 << 1), } ARAL_OPTIONS; +struct aral_ops { + struct { + alignas(64) size_t allocators; // the number of threads currently trying to allocate memory + alignas(64) size_t deallocators; // the number of threads currently trying to deallocate memory + } atomic; + + struct { + alignas(64) SPINLOCK spinlock; + size_t allocating_elements; // currently allocating elements + size_t allocation_size; // current / next allocation size + } adders; +}; + struct aral { + struct { char name[ARAL_MAX_NAME + 1]; @@ -63,7 +82,7 @@ struct aral { size_t max_allocation_size; // calculated in bytes size_t max_page_elements; // calculated size_t page_ptr_offset; // calculated - size_t natural_page_size; // calculated + size_t system_page_size; // calculated size_t initial_page_elements; size_t requested_element_size; @@ -77,9 +96,14 @@ struct aral { } config; struct { - SPINLOCK spinlock; + alignas(64) SPINLOCK spinlock; size_t file_number; // for mmap - struct aral_page *pages; // linked list of pages + + ARAL_PAGE *pages_free; // pages with free items + ARAL_PAGE *pages_full; // pages that are completely full + + ARAL_PAGE *pages_marked_free; // pages with marked items and free slots + ARAL_PAGE *pages_marked_full; // pages with marked items completely full size_t user_malloc_operations; size_t user_free_operations; @@ -87,26 +111,42 @@ struct aral { size_t defragment_linked_list_traversals; } aral_lock; - struct { - SPINLOCK spinlock; - size_t allocating_elements; // currently allocating elements - size_t allocation_size; // current / next allocation size - } adders; - - struct { - size_t allocators; // the number of threads currently trying to allocate memory - } atomic; + struct aral_ops ops[2]; struct aral_statistics *stats; }; +#define mark_to_idx(marked) (marked ? 1 : 0) +#define aral_pages_head_free(ar, marked) (marked ? &ar->aral_lock.pages_marked_free : &ar->aral_lock.pages_free) +#define aral_pages_head_full(ar, marked) (marked ? &ar->aral_lock.pages_marked_full : &ar->aral_lock.pages_full) + +const char *aral_name(ARAL *ar) { + return ar->config.name; +} + size_t aral_structures_from_stats(struct aral_statistics *stats) { + if(!stats) return 0; return __atomic_load_n(&stats->structures.allocated_bytes, __ATOMIC_RELAXED); } size_t aral_overhead_from_stats(struct aral_statistics *stats) { - return __atomic_load_n(&stats->malloc.allocated_bytes, __ATOMIC_RELAXED) - - __atomic_load_n(&stats->malloc.used_bytes, __ATOMIC_RELAXED); + if(!stats) return 0; + + size_t allocated = __atomic_load_n(&stats->malloc.allocated_bytes, __ATOMIC_RELAXED) + + __atomic_load_n(&stats->mmap.allocated_bytes, __ATOMIC_RELAXED); + + size_t used = __atomic_load_n(&stats->malloc.used_bytes, __ATOMIC_RELAXED) + + __atomic_load_n(&stats->mmap.used_bytes, __ATOMIC_RELAXED); + + if(allocated > used) return allocated - used; + return allocated; +} + +size_t aral_used_bytes_from_stats(struct aral_statistics *stats) { + size_t used = __atomic_load_n(&stats->malloc.used_bytes, __ATOMIC_RELAXED) + + __atomic_load_n(&stats->mmap.used_bytes, __ATOMIC_RELAXED); + + return used; } size_t aral_overhead(ARAL *ar) { @@ -121,17 +161,13 @@ struct aral_statistics *aral_get_statistics(ARAL *ar) { return ar->stats; } -#define ARAL_NATURAL_ALIGNMENT (sizeof(uintptr_t) * 2) -static inline size_t natural_alignment(size_t size, size_t alignment) { - if(unlikely(size % alignment)) - size = size + alignment - (size % alignment); - - return size; +static inline size_t memory_alignment(size_t size, size_t alignment) { + // return (size + alignment - 1) & ~(alignment - 1); // assumees alignment is power of 2 + return ((size + alignment - 1) / alignment) * alignment; } static size_t aral_align_alloc_size(ARAL *ar, uint64_t size) { - if(size % ar->config.natural_page_size) - size += ar->config.natural_page_size - (size % ar->config.natural_page_size) ; + size = memory_alignment(size, ar->config.system_page_size); if(size % ar->config.element_size) size -= size % ar->config.element_size; @@ -159,21 +195,27 @@ static inline void aral_page_free_unlock(ARAL *ar, ARAL_PAGE *page) { spinlock_unlock(&page->free.spinlock); } -static inline bool aral_adders_trylock(ARAL *ar) { - if(likely(!(ar->config.options & ARAL_LOCKLESS))) - return spinlock_trylock(&ar->adders.spinlock); +static inline bool aral_adders_trylock(ARAL *ar, bool marked) { + if(likely(!(ar->config.options & ARAL_LOCKLESS))) { + size_t idx = mark_to_idx(marked); + return spinlock_trylock(&ar->ops[idx].adders.spinlock); + } return true; } -static inline void aral_adders_lock(ARAL *ar) { - if(likely(!(ar->config.options & ARAL_LOCKLESS))) - spinlock_lock(&ar->adders.spinlock); +static inline void aral_adders_lock(ARAL *ar, bool marked) { + if(likely(!(ar->config.options & ARAL_LOCKLESS))) { + size_t idx = mark_to_idx(marked); + spinlock_lock(&ar->ops[idx].adders.spinlock); + } } -static inline void aral_adders_unlock(ARAL *ar) { - if(likely(!(ar->config.options & ARAL_LOCKLESS))) - spinlock_unlock(&ar->adders.spinlock); +static inline void aral_adders_unlock(ARAL *ar, bool marked) { + if(likely(!(ar->config.options & ARAL_LOCKLESS))) { + size_t idx = mark_to_idx(marked); + spinlock_unlock(&ar->ops[idx].adders.spinlock); + } } static void aral_delete_leftover_files(const char *name, const char *path, const char *required_prefix) { @@ -200,38 +242,76 @@ static void aral_delete_leftover_files(const char *name, const char *path, const closedir(dir); } -// ---------------------------------------------------------------------------- -// check a free slot +// -------------------------------------------------------------------------------------------------------------------- -#ifdef NETDATA_INTERNAL_CHECKS -static inline void aral_free_validate_internal_check(ARAL *ar, ARAL_FREE *fr) { - if(unlikely(fr->size < ar->config.element_size)) - fatal("ARAL: '%s' free item of size %zu, less than the expected element size %zu", - ar->config.name, fr->size, ar->config.element_size); +#ifdef NETDATA_ARAL_INTERNAL_CHECKS +struct free_space { + size_t pages; + size_t pages_with_free_elements; + size_t max_free_elements_on_a_page; + size_t free_elements; + size_t max_page_elements; + ARAL_PAGE *p, *lp; +}; - if(unlikely(fr->size % ar->config.element_size)) - fatal("ARAL: '%s' free item of size %zu is not multiple to element size %zu", - ar->config.name, fr->size, ar->config.element_size); +static inline struct free_space check_free_space___aral_lock_needed(ARAL *ar, ARAL_PAGE *my_page, bool marked) { + struct free_space f = { 0 }; + + f.max_page_elements = ar->config.max_allocation_size / ar->config.element_size; + for(f.p = *aral_pages_head_free(ar, marked); f.p ; f.lp = f.p, f.p = f.p->aral_lock.next) { + f.pages++; + internal_fatal(!f.p->aral_lock.free_elements, "page is in the free list, but does not have any elements free"); + internal_fatal(f.p->marked != marked, "page is in the wrong mark list"); + + if(f.p != my_page && f.max_free_elements_on_a_page < f.p->aral_lock.free_elements) + f.max_free_elements_on_a_page = f.p->aral_lock.free_elements; + + f.free_elements += f.p->aral_lock.free_elements; + f.pages_with_free_elements++; + } + + for(f.p = *aral_pages_head_full(ar, marked); f.p ; f.lp = f.p, f.p = f.p->aral_lock.next) { + f.pages++; + internal_fatal(f.p->aral_lock.free_elements, "found page with free items in a full page"); + internal_fatal(f.p->marked != marked, "page is in the wrong mark list"); + } + + return f; +} +static inline bool is_page_in_list(ARAL_PAGE *head, ARAL_PAGE *page) { + for(ARAL_PAGE *p = head; p ; p = p->aral_lock.next) + if(p == page) return true; + return false; } #else -#define aral_free_validate_internal_check(ar, fr) debug_dummy() + +#define is_page_in_list(head, page) true + #endif -// ---------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------------------------------------------- // find the page a pointer belongs to -#ifdef NETDATA_INTERNAL_CHECKS -static inline ARAL_PAGE *find_page_with_allocation_internal_check(ARAL *ar, void *ptr) { +#ifdef NETDATA_ARAL_INTERNAL_CHECKS +static inline ARAL_PAGE *find_page_with_allocation_internal_check(ARAL *ar, void *ptr, bool marked) { aral_lock(ar); uintptr_t seeking = (uintptr_t)ptr; ARAL_PAGE *page; - for(page = ar->aral_lock.pages; page ; page = page->aral_lock.next) { - if(unlikely(seeking >= (uintptr_t)page->data && seeking < (uintptr_t)page->data + page->size)) + for (page = *aral_pages_head_full(ar, marked); page; page = page->aral_lock.next) { + if (unlikely(seeking >= (uintptr_t)page->data && seeking < (uintptr_t)page->data + page->size)) break; } + if(!page) { + for(page = *aral_pages_head_free(ar, marked); page ; page = page->aral_lock.next) { + if(unlikely(seeking >= (uintptr_t)page->data && seeking < (uintptr_t)page->data + page->size)) + break; + } + } + aral_unlock(ar); return page; @@ -239,34 +319,78 @@ static inline ARAL_PAGE *find_page_with_allocation_internal_check(ARAL *ar, void #endif // ---------------------------------------------------------------------------- -// find a page with a free slot (there shouldn't be any) +// Tagging the pointer with the 'marked' flag -#ifdef NETDATA_ARAL_INTERNAL_CHECKS -static inline ARAL_PAGE *find_page_with_free_slots_internal_check___with_aral_lock(ARAL *ar) { - ARAL_PAGE *page; +// Retrieving the pointer and the 'marked' flag +static ARAL_PAGE *aral_get_page_pointer_after_element___do_NOT_have_aral_lock(ARAL *ar, void *ptr, bool *marked) { + uint8_t *data = ptr; + uintptr_t *page_ptr = (uintptr_t *)&data[ar->config.page_ptr_offset]; + uintptr_t tagged_page = __atomic_load_n(page_ptr, __ATOMIC_ACQUIRE); // Atomically load the tagged pointer + *marked = (tagged_page & 1) != 0; // Extract the LSB as the 'marked' flag + ARAL_PAGE *page = (ARAL_PAGE *)(tagged_page & ~1); // Mask out the LSB to get the original pointer - for(page = ar->aral_lock.pages; page ; page = page->next) { - if(page->aral_lock.free_elements) - break; + internal_fatal(!page, + "ARAL: '%s' possible corruption or double free of pointer %p", + ar->config.name, ptr); - internal_fatal(page->size - page->aral_lock.used_elements * ar->config.element_size >= ar->config.element_size, - "ARAL: '%s' a page is marked full, but it is not!", ar->config.name); +#ifdef NETDATA_ARAL_INTERNAL_CHECKS + { + // find the page ptr belongs + ARAL_PAGE *page2 = find_page_with_allocation_internal_check(ar, ptr, *marked); + if(!page2) { + page2 = find_page_with_allocation_internal_check(ar, ptr, !(*marked)); + internal_fatal(page2 && (*marked) && !page2->marked, "ARAL: '%s' page pointer is in different mark index", + ar->config.name); + } - internal_fatal(page->size < page->aral_lock.used_elements * ar->config.element_size, - "ARAL: '%s' a page has been overflown!", ar->config.name); + internal_fatal(page != page2, + "ARAL: '%s' page pointers do not match!", + ar->config.name); + + internal_fatal(!page2, + "ARAL: '%s' free of pointer %p is not in ARAL address space.", + ar->config.name, ptr); } +#endif return page; } + +static void aral_set_page_pointer_after_element___do_NOT_have_aral_lock(ARAL *ar, void *page, void *ptr, bool marked) { + uint8_t *data = ptr; + uintptr_t *page_ptr = (uintptr_t *)&data[ar->config.page_ptr_offset]; + uintptr_t tagged_page = (uintptr_t)page; // Cast the pointer to an integer + if (marked) tagged_page |= 1; // Set the LSB to 1 if 'marked' is true + __atomic_store_n(page_ptr, tagged_page, __ATOMIC_RELEASE); // Atomically store the tagged pointer +} + +// ---------------------------------------------------------------------------- +// check a free slot + +#ifdef NETDATA_INTERNAL_CHECKS +static inline void aral_free_validate_internal_check(ARAL *ar, ARAL_FREE *fr) { + if(unlikely(fr->size < ar->config.element_size)) + fatal("ARAL: '%s' free item of size %zu, less than the expected element size %zu", + ar->config.name, fr->size, ar->config.element_size); + + if(unlikely(fr->size % ar->config.element_size)) + fatal("ARAL: '%s' free item of size %zu is not multiple to element size %zu", + ar->config.name, fr->size, ar->config.element_size); +} +#else +#define aral_free_validate_internal_check(ar, fr) debug_dummy() #endif -size_t aral_next_allocation_size___adders_lock_needed(ARAL *ar) { - size_t size = ar->adders.allocation_size; +// ---------------------------------------------------------------------------- + +size_t aral_next_allocation_size___adders_lock_needed(ARAL *ar, bool marked) { + size_t idx = mark_to_idx(marked); + size_t size = ar->ops[idx].adders.allocation_size; if(size > ar->config.max_allocation_size) size = ar->config.max_allocation_size; else - ar->adders.allocation_size = aral_align_alloc_size(ar, (uint64_t)ar->adders.allocation_size * 2); + ar->ops[idx].adders.allocation_size = aral_align_alloc_size(ar, (uint64_t)ar->ops[idx].adders.allocation_size * 2); return size; } @@ -277,9 +401,6 @@ static ARAL_PAGE *aral_create_page___no_lock_needed(ARAL *ar, size_t size TRACE_ page->size = size; page->max_elements = page->size / ar->config.element_size; page->aral_lock.free_elements = page->max_elements; - page->free_elements_to_move_first = page->max_elements / 4; - if(unlikely(page->free_elements_to_move_first < 1)) - page->free_elements_to_move_first = 1; __atomic_add_fetch(&ar->stats->structures.allocations, 1, __ATOMIC_RELAXED); __atomic_add_fetch(&ar->stats->structures.allocated_bytes, sizeof(ARAL_PAGE), __ATOMIC_RELAXED); @@ -291,7 +412,7 @@ static ARAL_PAGE *aral_create_page___no_lock_needed(ARAL *ar, size_t size TRACE_ page->filename = strdupz(filename); page->data = netdata_mmap(page->filename, page->size, MAP_SHARED, 0, false, NULL); if (unlikely(!page->data)) - fatal("ARAL: '%s' cannot allocate aral buffer of size %zu on filename '%s'", + fatal("ARAL: '%s' cannot allocate aral buffer of size %u on filename '%s'", ar->config.name, page->size, page->filename); __atomic_add_fetch(&ar->stats->mmap.allocations, 1, __ATOMIC_RELAXED); __atomic_add_fetch(&ar->stats->mmap.allocated_bytes, page->size, __ATOMIC_RELAXED); @@ -302,12 +423,14 @@ static ARAL_PAGE *aral_create_page___no_lock_needed(ARAL *ar, size_t size TRACE_ #else page->data = mallocz(page->size); #endif + __atomic_add_fetch(&ar->stats->malloc.allocations, 1, __ATOMIC_RELAXED); __atomic_add_fetch(&ar->stats->malloc.allocated_bytes, page->size, __ATOMIC_RELAXED); } // link the free space to its page ARAL_FREE *fr = (ARAL_FREE *)page->data; + fr->size = page->size; fr->next = NULL; page->free.list = fr; @@ -347,90 +470,86 @@ void aral_del_page___no_lock_needed(ARAL *ar, ARAL_PAGE *page TRACE_ALLOCATIONS_ __atomic_sub_fetch(&ar->stats->structures.allocated_bytes, sizeof(ARAL_PAGE), __ATOMIC_RELAXED); } -static inline void aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) { - ARAL_PAGE *first = ar->aral_lock.pages; - - if (page->aral_lock.free_elements <= page->free_elements_to_move_first || - !first || - !first->aral_lock.free_elements || - page->aral_lock.free_elements <= first->aral_lock.free_elements + ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST) { - // first position - DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - } - else { - ARAL_PAGE *second = first->aral_lock.next; - - if (!second || - !second->aral_lock.free_elements || - page->aral_lock.free_elements <= second->aral_lock.free_elements) - // second position - DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, first, page, aral_lock.prev, aral_lock.next); - else - // third position - DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, second, page, aral_lock.prev, aral_lock.next); - } -} - -static inline ARAL_PAGE *aral_acquire_a_free_slot(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { - __atomic_add_fetch(&ar->atomic.allocators, 1, __ATOMIC_RELAXED); +static inline ARAL_PAGE *aral_get_first_page_with_a_free_slot(ARAL *ar, bool marked TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { + size_t idx = mark_to_idx(marked); + __atomic_add_fetch(&ar->ops[idx].atomic.allocators, 1, __ATOMIC_RELAXED); aral_lock(ar); - ARAL_PAGE *page = ar->aral_lock.pages; + ARAL_PAGE **head_ptr_free = aral_pages_head_free(ar, marked); + ARAL_PAGE *page = *head_ptr_free; + +#ifdef NETDATA_ARAL_INTERNAL_CHECKS + // bool added = false; + struct free_space f1, f2; +#endif while(!page || !page->aral_lock.free_elements) { + internal_fatal(page && page->aral_lock.next && page->aral_lock.next->aral_lock.free_elements, "hey!"); + #ifdef NETDATA_ARAL_INTERNAL_CHECKS - internal_fatal(find_page_with_free_slots_internal_check___with_aral_lock(ar), "ARAL: '%s' found page with free slot!", ar->config.name); + f1 = check_free_space___aral_lock_needed(ar, NULL, marked); #endif - aral_unlock(ar); - if(aral_adders_trylock(ar)) { - if(ar->adders.allocating_elements < __atomic_load_n(&ar->atomic.allocators, __ATOMIC_RELAXED)) { + size_t page_allocation_size = 0; + bool can_add = false; + if(aral_adders_trylock(ar, marked)) { + // we can add a page - let's see it is really needed + size_t threads_currently_allocating = __atomic_load_n(&ar->ops[idx].atomic.allocators, __ATOMIC_RELAXED); + size_t threads_currently_deallocating = __atomic_load_n(&ar->ops[idx].atomic.deallocators, __ATOMIC_RELAXED); + + // we will allocate a page, only if the number of elements required is more than the + // sum of all new allocations under their way plus the pages currently being deallocated + if(ar->ops[idx].adders.allocating_elements + threads_currently_deallocating < threads_currently_allocating) { + can_add = true; + page_allocation_size = aral_next_allocation_size___adders_lock_needed(ar, marked); + ar->ops[idx].adders.allocating_elements += page_allocation_size / ar->config.element_size; + } + aral_adders_unlock(ar, marked); + } + aral_unlock(ar); - size_t size = aral_next_allocation_size___adders_lock_needed(ar); - ar->adders.allocating_elements += size / ar->config.element_size; - aral_adders_unlock(ar); + if(can_add) { + page = aral_create_page___no_lock_needed(ar, page_allocation_size TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + page->marked = marked; - page = aral_create_page___no_lock_needed(ar, size TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + aral_lock(ar); - aral_lock(ar); - aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ar, page); + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(*head_ptr_free, page, aral_lock.prev, aral_lock.next); - aral_adders_lock(ar); - ar->adders.allocating_elements -= size / ar->config.element_size; - aral_adders_unlock(ar); +//#ifdef NETDATA_ARAL_INTERNAL_CHECKS +// added = true; +//#endif - // we have a page that is all empty - // and only aral_lock() is held, so - // break the loop - break; - } + aral_adders_lock(ar, marked); + ar->ops[idx].adders.allocating_elements -= page_allocation_size / ar->config.element_size; + aral_adders_unlock(ar, marked); - aral_adders_unlock(ar); + // we have a page that is all empty + // and only aral_lock() is held, so + // break the loop + break; } + else { + // let the adders/deallocators do it + // tinysleep(); + sched_yield(); - aral_lock(ar); - page = ar->aral_lock.pages; + aral_lock(ar); + page = *head_ptr_free; + } } - __atomic_sub_fetch(&ar->atomic.allocators, 1, __ATOMIC_RELAXED); - // we have a page // and aral locked - { - ARAL_PAGE *first = ar->aral_lock.pages; - ARAL_PAGE *second = first->aral_lock.next; + internal_fatal(marked && !page->marked, "ARAL: requested a marked page, but the page found is not marked"); - if (!second || - !second->aral_lock.free_elements || - first->aral_lock.free_elements <= second->aral_lock.free_elements + ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST) - page = first; - else { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, second, aral_lock.prev, aral_lock.next); - DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, second, aral_lock.prev, aral_lock.next); - page = second; - } - } +//#ifdef NETDATA_ARAL_INTERNAL_CHECKS +// if(added) { +// f2 = check_free_space___aral_lock_needed(ar, page, marked); +// internal_fatal(f2.failed, "hey!"); +// } +//#endif internal_fatal(!page || !page->aral_lock.free_elements, "ARAL: '%s' selected page does not have a free slot in it", @@ -451,31 +570,41 @@ static inline ARAL_PAGE *aral_acquire_a_free_slot(ARAL *ar TRACE_ALLOCATIONS_FUN // acquire a slot for the caller page->aral_lock.used_elements++; - if(--page->aral_lock.free_elements == 0) { - // we are done with this page - // move the full page last - // so that pages with free items remain first in the list - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); + page->aral_lock.free_elements--; + + if(marked) + page->aral_lock.marked_elements++; + + internal_fatal(page->aral_lock.marked_elements > page->aral_lock.used_elements, + "page has more marked elements than the used ones"); + + if(page->aral_lock.free_elements == 0) { + ARAL_PAGE **head_ptr_full = aral_pages_head_full(ar, marked); + internal_fatal(!is_page_in_list(*head_ptr_free, page), "Page is not in this list"); + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr_free, page, aral_lock.prev, aral_lock.next); + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(*head_ptr_full, page, aral_lock.prev, aral_lock.next); } + __atomic_sub_fetch(&ar->ops[idx].atomic.allocators, 1, __ATOMIC_RELAXED); aral_unlock(ar); return page; } -void *aral_callocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { - void *r = aral_mallocz_internal(ar TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); +void *aral_callocz_internal(ARAL *ar, bool marked TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { + void *r = aral_mallocz_internal(ar, marked TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); memset(r, 0, ar->config.requested_element_size); return r; } -void *aral_mallocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { -#ifdef FSANITIZE_ADDRESS +void *aral_mallocz_internal(ARAL *ar, bool marked TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { +#if defined(FSANITIZE_ADDRESS) return mallocz(ar->config.requested_element_size); #endif - ARAL_PAGE *page = aral_acquire_a_free_slot(ar TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + // reserve a slot on a free page + ARAL_PAGE *page = aral_get_first_page_with_a_free_slot(ar, marked TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + // the page returned has reserved a slot for us aral_page_free_lock(ar, page); @@ -499,6 +628,7 @@ void *aral_mallocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAM uint8_t *data = (uint8_t *)found_fr; ARAL_FREE *fr = (ARAL_FREE *)&data[ar->config.element_size]; + fr->size = found_fr->size - ar->config.element_size; // link the free slot first in the page @@ -511,9 +641,7 @@ void *aral_mallocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAM aral_page_free_unlock(ar, page); // put the page pointer after the element - uint8_t *data = (uint8_t *)found_fr; - ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->config.page_ptr_offset]; - *page_ptr = page; + aral_set_page_pointer_after_element___do_NOT_have_aral_lock(ar, page, found_fr, marked); if(unlikely(ar->config.mmap.enabled)) __atomic_add_fetch(&ar->stats->mmap.used_bytes, ar->config.element_size, __ATOMIC_RELAXED); @@ -523,121 +651,71 @@ void *aral_mallocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAM return (void *)found_fr; } -static inline ARAL_PAGE *aral_ptr_to_page___must_NOT_have_aral_lock(ARAL *ar, void *ptr) { - // given a data pointer we returned before, - // find the ARAL_PAGE it belongs to - - uint8_t *data = (uint8_t *)ptr; - ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->config.page_ptr_offset]; - ARAL_PAGE *page = *page_ptr; +// returns true if it moved the page to the unmarked list +static ARAL_PAGE **aral_remove_marked_allocation___aral_lock_needed(ARAL *ar, ARAL_PAGE **head_ptr, ARAL_PAGE *page) { + internal_fatal(!page->aral_lock.marked_elements, "marked elements refcount found zero"); + internal_fatal(!is_page_in_list(*head_ptr, page), "Page is not in this list"); -#ifdef NETDATA_INTERNAL_CHECKS - // make it NULL so that we will fail on double free - // do not enable this on production, because the MMAP file - // will need to be saved again! - *page_ptr = NULL; -#endif + page->aral_lock.marked_elements--; + if (!page->aral_lock.marked_elements && page->aral_lock.used_elements) { + internal_fatal(!page->marked, "The page should be marked at this point"); -#ifdef NETDATA_ARAL_INTERNAL_CHECKS - { - // find the page ptr belongs - ARAL_PAGE *page2 = find_page_with_allocation_internal_check(ar, ptr); + ARAL_PAGE **head_ptr_to = (page->aral_lock.free_elements) ? aral_pages_head_free(ar, false) : aral_pages_head_full(ar, false); + internal_fatal(!is_page_in_list(*head_ptr, page), "Page is not in this list"); - internal_fatal(page != page2, - "ARAL: '%s' page pointers do not match!", - ar->name); - - internal_fatal(!page2, - "ARAL: '%s' free of pointer %p is not in ARAL address space.", - ar->name, ptr); + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr, page, aral_lock.prev, aral_lock.next); + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(*head_ptr_to, page, aral_lock.prev, aral_lock.next); + page->marked = false; + return head_ptr_to; } -#endif - internal_fatal(!page, - "ARAL: '%s' possible corruption or double free of pointer %p", - ar->config.name, ptr); + internal_fatal(page->aral_lock.marked_elements > page->aral_lock.used_elements, + "page has more marked elements than the used ones"); - return page; + return head_ptr; } -static void aral_defrag_sorted_page_position___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) { - ARAL_PAGE *tmp; - - int action = 0; (void)action; - size_t move_later = 0, move_earlier = 0; - - for(tmp = page->aral_lock.next ; - tmp && tmp->aral_lock.free_elements && tmp->aral_lock.free_elements < page->aral_lock.free_elements ; - tmp = tmp->aral_lock.next) - move_later++; - - if(!tmp && page->aral_lock.next) { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - action = 1; - } - else if(tmp != page->aral_lock.next) { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - DOUBLE_LINKED_LIST_INSERT_ITEM_BEFORE_UNSAFE(ar->aral_lock.pages, tmp, page, aral_lock.prev, aral_lock.next); - action = 2; - } - else { - for(tmp = (page == ar->aral_lock.pages) ? NULL : page->aral_lock.prev ; - tmp && (!tmp->aral_lock.free_elements || tmp->aral_lock.free_elements > page->aral_lock.free_elements); - tmp = (tmp == ar->aral_lock.pages) ? NULL : tmp->aral_lock.prev) - move_earlier++; - - if(!tmp) { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - action = 3; - } - else if(tmp != page->aral_lock.prev){ - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, tmp, page, aral_lock.prev, aral_lock.next); - action = 4; - } - } - - ar->aral_lock.defragment_operations++; - ar->aral_lock.defragment_linked_list_traversals += move_earlier + move_later; +void aral_unmark_allocation(ARAL *ar, void *ptr) { + if(unlikely(!ptr)) return; - internal_fatal(page->aral_lock.next && page->aral_lock.next->aral_lock.free_elements && page->aral_lock.next->aral_lock.free_elements < page->aral_lock.free_elements, - "ARAL: '%s' item should be later in the list", ar->config.name); + // get the page pointer + bool marked; + ARAL_PAGE *page = aral_get_page_pointer_after_element___do_NOT_have_aral_lock(ar, ptr, &marked); - internal_fatal(page != ar->aral_lock.pages && (!page->aral_lock.prev->aral_lock.free_elements || page->aral_lock.prev->aral_lock.free_elements > page->aral_lock.free_elements), - "ARAL: '%s' item should be earlier in the list", ar->config.name); -} + internal_fatal(!page->marked, "This allocation does not belong to a marked page"); + internal_fatal(!marked, "This allocation does is not marked"); -static inline void aral_move_page_with_free_list___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) { - if(unlikely(page == ar->aral_lock.pages)) - // we are the first already - return; + if(marked) + aral_set_page_pointer_after_element___do_NOT_have_aral_lock(ar, page, ptr, false); - if(likely(!(ar->config.options & ARAL_DEFRAGMENT))) { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); - aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ar, page); + if(marked && page->marked) { + aral_lock(ar); + ARAL_PAGE **head_ptr = page->aral_lock.free_elements ? aral_pages_head_free(ar, page->marked) : aral_pages_head_full(ar, page->marked); + aral_remove_marked_allocation___aral_lock_needed(ar, head_ptr, page); + aral_unlock(ar); } - else - aral_defrag_sorted_page_position___aral_lock_needed(ar, page); } void aral_freez_internal(ARAL *ar, void *ptr TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { -#ifdef FSANITIZE_ADDRESS +#if defined(FSANITIZE_ADDRESS) freez(ptr); return; #endif if(unlikely(!ptr)) return; - // get the page pointer - ARAL_PAGE *page = aral_ptr_to_page___must_NOT_have_aral_lock(ar, ptr); - if(unlikely(ar->config.mmap.enabled)) __atomic_sub_fetch(&ar->stats->mmap.used_bytes, ar->config.element_size, __ATOMIC_RELAXED); else __atomic_sub_fetch(&ar->stats->malloc.used_bytes, ar->config.element_size, __ATOMIC_RELAXED); + // get the page pointer + bool marked; + ARAL_PAGE *page = aral_get_page_pointer_after_element___do_NOT_have_aral_lock(ar, ptr, &marked); + + size_t idx = mark_to_idx(marked); + __atomic_add_fetch(&ar->ops[idx].atomic.deallocators, 1, __ATOMIC_RELAXED); + // make this element available ARAL_FREE *fr = (ARAL_FREE *)ptr; fr->size = ar->config.element_size; @@ -662,37 +740,83 @@ void aral_freez_internal(ARAL *ar, void *ptr TRACE_ALLOCATIONS_FUNCTION_DEFINITI (size_t)page->max_elements, (size_t)page->aral_lock.used_elements, (size_t)page->aral_lock.free_elements, (size_t)page->aral_lock.used_elements + (size_t)page->aral_lock.free_elements - ); + ); + + ARAL_PAGE **head_ptr = page->aral_lock.free_elements ? aral_pages_head_free(ar, page->marked) : aral_pages_head_full(ar, page->marked); + internal_fatal(!is_page_in_list(*head_ptr, page), "Page is not in this list"); page->aral_lock.used_elements--; page->aral_lock.free_elements++; ar->aral_lock.user_free_operations++; + internal_fatal(marked && !page->marked, "ARAL: found a marked element on a non-marked page"); + + if(marked && page->marked) { + head_ptr = aral_remove_marked_allocation___aral_lock_needed(ar, head_ptr, page); + internal_fatal(!is_page_in_list(*head_ptr, page), "Page is not in this list"); + } + + internal_fatal(page->aral_lock.marked_elements > page->aral_lock.used_elements, + "page has more marked elements than the used ones"); + // if the page is empty, release it if(unlikely(!page->aral_lock.used_elements)) { - bool is_this_page_the_last_one = ar->aral_lock.pages == page && !page->aral_lock.next; + internal_fatal(page->aral_lock.marked_elements, "page has marked elements but not used ones"); - if(!is_this_page_the_last_one) - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); + bool is_this_page_the_last_one = *head_ptr == page && !page->aral_lock.next; + if(!is_this_page_the_last_one) { + internal_fatal(!is_page_in_list(*head_ptr, page), "Page is not in this list"); + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr, page, aral_lock.prev, aral_lock.next); + } + + __atomic_sub_fetch(&ar->ops[idx].atomic.deallocators, 1, __ATOMIC_RELAXED); aral_unlock(ar); if(!is_this_page_the_last_one) aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + + return; } - else { - aral_move_page_with_free_list___aral_lock_needed(ar, page); - aral_unlock(ar); + else if(page->aral_lock.free_elements) { + ARAL_PAGE **head_ptr_to = aral_pages_head_free(ar, page->marked); + if(head_ptr != head_ptr_to) { + internal_fatal(!is_page_in_list(*head_ptr, page), "Page is not in this list"); + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr, page, aral_lock.prev, aral_lock.next); + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(*head_ptr_to, page, aral_lock.prev, aral_lock.next); + } } + + __atomic_sub_fetch(&ar->ops[idx].atomic.deallocators, 1, __ATOMIC_RELAXED); + aral_unlock(ar); } void aral_destroy_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { aral_lock(ar); + ARAL_PAGE **head_ptr = aral_pages_head_free(ar, false); ARAL_PAGE *page; - while((page = ar->aral_lock.pages)) { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next); + while((page = *head_ptr)) { + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr, page, aral_lock.prev, aral_lock.next); + aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + } + + head_ptr = aral_pages_head_free(ar, true); + while((page = *head_ptr)) { + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr, page, aral_lock.prev, aral_lock.next); + aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + } + + head_ptr = aral_pages_head_full(ar, false); + while((page = *head_ptr)) { + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr, page, aral_lock.prev, aral_lock.next); + aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); + } + + head_ptr = aral_pages_head_full(ar, true); + while((page = *head_ptr)) { + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(*head_ptr, page, aral_lock.prev, aral_lock.next); aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); } @@ -704,14 +828,50 @@ void aral_destroy_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS freez(ar); } -size_t aral_element_size(ARAL *ar) { +size_t aral_requested_element_size(ARAL *ar) { return ar->config.requested_element_size; } +size_t aral_actual_element_size(ARAL *ar) { + return ar->config.element_size; +} + +static size_t aral_allocation_slot_size(size_t requested_element_size, bool usable) { + // we need to add a page pointer after the element + // so, first align the element size to the pointer size + size_t element_size = memory_alignment(requested_element_size, sizeof(uintptr_t)); + + // then add the size of a pointer to it + element_size += sizeof(uintptr_t); + + // make sure it is at least what we need for an ARAL_FREE slot + if (element_size < sizeof(ARAL_FREE)) + element_size = sizeof(ARAL_FREE); + + // and finally align it to the natural alignment + element_size = memory_alignment(element_size, SYSTEM_REQUIRED_ALIGNMENT); + + if(usable) + return element_size - sizeof(uintptr_t); + + return element_size; +} + +size_t aral_optimal_page_size(void) { + return ARAL_MAX_PAGE_SIZE_MALLOC; +} + +static void optimal_max_page_size(ARAL *ar) { + if(ar->config.requested_max_page_size) + return; + + ar->config.requested_max_page_size = aral_optimal_page_size(); +} + ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_elements, size_t max_page_size, struct aral_statistics *stats, const char *filename, const char **cache_dir, bool mmap, bool lockless) { ARAL *ar = callocz(1, sizeof(ARAL)); - ar->config.options = (lockless) ? ARAL_LOCKLESS : 0; + ar->config.options = ((lockless) ? ARAL_LOCKLESS : 0); ar->config.requested_element_size = element_size; ar->config.initial_page_elements = initial_page_elements; ar->config.requested_max_page_size = max_page_size; @@ -720,7 +880,8 @@ ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_ele ar->config.mmap.enabled = mmap; strncpyz(ar->config.name, name, ARAL_MAX_NAME); spinlock_init(&ar->aral_lock.spinlock); - spinlock_init(&ar->adders.spinlock); + spinlock_init(&ar->ops[0].adders.spinlock); + spinlock_init(&ar->ops[1].adders.spinlock); if(stats) { ar->stats = stats; @@ -733,23 +894,12 @@ ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_ele long int page_size = sysconf(_SC_PAGE_SIZE); if (unlikely(page_size == -1)) - ar->config.natural_page_size = 4096; + ar->config.system_page_size = 4096; else - ar->config.natural_page_size = page_size; + ar->config.system_page_size = page_size; - // we need to add a page pointer after the element - // so, first align the element size to the pointer size - ar->config.element_size = natural_alignment(ar->config.requested_element_size, sizeof(uintptr_t)); - - // then add the size of a pointer to it - ar->config.element_size += sizeof(uintptr_t); - - // make sure it is at least what we need for an ARAL_FREE slot - if (ar->config.element_size < sizeof(ARAL_FREE)) - ar->config.element_size = sizeof(ARAL_FREE); - - // and finally align it to the natural alignment - ar->config.element_size = natural_alignment(ar->config.element_size, ARAL_NATURAL_ALIGNMENT); + ar->config.element_size = aral_allocation_slot_size(ar->config.requested_element_size, false); + optimal_max_page_size(ar); ar->config.max_page_elements = ar->config.requested_max_page_size / ar->config.element_size; @@ -760,7 +910,8 @@ ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_ele fatal("ARAL: '%s' failed to calculate properly page_ptr_offset: " "element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, " "final element size %zu, page_ptr_offset %zu", - ar->config.name, ar->config.requested_element_size, sizeof(uintptr_t), ARAL_NATURAL_ALIGNMENT, + ar->config.name, ar->config.requested_element_size, sizeof(uintptr_t), + SYSTEM_REQUIRED_ALIGNMENT, ar->config.element_size, ar->config.page_ptr_offset); //netdata_log_info("ARAL: element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, final element size %zu, page_ptr_offset %zu", @@ -783,8 +934,11 @@ ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_ele max_alloc_size = ar->config.max_page_elements * ar->config.element_size; ar->config.max_allocation_size = aral_align_alloc_size(ar, max_alloc_size); - ar->adders.allocation_size = aral_align_alloc_size(ar, (uint64_t)ar->config.element_size * ar->config.initial_page_elements); - ar->aral_lock.pages = NULL; + ar->ops[0].adders.allocation_size = + ar->ops[1].adders.allocation_size = + aral_align_alloc_size(ar, (uint64_t)ar->config.element_size * ar->config.initial_page_elements); + ar->aral_lock.pages_free = NULL; + ar->aral_lock.pages_marked_free = NULL; ar->aral_lock.file_number = 0; if(ar->config.mmap.enabled) { @@ -808,7 +962,7 @@ ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_ele "max page size %zu bytes (requested %zu) " , ar->config.name , ar->config.element_size, ar->config.requested_element_size - , ar->adders.allocation_size / ar->config.element_size, ar->config.initial_page_elements + , ar->ops[0].adders.allocation_size / ar->config.element_size, ar->config.initial_page_elements , ar->config.max_allocation_size / ar->config.element_size , ar->config.max_allocation_size, ar->config.requested_max_page_size ); @@ -846,6 +1000,10 @@ size_t aral_by_size_overhead(void) { return aral_overhead_from_stats(&aral_by_size_globals.shared_statistics); } +size_t aral_by_size_used_bytes(void) { + return aral_used_bytes_from_stats(&aral_by_size_globals.shared_statistics); +} + ARAL *aral_by_size_acquire(size_t size) { spinlock_lock(&aral_by_size_globals.spinlock); @@ -855,8 +1013,9 @@ ARAL *aral_by_size_acquire(size_t size) { ar = aral_by_size_globals.array[size].ar; aral_by_size_globals.array[size].refcount++; - internal_fatal(aral_element_size(ar) != size, "DICTIONARY: aral has size %zu but we want %zu", - aral_element_size(ar), size); + internal_fatal( + aral_requested_element_size(ar) != size, "DICTIONARY: aral has size %zu but we want %zu", + aral_requested_element_size(ar), size); } if(!ar) { @@ -865,7 +1024,7 @@ ARAL *aral_by_size_acquire(size_t size) { ar = aral_create(buf, size, 0, - 65536 * ((size / 150) + 1), + 0, &aral_by_size_globals.shared_statistics, NULL, NULL, false, false); @@ -881,7 +1040,7 @@ ARAL *aral_by_size_acquire(size_t size) { } void aral_by_size_release(ARAL *ar) { - size_t size = aral_element_size(ar); + size_t size = aral_requested_element_size(ar); if(size <= ARAL_BY_SIZE_MAX_SIZE) { spinlock_lock(&aral_by_size_globals.spinlock); @@ -916,16 +1075,49 @@ struct aral_unittest_config { int errors; }; +struct aral_unittest_entry { + char TXT[27]; + char txt[27]; + char nnn[10]; +}; + +#define UNITTEST_ITEM (struct aral_unittest_entry){ \ + .TXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", \ + .txt = "abcdefghijklmnopqrstuvwxyz", \ + .nnn = "123456789", \ +} + +static inline struct aral_unittest_entry *unittest_aral_malloc(ARAL *ar, bool marked) { + struct aral_unittest_entry *t; + if(marked) + t = aral_mallocz_marked(ar); + else + t = aral_mallocz(ar); + + *t = UNITTEST_ITEM; + return t; +} + static void *aral_test_thread(void *ptr) { struct aral_unittest_config *auc = ptr; ARAL *ar = auc->ar; size_t elements = auc->elements; - void **pointers = callocz(elements, sizeof(void *)); + bool marked = os_random(2); + struct aral_unittest_entry **pointers = callocz(elements, sizeof(struct aral_unittest_entry *)); + size_t iterations = 0; do { + iterations++; + for (size_t i = 0; i < elements; i++) { - pointers[i] = aral_mallocz(ar); + pointers[i] = unittest_aral_malloc(ar, marked); + } + + if(marked) { + for (size_t i = 0; i < elements; i++) { + aral_unmark_allocation(ar, pointers[i]); + } } for (size_t div = 5; div >= 2; div--) { @@ -935,7 +1127,7 @@ static void *aral_test_thread(void *ptr) { } for (size_t i = 0; i < elements / div; i++) { - pointers[i] = aral_mallocz(ar); + pointers[i] = unittest_aral_malloc(ar, marked); } } @@ -946,7 +1138,7 @@ static void *aral_test_thread(void *ptr) { } for (size_t i = 0; i < elements; i += step) { - pointers[i] = aral_mallocz(ar); + pointers[i] = unittest_aral_malloc(ar, marked); } } @@ -955,7 +1147,7 @@ static void *aral_test_thread(void *ptr) { pointers[i] = NULL; } - if (auc->single_threaded && ar->aral_lock.pages && ar->aral_lock.pages->aral_lock.used_elements) { + if (auc->single_threaded && ar->aral_lock.pages_free && ar->aral_lock.pages_free->aral_lock.used_elements) { fprintf(stderr, "\n\nARAL leftovers detected (1)\n\n"); __atomic_add_fetch(&auc->errors, 1, __ATOMIC_RELAXED); } @@ -964,7 +1156,7 @@ static void *aral_test_thread(void *ptr) { break; for (size_t i = 0; i < elements; i++) { - pointers[i] = aral_mallocz(ar); + pointers[i] = unittest_aral_malloc(ar, marked); } size_t increment = elements / ar->config.max_page_elements; @@ -986,7 +1178,7 @@ static void *aral_test_thread(void *ptr) { for (size_t i = 0; i < to_free; i++) { size_t pos = free_list[i]; - pointers[pos] = aral_mallocz(ar); + pointers[pos] = unittest_aral_malloc(ar, marked); } } @@ -995,7 +1187,7 @@ static void *aral_test_thread(void *ptr) { pointers[i] = NULL; } - if (auc->single_threaded && ar->aral_lock.pages && ar->aral_lock.pages->aral_lock.used_elements) { + if (auc->single_threaded && ar->aral_lock.pages_free && ar->aral_lock.pages_free->aral_lock.used_elements) { fprintf(stderr, "\n\nARAL leftovers detected (2)\n\n"); __atomic_add_fetch(&auc->errors, 1, __ATOMIC_RELAXED); } @@ -1014,7 +1206,13 @@ int aral_stress_test(size_t threads, size_t elements, size_t seconds) { struct aral_unittest_config auc = { .single_threaded = false, .threads = threads, - .ar = aral_create("aral-stress-test", 20, 0, 8192, NULL, "aral-stress-test", NULL, false, false), + .ar = aral_create("aral-stress-test", + sizeof(struct aral_unittest_entry), + 0, + 16384, + NULL, + "aral-stress-test", + NULL, false, false), .elements = elements, .errors = 0, }; @@ -1061,7 +1259,7 @@ int aral_stress_test(size_t threads, size_t elements, size_t seconds) { usec_t ended_ut = now_monotonic_usec(); - if (auc.ar->aral_lock.pages && auc.ar->aral_lock.pages->aral_lock.used_elements) { + if (auc.ar->aral_lock.pages_free && auc.ar->aral_lock.pages_free->aral_lock.used_elements) { fprintf(stderr, "\n\nARAL leftovers detected (3)\n\n"); __atomic_add_fetch(&auc.errors, 1, __ATOMIC_RELAXED); } @@ -1084,7 +1282,15 @@ int aral_unittest(size_t elements) { struct aral_unittest_config auc = { .single_threaded = true, .threads = 1, - .ar = aral_create("aral-test", 20, 0, 8192, NULL, "aral-test", &cache_dir, false, false), + .ar = aral_create("aral-test", + sizeof(struct aral_unittest_entry), + 0, + 65536, + NULL, + "aral-test", + &cache_dir, + false, + false), .elements = elements, .errors = 0, }; diff --git a/src/libnetdata/aral/aral.h b/src/libnetdata/aral/aral.h index 4cd21d17afbeaf..474c160519acc6 100644 --- a/src/libnetdata/aral/aral.h +++ b/src/libnetdata/aral/aral.h @@ -10,26 +10,33 @@ typedef struct aral ARAL; struct aral_statistics { struct { - size_t allocations; - size_t allocated_bytes; + alignas(64) size_t allocations; + alignas(64) size_t allocated_bytes; } structures; struct { - size_t allocations; - size_t allocated_bytes; - size_t used_bytes; + alignas(64) size_t allocations; + alignas(64) size_t allocated_bytes; + alignas(64) size_t used_bytes; } malloc; struct { - size_t allocations; - size_t allocated_bytes; - size_t used_bytes; + alignas(64) size_t allocations; + alignas(64) size_t allocated_bytes; + alignas(64) size_t used_bytes; } mmap; }; ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_elements, size_t max_page_size, struct aral_statistics *stats, const char *filename, const char **cache_dir, bool mmap, bool lockless); -size_t aral_element_size(ARAL *ar); + +// return the size of the element, as requested +size_t aral_requested_element_size(ARAL *ar); + +// return the exact memory footprint of the elements +size_t aral_actual_element_size(ARAL *ar); + +const char *aral_name(ARAL *ar); size_t aral_overhead(ARAL *ar); size_t aral_structures(ARAL *ar); struct aral_statistics *aral_get_statistics(ARAL *ar); @@ -42,32 +49,44 @@ size_t aral_by_size_structures(void); size_t aral_by_size_overhead(void); struct aral_statistics *aral_by_size_statistics(void); +size_t aral_by_size_used_bytes(void); +size_t aral_used_bytes_from_stats(struct aral_statistics *stats); + +size_t aral_optimal_page_size(void); + int aral_unittest(size_t elements); #ifdef NETDATA_TRACE_ALLOCATIONS -#define aral_callocz(ar) aral_callocz_internal(ar, __FILE__, __FUNCTION__, __LINE__) -#define aral_mallocz(ar) aral_mallocz_internal(ar, __FILE__, __FUNCTION__, __LINE__) +#define aral_callocz(ar) aral_callocz_internal(ar, false, __FILE__, __FUNCTION__, __LINE__) +#define aral_callocz_marked(ar) aral_callocz_internal(ar, true, __FILE__, __FUNCTION__, __LINE__) +#define aral_mallocz(ar) aral_mallocz_internal(ar, false, __FILE__, __FUNCTION__, __LINE__) +#define aral_mallocz_marked(ar) aral_mallocz_internal(ar, true, __FILE__, __FUNCTION__, __LINE__) #define aral_freez(ar, ptr) aral_freez_internal(ar, ptr, __FILE__, __FUNCTION__, __LINE__) #define aral_destroy(ar) aral_destroy_internal(ar, __FILE__, __FUNCTION__, __LINE__) -void *aral_callocz_internal(ARAL *ar, const char *file, const char *function, size_t line); -void *aral_mallocz_internal(ARAL *ar, const char *file, const char *function, size_t line); +void *aral_callocz_internal(ARAL *ar, bool marked, const char *file, const char *function, size_t line); +void *aral_mallocz_internal(ARAL *ar, bool marked, const char *file, const char *function, size_t line); void aral_freez_internal(ARAL *ar, void *ptr, const char *file, const char *function, size_t line); void aral_destroy_internal(ARAL *ar, const char *file, const char *function, size_t line); #else // NETDATA_TRACE_ALLOCATIONS -#define aral_mallocz(ar) aral_mallocz_internal(ar) -#define aral_callocz(ar) aral_callocz_internal(ar) +#define aral_mallocz(ar) aral_mallocz_internal(ar, false) +#define aral_mallocz_marked(ar) aral_mallocz_internal(ar, true) +#define aral_callocz(ar) aral_callocz_internal(ar, false) +#define aral_callocz_marked(ar) aral_callocz_internal(ar, true) #define aral_freez(ar, ptr) aral_freez_internal(ar, ptr) #define aral_destroy(ar) aral_destroy_internal(ar) -void *aral_callocz_internal(ARAL *ar); -void *aral_mallocz_internal(ARAL *ar); + +void *aral_callocz_internal(ARAL *ar, bool marked); +void *aral_mallocz_internal(ARAL *ar, bool marked); void aral_freez_internal(ARAL *ar, void *ptr); void aral_destroy_internal(ARAL *ar); +void aral_unmark_allocation(ARAL *ar, void *ptr); + #endif // NETDATA_TRACE_ALLOCATIONS #endif // ARAL_H diff --git a/src/libnetdata/buffer/buffer.c b/src/libnetdata/buffer/buffer.c index 7194134b463a51..add1b950e5eec2 100644 --- a/src/libnetdata/buffer/buffer.c +++ b/src/libnetdata/buffer/buffer.c @@ -350,6 +350,20 @@ __attribute__((constructor)) void initialize_ascii_maps(void) { base64_value_from_ascii[(int)base64_digits[i]] = i; } +// ---------------------------------------------------------------------------- + +void buffer_json_member_add_datetime_rfc3339(BUFFER *wb, const char *key, uint64_t datetime_ut, bool utc) { + char buf[RFC3339_MAX_LENGTH]; + rfc3339_datetime_ut(buf, sizeof(buf), datetime_ut, 2, utc); + buffer_json_member_add_string(wb, key, buf); +} + +void buffer_json_member_add_duration_ut(BUFFER *wb, const char *key, int64_t duration_ut) { + char buf[64]; + duration_snprintf(buf, sizeof(buf), duration_ut, "us", true); + buffer_json_member_add_string(wb, key, buf); +} + // ---------------------------------------------------------------------------- // unit test diff --git a/src/libnetdata/buffer/buffer.h b/src/libnetdata/buffer/buffer.h index 78ee49d54bd802..90396296ba6084 100644 --- a/src/libnetdata/buffer/buffer.h +++ b/src/libnetdata/buffer/buffer.h @@ -800,6 +800,9 @@ static inline void buffer_json_member_add_string_or_empty(BUFFER *wb, const char buffer_json_member_add_string(wb, key, value); } +void buffer_json_member_add_datetime_rfc3339(BUFFER *wb, const char *key, uint64_t datetime_ut, bool utc); +void buffer_json_member_add_duration_ut(BUFFER *wb, const char *key, int64_t duration_ut); + static inline void buffer_json_member_add_quoted_string(BUFFER *wb, const char *key, const char *value) { buffer_print_json_comma_newline_spacing(wb); buffer_print_json_key(wb, key); diff --git a/src/libnetdata/buffered_reader/buffered_reader.h b/src/libnetdata/buffered_reader/buffered_reader.h index 505070b1c1e2d2..f244c8633717c3 100644 --- a/src/libnetdata/buffered_reader/buffered_reader.h +++ b/src/libnetdata/buffered_reader/buffered_reader.h @@ -115,22 +115,31 @@ static inline bool buffered_reader_next_line(struct buffered_reader *reader, BUF return false; } - // copy all bytes to buffer - while(ss < se && ds < de && *ss != '\n') { - *ds++ = *ss++; - dst->len++; + // Find out how many bytes we want to copy and whether we found a newline + size_t bytes_to_copy; + bool found_newline = false; + { + char *next_newline = (char *) memchr(ss, '\n', se - ss); + if (!next_newline) { + bytes_to_copy = se - ss; + } else { + bytes_to_copy = (next_newline - ss) + 1; + found_newline = true; + } } - // if we have a newline, return the buffer - if(ss < se && ds < de && *ss == '\n') { - // newline found in the r->read_buffer - - *ds++ = *ss++; // copy the newline too - dst->len++; + // Check we don't overflow the destination buffer + if (bytes_to_copy > (size_t)(de - ds)) { + bytes_to_copy = de - ds; + found_newline = false; + } - *ds = '\0'; + memcpy(ds, ss, bytes_to_copy); + ds[bytes_to_copy] = '\0'; + dst->len += bytes_to_copy; - reader->pos = ss - reader->read_buffer; + if (found_newline) { + reader->pos = start + bytes_to_copy; return true; } diff --git a/src/libnetdata/completion/completion.c b/src/libnetdata/completion/completion.c index 113423835a889e..23569b8451d0f9 100644 --- a/src/libnetdata/completion/completion.c +++ b/src/libnetdata/completion/completion.c @@ -26,16 +26,16 @@ void completion_wait_for(struct completion *p) uv_mutex_unlock(&p->mutex); } -bool completion_timedwait_for(struct completion *p, uint64_t timeout) +bool completion_timedwait_for(struct completion *p, uint64_t timeout_s) { - timeout *= NSEC_PER_SEC; + timeout_s *= NSEC_PER_SEC; uint64_t start_time = uv_hrtime(); bool result = true; uv_mutex_lock(&p->mutex); while (!p->completed) { - int rc = uv_cond_timedwait(&p->cond, &p->mutex, timeout); + int rc = uv_cond_timedwait(&p->cond, &p->mutex, timeout_s); if (rc == 0) { result = true; @@ -50,11 +50,11 @@ bool completion_timedwait_for(struct completion *p, uint64_t timeout) */ uint64_t elapsed = uv_hrtime() - start_time; - if (elapsed >= timeout) { + if (elapsed >= timeout_s) { result = false; break; } - timeout -= elapsed; + timeout_s -= elapsed; } uv_mutex_unlock(&p->mutex); @@ -81,6 +81,29 @@ unsigned completion_wait_for_a_job(struct completion *p, unsigned completed_jobs return completed_jobs; } +unsigned completion_wait_for_a_job_with_timeout(struct completion *p, unsigned completed_jobs, uint64_t timeout_ms) +{ + uint64_t timeout_ns = timeout_ms * NSEC_PER_MSEC; + if(!timeout_ns) timeout_ns = 1; + + uint64_t start_time_ns = uv_hrtime(); + + uv_mutex_lock(&p->mutex); + while (0 == p->completed && p->completed_jobs <= completed_jobs) { + int rc = uv_cond_timedwait(&p->cond, &p->mutex, timeout_ns); + if(rc == UV_ETIMEDOUT) + break; + + uint64_t elapsed = uv_hrtime() - start_time_ns; + if (elapsed >= timeout_ns) break; + timeout_ns -= elapsed; + } + completed_jobs = p->completed_jobs; + uv_mutex_unlock(&p->mutex); + + return completed_jobs; +} + void completion_mark_complete_a_job(struct completion *p) { uv_mutex_lock(&p->mutex); diff --git a/src/libnetdata/completion/completion.h b/src/libnetdata/completion/completion.h index 908ccfaf6419ff..32b561a3b05485 100644 --- a/src/libnetdata/completion/completion.h +++ b/src/libnetdata/completion/completion.h @@ -20,11 +20,12 @@ void completion_wait_for(struct completion *p); // Wait for at most `timeout` seconds. Return true on success, false on // error or timeout. -bool completion_timedwait_for(struct completion *p, uint64_t timeout); +bool completion_timedwait_for(struct completion *p, uint64_t timeout_s); void completion_mark_complete(struct completion *p); unsigned completion_wait_for_a_job(struct completion *p, unsigned completed_jobs); +unsigned completion_wait_for_a_job_with_timeout(struct completion *p, unsigned completed_jobs, uint64_t timeout_ms); void completion_mark_complete_a_job(struct completion *p); bool completion_is_done(struct completion *p); diff --git a/src/libnetdata/config/appconfig.c b/src/libnetdata/config/appconfig.c index f26417ac35d737..ae5008fd15b784 100644 --- a/src/libnetdata/config/appconfig.c +++ b/src/libnetdata/config/appconfig.c @@ -80,3 +80,13 @@ bool stream_conf_has_uuid_section(struct config *root) { return is_parent; } + +void appconfig_foreach_section(struct config *root, void (*cb)(struct config *root, const char *name, void *data), void *data) { + struct config_section *sect = NULL; + + APPCONFIG_LOCK(root); + for (sect = root->sections; sect; sect = sect->next) { + cb(root, string2str(sect->name), data); + } + APPCONFIG_UNLOCK(root); +} diff --git a/src/libnetdata/config/appconfig.h b/src/libnetdata/config/appconfig.h index f1551b387c148f..e4ae65e9436311 100644 --- a/src/libnetdata/config/appconfig.h +++ b/src/libnetdata/config/appconfig.h @@ -100,7 +100,7 @@ #define CONFIG_SECTION_PROMETHEUS "prometheus:exporter" #define CONFIG_SECTION_HOST_LABEL "host labels" #define EXPORTING_CONF "exporting.conf" -#define CONFIG_SECTION_GLOBAL_STATISTICS "global statistics" +#define CONFIG_SECTION_TELEMETRY "telemetry" #define CONFIG_SECTION_DB "db" // these are used to limit the configuration names and values lengths @@ -183,6 +183,8 @@ _CONNECTOR_INSTANCE *add_connector_instance(struct config_section *connector, st bool stream_conf_needs_dbengine(struct config *root); bool stream_conf_has_uuid_section(struct config *root); +void appconfig_foreach_section(struct config *root, void (*cb)(struct config *root, const char *name, void *data), void *data); + #include "appconfig_api_text.h" #include "appconfig_api_numbers.h" #include "appconfig_api_boolean.h" diff --git a/src/libnetdata/config/appconfig_api_sizes.c b/src/libnetdata/config/appconfig_api_sizes.c index 67b1dce9ef131a..80b0a25e426ab1 100644 --- a/src/libnetdata/config/appconfig_api_sizes.c +++ b/src/libnetdata/config/appconfig_api_sizes.c @@ -19,7 +19,7 @@ static STRING *reformat_size_bytes(STRING *value) { uint64_t appconfig_get_size_bytes(struct config *root, const char *section, const char *name, uint64_t default_value) { char default_str[128]; - size_snprintf_bytes(default_str, sizeof(default_str), (int)default_value); + size_snprintf_bytes(default_str, sizeof(default_str), default_value); struct config_option *opt = appconfig_get_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_SIZE_IN_BYTES, reformat_size_bytes); @@ -60,7 +60,7 @@ static STRING *reformat_size_mb(STRING *value) { uint64_t appconfig_get_size_mb(struct config *root, const char *section, const char *name, uint64_t default_value) { char default_str[128]; - size_snprintf_mb(default_str, sizeof(default_str), (int)default_value); + size_snprintf_mb(default_str, sizeof(default_str), default_value); struct config_option *opt = appconfig_get_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_SIZE_IN_MB, reformat_size_mb); diff --git a/src/libnetdata/config/appconfig_conf_file.c b/src/libnetdata/config/appconfig_conf_file.c index 4ac8b376e4c530..1a1149853cf49d 100644 --- a/src/libnetdata/config/appconfig_conf_file.c +++ b/src/libnetdata/config/appconfig_conf_file.c @@ -227,7 +227,7 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed, bool else if(!string_strcmp(sect->name, CONFIG_SECTION_WEBRTC)) pri = 11; // by default, new sections will get pri = 12 (set at the end, below) else if(!string_strcmp(sect->name, CONFIG_SECTION_REGISTRY)) pri = 13; - else if(!string_strcmp(sect->name, CONFIG_SECTION_GLOBAL_STATISTICS)) pri = 14; + else if(!string_strcmp(sect->name, CONFIG_SECTION_TELEMETRY)) pri = 14; else if(!string_strcmp(sect->name, CONFIG_SECTION_PLUGINS)) pri = 15; else if(!string_strcmp(sect->name, CONFIG_SECTION_STATSD)) pri = 16; else if(!string_strncmp(sect->name, "plugin:", 7)) pri = 17; // << change the loop too if you change this diff --git a/src/libnetdata/dictionary/dictionary-item.h b/src/libnetdata/dictionary/dictionary-item.h index f7c6e47a7dd541..6f78c6e40cc40f 100644 --- a/src/libnetdata/dictionary/dictionary-item.h +++ b/src/libnetdata/dictionary/dictionary-item.h @@ -84,9 +84,9 @@ static inline DICTIONARY_ITEM *dict_item_create(DICTIONARY *dict __maybe_unused, static inline void *dict_item_value_mallocz(DICTIONARY *dict, size_t value_len) { if(dict->value_aral) { - internal_fatal(aral_element_size(dict->value_aral) != value_len, + internal_fatal(aral_requested_element_size(dict->value_aral) != value_len, "DICTIONARY: item value size %zu does not match the configured fixed one %zu", - value_len, aral_element_size(dict->value_aral)); + value_len, aral_requested_element_size(dict->value_aral)); return aral_mallocz(dict->value_aral); } else diff --git a/src/libnetdata/dictionary/dictionary.c b/src/libnetdata/dictionary/dictionary.c index ebe67269a33e84..76146e1fc6114a 100644 --- a/src/libnetdata/dictionary/dictionary.c +++ b/src/libnetdata/dictionary/dictionary.c @@ -204,25 +204,11 @@ void dictionary_static_items_aral_init(void) { if(unlikely(!dict_items_aral || !dict_shared_items_aral)) { spinlock_lock(&spinlock); - // we have to check again if(!dict_items_aral) - dict_items_aral = aral_create( - "dict-items", - sizeof(DICTIONARY_ITEM), - 0, - 65536, - aral_by_size_statistics(), - NULL, NULL, false, false); - - // we have to check again + dict_items_aral = aral_by_size_acquire(sizeof(DICTIONARY_ITEM)); + if(!dict_shared_items_aral) - dict_shared_items_aral = aral_create( - "dict-shared-items", - sizeof(DICTIONARY_ITEM_SHARED), - 0, - 65536, - aral_by_size_statistics(), - NULL, NULL, false, false); + dict_shared_items_aral = aral_by_size_acquire(sizeof(DICTIONARY_ITEM_SHARED)); spinlock_unlock(&spinlock); } @@ -540,7 +526,7 @@ DICTIONARY *dictionary_create_view(DICTIONARY *master) { #endif DICTIONARY *dict = dictionary_create_internal(master->options, master->stats, - master->value_aral ? aral_element_size(master->value_aral) : 0); + master->value_aral ? aral_requested_element_size(master->value_aral) : 0); dict->master = master; diff --git a/src/libnetdata/dictionary/dictionary.h b/src/libnetdata/dictionary/dictionary.h index 3d041018dc9bde..51acaa2e841bcc 100644 --- a/src/libnetdata/dictionary/dictionary.h +++ b/src/libnetdata/dictionary/dictionary.h @@ -98,9 +98,9 @@ struct dictionary_stats { // memory struct { - long index; // bytes of keys indexed (indication of the index size) - long values; // bytes of caller structures - long dict; // bytes of the structures dictionary needs + ssize_t index; // bytes of keys indexed (indication of the index size) + ssize_t values; // bytes of caller structures + ssize_t dict; // bytes of the structures dictionary needs } memory; // spin locks diff --git a/src/libnetdata/functions_evloop/functions_evloop.h b/src/libnetdata/functions_evloop/functions_evloop.h index 35defe355e5a2d..ca1e3655e8ebca 100644 --- a/src/libnetdata/functions_evloop/functions_evloop.h +++ b/src/libnetdata/functions_evloop/functions_evloop.h @@ -77,7 +77,8 @@ #define PLUGINSD_KEYWORD_JSON "JSON" #define PLUGINSD_KEYWORD_JSON_END "JSON_PAYLOAD_END" -#define PLUGINSD_KEYWORD_STREAM_PATH "STREAM_PATH" +#define PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH "STREAM_PATH" +#define PLUGINSD_KEYWORD_JSON_CMD_ML_MODEL "ML_MODEL" typedef void (*functions_evloop_worker_execute_t)(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, HTTP_ACCESS access, diff --git a/src/libnetdata/gorilla/gorilla.cc b/src/libnetdata/gorilla/gorilla.cc index e3d6124181df13..608247bc5833de 100644 --- a/src/libnetdata/gorilla/gorilla.cc +++ b/src/libnetdata/gorilla/gorilla.cc @@ -17,6 +17,17 @@ static constexpr size_t bit_size() noexcept return (sizeof(T) * CHAR_BIT); } +static uint32_t gorilla_buffer_nbytes(uint32_t nbits) { + uint32_t slots = (nbits + RRDENG_GORILLA_32BIT_SLOT_BITS - 1) / RRDENG_GORILLA_32BIT_SLOT_BITS; + assert(slots > 0 && slots <= RRDENG_GORILLA_32BIT_BUFFER_SLOTS); + + // this is needed to avoid heap buffer overflow in bit_buffer_read() + if(slots < RRDENG_GORILLA_32BIT_BUFFER_SLOTS) + slots++; + + return slots * RRDENG_GORILLA_32BIT_SLOT_BYTES; +} + static void bit_buffer_write(uint32_t *buf, size_t pos, uint32_t v, size_t nbits) { assert(nbits > 0 && nbits <= bit_size()); @@ -191,20 +202,39 @@ gorilla_buffer_t *gorilla_writer_drop_head_buffer(gorilla_writer_t *gw) { return curr_head; } -uint32_t gorilla_writer_nbytes(const gorilla_writer_t *gw) +uint32_t gorilla_writer_actual_nbytes(const gorilla_writer_t *gw) +{ + uint32_t nbytes = 0; + + const gorilla_buffer_t *curr_gbuf = __atomic_load_n(&gw->head_buffer, __ATOMIC_SEQ_CST); + do { + const gorilla_buffer_t *next_gbuf = __atomic_load_n(&curr_gbuf->header.next, __ATOMIC_SEQ_CST); + + nbytes += RRDENG_GORILLA_32BIT_BUFFER_SIZE; + + curr_gbuf = next_gbuf; + } while (curr_gbuf); + + return nbytes; +} + +uint32_t gorilla_writer_optimal_nbytes(const gorilla_writer_t *gw) { - uint32_t nbits = 0; + uint32_t nbytes = 0; const gorilla_buffer_t *curr_gbuf = __atomic_load_n(&gw->head_buffer, __ATOMIC_SEQ_CST); do { const gorilla_buffer_t *next_gbuf = __atomic_load_n(&curr_gbuf->header.next, __ATOMIC_SEQ_CST); - nbits += __atomic_load_n(&curr_gbuf->header.nbits, __ATOMIC_SEQ_CST); + if(next_gbuf) + nbytes += RRDENG_GORILLA_32BIT_BUFFER_SIZE; + else + nbytes += gorilla_buffer_nbytes(__atomic_load_n(&curr_gbuf->header.nbits, __ATOMIC_SEQ_CST)); curr_gbuf = next_gbuf; } while (curr_gbuf); - return (nbits + (CHAR_BIT - 1)) / CHAR_BIT; + return nbytes; } bool gorilla_writer_serialize(const gorilla_writer_t *gw, uint8_t *dst, uint32_t dst_size) { @@ -247,6 +277,39 @@ uint32_t gorilla_buffer_patch(gorilla_buffer_t *gbuf) { return n; } +size_t gorilla_buffer_unpatched_nbuffers(const gorilla_buffer_t *gbuf) { + size_t nbuffers = 0; + while(gbuf) { + nbuffers++; + + if(gbuf->header.next) { + const auto *buf = reinterpret_cast(gbuf); + gbuf = reinterpret_cast(&buf[RRDENG_GORILLA_32BIT_BUFFER_SLOTS]); + } + else + break; + } + + return nbuffers; +} + +size_t gorilla_buffer_unpatched_nbytes(const gorilla_buffer_t *gbuf) { + size_t nbytes = sizeof(gorilla_buffer_t); + while(gbuf) { + if(gbuf->header.next) { + nbytes += RRDENG_GORILLA_32BIT_BUFFER_SIZE; + const auto *buf = reinterpret_cast(gbuf); + gbuf = reinterpret_cast(&buf[RRDENG_GORILLA_32BIT_BUFFER_SLOTS]); + } + else { + nbytes += gorilla_buffer_nbytes(gbuf->header.nbits); + break; + } + } + + return nbytes; +} + gorilla_reader_t gorilla_writer_get_reader(const gorilla_writer_t *gw) { const gorilla_buffer_t *buffer = __atomic_load_n(&gw->head_buffer, __ATOMIC_SEQ_CST); @@ -358,6 +421,24 @@ bool gorilla_reader_read(gorilla_reader_t *gr, uint32_t *number) return true; } +extern "C" { +struct aral; +void aral_unmark_allocation(struct aral *ar, void *ptr); +} + +void gorilla_writer_aral_unmark(const gorilla_writer_t *gw, struct aral *ar) +{ + const gorilla_buffer_t *curr_gbuf = __atomic_load_n(&gw->head_buffer, __ATOMIC_SEQ_CST); + do { + const gorilla_buffer_t *next_gbuf = __atomic_load_n(&curr_gbuf->header.next, __ATOMIC_SEQ_CST); + + // Call the C function here + aral_unmark_allocation(ar, const_cast(static_cast(curr_gbuf))); + + curr_gbuf = next_gbuf; + } while (curr_gbuf); +} + /* * Internal code used for fuzzing the library */ diff --git a/src/libnetdata/gorilla/gorilla.h b/src/libnetdata/gorilla/gorilla.h index 7975d85ee23957..aa4ea029aaf490 100644 --- a/src/libnetdata/gorilla/gorilla.h +++ b/src/libnetdata/gorilla/gorilla.h @@ -56,19 +56,27 @@ void gorilla_writer_add_buffer(gorilla_writer_t *gw, gorilla_buffer_t *gbuf, siz bool gorilla_writer_write(gorilla_writer_t *gw, uint32_t number); uint32_t gorilla_writer_entries(const gorilla_writer_t *gw); +struct aral; +void gorilla_writer_aral_unmark(const gorilla_writer_t *gw, struct aral *ar); + gorilla_reader_t gorilla_writer_get_reader(const gorilla_writer_t *gw); gorilla_buffer_t *gorilla_writer_drop_head_buffer(gorilla_writer_t *gw); -uint32_t gorilla_writer_nbytes(const gorilla_writer_t *gw); +uint32_t gorilla_writer_actual_nbytes(const gorilla_writer_t *gw); +uint32_t gorilla_writer_optimal_nbytes(const gorilla_writer_t *gw); bool gorilla_writer_serialize(const gorilla_writer_t *gw, uint8_t *dst, uint32_t dst_size); uint32_t gorilla_buffer_patch(gorilla_buffer_t *buf); +size_t gorilla_buffer_unpatched_nbuffers(const gorilla_buffer_t *gbuf); +size_t gorilla_buffer_unpatched_nbytes(const gorilla_buffer_t *gbuf); gorilla_reader_t gorilla_reader_init(gorilla_buffer_t *buf); bool gorilla_reader_read(gorilla_reader_t *gr, uint32_t *number); +#define RRDENG_GORILLA_32BIT_SLOT_BYTES sizeof(uint32_t) +#define RRDENG_GORILLA_32BIT_SLOT_BITS (RRDENG_GORILLA_32BIT_SLOT_BYTES * CHAR_BIT) #define RRDENG_GORILLA_32BIT_BUFFER_SLOTS 128 -#define RRDENG_GORILLA_32BIT_BUFFER_SIZE (RRDENG_GORILLA_32BIT_BUFFER_SLOTS * sizeof(uint32_t)) +#define RRDENG_GORILLA_32BIT_BUFFER_SIZE (RRDENG_GORILLA_32BIT_BUFFER_SLOTS * RRDENG_GORILLA_32BIT_SLOT_BYTES) #ifdef __cplusplus } diff --git a/src/libnetdata/json/json.c b/src/libnetdata/json/json.c index a50f6b542c4cc8..c46b8c47a6d7f4 100644 --- a/src/libnetdata/json/json.c +++ b/src/libnetdata/json/json.c @@ -1,8 +1,9 @@ -#include "jsmn.h" -#include "../libnetdata.h" -#include "json.h" #include "libnetdata/libnetdata.h" -#include "health/health.h" +#include "json.h" + +#ifndef ENABLE_JSONC +#include "jsmn.h" +#endif #define JSON_TOKENS 1024 @@ -450,7 +451,7 @@ size_t json_walk(json_object *t, void *callback_data, int (*callback_function)(s type = json_object_get_type(val); if (type == json_type_array) { e.type = JSON_ARRAY; - json_jsonc_parse_array(val,NULL,health_silencers_json_read_callback); + json_jsonc_parse_array(val,NULL,callback_function); } else if (type == json_type_object) { e.type = JSON_OBJECT; } else if (type == json_type_string) { diff --git a/src/libnetdata/libjudy/judy-malloc.c b/src/libnetdata/libjudy/judy-malloc.c index ec736393dbd509..477da633b378f2 100644 --- a/src/libnetdata/libjudy/judy-malloc.c +++ b/src/libnetdata/libjudy/judy-malloc.c @@ -28,7 +28,7 @@ __attribute__((constructor)) void aral_judy_init(void) { buf, Words * sizeof(Word_t), 0, - 65536, + 0, &judy_sizes_aral_statistics, NULL, NULL, false, false); } @@ -49,6 +49,18 @@ static ARAL *judy_size_aral(Word_t Words) { return NULL; } +static __thread int64_t judy_allocated = 0; + +void JudyAllocThreadTelemetryReset(void) { + judy_allocated = 0; +} + +int64_t JudyAllocThreadTelemetryGetAndReset(void) { + int64_t rc = judy_allocated; + judy_allocated = 0; + return rc; +} + inline Word_t JudyMalloc(Word_t Words) { Word_t Addr; @@ -58,6 +70,8 @@ inline Word_t JudyMalloc(Word_t Words) { else Addr = (Word_t) mallocz(Words * sizeof(Word_t)); + judy_allocated += Words * sizeof(Word_t); + return(Addr); } @@ -67,6 +81,8 @@ inline void JudyFree(void * PWord, Word_t Words) { aral_freez(ar, PWord); else freez(PWord); + + judy_allocated -= Words * sizeof(Word_t); } Word_t JudyMallocVirtual(Word_t Words) { diff --git a/src/libnetdata/libjudy/judy-malloc.h b/src/libnetdata/libjudy/judy-malloc.h index 65cba982b0c4ec..23e0a55fd7f15e 100644 --- a/src/libnetdata/libjudy/judy-malloc.h +++ b/src/libnetdata/libjudy/judy-malloc.h @@ -8,4 +8,7 @@ size_t judy_aral_overhead(void); size_t judy_aral_structures(void); +void JudyAllocThreadTelemetryReset(void); +int64_t JudyAllocThreadTelemetryGetAndReset(void); + #endif //NETDATA_JUDY_MALLOC_H diff --git a/src/libnetdata/libjudy/judyl-typed.h b/src/libnetdata/libjudy/judyl-typed.h new file mode 100644 index 00000000000000..97cb0deb96079f --- /dev/null +++ b/src/libnetdata/libjudy/judyl-typed.h @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_JUDYL_TYPED_H +#define NETDATA_JUDYL_TYPED_H + +#include + +#define DEFINE_JUDYL_TYPED(NAME, TYPE) \ + _Static_assert(sizeof(TYPE) == sizeof(Word_t), \ + #NAME "_type_must_have_same_size_as_Word_t"); \ + typedef struct { \ + Pvoid_t judyl; \ + } NAME##_JudyLSet; \ + \ + static inline void NAME##_INIT(NAME##_JudyLSet *set) { \ + set->judyl = NULL; \ + } \ + \ + static inline bool NAME##_SET(NAME##_JudyLSet *set, Word_t index, TYPE value) { \ + Pvoid_t *pValue = JudyLIns(&set->judyl, index, PJE0); \ + if (pValue == PJERR) return false; \ + *pValue = (void *)(uintptr_t)value; \ + return true; \ + } \ + \ + static inline TYPE NAME##_GET(NAME##_JudyLSet *set, Word_t index) { \ + Pvoid_t *pValue = JudyLGet(set->judyl, index, PJE0); \ + return (pValue != NULL) ? (TYPE)(uintptr_t)(*pValue) : (TYPE)0; \ + } \ + \ + static inline TYPE *NAME##_GETPTR(NAME##_JudyLSet *set, Word_t index) { \ + Pvoid_t *pValue = JudyLGet(set->judyl, index, PJE0); \ + return (TYPE *)pValue; \ + } \ + \ + static inline bool NAME##_DEL(NAME##_JudyLSet *set, Word_t index) { \ + int Rc; \ + PPvoid_t ppJudy = &set->judyl; \ + Rc = JudyLDel(ppJudy, index, PJE0); \ + return Rc == 1; \ + } \ + \ + static inline TYPE NAME##_FIRST(NAME##_JudyLSet *set, Word_t *index) { \ + Pvoid_t *pValue = JudyLFirst(set->judyl, index, PJE0); \ + return (pValue != NULL) ? (TYPE)(uintptr_t)(*pValue) : (TYPE)0; \ + } \ + \ + static inline TYPE NAME##_NEXT(NAME##_JudyLSet *set, Word_t *index) { \ + Pvoid_t *pValue = JudyLNext(set->judyl, index, PJE0); \ + return (pValue != NULL) ? (TYPE)(uintptr_t)(*pValue) : (TYPE)0; \ + } \ + \ + static inline TYPE NAME##_LAST(NAME##_JudyLSet *set, Word_t *index) { \ + Pvoid_t *pValue = JudyLLast(set->judyl, index, PJE0); \ + return (pValue != NULL) ? (TYPE)(uintptr_t)(*pValue) : (TYPE)0; \ + } \ + \ + static inline TYPE NAME##_PREV(NAME##_JudyLSet *set, Word_t *index) { \ + Pvoid_t *pValue = JudyLPrev(set->judyl, index, PJE0); \ + return (pValue != NULL) ? (TYPE)(uintptr_t)(*pValue) : (TYPE)0; \ + } \ + \ + static inline void NAME##_FREE(NAME##_JudyLSet *set, void (*callback)(TYPE)) { \ + Word_t index = 0; \ + Pvoid_t *pValue; \ + if (callback) { \ + for (pValue = JudyLFirst(set->judyl, &index, PJE0); \ + pValue != NULL; \ + pValue = JudyLNext(set->judyl, &index, PJE0)) { \ + callback((TYPE)(uintptr_t)(*pValue)); \ + } \ + } \ + JudyLFreeArray(&set->judyl, PJE0); \ + } + + + +#endif //NETDATA_JUDYL_TYPED_H diff --git a/src/libnetdata/libnetdata.c b/src/libnetdata/libnetdata.c index e21bf119dcb0eb..043bc2febd40aa 100644 --- a/src/libnetdata/libnetdata.c +++ b/src/libnetdata/libnetdata.c @@ -446,9 +446,26 @@ void *reallocz(void *ptr, size_t size) { void posix_memfree(void *ptr) { free(ptr); } +#endif + +void mallocz_release_as_much_memory_to_the_system(void) { +#if defined(HAVE_C_MALLOPT) || defined(HAVE_C_MALLOC_TRIM) + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; + spinlock_lock(&spinlock); + +#ifdef HAVE_C_MALLOPT + size_t trim_threshold = aral_optimal_page_size(); + mallopt(M_TRIM_THRESHOLD, (int)trim_threshold); +#endif +#ifdef HAVE_C_MALLOC_TRIM + malloc_trim(0); #endif + spinlock_unlock(&spinlock); +#endif +} + // -------------------------------------------------------------------------------------------------------------------- void json_escape_string(char *dst, const char *src, size_t size) { @@ -848,6 +865,28 @@ struct timing_steps { [TIMING_STEP_END2_PROPAGATE] = { .name = "END2 propagate", .time = 0, }, [TIMING_STEP_END2_STORE] = { .name = "END2 store", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_LOCK] = { .name = "EVC_LOCK", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_SELECT] = { .name = "EVC_SELECT", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_SELECT_PAGE ] = { .name = "EVT_SELECT_PAGE", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_RELOCATE_PAGE ] = { .name = "EVT_RELOCATE_PAGE", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_SORT] = { .name = "EVC_SORT", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_DEINDEX] = { .name = "EVC_DEINDEX", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_DEINDEX_PAGE] = { .name = "EVC_DEINDEX_PAGE", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FINISHED] = { .name = "EVC_FINISHED", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_LOOP] = { .name = "EVC_FREE_LOOP", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_PAGE] = { .name = "EVC_FREE_PAGE", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_ATOMICS] = { .name = "EVC_FREE_ATOMICS", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_CB] = { .name = "EVC_FREE_CB", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_ATOMICS2] = { .name = "EVC_FREE_ATOMICS2", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_ARAL] = { .name = "EVC_FREE_ARAL", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_DATA] = { .name = "EVC_FREE_PGD_DATA", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_ARAL] = { .name = "EVC_FREE_PGD_ARAL", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_TIER1_ARAL] = { .name = "EVC_FREE_MAIN_T1ARL", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_GLIVE] = { .name = "EVC_FREE_MAIN_GLIVE", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_GWORKER] = { .name = "EVC_FREE_MAIN_GWORK", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_OPEN] = { .name = "EVC_FREE_OPEN", .time = 0, }, + [TIMING_STEP_DBENGINE_EVICT_FREE_EXTENT] = { .name = "EVC_FREE_EXTENT", .time = 0, }, + // terminator [TIMING_STEP_MAX] = { .name = NULL, .time = 0, }, }; diff --git a/src/libnetdata/libnetdata.h b/src/libnetdata/libnetdata.h index acee0675f4f5e3..02ae57126a9a22 100644 --- a/src/libnetdata/libnetdata.h +++ b/src/libnetdata/libnetdata.h @@ -61,6 +61,7 @@ void *reallocz(void *ptr, size_t size) MALLOCLIKE NEVERNULL; void freez(void *ptr); #endif // NETDATA_TRACE_ALLOCATIONS +void mallocz_release_as_much_memory_to_the_system(void); void posix_memfree(void *ptr); void json_escape_string(char *dst, const char *src, size_t size); @@ -86,8 +87,6 @@ char *find_and_replace(const char *src, const char *find, const char *replace, c #define BITS_IN_A_KILOBIT 1000 #define KILOBITS_IN_A_MEGABIT 1000 -#define error_report(x, args...) do { errno_clear(); netdata_log_error(x, ##args); } while(0) - #include "bitmap/bitmap64.h" #define COMPRESSION_MAX_CHUNK 0x4000 @@ -109,6 +108,7 @@ extern const char *netdata_configured_host_prefix; // safe includes before O/S specific functions #include "template-enum.h" #include "libjudy/src/Judy.h" +#include "libjudy/judyl-typed.h" #include "july/july.h" #include "string/string.h" @@ -134,6 +134,12 @@ extern const char *netdata_configured_host_prefix; #include "os/os.h" #include "socket/socket.h" +#include "socket/nd-sock.h" +#include "socket/nd-poll.h" +#include "socket/listen-sockets.h" +#include "socket/poll-events.h" +#include "socket/connect-to.h" +#include "socket/socket-peers.h" #include "avl/avl.h" #include "line_splitter/line_splitter.h" @@ -290,6 +296,28 @@ typedef enum { TIMING_STEP_FREEIPMI_READ_event_offset_string, TIMING_STEP_FREEIPMI_READ_manufacturer_id, + TIMING_STEP_DBENGINE_EVICT_LOCK, + TIMING_STEP_DBENGINE_EVICT_SELECT, + TIMING_STEP_DBENGINE_EVICT_SELECT_PAGE, + TIMING_STEP_DBENGINE_EVICT_RELOCATE_PAGE, + TIMING_STEP_DBENGINE_EVICT_SORT, + TIMING_STEP_DBENGINE_EVICT_DEINDEX, + TIMING_STEP_DBENGINE_EVICT_DEINDEX_PAGE, + TIMING_STEP_DBENGINE_EVICT_FINISHED, + TIMING_STEP_DBENGINE_EVICT_FREE_LOOP, + TIMING_STEP_DBENGINE_EVICT_FREE_PAGE, + TIMING_STEP_DBENGINE_EVICT_FREE_ATOMICS, + TIMING_STEP_DBENGINE_EVICT_FREE_CB, + TIMING_STEP_DBENGINE_EVICT_FREE_ATOMICS2, + TIMING_STEP_DBENGINE_EVICT_FREE_ARAL, + TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_DATA, + TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_ARAL, + TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_TIER1_ARAL, + TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_GLIVE, + TIMING_STEP_DBENGINE_EVICT_FREE_MAIN_PGD_GWORKER, + TIMING_STEP_DBENGINE_EVICT_FREE_OPEN, + TIMING_STEP_DBENGINE_EVICT_FREE_EXTENT, + // terminator TIMING_STEP_MAX, } TIMING_STEP; @@ -304,11 +332,21 @@ typedef enum { #define timing_init() timing_action(TIMING_ACTION_INIT, TIMING_STEP_INTERNAL) #define timing_step(step) timing_action(TIMING_ACTION_STEP, step) #define timing_report() timing_action(TIMING_ACTION_FINISH, TIMING_STEP_INTERNAL) + +#define timing_dbengine_evict_init() timing_action(TIMING_ACTION_INIT, TIMING_STEP_INTERNAL) +#define timing_dbengine_evict_step(step) timing_action(TIMING_ACTION_STEP, step) +#define timing_dbengine_evict_report() timing_action(TIMING_ACTION_FINISH, TIMING_STEP_INTERNAL) #else #define timing_init() debug_dummy() #define timing_step(step) debug_dummy() #define timing_report() debug_dummy() + +#define timing_dbengine_evict_init() debug_dummy() +#define timing_dbengine_evict_step(step) debug_dummy() +#define timing_dbengine_evict_report() debug_dummy() #endif + + void timing_action(TIMING_ACTION action, TIMING_STEP step); int hash256_string(const unsigned char *string, size_t size, char *hash); diff --git a/src/libnetdata/local-sockets/local-sockets.h b/src/libnetdata/local-sockets/local-sockets.h index 06ac08767c82f3..95529735e01adc 100644 --- a/src/libnetdata/local-sockets/local-sockets.h +++ b/src/libnetdata/local-sockets/local-sockets.h @@ -1188,14 +1188,14 @@ static inline void local_sockets_init(LS_STATE *ls) { ls->local_socket_aral = aral_create( "local-sockets", sizeof(LOCAL_SOCKET), - 65536, + 65536 / sizeof(LOCAL_SOCKET), 65536, NULL, NULL, NULL, false, true); ls->pid_socket_aral = aral_create( "pid-sockets", sizeof(struct pid_socket), - 65536, + 65536 / sizeof(struct pid_socket), 65536, NULL, NULL, NULL, false, true); diff --git a/src/libnetdata/locks/locks.c b/src/libnetdata/locks/locks.c index 424b86ce9526f5..9df1942c90fb7d 100644 --- a/src/libnetdata/locks/locks.c +++ b/src/libnetdata/locks/locks.c @@ -253,9 +253,13 @@ static inline void spinlock_lock_internal(SPINLOCK *spinlock) spins++; #endif - if(unlikely(i == 8)) { - i = 0; - tinysleep(); + if(unlikely(i % 8 == 0)) { + if(i == 8 * 4) { + i = 0; + yield_the_processor(); + } + else + tinysleep(); } } @@ -373,13 +377,22 @@ bool spinlock_trylock_cancelable(SPINLOCK *spinlock) void rw_spinlock_init(RW_SPINLOCK *rw_spinlock) { rw_spinlock->readers = 0; + rw_spinlock->writers_waiting = 0; spinlock_init(&rw_spinlock->spinlock); } void rw_spinlock_read_lock(RW_SPINLOCK *rw_spinlock) { - spinlock_lock(&rw_spinlock->spinlock); - __atomic_add_fetch(&rw_spinlock->readers, 1, __ATOMIC_RELAXED); - spinlock_unlock(&rw_spinlock->spinlock); + while(1) { + spinlock_lock(&rw_spinlock->spinlock); + if (!rw_spinlock->writers_waiting) { + __atomic_add_fetch(&rw_spinlock->readers, 1, __ATOMIC_RELAXED); + spinlock_unlock(&rw_spinlock->spinlock); + break; + } + + spinlock_unlock(&rw_spinlock->spinlock); + yield_the_processor(); // let the writer run + } nd_thread_rwspinlock_read_locked(); } @@ -398,16 +411,25 @@ void rw_spinlock_read_unlock(RW_SPINLOCK *rw_spinlock) { void rw_spinlock_write_lock(RW_SPINLOCK *rw_spinlock) { size_t spins = 0; - while(1) { - spins++; + for(size_t i = 1; true ;i++) { spinlock_lock(&rw_spinlock->spinlock); - if(__atomic_load_n(&rw_spinlock->readers, __ATOMIC_RELAXED) == 0) + if(__atomic_load_n(&rw_spinlock->readers, __ATOMIC_RELAXED) == 0) { + if(spins != 0) + rw_spinlock->writers_waiting--; break; + } + + if(spins == 0) + rw_spinlock->writers_waiting++; // Busy wait until all readers have released their locks. spinlock_unlock(&rw_spinlock->spinlock); - tinysleep(); + if(i == 8 * 2) { + i = 0; + tinysleep(); + } + spins++; } (void)spins; diff --git a/src/libnetdata/locks/locks.h b/src/libnetdata/locks/locks.h index c05c65fe2d5e4a..e5f3a7e2db2fee 100644 --- a/src/libnetdata/locks/locks.h +++ b/src/libnetdata/locks/locks.h @@ -46,6 +46,7 @@ bool spinlock_trylock_cancelable(SPINLOCK *spinlock); typedef struct netdata_rw_spinlock { int32_t readers; + int32_t writers_waiting; SPINLOCK spinlock; } RW_SPINLOCK; diff --git a/src/libnetdata/log/nd_log.h b/src/libnetdata/log/nd_log.h index 1fefbe32831e15..5d8a3cd3f89fe2 100644 --- a/src/libnetdata/log/nd_log.h +++ b/src/libnetdata/log/nd_log.h @@ -172,6 +172,8 @@ void netdata_logger_with_limit(ERROR_LIMIT *erl, ND_LOG_SOURCES source, ND_LOG_F void netdata_logger_fatal( const char *file, const char *function, unsigned long line, const char *fmt, ... ) NORETURN PRINTFLIKE(4, 5); +#define error_report(x, args...) do { errno_clear(); netdata_log_error(x, ##args); } while(0) + # ifdef __cplusplus } # endif diff --git a/src/libnetdata/os/os.h b/src/libnetdata/os/os.h index 1846afb6d815d6..7976c6c2d9a170 100644 --- a/src/libnetdata/os/os.h +++ b/src/libnetdata/os/os.h @@ -7,6 +7,7 @@ #include #endif +#include "system_memory.h" #include "random.h" #include "timestamps.h" #include "setproctitle.h" diff --git a/src/libnetdata/os/sleep.c b/src/libnetdata/os/sleep.c index 131b47c44d4fd6..744ab88ae8a060 100644 --- a/src/libnetdata/os/sleep.c +++ b/src/libnetdata/os/sleep.c @@ -4,7 +4,8 @@ #ifdef OS_WINDOWS void tinysleep(void) { - Sleep(1); + Sleep(0); + // SwitchToThread(); } #else void tinysleep(void) { @@ -13,6 +14,16 @@ void tinysleep(void) { } #endif +#ifdef OS_WINDOWS +void yield_the_processor(void) { + Sleep(0); +} +#else +void yield_the_processor(void) { + sched_yield(); +} +#endif + #ifdef OS_WINDOWS void microsleep(usec_t ut) { size_t ms = ut / USEC_PER_MS + ((ut == 0 || (ut % USEC_PER_MS)) ? 1 : 0); diff --git a/src/libnetdata/os/sleep.h b/src/libnetdata/os/sleep.h index 3582387623531f..7266032e739f6d 100644 --- a/src/libnetdata/os/sleep.h +++ b/src/libnetdata/os/sleep.h @@ -3,6 +3,7 @@ #ifndef NETDATA_SLEEP_H #define NETDATA_SLEEP_H +void yield_the_processor(void); void tinysleep(void); void microsleep(usec_t ut); diff --git a/src/libnetdata/os/system_memory.c b/src/libnetdata/os/system_memory.c new file mode 100644 index 00000000000000..aa7d40badf3411 --- /dev/null +++ b/src/libnetdata/os/system_memory.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +// Windows +#if defined(OS_WINDOWS) +#include + +OS_SYSTEM_MEMORY os_system_memory(bool query_total_ram __maybe_unused) { + OS_SYSTEM_MEMORY sm = {0, 0}; + + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + if (GlobalMemoryStatusEx(&statex)) { + sm.ram_total_bytes = statex.ullTotalPhys; + sm.ram_available_bytes = statex.ullAvailPhys; + } + + return sm; +} +#endif + +// macOS +#if defined(OS_MACOS) +#include +#include + +OS_SYSTEM_MEMORY os_system_memory(bool query_total_ram) { + static uint64_t total_ram = 0; + static uint64_t page_size = 0; + + if (page_size == 0) { + size_t len = sizeof(page_size); + if (sysctlbyname("hw.pagesize", &page_size, &len, NULL, 0) != 0) + return (OS_SYSTEM_MEMORY){ 0, 0 }; + } + + if (query_total_ram || total_ram == 0) { + size_t len = sizeof(total_ram); + if (sysctlbyname("hw.memsize", &total_ram, &len, NULL, 0) != 0) + return (OS_SYSTEM_MEMORY){ 0, 0 }; + } + + uint64_t ram_available = 0; + if (page_size > 0) { + vm_statistics64_data_t vm_info; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + mach_port_t mach_port = mach_host_self(); + + if (host_statistics64(mach_port, HOST_VM_INFO64, (host_info_t)&vm_info, &count) != KERN_SUCCESS) { + mach_port_deallocate(mach_task_self(), mach_port); + return (OS_SYSTEM_MEMORY){0, 0}; + } + + ram_available = (vm_info.free_count + vm_info.inactive_count + vm_info.purgeable_count) * page_size; + mach_port_deallocate(mach_task_self(), mach_port); + } + + return (OS_SYSTEM_MEMORY){ + .ram_total_bytes = total_ram, + .ram_available_bytes = ram_available, + }; +} +#endif + +// Linux +#if defined(OS_LINUX) + +static OS_SYSTEM_MEMORY os_system_memory_cgroup_v1(bool query_total_ram __maybe_unused) { + static OS_SYSTEM_MEMORY sm = {0, 0}; + char buf[64]; + + if(query_total_ram || sm.ram_total_bytes == 0) { + if (read_txt_file("/sys/fs/cgroup/memory/memory.limit_in_bytes", buf, sizeof(buf)) != 0) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v1: cannot read /sys/fs/cgroup/memory/memory.limit_in_bytes"); + goto failed; + } + + sm.ram_total_bytes = strtoull(buf, NULL, 10); + if(!sm.ram_total_bytes) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v1: /sys/fs/cgroup/memory/memory.limit_in_bytes is zero"); + goto failed; + } + } + + buf[0] = '\0'; + if (read_txt_file("/sys/fs/cgroup/memory/memory.usage_in_bytes", buf, sizeof(buf)) != 0) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v1: cannot read /sys/fs/cgroup/memory/memory.usage_in_bytes"); + goto failed; + } + + uint64_t used = strtoull(buf, NULL, 10); + if(!used || used > sm.ram_total_bytes) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v1: used is %llu, total is %llu: used is invalid", +// used, sm.ram_total_bytes); + goto failed; + } + + sm.ram_available_bytes = sm.ram_total_bytes - used; + return sm; + +failed: + sm.ram_total_bytes = 0; + sm.ram_available_bytes = 0; + return sm; +} + +static OS_SYSTEM_MEMORY os_system_memory_cgroup_v2(bool query_total_ram __maybe_unused) { + static OS_SYSTEM_MEMORY sm = {0, 0}; + char buf[64]; + + if(query_total_ram || sm.ram_total_bytes == 0) { + if (read_txt_file("/sys/fs/cgroup/memory.max", buf, sizeof(buf)) != 0) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v2: cannot read /sys/fs/cgroup/memory.max"); + goto failed; + } + + if(strcmp(buf, "max") == 0) + sm.ram_total_bytes = UINT64_MAX; + else + sm.ram_total_bytes = strtoull(buf, NULL, 0); + + if(!sm.ram_total_bytes) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v2: /sys/fs/cgroup/memory.max is zero"); + goto failed; + } + } + + buf[0] = '\0'; + if (read_txt_file("/sys/fs/cgroup/memory.current", buf, sizeof(buf)) != 0) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v2: cannot read /sys/fs/cgroup/memory.current"); + goto failed; + } + + uint64_t used = strtoull(buf, NULL, 0); + if(!used || used > sm.ram_total_bytes) { +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroups v2: used is %llu, total is %llu: used is invalid", +// used, sm.ram_total_bytes); + goto failed; + } + + sm.ram_available_bytes = sm.ram_total_bytes - used; + return sm; + +failed: + sm.ram_total_bytes = 0; + sm.ram_available_bytes = 0; + return sm; +} + +OS_SYSTEM_MEMORY os_system_memory_meminfo(bool query_total_ram __maybe_unused) { + static OS_SYSTEM_MEMORY sm = {0, 0}; + static procfile *ff = NULL; + + if(unlikely(!ff)) { + ff = procfile_open("/proc/meminfo", ": \t", PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) + goto failed; + } + + ff = procfile_readall(ff); + if(unlikely(!ff)) + goto failed; + + bool matched_total = false, matched_available = false; + size_t lines = procfile_lines(ff); + for(size_t line = 0; line < lines ;line++) { + if(!matched_total && strcmp(procfile_lineword(ff, line, 0), "MemTotal") == 0) { + sm.ram_total_bytes = str2ull(procfile_lineword(ff, line, 1), NULL) * 1024; + matched_total = true; + } + + if(!matched_available && strcmp(procfile_lineword(ff, line, 0), "MemAvailable") == 0) { + sm.ram_available_bytes = str2ull(procfile_lineword(ff, line, 1), NULL) * 1024; + matched_available = true; + } + + if(matched_total && matched_available) + break; + } + + // we keep ff open to speed up the next calls + return sm; + +failed: + sm.ram_total_bytes = 0; + sm.ram_available_bytes = 0; + return sm; +} + +typedef enum { + OS_MEM_SRC_UNKNOWN, + OS_MEM_SRC_CGROUP_V1, + OS_MEM_SRC_CGROUP_V2, + OS_MEM_SRC_MEMINFO, +} OS_MEM_SRC; + +OS_SYSTEM_MEMORY os_system_memory(bool query_total_ram __maybe_unused) { + static OS_SYSTEM_MEMORY sm = {0, 0}; + static usec_t last_ut = 0, last_total_ut = 0; + static OS_MEM_SRC src = OS_MEM_SRC_UNKNOWN; + + usec_t now_ut = now_monotonic_usec(); + if(sm.ram_total_bytes && sm.ram_available_bytes && last_ut + USEC_PER_MS > now_ut) + return sm; + + last_ut = now_ut; + + if(query_total_ram) + // let it auto-detect + src = OS_MEM_SRC_UNKNOWN; + + if(last_total_ut + USEC_PER_SEC > now_ut) + // query also the total ram + query_total_ram = true; + + switch(src) { + case OS_MEM_SRC_MEMINFO: + sm = os_system_memory_meminfo(query_total_ram); + break; + + case OS_MEM_SRC_CGROUP_V2: + sm = os_system_memory_cgroup_v2(query_total_ram); + break; + + case OS_MEM_SRC_CGROUP_V1: + sm = os_system_memory_cgroup_v1(query_total_ram); + break; + + default: + case OS_MEM_SRC_UNKNOWN: { + OS_SYSTEM_MEMORY mi = os_system_memory_meminfo(true); + OS_SYSTEM_MEMORY v1 = os_system_memory_cgroup_v1(true); + OS_SYSTEM_MEMORY v2 = os_system_memory_cgroup_v2(true); + + if(v2.ram_total_bytes && v2.ram_available_bytes && v2.ram_total_bytes <= mi.ram_total_bytes && v2.ram_available_bytes < mi.ram_available_bytes) { + sm = v2; + src = OS_MEM_SRC_CGROUP_V2; + } + else { +// if(v2.ram_total_bytes || v2.ram_available_bytes) +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroup v2 reports more memory than meminfo. Ignoring cgroup v2."); + + if (v1.ram_total_bytes && v1.ram_available_bytes && v1.ram_total_bytes <= mi.ram_total_bytes && + v1.ram_available_bytes < mi.ram_available_bytes) { + sm = v1; + src = OS_MEM_SRC_CGROUP_V1; + } + else { +// if(v1.ram_total_bytes || v1.ram_available_bytes) +// nd_log(NDLS_DAEMON, NDLP_ERR, "SYSTEM_MEMORY: cgroup v1 reports more memory than meminfo. Ignoring cgroup v1."); + + sm = mi; + src = OS_MEM_SRC_MEMINFO; + } + } + } + } + + return sm; +} +#endif + +// FreeBSD +#if defined(OS_FREEBSD) +#include +#include + +OS_SYSTEM_MEMORY os_system_memory(bool query_total_ram) { + static OS_SYSTEM_MEMORY sm = {0, 0}; + + // Query the total RAM only if needed or if it hasn't been cached + if (query_total_ram || sm.ram_total_bytes == 0) { + uint64_t total_pages = 0; + size_t size = sizeof(total_pages); + if (sysctlbyname("vm.stats.vm.v_page_count", &total_pages, &size, NULL, 0) != 0) + goto failed; + + unsigned long page_size = 0; + size = sizeof(page_size); + if (sysctlbyname("hw.pagesize", &page_size, &size, NULL, 0) != 0) + goto failed; + + sm.ram_total_bytes = total_pages * page_size; + } + + // Query the available RAM (free + inactive pages) + uint64_t free_pages = 0, inactive_pages = 0; + size_t size = sizeof(free_pages); + if (sysctlbyname("vm.stats.vm.v_free_count", &free_pages, &size, NULL, 0) != 0 || + sysctlbyname("vm.stats.vm.v_inactive_count", &inactive_pages, &size, NULL, 0) != 0) + goto failed; + + unsigned long page_size = 0; + size = sizeof(page_size); + if (sysctlbyname("hw.pagesize", &page_size, &size, NULL, 0) != 0) + goto failed; + + sm.ram_available_bytes = (free_pages + inactive_pages) * page_size; + + return sm; + +failed: + sm.ram_total_bytes = 0; + sm.ram_available_bytes = 0; + return sm; +} +#endif diff --git a/src/libnetdata/os/system_memory.h b/src/libnetdata/os/system_memory.h new file mode 100644 index 00000000000000..fbfd0d01f43de1 --- /dev/null +++ b/src/libnetdata/os/system_memory.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_OS_MEM_AVAILABLE_H +#define NETDATA_OS_MEM_AVAILABLE_H + +#include "libnetdata/libnetdata.h" + +typedef struct { + // ram_total_bytes is the total memory available in the system + // and it includes all the physical RAM the system may have. + // It does not include non-RAM memory (i.e. SWAP in not included). + // ram_total_bytes may be cached between calls to these functions + // (i.e. it may not be as up to date as ram_available_bytes). + uint64_t ram_total_bytes; + + // ram_available_bytes is the total RAM memory available for + // applications, before the system and its applications run + // out-of-memory. This is always provided live by querying the system. + // It does not include non-RAM memory (i.e. SWAP in not included). + uint64_t ram_available_bytes; +} OS_SYSTEM_MEMORY; + +// The function to get current system memory: +OS_SYSTEM_MEMORY os_system_memory(bool query_total_ram); + +#endif //NETDATA_OS_MEM_AVAILABLE_H diff --git a/src/libnetdata/parsers/entries.c b/src/libnetdata/parsers/entries.c index d6ed31de16d447..7f98278f5a18dc 100644 --- a/src/libnetdata/parsers/entries.c +++ b/src/libnetdata/parsers/entries.c @@ -147,11 +147,11 @@ ssize_t entries_snprintf(char *dst, size_t dst_size, uint64_t value, const char double converted = entries_round_to_resolution_dbl2(bytes, su->multiplier); - uint64_t reversed_bytes = (uint64_t)(converted * (double)su->multiplier); + uint64_t reversed_bytes = (uint64_t)round((converted * (double)su->multiplier)); if(accurate) { // no precision loss is required - if (reversed_bytes == bytes) + if (reversed_bytes == bytes && converted > 1.0) // no precision loss, this is good to use su_best = su; } diff --git a/src/libnetdata/parsers/size.c b/src/libnetdata/parsers/size.c index d3a24c540ac89d..442b995159e893 100644 --- a/src/libnetdata/parsers/size.c +++ b/src/libnetdata/parsers/size.c @@ -175,12 +175,11 @@ ssize_t size_snprintf(char *dst, size_t dst_size, uint64_t value, const char *un continue; double converted = size_round_to_resolution_dbl2(bytes, su->multiplier); - - uint64_t reversed_bytes = (uint64_t)(converted * (double)su->multiplier); + uint64_t reversed_bytes = (uint64_t)round((converted * (double)su->multiplier)); if(accurate) { // no precision loss is required - if (reversed_bytes == bytes) + if (reversed_bytes == bytes && converted > 1.0) // no precision loss, this is good to use su_best = su; } diff --git a/src/libnetdata/required_dummies.h b/src/libnetdata/required_dummies.h index cff4c563a5a375..d660d8c048c142 100644 --- a/src/libnetdata/required_dummies.h +++ b/src/libnetdata/required_dummies.h @@ -14,7 +14,7 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re } void rrdset_thread_rda_free(void){} -void sender_thread_buffer_free(void){} +void sender_commit_thread_buffer_free(void){} void query_target_free(void){} void service_exits(void){} void rrd_collector_finished(void){} diff --git a/src/libnetdata/socket/connect-to.c b/src/libnetdata/socket/connect-to.c new file mode 100644 index 00000000000000..d3cab13003e90e --- /dev/null +++ b/src/libnetdata/socket/connect-to.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +// -------------------------------------------------------------------------------------------------------------------- +// connect to another host/port + +// connect_to_this_unix() +// path the path of the unix socket +// timeout the timeout for establishing a connection + +static inline int connect_to_unix(const char *path, struct timeval *timeout) { + int fd = socket(AF_UNIX, SOCK_STREAM | DEFAULT_SOCKET_FLAGS, 0); + if(fd == -1) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to create UNIX socket() for '%s'", + path); + + return -1; + } + + if(timeout) { + if(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) timeout, sizeof(struct timeval)) < 0) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to set timeout on UNIX socket '%s'", + path); + } + + sock_setcloexec(fd); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpyz(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "Cannot connect to UNIX socket on path '%s'.", + path); + + close(fd); + return -1; + } + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "Connected to UNIX socket on path '%s'.", + path); + + return fd; +} + +// connect_to_this_ip46() +// protocol IPPROTO_TCP, IPPROTO_UDP +// socktype SOCK_STREAM, SOCK_DGRAM +// host the destination hostname or IP address (IPv4 or IPv6) to connect to +// if it resolves to many IPs, all are tried (IPv4 and IPv6) +// scope_id the if_index id of the interface to use for connecting (0 = any) +// (used only under IPv6) +// service the service name or port to connect to +// timeout the timeout for establishing a connection + +int connect_to_this_ip46( + int protocol, + int socktype, + const char *host, + uint32_t scope_id, + const char *service, + struct timeval *timeout, + bool *fallback_ipv4) +{ + struct addrinfo hints; + struct addrinfo *ai_head = NULL, *ai = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = socktype; + hints.ai_protocol = protocol; + + int ai_err = getaddrinfo(host, service, &hints, &ai_head); + if (ai_err != 0) { + + nd_log(NDLS_DAEMON, NDLP_ERR, + "Cannot resolve host '%s', port '%s': %s", + host, service, gai_strerror(ai_err)); + + return -ND_SOCK_ERR_CANNOT_RESOLVE_HOSTNAME; + } + + char hostBfr[NI_MAXHOST + 1]; + char servBfr[NI_MAXSERV + 1]; + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_TXT(NDF_DST_IP, hostBfr), + ND_LOG_FIELD_TXT(NDF_DST_PORT, servBfr), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + int fd = -1; + for (ai = ai_head; ai != NULL && fd == -1; ai = ai->ai_next) { + if(nd_thread_signaled_to_cancel()) break; + + if (fallback_ipv4 && *fallback_ipv4 && ai->ai_family == PF_INET6) + continue; + + if (ai->ai_family == PF_INET6) { + struct sockaddr_in6 *pSadrIn6 = (struct sockaddr_in6 *) ai->ai_addr; + if(pSadrIn6->sin6_scope_id == 0) { + pSadrIn6->sin6_scope_id = scope_id; + } + } + + getnameinfo(ai->ai_addr, + ai->ai_addrlen, + hostBfr, + sizeof(hostBfr), + servBfr, + sizeof(servBfr), + NI_NUMERICHOST | NI_NUMERICSERV); + + switch (ai->ai_addr->sa_family) { + case PF_INET: { + struct sockaddr_in *pSadrIn = (struct sockaddr_in *)ai->ai_addr; + (void)pSadrIn; + break; + } + + case PF_INET6: { + struct sockaddr_in6 *pSadrIn6 = (struct sockaddr_in6 *) ai->ai_addr; + (void)pSadrIn6; + break; + } + + default: { + // Unknown protocol family + continue; + } + } + + fd = socket(ai->ai_family, ai->ai_socktype | DEFAULT_SOCKET_FLAGS, ai->ai_protocol); + if(fd != -1) { + if(timeout) { + if(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) timeout, sizeof(struct timeval)) < 0) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to set timeout on the socket to ip '%s' port '%s'", + hostBfr, servBfr); + } + sock_setcloexec(fd); + + errno_clear(); + if(connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) { + if(errno == EALREADY || errno == EINPROGRESS) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "Waiting for connection to ip %s port %s to be established", + hostBfr, servBfr); + + // Convert 'struct timeval' to milliseconds for poll(): + int timeout_ms = timeout ? (timeout->tv_sec * 1000 + timeout->tv_usec / 1000) : 1000; + + switch(wait_on_socket_or_cancel_with_timeout(NULL, fd, timeout_ms, POLLOUT, NULL)) { + case 0: // proceed + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "connect() to ip %s port %s completed successfully", + hostBfr, servBfr); + break; + + case -1: // thread cancelled + nd_log(NDLS_DAEMON, NDLP_ERR, + "Thread is cancelled while connecting to '%s', port '%s'.", + hostBfr, servBfr); + + close(fd); + fd = -ND_SOCK_ERR_THREAD_CANCELLED; + break; + + case 1: // timeout + nd_log(NDLS_DAEMON, NDLP_ERR, + "Timed out while connecting to '%s', port '%s'.", + hostBfr, servBfr); + + close(fd); + fd = -ND_SOCK_ERR_TIMEOUT; + + if (fallback_ipv4 && ai->ai_family == PF_INET6) + *fallback_ipv4 = true; + break; + + default: + case 2: // poll error + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to connect to '%s', port '%s'.", + hostBfr, servBfr); + + close(fd); + fd = -ND_SOCK_ERR_POLL_ERROR; + break; + } + } + else { + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to connect to '%s', port '%s'", + hostBfr, servBfr); + + close(fd); + fd = -ND_SOCK_ERR_CONNECTION_REFUSED; + } + } + } + else { + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to socket() to '%s', port '%s'", hostBfr, servBfr); + fd = -ND_SOCK_ERR_FAILED_TO_CREATE_SOCKET; + } + } + + freeaddrinfo(ai_head); + + return fd; +} + +// connect_to_this() +// +// definition format: +// +// [PROTOCOL:]IP[%INTERFACE][:PORT] +// +// PROTOCOL = tcp or udp +// IP = IPv4 or IPv6 IP or hostname, optionally enclosed in [] (required for IPv6) +// INTERFACE = for IPv6 only, the network interface to use +// PORT = port number or service name + +int connect_to_this(const char *definition, int default_port, struct timeval *timeout) { + char buffer[strlen(definition) + 1]; + strcpy(buffer, definition); + + char default_service[10 + 1]; + snprintfz(default_service, 10, "%d", default_port); + + char *host = buffer, *service = default_service, *iface = ""; + int protocol = IPPROTO_TCP, socktype = SOCK_STREAM; + uint32_t scope_id = 0; + + if(strncmp(host, "tcp:", 4) == 0) { + host += 4; + protocol = IPPROTO_TCP; + socktype = SOCK_STREAM; + } + else if(strncmp(host, "udp:", 4) == 0) { + host += 4; + protocol = IPPROTO_UDP; + socktype = SOCK_DGRAM; + } + else if(strncmp(host, "unix:", 5) == 0) { + char *path = host + 5; + return connect_to_unix(path, timeout); + } + else if(*host == '/') { + char *path = host; + return connect_to_unix(path, timeout); + } + + char *e = host; + if(*e == '[') { + e = ++host; + while(*e && *e != ']') e++; + if(*e == ']') { + *e = '\0'; + e++; + } + } + else { + while(*e && *e != ':' && *e != '%') e++; + } + + if(*e == '%') { + *e = '\0'; + e++; + iface = e; + while(*e && *e != ':') e++; + } + + if(*e == ':') { + *e = '\0'; + e++; + service = e; + } + + if(!*host) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "Definition '%s' does not specify a host.", + definition); + + return -ND_SOCK_ERR_NO_HOST_IN_DEFINITION; + } + + if(*iface) { + scope_id = if_nametoindex(iface); + if(!scope_id) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Cannot find a network interface named '%s'. Continuing with limiting the network interface", + iface); + } + + if(!*service) + service = default_service; + + + return connect_to_this_ip46(protocol, socktype, host, scope_id, service, timeout, NULL); +} + +void foreach_entry_in_connection_string(const char *destination, bool (*callback)(char *entry, void *data), void *data) { + const char *s = destination; + while(*s) { + const char *e = s; + + // skip separators, moving both s(tart) and e(nd) + while(isspace((uint8_t)*e) || *e == ',') s = ++e; + + // move e(nd) to the first separator + while(*e && !isspace((uint8_t)*e) && *e != ',') e++; + + // is there anything? + if(!*s || s == e) break; + + char buf[e - s + 1]; + strncpyz(buf, s, e - s); + + if(callback(buf, data)) break; + + s = e; + } +} + +struct connect_to_one_of_data { + int default_port; + struct timeval *timeout; + size_t *reconnects_counter; + char *connected_to; + size_t connected_to_size; + int sock; +}; + +static bool connect_to_one_of_callback(char *entry, void *data) { + struct connect_to_one_of_data *t = data; + + if(t->reconnects_counter) + t->reconnects_counter++; + + t->sock = connect_to_this(entry, t->default_port, t->timeout); + if(t->sock != -1) { + if(t->connected_to && t->connected_to_size) { + strncpyz(t->connected_to, entry, t->connected_to_size); + t->connected_to[t->connected_to_size - 1] = '\0'; + } + + return true; + } + + return false; +} + +int connect_to_one_of(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size) { + struct connect_to_one_of_data t = { + .default_port = default_port, + .timeout = timeout, + .reconnects_counter = reconnects_counter, + .connected_to = connected_to, + .connected_to_size = connected_to_size, + .sock = -1, + }; + + foreach_entry_in_connection_string(destination, connect_to_one_of_callback, &t); + + return t.sock; +} + +static bool connect_to_one_of_urls_callback(char *entry, void *data) { + char *s = strchr(entry, '/'); + if(s) *s = '\0'; + + return connect_to_one_of_callback(entry, data); +} + +int connect_to_one_of_urls(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size) { + struct connect_to_one_of_data t = { + .default_port = default_port, + .timeout = timeout, + .reconnects_counter = reconnects_counter, + .connected_to = connected_to, + .connected_to_size = connected_to_size, + .sock = -1, + }; + + foreach_entry_in_connection_string(destination, connect_to_one_of_urls_callback, &t); + + return t.sock; +} diff --git a/src/libnetdata/socket/connect-to.h b/src/libnetdata/socket/connect-to.h new file mode 100644 index 00000000000000..032099f352f6e7 --- /dev/null +++ b/src/libnetdata/socket/connect-to.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_CONNECT_TO_H +#define NETDATA_CONNECT_TO_H + +void foreach_entry_in_connection_string(const char *destination, bool (*callback)(char *entry, void *data), void *data); +int connect_to_this_ip46( + int protocol, + int socktype, + const char *host, + uint32_t scope_id, + const char *service, + struct timeval *timeout, + bool *fallback_ipv4); +int connect_to_this(const char *definition, int default_port, struct timeval *timeout); +int connect_to_one_of(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size); +int connect_to_one_of_urls(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size); + +#endif //NETDATA_CONNECT_TO_H diff --git a/src/libnetdata/socket/listen-sockets.c b/src/libnetdata/socket/listen-sockets.c new file mode 100644 index 00000000000000..758476d638ab95 --- /dev/null +++ b/src/libnetdata/socket/listen-sockets.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" +#include "listen-sockets.h" + +static HTTP_ACL socket_ssl_acl(char *acl) { + char *ssl = strchr(acl,'^'); + if(ssl) { + //Due the format of the SSL command it is always the last command, + //we finish it here to avoid problems with the ACLs + *ssl = '\0'; + ssl++; + if (!strncmp("SSL=",ssl,4)) { + ssl += 4; + if (!strcmp(ssl,"optional")) { + return HTTP_ACL_SSL_OPTIONAL; + } + else if (!strcmp(ssl,"force")) { + return HTTP_ACL_SSL_FORCE; + } + } + } + + return HTTP_ACL_NONE; +} + +static HTTP_ACL read_acl(char *st) { + HTTP_ACL ret = socket_ssl_acl(st); + + if (!strcmp(st,"dashboard")) ret |= HTTP_ACL_DASHBOARD; + if (!strcmp(st,"registry")) ret |= HTTP_ACL_REGISTRY; + if (!strcmp(st,"badges")) ret |= HTTP_ACL_BADGES; + if (!strcmp(st,"management")) ret |= HTTP_ACL_MANAGEMENT; + if (!strcmp(st,"streaming")) ret |= HTTP_ACL_STREAMING; + if (!strcmp(st,"netdata.conf")) ret |= HTTP_ACL_NETDATACONF; + + return ret; +} + +static char *strdup_client_description(int family, const char *protocol, const char *ip, uint16_t port) { + char buffer[100 + 1]; + + switch(family) { + case AF_INET: + snprintfz(buffer, sizeof(buffer) - 1, "%s:%s:%d", protocol, ip, port); + break; + + case AF_INET6: + default: + snprintfz(buffer, sizeof(buffer) - 1, "%s:[%s]:%d", protocol, ip, port); + break; + + case AF_UNIX: + snprintfz(buffer, sizeof(buffer) - 1, "%s:%s", protocol, ip); + break; + } + + return strdupz(buffer); +} + +static int create_listen_socket_unix(const char *path, int listen_backlog) { + int sock; + + sock = socket(AF_UNIX, SOCK_STREAM | DEFAULT_SOCKET_FLAGS, 0); + if(sock < 0) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: UNIX socket() on path '%s' failed.", + path); + + return -1; + } + + sock_setnonblock(sock); + sock_setcloexec(sock); + sock_enlarge_in(sock); + + struct sockaddr_un name; + memset(&name, 0, sizeof(struct sockaddr_un)); + name.sun_family = AF_UNIX; + strncpyz(name.sun_path, path, sizeof(name.sun_path) - 1); + + errno_clear(); + if (unlink(path) == -1 && errno != ENOENT) + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: failed to remove existing (probably obsolete or left-over) file on UNIX socket path '%s'.", + path); + + if(bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { + close(sock); + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: UNIX bind() on path '%s' failed.", + path); + + return -1; + } + + // we have to chmod this to 0777 so that the client will be able + // to read from and write to this socket. + if(chmod(path, 0777) == -1) + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: failed to chmod() socket file '%s'.", + path); + + if(listen(sock, listen_backlog) < 0) { + close(sock); + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: UNIX listen() on path '%s' failed.", + path); + + return -1; + } + + return sock; +} + +static int create_listen_socket4(int socktype, const char *ip, uint16_t port, int listen_backlog) { + int sock; + + sock = socket(AF_INET, socktype | DEFAULT_SOCKET_FLAGS, 0); + if(sock < 0) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: IPv4 socket() on ip '%s' port %d, socktype %d failed.", + ip, port, socktype); + + return -1; + } + sock_setreuse(sock, 1); + sock_setreuse_port(sock, 0); + sock_setnonblock(sock); + sock_setcloexec(sock); + sock_enlarge_in(sock); + + struct sockaddr_in name; + memset(&name, 0, sizeof(struct sockaddr_in)); + name.sin_family = AF_INET; + name.sin_port = htons (port); + + int ret = inet_pton(AF_INET, ip, (void *)&name.sin_addr.s_addr); + if(ret != 1) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Failed to convert IP '%s' to a valid IPv4 address.", + ip); + + close(sock); + return -1; + } + + if(bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { + close(sock); + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: IPv4 bind() on ip '%s' port %d, socktype %d failed.", + ip, port, socktype); + + return -1; + } + + if(socktype == SOCK_STREAM && listen(sock, listen_backlog) < 0) { + close(sock); + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: IPv4 listen() on ip '%s' port %d, socktype %d failed.", + ip, port, socktype); + + return -1; + } + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "LISTENER: Listening on IPv4 ip '%s' port %d, socktype %d", + ip, port, socktype); + + return sock; +} + +static int create_listen_socket6(int socktype, uint32_t scope_id, const char *ip, int port, int listen_backlog) { + int sock; + int ipv6only = 1; + + sock = socket(AF_INET6, socktype | DEFAULT_SOCKET_FLAGS, 0); + if (sock < 0) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: IPv6 socket() on ip '%s' port %d, socktype %d, failed.", + ip, port, socktype); + + return -1; + } + sock_setreuse(sock, 1); + sock_setreuse_port(sock, 0); + sock_setnonblock(sock); + sock_setcloexec(sock); + sock_enlarge_in(sock); + + /* IPv6 only */ + if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&ipv6only, sizeof(ipv6only)) != 0) + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Cannot set IPV6_V6ONLY on ip '%s' port %d, socktype %d.", + ip, port, socktype); + + struct sockaddr_in6 name; + memset(&name, 0, sizeof(struct sockaddr_in6)); + name.sin6_family = AF_INET6; + name.sin6_port = htons ((uint16_t) port); + name.sin6_scope_id = scope_id; + + int ret = inet_pton(AF_INET6, ip, (void *)&name.sin6_addr.s6_addr); + if(ret != 1) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Failed to convert IP '%s' to a valid IPv6 address.", + ip); + + close(sock); + return -1; + } + + name.sin6_scope_id = scope_id; + + if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { + close(sock); + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: IPv6 bind() on ip '%s' port %d, socktype %d failed.", + ip, port, socktype); + + return -1; + } + + if (socktype == SOCK_STREAM && listen(sock, listen_backlog) < 0) { + close(sock); + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: IPv6 listen() on ip '%s' port %d, socktype %d failed.", + ip, port, socktype); + + return -1; + } + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "LISTENER: Listening on IPv6 ip '%s' port %d, socktype %d", + ip, port, socktype); + + return sock; +} + +static inline int listen_sockets_add(LISTEN_SOCKETS *sockets, int fd, int family, int socktype, const char *protocol, const char *ip, uint16_t port, int acl_flags) { + if(sockets->opened >= MAX_LISTEN_FDS) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Too many listening sockets. Failed to add listening %s socket at ip '%s' port %d, protocol %s, socktype %d", + protocol, ip, port, protocol, socktype); + + close(fd); + return -1; + } + + sockets->fds[sockets->opened] = fd; + sockets->fds_types[sockets->opened] = socktype; + sockets->fds_families[sockets->opened] = family; + sockets->fds_names[sockets->opened] = strdup_client_description(family, protocol, ip, port); + sockets->fds_acl_flags[sockets->opened] = acl_flags; + + sockets->opened++; + return 0; +} + +static inline int listen_sockets_check_is_member(LISTEN_SOCKETS *sockets, int fd) { + size_t i; + for(i = 0; i < sockets->opened ;i++) + if(sockets->fds[i] == fd) return 1; + + return 0; +} + +static inline void listen_sockets_init(LISTEN_SOCKETS *sockets) { + size_t i; + for(i = 0; i < MAX_LISTEN_FDS ;i++) { + sockets->fds[i] = -1; + sockets->fds_names[i] = NULL; + sockets->fds_types[i] = -1; + } + + sockets->opened = 0; + sockets->failed = 0; +} + +void listen_sockets_close(LISTEN_SOCKETS *sockets) { + size_t i; + for(i = 0; i < sockets->opened ;i++) { + close(sockets->fds[i]); + sockets->fds[i] = -1; + + freez(sockets->fds_names[i]); + sockets->fds_names[i] = NULL; + + sockets->fds_types[i] = -1; + } + + sockets->opened = 0; + sockets->failed = 0; +} + +static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition, uint16_t default_port, int listen_backlog) { + int added = 0; + HTTP_ACL acl_flags = HTTP_ACL_NONE; + + struct addrinfo hints; + struct addrinfo *result = NULL, *rp = NULL; + + char buffer[strlen(definition) + 1]; + strcpy(buffer, definition); + + char buffer2[10 + 1]; + snprintfz(buffer2, 10, "%d", default_port); + + char *ip = buffer, *port = buffer2, *iface = "", *portconfig; + + int protocol = IPPROTO_TCP, socktype = SOCK_STREAM; + const char *protocol_str = "tcp"; + + if(strncmp(ip, "tcp:", 4) == 0) { + ip += 4; + protocol = IPPROTO_TCP; + socktype = SOCK_STREAM; + protocol_str = "tcp"; + acl_flags |= HTTP_ACL_API; + } + else if(strncmp(ip, "udp:", 4) == 0) { + ip += 4; + protocol = IPPROTO_UDP; + socktype = SOCK_DGRAM; + protocol_str = "udp"; + acl_flags |= HTTP_ACL_API_UDP; + } + else if(strncmp(ip, "unix:", 5) == 0) { + char *path = ip + 5; + socktype = SOCK_STREAM; + protocol_str = "unix"; + int fd = create_listen_socket_unix(path, listen_backlog); + if (fd == -1) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Cannot create unix socket '%s'", + path); + + sockets->failed++; + } else { + acl_flags = HTTP_ACL_API_UNIX | HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | + HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT; + listen_sockets_add(sockets, fd, AF_UNIX, socktype, protocol_str, path, 0, acl_flags); + added++; + } + return added; + } + + char *e = ip; + if(*e == '[') { + e = ++ip; + while(*e && *e != ']') e++; + if(*e == ']') { + *e = '\0'; + e++; + } + } + else { + while(*e && *e != ':' && *e != '%' && *e != '=') e++; + } + + if(*e == '%') { + *e = '\0'; + e++; + iface = e; + while(*e && *e != ':' && *e != '=') e++; + } + + if(*e == ':') { + port = e + 1; + *e = '\0'; + e++; + while(*e && *e != '=') e++; + } + + if(*e == '=') { + *e='\0'; + e++; + portconfig = e; + while (*e != '\0') { + if (*e == '|') { + *e = '\0'; + acl_flags |= read_acl(portconfig); + e++; + portconfig = e; + continue; + } + e++; + } + acl_flags |= read_acl(portconfig); + } else { + acl_flags |= HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT; + } + + //Case the user does not set the option SSL in the "bind to", but he has + //the certificates, I must redirect, so I am assuming here the default option + if(!(acl_flags & HTTP_ACL_SSL_OPTIONAL) && !(acl_flags & HTTP_ACL_SSL_FORCE)) { + acl_flags |= HTTP_ACL_SSL_DEFAULT; + } + + uint32_t scope_id = 0; + if(*iface) { + scope_id = if_nametoindex(iface); + if(!scope_id) + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Cannot find a network interface named '%s'. " + "Continuing with limiting the network interface", + iface); + } + + if(!*ip || *ip == '*' || !strcmp(ip, "any") || !strcmp(ip, "all")) + ip = NULL; + + if(!*port) + port = buffer2; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = socktype; + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_protocol = protocol; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + int r = getaddrinfo(ip, port, &hints, &result); + if (r != 0) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: getaddrinfo('%s', '%s'): %s\n", + ip, port, gai_strerror(r)); + + return -1; + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + int fd = -1; + int family; + + char rip[INET_ADDRSTRLEN + INET6_ADDRSTRLEN] = "INVALID"; + uint16_t rport = default_port; + + family = rp->ai_addr->sa_family; + switch (family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *) rp->ai_addr; + inet_ntop(AF_INET, &sin->sin_addr, rip, INET_ADDRSTRLEN); + rport = ntohs(sin->sin_port); + fd = create_listen_socket4(socktype, rip, rport, listen_backlog); + break; + } + + case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) rp->ai_addr; + inet_ntop(AF_INET6, &sin6->sin6_addr, rip, INET6_ADDRSTRLEN); + rport = ntohs(sin6->sin6_port); + fd = create_listen_socket6(socktype, scope_id, rip, rport, listen_backlog); + break; + } + + default: + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "LISTENER: Unknown socket family %d", + family); + + break; + } + + if (fd == -1) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Cannot bind to ip '%s', port %d", + rip, rport); + + sockets->failed++; + } + else { + listen_sockets_add(sockets, fd, family, socktype, protocol_str, rip, rport, acl_flags); + added++; + } + } + + freeaddrinfo(result); + + return added; +} + +int listen_sockets_setup(LISTEN_SOCKETS *sockets) { + listen_sockets_init(sockets); + + sockets->backlog = (int) appconfig_get_number(sockets->config, sockets->config_section, "listen backlog", sockets->backlog); + + long long int old_port = sockets->default_port; + long long int new_port = appconfig_get_number(sockets->config, sockets->config_section, "default port", sockets->default_port); + if(new_port < 1 || new_port > 65535) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "LISTENER: Invalid listen port %lld given. Defaulting to %lld.", + new_port, old_port); + + sockets->default_port = (uint16_t) appconfig_set_number(sockets->config, sockets->config_section, "default port", old_port); + } + else sockets->default_port = (uint16_t)new_port; + + const char *s = appconfig_get(sockets->config, sockets->config_section, "bind to", sockets->default_bind_to); + while(*s) { + const char *e = s; + + // skip separators, moving both s(tart) and e(nd) + while(isspace((uint8_t)*e) || *e == ',') s = ++e; + + // move e(nd) to the first separator + while(*e && !isspace((uint8_t)*e) && *e != ',') e++; + + // is there anything? + if(!*s || s == e) break; + + char buf[e - s + 1]; + strncpyz(buf, s, e - s); + bind_to_this(sockets, buf, sockets->default_port, sockets->backlog); + + s = e; + } + + if(sockets->failed) { + size_t i; + for(i = 0; i < sockets->opened ;i++) + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "LISTENER: Listen socket %s opened successfully.", + sockets->fds_names[i]); + } + + return (int)sockets->opened; +} diff --git a/src/libnetdata/socket/listen-sockets.h b/src/libnetdata/socket/listen-sockets.h new file mode 100644 index 00000000000000..ebeb55994d717e --- /dev/null +++ b/src/libnetdata/socket/listen-sockets.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_LISTEN_SOCKETS_H +#define NETDATA_LISTEN_SOCKETS_H + +#include "libnetdata/common.h" + +#ifndef MAX_LISTEN_FDS +#define MAX_LISTEN_FDS 50 +#endif + +typedef struct listen_sockets { + struct config *config; // the config file to use + const char *config_section; // the netdata configuration section to read settings from + const char *default_bind_to; // the default bind to configuration string + uint16_t default_port; // the default port to use + int backlog; // the default listen backlog to use + + size_t opened; // the number of sockets opened + size_t failed; // the number of sockets attempted to open, but failed + int fds[MAX_LISTEN_FDS]; // the open sockets + char *fds_names[MAX_LISTEN_FDS]; // descriptions for the open sockets + int fds_types[MAX_LISTEN_FDS]; // the socktype for the open sockets (SOCK_STREAM, SOCK_DGRAM) + int fds_families[MAX_LISTEN_FDS]; // the family of the open sockets (AF_UNIX, AF_INET, AF_INET6) + HTTP_ACL fds_acl_flags[MAX_LISTEN_FDS]; // the acl to apply to the open sockets (dashboard, badges, streaming, netdata.conf, management) +} LISTEN_SOCKETS; + +int listen_sockets_setup(LISTEN_SOCKETS *sockets); +void listen_sockets_close(LISTEN_SOCKETS *sockets); + +#endif //NETDATA_LISTEN_SOCKETS_H diff --git a/src/libnetdata/socket/nd-poll.c b/src/libnetdata/socket/nd-poll.c new file mode 100644 index 00000000000000..c731ff209a1ce8 --- /dev/null +++ b/src/libnetdata/socket/nd-poll.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "nd-poll.h" + +#if defined(OS_LINUX) +#include + +#define MAX_EVENTS_PER_CALL 100 + +// Event poll context +struct nd_poll_t { + int epoll_fd; + struct epoll_event ev[MAX_EVENTS_PER_CALL]; + size_t last_pos; + size_t used; +}; + +// Initialize the event poll context +nd_poll_t *nd_poll_create() { + nd_poll_t *ndpl = callocz(1, sizeof(nd_poll_t)); + + ndpl->epoll_fd = epoll_create1(0); + if (ndpl->epoll_fd < 0) { + freez(ndpl); + return NULL; + } + + return ndpl; +} + +// Add a file descriptor to the event poll +bool nd_poll_add(nd_poll_t *ndpl, int fd, nd_poll_event_t events, void *data) { + struct epoll_event ev = { + .events = (events & ND_POLL_READ ? EPOLLIN : 0) | (events & ND_POLL_WRITE ? EPOLLOUT : 0), + .data.ptr = data, + }; + return epoll_ctl(ndpl->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == 0; +} + +// Remove a file descriptor from the event poll +bool nd_poll_del(nd_poll_t *ndpl, int fd) { + return epoll_ctl(ndpl->epoll_fd, EPOLL_CTL_DEL, fd, NULL) == 0; +} + +// Update an existing file descriptor in the event poll +bool nd_poll_upd(nd_poll_t *ndpl, int fd, nd_poll_event_t events, void *data) { + struct epoll_event ev = { + .events = (events & ND_POLL_READ ? EPOLLIN : 0) | (events & ND_POLL_WRITE ? EPOLLOUT : 0), + .data.ptr = data, + }; + return epoll_ctl(ndpl->epoll_fd, EPOLL_CTL_MOD, fd, &ev) == 0; +} + +static inline bool nd_poll_get_next_event(nd_poll_t *ndpl, nd_poll_result_t *result) { + for(size_t i = ndpl->last_pos; i < ndpl->used ;i++) { + *result = (nd_poll_result_t){ + .events = 0, + .data = ndpl->ev[i].data.ptr, + }; + + if (ndpl->ev[i].events & EPOLLIN) + result->events |= ND_POLL_READ; + + if (ndpl->ev[i].events & EPOLLOUT) + result->events |= ND_POLL_WRITE; + + if (ndpl->ev[i].events & EPOLLERR) + result->events |= ND_POLL_ERROR; + + if (ndpl->ev[i].events & EPOLLHUP) + result->events |= ND_POLL_HUP; + + ndpl->last_pos = i + 1; + return true; + } + + ndpl->last_pos = _countof(ndpl->ev); + return false; +} + +// Wait for events +int nd_poll_wait(nd_poll_t *ndpl, int timeout_ms, nd_poll_result_t *result) { + if(nd_poll_get_next_event(ndpl, result)) + return 1; + + do { + errno_clear(); + ndpl->last_pos = 0; + ndpl->used = 0; + int n = epoll_wait(ndpl->epoll_fd, &ndpl->ev[0], _countof(ndpl->ev), timeout_ms); + + if(unlikely(n <= 0)) { + if (n < 0) { + result->events = ND_POLL_OTHER_ERROR; + result->data = NULL; + return -1; + } + else { + result->events = ND_POLL_TIMEOUT; + result->data = NULL; + return 0; + } + } + + ndpl->used = n; + if (nd_poll_get_next_event(ndpl, result)) + return 1; + + } while(true); +} + +// Destroy the event poll context +void nd_poll_destroy(nd_poll_t *ndpl) { + if (ndpl) { + close(ndpl->epoll_fd); + freez(ndpl); + } +} +#else + +DEFINE_JUDYL_TYPED(POINTERS, void *); + +struct nd_poll_t { + struct pollfd *fds; // Array of file descriptors + nfds_t nfds; // Number of active file descriptors + nfds_t capacity; // Allocated capacity for `fds` array + nfds_t last_pos; + POINTERS_JudyLSet pointers; // Judy array to store user data +}; + +#define INITIAL_CAPACITY 4 + +// Initialize the event poll context +nd_poll_t *nd_poll_create() { + nd_poll_t *ndpl = callocz(1, sizeof(nd_poll_t)); + ndpl->fds = mallocz(INITIAL_CAPACITY * sizeof(struct pollfd)); + ndpl->nfds = 0; + ndpl->capacity = INITIAL_CAPACITY; + + POINTERS_INIT(&ndpl->pointers); + + return ndpl; +} + +// Ensure capacity for adding new file descriptors +static void ensure_capacity(nd_poll_t *ndpl) { + if (ndpl->nfds < ndpl->capacity) return; + + nfds_t new_capacity = ndpl->capacity * 2; + struct pollfd *new_fds = reallocz(ndpl->fds, new_capacity * sizeof(struct pollfd)); + + ndpl->fds = new_fds; + ndpl->capacity = new_capacity; +} + +bool nd_poll_add(nd_poll_t *ndpl, int fd, nd_poll_event_t events, void *data) { + internal_fatal(POINTERS_GET(&ndpl->pointers, fd) != NULL, "File descriptor %d is already served - cannot add", fd); + + ensure_capacity(ndpl); + struct pollfd *pfd = &ndpl->fds[ndpl->nfds++]; + pfd->fd = fd; + pfd->events = 0; + if (events & ND_POLL_READ) pfd->events |= POLLIN; + if (events & ND_POLL_WRITE) pfd->events |= POLLOUT; + pfd->revents = 0; + + POINTERS_SET(&ndpl->pointers, fd, data); + + return true; +} + +// Remove a file descriptor from the event poll +bool nd_poll_del(nd_poll_t *ndpl, int fd) { + for (nfds_t i = 0; i < ndpl->nfds; i++) { + if (ndpl->fds[i].fd == fd) { + // Remove the file descriptor by shifting the array + memmove(&ndpl->fds[i], &ndpl->fds[i + 1], (ndpl->nfds - i - 1) * sizeof(struct pollfd)); + ndpl->nfds--; + POINTERS_DEL(&ndpl->pointers, fd); + return true; + } + } + + return false; // File descriptor not found +} + +// Update an existing file descriptor in the event poll +bool nd_poll_upd(nd_poll_t *ndpl, int fd, nd_poll_event_t events, void *data) { + internal_fatal(POINTERS_GET(&ndpl->pointers, fd) == NULL, "File descriptor %d is not found - cannot modify", fd); + + for (nfds_t i = 0; i < ndpl->nfds; i++) { + if (ndpl->fds[i].fd == fd) { + struct pollfd *pfd = &ndpl->fds[i]; + pfd->events = 0; + if (events & ND_POLL_READ) pfd->events |= POLLIN; + if (events & ND_POLL_WRITE) pfd->events |= POLLOUT; + POINTERS_SET(&ndpl->pointers, fd, data); + return true; + } + } + + // File descriptor not found + return false; +} + +static inline bool nd_poll_get_next_event(nd_poll_t *ndpl, nd_poll_result_t *result) { + for (nfds_t i = ndpl->last_pos; i < ndpl->nfds; i++) { + if (ndpl->fds[i].revents != 0) { + + result->data = POINTERS_GET(&ndpl->pointers, ndpl->fds[i].fd); + + result->events = 0; + if (ndpl->fds[i].revents & (POLLIN|POLLPRI)) + result->events |= ND_POLL_READ; + + if (ndpl->fds[i].revents & POLLOUT) + result->events |= ND_POLL_WRITE; + + if (ndpl->fds[i].revents & POLLERR) + result->events |= ND_POLL_ERROR; + + if (ndpl->fds[i].revents & POLLHUP) + result->events |= ND_POLL_HUP; + + if (ndpl->fds[i].revents & POLLNVAL) + result->events |= ND_POLL_INVALID; + + ndpl->fds[i].revents = 0; // Clear the event after handling + ndpl->last_pos = i + 1; + return true; // Return only the first triggered event + } + } + + ndpl->last_pos = ndpl->nfds; + return false; +} + +// Rotate the fds array to prevent starvation +static inline void rotate_fds(nd_poll_t *ndpl) { + if (ndpl->nfds == 0 || ndpl->nfds == 1) + return; // No rotation needed for empty or single-entry arrays + + struct pollfd first = ndpl->fds[0]; + memmove(&ndpl->fds[0], &ndpl->fds[1], (ndpl->nfds - 1) * sizeof(struct pollfd)); + ndpl->fds[ndpl->nfds - 1] = first; +} + +// Wait for events +int nd_poll_wait(nd_poll_t *ndpl, int timeout_ms, nd_poll_result_t *result) { + if (nd_poll_get_next_event(ndpl, result)) + return 1; // Return immediately if there's a pending event + + do { + errno_clear(); + ndpl->last_pos = 0; + rotate_fds(ndpl); // Rotate the array on every wait + int ret = poll(ndpl->fds, ndpl->nfds, timeout_ms); + + if(unlikely(ret <= 0)) { + if (ret < 0) { + if(errno == EAGAIN || errno == EINTR) + continue; + + result->events = ND_POLL_OTHER_ERROR; + result->data = NULL; + return -1; + } + else { + result->events = ND_POLL_TIMEOUT; + result->data = NULL; + return 0; + } + } + + // Process the next event + if (nd_poll_get_next_event(ndpl, result)) + return 1; + + } while (true); +} + +// Destroy the event poll context +void nd_poll_destroy(nd_poll_t *ndpl) { + if (ndpl) { + free(ndpl->fds); + POINTERS_FREE(&ndpl->pointers, NULL); + freez(ndpl); + } +} + +#endif diff --git a/src/libnetdata/socket/nd-poll.h b/src/libnetdata/socket/nd-poll.h new file mode 100644 index 00000000000000..b8d99094c0f2e7 --- /dev/null +++ b/src/libnetdata/socket/nd-poll.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ND_POLL_H +#define NETDATA_ND_POLL_H + +#include "libnetdata/libnetdata.h" + +typedef enum { + ND_POLL_READ = 1 << 0, + ND_POLL_WRITE = 1 << 1, + ND_POLL_ERROR = 1 << 2, + ND_POLL_HUP = 1 << 3, + ND_POLL_INVALID = 1 << 4, + ND_POLL_TIMEOUT = 1 << 5, + ND_POLL_OTHER_ERROR = 1 << 6, +} nd_poll_event_t; + +typedef struct { + nd_poll_event_t events; + void *data; +} nd_poll_result_t; + +typedef struct nd_poll_t nd_poll_t; + +nd_poll_t *nd_poll_create(); +bool nd_poll_add(nd_poll_t *ndpl, int fd, nd_poll_event_t events, void *data); +bool nd_poll_del(nd_poll_t *ndpl, int fd); +bool nd_poll_upd(nd_poll_t *ndpl, int fd, nd_poll_event_t events, void *data); + +// returns -1 = error, 0 = timeout, 1 = event in result +int nd_poll_wait(nd_poll_t *ndpl, int timeout_ms, nd_poll_result_t *result); + +void nd_poll_destroy(nd_poll_t *ndpl); + +#endif //NETDATA_ND_POLL_H diff --git a/src/libnetdata/socket/nd-sock.c b/src/libnetdata/socket/nd-sock.c new file mode 100644 index 00000000000000..d8b5ebabf6f649 --- /dev/null +++ b/src/libnetdata/socket/nd-sock.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +ENUM_STR_MAP_DEFINE(ND_SOCK_ERROR) = { + { .id = ND_SOCK_ERR_NONE, .name = "NONE", }, + { .id = ND_SOCK_ERR_CONNECTION_REFUSED, .name = "CONNECTION REFUSED", }, + { .id = ND_SOCK_ERR_CANNOT_RESOLVE_HOSTNAME, .name = "CANNOT RESOLVE HOSTNAME", }, + { .id = ND_SOCK_ERR_FAILED_TO_CREATE_SOCKET, .name = "FAILED TO CREATE SOCKET", }, + { .id = ND_SOCK_ERR_NO_HOST_IN_DEFINITION, .name = "NO HOST IN DEFINITION", }, + { .id = ND_SOCK_ERR_POLL_ERROR, .name = "POLL ERROR", }, + { .id = ND_SOCK_ERR_TIMEOUT, .name = "TIMEOUT", }, + { .id = ND_SOCK_ERR_SSL_CANT_ESTABLISH_SSL_CONNECTION, .name = "CANT ESTABLISH SSL CONNECTION", }, + { .id = ND_SOCK_ERR_SSL_INVALID_CERTIFICATE, .name = "INVALID SSL CERTIFICATE", }, + { .id = ND_SOCK_ERR_SSL_FAILED_TO_OPEN, .name = "FAILED TO OPEN SSL", }, + { .id = ND_SOCK_ERR_THREAD_CANCELLED, .name = "THREAD CANCELLED", }, + { .id = ND_SOCK_ERR_NO_DESTINATION_AVAILABLE, .name = "NO PARENT AVAILABLE", }, + { .id = ND_SOCK_ERR_UNKNOWN_ERROR, .name = "UNKNOWN ERROR", }, + + // terminator + { .name = NULL, .id = 0 } +}; + +ENUM_STR_DEFINE_FUNCTIONS(ND_SOCK_ERROR, ND_SOCK_ERR_NONE, ""); + +// -------------------------------------------------------------------------------------------------------------------- + +static const unsigned char alpn_proto_list[] = { + 18, 'n', 'e', 't', 'd', 'a', 't', 'a', '_', 's', 't', 'r', 'e', 'a', 'm', '/', '2', '.', '0', + 8, 'h', 't', 't', 'p', '/', '1', '.', '1' +}; + +static bool nd_sock_open_ssl(ND_SOCK *s) { + if(!s) return false; + + if (netdata_ssl_open_ext(&s->ssl, s->ctx, s->fd, alpn_proto_list, sizeof(alpn_proto_list))) { + if(!netdata_ssl_connect(&s->ssl)) { + // couldn't connect + s->error = ND_SOCK_ERR_SSL_CANT_ESTABLISH_SSL_CONNECTION; + return false; + } + + if (s->verify_certificate && security_test_certificate(s->ssl.conn)) { + // certificate is not valid + s->error = ND_SOCK_ERR_SSL_INVALID_CERTIFICATE; + return false; + } + + return true; + } + + s->error = ND_SOCK_ERR_SSL_FAILED_TO_OPEN; + return false; +} + +bool nd_sock_connect_to_this(ND_SOCK *s, const char *definition, int default_port, time_t timeout, bool ssl) { + nd_sock_close(s); + + struct timeval tv = { + .tv_sec = timeout, + .tv_usec = 0 + }; + + s->fd = connect_to_this(definition, default_port, &tv); + if(s->fd < 0) { + s->error = -s->fd; + return false; + } + + if(ssl && s->ctx) { + if (!nd_sock_open_ssl(s)) { + close(s->fd); + s->fd = -1; + return false; + } + } + else + s->ssl = NETDATA_SSL_UNSET_CONNECTION; + + return true; +} + +ssize_t nd_sock_send_timeout(ND_SOCK *s, void *buf, size_t len, int flags, time_t timeout) { + switch(wait_on_socket_or_cancel_with_timeout(&s->ssl, s->fd, (int)(timeout * 1000), POLLOUT, NULL)) { + case 0: // data are waiting + break; + + case 1: // timeout + s->error = ND_SOCK_ERR_TIMEOUT; + return 0; + + case -1: // thread cancelled + s->error = ND_SOCK_ERR_THREAD_CANCELLED; + return -1; + + case 2: // poll() error + s->error = ND_SOCK_ERR_POLL_ERROR; + return -1; + + default: + s->error = ND_SOCK_ERR_UNKNOWN_ERROR; + return -1; + } + + if(s->ssl.conn) { + if (nd_sock_is_ssl(s)) + return netdata_ssl_write(&s->ssl, buf, len); + else + return -1; + } + + return send(s->fd, buf, len, flags); +} + +ssize_t nd_sock_recv_timeout(ND_SOCK *s, void *buf, size_t len, int flags, time_t timeout) { + switch(wait_on_socket_or_cancel_with_timeout(&s->ssl, s->fd, (int)(timeout * 1000), POLLIN, NULL)) { + case 0: // data are waiting + break; + + case 1: // timeout + s->error = ND_SOCK_ERR_TIMEOUT; + return 0; + + case -1: // thread cancelled + s->error = ND_SOCK_ERR_THREAD_CANCELLED; + return -1; + + case 2: // poll() error + s->error = ND_SOCK_ERR_POLL_ERROR; + return -1; + + default: + s->error = ND_SOCK_ERR_UNKNOWN_ERROR; + return -1; + } + + if (nd_sock_is_ssl(s)) + return netdata_ssl_read(&s->ssl, buf, len); + + return recv(s->fd, buf, len, flags); +} diff --git a/src/libnetdata/socket/nd-sock.h b/src/libnetdata/socket/nd-sock.h new file mode 100644 index 00000000000000..03c2e38e1083dc --- /dev/null +++ b/src/libnetdata/socket/nd-sock.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ND_SOCK_H +#define NETDATA_ND_SOCK_H + +#include "socket-peers.h" + +typedef enum __attribute__((packed)) { + ND_SOCK_ERR_NONE = 0, + ND_SOCK_ERR_CONNECTION_REFUSED, + ND_SOCK_ERR_CANNOT_RESOLVE_HOSTNAME, + ND_SOCK_ERR_FAILED_TO_CREATE_SOCKET, + ND_SOCK_ERR_NO_HOST_IN_DEFINITION, + ND_SOCK_ERR_POLL_ERROR, + ND_SOCK_ERR_TIMEOUT, + ND_SOCK_ERR_SSL_CANT_ESTABLISH_SSL_CONNECTION, + ND_SOCK_ERR_SSL_INVALID_CERTIFICATE, + ND_SOCK_ERR_SSL_FAILED_TO_OPEN, + ND_SOCK_ERR_THREAD_CANCELLED, + ND_SOCK_ERR_NO_DESTINATION_AVAILABLE, + ND_SOCK_ERR_UNKNOWN_ERROR, +} ND_SOCK_ERROR; + +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(ND_SOCK_ERROR); + +typedef struct nd_sock { + bool verify_certificate; + ND_SOCK_ERROR error; + int fd; + NETDATA_SSL ssl; + SSL_CTX *ctx; +} ND_SOCK; + +#define ND_SOCK_INIT(ssl_ctx, ssl_verify) (ND_SOCK){ \ + .verify_certificate = ssl_verify, \ + .error = ND_SOCK_ERR_NONE, \ + .fd = -1, \ + .ssl = NETDATA_SSL_UNSET_CONNECTION, \ + .ctx = ssl_ctx, \ +} + +static inline void nd_sock_init(ND_SOCK *s, SSL_CTX *ctx, bool verify_certificate) { + s->verify_certificate = verify_certificate; + s->error = ND_SOCK_ERR_NONE; + s->fd = -1; + s->ssl = NETDATA_SSL_UNSET_CONNECTION; + s->ctx = ctx; +} + +static inline bool nd_sock_is_ssl(ND_SOCK *s) { + return SSL_connection(&s->ssl); +} + +static inline SOCKET_PEERS nd_sock_socket_peers(ND_SOCK *s) { + return socket_peers(s->fd); +} + +static inline void nd_sock_close(ND_SOCK *s) { + netdata_ssl_close(&s->ssl); + + if(s->fd != -1) { + close(s->fd); + s->fd = -1; + } + + s->error = ND_SOCK_ERR_NONE; +} + +static inline ssize_t nd_sock_read(ND_SOCK *s, void *buf, size_t num, size_t retries) { + ssize_t rc; + do { + if (nd_sock_is_ssl(s)) + rc = netdata_ssl_read(&s->ssl, buf, num); + else + rc = read(s->fd, buf, num); + } + while(rc <= 0 && (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) && retries--); + + return rc; +} + +static inline ssize_t nd_sock_write(ND_SOCK *s, const void *buf, size_t num, size_t retries) { + ssize_t rc; + + do { + errno_clear(); + if (nd_sock_is_ssl(s)) + rc = netdata_ssl_write(&s->ssl, buf, num); + else + rc = write(s->fd, buf, num); + } + while(rc <= 0 && (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) && retries--); + + return rc; +} + +static inline ssize_t nd_sock_write_persist(ND_SOCK *s, const void *buf, const size_t num, size_t retries) { + const uint8_t *src = (const uint8_t *)buf; + ssize_t bytes = 0; + + do { + ssize_t sent = nd_sock_write(s, &src[bytes], (ssize_t)num - bytes, retries); + if(sent <= 0) return sent; + bytes += sent; + } + while(bytes < (ssize_t)num && retries--); + + return bytes; +} + +static inline ssize_t nd_sock_revc_nowait(ND_SOCK *s, void *buf, size_t num) { + if (nd_sock_is_ssl(s)) + return netdata_ssl_read(&s->ssl, buf, num); + else + return recv(s->fd, buf, num, MSG_DONTWAIT); +} + +static inline ssize_t nd_sock_send_nowait(ND_SOCK *s, void *buf, size_t num) { + if (nd_sock_is_ssl(s)) + return netdata_ssl_write(&s->ssl, buf, num); + else + return send(s->fd, buf, num, MSG_DONTWAIT); +} + +ssize_t nd_sock_send_timeout(ND_SOCK *s, void *buf, size_t len, int flags, time_t timeout); +ssize_t nd_sock_recv_timeout(ND_SOCK *s, void *buf, size_t len, int flags, time_t timeout); + +bool nd_sock_connect_to_this(ND_SOCK *s, const char *definition, int default_port, time_t timeout, bool ssl); + +static inline void cleanup_nd_sock_p(ND_SOCK *s) { + if(s) nd_sock_close(s); +} +#define CLEAN_ND_SOCK _cleanup_(cleanup_nd_sock_p) ND_SOCK + +#endif //NETDATA_ND_SOCK_H diff --git a/src/libnetdata/socket/poll-events.c b/src/libnetdata/socket/poll-events.c new file mode 100644 index 00000000000000..4e769c05c7e9ed --- /dev/null +++ b/src/libnetdata/socket/poll-events.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +// poll() based listener +// this should be the fastest possible listener for up to 100 sockets +// above 100, an epoll() interface is needed on Linux + +#define POLL_FDS_INCREASE_STEP 10 + +inline POLLINFO *poll_add_fd(POLLJOB *p + , int fd + , int socktype + , HTTP_ACL port_acl + , uint32_t flags + , const char *client_ip + , const char *client_port + , const char *client_host + , void *(*add_callback)(POLLINFO * /*pi*/, short int * /*events*/, void * /*data*/) + , void (*del_callback)(POLLINFO * /*pi*/) + , int (*rcv_callback)(POLLINFO * /*pi*/, short int * /*events*/) + , int (*snd_callback)(POLLINFO * /*pi*/, short int * /*events*/) + , void *data +) { + if(unlikely(fd < 0)) return NULL; + + //if(p->limit && p->used >= p->limit) { + // nd_log(NDLS_DAEMON, NDLP_WARNING, "Max sockets limit reached (%zu sockets), dropping connection", p->used); + // close(fd); + // return NULL; + //} + + if(unlikely(!p->first_free)) { + size_t new_slots = p->slots + POLL_FDS_INCREASE_STEP; + + p->fds = reallocz(p->fds, sizeof(struct pollfd) * new_slots); + p->inf = reallocz(p->inf, sizeof(POLLINFO) * new_slots); + + // reset all the newly added slots + ssize_t i; + for(i = new_slots - 1; i >= (ssize_t)p->slots ; i--) { + p->fds[i].fd = -1; + p->fds[i].events = 0; + p->fds[i].revents = 0; + + p->inf[i].p = p; + p->inf[i].slot = (size_t)i; + p->inf[i].flags = 0; + p->inf[i].socktype = -1; + p->inf[i].port_acl = -1; + + p->inf[i].client_ip = NULL; + p->inf[i].client_port = NULL; + p->inf[i].client_host = NULL; + p->inf[i].del_callback = p->del_callback; + p->inf[i].rcv_callback = p->rcv_callback; + p->inf[i].snd_callback = p->snd_callback; + p->inf[i].data = NULL; + + // link them so that the first free will be earlier in the array + // (we loop decrementing i) + p->inf[i].next = p->first_free; + p->first_free = &p->inf[i]; + } + + p->slots = new_slots; + } + + POLLINFO *pi = p->first_free; + p->first_free = p->first_free->next; + + struct pollfd *pf = &p->fds[pi->slot]; + pf->fd = fd; + pf->events = POLLIN; + pf->revents = 0; + + pi->fd = fd; + pi->p = p; + pi->socktype = socktype; + pi->port_acl = port_acl; + pi->flags = flags; + pi->next = NULL; + pi->client_ip = strdupz(client_ip); + pi->client_port = strdupz(client_port); + pi->client_host = strdupz(client_host); + + pi->del_callback = del_callback; + pi->rcv_callback = rcv_callback; + pi->snd_callback = snd_callback; + + pi->connected_t = now_boottime_sec(); + pi->last_received_t = 0; + pi->last_sent_t = 0; + pi->last_sent_t = 0; + pi->recv_count = 0; + pi->send_count = 0; + + p->used++; + if(unlikely(pi->slot > p->max)) + p->max = pi->slot; + + if(pi->flags & POLLINFO_FLAG_CLIENT_SOCKET) { + pi->data = add_callback(pi, &pf->events, data); + } + + if(pi->flags & POLLINFO_FLAG_SERVER_SOCKET) { + p->min = pi->slot; + } + + return pi; +} + +inline void poll_close_fd(POLLINFO *pi) { + POLLJOB *p = pi->p; + + struct pollfd *pf = &p->fds[pi->slot]; + + if(unlikely(pf->fd == -1)) return; + + if(pi->flags & POLLINFO_FLAG_CLIENT_SOCKET) { + pi->del_callback(pi); + + if(likely(!(pi->flags & POLLINFO_FLAG_DONT_CLOSE))) { + if(close(pf->fd) == -1) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to close() poll_events() socket %d", + pf->fd); + } + } + + pf->fd = -1; + pf->events = 0; + pf->revents = 0; + + pi->fd = -1; + pi->socktype = -1; + pi->flags = 0; + pi->data = NULL; + + pi->del_callback = NULL; + pi->rcv_callback = NULL; + pi->snd_callback = NULL; + + freez(pi->client_ip); + pi->client_ip = NULL; + + freez(pi->client_port); + pi->client_port = NULL; + + freez(pi->client_host); + pi->client_host = NULL; + + pi->next = p->first_free; + p->first_free = pi; + + p->used--; + if(unlikely(p->max == pi->slot)) { + p->max = p->min; + ssize_t i; + for(i = (ssize_t)pi->slot; i > (ssize_t)p->min ;i--) { + if (unlikely(p->fds[i].fd != -1)) { + p->max = (size_t)i; + break; + } + } + } +} + +void *poll_default_add_callback(POLLINFO *pi, short int *events, void *data) { + (void)pi; + (void)events; + (void)data; + + return NULL; +} + +void poll_default_del_callback(POLLINFO *pi) { + if(pi->data) + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: internal error: del_callback_default() called with data pointer - possible memory leak"); +} + +int poll_default_rcv_callback(POLLINFO *pi, short int *events) { + *events |= POLLIN; + + char buffer[1024 + 1]; + + ssize_t rc; + do { + rc = recv(pi->fd, buffer, 1024, MSG_DONTWAIT); + if (rc < 0) { + // read failed + if (errno != EWOULDBLOCK && errno != EAGAIN) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: poll_default_rcv_callback(): recv() failed with %zd.", + rc); + + return -1; + } + } else if (rc) { + // data received + nd_log(NDLS_DAEMON, NDLP_WARNING, + "POLLFD: internal error: poll_default_rcv_callback() is discarding %zd bytes received on socket %d", + rc, pi->fd); + } + } while (rc != -1); + + return 0; +} + +int poll_default_snd_callback(POLLINFO *pi, short int *events) { + *events &= ~POLLOUT; + + nd_log(NDLS_DAEMON, NDLP_WARNING, + "POLLFD: internal error: poll_default_snd_callback(): nothing to send on socket %d", + pi->fd); + + return 0; +} + +void poll_default_tmr_callback(void *timer_data) { + (void)timer_data; +} + +static void poll_events_cleanup(void *pptr) { + POLLJOB *p = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!p) return; + + for(size_t i = 0 ; i <= p->max ; i++) { + POLLINFO *pi = &p->inf[i]; + poll_close_fd(pi); + } + + freez(p->fds); + freez(p->inf); +} + +static int poll_process_error(POLLINFO *pi, struct pollfd *pf, short int revents) { + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_TXT(NDF_SRC_IP, pi->client_ip), + ND_LOG_FIELD_TXT(NDF_SRC_PORT, pi->client_port), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "POLLFD: LISTENER: received %s %s %s on socket at slot %zu (fd %d) client '%s' port '%s' expecting %s %s %s, having %s %s %s" + , revents & POLLERR ? "POLLERR" : "" + , revents & POLLHUP ? "POLLHUP" : "" + , revents & POLLNVAL ? "POLLNVAL" : "" + , pi->slot + , pi->fd + , pi->client_ip ? pi->client_ip : "" + , pi->client_port ? pi->client_port : "" + , pf->events & POLLIN ? "POLLIN" : "", pf->events & POLLOUT ? "POLLOUT" : "", pf->events & POLLPRI ? "POLLPRI" : "" + , revents & POLLIN ? "POLLIN" : "", revents & POLLOUT ? "POLLOUT" : "", revents & POLLPRI ? "POLLPRI" : "" + ); + + pf->events = 0; + poll_close_fd(pi); + return 1; +} + +static inline int poll_process_send(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, time_t now) { + pi->last_sent_t = now; + pi->send_count++; + + pf->events = 0; + + // remember the slot, in case we need to close it later + // the callback may manipulate the socket list and our pf and pi pointers may be invalid after that call + size_t slot = pi->slot; + + if (unlikely(pi->snd_callback(pi, &pf->events) == -1)) + poll_close_fd(&p->inf[slot]); + + // IMPORTANT: + // pf and pi may be invalid below this point, they may have been reallocated. + + return 1; +} + +static inline int poll_process_tcp_read(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, time_t now) { + pi->last_received_t = now; + pi->recv_count++; + + pf->events = 0; + + // remember the slot, in case we need to close it later + // the callback may manipulate the socket list and our pf and pi pointers may be invalid after that call + size_t slot = pi->slot; + + if (pi->rcv_callback(pi, &pf->events) == -1) + poll_close_fd(&p->inf[slot]); + + // IMPORTANT: + // pf and pi may be invalid below this point, they may have been reallocated. + + return 1; +} + +static inline int poll_process_udp_read(POLLINFO *pi, struct pollfd *pf, time_t now __maybe_unused) { + pi->last_received_t = now; + pi->recv_count++; + + // TODO: access_list is not applied to UDP + // but checking the access list on every UDP packet will destroy + // performance, especially for statsd. + + pf->events = 0; + if(pi->rcv_callback(pi, &pf->events) == -1) + return 0; + + // IMPORTANT: + // pf and pi may be invalid below this point, they may have been reallocated. + + return 1; +} + +static int poll_process_new_tcp_connection(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, time_t now) { + pi->last_received_t = now; + pi->recv_count++; + + char client_ip[INET6_ADDRSTRLEN] = ""; + char client_port[NI_MAXSERV] = ""; + char client_host[NI_MAXHOST] = ""; + +#ifdef SOCK_NONBLOCK + int flags = SOCK_NONBLOCK; +#else + int flags = 0; +#endif + + int nfd = accept_socket( + pf->fd, flags, + client_ip, INET6_ADDRSTRLEN, client_port,NI_MAXSERV, client_host, NI_MAXHOST, + p->access_list, p->allow_dns + ); + +#ifndef SOCK_NONBLOCK + if (nfd > 0) { + int flags = fcntl(nfd, F_GETFL); + (void)fcntl(nfd, F_SETFL, flags| O_NONBLOCK); + } +#endif + + if (unlikely(nfd < 0)) { + // accept failed + + if(unlikely(errno == EMFILE)) { + nd_log_limit_static_global_var(erl, 10, 1000); + nd_log_limit(&erl, NDLS_DAEMON, NDLP_ERR, + "POLLFD: LISTENER: too many open files - used by this thread %zu, max for this thread %zu", + p->used, p->limit); + } + else if(unlikely(errno != EWOULDBLOCK && errno != EAGAIN)) + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: LISTENER: accept() failed."); + + } + else { + // accept ok + + poll_add_fd(p + , nfd + , SOCK_STREAM + , pi->port_acl + , POLLINFO_FLAG_CLIENT_SOCKET + , client_ip + , client_port + , client_host + , p->add_callback + , p->del_callback + , p->rcv_callback + , p->snd_callback + , NULL + ); + + // IMPORTANT: + // pf and pi may be invalid below this point, they may have been reallocated. + + return 1; + } + + return 0; +} + +void poll_events(LISTEN_SOCKETS *sockets + , void *(*add_callback)(POLLINFO * /*pi*/, short int * /*events*/, void * /*data*/) + , void (*del_callback)(POLLINFO * /*pi*/) + , int (*rcv_callback)(POLLINFO * /*pi*/, short int * /*events*/) + , int (*snd_callback)(POLLINFO * /*pi*/, short int * /*events*/) + , void (*tmr_callback)(void * /*timer_data*/) + , bool (*check_to_stop_callback)(void) + , SIMPLE_PATTERN *access_list + , int allow_dns + , void *data + , time_t tcp_request_timeout_seconds + , time_t tcp_idle_timeout_seconds + , time_t timer_milliseconds + , void *timer_data + , size_t max_tcp_sockets +) { + if(!sockets || !sockets->opened) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: internal error: no listening sockets are opened"); + return; + } + + if(timer_milliseconds <= 0) timer_milliseconds = 0; + + int retval; + + POLLJOB p = { + .slots = 0, + .used = 0, + .max = 0, + .limit = max_tcp_sockets, + .fds = NULL, + .inf = NULL, + .first_free = NULL, + + .complete_request_timeout = tcp_request_timeout_seconds, + .idle_timeout = tcp_idle_timeout_seconds, + .checks_every = (tcp_idle_timeout_seconds / 3) + 1, + + .access_list = access_list, + .allow_dns = allow_dns, + + .timer_milliseconds = timer_milliseconds, + .timer_data = timer_data, + + .add_callback = add_callback?add_callback:poll_default_add_callback, + .del_callback = del_callback?del_callback:poll_default_del_callback, + .rcv_callback = rcv_callback?rcv_callback:poll_default_rcv_callback, + .snd_callback = snd_callback?snd_callback:poll_default_snd_callback, + .tmr_callback = tmr_callback?tmr_callback:poll_default_tmr_callback + }; + + size_t i; + for(i = 0; i < sockets->opened ;i++) { + + POLLINFO *pi = poll_add_fd(&p + , sockets->fds[i] + , sockets->fds_types[i] + , sockets->fds_acl_flags[i] + , POLLINFO_FLAG_SERVER_SOCKET + , (sockets->fds_names[i])?sockets->fds_names[i]:"UNKNOWN" + , "" + , "" + , p.add_callback + , p.del_callback + , p.rcv_callback + , p.snd_callback + , NULL + ); + + pi->data = data; + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "POLLFD: LISTENER: listening on '%s'", + (sockets->fds_names[i])?sockets->fds_names[i]:"UNKNOWN"); + } + + int listen_sockets_active = 1; + + time_t last_check = now_boottime_sec(); + + usec_t timer_usec = timer_milliseconds * USEC_PER_MS; + usec_t now_usec = 0, next_timer_usec = 0, last_timer_usec = 0; + (void)last_timer_usec; + + if(unlikely(timer_usec)) { + now_usec = now_boottime_usec(); + next_timer_usec = now_usec - (now_usec % timer_usec) + timer_usec; + } + + CLEANUP_FUNCTION_REGISTER(poll_events_cleanup) cleanup_ptr = &p; + + while(!check_to_stop_callback() && !nd_thread_signaled_to_cancel()) { + if(unlikely(timer_usec)) { + now_usec = now_boottime_usec(); + + if(unlikely(timer_usec && now_usec >= next_timer_usec)) { + last_timer_usec = now_usec; + p.tmr_callback(p.timer_data); + now_usec = now_boottime_usec(); + next_timer_usec = now_usec - (now_usec % timer_usec) + timer_usec; + } + } + + // enable or disable the TCP listening sockets, based on the current number of sockets used and the limit set + if((listen_sockets_active && (p.limit && p.used >= p.limit)) || (!listen_sockets_active && (!p.limit || p.used < p.limit))) { + listen_sockets_active = !listen_sockets_active; + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "%s listening sockets (used TCP sockets %zu, max allowed for this worker %zu)", + (listen_sockets_active)?"ENABLING":"DISABLING", p.used, p.limit); + + for (i = 0; i <= p.max; i++) { + if(p.inf[i].flags & POLLINFO_FLAG_SERVER_SOCKET && p.inf[i].socktype == SOCK_STREAM) { + p.fds[i].events = (short int) ((listen_sockets_active) ? POLLIN : 0); + } + } + } + + retval = poll(p.fds, p.max + 1, ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS); + time_t now = now_boottime_sec(); + + if(unlikely(retval == -1)) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: LISTENER: poll() failed while waiting on %zu sockets.", + p.max + 1); + + break; + } + else if(unlikely(!retval)) { + // timeout + ; + } + else { + POLLINFO *pi; + struct pollfd *pf; + size_t idx, processed = 0; + short int revents; + + // keep fast lookup arrays per function + // to avoid looping through the entire list every time + size_t sends[p.max + 1], sends_max = 0; + size_t reads[p.max + 1], reads_max = 0; + size_t conns[p.max + 1], conns_max = 0; + size_t udprd[p.max + 1], udprd_max = 0; + + for (i = 0; i <= p.max; i++) { + pi = &p.inf[i]; + pf = &p.fds[i]; + revents = pf->revents; + + if(unlikely(revents == 0 || pf->fd == -1)) + continue; + + if (unlikely(revents & (POLLERR|POLLHUP|POLLNVAL))) { + // something is wrong to one of our sockets + + pf->revents = 0; + processed += poll_process_error(pi, pf, revents); + } + else if (likely(revents & POLLOUT)) { + // a client is ready to receive data + + sends[sends_max++] = i; + } + else if (likely(revents & (POLLIN|POLLPRI))) { + if (pi->flags & POLLINFO_FLAG_CLIENT_SOCKET) { + // a client sent data to us + + reads[reads_max++] = i; + } + else if (pi->flags & POLLINFO_FLAG_SERVER_SOCKET) { + // something is coming to our server sockets + + if(pi->socktype == SOCK_DGRAM) { + // UDP receive, directly on our listening socket + + udprd[udprd_max++] = i; + } + else if(pi->socktype == SOCK_STREAM) { + // new TCP connection + + conns[conns_max++] = i; + } + else + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: LISTENER: server slot %zu (fd %d) connection from %s port %s using unhandled socket type %d." + , i + , pi->fd + , pi->client_ip ? pi->client_ip : "" + , pi->client_port ? pi->client_port : "" + , pi->socktype + ); + } + else + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: LISTENER: client slot %zu (fd %d) data from %s port %s using flags %08X is neither client nor server." + , i + , pi->fd + , pi->client_ip ? pi->client_ip : "" + , pi->client_port ? pi->client_port : "" + , pi->flags + ); + } + else + nd_log(NDLS_DAEMON, NDLP_ERR, + "POLLFD: LISTENER: socket slot %zu (fd %d) client %s port %s unhandled event id %d." + , i + , pi->fd + , pi->client_ip ? pi->client_ip : "" + , pi->client_port ? pi->client_port : "" + , revents + ); + } + + // process sends + for (idx = 0; idx < sends_max; idx++) { + i = sends[idx]; + pi = &p.inf[i]; + pf = &p.fds[i]; + pf->revents = 0; + processed += poll_process_send(&p, pi, pf, now); + } + + // process UDP reads + for (idx = 0; idx < udprd_max; idx++) { + i = udprd[idx]; + pi = &p.inf[i]; + pf = &p.fds[i]; + pf->revents = 0; + processed += poll_process_udp_read(pi, pf, now); + } + + // process TCP reads + for (idx = 0; idx < reads_max; idx++) { + i = reads[idx]; + pi = &p.inf[i]; + pf = &p.fds[i]; + pf->revents = 0; + processed += poll_process_tcp_read(&p, pi, pf, now); + } + + if(!processed && (!p.limit || p.used < p.limit)) { + // nothing processed above (rcv, snd) and we have room for another TCP connection + // so, accept one TCP connection + for (idx = 0; idx < conns_max; idx++) { + i = conns[idx]; + pi = &p.inf[i]; + pf = &p.fds[i]; + pf->revents = 0; + if (poll_process_new_tcp_connection(&p, pi, pf, now)) + break; + } + } + } + + if(unlikely(p.checks_every > 0 && now - last_check > p.checks_every)) { + last_check = now; + + // cleanup old sockets + for(i = 0; i <= p.max; i++) { + POLLINFO *pi = &p.inf[i]; + + if(likely(pi->flags & POLLINFO_FLAG_CLIENT_SOCKET)) { + if (unlikely(pi->send_count == 0 && p.complete_request_timeout > 0 && (now - pi->connected_t) >= p.complete_request_timeout)) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "POLLFD: LISTENER: client slot %zu (fd %d) from %s port %s has not sent a complete request in %zu seconds - closing it. " + , i + , pi->fd + , pi->client_ip ? pi->client_ip : "" + , pi->client_port ? pi->client_port : "" + , (size_t) p.complete_request_timeout + ); + poll_close_fd(pi); + } + else if(unlikely(pi->recv_count && p.idle_timeout > 0 && now - ((pi->last_received_t > pi->last_sent_t) ? pi->last_received_t : pi->last_sent_t) >= p.idle_timeout )) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "POLLFD: LISTENER: client slot %zu (fd %d) from %s port %s is idle for more than %zu seconds - closing it. " + , i + , pi->fd + , pi->client_ip ? pi->client_ip : "" + , pi->client_port ? pi->client_port : "" + , (size_t) p.idle_timeout + ); + poll_close_fd(pi); + } + } + } + } + } +} diff --git a/src/libnetdata/socket/poll-events.h b/src/libnetdata/socket/poll-events.h new file mode 100644 index 00000000000000..c34ea4e3829c54 --- /dev/null +++ b/src/libnetdata/socket/poll-events.h @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_POLL_EVENTS_H +#define NETDATA_POLL_EVENTS_H + +#define POLLINFO_FLAG_SERVER_SOCKET 0x00000001 +#define POLLINFO_FLAG_CLIENT_SOCKET 0x00000002 +#define POLLINFO_FLAG_DONT_CLOSE 0x00000004 + +typedef struct poll POLLJOB; + +typedef struct pollinfo { + POLLJOB *p; // the parent + size_t slot; // the slot id + + int fd; // the file descriptor + int socktype; // the client socket type + HTTP_ACL port_acl; // the access lists permitted on this web server port (it's -1 for client sockets) + char *client_ip; // Max INET6_ADDRSTRLEN bytes + char *client_port; // Max NI_MAXSERV bytes + char *client_host; // Max NI_MAXHOST bytes + + time_t connected_t; // the time the socket connected + time_t last_received_t; // the time the socket last received data + time_t last_sent_t; // the time the socket last sent data + + size_t recv_count; // the number of times the socket was ready for inbound traffic + size_t send_count; // the number of times the socket was ready for outbound traffic + + uint32_t flags; // internal flags + + // callbacks for this socket + void (*del_callback)(struct pollinfo *pi); + int (*rcv_callback)(struct pollinfo *pi, short int *events); + int (*snd_callback)(struct pollinfo *pi, short int *events); + + // the user data + void *data; + + // linking of free pollinfo structures + // for quickly finding the next available + // this is like a stack, it grows and shrinks + // (with gaps - lower empty slots are preferred) + struct pollinfo *next; +} POLLINFO; + +struct poll { + size_t slots; + size_t used; + size_t min; + size_t max; + + size_t limit; + + time_t complete_request_timeout; + time_t idle_timeout; + time_t checks_every; + + time_t timer_milliseconds; + void *timer_data; + + struct pollfd *fds; + struct pollinfo *inf; + struct pollinfo *first_free; + + SIMPLE_PATTERN *access_list; + int allow_dns; + + void *(*add_callback)(POLLINFO *pi, short int *events, void *data); + void (*del_callback)(POLLINFO *pi); + int (*rcv_callback)(POLLINFO *pi, short int *events); + int (*snd_callback)(POLLINFO *pi, short int *events); + void (*tmr_callback)(void *timer_data); +}; + +#define pollinfo_from_slot(p, slot) (&((p)->inf[(slot)])) + +int poll_default_snd_callback(POLLINFO *pi, short int *events); +int poll_default_rcv_callback(POLLINFO *pi, short int *events); +void poll_default_del_callback(POLLINFO *pi); +void *poll_default_add_callback(POLLINFO *pi, short int *events, void *data); + +POLLINFO *poll_add_fd(POLLJOB *p + , int fd + , int socktype + , HTTP_ACL port_acl + , uint32_t flags + , const char *client_ip + , const char *client_port + , const char *client_host + , void *(*add_callback)(POLLINFO *pi, short int *events, void *data) + , void (*del_callback)(POLLINFO *pi) + , int (*rcv_callback)(POLLINFO *pi, short int *events) + , int (*snd_callback)(POLLINFO *pi, short int *events) + , void *data +); +void poll_close_fd(POLLINFO *pi); + +void poll_events(LISTEN_SOCKETS *sockets + , void *(*add_callback)(POLLINFO *pi, short int *events, void *data) + , void (*del_callback)(POLLINFO *pi) + , int (*rcv_callback)(POLLINFO *pi, short int *events) + , int (*snd_callback)(POLLINFO *pi, short int *events) + , void (*tmr_callback)(void *timer_data) + , bool (*check_to_stop_callback)(void) + , SIMPLE_PATTERN *access_list + , int allow_dns + , void *data + , time_t tcp_request_timeout_seconds + , time_t tcp_idle_timeout_seconds + , time_t timer_milliseconds + , void *timer_data + , size_t max_tcp_sockets +); + +#endif //NETDATA_POLL_EVENTS_H diff --git a/src/libnetdata/socket/security.c b/src/libnetdata/socket/security.c index 33bf22d75491ae..56e5d7c7bb0277 100644 --- a/src/libnetdata/socket/security.c +++ b/src/libnetdata/socket/security.c @@ -731,6 +731,9 @@ int security_test_certificate(SSL *ssl) { * @return It returns 0 on success and -1 otherwise. */ int ssl_security_location_for_context(SSL_CTX *ctx, const char *file, const char *path) { + if(file && !*file) file = NULL; + if(path && !*path) path = NULL; + int load_custom = 1, load_default = 1; if (file || path) { if(!SSL_CTX_load_verify_locations(ctx, file, path)) { diff --git a/src/libnetdata/socket/socket-peers.c b/src/libnetdata/socket/socket-peers.c new file mode 100644 index 00000000000000..06499110423f4f --- /dev/null +++ b/src/libnetdata/socket/socket-peers.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +SOCKET_PEERS socket_peers(int sock_fd) { + SOCKET_PEERS peers; + + if(sock_fd < 0) { + strncpyz(peers.peer.ip, "not connected", sizeof(peers.peer.ip) - 1); + peers.peer.port = 0; + + strncpyz(peers.local.ip, "not connected", sizeof(peers.local.ip) - 1); + peers.local.port = 0; + + return peers; + } + + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + // Get peer info + if (getpeername(sock_fd, (struct sockaddr *)&addr, &addr_len) == 0) { + if (addr.ss_family == AF_INET) { // IPv4 + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + inet_ntop(AF_INET, &s->sin_addr, peers.peer.ip, sizeof(peers.peer.ip)); + peers.peer.port = ntohs(s->sin_port); + } + else { // IPv6 + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; + inet_ntop(AF_INET6, &s->sin6_addr, peers.peer.ip, sizeof(peers.peer.ip)); + peers.peer.port = ntohs(s->sin6_port); + } + } + else { + strncpyz(peers.peer.ip, "unknown", sizeof(peers.peer.ip) - 1); + peers.peer.port = 0; + } + + // Get local info + addr_len = sizeof(addr); + if (getsockname(sock_fd, (struct sockaddr *)&addr, &addr_len) == 0) { + if (addr.ss_family == AF_INET) { // IPv4 + struct sockaddr_in *s = (struct sockaddr_in *) &addr; + inet_ntop(AF_INET, &s->sin_addr, peers.local.ip, sizeof(peers.local.ip)); + peers.local.port = ntohs(s->sin_port); + } else { // IPv6 + struct sockaddr_in6 *s = (struct sockaddr_in6 *) &addr; + inet_ntop(AF_INET6, &s->sin6_addr, peers.local.ip, sizeof(peers.local.ip)); + peers.local.port = ntohs(s->sin6_port); + } + } + else { + strncpyz(peers.local.ip, "unknown", sizeof(peers.local.ip) - 1); + peers.local.port = 0; + } + + return peers; +} diff --git a/src/libnetdata/socket/socket-peers.h b/src/libnetdata/socket/socket-peers.h new file mode 100644 index 00000000000000..dfeb8e2ae2974d --- /dev/null +++ b/src/libnetdata/socket/socket-peers.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_SOCKET_PEERS_H +#define NETDATA_SOCKET_PEERS_H + +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +typedef struct { + struct { + char ip[INET6_ADDRSTRLEN]; + int port; + } local; + + struct { + char ip[INET6_ADDRSTRLEN]; + int port; + } peer; +} SOCKET_PEERS; + +SOCKET_PEERS socket_peers(int sock_fd); + +#endif //NETDATA_SOCKET_PEERS_H diff --git a/src/libnetdata/socket/socket.c b/src/libnetdata/socket/socket.c index 3b0d1f824f808f..8f5126cb38e43d 100644 --- a/src/libnetdata/socket/socket.c +++ b/src/libnetdata/socket/socket.c @@ -45,62 +45,6 @@ bool ip_to_hostname(const char *ip, char *dst, size_t dst_len) { return true; } -SOCKET_PEERS socket_peers(int sock_fd) { - SOCKET_PEERS peers; - - if(sock_fd < 0) { - strncpyz(peers.peer.ip, "not connected", sizeof(peers.peer.ip) - 1); - peers.peer.port = 0; - - strncpyz(peers.local.ip, "not connected", sizeof(peers.local.ip) - 1); - peers.local.port = 0; - - return peers; - } - - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - - // Get peer info - if (getpeername(sock_fd, (struct sockaddr *)&addr, &addr_len) == 0) { - if (addr.ss_family == AF_INET) { // IPv4 - struct sockaddr_in *s = (struct sockaddr_in *)&addr; - inet_ntop(AF_INET, &s->sin_addr, peers.peer.ip, sizeof(peers.peer.ip)); - peers.peer.port = ntohs(s->sin_port); - } - else { // IPv6 - struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr; - inet_ntop(AF_INET6, &s->sin6_addr, peers.peer.ip, sizeof(peers.peer.ip)); - peers.peer.port = ntohs(s->sin6_port); - } - } - else { - strncpyz(peers.peer.ip, "unknown", sizeof(peers.peer.ip) - 1); - peers.peer.port = 0; - } - - // Get local info - addr_len = sizeof(addr); - if (getsockname(sock_fd, (struct sockaddr *)&addr, &addr_len) == 0) { - if (addr.ss_family == AF_INET) { // IPv4 - struct sockaddr_in *s = (struct sockaddr_in *) &addr; - inet_ntop(AF_INET, &s->sin_addr, peers.local.ip, sizeof(peers.local.ip)); - peers.local.port = ntohs(s->sin_port); - } else { // IPv6 - struct sockaddr_in6 *s = (struct sockaddr_in6 *) &addr; - inet_ntop(AF_INET6, &s->sin6_addr, peers.local.ip, sizeof(peers.local.ip)); - peers.local.port = ntohs(s->sin6_port); - } - } - else { - strncpyz(peers.local.ip, "unknown", sizeof(peers.local.ip) - 1); - peers.local.port = 0; - } - - return peers; -} - - // -------------------------------------------------------------------------------------------------------------------- // various library calls @@ -174,1028 +118,92 @@ int sock_setnonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL); - flags |= O_NONBLOCK; - - int ret = fcntl(fd, F_SETFL, flags); - if(ret < 0) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to set O_NONBLOCK on socket %d", - fd); - - return ret; -} - -int sock_delnonblock(int fd) { - int flags; - - flags = fcntl(fd, F_GETFL); - flags &= ~O_NONBLOCK; - - int ret = fcntl(fd, F_SETFL, flags); - if(ret < 0) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to remove O_NONBLOCK on socket %d", - fd); - - return ret; -} - -int sock_setreuse(int fd, int reuse) { - int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); - - if(ret == -1) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to set SO_REUSEADDR on socket %d", - fd); - - return ret; -} - -void sock_setcloexec(int fd) -{ - UNUSED(fd); - int flags = fcntl(fd, F_GETFD); - if (flags != -1) - (void) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); -} - -int sock_setreuse_port(int fd __maybe_unused, int reuse __maybe_unused) { - int ret; - -#ifdef SO_REUSEPORT - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)); - if(ret == -1 && errno != ENOPROTOOPT) - nd_log(NDLS_DAEMON, NDLP_ERR, - "failed to set SO_REUSEPORT on socket %d", - fd); -#else - ret = -1; -#endif - - return ret; -} - -int sock_enlarge_in(int fd) { - int ret, bs = LARGE_SOCK_SIZE; - - ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bs, sizeof(bs)); - - if(ret == -1) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to set SO_RCVBUF on socket %d", - fd); - - return ret; -} - -int sock_enlarge_out(int fd) { - int ret, bs = LARGE_SOCK_SIZE; - ret = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bs, sizeof(bs)); - - if(ret == -1) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to set SO_SNDBUF on socket %d", - fd); - - return ret; -} - - -// -------------------------------------------------------------------------------------------------------------------- - -char *strdup_client_description(int family, const char *protocol, const char *ip, uint16_t port) { - char buffer[100 + 1]; - - switch(family) { - case AF_INET: - snprintfz(buffer, sizeof(buffer) - 1, "%s:%s:%d", protocol, ip, port); - break; - - case AF_INET6: - default: - snprintfz(buffer, sizeof(buffer) - 1, "%s:[%s]:%d", protocol, ip, port); - break; - - case AF_UNIX: - snprintfz(buffer, sizeof(buffer) - 1, "%s:%s", protocol, ip); - break; - } - - return strdupz(buffer); -} - -// -------------------------------------------------------------------------------------------------------------------- -// listening sockets - -int create_listen_socket_unix(const char *path, int listen_backlog) { - int sock; - - sock = socket(AF_UNIX, SOCK_STREAM | DEFAULT_SOCKET_FLAGS, 0); - if(sock < 0) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: UNIX socket() on path '%s' failed.", - path); - - return -1; - } - - sock_setnonblock(sock); - sock_setcloexec(sock); - sock_enlarge_in(sock); - - struct sockaddr_un name; - memset(&name, 0, sizeof(struct sockaddr_un)); - name.sun_family = AF_UNIX; - strncpy(name.sun_path, path, sizeof(name.sun_path)-1); - - errno_clear(); - if (unlink(path) == -1 && errno != ENOENT) - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: failed to remove existing (probably obsolete or left-over) file on UNIX socket path '%s'.", - path); - - if(bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { - close(sock); - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: UNIX bind() on path '%s' failed.", - path); - - return -1; - } - - // we have to chmod this to 0777 so that the client will be able - // to read from and write to this socket. - if(chmod(path, 0777) == -1) - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: failed to chmod() socket file '%s'.", - path); - - if(listen(sock, listen_backlog) < 0) { - close(sock); - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: UNIX listen() on path '%s' failed.", - path); - - return -1; - } - - return sock; -} - -int create_listen_socket4(int socktype, const char *ip, uint16_t port, int listen_backlog) { - int sock; - - sock = socket(AF_INET, socktype | DEFAULT_SOCKET_FLAGS, 0); - if(sock < 0) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: IPv4 socket() on ip '%s' port %d, socktype %d failed.", - ip, port, socktype); - - return -1; - } - sock_setreuse(sock, 1); - sock_setreuse_port(sock, 0); - sock_setnonblock(sock); - sock_setcloexec(sock); - sock_enlarge_in(sock); - - struct sockaddr_in name; - memset(&name, 0, sizeof(struct sockaddr_in)); - name.sin_family = AF_INET; - name.sin_port = htons (port); - - int ret = inet_pton(AF_INET, ip, (void *)&name.sin_addr.s_addr); - if(ret != 1) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Failed to convert IP '%s' to a valid IPv4 address.", - ip); - - close(sock); - return -1; - } - - if(bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { - close(sock); - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: IPv4 bind() on ip '%s' port %d, socktype %d failed.", - ip, port, socktype); - - return -1; - } - - if(socktype == SOCK_STREAM && listen(sock, listen_backlog) < 0) { - close(sock); - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: IPv4 listen() on ip '%s' port %d, socktype %d failed.", - ip, port, socktype); - - return -1; - } - - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "LISTENER: Listening on IPv4 ip '%s' port %d, socktype %d", - ip, port, socktype); - - return sock; -} - -int create_listen_socket6(int socktype, uint32_t scope_id, const char *ip, int port, int listen_backlog) { - int sock; - int ipv6only = 1; - - sock = socket(AF_INET6, socktype | DEFAULT_SOCKET_FLAGS, 0); - if (sock < 0) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: IPv6 socket() on ip '%s' port %d, socktype %d, failed.", - ip, port, socktype); - - return -1; - } - sock_setreuse(sock, 1); - sock_setreuse_port(sock, 0); - sock_setnonblock(sock); - sock_setcloexec(sock); - sock_enlarge_in(sock); - - /* IPv6 only */ - if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&ipv6only, sizeof(ipv6only)) != 0) - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Cannot set IPV6_V6ONLY on ip '%s' port %d, socktype %d.", - ip, port, socktype); - - struct sockaddr_in6 name; - memset(&name, 0, sizeof(struct sockaddr_in6)); - name.sin6_family = AF_INET6; - name.sin6_port = htons ((uint16_t) port); - name.sin6_scope_id = scope_id; - - int ret = inet_pton(AF_INET6, ip, (void *)&name.sin6_addr.s6_addr); - if(ret != 1) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Failed to convert IP '%s' to a valid IPv6 address.", - ip); - - close(sock); - return -1; - } - - name.sin6_scope_id = scope_id; - - if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { - close(sock); - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: IPv6 bind() on ip '%s' port %d, socktype %d failed.", - ip, port, socktype); - - return -1; - } - - if (socktype == SOCK_STREAM && listen(sock, listen_backlog) < 0) { - close(sock); - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: IPv6 listen() on ip '%s' port %d, socktype %d failed.", - ip, port, socktype); - - return -1; - } - - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "LISTENER: Listening on IPv6 ip '%s' port %d, socktype %d", - ip, port, socktype); - - return sock; -} - -static inline int listen_sockets_add(LISTEN_SOCKETS *sockets, int fd, int family, int socktype, const char *protocol, const char *ip, uint16_t port, int acl_flags) { - if(sockets->opened >= MAX_LISTEN_FDS) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Too many listening sockets. Failed to add listening %s socket at ip '%s' port %d, protocol %s, socktype %d", - protocol, ip, port, protocol, socktype); - - close(fd); - return -1; - } - - sockets->fds[sockets->opened] = fd; - sockets->fds_types[sockets->opened] = socktype; - sockets->fds_families[sockets->opened] = family; - sockets->fds_names[sockets->opened] = strdup_client_description(family, protocol, ip, port); - sockets->fds_acl_flags[sockets->opened] = acl_flags; - - sockets->opened++; - return 0; -} - -int listen_sockets_check_is_member(LISTEN_SOCKETS *sockets, int fd) { - size_t i; - for(i = 0; i < sockets->opened ;i++) - if(sockets->fds[i] == fd) return 1; - - return 0; -} - -static inline void listen_sockets_init(LISTEN_SOCKETS *sockets) { - size_t i; - for(i = 0; i < MAX_LISTEN_FDS ;i++) { - sockets->fds[i] = -1; - sockets->fds_names[i] = NULL; - sockets->fds_types[i] = -1; - } - - sockets->opened = 0; - sockets->failed = 0; -} - -void listen_sockets_close(LISTEN_SOCKETS *sockets) { - size_t i; - for(i = 0; i < sockets->opened ;i++) { - close(sockets->fds[i]); - sockets->fds[i] = -1; - - freez(sockets->fds_names[i]); - sockets->fds_names[i] = NULL; - - sockets->fds_types[i] = -1; - } - - sockets->opened = 0; - sockets->failed = 0; -} - -/* - * SSL ACL - * - * Search the SSL acl and apply it case it is set. - * - * @param acl is the acl given by the user. - */ -HTTP_ACL socket_ssl_acl(char *acl) { - char *ssl = strchr(acl,'^'); - if(ssl) { - //Due the format of the SSL command it is always the last command, - //we finish it here to avoid problems with the ACLs - *ssl = '\0'; - ssl++; - if (!strncmp("SSL=",ssl,4)) { - ssl += 4; - if (!strcmp(ssl,"optional")) { - return HTTP_ACL_SSL_OPTIONAL; - } - else if (!strcmp(ssl,"force")) { - return HTTP_ACL_SSL_FORCE; - } - } - } - - return HTTP_ACL_NONE; -} - -HTTP_ACL read_acl(char *st) { - HTTP_ACL ret = socket_ssl_acl(st); - - if (!strcmp(st,"dashboard")) ret |= HTTP_ACL_DASHBOARD; - if (!strcmp(st,"registry")) ret |= HTTP_ACL_REGISTRY; - if (!strcmp(st,"badges")) ret |= HTTP_ACL_BADGES; - if (!strcmp(st,"management")) ret |= HTTP_ACL_MANAGEMENT; - if (!strcmp(st,"streaming")) ret |= HTTP_ACL_STREAMING; - if (!strcmp(st,"netdata.conf")) ret |= HTTP_ACL_NETDATACONF; - - return ret; -} - -static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition, uint16_t default_port, int listen_backlog) { - int added = 0; - HTTP_ACL acl_flags = HTTP_ACL_NONE; - - struct addrinfo hints; - struct addrinfo *result = NULL, *rp = NULL; - - char buffer[strlen(definition) + 1]; - strcpy(buffer, definition); - - char buffer2[10 + 1]; - snprintfz(buffer2, 10, "%d", default_port); - - char *ip = buffer, *port = buffer2, *iface = "", *portconfig; - - int protocol = IPPROTO_TCP, socktype = SOCK_STREAM; - const char *protocol_str = "tcp"; - - if(strncmp(ip, "tcp:", 4) == 0) { - ip += 4; - protocol = IPPROTO_TCP; - socktype = SOCK_STREAM; - protocol_str = "tcp"; - acl_flags |= HTTP_ACL_API; - } - else if(strncmp(ip, "udp:", 4) == 0) { - ip += 4; - protocol = IPPROTO_UDP; - socktype = SOCK_DGRAM; - protocol_str = "udp"; - acl_flags |= HTTP_ACL_API_UDP; - } - else if(strncmp(ip, "unix:", 5) == 0) { - char *path = ip + 5; - socktype = SOCK_STREAM; - protocol_str = "unix"; - int fd = create_listen_socket_unix(path, listen_backlog); - if (fd == -1) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Cannot create unix socket '%s'", - path); - - sockets->failed++; - } else { - acl_flags = HTTP_ACL_API_UNIX | HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | - HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT; - listen_sockets_add(sockets, fd, AF_UNIX, socktype, protocol_str, path, 0, acl_flags); - added++; - } - return added; - } - - char *e = ip; - if(*e == '[') { - e = ++ip; - while(*e && *e != ']') e++; - if(*e == ']') { - *e = '\0'; - e++; - } - } - else { - while(*e && *e != ':' && *e != '%' && *e != '=') e++; - } - - if(*e == '%') { - *e = '\0'; - e++; - iface = e; - while(*e && *e != ':' && *e != '=') e++; - } - - if(*e == ':') { - port = e + 1; - *e = '\0'; - e++; - while(*e && *e != '=') e++; - } - - if(*e == '=') { - *e='\0'; - e++; - portconfig = e; - while (*e != '\0') { - if (*e == '|') { - *e = '\0'; - acl_flags |= read_acl(portconfig); - e++; - portconfig = e; - continue; - } - e++; - } - acl_flags |= read_acl(portconfig); - } else { - acl_flags |= HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT; - } - - //Case the user does not set the option SSL in the "bind to", but he has - //the certificates, I must redirect, so I am assuming here the default option - if(!(acl_flags & HTTP_ACL_SSL_OPTIONAL) && !(acl_flags & HTTP_ACL_SSL_FORCE)) { - acl_flags |= HTTP_ACL_SSL_DEFAULT; - } - - uint32_t scope_id = 0; - if(*iface) { - scope_id = if_nametoindex(iface); - if(!scope_id) - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Cannot find a network interface named '%s'. " - "Continuing with limiting the network interface", - iface); - } - - if(!*ip || *ip == '*' || !strcmp(ip, "any") || !strcmp(ip, "all")) - ip = NULL; - - if(!*port) - port = buffer2; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ - hints.ai_socktype = socktype; - hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ - hints.ai_protocol = protocol; - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; - - int r = getaddrinfo(ip, port, &hints, &result); - if (r != 0) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: getaddrinfo('%s', '%s'): %s\n", - ip, port, gai_strerror(r)); - - return -1; - } - - for (rp = result; rp != NULL; rp = rp->ai_next) { - int fd = -1; - int family; - - char rip[INET_ADDRSTRLEN + INET6_ADDRSTRLEN] = "INVALID"; - uint16_t rport = default_port; - - family = rp->ai_addr->sa_family; - switch (family) { - case AF_INET: { - struct sockaddr_in *sin = (struct sockaddr_in *) rp->ai_addr; - inet_ntop(AF_INET, &sin->sin_addr, rip, INET_ADDRSTRLEN); - rport = ntohs(sin->sin_port); - fd = create_listen_socket4(socktype, rip, rport, listen_backlog); - break; - } - - case AF_INET6: { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) rp->ai_addr; - inet_ntop(AF_INET6, &sin6->sin6_addr, rip, INET6_ADDRSTRLEN); - rport = ntohs(sin6->sin6_port); - fd = create_listen_socket6(socktype, scope_id, rip, rport, listen_backlog); - break; - } - - default: - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "LISTENER: Unknown socket family %d", - family); - - break; - } - - if (fd == -1) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Cannot bind to ip '%s', port %d", - rip, rport); - - sockets->failed++; - } - else { - listen_sockets_add(sockets, fd, family, socktype, protocol_str, rip, rport, acl_flags); - added++; - } - } - - freeaddrinfo(result); - - return added; -} - -int listen_sockets_setup(LISTEN_SOCKETS *sockets) { - listen_sockets_init(sockets); - - sockets->backlog = (int) appconfig_get_number(sockets->config, sockets->config_section, "listen backlog", sockets->backlog); - - long long int old_port = sockets->default_port; - long long int new_port = appconfig_get_number(sockets->config, sockets->config_section, "default port", sockets->default_port); - if(new_port < 1 || new_port > 65535) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "LISTENER: Invalid listen port %lld given. Defaulting to %lld.", - new_port, old_port); - - sockets->default_port = (uint16_t) appconfig_set_number(sockets->config, sockets->config_section, "default port", old_port); - } - else sockets->default_port = (uint16_t)new_port; - - const char *s = appconfig_get(sockets->config, sockets->config_section, "bind to", sockets->default_bind_to); - while(*s) { - const char *e = s; - - // skip separators, moving both s(tart) and e(nd) - while(isspace((uint8_t)*e) || *e == ',') s = ++e; - - // move e(nd) to the first separator - while(*e && !isspace((uint8_t)*e) && *e != ',') e++; - - // is there anything? - if(!*s || s == e) break; - - char buf[e - s + 1]; - strncpyz(buf, s, e - s); - bind_to_this(sockets, buf, sockets->default_port, sockets->backlog); - - s = e; - } - - if(sockets->failed) { - size_t i; - for(i = 0; i < sockets->opened ;i++) - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "LISTENER: Listen socket %s opened successfully.", - sockets->fds_names[i]); - } - - return (int)sockets->opened; -} - - -// -------------------------------------------------------------------------------------------------------------------- -// connect to another host/port - -// connect_to_this_unix() -// path the path of the unix socket -// timeout the timeout for establishing a connection - -static inline int connect_to_unix(const char *path, struct timeval *timeout) { - int fd = socket(AF_UNIX, SOCK_STREAM | DEFAULT_SOCKET_FLAGS, 0); - if(fd == -1) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to create UNIX socket() for '%s'", - path); - - return -1; - } - - if(timeout) { - if(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) timeout, sizeof(struct timeval)) < 0) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to set timeout on UNIX socket '%s'", - path); - } - - sock_setcloexec(fd); - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1); - - if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "Cannot connect to UNIX socket on path '%s'.", - path); - - close(fd); - return -1; - } - - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "Connected to UNIX socket on path '%s'.", - path); - - return fd; -} - -// connect_to_this_ip46() -// protocol IPPROTO_TCP, IPPROTO_UDP -// socktype SOCK_STREAM, SOCK_DGRAM -// host the destination hostname or IP address (IPv4 or IPv6) to connect to -// if it resolves to many IPs, all are tried (IPv4 and IPv6) -// scope_id the if_index id of the interface to use for connecting (0 = any) -// (used only under IPv6) -// service the service name or port to connect to -// timeout the timeout for establishing a connection - -int connect_to_this_ip46( - int protocol, - int socktype, - const char *host, - uint32_t scope_id, - const char *service, - struct timeval *timeout, - bool *fallback_ipv4) -{ - struct addrinfo hints; - struct addrinfo *ai_head = NULL, *ai = NULL; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; /* Allow IPv4 or IPv6 */ - hints.ai_socktype = socktype; - hints.ai_protocol = protocol; - - int ai_err = getaddrinfo(host, service, &hints, &ai_head); - if (ai_err != 0) { - - nd_log(NDLS_DAEMON, NDLP_ERR, - "Cannot resolve host '%s', port '%s': %s", - host, service, gai_strerror(ai_err)); - - return -1; - } - - char hostBfr[NI_MAXHOST + 1]; - char servBfr[NI_MAXSERV + 1]; - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_DST_IP, hostBfr), - ND_LOG_FIELD_TXT(NDF_DST_PORT, servBfr), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - int fd = -1; - for (ai = ai_head; ai != NULL && fd == -1; ai = ai->ai_next) { - if(nd_thread_signaled_to_cancel()) break; - - if (fallback_ipv4 && *fallback_ipv4 && ai->ai_family == PF_INET6) - continue; - - if (ai->ai_family == PF_INET6) { - struct sockaddr_in6 *pSadrIn6 = (struct sockaddr_in6 *) ai->ai_addr; - if(pSadrIn6->sin6_scope_id == 0) { - pSadrIn6->sin6_scope_id = scope_id; - } - } - - getnameinfo(ai->ai_addr, - ai->ai_addrlen, - hostBfr, - sizeof(hostBfr), - servBfr, - sizeof(servBfr), - NI_NUMERICHOST | NI_NUMERICSERV); - - switch (ai->ai_addr->sa_family) { - case PF_INET: { - struct sockaddr_in *pSadrIn = (struct sockaddr_in *)ai->ai_addr; - (void)pSadrIn; - break; - } - - case PF_INET6: { - struct sockaddr_in6 *pSadrIn6 = (struct sockaddr_in6 *) ai->ai_addr; - (void)pSadrIn6; - break; - } - - default: { - // Unknown protocol family - continue; - } - } - - fd = socket(ai->ai_family, ai->ai_socktype | DEFAULT_SOCKET_FLAGS, ai->ai_protocol); - if(fd != -1) { - if(timeout) { - if(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) timeout, sizeof(struct timeval)) < 0) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to set timeout on the socket to ip '%s' port '%s'", - hostBfr, servBfr); - } - sock_setcloexec(fd); - - errno_clear(); - if(connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) { - if(errno == EALREADY || errno == EINPROGRESS) { - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "Waiting for connection to ip %s port %s to be established", - hostBfr, servBfr); - - // Convert 'struct timeval' to milliseconds for poll(): - int timeout_ms = timeout ? (timeout->tv_sec * 1000 + timeout->tv_usec / 1000) : 1000; - - switch(wait_on_socket_or_cancel_with_timeout( - NULL, - fd, timeout_ms, POLLOUT, NULL)) { - case 0: // proceed - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "connect() to ip %s port %s completed successfully", - hostBfr, servBfr); - break; - - case -1: // thread cancelled - nd_log(NDLS_DAEMON, NDLP_ERR, - "Thread is cancelled while connecting to '%s', port '%s'.", - hostBfr, servBfr); - - close(fd); - fd = -1; - break; - - case 1: // timeout - nd_log(NDLS_DAEMON, NDLP_ERR, - "Timed out while connecting to '%s', port '%s'.", - hostBfr, servBfr); - - close(fd); - fd = -1; - - if (fallback_ipv4 && ai->ai_family == PF_INET6) - *fallback_ipv4 = true; - break; - - default: - case 2: // error - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to connect to '%s', port '%s'.", - hostBfr, servBfr); - - close(fd); - fd = -1; - break; - } - } - else { - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to connect to '%s', port '%s'", - hostBfr, servBfr); - - close(fd); - fd = -1; - } - } - } - else - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to socket() to '%s', port '%s'", - hostBfr, servBfr); - } - - freeaddrinfo(ai_head); - - return fd; -} - -// connect_to_this() -// -// definition format: -// -// [PROTOCOL:]IP[%INTERFACE][:PORT] -// -// PROTOCOL = tcp or udp -// IP = IPv4 or IPv6 IP or hostname, optionally enclosed in [] (required for IPv6) -// INTERFACE = for IPv6 only, the network interface to use -// PORT = port number or service name - -int connect_to_this(const char *definition, int default_port, struct timeval *timeout) { - char buffer[strlen(definition) + 1]; - strcpy(buffer, definition); - - char default_service[10 + 1]; - snprintfz(default_service, 10, "%d", default_port); - - char *host = buffer, *service = default_service, *iface = ""; - int protocol = IPPROTO_TCP, socktype = SOCK_STREAM; - uint32_t scope_id = 0; - - if(strncmp(host, "tcp:", 4) == 0) { - host += 4; - protocol = IPPROTO_TCP; - socktype = SOCK_STREAM; - } - else if(strncmp(host, "udp:", 4) == 0) { - host += 4; - protocol = IPPROTO_UDP; - socktype = SOCK_DGRAM; - } - else if(strncmp(host, "unix:", 5) == 0) { - char *path = host + 5; - return connect_to_unix(path, timeout); - } - else if(*host == '/') { - char *path = host; - return connect_to_unix(path, timeout); - } - - char *e = host; - if(*e == '[') { - e = ++host; - while(*e && *e != ']') e++; - if(*e == ']') { - *e = '\0'; - e++; - } - } - else { - while(*e && *e != ':' && *e != '%') e++; - } - - if(*e == '%') { - *e = '\0'; - e++; - iface = e; - while(*e && *e != ':') e++; - } - - if(*e == ':') { - *e = '\0'; - e++; - service = e; - } - - if(!*host) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "Definition '%s' does not specify a host.", - definition); - - return -1; - } - - if(*iface) { - scope_id = if_nametoindex(iface); - if(!scope_id) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Cannot find a network interface named '%s'. Continuing with limiting the network interface", - iface); - } - - if(!*service) - service = default_service; + flags |= O_NONBLOCK; + int ret = fcntl(fd, F_SETFL, flags); + if(ret < 0) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to set O_NONBLOCK on socket %d", + fd); - return connect_to_this_ip46(protocol, socktype, host, scope_id, service, timeout,NULL); + return ret; } -void foreach_entry_in_connection_string(const char *destination, bool (*callback)(char *entry, void *data), void *data) { - const char *s = destination; - while(*s) { - const char *e = s; +int sock_delnonblock(int fd) { + int flags; - // skip separators, moving both s(tart) and e(nd) - while(isspace((uint8_t)*e) || *e == ',') s = ++e; + flags = fcntl(fd, F_GETFL); + flags &= ~O_NONBLOCK; - // move e(nd) to the first separator - while(*e && !isspace((uint8_t)*e) && *e != ',') e++; + int ret = fcntl(fd, F_SETFL, flags); + if(ret < 0) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to remove O_NONBLOCK on socket %d", + fd); - // is there anything? - if(!*s || s == e) break; + return ret; +} - char buf[e - s + 1]; - strncpyz(buf, s, e - s); +int sock_setreuse(int fd, int reuse) { + int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); - if(callback(buf, data)) break; + if(ret == -1) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to set SO_REUSEADDR on socket %d", + fd); - s = e; - } + return ret; } -struct connect_to_one_of_data { - int default_port; - struct timeval *timeout; - size_t *reconnects_counter; - char *connected_to; - size_t connected_to_size; - int sock; -}; - -static bool connect_to_one_of_callback(char *entry, void *data) { - struct connect_to_one_of_data *t = data; - - if(t->reconnects_counter) - t->reconnects_counter++; - - t->sock = connect_to_this(entry, t->default_port, t->timeout); - if(t->sock != -1) { - if(t->connected_to && t->connected_to_size) { - strncpyz(t->connected_to, entry, t->connected_to_size); - t->connected_to[t->connected_to_size - 1] = '\0'; - } - - return true; - } - - return false; +void sock_setcloexec(int fd) +{ + UNUSED(fd); + int flags = fcntl(fd, F_GETFD); + if (flags != -1) + (void) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } -int connect_to_one_of(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size) { - struct connect_to_one_of_data t = { - .default_port = default_port, - .timeout = timeout, - .reconnects_counter = reconnects_counter, - .connected_to = connected_to, - .connected_to_size = connected_to_size, - .sock = -1, - }; +int sock_setreuse_port(int fd __maybe_unused, int reuse __maybe_unused) { + int ret; - foreach_entry_in_connection_string(destination, connect_to_one_of_callback, &t); +#ifdef SO_REUSEPORT + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)); + if(ret == -1 && errno != ENOPROTOOPT) + nd_log(NDLS_DAEMON, NDLP_ERR, + "failed to set SO_REUSEPORT on socket %d", + fd); +#else + ret = -1; +#endif - return t.sock; + return ret; } -static bool connect_to_one_of_urls_callback(char *entry, void *data) { - char *s = strchr(entry, '/'); - if(s) *s = '\0'; +int sock_enlarge_in(int fd) { + int ret, bs = LARGE_SOCK_SIZE; + + ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bs, sizeof(bs)); + + if(ret == -1) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to set SO_RCVBUF on socket %d", + fd); - return connect_to_one_of_callback(entry, data); + return ret; } -int connect_to_one_of_urls(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size) { - struct connect_to_one_of_data t = { - .default_port = default_port, - .timeout = timeout, - .reconnects_counter = reconnects_counter, - .connected_to = connected_to, - .connected_to_size = connected_to_size, - .sock = -1, - }; +int sock_enlarge_out(int fd) { + int ret, bs = LARGE_SOCK_SIZE; + ret = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bs, sizeof(bs)); - foreach_entry_in_connection_string(destination, connect_to_one_of_urls_callback, &t); + if(ret == -1) + nd_log(NDLS_DAEMON, NDLP_ERR, + "Failed to set SO_SNDBUF on socket %d", + fd); - return t.sock; + return ret; } - // -------------------------------------------------------------------------------------------------------------------- // helpers to send/receive data in one call, in blocking mode, with a timeout @@ -1259,34 +267,9 @@ inline int wait_on_socket_or_cancel_with_timeout( return 1; } -ssize_t recv_timeout(NETDATA_SSL *ssl, int sockfd, void *buf, size_t len, int flags, int timeout) { - - switch(wait_on_socket_or_cancel_with_timeout( - ssl, - sockfd, timeout * 1000, POLLIN, NULL)) { - case 0: // data are waiting - break; - - case 1: // timeout - return 0; - - default: - case -1: // thread cancelled - case 2: // error on socket - return -1; - } - - if (SSL_connection(ssl)) - return netdata_ssl_read(ssl, buf, len); - - return recv(sockfd, buf, len, flags); -} - -ssize_t send_timeout(NETDATA_SSL *ssl, int sockfd, void *buf, size_t len, int flags, int timeout) { +ssize_t send_timeout(NETDATA_SSL *ssl, int sockfd, void *buf, size_t len, int flags, time_t timeout) { - switch(wait_on_socket_or_cancel_with_timeout( - ssl, - sockfd, timeout * 1000, POLLOUT, NULL)) { + switch(wait_on_socket_or_cancel_with_timeout(ssl, sockfd, timeout * 1000, POLLOUT, NULL)) { case 0: // data are waiting break; @@ -1300,9 +283,9 @@ ssize_t send_timeout(NETDATA_SSL *ssl, int sockfd, void *buf, size_t len, int fl } if(ssl->conn) { - if (SSL_connection(ssl)) { + if (SSL_connection(ssl)) return netdata_ssl_write(ssl, buf, len); - } + else { nd_log(NDLS_DAEMON, NDLP_ERR, "cannot write to SSL connection - connection is not ready."); @@ -1314,7 +297,6 @@ ssize_t send_timeout(NETDATA_SSL *ssl, int sockfd, void *buf, size_t len, int fl return send(sockfd, buf, len, flags); } - // -------------------------------------------------------------------------------------------------------------------- // accept4() replacement for systems that do not have one @@ -1509,679 +491,3 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien return nfd; } - - -// -------------------------------------------------------------------------------------------------------------------- -// poll() based listener -// this should be the fastest possible listener for up to 100 sockets -// above 100, an epoll() interface is needed on Linux - -#define POLL_FDS_INCREASE_STEP 10 - -inline POLLINFO *poll_add_fd(POLLJOB *p - , int fd - , int socktype - , HTTP_ACL port_acl - , uint32_t flags - , const char *client_ip - , const char *client_port - , const char *client_host - , void *(*add_callback)(POLLINFO * /*pi*/, short int * /*events*/, void * /*data*/) - , void (*del_callback)(POLLINFO * /*pi*/) - , int (*rcv_callback)(POLLINFO * /*pi*/, short int * /*events*/) - , int (*snd_callback)(POLLINFO * /*pi*/, short int * /*events*/) - , void *data -) { - if(unlikely(fd < 0)) return NULL; - - //if(p->limit && p->used >= p->limit) { - // nd_log(NDLS_DAEMON, NDLP_WARNING, "Max sockets limit reached (%zu sockets), dropping connection", p->used); - // close(fd); - // return NULL; - //} - - if(unlikely(!p->first_free)) { - size_t new_slots = p->slots + POLL_FDS_INCREASE_STEP; - - p->fds = reallocz(p->fds, sizeof(struct pollfd) * new_slots); - p->inf = reallocz(p->inf, sizeof(POLLINFO) * new_slots); - - // reset all the newly added slots - ssize_t i; - for(i = new_slots - 1; i >= (ssize_t)p->slots ; i--) { - p->fds[i].fd = -1; - p->fds[i].events = 0; - p->fds[i].revents = 0; - - p->inf[i].p = p; - p->inf[i].slot = (size_t)i; - p->inf[i].flags = 0; - p->inf[i].socktype = -1; - p->inf[i].port_acl = -1; - - p->inf[i].client_ip = NULL; - p->inf[i].client_port = NULL; - p->inf[i].client_host = NULL; - p->inf[i].del_callback = p->del_callback; - p->inf[i].rcv_callback = p->rcv_callback; - p->inf[i].snd_callback = p->snd_callback; - p->inf[i].data = NULL; - - // link them so that the first free will be earlier in the array - // (we loop decrementing i) - p->inf[i].next = p->first_free; - p->first_free = &p->inf[i]; - } - - p->slots = new_slots; - } - - POLLINFO *pi = p->first_free; - p->first_free = p->first_free->next; - - struct pollfd *pf = &p->fds[pi->slot]; - pf->fd = fd; - pf->events = POLLIN; - pf->revents = 0; - - pi->fd = fd; - pi->p = p; - pi->socktype = socktype; - pi->port_acl = port_acl; - pi->flags = flags; - pi->next = NULL; - pi->client_ip = strdupz(client_ip); - pi->client_port = strdupz(client_port); - pi->client_host = strdupz(client_host); - - pi->del_callback = del_callback; - pi->rcv_callback = rcv_callback; - pi->snd_callback = snd_callback; - - pi->connected_t = now_boottime_sec(); - pi->last_received_t = 0; - pi->last_sent_t = 0; - pi->last_sent_t = 0; - pi->recv_count = 0; - pi->send_count = 0; - - p->used++; - if(unlikely(pi->slot > p->max)) - p->max = pi->slot; - - if(pi->flags & POLLINFO_FLAG_CLIENT_SOCKET) { - pi->data = add_callback(pi, &pf->events, data); - } - - if(pi->flags & POLLINFO_FLAG_SERVER_SOCKET) { - p->min = pi->slot; - } - - return pi; -} - -inline void poll_close_fd(POLLINFO *pi) { - POLLJOB *p = pi->p; - - struct pollfd *pf = &p->fds[pi->slot]; - - if(unlikely(pf->fd == -1)) return; - - if(pi->flags & POLLINFO_FLAG_CLIENT_SOCKET) { - pi->del_callback(pi); - - if(likely(!(pi->flags & POLLINFO_FLAG_DONT_CLOSE))) { - if(close(pf->fd) == -1) - nd_log(NDLS_DAEMON, NDLP_ERR, - "Failed to close() poll_events() socket %d", - pf->fd); - } - } - - pf->fd = -1; - pf->events = 0; - pf->revents = 0; - - pi->fd = -1; - pi->socktype = -1; - pi->flags = 0; - pi->data = NULL; - - pi->del_callback = NULL; - pi->rcv_callback = NULL; - pi->snd_callback = NULL; - - freez(pi->client_ip); - pi->client_ip = NULL; - - freez(pi->client_port); - pi->client_port = NULL; - - freez(pi->client_host); - pi->client_host = NULL; - - pi->next = p->first_free; - p->first_free = pi; - - p->used--; - if(unlikely(p->max == pi->slot)) { - p->max = p->min; - ssize_t i; - for(i = (ssize_t)pi->slot; i > (ssize_t)p->min ;i--) { - if (unlikely(p->fds[i].fd != -1)) { - p->max = (size_t)i; - break; - } - } - } -} - -void *poll_default_add_callback(POLLINFO *pi, short int *events, void *data) { - (void)pi; - (void)events; - (void)data; - - return NULL; -} - -void poll_default_del_callback(POLLINFO *pi) { - if(pi->data) - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: internal error: del_callback_default() called with data pointer - possible memory leak"); -} - -int poll_default_rcv_callback(POLLINFO *pi, short int *events) { - *events |= POLLIN; - - char buffer[1024 + 1]; - - ssize_t rc; - do { - rc = recv(pi->fd, buffer, 1024, MSG_DONTWAIT); - if (rc < 0) { - // read failed - if (errno != EWOULDBLOCK && errno != EAGAIN) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: poll_default_rcv_callback(): recv() failed with %zd.", - rc); - - return -1; - } - } else if (rc) { - // data received - nd_log(NDLS_DAEMON, NDLP_WARNING, - "POLLFD: internal error: poll_default_rcv_callback() is discarding %zd bytes received on socket %d", - rc, pi->fd); - } - } while (rc != -1); - - return 0; -} - -int poll_default_snd_callback(POLLINFO *pi, short int *events) { - *events &= ~POLLOUT; - - nd_log(NDLS_DAEMON, NDLP_WARNING, - "POLLFD: internal error: poll_default_snd_callback(): nothing to send on socket %d", - pi->fd); - - return 0; -} - -void poll_default_tmr_callback(void *timer_data) { - (void)timer_data; -} - -static void poll_events_cleanup(void *pptr) { - POLLJOB *p = CLEANUP_FUNCTION_GET_PTR(pptr); - if(!p) return; - - for(size_t i = 0 ; i <= p->max ; i++) { - POLLINFO *pi = &p->inf[i]; - poll_close_fd(pi); - } - - freez(p->fds); - freez(p->inf); -} - -static int poll_process_error(POLLINFO *pi, struct pollfd *pf, short int revents) { - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_SRC_IP, pi->client_ip), - ND_LOG_FIELD_TXT(NDF_SRC_PORT, pi->client_port), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "POLLFD: LISTENER: received %s %s %s on socket at slot %zu (fd %d) client '%s' port '%s' expecting %s %s %s, having %s %s %s" - , revents & POLLERR ? "POLLERR" : "" - , revents & POLLHUP ? "POLLHUP" : "" - , revents & POLLNVAL ? "POLLNVAL" : "" - , pi->slot - , pi->fd - , pi->client_ip ? pi->client_ip : "" - , pi->client_port ? pi->client_port : "" - , pf->events & POLLIN ? "POLLIN" : "", pf->events & POLLOUT ? "POLLOUT" : "", pf->events & POLLPRI ? "POLLPRI" : "" - , revents & POLLIN ? "POLLIN" : "", revents & POLLOUT ? "POLLOUT" : "", revents & POLLPRI ? "POLLPRI" : "" - ); - - pf->events = 0; - poll_close_fd(pi); - return 1; -} - -static inline int poll_process_send(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, time_t now) { - pi->last_sent_t = now; - pi->send_count++; - - pf->events = 0; - - // remember the slot, in case we need to close it later - // the callback may manipulate the socket list and our pf and pi pointers may be invalid after that call - size_t slot = pi->slot; - - if (unlikely(pi->snd_callback(pi, &pf->events) == -1)) - poll_close_fd(&p->inf[slot]); - - // IMPORTANT: - // pf and pi may be invalid below this point, they may have been reallocated. - - return 1; -} - -static inline int poll_process_tcp_read(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, time_t now) { - pi->last_received_t = now; - pi->recv_count++; - - pf->events = 0; - - // remember the slot, in case we need to close it later - // the callback may manipulate the socket list and our pf and pi pointers may be invalid after that call - size_t slot = pi->slot; - - if (pi->rcv_callback(pi, &pf->events) == -1) - poll_close_fd(&p->inf[slot]); - - // IMPORTANT: - // pf and pi may be invalid below this point, they may have been reallocated. - - return 1; -} - -static inline int poll_process_udp_read(POLLINFO *pi, struct pollfd *pf, time_t now __maybe_unused) { - pi->last_received_t = now; - pi->recv_count++; - - // TODO: access_list is not applied to UDP - // but checking the access list on every UDP packet will destroy - // performance, especially for statsd. - - pf->events = 0; - if(pi->rcv_callback(pi, &pf->events) == -1) - return 0; - - // IMPORTANT: - // pf and pi may be invalid below this point, they may have been reallocated. - - return 1; -} - -static int poll_process_new_tcp_connection(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, time_t now) { - pi->last_received_t = now; - pi->recv_count++; - - char client_ip[INET6_ADDRSTRLEN] = ""; - char client_port[NI_MAXSERV] = ""; - char client_host[NI_MAXHOST] = ""; - -#ifdef SOCK_NONBLOCK - int flags = SOCK_NONBLOCK; -#else - int flags = 0; -#endif - - int nfd = accept_socket( - pf->fd, flags, - client_ip, INET6_ADDRSTRLEN, client_port,NI_MAXSERV, client_host, NI_MAXHOST, - p->access_list, p->allow_dns - ); - -#ifndef SOCK_NONBLOCK - if (nfd > 0) { - int flags = fcntl(nfd, F_GETFL); - (void)fcntl(nfd, F_SETFL, flags| O_NONBLOCK); - } -#endif - - if (unlikely(nfd < 0)) { - // accept failed - - if(unlikely(errno == EMFILE)) { - nd_log_limit_static_global_var(erl, 10, 1000); - nd_log_limit(&erl, NDLS_DAEMON, NDLP_ERR, - "POLLFD: LISTENER: too many open files - used by this thread %zu, max for this thread %zu", - p->used, p->limit); - } - else if(unlikely(errno != EWOULDBLOCK && errno != EAGAIN)) - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: LISTENER: accept() failed."); - - } - else { - // accept ok - - poll_add_fd(p - , nfd - , SOCK_STREAM - , pi->port_acl - , POLLINFO_FLAG_CLIENT_SOCKET - , client_ip - , client_port - , client_host - , p->add_callback - , p->del_callback - , p->rcv_callback - , p->snd_callback - , NULL - ); - - // IMPORTANT: - // pf and pi may be invalid below this point, they may have been reallocated. - - return 1; - } - - return 0; -} - -void poll_events(LISTEN_SOCKETS *sockets - , void *(*add_callback)(POLLINFO * /*pi*/, short int * /*events*/, void * /*data*/) - , void (*del_callback)(POLLINFO * /*pi*/) - , int (*rcv_callback)(POLLINFO * /*pi*/, short int * /*events*/) - , int (*snd_callback)(POLLINFO * /*pi*/, short int * /*events*/) - , void (*tmr_callback)(void * /*timer_data*/) - , bool (*check_to_stop_callback)(void) - , SIMPLE_PATTERN *access_list - , int allow_dns - , void *data - , time_t tcp_request_timeout_seconds - , time_t tcp_idle_timeout_seconds - , time_t timer_milliseconds - , void *timer_data - , size_t max_tcp_sockets -) { - if(!sockets || !sockets->opened) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: internal error: no listening sockets are opened"); - return; - } - - if(timer_milliseconds <= 0) timer_milliseconds = 0; - - int retval; - - POLLJOB p = { - .slots = 0, - .used = 0, - .max = 0, - .limit = max_tcp_sockets, - .fds = NULL, - .inf = NULL, - .first_free = NULL, - - .complete_request_timeout = tcp_request_timeout_seconds, - .idle_timeout = tcp_idle_timeout_seconds, - .checks_every = (tcp_idle_timeout_seconds / 3) + 1, - - .access_list = access_list, - .allow_dns = allow_dns, - - .timer_milliseconds = timer_milliseconds, - .timer_data = timer_data, - - .add_callback = add_callback?add_callback:poll_default_add_callback, - .del_callback = del_callback?del_callback:poll_default_del_callback, - .rcv_callback = rcv_callback?rcv_callback:poll_default_rcv_callback, - .snd_callback = snd_callback?snd_callback:poll_default_snd_callback, - .tmr_callback = tmr_callback?tmr_callback:poll_default_tmr_callback - }; - - size_t i; - for(i = 0; i < sockets->opened ;i++) { - - POLLINFO *pi = poll_add_fd(&p - , sockets->fds[i] - , sockets->fds_types[i] - , sockets->fds_acl_flags[i] - , POLLINFO_FLAG_SERVER_SOCKET - , (sockets->fds_names[i])?sockets->fds_names[i]:"UNKNOWN" - , "" - , "" - , p.add_callback - , p.del_callback - , p.rcv_callback - , p.snd_callback - , NULL - ); - - pi->data = data; - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "POLLFD: LISTENER: listening on '%s'", - (sockets->fds_names[i])?sockets->fds_names[i]:"UNKNOWN"); - } - - int listen_sockets_active = 1; - - time_t last_check = now_boottime_sec(); - - usec_t timer_usec = timer_milliseconds * USEC_PER_MS; - usec_t now_usec = 0, next_timer_usec = 0, last_timer_usec = 0; - (void)last_timer_usec; - - if(unlikely(timer_usec)) { - now_usec = now_boottime_usec(); - next_timer_usec = now_usec - (now_usec % timer_usec) + timer_usec; - } - - CLEANUP_FUNCTION_REGISTER(poll_events_cleanup) cleanup_ptr = &p; - - while(!check_to_stop_callback() && !nd_thread_signaled_to_cancel()) { - if(unlikely(timer_usec)) { - now_usec = now_boottime_usec(); - - if(unlikely(timer_usec && now_usec >= next_timer_usec)) { - last_timer_usec = now_usec; - p.tmr_callback(p.timer_data); - now_usec = now_boottime_usec(); - next_timer_usec = now_usec - (now_usec % timer_usec) + timer_usec; - } - } - - // enable or disable the TCP listening sockets, based on the current number of sockets used and the limit set - if((listen_sockets_active && (p.limit && p.used >= p.limit)) || (!listen_sockets_active && (!p.limit || p.used < p.limit))) { - listen_sockets_active = !listen_sockets_active; - - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "%s listening sockets (used TCP sockets %zu, max allowed for this worker %zu)", - (listen_sockets_active)?"ENABLING":"DISABLING", p.used, p.limit); - - for (i = 0; i <= p.max; i++) { - if(p.inf[i].flags & POLLINFO_FLAG_SERVER_SOCKET && p.inf[i].socktype == SOCK_STREAM) { - p.fds[i].events = (short int) ((listen_sockets_active) ? POLLIN : 0); - } - } - } - - retval = poll(p.fds, p.max + 1, ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS); - time_t now = now_boottime_sec(); - - if(unlikely(retval == -1)) { - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: LISTENER: poll() failed while waiting on %zu sockets.", - p.max + 1); - - break; - } - else if(unlikely(!retval)) { - // timeout - ; - } - else { - POLLINFO *pi; - struct pollfd *pf; - size_t idx, processed = 0; - short int revents; - - // keep fast lookup arrays per function - // to avoid looping through the entire list every time - size_t sends[p.max + 1], sends_max = 0; - size_t reads[p.max + 1], reads_max = 0; - size_t conns[p.max + 1], conns_max = 0; - size_t udprd[p.max + 1], udprd_max = 0; - - for (i = 0; i <= p.max; i++) { - pi = &p.inf[i]; - pf = &p.fds[i]; - revents = pf->revents; - - if(unlikely(revents == 0 || pf->fd == -1)) - continue; - - if (unlikely(revents & (POLLERR|POLLHUP|POLLNVAL))) { - // something is wrong to one of our sockets - - pf->revents = 0; - processed += poll_process_error(pi, pf, revents); - } - else if (likely(revents & POLLOUT)) { - // a client is ready to receive data - - sends[sends_max++] = i; - } - else if (likely(revents & (POLLIN|POLLPRI))) { - if (pi->flags & POLLINFO_FLAG_CLIENT_SOCKET) { - // a client sent data to us - - reads[reads_max++] = i; - } - else if (pi->flags & POLLINFO_FLAG_SERVER_SOCKET) { - // something is coming to our server sockets - - if(pi->socktype == SOCK_DGRAM) { - // UDP receive, directly on our listening socket - - udprd[udprd_max++] = i; - } - else if(pi->socktype == SOCK_STREAM) { - // new TCP connection - - conns[conns_max++] = i; - } - else - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: LISTENER: server slot %zu (fd %d) connection from %s port %s using unhandled socket type %d." - , i - , pi->fd - , pi->client_ip ? pi->client_ip : "" - , pi->client_port ? pi->client_port : "" - , pi->socktype - ); - } - else - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: LISTENER: client slot %zu (fd %d) data from %s port %s using flags %08X is neither client nor server." - , i - , pi->fd - , pi->client_ip ? pi->client_ip : "" - , pi->client_port ? pi->client_port : "" - , pi->flags - ); - } - else - nd_log(NDLS_DAEMON, NDLP_ERR, - "POLLFD: LISTENER: socket slot %zu (fd %d) client %s port %s unhandled event id %d." - , i - , pi->fd - , pi->client_ip ? pi->client_ip : "" - , pi->client_port ? pi->client_port : "" - , revents - ); - } - - // process sends - for (idx = 0; idx < sends_max; idx++) { - i = sends[idx]; - pi = &p.inf[i]; - pf = &p.fds[i]; - pf->revents = 0; - processed += poll_process_send(&p, pi, pf, now); - } - - // process UDP reads - for (idx = 0; idx < udprd_max; idx++) { - i = udprd[idx]; - pi = &p.inf[i]; - pf = &p.fds[i]; - pf->revents = 0; - processed += poll_process_udp_read(pi, pf, now); - } - - // process TCP reads - for (idx = 0; idx < reads_max; idx++) { - i = reads[idx]; - pi = &p.inf[i]; - pf = &p.fds[i]; - pf->revents = 0; - processed += poll_process_tcp_read(&p, pi, pf, now); - } - - if(!processed && (!p.limit || p.used < p.limit)) { - // nothing processed above (rcv, snd) and we have room for another TCP connection - // so, accept one TCP connection - for (idx = 0; idx < conns_max; idx++) { - i = conns[idx]; - pi = &p.inf[i]; - pf = &p.fds[i]; - pf->revents = 0; - if (poll_process_new_tcp_connection(&p, pi, pf, now)) - break; - } - } - } - - if(unlikely(p.checks_every > 0 && now - last_check > p.checks_every)) { - last_check = now; - - // cleanup old sockets - for(i = 0; i <= p.max; i++) { - POLLINFO *pi = &p.inf[i]; - - if(likely(pi->flags & POLLINFO_FLAG_CLIENT_SOCKET)) { - if (unlikely(pi->send_count == 0 && p.complete_request_timeout > 0 && (now - pi->connected_t) >= p.complete_request_timeout)) { - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "POLLFD: LISTENER: client slot %zu (fd %d) from %s port %s has not sent a complete request in %zu seconds - closing it. " - , i - , pi->fd - , pi->client_ip ? pi->client_ip : "" - , pi->client_port ? pi->client_port : "" - , (size_t) p.complete_request_timeout - ); - poll_close_fd(pi); - } - else if(unlikely(pi->recv_count && p.idle_timeout > 0 && now - ((pi->last_received_t > pi->last_sent_t) ? pi->last_received_t : pi->last_sent_t) >= p.idle_timeout )) { - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "POLLFD: LISTENER: client slot %zu (fd %d) from %s port %s is idle for more than %zu seconds - closing it. " - , i - , pi->fd - , pi->client_ip ? pi->client_ip : "" - , pi->client_port ? pi->client_port : "" - , (size_t) p.idle_timeout - ); - poll_close_fd(pi); - } - } - } - } - } -} diff --git a/src/libnetdata/socket/socket.h b/src/libnetdata/socket/socket.h index 2c282c4c60b15c..1a58a73c6d7c83 100644 --- a/src/libnetdata/socket/socket.h +++ b/src/libnetdata/socket/socket.h @@ -5,49 +5,9 @@ #include "../libnetdata.h" -#ifndef MAX_LISTEN_FDS -#define MAX_LISTEN_FDS 50 -#endif - #define ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS 100 -typedef struct listen_sockets { - struct config *config; // the config file to use - const char *config_section; // the netdata configuration section to read settings from - const char *default_bind_to; // the default bind to configuration string - uint16_t default_port; // the default port to use - int backlog; // the default listen backlog to use - - size_t opened; // the number of sockets opened - size_t failed; // the number of sockets attempted to open, but failed - int fds[MAX_LISTEN_FDS]; // the open sockets - char *fds_names[MAX_LISTEN_FDS]; // descriptions for the open sockets - int fds_types[MAX_LISTEN_FDS]; // the socktype for the open sockets (SOCK_STREAM, SOCK_DGRAM) - int fds_families[MAX_LISTEN_FDS]; // the family of the open sockets (AF_UNIX, AF_INET, AF_INET6) - HTTP_ACL fds_acl_flags[MAX_LISTEN_FDS]; // the acl to apply to the open sockets (dashboard, badges, streaming, netdata.conf, management) -} LISTEN_SOCKETS; - -char *strdup_client_description(int family, const char *protocol, const char *ip, uint16_t port); - -int listen_sockets_setup(LISTEN_SOCKETS *sockets); -void listen_sockets_close(LISTEN_SOCKETS *sockets); - -void foreach_entry_in_connection_string(const char *destination, bool (*callback)(char *entry, void *data), void *data); -int connect_to_this_ip46( - int protocol, - int socktype, - const char *host, - uint32_t scope_id, - const char *service, - struct timeval *timeout, - bool *fallback_ipv4); -int connect_to_this(const char *definition, int default_port, struct timeval *timeout); -int connect_to_one_of(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size); -int connect_to_one_of_urls(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size); - - -ssize_t recv_timeout(NETDATA_SSL *ssl,int sockfd, void *buf, size_t len, int flags, int timeout); -ssize_t send_timeout(NETDATA_SSL *ssl,int sockfd, void *buf, size_t len, int flags, int timeout); +ssize_t send_timeout(NETDATA_SSL *ssl,int sockfd, void *buf, size_t len, int flags, time_t timeout); int wait_on_socket_or_cancel_with_timeout(NETDATA_SSL *ssl, int fd, int timeout_ms, short int poll_events, short int *revents); bool fd_is_socket(int fd); @@ -77,136 +37,6 @@ int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags); #endif -// ---------------------------------------------------------------------------- -// poll() based listener - -#define POLLINFO_FLAG_SERVER_SOCKET 0x00000001 -#define POLLINFO_FLAG_CLIENT_SOCKET 0x00000002 -#define POLLINFO_FLAG_DONT_CLOSE 0x00000004 - -typedef struct poll POLLJOB; - -typedef struct pollinfo { - POLLJOB *p; // the parent - size_t slot; // the slot id - - int fd; // the file descriptor - int socktype; // the client socket type - HTTP_ACL port_acl; // the access lists permitted on this web server port (it's -1 for client sockets) - char *client_ip; // Max INET6_ADDRSTRLEN bytes - char *client_port; // Max NI_MAXSERV bytes - char *client_host; // Max NI_MAXHOST bytes - - time_t connected_t; // the time the socket connected - time_t last_received_t; // the time the socket last received data - time_t last_sent_t; // the time the socket last sent data - - size_t recv_count; // the number of times the socket was ready for inbound traffic - size_t send_count; // the number of times the socket was ready for outbound traffic - - uint32_t flags; // internal flags - - // callbacks for this socket - void (*del_callback)(struct pollinfo *pi); - int (*rcv_callback)(struct pollinfo *pi, short int *events); - int (*snd_callback)(struct pollinfo *pi, short int *events); - - // the user data - void *data; - - // linking of free pollinfo structures - // for quickly finding the next available - // this is like a stack, it grows and shrinks - // (with gaps - lower empty slots are preferred) - struct pollinfo *next; -} POLLINFO; - -struct poll { - size_t slots; - size_t used; - size_t min; - size_t max; - - size_t limit; - - time_t complete_request_timeout; - time_t idle_timeout; - time_t checks_every; - - time_t timer_milliseconds; - void *timer_data; - - struct pollfd *fds; - struct pollinfo *inf; - struct pollinfo *first_free; - - SIMPLE_PATTERN *access_list; - int allow_dns; - - void *(*add_callback)(POLLINFO *pi, short int *events, void *data); - void (*del_callback)(POLLINFO *pi); - int (*rcv_callback)(POLLINFO *pi, short int *events); - int (*snd_callback)(POLLINFO *pi, short int *events); - void (*tmr_callback)(void *timer_data); -}; - -#define pollinfo_from_slot(p, slot) (&((p)->inf[(slot)])) - -int poll_default_snd_callback(POLLINFO *pi, short int *events); -int poll_default_rcv_callback(POLLINFO *pi, short int *events); -void poll_default_del_callback(POLLINFO *pi); -void *poll_default_add_callback(POLLINFO *pi, short int *events, void *data); - -POLLINFO *poll_add_fd(POLLJOB *p - , int fd - , int socktype - , HTTP_ACL port_acl - , uint32_t flags - , const char *client_ip - , const char *client_port - , const char *client_host - , void *(*add_callback)(POLLINFO *pi, short int *events, void *data) - , void (*del_callback)(POLLINFO *pi) - , int (*rcv_callback)(POLLINFO *pi, short int *events) - , int (*snd_callback)(POLLINFO *pi, short int *events) - , void *data -); -void poll_close_fd(POLLINFO *pi); - -void poll_events(LISTEN_SOCKETS *sockets - , void *(*add_callback)(POLLINFO *pi, short int *events, void *data) - , void (*del_callback)(POLLINFO *pi) - , int (*rcv_callback)(POLLINFO *pi, short int *events) - , int (*snd_callback)(POLLINFO *pi, short int *events) - , void (*tmr_callback)(void *timer_data) - , bool (*check_to_stop_callback)(void) - , SIMPLE_PATTERN *access_list - , int allow_dns - , void *data - , time_t tcp_request_timeout_seconds - , time_t tcp_idle_timeout_seconds - , time_t timer_milliseconds - , void *timer_data - , size_t max_tcp_sockets -); - -#ifndef INET6_ADDRSTRLEN -#define INET6_ADDRSTRLEN 46 -#endif - -typedef struct { - struct { - char ip[INET6_ADDRSTRLEN]; - int port; - } local; - - struct { - char ip[INET6_ADDRSTRLEN]; - int port; - } peer; -} SOCKET_PEERS; - -SOCKET_PEERS socket_peers(int sock_fd); bool ip_to_hostname(const char *ip, char *dst, size_t dst_len); #endif //NETDATA_SOCKET_H diff --git a/src/libnetdata/spawn_server/log-forwarder.c b/src/libnetdata/spawn_server/log-forwarder.c index 5c4db55ea0f9c3..597ad07c04481f 100644 --- a/src/libnetdata/spawn_server/log-forwarder.c +++ b/src/libnetdata/spawn_server/log-forwarder.c @@ -257,7 +257,7 @@ static void *log_forwarder_thread_func(void *arg) { ssize_t bytes_read = read(lf->pipe_fds[PIPE_READ], buf, sizeof(buf)); // Ignore the data; proceed regardless of the result if (bytes_read == -1) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { + if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { // Handle read error if necessary nd_log(NDLS_COLLECTORS, NDLP_ERR, "Failed to read from notification pipe"); return NULL; diff --git a/src/libnetdata/threads/threads.c b/src/libnetdata/threads/threads.c index 36c63f4e0fec68..f71972eb673ece 100644 --- a/src/libnetdata/threads/threads.c +++ b/src/libnetdata/threads/threads.c @@ -236,7 +236,7 @@ void netdata_threads_init_for_external_plugins(size_t stacksize) { // ---------------------------------------------------------------------------- void rrdset_thread_rda_free(void); -void sender_thread_buffer_free(void); +void sender_commit_thread_buffer_free(void); void query_target_free(void); void service_exits(void); void rrd_collector_finished(void); @@ -305,7 +305,7 @@ static void nd_thread_exit(void *pptr) { nd_log(NDLS_DAEMON, NDLP_DEBUG, "thread with task id %d finished", nti->tid); rrd_collector_finished(); - sender_thread_buffer_free(); + sender_commit_thread_buffer_free(); rrdset_thread_rda_free(); query_target_free(); thread_cache_destroy(); diff --git a/src/libnetdata/worker_utilization/README.md b/src/libnetdata/worker_utilization/README.md index 17dd85e3ed8345..99f53a8e6fb04a 100644 --- a/src/libnetdata/worker_utilization/README.md +++ b/src/libnetdata/worker_utilization/README.md @@ -80,6 +80,6 @@ busy or idle all the time or part of the time. Works well for both thousands of per second and unlimited working time (being totally busy with a single request for ages). -The statistics collector is called by the global statistics thread of netdata. So, +The statistics collector is called by the telemetry thread of netdata. So, even if the workers are extremely busy with their jobs, netdata will be able to know how busy they are. diff --git a/src/libnetdata/worker_utilization/worker_utilization.c b/src/libnetdata/worker_utilization/worker_utilization.c index 4c61ea921ff9f8..d30434d66d1910 100644 --- a/src/libnetdata/worker_utilization/worker_utilization.c +++ b/src/libnetdata/worker_utilization/worker_utilization.c @@ -223,9 +223,7 @@ void worker_is_busy(size_t job_id) { } void worker_set_metric(size_t job_id, NETDATA_DOUBLE value) { - if(unlikely(!worker)) return; - - if(unlikely(job_id >= WORKER_UTILIZATION_MAX_JOB_TYPES)) + if(unlikely(!worker || job_id >= WORKER_UTILIZATION_MAX_JOB_TYPES)) return; switch(worker->per_job_type[job_id].type) { diff --git a/src/libnetdata/worker_utilization/worker_utilization.h b/src/libnetdata/worker_utilization/worker_utilization.h index e2f46c5a6f9284..806b7647b9bc82 100644 --- a/src/libnetdata/worker_utilization/worker_utilization.h +++ b/src/libnetdata/worker_utilization/worker_utilization.h @@ -5,7 +5,7 @@ // workers interfaces -#define WORKER_UTILIZATION_MAX_JOB_TYPES 50 +#define WORKER_UTILIZATION_MAX_JOB_TYPES 80 typedef enum __attribute__((packed)) { WORKER_METRIC_EMPTY = 0, diff --git a/src/ml/ad_charts.cc b/src/ml/ad_charts.cc index f70d009c4bed4a..abb9ea0ed5a65f 100644 --- a/src/ml/ad_charts.cc +++ b/src/ml/ad_charts.cc @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "ad_charts.h" +#include "ml_config.h" void ml_update_dimensions_chart(ml_host_t *host, const ml_machine_learning_stats_t &mls) { /* @@ -399,19 +400,19 @@ void ml_update_host_and_detection_rate_charts(ml_host_t *host, collected_number } } -void ml_update_training_statistics_chart(ml_training_thread_t *training_thread, const ml_training_stats_t &ts) { +void ml_update_training_statistics_chart(ml_worker_t *worker, const ml_queue_stats_t &stats) { /* * queue stats */ { - if (!training_thread->queue_stats_rs) { + if (!worker->queue_stats_rs) { char id_buf[1024]; char name_buf[1024]; - snprintfz(id_buf, 1024, "training_queue_%zu_stats", training_thread->id); - snprintfz(name_buf, 1024, "training_queue_%zu_stats", training_thread->id); + snprintfz(id_buf, 1024, "training_queue_%zu_stats", worker->id); + snprintfz(name_buf, 1024, "training_queue_%zu_stats", worker->id); - training_thread->queue_stats_rs = rrdset_create( + worker->queue_stats_rs = rrdset_create( localhost, "netdata", // type id_buf, // id @@ -426,34 +427,34 @@ void ml_update_training_statistics_chart(ml_training_thread_t *training_thread, localhost->rrd_update_every, // update_every RRDSET_TYPE_LINE// chart_type ); - rrdset_flag_set(training_thread->queue_stats_rs, RRDSET_FLAG_ANOMALY_DETECTION); + rrdset_flag_set(worker->queue_stats_rs, RRDSET_FLAG_ANOMALY_DETECTION); - training_thread->queue_stats_queue_size_rd = - rrddim_add(training_thread->queue_stats_rs, "queue_size", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - training_thread->queue_stats_popped_items_rd = - rrddim_add(training_thread->queue_stats_rs, "popped_items", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + worker->queue_stats_queue_size_rd = + rrddim_add(worker->queue_stats_rs, "queue_size", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + worker->queue_stats_popped_items_rd = + rrddim_add(worker->queue_stats_rs, "popped_items", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); } - rrddim_set_by_pointer(training_thread->queue_stats_rs, - training_thread->queue_stats_queue_size_rd, ts.queue_size); - rrddim_set_by_pointer(training_thread->queue_stats_rs, - training_thread->queue_stats_popped_items_rd, ts.num_popped_items); + rrddim_set_by_pointer(worker->queue_stats_rs, + worker->queue_stats_queue_size_rd, stats.queue_size); + rrddim_set_by_pointer(worker->queue_stats_rs, + worker->queue_stats_popped_items_rd, stats.num_popped_items); - rrdset_done(training_thread->queue_stats_rs); + rrdset_done(worker->queue_stats_rs); } /* * training stats */ { - if (!training_thread->training_time_stats_rs) { + if (!worker->training_time_stats_rs) { char id_buf[1024]; char name_buf[1024]; - snprintfz(id_buf, 1024, "training_queue_%zu_time_stats", training_thread->id); - snprintfz(name_buf, 1024, "training_queue_%zu_time_stats", training_thread->id); + snprintfz(id_buf, 1024, "training_queue_%zu_time_stats", worker->id); + snprintfz(name_buf, 1024, "training_queue_%zu_time_stats", worker->id); - training_thread->training_time_stats_rs = rrdset_create( + worker->training_time_stats_rs = rrdset_create( localhost, "netdata", // type id_buf, // id @@ -468,38 +469,38 @@ void ml_update_training_statistics_chart(ml_training_thread_t *training_thread, localhost->rrd_update_every, // update_every RRDSET_TYPE_LINE// chart_type ); - rrdset_flag_set(training_thread->training_time_stats_rs, RRDSET_FLAG_ANOMALY_DETECTION); - - training_thread->training_time_stats_allotted_rd = - rrddim_add(training_thread->training_time_stats_rs, "allotted", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); - training_thread->training_time_stats_consumed_rd = - rrddim_add(training_thread->training_time_stats_rs, "consumed", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); - training_thread->training_time_stats_remaining_rd = - rrddim_add(training_thread->training_time_stats_rs, "remaining", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + rrdset_flag_set(worker->training_time_stats_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + worker->training_time_stats_allotted_rd = + rrddim_add(worker->training_time_stats_rs, "allotted", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + worker->training_time_stats_consumed_rd = + rrddim_add(worker->training_time_stats_rs, "consumed", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + worker->training_time_stats_remaining_rd = + rrddim_add(worker->training_time_stats_rs, "remaining", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); } - rrddim_set_by_pointer(training_thread->training_time_stats_rs, - training_thread->training_time_stats_allotted_rd, ts.allotted_ut); - rrddim_set_by_pointer(training_thread->training_time_stats_rs, - training_thread->training_time_stats_consumed_rd, ts.consumed_ut); - rrddim_set_by_pointer(training_thread->training_time_stats_rs, - training_thread->training_time_stats_remaining_rd, ts.remaining_ut); + rrddim_set_by_pointer(worker->training_time_stats_rs, + worker->training_time_stats_allotted_rd, stats.allotted_ut); + rrddim_set_by_pointer(worker->training_time_stats_rs, + worker->training_time_stats_consumed_rd, stats.consumed_ut); + rrddim_set_by_pointer(worker->training_time_stats_rs, + worker->training_time_stats_remaining_rd, stats.remaining_ut); - rrdset_done(training_thread->training_time_stats_rs); + rrdset_done(worker->training_time_stats_rs); } /* * training result stats */ { - if (!training_thread->training_results_rs) { + if (!worker->training_results_rs) { char id_buf[1024]; char name_buf[1024]; - snprintfz(id_buf, 1024, "training_queue_%zu_results", training_thread->id); - snprintfz(name_buf, 1024, "training_queue_%zu_results", training_thread->id); + snprintfz(id_buf, 1024, "training_queue_%zu_results", worker->id); + snprintfz(name_buf, 1024, "training_queue_%zu_results", worker->id); - training_thread->training_results_rs = rrdset_create( + worker->training_results_rs = rrdset_create( localhost, "netdata", // type id_buf, // id @@ -514,37 +515,48 @@ void ml_update_training_statistics_chart(ml_training_thread_t *training_thread, localhost->rrd_update_every, // update_every RRDSET_TYPE_LINE// chart_type ); - rrdset_flag_set(training_thread->training_results_rs, RRDSET_FLAG_ANOMALY_DETECTION); - - training_thread->training_results_ok_rd = - rrddim_add(training_thread->training_results_rs, "ok", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - training_thread->training_results_invalid_query_time_range_rd = - rrddim_add(training_thread->training_results_rs, "invalid-queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - training_thread->training_results_not_enough_collected_values_rd = - rrddim_add(training_thread->training_results_rs, "not-enough-values", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - training_thread->training_results_null_acquired_dimension_rd = - rrddim_add(training_thread->training_results_rs, "null-acquired-dimensions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - training_thread->training_results_chart_under_replication_rd = - rrddim_add(training_thread->training_results_rs, "chart-under-replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + rrdset_flag_set(worker->training_results_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + worker->training_results_ok_rd = + rrddim_add(worker->training_results_rs, "ok", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + worker->training_results_invalid_query_time_range_rd = + rrddim_add(worker->training_results_rs, "invalid-queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + worker->training_results_not_enough_collected_values_rd = + rrddim_add(worker->training_results_rs, "not-enough-values", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + worker->training_results_null_acquired_dimension_rd = + rrddim_add(worker->training_results_rs, "null-acquired-dimensions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + worker->training_results_chart_under_replication_rd = + rrddim_add(worker->training_results_rs, "chart-under-replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); } - rrddim_set_by_pointer(training_thread->training_results_rs, - training_thread->training_results_ok_rd, ts.training_result_ok); - rrddim_set_by_pointer(training_thread->training_results_rs, - training_thread->training_results_invalid_query_time_range_rd, ts.training_result_invalid_query_time_range); - rrddim_set_by_pointer(training_thread->training_results_rs, - training_thread->training_results_not_enough_collected_values_rd, ts.training_result_not_enough_collected_values); - rrddim_set_by_pointer(training_thread->training_results_rs, - training_thread->training_results_null_acquired_dimension_rd, ts.training_result_null_acquired_dimension); - rrddim_set_by_pointer(training_thread->training_results_rs, - training_thread->training_results_chart_under_replication_rd, ts.training_result_chart_under_replication); - - rrdset_done(training_thread->training_results_rs); + rrddim_set_by_pointer(worker->training_results_rs, + worker->training_results_ok_rd, stats.item_result_ok); + rrddim_set_by_pointer(worker->training_results_rs, + worker->training_results_invalid_query_time_range_rd, stats.item_result_invalid_query_time_range); + rrddim_set_by_pointer(worker->training_results_rs, + worker->training_results_not_enough_collected_values_rd, stats.item_result_not_enough_collected_values); + rrddim_set_by_pointer(worker->training_results_rs, + worker->training_results_null_acquired_dimension_rd, stats.item_result_null_acquired_dimension); + rrddim_set_by_pointer(worker->training_results_rs, + worker->training_results_chart_under_replication_rd, stats.item_result_chart_under_replication); + + rrdset_done(worker->training_results_rs); } } -void ml_update_global_statistics_charts(uint64_t models_consulted) { - if (Cfg.enable_statistics_charts) { +void ml_update_global_statistics_charts(uint64_t models_consulted, + uint64_t models_received, + uint64_t models_sent, + uint64_t models_ignored, + uint64_t models_deserialization_failures, + uint64_t memory_consumption, + uint64_t memory_new, + uint64_t memory_delete) +{ + if (!Cfg.enable_statistics_charts) + return; + + { static RRDSET *st = NULL; static RRDDIM *rd = NULL; @@ -571,4 +583,98 @@ void ml_update_global_statistics_charts(uint64_t models_consulted) { rrdset_done(st); } + + { + static RRDSET *st = NULL; + static RRDDIM *rd_received = NULL; + static RRDDIM *rd_sent = NULL; + static RRDDIM *rd_ignored = NULL; + static RRDDIM *rd_deserialization_failures = NULL; + + if (unlikely(!st)) { + st = rrdset_create_localhost( + "netdata" // type + , "ml_models_streamed" // id + , NULL // name + , NETDATA_ML_CHART_FAMILY // family + , NULL // context + , "KMeans models streamed" // title + , "models" // units + , NETDATA_ML_PLUGIN // plugin + , NETDATA_ML_MODULE_DETECTION // module + , NETDATA_ML_CHART_PRIO_MACHINE_LEARNING_STATUS // priority + , localhost->rrd_update_every // update_every + , RRDSET_TYPE_LINE // chart_type + ); + + rd_received = rrddim_add(st, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_sent = rrddim_add(st, "sent", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_ignored = rrddim_add(st, "ignored", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_deserialization_failures = rrddim_add(st, "deserialization failures", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st, rd_received, (collected_number) models_received); + rrddim_set_by_pointer(st, rd_sent, (collected_number) models_sent); + rrddim_set_by_pointer(st, rd_ignored, (collected_number) models_ignored); + rrddim_set_by_pointer(st, rd_deserialization_failures, (collected_number) models_deserialization_failures); + + rrdset_done(st); + } + + { + static RRDSET *st = NULL; + static RRDDIM *rd_memory_consumption = NULL; + + if (unlikely(!st)) { + st = rrdset_create_localhost( + "netdata" // type + , "ml_memory_used" // id + , NULL // name + , NETDATA_ML_CHART_FAMILY // family + , NULL // context + , "ML memory usage" // title + , "bytes" // units + , NETDATA_ML_PLUGIN // plugin + , NETDATA_ML_MODULE_DETECTION // module + , NETDATA_ML_CHART_PRIO_MACHINE_LEARNING_STATUS // priority + , localhost->rrd_update_every // update_every + , RRDSET_TYPE_LINE // chart_type + ); + + rd_memory_consumption = rrddim_add(st, "used", NULL, 1024, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(st, rd_memory_consumption, (collected_number) memory_consumption / (1024)); + rrdset_done(st); + } + + { + static RRDSET *st = NULL; + static RRDDIM *rd_memory_new = NULL; + static RRDDIM *rd_memory_delete = NULL; + + if (unlikely(!st)) { + st = rrdset_create_localhost( + "netdata" // type + , "ml_memory_ops" // id + , NULL // name + , NETDATA_ML_CHART_FAMILY // family + , NULL // context + , "ML memory operations" // title + , "count" // units + , NETDATA_ML_PLUGIN // plugin + , NETDATA_ML_MODULE_DETECTION // module + , NETDATA_ML_CHART_PRIO_MACHINE_LEARNING_STATUS // priority + , localhost->rrd_update_every // update_every + , RRDSET_TYPE_LINE // chart_type + ); + + rd_memory_new = rrddim_add(st, "new", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_memory_delete = rrddim_add(st, "delete", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st, rd_memory_new, (collected_number) memory_new); + rrddim_set_by_pointer(st, rd_memory_delete, (collected_number) memory_delete); + rrdset_done(st); + } } diff --git a/src/ml/ad_charts.h b/src/ml/ad_charts.h index 349b369a24dcf7..1b11ba871e4fce 100644 --- a/src/ml/ad_charts.h +++ b/src/ml/ad_charts.h @@ -3,12 +3,12 @@ #ifndef ML_ADCHARTS_H #define ML_ADCHARTS_H -#include "ml-private.h" +#include "ml_private.h" void ml_update_dimensions_chart(ml_host_t *host, const ml_machine_learning_stats_t &mls); void ml_update_host_and_detection_rate_charts(ml_host_t *host, collected_number anomaly_rate); -void ml_update_training_statistics_chart(ml_training_thread_t *training_thread, const ml_training_stats_t &ts); +void ml_update_training_statistics_chart(ml_worker_t *worker, const ml_queue_stats_t &ts); #endif /* ML_ADCHARTS_H */ diff --git a/src/ml/ml-dummy.c b/src/ml/ml-dummy.c index 64a1a6b099e3c6..036a2b1a2bf9fa 100644 --- a/src/ml/ml-dummy.c +++ b/src/ml/ml-dummy.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "ml.h" +#include "ml_public.h" #if !defined(ENABLE_ML) @@ -41,18 +41,6 @@ void ml_host_stop(RRDHOST *rh) { UNUSED(rh); } -void ml_host_start_training_thread(RRDHOST *rh) { - UNUSED(rh); -} - -void ml_host_stop_training_thread(RRDHOST *rh) { - UNUSED(rh); -} - -void ml_host_cancel_training_thread(RRDHOST *rh) { - UNUSED(rh); -} - void ml_host_get_info(RRDHOST *rh, BUFFER *wb) { UNUSED(rh); UNUSED(wb); @@ -105,8 +93,27 @@ int ml_dimension_load_models(RRDDIM *rd, sqlite3_stmt **stmp __maybe_unused) { return 0; } -void ml_update_global_statistics_charts(uint64_t models_consulted) { +void ml_dimension_received_anomaly(RRDDIM *rd, bool is_anomalous) { + UNUSED(rd); + UNUSED(is_anomalous); +} + +void ml_update_global_statistics_charts(uint64_t models_consulted, + uint64_t models_received, + uint64_t models_sent, + uint64_t models_ignored, + uint64_t models_deserialization_failures, + uint64_t memory_consumption, + uint64_t memory_new, + uint64_t memory_delete) { UNUSED(models_consulted); + UNUSED(models_received); + UNUSED(models_sent); + UNUSED(models_ignored); + UNUSED(models_deserialization_failures); + UNUSED(memory_consumption); + UNUSED(memory_new); + UNUSED(memory_delete); } bool ml_host_get_host_status(RRDHOST *rh __maybe_unused, struct ml_metrics_statistics *mlm) { @@ -118,4 +125,10 @@ bool ml_host_running(RRDHOST *rh __maybe_unused) { return false; } +bool ml_model_received_from_child(RRDHOST *host, const char *json) { + UNUSED(host); + UNUSED(json); + return false; +} + #endif diff --git a/src/ml/ml-private.h b/src/ml/ml-private.h deleted file mode 100644 index cda24d0eda1ff3..00000000000000 --- a/src/ml/ml-private.h +++ /dev/null @@ -1,369 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_ML_PRIVATE_H -#define NETDATA_ML_PRIVATE_H - -#include "dlib/dlib/matrix.h" - -// CentOS 7 shenanigans -#include -using std::isfinite; - -#include "ml/ml.h" - -#include -#include -#include - -typedef double calculated_number_t; -typedef dlib::matrix DSample; - -/* - * Features - */ - -typedef struct { - size_t diff_n; - size_t smooth_n; - size_t lag_n; - - calculated_number_t *dst; - size_t dst_n; - - calculated_number_t *src; - size_t src_n; - - std::vector &preprocessed_features; -} ml_features_t; - -/* - * KMeans - */ - -typedef struct { - std::vector cluster_centers; - - calculated_number_t min_dist; - calculated_number_t max_dist; - - uint32_t after; - uint32_t before; -} ml_kmeans_t; - -typedef struct machine_learning_stats_t { - size_t num_machine_learning_status_enabled; - size_t num_machine_learning_status_disabled_sp; - - size_t num_metric_type_constant; - size_t num_metric_type_variable; - - size_t num_training_status_untrained; - size_t num_training_status_pending_without_model; - size_t num_training_status_trained; - size_t num_training_status_pending_with_model; - size_t num_training_status_silenced; - - size_t num_anomalous_dimensions; - size_t num_normal_dimensions; -} ml_machine_learning_stats_t; - -typedef struct training_stats_t { - size_t queue_size; - size_t num_popped_items; - - usec_t allotted_ut; - usec_t consumed_ut; - usec_t remaining_ut; - - size_t training_result_ok; - size_t training_result_invalid_query_time_range; - size_t training_result_not_enough_collected_values; - size_t training_result_null_acquired_dimension; - size_t training_result_chart_under_replication; -} ml_training_stats_t; - -enum ml_metric_type { - // The dimension has constant values, no need to train - METRIC_TYPE_CONSTANT, - - // The dimension's values fluctuate, we need to generate a model - METRIC_TYPE_VARIABLE, -}; - -enum ml_machine_learning_status { - // Enable training/prediction - MACHINE_LEARNING_STATUS_ENABLED, - - // Disable because configuration pattern matches the chart's id - MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART, -}; - -enum ml_training_status { - // We don't have a model for this dimension - TRAINING_STATUS_UNTRAINED, - - // Request for training sent, but we don't have any models yet - TRAINING_STATUS_PENDING_WITHOUT_MODEL, - - // Request to update existing models sent - TRAINING_STATUS_PENDING_WITH_MODEL, - - // Have a valid, up-to-date model - TRAINING_STATUS_TRAINED, - - // Have a valid, up-to-date model that is silenced because its too noisy - TRAINING_STATUS_SILENCED, -}; - -enum ml_training_result { - // We managed to create a KMeans model - TRAINING_RESULT_OK, - - // Could not query DB with a correct time range - TRAINING_RESULT_INVALID_QUERY_TIME_RANGE, - - // Did not gather enough data from DB to run KMeans - TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES, - - // Acquired a null dimension - TRAINING_RESULT_NULL_ACQUIRED_DIMENSION, - - // Chart is under replication - TRAINING_RESULT_CHART_UNDER_REPLICATION, -}; - -typedef struct { - // Chart/dimension we want to train - char machine_guid[GUID_LEN + 1]; - STRING *chart_id; - STRING *dimension_id; - - // Creation time of request - time_t request_time; - - // First/last entry of this dimension in DB - // at the point the request was made - time_t first_entry_on_request; - time_t last_entry_on_request; -} ml_training_request_t; - -typedef struct { - // Time when the request for this response was made - time_t request_time; - - // First/last entry of the dimension in DB when generating the request - time_t first_entry_on_request; - time_t last_entry_on_request; - - // First/last entry of the dimension in DB when generating the response - time_t first_entry_on_response; - time_t last_entry_on_response; - - // After/Before timestamps of our DB query - time_t query_after_t; - time_t query_before_t; - - // Actual after/before returned by the DB query ops - time_t db_after_t; - time_t db_before_t; - - // Number of doubles returned by the DB query - size_t collected_values; - - // Number of values we return to the caller - size_t total_values; - - // Result of training response - enum ml_training_result result; -} ml_training_response_t; - -/* - * Queue -*/ - -typedef struct { - std::queue internal; - netdata_mutex_t mutex; - pthread_cond_t cond_var; - std::atomic exit; -} ml_queue_t; - -typedef struct { - RRDDIM *rd; - - enum ml_metric_type mt; - enum ml_training_status ts; - enum ml_machine_learning_status mls; - - ml_training_response_t tr; - time_t last_training_time; - - std::vector cns; - - std::vector km_contexts; - SPINLOCK slock; - ml_kmeans_t kmeans; - std::vector feature; - - uint32_t suppression_window_counter; - uint32_t suppression_anomaly_counter; -} ml_dimension_t; - -typedef struct { - RRDSET *rs; - ml_machine_learning_stats_t mls; -} ml_chart_t; - -void ml_chart_update_dimension(ml_chart_t *chart, ml_dimension_t *dim, bool is_anomalous); - -typedef struct { - RRDDIM *rd; - size_t normal_dimensions; - size_t anomalous_dimensions; -} ml_type_anomaly_rate_t; - -typedef struct { - RRDHOST *rh; - - std::atomic ml_running; - - ml_machine_learning_stats_t mls; - - calculated_number_t host_anomaly_rate; - - netdata_mutex_t mutex; - - ml_queue_t *training_queue; - - /* - * bookkeeping for anomaly detection charts - */ - - RRDSET *ml_running_rs; - RRDDIM *ml_running_rd; - - RRDSET *machine_learning_status_rs; - RRDDIM *machine_learning_status_enabled_rd; - RRDDIM *machine_learning_status_disabled_sp_rd; - - RRDSET *metric_type_rs; - RRDDIM *metric_type_constant_rd; - RRDDIM *metric_type_variable_rd; - - RRDSET *training_status_rs; - RRDDIM *training_status_untrained_rd; - RRDDIM *training_status_pending_without_model_rd; - RRDDIM *training_status_trained_rd; - RRDDIM *training_status_pending_with_model_rd; - RRDDIM *training_status_silenced_rd; - - RRDSET *dimensions_rs; - RRDDIM *dimensions_anomalous_rd; - RRDDIM *dimensions_normal_rd; - - RRDSET *anomaly_rate_rs; - RRDDIM *anomaly_rate_rd; - - RRDSET *detector_events_rs; - RRDDIM *detector_events_above_threshold_rd; - RRDDIM *detector_events_new_anomaly_event_rd; - - RRDSET *type_anomaly_rate_rs; - SPINLOCK type_anomaly_rate_spinlock; - std::unordered_map type_anomaly_rate; -} ml_host_t; - -typedef struct { - nd_uuid_t metric_uuid; - ml_kmeans_t kmeans; -} ml_model_info_t; - -typedef struct { - size_t id; - ND_THREAD *nd_thread; - netdata_mutex_t nd_mutex; - - ml_queue_t *training_queue; - ml_training_stats_t training_stats; - - calculated_number_t *training_cns; - calculated_number_t *scratch_training_cns; - std::vector training_samples; - - std::vector pending_model_info; - - RRDSET *queue_stats_rs; - RRDDIM *queue_stats_queue_size_rd; - RRDDIM *queue_stats_popped_items_rd; - - RRDSET *training_time_stats_rs; - RRDDIM *training_time_stats_allotted_rd; - RRDDIM *training_time_stats_consumed_rd; - RRDDIM *training_time_stats_remaining_rd; - - RRDSET *training_results_rs; - RRDDIM *training_results_ok_rd; - RRDDIM *training_results_invalid_query_time_range_rd; - RRDDIM *training_results_not_enough_collected_values_rd; - RRDDIM *training_results_null_acquired_dimension_rd; - RRDDIM *training_results_chart_under_replication_rd; - - size_t num_db_transactions; - size_t num_models_to_prune; -} ml_training_thread_t; - -typedef struct { - int enable_anomaly_detection; - - unsigned max_train_samples; - unsigned min_train_samples; - unsigned train_every; - - unsigned num_models_to_use; - unsigned delete_models_older_than; - - unsigned db_engine_anomaly_rate_every; - - unsigned diff_n; - unsigned smooth_n; - unsigned lag_n; - - double random_sampling_ratio; - unsigned max_kmeans_iters; - - double dimension_anomaly_score_threshold; - - double host_anomaly_rate_threshold; - RRDR_TIME_GROUPING anomaly_detection_grouping_method; - time_t anomaly_detection_query_duration; - - bool stream_anomaly_detection_charts; - - std::string hosts_to_skip; - SIMPLE_PATTERN *sp_host_to_skip; - - std::string charts_to_skip; - SIMPLE_PATTERN *sp_charts_to_skip; - - std::vector random_nums; - - ND_THREAD *detection_thread; - std::atomic detection_stop; - - size_t num_training_threads; - size_t flush_models_batch_size; - - std::vector training_threads; - std::atomic training_stop; - - size_t suppression_window; - size_t suppression_threshold; - - bool enable_statistics_charts; -} ml_config_t; - -void ml_config_load(ml_config_t *cfg); - -extern ml_config_t Cfg; - -#endif /* NETDATA_ML_PRIVATE_H */ diff --git a/src/ml/ml.cc b/src/ml/ml.cc index 61574b287e9f62..618239f534dc2a 100644 --- a/src/ml/ml.cc +++ b/src/ml/ml.cc @@ -1,16 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "dlib/dlib/clustering.h" +#include "ml_private.h" -#include "ml-private.h" - -#include +#include #include "ad_charts.h" #include "database/sqlite/sqlite3.h" -#define ML_METADATA_VERSION 2 - #define WORKER_TRAIN_QUEUE_POP 0 #define WORKER_TRAIN_ACQUIRE_DIMENSION 1 #define WORKER_TRAIN_QUERY 2 @@ -20,315 +16,44 @@ #define WORKER_TRAIN_UPDATE_HOST 6 #define WORKER_TRAIN_FLUSH_MODELS 7 -static sqlite3 *db = NULL; +sqlite3 *ml_db = NULL; static netdata_mutex_t db_mutex = NETDATA_MUTEX_INITIALIZER; -/* - * Functions to convert enums to strings -*/ - -__attribute__((unused)) static const char * -ml_machine_learning_status_to_string(enum ml_machine_learning_status mls) -{ - switch (mls) { - case MACHINE_LEARNING_STATUS_ENABLED: - return "enabled"; - case MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART: - return "disabled-sp"; - default: - return "unknown"; - } -} - -__attribute__((unused)) static const char * -ml_metric_type_to_string(enum ml_metric_type mt) -{ - switch (mt) { - case METRIC_TYPE_CONSTANT: - return "constant"; - case METRIC_TYPE_VARIABLE: - return "variable"; - default: - return "unknown"; - } -} - -__attribute__((unused)) static const char * -ml_training_status_to_string(enum ml_training_status ts) -{ - switch (ts) { - case TRAINING_STATUS_PENDING_WITH_MODEL: - return "pending-with-model"; - case TRAINING_STATUS_PENDING_WITHOUT_MODEL: - return "pending-without-model"; - case TRAINING_STATUS_TRAINED: - return "trained"; - case TRAINING_STATUS_UNTRAINED: - return "untrained"; - case TRAINING_STATUS_SILENCED: - return "silenced"; - default: - return "unknown"; - } -} - -__attribute__((unused)) static const char * -ml_training_result_to_string(enum ml_training_result tr) -{ - switch (tr) { - case TRAINING_RESULT_OK: - return "ok"; - case TRAINING_RESULT_INVALID_QUERY_TIME_RANGE: - return "invalid-query"; - case TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES: - return "missing-values"; - case TRAINING_RESULT_NULL_ACQUIRED_DIMENSION: - return "null-acquired-dim"; - case TRAINING_RESULT_CHART_UNDER_REPLICATION: - return "chart-under-replication"; - default: - return "unknown"; - } -} - -/* - * Features -*/ - -// subtract elements that are `diff_n` positions apart -static void -ml_features_diff(ml_features_t *features) -{ - if (features->diff_n == 0) - return; - - for (size_t idx = 0; idx != (features->src_n - features->diff_n); idx++) { - size_t high = (features->src_n - 1) - idx; - size_t low = high - features->diff_n; - - features->dst[low] = features->src[high] - features->src[low]; - } - - size_t n = features->src_n - features->diff_n; - memcpy(features->src, features->dst, n * sizeof(calculated_number_t)); - - for (size_t idx = features->src_n - features->diff_n; idx != features->src_n; idx++) - features->src[idx] = 0.0; -} - -// a function that computes the window average of an array inplace -static void -ml_features_smooth(ml_features_t *features) -{ - calculated_number_t sum = 0.0; - - size_t idx = 0; - for (; idx != features->smooth_n - 1; idx++) - sum += features->src[idx]; - - for (; idx != (features->src_n - features->diff_n); idx++) { - sum += features->src[idx]; - calculated_number_t prev_cn = features->src[idx - (features->smooth_n - 1)]; - features->src[idx - (features->smooth_n - 1)] = sum / features->smooth_n; - sum -= prev_cn; - } - - for (idx = 0; idx != features->smooth_n; idx++) - features->src[(features->src_n - 1) - idx] = 0.0; -} - -// create lag'd vectors out of the preprocessed buffer -static void -ml_features_lag(ml_features_t *features) -{ - size_t n = features->src_n - features->diff_n - features->smooth_n + 1 - features->lag_n; - features->preprocessed_features.resize(n); - - unsigned target_num_samples = Cfg.max_train_samples * Cfg.random_sampling_ratio; - double sampling_ratio = std::min(static_cast(target_num_samples) / n, 1.0); - - uint32_t max_mt = std::numeric_limits::max(); - uint32_t cutoff = static_cast(max_mt) * sampling_ratio; - - size_t sample_idx = 0; - - for (size_t idx = 0; idx != n; idx++) { - DSample &DS = features->preprocessed_features[sample_idx++]; - DS.set_size(features->lag_n); - - if (Cfg.random_nums[idx] > cutoff) { - sample_idx--; - continue; - } - - for (size_t feature_idx = 0; feature_idx != features->lag_n + 1; feature_idx++) - DS(feature_idx) = features->src[idx + feature_idx]; - } - - features->preprocessed_features.resize(sample_idx); -} - -static void -ml_features_preprocess(ml_features_t *features) -{ - ml_features_diff(features); - ml_features_smooth(features); - ml_features_lag(features); -} - -/* - * KMeans -*/ - -static void -ml_kmeans_init(ml_kmeans_t *kmeans) -{ - kmeans->cluster_centers.reserve(2); - kmeans->min_dist = std::numeric_limits::max(); - kmeans->max_dist = std::numeric_limits::min(); -} - -static void -ml_kmeans_train(ml_kmeans_t *kmeans, const ml_features_t *features, time_t after, time_t before) -{ - kmeans->after = (uint32_t) after; - kmeans->before = (uint32_t) before; - - kmeans->min_dist = std::numeric_limits::max(); - kmeans->max_dist = std::numeric_limits::min(); - - kmeans->cluster_centers.clear(); - - dlib::pick_initial_centers(2, kmeans->cluster_centers, features->preprocessed_features); - dlib::find_clusters_using_kmeans(features->preprocessed_features, kmeans->cluster_centers, Cfg.max_kmeans_iters); - - for (const auto &preprocessed_feature : features->preprocessed_features) { - calculated_number_t mean_dist = 0.0; - - for (const auto &cluster_center : kmeans->cluster_centers) { - mean_dist += dlib::length(cluster_center - preprocessed_feature); - } - - mean_dist /= kmeans->cluster_centers.size(); - - if (mean_dist < kmeans->min_dist) - kmeans->min_dist = mean_dist; - - if (mean_dist > kmeans->max_dist) - kmeans->max_dist = mean_dist; - } -} - -static calculated_number_t -ml_kmeans_anomaly_score(const ml_kmeans_t *kmeans, const DSample &DS) -{ - calculated_number_t mean_dist = 0.0; - for (const auto &CC: kmeans->cluster_centers) - mean_dist += dlib::length(CC - DS); - - mean_dist /= kmeans->cluster_centers.size(); - - if (kmeans->max_dist == kmeans->min_dist) - return 0.0; - - calculated_number_t anomaly_score = 100.0 * std::abs((mean_dist - kmeans->min_dist) / (kmeans->max_dist - kmeans->min_dist)); - return (anomaly_score > 100.0) ? 100.0 : anomaly_score; -} - -/* - * Queue -*/ - -static ml_queue_t * -ml_queue_init() -{ - ml_queue_t *q = new ml_queue_t(); - - netdata_mutex_init(&q->mutex); - pthread_cond_init(&q->cond_var, NULL); - q->exit = false; - return q; -} - -static void -ml_queue_destroy(ml_queue_t *q) -{ - netdata_mutex_destroy(&q->mutex); - pthread_cond_destroy(&q->cond_var); - delete q; -} - -static void -ml_queue_push(ml_queue_t *q, const ml_training_request_t req) -{ - netdata_mutex_lock(&q->mutex); - q->internal.push(req); - pthread_cond_signal(&q->cond_var); - netdata_mutex_unlock(&q->mutex); -} - -static ml_training_request_t -ml_queue_pop(ml_queue_t *q) -{ - netdata_mutex_lock(&q->mutex); - - ml_training_request_t req = { - {'\0'}, // machine_guid - NULL, // chart id - NULL, // dimension id - 0, // current time - 0, // first entry - 0 // last entry - }; +typedef struct { + // Time when the request for this response was made + time_t request_time; - while (q->internal.empty()) { - pthread_cond_wait(&q->cond_var, &q->mutex); + // First/last entry of the dimension in DB when generating the request + time_t first_entry_on_request; + time_t last_entry_on_request; - if (q->exit) { - netdata_mutex_unlock(&q->mutex); + // First/last entry of the dimension in DB when generating the response + time_t first_entry_on_response; + time_t last_entry_on_response; - // We return a dummy request because the queue has been signaled - return req; - } - } + // After/Before timestamps of our DB query + time_t query_after_t; + time_t query_before_t; - req = q->internal.front(); - q->internal.pop(); + // Actual after/before returned by the DB query ops + time_t db_after_t; + time_t db_before_t; - netdata_mutex_unlock(&q->mutex); - return req; -} - -static size_t -ml_queue_size(ml_queue_t *q) -{ - netdata_mutex_lock(&q->mutex); - size_t size = q->internal.size(); - netdata_mutex_unlock(&q->mutex); - return size; -} - -static void -ml_queue_signal(ml_queue_t *q) -{ - netdata_mutex_lock(&q->mutex); - q->exit = true; - pthread_cond_signal(&q->cond_var); - netdata_mutex_unlock(&q->mutex); -} + // Number of doubles returned by the DB query + size_t collected_values; -/* - * Dimension -*/ + // Number of values we return to the caller + size_t total_values; +} ml_training_response_t; -static std::pair -ml_dimension_calculated_numbers(ml_training_thread_t *training_thread, ml_dimension_t *dim, const ml_training_request_t &training_request) +static std::pair +ml_dimension_calculated_numbers(ml_worker_t *worker, ml_dimension_t *dim, const ml_request_create_new_model_t &req) { ml_training_response_t training_response = {}; - training_response.request_time = training_request.request_time; - training_response.first_entry_on_request = training_request.first_entry_on_request; - training_response.last_entry_on_request = training_request.last_entry_on_request; + training_response.request_time = req.request_time; + training_response.first_entry_on_request = req.first_entry_on_request; + training_response.last_entry_on_request = req.last_entry_on_request; training_response.first_entry_on_response = rrddim_first_entry_s_of_tier(dim->rd, 0); training_response.last_entry_on_response = rrddim_last_entry_s_of_tier(dim->rd, 0); @@ -344,13 +69,11 @@ ml_dimension_calculated_numbers(ml_training_thread_t *training_thread, ml_dimens ); if (training_response.query_after_t >= training_response.query_before_t) { - training_response.result = TRAINING_RESULT_INVALID_QUERY_TIME_RANGE; - return { NULL, training_response }; + return { ML_WORKER_RESULT_INVALID_QUERY_TIME_RANGE, training_response }; } if (rrdset_is_replicating(dim->rd->rrdset)) { - training_response.result = TRAINING_RESULT_CHART_UNDER_REPLICATION; - return { NULL, training_response }; + return { ML_WORKER_RESULT_CHART_UNDER_REPLICATION, training_response }; } /* @@ -363,7 +86,7 @@ ml_dimension_calculated_numbers(ml_training_thread_t *training_thread, ml_dimens STORAGE_PRIORITY_BEST_EFFORT); size_t idx = 0; - memset(training_thread->training_cns, 0, sizeof(calculated_number_t) * max_n * (Cfg.lag_n + 1)); + memset(worker->training_cns, 0, sizeof(calculated_number_t) * max_n * (Cfg.lag_n + 1)); calculated_number_t last_value = std::numeric_limits::quiet_NaN(); while (!storage_engine_query_is_finished(&handle)) { @@ -380,33 +103,31 @@ ml_dimension_calculated_numbers(ml_training_thread_t *training_thread, ml_dimens training_response.db_after_t = timestamp; training_response.db_before_t = timestamp; - training_thread->training_cns[idx] = value; - last_value = training_thread->training_cns[idx]; + worker->training_cns[idx] = value; + last_value = worker->training_cns[idx]; training_response.collected_values++; } else - training_thread->training_cns[idx] = last_value; + worker->training_cns[idx] = last_value; idx++; } storage_engine_query_finalize(&handle); - global_statistics_ml_query_completed(/* points_read */ idx); + telemetry_queries_ml_query_completed(/* points_read */ idx); training_response.total_values = idx; if (training_response.collected_values < min_n) { - training_response.result = TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES; - return { NULL, training_response }; + return { ML_WORKER_RESULT_NOT_ENOUGH_COLLECTED_VALUES, training_response }; } // Find first non-NaN value. - for (idx = 0; std::isnan(training_thread->training_cns[idx]); idx++, training_response.total_values--) { } + for (idx = 0; std::isnan(worker->training_cns[idx]); idx++, training_response.total_values--) { } // Overwrite NaN values. if (idx != 0) - memmove(training_thread->training_cns, &training_thread->training_cns[idx], sizeof(calculated_number_t) * training_response.total_values); + memmove(worker->training_cns, &worker->training_cns[idx], sizeof(calculated_number_t) * training_response.total_values); - training_response.result = TRAINING_RESULT_OK; - return { training_thread->training_cns, training_response }; + return { ML_WORKER_RESULT_OK, training_response }; } const char *db_models_create_table = @@ -443,19 +164,19 @@ const char *db_models_prune = "WHERE after < @after LIMIT @n;"; static int -ml_dimension_add_model(const nd_uuid_t *metric_uuid, const ml_kmeans_t *km) +ml_dimension_add_model(const nd_uuid_t *metric_uuid, const ml_kmeans_inlined_t *inlined_km) { static __thread sqlite3_stmt *res = NULL; int param = 0; int rc = 0; - if (unlikely(!db)) { + if (unlikely(!ml_db)) { error_report("Database has not been initialized"); return 1; } if (unlikely(!res)) { - rc = prepare_statement(db, db_models_add_model, &res); + rc = prepare_statement(ml_db, db_models_add_model, &res); if (unlikely(rc != SQLITE_OK)) { error_report("Failed to prepare statement to store model, rc = %d", rc); return 1; @@ -466,26 +187,23 @@ ml_dimension_add_model(const nd_uuid_t *metric_uuid, const ml_kmeans_t *km) if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_int(res, ++param, (int) km->after); + rc = sqlite3_bind_int(res, ++param, (int) inlined_km->after); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_int(res, ++param, (int) km->before); + rc = sqlite3_bind_int(res, ++param, (int) inlined_km->before); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_double(res, ++param, km->min_dist); + rc = sqlite3_bind_double(res, ++param, inlined_km->min_dist); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_double(res, ++param, km->max_dist); + rc = sqlite3_bind_double(res, ++param, inlined_km->max_dist); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - if (km->cluster_centers.size() != 2) - fatal("Expected 2 cluster centers, got %zu", km->cluster_centers.size()); - - for (const DSample &ds : km->cluster_centers) { + for (const DSample &ds : inlined_km->cluster_centers) { if (ds.size() != 6) fatal("Expected dsample with 6 dimensions, got %ld", ds.size()); @@ -526,13 +244,13 @@ ml_dimension_delete_models(const nd_uuid_t *metric_uuid, time_t before) int rc = 0; int param = 0; - if (unlikely(!db)) { + if (unlikely(!ml_db)) { error_report("Database has not been initialized"); return 1; } if (unlikely(!res)) { - rc = prepare_statement(db, db_models_delete, &res); + rc = prepare_statement(ml_db, db_models_delete, &res); if (unlikely(rc != SQLITE_OK)) { error_report("Failed to prepare statement to delete models, rc = %d", rc); return rc; @@ -576,13 +294,13 @@ ml_prune_old_models(size_t num_models_to_prune) int rc = 0; int param = 0; - if (unlikely(!db)) { + if (unlikely(!ml_db)) { error_report("Database has not been initialized"); return 1; } if (unlikely(!res)) { - rc = prepare_statement(db, db_models_prune, &res); + rc = prepare_statement(ml_db, db_models_prune, &res); if (unlikely(rc != SQLITE_OK)) { error_report("Failed to prepare statement to prune models, rc = %d", rc); return rc; @@ -639,13 +357,13 @@ int ml_dimension_load_models(RRDDIM *rd, sqlite3_stmt **active_stmt) { int rc = 0; int param = 0; - if (unlikely(!db)) { + if (unlikely(!ml_db)) { error_report("Database has not been initialized"); return 1; } if (unlikely(!res)) { - rc = sqlite3_prepare_v2(db, db_models_load, -1, &res, NULL); + rc = sqlite3_prepare_v2(ml_db, db_models_load, -1, &res, NULL); if (unlikely(rc != SQLITE_OK)) { error_report("Failed to prepare statement to load models, rc = %d", rc); return 1; @@ -692,7 +410,7 @@ int ml_dimension_load_models(RRDDIM *rd, sqlite3_stmt **active_stmt) { km.cluster_centers[1](4) = sqlite3_column_double(res, 16); km.cluster_centers[1](5) = sqlite3_column_double(res, 17); - dim->km_contexts.push_back(km); + dim->km_contexts.emplace_back(km); } if (!dim->km_contexts.empty()) { @@ -721,14 +439,210 @@ int ml_dimension_load_models(RRDDIM *rd, sqlite3_stmt **active_stmt) { return 1; } -static enum ml_training_result -ml_dimension_train_model(ml_training_thread_t *training_thread, ml_dimension_t *dim, const ml_training_request_t &training_request) +static void ml_dimension_serialize_kmeans(const ml_dimension_t *dim, BUFFER *wb) +{ + RRDDIM *rd = dim->rd; + + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT); + buffer_json_member_add_string(wb, "version", "1"); + buffer_json_member_add_string(wb, "machine-guid", rd->rrdset->rrdhost->machine_guid); + buffer_json_member_add_string(wb, "chart", rrdset_id(rd->rrdset)); + buffer_json_member_add_string(wb, "dimension", rrddim_id(rd)); + + buffer_json_member_add_object(wb, "model"); + ml_kmeans_serialize(&dim->km_contexts.back(), wb); + buffer_json_object_close(wb); + + buffer_json_finalize(wb); +} + +bool +ml_dimension_deserialize_kmeans(const char *json_str) +{ + if (!json_str) { + netdata_log_error("Failed to deserialize kmeans: json string is null"); + return false; + } + + struct json_object *root = json_tokener_parse(json_str); + if (!root) { + netdata_log_error("Failed to deserialize kmeans: json parsing failed"); + return false; + } + + // Check the version + { + struct json_object *tmp_obj; + if (!json_object_object_get_ex(root, "version", &tmp_obj)) { + netdata_log_error("Failed to deserialize kmeans: missing key 'version'"); + json_object_put(root); + return false; + } + if (!json_object_is_type(tmp_obj, json_type_string)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse string for 'version'"); + json_object_put(root); + return false; + } + const char *version = json_object_get_string(tmp_obj); + + if (strcmp(version, "1")) { + netdata_log_error("Failed to deserialize kmeans: expected version 1"); + json_object_put(root); + return false; + } + } + + // Get the value of each key + std::array values; + { + std::array keys = { + "machine-guid", + "chart", + "dimension", + }; + + struct json_object *tmp_obj; + for (size_t i = 0; i != keys.size(); i++) { + if (!json_object_object_get_ex(root, keys[i], &tmp_obj)) { + netdata_log_error("Failed to deserialize kmeans: missing key '%s'", keys[i]); + json_object_put(root); + return false; + } + if (!json_object_is_type(tmp_obj, json_type_string)) { + netdata_log_error("Failed to deserialize kmeans: missing string value for key '%s'", keys[i]); + json_object_put(root); + return false; + } + values[i] = json_object_get_string(tmp_obj); + } + } + + DimensionLookupInfo DLI(values[0], values[1], values[2]); + + // Parse the kmeans model + ml_kmeans_inlined_t inlined_km; + { + struct json_object *kmeans_obj; + if (!json_object_object_get_ex(root, "model", &kmeans_obj)) { + netdata_log_error("Failed to deserialize kmeans: missing key 'model'"); + json_object_put(root); + return false; + } + if (!json_object_is_type(kmeans_obj, json_type_object)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse object for 'model'"); + json_object_put(root); + return false; + } + + if (!ml_kmeans_deserialize(&inlined_km, kmeans_obj)) { + json_object_put(root); + return false; + } + } + + AcquiredDimension AcqDim(DLI); + if (!AcqDim.acquired()) { + netdata_log_error("Failed to deserialize kmeans: could not acquire dimension (machine-guid: %s, dimension: '%s.%s', reason: %s)", + DLI.machineGuid(), DLI.chartId(), DLI.dimensionId(), AcqDim.acquire_failure()); + json_object_put(root); + return false; + } + + ml_dimension_t *Dim = reinterpret_cast(AcqDim.dimension()); + if (!Dim) { + telemetry_ml_models_ignored(); + return true; + } + + ml_queue_item_t item; + item.type = ML_QUEUE_ITEM_TYPE_ADD_EXISTING_MODEL; + item.add_existing_model = { + DLI, inlined_km + }; + ml_queue_push(AcqDim.queue(), item); + + json_object_put(root); + return true; +} + +static void ml_dimension_stream_kmeans(const ml_dimension_t *dim) +{ + struct sender_state *s = dim->rd->rrdset->rrdhost->sender; + if (!s) + return; + + if(!stream_sender_has_capabilities(dim->rd->rrdset->rrdhost, STREAM_CAP_ML_MODELS)) + return; + + CLEAN_BUFFER *payload = buffer_create(0, NULL); + ml_dimension_serialize_kmeans(dim, payload); + + CLEAN_BUFFER *wb = buffer_create(0, NULL); + + buffer_sprintf( + wb, PLUGINSD_KEYWORD_JSON " " PLUGINSD_KEYWORD_JSON_CMD_ML_MODEL "\n%s\n" PLUGINSD_KEYWORD_JSON_END "\n", + buffer_tostring(payload)); + + sender_commit_clean_buffer(s, wb, STREAM_TRAFFIC_TYPE_METADATA); + telemetry_ml_models_sent(); +} + +static void ml_dimension_update_models(ml_worker_t *worker, ml_dimension_t *dim) +{ + worker_is_busy(WORKER_TRAIN_UPDATE_MODELS); + + spinlock_lock(&dim->slock); + + if (dim->km_contexts.size() < Cfg.num_models_to_use) { + dim->km_contexts.emplace_back(dim->kmeans); + } else { + bool can_drop_middle_km = false; + + if (Cfg.num_models_to_use > 2) { + const ml_kmeans_inlined_t *old_km = &dim->km_contexts[dim->km_contexts.size() - 1]; + const ml_kmeans_inlined_t *middle_km = &dim->km_contexts[dim->km_contexts.size() - 2]; + const ml_kmeans_t *new_km = &dim->kmeans; + + can_drop_middle_km = (middle_km->after < old_km->before) && + (middle_km->before > new_km->after); + } + + if (can_drop_middle_km) { + dim->km_contexts.back() = dim->kmeans; + } else { + std::rotate(std::begin(dim->km_contexts), std::begin(dim->km_contexts) + 1, std::end(dim->km_contexts)); + dim->km_contexts[dim->km_contexts.size() - 1] = dim->kmeans; + } + } + + dim->mt = METRIC_TYPE_CONSTANT; + dim->ts = TRAINING_STATUS_TRAINED; + + dim->suppression_anomaly_counter = 0; + dim->suppression_window_counter = 0; + + dim->last_training_time = rrddim_last_entry_s(dim->rd); + + // Add the newly generated model to the list of pending models to flush + ml_model_info_t model_info; + uuid_copy(model_info.metric_uuid, dim->rd->metric_uuid); + model_info.inlined_kmeans = dim->km_contexts.back(); + worker->pending_model_info.push_back(model_info); + + ml_dimension_stream_kmeans(dim); + + spinlock_unlock(&dim->slock); +} + +static enum ml_worker_result +ml_dimension_train_model(ml_worker_t *worker, ml_dimension_t *dim, const ml_request_create_new_model_t &req) { worker_is_busy(WORKER_TRAIN_QUERY); - auto P = ml_dimension_calculated_numbers(training_thread, dim, training_request); + auto P = ml_dimension_calculated_numbers(worker, dim, req); + ml_worker_result worker_result = P.first; ml_training_response_t training_response = P.second; - if (training_response.result != TRAINING_RESULT_OK) { + if (worker_result != ML_WORKER_RESULT_OK) { spinlock_lock(&dim->slock); dim->mt = METRIC_TYPE_CONSTANT; @@ -746,80 +660,36 @@ ml_dimension_train_model(ml_training_thread_t *training_thread, ml_dimension_t * dim->suppression_anomaly_counter = 0; dim->suppression_window_counter = 0; - dim->tr = training_response; dim->last_training_time = training_response.last_entry_on_response; - enum ml_training_result result = training_response.result; spinlock_unlock(&dim->slock); - return result; + return worker_result; } // compute kmeans worker_is_busy(WORKER_TRAIN_KMEANS); { - memcpy(training_thread->scratch_training_cns, training_thread->training_cns, + memcpy(worker->scratch_training_cns, worker->training_cns, training_response.total_values * sizeof(calculated_number_t)); ml_features_t features = { Cfg.diff_n, Cfg.smooth_n, Cfg.lag_n, - training_thread->scratch_training_cns, training_response.total_values, - training_thread->training_cns, training_response.total_values, - training_thread->training_samples + worker->scratch_training_cns, training_response.total_values, + worker->training_cns, training_response.total_values, + worker->training_samples }; ml_features_preprocess(&features); ml_kmeans_init(&dim->kmeans); - ml_kmeans_train(&dim->kmeans, &features, training_response.query_after_t, training_response.query_before_t); + ml_kmeans_train(&dim->kmeans, &features, Cfg.max_kmeans_iters, training_response.query_after_t, training_response.query_before_t); } // update models - worker_is_busy(WORKER_TRAIN_UPDATE_MODELS); - { - spinlock_lock(&dim->slock); - - if (dim->km_contexts.size() < Cfg.num_models_to_use) { - dim->km_contexts.push_back(std::move(dim->kmeans)); - } else { - bool can_drop_middle_km = false; - - if (Cfg.num_models_to_use > 2) { - const ml_kmeans_t *old_km = &dim->km_contexts[dim->km_contexts.size() - 1]; - const ml_kmeans_t *middle_km = &dim->km_contexts[dim->km_contexts.size() - 2]; - const ml_kmeans_t *new_km = &dim->kmeans; - - can_drop_middle_km = (middle_km->after < old_km->before) && - (middle_km->before > new_km->after); - } - - if (can_drop_middle_km) { - dim->km_contexts.back() = dim->kmeans; - } else { - std::rotate(std::begin(dim->km_contexts), std::begin(dim->km_contexts) + 1, std::end(dim->km_contexts)); - dim->km_contexts[dim->km_contexts.size() - 1] = std::move(dim->kmeans); - } - } - - dim->mt = METRIC_TYPE_CONSTANT; - dim->ts = TRAINING_STATUS_TRAINED; - - dim->suppression_anomaly_counter = 0; - dim->suppression_window_counter = 0; + ml_dimension_update_models(worker, dim); - dim->tr = training_response; - dim->last_training_time = rrddim_last_entry_s(dim->rd); - - // Add the newly generated model to the list of pending models to flush - ml_model_info_t model_info; - uuid_copy(model_info.metric_uuid, dim->rd->metric_uuid); - model_info.kmeans = dim->km_contexts.back(); - training_thread->pending_model_info.push_back(model_info); - - spinlock_unlock(&dim->slock); - } - - return training_response.result; + return worker_result; } static void @@ -853,21 +723,27 @@ ml_dimension_schedule_for_training(ml_dimension_t *dim, time_t curr_time) } if (schedule_for_training) { - ml_training_request_t req; + ml_request_create_new_model_t req; - memcpy(req.machine_guid, dim->rd->rrdset->rrdhost->machine_guid, GUID_LEN + 1); - req.chart_id = string_dup(dim->rd->rrdset->id); - req.dimension_id = string_dup(dim->rd->id); + req.DLI = DimensionLookupInfo( + &dim->rd->rrdset->rrdhost->machine_guid[0], + dim->rd->rrdset->id, + dim->rd->id + ); req.request_time = curr_time; req.first_entry_on_request = rrddim_first_entry_s(dim->rd); req.last_entry_on_request = rrddim_last_entry_s(dim->rd); ml_host_t *host = (ml_host_t *) dim->rd->rrdset->rrdhost->ml_host; - ml_queue_push(host->training_queue, req); + + ml_queue_item_t item; + item.type = ML_QUEUE_ITEM_TYPE_CREATE_NEW_MODEL; + item.create_new_model = req; + ml_queue_push(host->queue, item); } } -static bool +bool ml_dimension_predict(ml_dimension_t *dim, time_t curr_time, calculated_number_t value, bool exists) { // Nothing to do if ML is disabled for this dimension @@ -954,7 +830,7 @@ ml_dimension_predict(ml_dimension_t *dim, time_t curr_time, calculated_number_t continue; if (anomaly_score < (100 * Cfg.dimension_anomaly_score_threshold)) { - global_statistics_ml_models_consulted(models_consulted); + telemetry_ml_models_consulted(models_consulted); spinlock_unlock(&dim->slock); return false; } @@ -971,7 +847,7 @@ ml_dimension_predict(ml_dimension_t *dim, time_t curr_time, calculated_number_t spinlock_unlock(&dim->slock); - global_statistics_ml_models_consulted(models_consulted); + telemetry_ml_models_consulted(models_consulted); return sum; } @@ -1139,74 +1015,7 @@ ml_host_detect_once(ml_host_t *host) ml_update_host_and_detection_rate_charts(host, host->host_anomaly_rate * 10000.0); } -typedef struct { - RRDHOST_ACQUIRED *acq_rh; - RRDSET_ACQUIRED *acq_rs; - RRDDIM_ACQUIRED *acq_rd; - ml_dimension_t *dim; -} ml_acquired_dimension_t; - -static ml_acquired_dimension_t -ml_acquired_dimension_get(char *machine_guid, STRING *chart_id, STRING *dimension_id) -{ - RRDHOST_ACQUIRED *acq_rh = NULL; - RRDSET_ACQUIRED *acq_rs = NULL; - RRDDIM_ACQUIRED *acq_rd = NULL; - ml_dimension_t *dim = NULL; - - rrd_rdlock(); - - acq_rh = rrdhost_find_and_acquire(machine_guid); - if (acq_rh) { - RRDHOST *rh = rrdhost_acquired_to_rrdhost(acq_rh); - if (rh && !rrdhost_flag_check(rh, RRDHOST_FLAG_ORPHAN | RRDHOST_FLAG_ARCHIVED)) { - acq_rs = rrdset_find_and_acquire(rh, string2str(chart_id)); - if (acq_rs) { - RRDSET *rs = rrdset_acquired_to_rrdset(acq_rs); - if (rs && !rrdset_flag_check(rs, RRDSET_FLAG_OBSOLETE)) { - acq_rd = rrddim_find_and_acquire(rs, string2str(dimension_id)); - if (acq_rd) { - RRDDIM *rd = rrddim_acquired_to_rrddim(acq_rd); - if (rd) - dim = (ml_dimension_t *) rd->ml_dimension; - } - } - } - } - } - - rrd_rdunlock(); - - ml_acquired_dimension_t acq_dim = { - acq_rh, acq_rs, acq_rd, dim - }; - - return acq_dim; -} - -static void -ml_acquired_dimension_release(ml_acquired_dimension_t acq_dim) -{ - if (acq_dim.acq_rd) - rrddim_acquired_release(acq_dim.acq_rd); - - if (acq_dim.acq_rs) - rrdset_acquired_release(acq_dim.acq_rs); - - if (acq_dim.acq_rh) - rrdhost_acquired_release(acq_dim.acq_rh); -} - -static enum ml_training_result -ml_acquired_dimension_train(ml_training_thread_t *training_thread, ml_acquired_dimension_t acq_dim, const ml_training_request_t &tr) -{ - if (!acq_dim.dim) - return TRAINING_RESULT_NULL_ACQUIRED_DIMENSION; - - return ml_dimension_train_model(training_thread, acq_dim.dim, tr); -} - -static void * +void * ml_detect_main(void *arg) { UNUSED(arg); @@ -1239,33 +1048,33 @@ ml_detect_main(void *arg) if (Cfg.enable_statistics_charts) { // collect and update training thread stats - for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { - ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + for (size_t idx = 0; idx != Cfg.num_worker_threads; idx++) { + ml_worker_t *worker = &Cfg.workers[idx]; - netdata_mutex_lock(&training_thread->nd_mutex); - ml_training_stats_t training_stats = training_thread->training_stats; - training_thread->training_stats = {}; - netdata_mutex_unlock(&training_thread->nd_mutex); + netdata_mutex_lock(&worker->nd_mutex); + ml_queue_stats_t queue_stats = worker->queue_stats; + worker->queue_stats = {}; + netdata_mutex_unlock(&worker->nd_mutex); // calc the avg values - if (training_stats.num_popped_items) { - training_stats.queue_size /= training_stats.num_popped_items; - training_stats.allotted_ut /= training_stats.num_popped_items; - training_stats.consumed_ut /= training_stats.num_popped_items; - training_stats.remaining_ut /= training_stats.num_popped_items; + if (queue_stats.num_popped_items) { + queue_stats.queue_size /= queue_stats.num_popped_items; + queue_stats.allotted_ut /= queue_stats.num_popped_items; + queue_stats.consumed_ut /= queue_stats.num_popped_items; + queue_stats.remaining_ut /= queue_stats.num_popped_items; } else { - training_stats.queue_size = ml_queue_size(training_thread->training_queue); - training_stats.consumed_ut = 0; - training_stats.remaining_ut = training_stats.allotted_ut; - - training_stats.training_result_ok = 0; - training_stats.training_result_invalid_query_time_range = 0; - training_stats.training_result_not_enough_collected_values = 0; - training_stats.training_result_null_acquired_dimension = 0; - training_stats.training_result_chart_under_replication = 0; + queue_stats.queue_size = ml_queue_size(worker->queue); + queue_stats.consumed_ut = 0; + queue_stats.remaining_ut = queue_stats.allotted_ut; + + queue_stats.item_result_ok = 0; + queue_stats.item_result_invalid_query_time_range = 0; + queue_stats.item_result_not_enough_collected_values = 0; + queue_stats.item_result_null_acquired_dimension = 0; + queue_stats.item_result_chart_under_replication = 0; } - ml_update_training_statistics_chart(training_thread, training_stats); + ml_update_training_statistics_chart(worker, queue_stats); } } } @@ -1274,382 +1083,101 @@ ml_detect_main(void *arg) return NULL; } -/* - * Public API -*/ - -bool ml_capable() -{ - return true; -} - -bool ml_enabled(RRDHOST *rh) -{ - if (!rh) - return false; - - if (!Cfg.enable_anomaly_detection) - return false; - - if (simple_pattern_matches(Cfg.sp_host_to_skip, rrdhost_hostname(rh))) - return false; - - return true; -} - -bool ml_streaming_enabled() -{ - return Cfg.stream_anomaly_detection_charts; -} - -void ml_host_new(RRDHOST *rh) -{ - if (!ml_enabled(rh)) - return; - - ml_host_t *host = new ml_host_t(); - - host->rh = rh; - host->mls = ml_machine_learning_stats_t(); - host->host_anomaly_rate = 0.0; - host->anomaly_rate_rs = NULL; - - static std::atomic times_called(0); - host->training_queue = Cfg.training_threads[times_called++ % Cfg.num_training_threads].training_queue; - - netdata_mutex_init(&host->mutex); - spinlock_init(&host->type_anomaly_rate_spinlock); - - host->ml_running = true; - rh->ml_host = (rrd_ml_host_t *) host; -} - -void ml_host_delete(RRDHOST *rh) -{ - ml_host_t *host = (ml_host_t *) rh->ml_host; - if (!host) - return; - - netdata_mutex_destroy(&host->mutex); - - delete host; - rh->ml_host = NULL; -} - -void ml_host_start(RRDHOST *rh) { - ml_host_t *host = (ml_host_t *) rh->ml_host; - if (!host) - return; - - host->ml_running = true; -} - -void ml_host_stop(RRDHOST *rh) { - ml_host_t *host = (ml_host_t *) rh->ml_host; - if (!host || !host->ml_running) - return; - - netdata_mutex_lock(&host->mutex); - - // reset host stats - host->mls = ml_machine_learning_stats_t(); - - // reset charts/dims - void *rsp = NULL; - rrdset_foreach_read(rsp, host->rh) { - RRDSET *rs = static_cast(rsp); - - ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; - if (!chart) - continue; - - // reset chart - chart->mls = ml_machine_learning_stats_t(); - - void *rdp = NULL; - rrddim_foreach_read(rdp, rs) { - RRDDIM *rd = static_cast(rdp); - - ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; - if (!dim) - continue; - - spinlock_lock(&dim->slock); - - // reset dim - // TODO: should we drop in-mem models, or mark them as stale? Is it - // okay to resume training straight away? - - dim->mt = METRIC_TYPE_CONSTANT; - dim->ts = TRAINING_STATUS_UNTRAINED; - dim->last_training_time = 0; - dim->suppression_anomaly_counter = 0; - dim->suppression_window_counter = 0; - dim->cns.clear(); - - ml_kmeans_init(&dim->kmeans); - - spinlock_unlock(&dim->slock); - } - rrddim_foreach_done(rdp); - } - rrdset_foreach_done(rsp); - - netdata_mutex_unlock(&host->mutex); - - host->ml_running = false; -} - -void ml_host_get_info(RRDHOST *rh, BUFFER *wb) -{ - ml_host_t *host = (ml_host_t *) rh->ml_host; - if (!host) { - buffer_json_member_add_boolean(wb, "enabled", false); - return; - } - - buffer_json_member_add_uint64(wb, "version", 1); - - buffer_json_member_add_boolean(wb, "enabled", Cfg.enable_anomaly_detection); - - buffer_json_member_add_uint64(wb, "min-train-samples", Cfg.min_train_samples); - buffer_json_member_add_uint64(wb, "max-train-samples", Cfg.max_train_samples); - buffer_json_member_add_uint64(wb, "train-every", Cfg.train_every); - - buffer_json_member_add_uint64(wb, "diff-n", Cfg.diff_n); - buffer_json_member_add_uint64(wb, "smooth-n", Cfg.smooth_n); - buffer_json_member_add_uint64(wb, "lag-n", Cfg.lag_n); - - buffer_json_member_add_double(wb, "random-sampling-ratio", Cfg.random_sampling_ratio); - buffer_json_member_add_uint64(wb, "max-kmeans-iters", Cfg.random_sampling_ratio); - - buffer_json_member_add_double(wb, "dimension-anomaly-score-threshold", Cfg.dimension_anomaly_score_threshold); - - buffer_json_member_add_string(wb, "anomaly-detection-grouping-method", time_grouping_id2txt(Cfg.anomaly_detection_grouping_method)); - - buffer_json_member_add_int64(wb, "anomaly-detection-query-duration", Cfg.anomaly_detection_query_duration); - - buffer_json_member_add_string(wb, "hosts-to-skip", Cfg.hosts_to_skip.c_str()); - buffer_json_member_add_string(wb, "charts-to-skip", Cfg.charts_to_skip.c_str()); -} - -void ml_host_get_detection_info(RRDHOST *rh, BUFFER *wb) -{ - ml_host_t *host = (ml_host_t *) rh->ml_host; - if (!host) - return; - - netdata_mutex_lock(&host->mutex); - - buffer_json_member_add_uint64(wb, "version", 2); - buffer_json_member_add_uint64(wb, "ml-running", host->ml_running); - buffer_json_member_add_uint64(wb, "anomalous-dimensions", host->mls.num_anomalous_dimensions); - buffer_json_member_add_uint64(wb, "normal-dimensions", host->mls.num_normal_dimensions); - buffer_json_member_add_uint64(wb, "total-dimensions", host->mls.num_anomalous_dimensions + - host->mls.num_normal_dimensions); - buffer_json_member_add_uint64(wb, "trained-dimensions", host->mls.num_training_status_trained + - host->mls.num_training_status_pending_with_model); - netdata_mutex_unlock(&host->mutex); -} - -bool ml_host_get_host_status(RRDHOST *rh, struct ml_metrics_statistics *mlm) { - ml_host_t *host = (ml_host_t *) rh->ml_host; - if (!host) { - memset(mlm, 0, sizeof(*mlm)); - return false; - } - - netdata_mutex_lock(&host->mutex); - - mlm->anomalous = host->mls.num_anomalous_dimensions; - mlm->normal = host->mls.num_normal_dimensions; - mlm->trained = host->mls.num_training_status_trained + host->mls.num_training_status_pending_with_model; - mlm->pending = host->mls.num_training_status_untrained + host->mls.num_training_status_pending_without_model; - mlm->silenced = host->mls.num_training_status_silenced; - - netdata_mutex_unlock(&host->mutex); - - return true; -} - -bool ml_host_running(RRDHOST *rh) { - ml_host_t *host = (ml_host_t *) rh->ml_host; - if(!host) - return false; - - return true; -} - -void ml_host_get_models(RRDHOST *rh, BUFFER *wb) -{ - UNUSED(rh); - UNUSED(wb); - - // TODO: To be implemented - netdata_log_error("Fetching KMeans models is not supported yet"); -} - -void ml_chart_new(RRDSET *rs) -{ - ml_host_t *host = (ml_host_t *) rs->rrdhost->ml_host; - if (!host) - return; - - ml_chart_t *chart = new ml_chart_t(); - - chart->rs = rs; - chart->mls = ml_machine_learning_stats_t(); - - rs->ml_chart = (rrd_ml_chart_t *) chart; -} - -void ml_chart_delete(RRDSET *rs) -{ - ml_host_t *host = (ml_host_t *) rs->rrdhost->ml_host; - if (!host) - return; - - ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; - - delete chart; - rs->ml_chart = NULL; -} - -bool ml_chart_update_begin(RRDSET *rs) -{ - ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; - if (!chart) - return false; - - chart->mls = {}; - return true; -} - -void ml_chart_update_end(RRDSET *rs) -{ - ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; - if (!chart) - return; -} - -void ml_dimension_new(RRDDIM *rd) -{ - ml_chart_t *chart = (ml_chart_t *) rd->rrdset->ml_chart; - if (!chart) - return; - - ml_dimension_t *dim = new ml_dimension_t(); - - dim->rd = rd; - - dim->mt = METRIC_TYPE_CONSTANT; - dim->ts = TRAINING_STATUS_UNTRAINED; - dim->last_training_time = 0; - dim->suppression_anomaly_counter = 0; - dim->suppression_window_counter = 0; - - ml_kmeans_init(&dim->kmeans); - - if (simple_pattern_matches(Cfg.sp_charts_to_skip, rrdset_name(rd->rrdset))) - dim->mls = MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART; - else - dim->mls = MACHINE_LEARNING_STATUS_ENABLED; - - spinlock_init(&dim->slock); - - dim->km_contexts.reserve(Cfg.num_models_to_use); - - rd->ml_dimension = (rrd_ml_dimension_t *) dim; - - metaqueue_ml_load_models(rd); -} - -void ml_dimension_delete(RRDDIM *rd) -{ - ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; - if (!dim) - return; - - delete dim; - rd->ml_dimension = NULL; -} - -bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool exists) -{ - ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; - if (!dim) - return false; - - ml_host_t *host = (ml_host_t *) rd->rrdset->rrdhost->ml_host; - if (!host->ml_running) - return false; - - ml_chart_t *chart = (ml_chart_t *) rd->rrdset->ml_chart; - - bool is_anomalous = ml_dimension_predict(dim, curr_time, value, exists); - ml_chart_update_dimension(chart, dim, is_anomalous); - - return is_anomalous; -} - -static void ml_flush_pending_models(ml_training_thread_t *training_thread) { +static void ml_flush_pending_models(ml_worker_t *worker) { int op_no = 1; // begin transaction - int rc = db_execute(db, "BEGIN TRANSACTION;"); + int rc = db_execute(ml_db, "BEGIN TRANSACTION;"); // add/delete models if (!rc) { op_no++; - for (const auto &pending_model: training_thread->pending_model_info) { + for (const auto &pending_model: worker->pending_model_info) { if (!rc) - rc = ml_dimension_add_model(&pending_model.metric_uuid, &pending_model.kmeans); + rc = ml_dimension_add_model(&pending_model.metric_uuid, &pending_model.inlined_kmeans); if (!rc) - rc = ml_dimension_delete_models(&pending_model.metric_uuid, pending_model.kmeans.before - (Cfg.num_models_to_use * Cfg.train_every)); + rc = ml_dimension_delete_models(&pending_model.metric_uuid, pending_model.inlined_kmeans.before - (Cfg.num_models_to_use * Cfg.train_every)); } } // prune old models if (!rc) { - if ((training_thread->num_db_transactions % 64) == 0) { - rc = ml_prune_old_models(training_thread->num_models_to_prune); + if ((worker->num_db_transactions % 64) == 0) { + rc = ml_prune_old_models(worker->num_models_to_prune); if (!rc) - training_thread->num_models_to_prune = 0; + worker->num_models_to_prune = 0; } } // commit transaction if (!rc) { op_no++; - rc = db_execute(db, "COMMIT TRANSACTION;"); + rc = db_execute(ml_db, "COMMIT TRANSACTION;"); } // rollback transaction on failure if (rc) { netdata_log_error("Trying to rollback ML transaction because it failed with rc=%d, op_no=%d", rc, op_no); op_no++; - rc = db_execute(db, "ROLLBACK;"); + rc = db_execute(ml_db, "ROLLBACK;"); if (rc) netdata_log_error("ML transaction rollback failed with rc=%d", rc); } if (!rc) { - training_thread->num_db_transactions++; - training_thread->num_models_to_prune += training_thread->pending_model_info.size(); + worker->num_db_transactions++; + worker->num_models_to_prune += worker->pending_model_info.size(); + } + + vacuum_database(ml_db, "ML", 0, 0); + + worker->pending_model_info.clear(); +} + +static enum ml_worker_result ml_worker_create_new_model(ml_worker_t *worker, ml_request_create_new_model_t req) { + AcquiredDimension AcqDim(req.DLI); + + if (!AcqDim.acquired()) { + netdata_log_error("Failed to create new model: could not acquire dimension (machine-guid: %s, dimension: '%s.%s')", + req.DLI.machineGuid(), req.DLI.chartId(), req.DLI.dimensionId()); + return ML_WORKER_RESULT_NULL_ACQUIRED_DIMENSION; } - vacuum_database(db, "ML", 0, 0); + ml_dimension_t *Dim = reinterpret_cast(AcqDim.dimension()); + return ml_dimension_train_model(worker, Dim, req); +} + +static enum ml_worker_result ml_worker_add_existing_model(ml_worker_t *worker, ml_request_add_existing_model_t req) { + UNUSED(worker); + UNUSED(req); + + AcquiredDimension AcqDim(req.DLI); + + if (!AcqDim.acquired()) { + netdata_log_error("Failed to add existing model: could not acquire dimension (machine-guid: %s, dimension: '%s.%s')", + req.DLI.machineGuid(), req.DLI.chartId(), req.DLI.dimensionId()); + return ML_WORKER_RESULT_NULL_ACQUIRED_DIMENSION; + } + + ml_dimension_t *Dim = reinterpret_cast(AcqDim.dimension()); + if (!Dim) { + telemetry_ml_models_ignored(); + return ML_WORKER_RESULT_OK; + } - training_thread->pending_model_info.clear(); + Dim->kmeans = req.inlined_km; + ml_dimension_update_models(worker, Dim); + telemetry_ml_models_received(); + return ML_WORKER_RESULT_OK; } -static void *ml_train_main(void *arg) { - ml_training_thread_t *training_thread = (ml_training_thread_t *) arg; +void *ml_train_main(void *arg) { + ml_worker_t *worker = (ml_worker_t *) arg; char worker_name[1024]; - snprintfz(worker_name, 1024, "training_thread_%zu", training_thread->id); + snprintfz(worker_name, 1024, "ml_worker_%zu", worker->id); worker_register("MLTRAIN"); worker_register_job_name(WORKER_TRAIN_QUEUE_POP, "pop queue"); @@ -1664,14 +1192,12 @@ static void *ml_train_main(void *arg) { while (!Cfg.training_stop) { worker_is_busy(WORKER_TRAIN_QUEUE_POP); - ml_training_request_t training_req = ml_queue_pop(training_thread->training_queue); - - // we know this thread has been cancelled, when the queue starts - // returning "null" requests without blocking on queue's pop(). - if (training_req.chart_id == NULL) + ml_queue_item_t item = ml_queue_pop(worker->queue); + if (item.type == ML_QUEUE_ITEM_STOP_REQUEST) { break; + } - size_t queue_size = ml_queue_size(training_thread->training_queue) + 1; + size_t queue_size = ml_queue_size(worker->queue) + 1; usec_t allotted_ut = (Cfg.train_every * USEC_PER_SEC) / queue_size; if (allotted_ut > USEC_PER_SEC) @@ -1679,21 +1205,20 @@ static void *ml_train_main(void *arg) { usec_t start_ut = now_monotonic_usec(); - enum ml_training_result training_res; - { - worker_is_busy(WORKER_TRAIN_ACQUIRE_DIMENSION); - ml_acquired_dimension_t acq_dim = ml_acquired_dimension_get( - training_req.machine_guid, - training_req.chart_id, - training_req.dimension_id); - - training_res = ml_acquired_dimension_train(training_thread, acq_dim, training_req); - - string_freez(training_req.chart_id); - string_freez(training_req.dimension_id); + enum ml_worker_result worker_res; - worker_is_busy(WORKER_TRAIN_RELEASE_DIMENSION); - ml_acquired_dimension_release(acq_dim); + switch (item.type) { + case ML_QUEUE_ITEM_TYPE_CREATE_NEW_MODEL: { + worker_res = ml_worker_create_new_model(worker, item.create_new_model); + break; + } + case ML_QUEUE_ITEM_TYPE_ADD_EXISTING_MODEL: { + worker_res = ml_worker_add_existing_model(worker, item.add_existing_model); + break; + } + default: { + fatal("Unknown queue item type"); + } } usec_t consumed_ut = now_monotonic_usec() - start_ut; @@ -1705,40 +1230,40 @@ static void *ml_train_main(void *arg) { if (Cfg.enable_statistics_charts) { worker_is_busy(WORKER_TRAIN_UPDATE_HOST); - netdata_mutex_lock(&training_thread->nd_mutex); + netdata_mutex_lock(&worker->nd_mutex); - training_thread->training_stats.queue_size += queue_size; - training_thread->training_stats.num_popped_items += 1; + worker->queue_stats.queue_size += queue_size; + worker->queue_stats.num_popped_items += 1; - training_thread->training_stats.allotted_ut += allotted_ut; - training_thread->training_stats.consumed_ut += consumed_ut; - training_thread->training_stats.remaining_ut += remaining_ut; + worker->queue_stats.allotted_ut += allotted_ut; + worker->queue_stats.consumed_ut += consumed_ut; + worker->queue_stats.remaining_ut += remaining_ut; - switch (training_res) { - case TRAINING_RESULT_OK: - training_thread->training_stats.training_result_ok += 1; + switch (worker_res) { + case ML_WORKER_RESULT_OK: + worker->queue_stats.item_result_ok += 1; break; - case TRAINING_RESULT_INVALID_QUERY_TIME_RANGE: - training_thread->training_stats.training_result_invalid_query_time_range += 1; + case ML_WORKER_RESULT_INVALID_QUERY_TIME_RANGE: + worker->queue_stats.item_result_invalid_query_time_range += 1; break; - case TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES: - training_thread->training_stats.training_result_not_enough_collected_values += 1; + case ML_WORKER_RESULT_NOT_ENOUGH_COLLECTED_VALUES: + worker->queue_stats.item_result_not_enough_collected_values += 1; break; - case TRAINING_RESULT_NULL_ACQUIRED_DIMENSION: - training_thread->training_stats.training_result_null_acquired_dimension += 1; + case ML_WORKER_RESULT_NULL_ACQUIRED_DIMENSION: + worker->queue_stats.item_result_null_acquired_dimension += 1; break; - case TRAINING_RESULT_CHART_UNDER_REPLICATION: - training_thread->training_stats.training_result_chart_under_replication += 1; + case ML_WORKER_RESULT_CHART_UNDER_REPLICATION: + worker->queue_stats.item_result_chart_under_replication += 1; break; } - netdata_mutex_unlock(&training_thread->nd_mutex); + netdata_mutex_unlock(&worker->nd_mutex); } - if (training_thread->pending_model_info.size() >= Cfg.flush_models_batch_size) { + if (worker->pending_model_info.size() >= Cfg.flush_models_batch_size) { worker_is_busy(WORKER_TRAIN_FLUSH_MODELS); netdata_mutex_lock(&db_mutex); - ml_flush_pending_models(training_thread); + ml_flush_pending_models(worker); netdata_mutex_unlock(&db_mutex); continue; } @@ -1749,140 +1274,3 @@ static void *ml_train_main(void *arg) { return NULL; } - -void ml_init() -{ - // Read config values - ml_config_load(&Cfg); - - if (!Cfg.enable_anomaly_detection) - return; - - // Generate random numbers to efficiently sample the features we need - // for KMeans clustering. - std::random_device RD; - std::mt19937 Gen(RD()); - - Cfg.random_nums.reserve(Cfg.max_train_samples); - for (size_t Idx = 0; Idx != Cfg.max_train_samples; Idx++) - Cfg.random_nums.push_back(Gen()); - - // init training thread-specific data - Cfg.training_threads.resize(Cfg.num_training_threads); - for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { - ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; - - size_t max_elements_needed_for_training = (size_t) Cfg.max_train_samples * (size_t) (Cfg.lag_n + 1); - training_thread->training_cns = new calculated_number_t[max_elements_needed_for_training](); - training_thread->scratch_training_cns = new calculated_number_t[max_elements_needed_for_training](); - - training_thread->id = idx; - training_thread->training_queue = ml_queue_init(); - training_thread->pending_model_info.reserve(Cfg.flush_models_batch_size); - netdata_mutex_init(&training_thread->nd_mutex); - } - - // open sqlite db - char path[FILENAME_MAX]; - snprintfz(path, FILENAME_MAX - 1, "%s/%s", netdata_configured_cache_dir, "ml.db"); - int rc = sqlite3_open(path, &db); - if (rc != SQLITE_OK) { - error_report("Failed to initialize database at %s, due to \"%s\"", path, sqlite3_errstr(rc)); - sqlite3_close(db); - db = NULL; - } - - // create table - if (db) { - int target_version = perform_ml_database_migration(db, ML_METADATA_VERSION); - if (configure_sqlite_database(db, target_version, "ml_config")) { - error_report("Failed to setup ML database"); - sqlite3_close(db); - db = NULL; - } - else { - char *err = NULL; - int rc = sqlite3_exec(db, db_models_create_table, NULL, NULL, &err); - if (rc != SQLITE_OK) { - error_report("Failed to create models table (%s, %s)", sqlite3_errstr(rc), err ? err : ""); - sqlite3_close(db); - sqlite3_free(err); - db = NULL; - } - } - } -} - -uint64_t sqlite_get_ml_space(void) -{ - return sqlite_get_db_space(db); -} - -void ml_fini() { - if (!Cfg.enable_anomaly_detection || !db) - return; - - sql_close_database(db, "ML"); - db = NULL; -} - -void ml_start_threads() { - if (!Cfg.enable_anomaly_detection) - return; - - // start detection & training threads - Cfg.detection_stop = false; - Cfg.training_stop = false; - - char tag[NETDATA_THREAD_TAG_MAX + 1]; - - snprintfz(tag, NETDATA_THREAD_TAG_MAX, "%s", "PREDICT"); - Cfg.detection_thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE, - ml_detect_main, NULL); - - for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { - ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; - snprintfz(tag, NETDATA_THREAD_TAG_MAX, "TRAIN[%zu]", training_thread->id); - training_thread->nd_thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE, - ml_train_main, training_thread); - } -} - -void ml_stop_threads() -{ - if (!Cfg.enable_anomaly_detection) - return; - - Cfg.detection_stop = true; - Cfg.training_stop = true; - - if (!Cfg.detection_thread) - return; - - nd_thread_join(Cfg.detection_thread); - Cfg.detection_thread = 0; - - // signal the training queue of each thread - for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { - ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; - - ml_queue_signal(training_thread->training_queue); - } - - // join training threads - for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { - ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; - - nd_thread_join(training_thread->nd_thread); - } - - // clear training thread data - for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { - ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; - - delete[] training_thread->training_cns; - delete[] training_thread->scratch_training_cns; - ml_queue_destroy(training_thread->training_queue); - netdata_mutex_destroy(&training_thread->nd_mutex); - } -} diff --git a/src/ml/ml_calculated_number.h b/src/ml/ml_calculated_number.h new file mode 100644 index 00000000000000..411d7b3970ae1c --- /dev/null +++ b/src/ml/ml_calculated_number.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ML_CALCULATED_NUMBER_H +#define NETDATA_ML_CALCULATED_NUMBER_H + +#include "dlib/dlib/matrix.h" + +// CentOS 7 shenanigans +#include +using std::isfinite; + +typedef double calculated_number_t; +typedef dlib::matrix DSample; + +#endif /* NETDATA_ML_CALCULATED_NUMBER_H */ diff --git a/src/ml/ml_chart.h b/src/ml/ml_chart.h new file mode 100644 index 00000000000000..e3ef2e3b2afb5a --- /dev/null +++ b/src/ml/ml_chart.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ML_CHART_H +#define NETDATA_ML_CHART_H + +#include "ml_host.h" + +struct ml_dimension_t; + +struct ml_chart_t { + RRDSET *rs; + ml_machine_learning_stats_t mls; +}; + +void ml_chart_update_dimension(ml_chart_t *chart, ml_dimension_t *dim, bool is_anomalous); + +#endif /* NETDATA_ML_CHART_H */ diff --git a/src/ml/Config.cc b/src/ml/ml_config.cc similarity index 95% rename from src/ml/Config.cc rename to src/ml/ml_config.cc index ccca7723b7d642..4890e1fa813544 100644 --- a/src/ml/Config.cc +++ b/src/ml/ml_config.cc @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "ml-private.h" +#include "ml_config.h" /* * Global configuration instance to be shared between training and @@ -45,7 +45,7 @@ void ml_config_load(ml_config_t *cfg) { std::string anomaly_detection_grouping_method = config_get(config_section_ml, "anomaly detection grouping method", "average"); time_t anomaly_detection_query_duration = config_get_duration_seconds(config_section_ml, "anomaly detection grouping duration", 5 * 60); - size_t num_training_threads = config_get_number(config_section_ml, "num training threads", 4); + size_t num_worker_threads = config_get_number(config_section_ml, "num training threads", os_get_system_cpus() / 4); size_t flush_models_batch_size = config_get_number(config_section_ml, "flush models batch size", 128); size_t suppression_window = @@ -54,7 +54,7 @@ void ml_config_load(ml_config_t *cfg) { size_t suppression_threshold = config_get_number(config_section_ml, "dimension anomaly rate suppression threshold", suppression_window / 2); - bool enable_statistics_charts = config_get_boolean(config_section_ml, "enable statistics charts", false); + bool enable_statistics_charts = config_get_boolean(config_section_ml, "enable statistics charts", true); /* * Clamp @@ -79,7 +79,7 @@ void ml_config_load(ml_config_t *cfg) { host_anomaly_rate_threshold = clamp(host_anomaly_rate_threshold, 0.1, 10.0); anomaly_detection_query_duration = clamp(anomaly_detection_query_duration, 60, 15 * 60); - num_training_threads = clamp(num_training_threads, 1, 128); + num_worker_threads = clamp(num_worker_threads, 4, os_get_system_cpus()); flush_models_batch_size = clamp(flush_models_batch_size, 8, 512); suppression_window = clamp(suppression_window, 1, max_train_samples); @@ -132,7 +132,7 @@ void ml_config_load(ml_config_t *cfg) { cfg->stream_anomaly_detection_charts = config_get_boolean(config_section_ml, "stream anomaly detection charts", true); - cfg->num_training_threads = num_training_threads; + cfg->num_worker_threads = num_worker_threads; cfg->flush_models_batch_size = flush_models_batch_size; cfg->suppression_window = suppression_window; diff --git a/src/ml/ml_config.h b/src/ml/ml_config.h new file mode 100644 index 00000000000000..34799ba8c7fa67 --- /dev/null +++ b/src/ml/ml_config.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ML_CONFIG_H +#define ML_CONFIG_H + +#include "ml_worker.h" + +typedef struct { + int enable_anomaly_detection; + + unsigned max_train_samples; + unsigned min_train_samples; + unsigned train_every; + + unsigned num_models_to_use; + unsigned delete_models_older_than; + + unsigned db_engine_anomaly_rate_every; + + unsigned diff_n; + unsigned smooth_n; + unsigned lag_n; + + double random_sampling_ratio; + unsigned max_kmeans_iters; + + double dimension_anomaly_score_threshold; + + double host_anomaly_rate_threshold; + RRDR_TIME_GROUPING anomaly_detection_grouping_method; + time_t anomaly_detection_query_duration; + + bool stream_anomaly_detection_charts; + + std::string hosts_to_skip; + SIMPLE_PATTERN *sp_host_to_skip; + + std::string charts_to_skip; + SIMPLE_PATTERN *sp_charts_to_skip; + + std::vector random_nums; + + ND_THREAD *detection_thread; + std::atomic detection_stop; + + size_t num_worker_threads; + size_t flush_models_batch_size; + + std::vector workers; + std::atomic training_stop; + + size_t suppression_window; + size_t suppression_threshold; + + bool enable_statistics_charts; +} ml_config_t; + +void ml_config_load(ml_config_t *cfg); + +extern ml_config_t Cfg; + +#endif /* ML_CONFIG_H */ diff --git a/src/ml/ml_dimension.h b/src/ml/ml_dimension.h new file mode 100644 index 00000000000000..348e963842cad7 --- /dev/null +++ b/src/ml/ml_dimension.h @@ -0,0 +1,168 @@ +#ifndef ML_LOOKUP_H +#define ML_LOOKUP_H + +#include "ml_string_wrapper.h" +#include "ml_enums.h" +#include "ml_kmeans.h" +#include "ml_chart.h" + +#include + +struct ml_dimension_t { + RRDDIM *rd; + + enum ml_metric_type mt; + enum ml_training_status ts; + enum ml_machine_learning_status mls; + + time_t last_training_time; + + std::vector cns; + + std::vector km_contexts; + SPINLOCK slock; + ml_kmeans_t kmeans; + std::vector feature; + + uint32_t suppression_window_counter; + uint32_t suppression_anomaly_counter; +}; + +bool +ml_dimension_predict(ml_dimension_t *dim, time_t curr_time, calculated_number_t value, bool exists); + +bool ml_dimension_deserialize_kmeans(const char *json_str); + +class DimensionLookupInfo { +public: + DimensionLookupInfo() + { + memset(MachineGuid.data(), 0, MachineGuid.size()); + } + + DimensionLookupInfo(const char *MachineGuid, STRING *ChartId, STRING *DimensionId) + : ChartId(ChartId), DimensionId(DimensionId) + { + memcpy(this->MachineGuid.data(), MachineGuid, this->MachineGuid.size()); + } + + DimensionLookupInfo(const char *MachineGuid, const char *ChartId, const char *DimensionId) + : ChartId(ChartId), DimensionId(DimensionId) + { + memcpy(this->MachineGuid.data(), MachineGuid, this->MachineGuid.size()); + } + + const char *machineGuid() const + { + return MachineGuid.data(); + } + + const char *chartId() const + { + return ChartId; + } + + const char *dimensionId() const + { + return DimensionId; + } + +private: + std::array MachineGuid; + StringWrapper ChartId; + StringWrapper DimensionId; +}; + +class AcquiredDimension { +public: + AcquiredDimension(const DimensionLookupInfo &DLI) : AcqRH(nullptr), AcqRS(nullptr), AcqRD(nullptr), Dim(nullptr) + { + rrd_rdlock(); + + AcqRH = rrdhost_find_and_acquire(DLI.machineGuid()); + if (AcqRH) { + RRDHOST *RH = rrdhost_acquired_to_rrdhost(AcqRH); + if (RH && !rrdhost_flag_check(RH, RRDHOST_FLAG_ORPHAN | RRDHOST_FLAG_ARCHIVED)) { + AcqRS = rrdset_find_and_acquire(RH, DLI.chartId()); + if (AcqRS) { + RRDSET *RS = rrdset_acquired_to_rrdset(AcqRS); + if (RS && !rrdset_flag_check(RS, RRDSET_FLAG_OBSOLETE)) { + AcqRD = rrddim_find_and_acquire(RS, DLI.dimensionId()); + if (AcqRD) { + RRDDIM *RD = rrddim_acquired_to_rrddim(AcqRD); + if (RD) { + Dim = reinterpret_cast(RD->ml_dimension); + acquire_failure_reason = "ok"; + } + else + acquire_failure_reason = "no dimension"; + } + else + acquire_failure_reason = "can't find dimension"; + } + else + acquire_failure_reason = "chart is obsolete"; + } + else + acquire_failure_reason = "can't find chart"; + } + else + acquire_failure_reason = "host is orphan or archived"; + } + else + acquire_failure_reason = "can't find host"; + + rrd_rdunlock(); + } + + AcquiredDimension(const AcquiredDimension &) = delete; + AcquiredDimension operator=(const AcquiredDimension &) = delete; + + AcquiredDimension(AcquiredDimension &&) = default; + AcquiredDimension &operator=(AcquiredDimension &&) = default; + + bool acquired() const { + return AcqRD != nullptr; + } + + const char *acquire_failure() const { + return acquire_failure_reason; + } + + ml_host_t *host() const { + assert(acquired()); + RRDHOST *RH = rrdhost_acquired_to_rrdhost(AcqRH); + return reinterpret_cast(RH->ml_host); + } + + ml_queue_t *queue() const { + assert(acquired()); + return host()->queue; + } + + ml_dimension_t *dimension() const { + assert(acquired()); + return Dim; + } + + ~AcquiredDimension() + { + if (AcqRD) + rrddim_acquired_release(AcqRD); + + if (AcqRS) + rrdset_acquired_release(AcqRS); + + if (AcqRD) + rrdhost_acquired_release(AcqRH); + } + +private: + const char *acquire_failure_reason; + RRDHOST_ACQUIRED *AcqRH; + RRDSET_ACQUIRED *AcqRS; + RRDDIM_ACQUIRED *AcqRD; + ml_dimension_t *Dim; +}; + +#endif /* ML_LOOKUP_H */ diff --git a/src/ml/ml_enums.cc b/src/ml/ml_enums.cc new file mode 100644 index 00000000000000..db8557227f4c31 --- /dev/null +++ b/src/ml/ml_enums.cc @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ml_enums.h" + +const char * +ml_machine_learning_status_to_string(enum ml_machine_learning_status mls) +{ + switch (mls) { + case MACHINE_LEARNING_STATUS_ENABLED: + return "enabled"; + case MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART: + return "disabled-sp"; + default: + return "unknown"; + } +} + +const char * +ml_metric_type_to_string(enum ml_metric_type mt) +{ + switch (mt) { + case METRIC_TYPE_CONSTANT: + return "constant"; + case METRIC_TYPE_VARIABLE: + return "variable"; + default: + return "unknown"; + } +} + +const char * +ml_training_status_to_string(enum ml_training_status ts) +{ + switch (ts) { + case TRAINING_STATUS_PENDING_WITH_MODEL: + return "pending-with-model"; + case TRAINING_STATUS_PENDING_WITHOUT_MODEL: + return "pending-without-model"; + case TRAINING_STATUS_TRAINED: + return "trained"; + case TRAINING_STATUS_UNTRAINED: + return "untrained"; + case TRAINING_STATUS_SILENCED: + return "silenced"; + default: + return "unknown"; + } +} + +const char * +ml_worker_result_to_string(enum ml_worker_result tr) +{ + switch (tr) { + case ML_WORKER_RESULT_OK: + return "ok"; + case ML_WORKER_RESULT_INVALID_QUERY_TIME_RANGE: + return "invalid-query"; + case ML_WORKER_RESULT_NOT_ENOUGH_COLLECTED_VALUES: + return "missing-values"; + case ML_WORKER_RESULT_NULL_ACQUIRED_DIMENSION: + return "null-acquired-dim"; + case ML_WORKER_RESULT_CHART_UNDER_REPLICATION: + return "chart-under-replication"; + default: + return "unknown"; + } +} + +const char * +ml_queue_item_type_to_string(enum ml_queue_item_type qit) +{ + switch (qit) { + case ML_QUEUE_ITEM_TYPE_CREATE_NEW_MODEL: + return "create-new-model"; + case ML_QUEUE_ITEM_TYPE_ADD_EXISTING_MODEL: + return "add-existing-model"; + case ML_QUEUE_ITEM_STOP_REQUEST: + return "stop-request"; + default: + return "unknown"; + } +} diff --git a/src/ml/ml_enums.h b/src/ml/ml_enums.h new file mode 100644 index 00000000000000..58ef1b349e84e8 --- /dev/null +++ b/src/ml/ml_enums.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ML_ENUMS_H +#define NETDATA_ML_ENUMS_H + +enum ml_metric_type { + // The dimension has constant values, no need to train + METRIC_TYPE_CONSTANT, + + // The dimension's values fluctuate, we need to generate a model + METRIC_TYPE_VARIABLE, +}; + +const char *ml_metric_type_to_string(enum ml_metric_type mt); + +enum ml_machine_learning_status { + // Enable training/prediction + MACHINE_LEARNING_STATUS_ENABLED, + + // Disable because configuration pattern matches the chart's id + MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART, +}; + +const char *ml_machine_learning_status_to_string(enum ml_machine_learning_status mls); + +enum ml_training_status { + // We don't have a model for this dimension + TRAINING_STATUS_UNTRAINED, + + // Request for training sent, but we don't have any models yet + TRAINING_STATUS_PENDING_WITHOUT_MODEL, + + // Request to update existing models sent + TRAINING_STATUS_PENDING_WITH_MODEL, + + // Have a valid, up-to-date model + TRAINING_STATUS_TRAINED, + + // Have a valid, up-to-date model that is silenced because its too noisy + TRAINING_STATUS_SILENCED, +}; + +const char *ml_training_status_to_string(enum ml_training_status ts); + +enum ml_worker_result { + // We managed to create a KMeans model + ML_WORKER_RESULT_OK, + + // Could not query DB with a correct time range + ML_WORKER_RESULT_INVALID_QUERY_TIME_RANGE, + + // Did not gather enough data from DB to run KMeans + ML_WORKER_RESULT_NOT_ENOUGH_COLLECTED_VALUES, + + // Acquired a null dimension + ML_WORKER_RESULT_NULL_ACQUIRED_DIMENSION, + + // Chart is under replication + ML_WORKER_RESULT_CHART_UNDER_REPLICATION, +}; + +const char *ml_worker_result_to_string(enum ml_worker_result tr); + +enum ml_queue_item_type { + ML_QUEUE_ITEM_TYPE_CREATE_NEW_MODEL, + ML_QUEUE_ITEM_TYPE_ADD_EXISTING_MODEL, + ML_QUEUE_ITEM_STOP_REQUEST, +}; + +const char *ml_queue_item_type_to_string(enum ml_queue_item_type qit); + +#endif /* NETDATA_ML_ENUMS_H */ diff --git a/src/ml/ml_features.cc b/src/ml/ml_features.cc new file mode 100644 index 00000000000000..29269ce82b73d9 --- /dev/null +++ b/src/ml/ml_features.cc @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ml_config.h" +#include "ml_features.h" + +static void ml_features_diff(ml_features_t *features) +{ + if (features->diff_n == 0) + return; + + for (size_t idx = 0; idx != (features->src_n - features->diff_n); idx++) { + size_t high = (features->src_n - 1) - idx; + size_t low = high - features->diff_n; + + features->dst[low] = features->src[high] - features->src[low]; + } + + size_t n = features->src_n - features->diff_n; + memcpy(features->src, features->dst, n * sizeof(calculated_number_t)); + + for (size_t idx = features->src_n - features->diff_n; idx != features->src_n; idx++) + features->src[idx] = 0.0; +} + +static void ml_features_smooth(ml_features_t *features) +{ + calculated_number_t sum = 0.0; + + size_t idx = 0; + for (; idx != features->smooth_n - 1; idx++) + sum += features->src[idx]; + + for (; idx != (features->src_n - features->diff_n); idx++) { + sum += features->src[idx]; + calculated_number_t prev_cn = features->src[idx - (features->smooth_n - 1)]; + features->src[idx - (features->smooth_n - 1)] = sum / features->smooth_n; + sum -= prev_cn; + } + + for (idx = 0; idx != features->smooth_n; idx++) + features->src[(features->src_n - 1) - idx] = 0.0; +} + +static void ml_features_lag(ml_features_t *features) +{ + size_t n = features->src_n - features->diff_n - features->smooth_n + 1 - features->lag_n; + features->preprocessed_features.resize(n); + + unsigned target_num_samples = Cfg.max_train_samples * Cfg.random_sampling_ratio; + double sampling_ratio = std::min(static_cast(target_num_samples) / n, 1.0); + + uint32_t max_mt = std::numeric_limits::max(); + uint32_t cutoff = static_cast(max_mt) * sampling_ratio; + + size_t sample_idx = 0; + + for (size_t idx = 0; idx != n; idx++) { + DSample &DS = features->preprocessed_features[sample_idx++]; + DS.set_size(features->lag_n); + + if (Cfg.random_nums[idx] > cutoff) { + sample_idx--; + continue; + } + + for (size_t feature_idx = 0; feature_idx != features->lag_n + 1; feature_idx++) + DS(feature_idx) = features->src[idx + feature_idx]; + } + + features->preprocessed_features.resize(sample_idx); +} + +void ml_features_preprocess(ml_features_t *features) +{ + ml_features_diff(features); + ml_features_smooth(features); + ml_features_lag(features); +} diff --git a/src/ml/ml_features.h b/src/ml/ml_features.h new file mode 100644 index 00000000000000..94bcd6337143ca --- /dev/null +++ b/src/ml/ml_features.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ML_FEATURES_H +#define ML_FEATURES_H + +#include "ml_calculated_number.h" + +#include + +typedef struct { + size_t diff_n; + size_t smooth_n; + size_t lag_n; + + calculated_number_t *dst; + size_t dst_n; + + calculated_number_t *src; + size_t src_n; + + std::vector &preprocessed_features; +} ml_features_t; + +void ml_features_preprocess(ml_features_t *features); + +#endif /* ML_FEATURES_H */ diff --git a/src/ml/ml_host.h b/src/ml/ml_host.h new file mode 100644 index 00000000000000..20cb896e344af7 --- /dev/null +++ b/src/ml/ml_host.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ML_HOST_H +#define NETDATA_ML_HOST_H + +#include "ml_calculated_number.h" + +#include "database/rrd.h" + +#include +#include + +struct ml_queue_t; + +typedef struct machine_learning_stats_t { + size_t num_machine_learning_status_enabled; + size_t num_machine_learning_status_disabled_sp; + + size_t num_metric_type_constant; + size_t num_metric_type_variable; + + size_t num_training_status_untrained; + size_t num_training_status_pending_without_model; + size_t num_training_status_trained; + size_t num_training_status_pending_with_model; + size_t num_training_status_silenced; + + size_t num_anomalous_dimensions; + size_t num_normal_dimensions; +} ml_machine_learning_stats_t; + +typedef struct { + RRDDIM *rd; + size_t normal_dimensions; + size_t anomalous_dimensions; +} ml_type_anomaly_rate_t; + +typedef struct { + RRDHOST *rh; + + std::atomic ml_running; + + ml_machine_learning_stats_t mls; + + calculated_number_t host_anomaly_rate; + + netdata_mutex_t mutex; + + ml_queue_t *queue; + + /* + * bookkeeping for anomaly detection charts + */ + + RRDSET *ml_running_rs; + RRDDIM *ml_running_rd; + + RRDSET *machine_learning_status_rs; + RRDDIM *machine_learning_status_enabled_rd; + RRDDIM *machine_learning_status_disabled_sp_rd; + + RRDSET *metric_type_rs; + RRDDIM *metric_type_constant_rd; + RRDDIM *metric_type_variable_rd; + + RRDSET *training_status_rs; + RRDDIM *training_status_untrained_rd; + RRDDIM *training_status_pending_without_model_rd; + RRDDIM *training_status_trained_rd; + RRDDIM *training_status_pending_with_model_rd; + RRDDIM *training_status_silenced_rd; + + RRDSET *dimensions_rs; + RRDDIM *dimensions_anomalous_rd; + RRDDIM *dimensions_normal_rd; + + RRDSET *anomaly_rate_rs; + RRDDIM *anomaly_rate_rd; + + RRDSET *detector_events_rs; + RRDDIM *detector_events_above_threshold_rd; + RRDDIM *detector_events_new_anomaly_event_rd; + + RRDSET *type_anomaly_rate_rs; + SPINLOCK type_anomaly_rate_spinlock; + std::unordered_map type_anomaly_rate; +} ml_host_t; + +#endif /* NETDATA_ML_HOST_H */ diff --git a/src/ml/ml_kmeans.cc b/src/ml/ml_kmeans.cc new file mode 100644 index 00000000000000..b3dffe01d8d70e --- /dev/null +++ b/src/ml/ml_kmeans.cc @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ml_kmeans.h" +#include "libnetdata/libnetdata.h" +#include "dlib/dlib/clustering.h" + +void +ml_kmeans_init(ml_kmeans_t *kmeans) +{ + kmeans->cluster_centers.reserve(2); + kmeans->cluster_centers.clear(); + kmeans->min_dist = std::numeric_limits::max(); + kmeans->max_dist = std::numeric_limits::min(); +} + +void +ml_kmeans_train(ml_kmeans_t *kmeans, const ml_features_t *features, unsigned max_iters, time_t after, time_t before) +{ + kmeans->after = (uint32_t) after; + kmeans->before = (uint32_t) before; + + kmeans->min_dist = std::numeric_limits::max(); + kmeans->max_dist = std::numeric_limits::min(); + + kmeans->cluster_centers.clear(); + + dlib::pick_initial_centers(2, kmeans->cluster_centers, features->preprocessed_features); + dlib::find_clusters_using_kmeans(features->preprocessed_features, kmeans->cluster_centers, max_iters); + + for (const auto &preprocessed_feature : features->preprocessed_features) { + calculated_number_t mean_dist = 0.0; + + for (const auto &cluster_center : kmeans->cluster_centers) { + mean_dist += dlib::length(cluster_center - preprocessed_feature); + } + + mean_dist /= kmeans->cluster_centers.size(); + + if (mean_dist < kmeans->min_dist) + kmeans->min_dist = mean_dist; + + if (mean_dist > kmeans->max_dist) + kmeans->max_dist = mean_dist; + } +} + +calculated_number_t +ml_kmeans_anomaly_score(const ml_kmeans_inlined_t *inlined_km, const DSample &DS) +{ + calculated_number_t mean_dist = 0.0; + for (const auto &CC: inlined_km->cluster_centers) + mean_dist += dlib::length(CC - DS); + + mean_dist /= inlined_km->cluster_centers.size(); + + if (inlined_km->max_dist == inlined_km->min_dist) + return 0.0; + + calculated_number_t anomaly_score = 100.0 * std::abs((mean_dist - inlined_km->min_dist) / (inlined_km->max_dist - inlined_km->min_dist)); + return (anomaly_score > 100.0) ? 100.0 : anomaly_score; +} + +static void ml_buffer_json_member_add_double(BUFFER *wb, const char *key, calculated_number_t cn) { + if (!isnan(cn) && !isinf(cn)) { + buffer_json_member_add_double(wb, key, cn); + return; + } + + const char *classification = nullptr; + if (isnan(cn)) { + classification = "nan"; + } else if (isinf(cn)) { + if (cn > 0) { + classification = "+inf"; + } else { + classification = "-inf"; + } + } + + buffer_json_member_add_string(wb, key, classification); +} + +static void ml_buffer_json_add_array_item_double(BUFFER *wb, calculated_number_t cn) { + if (!isnan(cn) && !isinf(cn)) { + buffer_json_add_array_item_double(wb, cn); + return; + } + + const char *classification = nullptr; + if (isnan(cn)) { + classification = "nan"; + } else if (isinf(cn)) { + if (cn > 0) { + classification = "+inf"; + } else if (cn < 0) { + classification = "-inf"; + } + } + + buffer_json_add_array_item_string(wb, classification); +} + +bool ml_json_parse_double(struct json_object *jo, calculated_number_t *cn) { + switch(json_object_get_type(jo)) { + case json_type_string: { + const char *s = json_object_get_string(jo); + if (strcmp(s, "nan") == 0) { + *cn = NAN; + return true; + } + else if (strcmp(s, "+inf") == 0) { + *cn = INFINITY; + return true; + } + else if (strcmp(s, "-inf") == 0) { + *cn = -INFINITY; + return true; + } + + return false; + } + case json_type_double: { + *cn = json_object_get_double(jo); + return true; + } + case json_type_int: { + *cn = json_object_get_int64(jo); + return true; + } + default: + return false; + } +} + +void +ml_kmeans_serialize(const ml_kmeans_inlined_t *inlined_km, BUFFER *wb) +{ + buffer_json_member_add_uint64(wb, "after", inlined_km->after); + buffer_json_member_add_uint64(wb, "before", inlined_km->before); + + ml_buffer_json_member_add_double(wb, "min_dist", inlined_km->min_dist); + ml_buffer_json_member_add_double(wb, "max_dist", inlined_km->max_dist); + + buffer_json_member_add_array(wb, "cluster_centers"); + for (const auto &cc: inlined_km->cluster_centers) { + buffer_json_add_array_item_array(wb); + + for (const auto &d: cc) { + ml_buffer_json_add_array_item_double(wb, d); + } + + buffer_json_array_close(wb); + } + buffer_json_array_close(wb); +} + +bool ml_kmeans_deserialize(ml_kmeans_inlined_t *inlined_km, struct json_object *root) +{ + struct json_object *value; + + if (!json_object_object_get_ex(root, "after", &value)) { + netdata_log_error("Failed to deserialize kmeans: missing key 'after'"); + return false; + } + if (!json_object_is_type(value, json_type_int)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse int for 'after'"); + return false; + } + inlined_km->after = json_object_get_int(value); + + if (!json_object_object_get_ex(root, "before", &value)) { + netdata_log_error("Failed to deserialize kmeans: missing key 'before'"); + return false; + } + if (!json_object_is_type(value, json_type_int)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse int for 'before'"); + return false; + } + inlined_km->before = json_object_get_int(value); + + if (!json_object_object_get_ex(root, "min_dist", &value)) { + netdata_log_error("Failed to deserialize kmeans: missing key 'min_dist'"); + return false; + } + if (!ml_json_parse_double(value, &inlined_km->min_dist)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse double for 'min_dist'"); + return false; + } + + if (!json_object_object_get_ex(root, "max_dist", &value)) { + netdata_log_error("Failed to deserialize kmeans: missing key 'max_dist'"); + return false; + } + if (!ml_json_parse_double(value, &inlined_km->max_dist)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse double for 'max_dist'"); + return false; + } + + struct json_object *cc_root; + if (!json_object_object_get_ex(root, "cluster_centers", &cc_root)) { + netdata_log_error("Failed to deserialize kmeans: missing key 'cluster_centers'"); + return false; + } + if (!json_object_is_type(cc_root, json_type_array)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse array for 'cluster_centers'"); + return false; + } + + size_t num_centers = json_object_array_length(cc_root); + if (num_centers != 2) { + netdata_log_error("Failed to deserialize kmeans: expected cluster centers array of size 2"); + return false; + } + + for (size_t i = 0; i < num_centers; i++) { + struct json_object *cc_obj = json_object_array_get_idx(cc_root, i); + if (!cc_obj || !json_object_is_type(cc_obj, json_type_array)) { + netdata_log_error("Failed to deserialize kmeans: expected cluster center array"); + return false; + } + + size_t size = json_object_array_length(cc_obj); + if (size != 6) { + netdata_log_error("Failed to deserialize kmeans: expected cluster center array of size 6"); + return false; + } + + inlined_km->cluster_centers[i].set_size(size); + for (size_t j = 0; j < size; j++) { + struct json_object *value = json_object_array_get_idx(cc_obj, j); + calculated_number_t cn; + + if (!ml_json_parse_double(value, &cn)) { + netdata_log_error("Failed to deserialize kmeans: failed to parse double %zu for cluster center %zu", j, i); + return false; + } + + inlined_km->cluster_centers[i](j) = cn; + } + } + + return true; +} diff --git a/src/ml/ml_kmeans.h b/src/ml/ml_kmeans.h new file mode 100644 index 00000000000000..60b0e480682b96 --- /dev/null +++ b/src/ml/ml_kmeans.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ML_KMEANS_H +#define ML_KMEANS_H + +#include "ml_features.h" + +typedef struct web_buffer BUFFER; + +struct ml_kmeans_inlined_t; + +struct ml_kmeans_t { + std::vector cluster_centers; + calculated_number_t min_dist; + calculated_number_t max_dist; + uint32_t after; + uint32_t before; + + ml_kmeans_t() : min_dist(0), max_dist(0), after(0), before(0) + { + } + + explicit ml_kmeans_t(const ml_kmeans_inlined_t &inlined); + ml_kmeans_t &operator=(const ml_kmeans_inlined_t &inlined); +}; + +struct ml_kmeans_inlined_t { + std::array cluster_centers; + calculated_number_t min_dist; + calculated_number_t max_dist; + uint32_t after; + uint32_t before; + + ml_kmeans_inlined_t() : min_dist(0), max_dist(0), after(0), before(0) + { + } + + explicit ml_kmeans_inlined_t(const ml_kmeans_t &km) + { + if (km.cluster_centers.size() != 2) { + throw std::runtime_error("ml_kmeans_t must have exactly 2 cluster centers"); + } + + cluster_centers[0] = km.cluster_centers[0]; + cluster_centers[1] = km.cluster_centers[1]; + min_dist = km.min_dist; + max_dist = km.max_dist; + after = km.after; + before = km.before; + } + + ml_kmeans_inlined_t &operator=(const ml_kmeans_t &km) + { + if (km.cluster_centers.size() != 2) { + throw std::runtime_error("ml_kmeans_t must have exactly 2 cluster centers"); + } + cluster_centers[0] = km.cluster_centers[0]; + cluster_centers[1] = km.cluster_centers[1]; + min_dist = km.min_dist; + max_dist = km.max_dist; + after = km.after; + before = km.before; + return *this; + } +}; + +inline ml_kmeans_t::ml_kmeans_t(const ml_kmeans_inlined_t &inlined_km) +{ + cluster_centers.reserve(2); + cluster_centers.push_back(inlined_km.cluster_centers[0]); + cluster_centers.push_back(inlined_km.cluster_centers[1]); + + min_dist = inlined_km.min_dist; + max_dist = inlined_km.max_dist; + + after = inlined_km.after; + before = inlined_km.before; +} + +inline ml_kmeans_t &ml_kmeans_t::operator=(const ml_kmeans_inlined_t &inlined_km) +{ + cluster_centers.clear(); + cluster_centers.reserve(2); + cluster_centers.push_back(inlined_km.cluster_centers[0]); + cluster_centers.push_back(inlined_km.cluster_centers[1]); + + min_dist = inlined_km.min_dist; + max_dist = inlined_km.max_dist; + + after = inlined_km.after; + before = inlined_km.before; + return *this; +} + +void ml_kmeans_init(ml_kmeans_t *kmeans); + +void ml_kmeans_train(ml_kmeans_t *kmeans, const ml_features_t *features, unsigned max_iters, time_t after, time_t before); + +calculated_number_t ml_kmeans_anomaly_score(const ml_kmeans_inlined_t *kmeans, const DSample &DS); + +void ml_kmeans_serialize(const ml_kmeans_inlined_t *inlined_km, BUFFER *wb); + +bool ml_kmeans_deserialize(ml_kmeans_inlined_t *inlined_km, struct json_object *root); + +#endif /* ML_KMEANS_H */ diff --git a/src/ml/ml_memory.cc b/src/ml/ml_memory.cc new file mode 100644 index 00000000000000..321f59c69ede51 --- /dev/null +++ b/src/ml/ml_memory.cc @@ -0,0 +1,54 @@ +#include +#include + +#include "daemon/telemetry/telemetry-ml.h" + +void *operator new(size_t size) +{ + void *ptr = malloc(size); + if (!ptr) + throw std::bad_alloc(); + + telemetry_ml_memory_allocated(size); + return ptr; +} + +void *operator new[](size_t size) +{ + void *ptr = malloc(size); + if (!ptr) + throw std::bad_alloc(); + + telemetry_ml_memory_allocated(size); + return ptr; +} + +void operator delete(void *ptr, size_t size) noexcept +{ + if (ptr) { + telemetry_ml_memory_freed(size); + free(ptr); + } +} + +void operator delete[](void *ptr, size_t size) noexcept +{ + if (ptr) { + telemetry_ml_memory_freed(size); + free(ptr); + } +} + +void operator delete(void *ptr) noexcept +{ + if (ptr) { + free(ptr); + } +} + +void operator delete[](void *ptr) noexcept +{ + if (ptr) { + free(ptr); + } +} diff --git a/src/ml/ml_private.h b/src/ml/ml_private.h new file mode 100644 index 00000000000000..b203d46c089e32 --- /dev/null +++ b/src/ml/ml_private.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ML_PRIVATE_H +#define NETDATA_ML_PRIVATE_H + +#include +#include + +#include "ml_config.h" + +void *ml_train_main(void *arg); +void *ml_detect_main(void *arg); + +extern sqlite3 *ml_db; +extern const char *db_models_create_table; + + +#endif /* NETDATA_ML_PRIVATE_H */ diff --git a/src/ml/ml_public.cc b/src/ml/ml_public.cc new file mode 100644 index 00000000000000..628b00f0e3cc72 --- /dev/null +++ b/src/ml/ml_public.cc @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ml_private.h" + +#include + +#define ML_METADATA_VERSION 2 + +bool ml_capable() +{ + return true; +} + +bool ml_enabled(RRDHOST *rh) +{ + if (!rh) + return false; + + if (!Cfg.enable_anomaly_detection) + return false; + + if (simple_pattern_matches(Cfg.sp_host_to_skip, rrdhost_hostname(rh))) + return false; + + return true; +} + +bool ml_streaming_enabled() +{ + return Cfg.stream_anomaly_detection_charts; +} + +void ml_host_new(RRDHOST *rh) +{ + if (!ml_enabled(rh)) + return; + + ml_host_t *host = new ml_host_t(); + + host->rh = rh; + host->mls = ml_machine_learning_stats_t(); + host->host_anomaly_rate = 0.0; + host->anomaly_rate_rs = NULL; + + static std::atomic times_called(0); + host->queue = Cfg.workers[times_called++ % Cfg.num_worker_threads].queue; + + netdata_mutex_init(&host->mutex); + spinlock_init(&host->type_anomaly_rate_spinlock); + + host->ml_running = true; + rh->ml_host = (rrd_ml_host_t *) host; +} + +void ml_host_delete(RRDHOST *rh) +{ + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) + return; + + netdata_mutex_destroy(&host->mutex); + + delete host; + rh->ml_host = NULL; +} + +void ml_host_start(RRDHOST *rh) { + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) + return; + + host->ml_running = true; +} + +void ml_host_stop(RRDHOST *rh) { + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host || !host->ml_running) + return; + + netdata_mutex_lock(&host->mutex); + + // reset host stats + host->mls = ml_machine_learning_stats_t(); + + // reset charts/dims + void *rsp = NULL; + rrdset_foreach_read(rsp, host->rh) { + RRDSET *rs = static_cast(rsp); + + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + if (!chart) + continue; + + // reset chart + chart->mls = ml_machine_learning_stats_t(); + + void *rdp = NULL; + rrddim_foreach_read(rdp, rs) { + RRDDIM *rd = static_cast(rdp); + + ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; + if (!dim) + continue; + + spinlock_lock(&dim->slock); + + // reset dim + // TODO: should we drop in-mem models, or mark them as stale? Is it + // okay to resume training straight away? + + dim->mt = METRIC_TYPE_CONSTANT; + dim->ts = TRAINING_STATUS_UNTRAINED; + dim->last_training_time = 0; + dim->suppression_anomaly_counter = 0; + dim->suppression_window_counter = 0; + dim->cns.clear(); + + ml_kmeans_init(&dim->kmeans); + + spinlock_unlock(&dim->slock); + } + rrddim_foreach_done(rdp); + } + rrdset_foreach_done(rsp); + + netdata_mutex_unlock(&host->mutex); + + host->ml_running = false; +} + +void ml_host_get_info(RRDHOST *rh, BUFFER *wb) +{ + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) { + buffer_json_member_add_boolean(wb, "enabled", false); + return; + } + + buffer_json_member_add_uint64(wb, "version", 1); + + buffer_json_member_add_boolean(wb, "enabled", Cfg.enable_anomaly_detection); + + buffer_json_member_add_uint64(wb, "min-train-samples", Cfg.min_train_samples); + buffer_json_member_add_uint64(wb, "max-train-samples", Cfg.max_train_samples); + buffer_json_member_add_uint64(wb, "train-every", Cfg.train_every); + + buffer_json_member_add_uint64(wb, "diff-n", Cfg.diff_n); + buffer_json_member_add_uint64(wb, "smooth-n", Cfg.smooth_n); + buffer_json_member_add_uint64(wb, "lag-n", Cfg.lag_n); + + buffer_json_member_add_double(wb, "random-sampling-ratio", Cfg.random_sampling_ratio); + buffer_json_member_add_uint64(wb, "max-kmeans-iters", Cfg.random_sampling_ratio); + + buffer_json_member_add_double(wb, "dimension-anomaly-score-threshold", Cfg.dimension_anomaly_score_threshold); + + buffer_json_member_add_string(wb, "anomaly-detection-grouping-method", time_grouping_id2txt(Cfg.anomaly_detection_grouping_method)); + + buffer_json_member_add_int64(wb, "anomaly-detection-query-duration", Cfg.anomaly_detection_query_duration); + + buffer_json_member_add_string(wb, "hosts-to-skip", Cfg.hosts_to_skip.c_str()); + buffer_json_member_add_string(wb, "charts-to-skip", Cfg.charts_to_skip.c_str()); +} + +void ml_host_get_detection_info(RRDHOST *rh, BUFFER *wb) +{ + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) + return; + + netdata_mutex_lock(&host->mutex); + + buffer_json_member_add_uint64(wb, "version", 2); + buffer_json_member_add_uint64(wb, "ml-running", host->ml_running); + buffer_json_member_add_uint64(wb, "anomalous-dimensions", host->mls.num_anomalous_dimensions); + buffer_json_member_add_uint64(wb, "normal-dimensions", host->mls.num_normal_dimensions); + buffer_json_member_add_uint64(wb, "total-dimensions", host->mls.num_anomalous_dimensions + + host->mls.num_normal_dimensions); + buffer_json_member_add_uint64(wb, "trained-dimensions", host->mls.num_training_status_trained + + host->mls.num_training_status_pending_with_model); + netdata_mutex_unlock(&host->mutex); +} + +bool ml_host_get_host_status(RRDHOST *rh, struct ml_metrics_statistics *mlm) { + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) { + memset(mlm, 0, sizeof(*mlm)); + return false; + } + + netdata_mutex_lock(&host->mutex); + + mlm->anomalous = host->mls.num_anomalous_dimensions; + mlm->normal = host->mls.num_normal_dimensions; + mlm->trained = host->mls.num_training_status_trained + host->mls.num_training_status_pending_with_model; + mlm->pending = host->mls.num_training_status_untrained + host->mls.num_training_status_pending_without_model; + mlm->silenced = host->mls.num_training_status_silenced; + + netdata_mutex_unlock(&host->mutex); + + return true; +} + +bool ml_host_running(RRDHOST *rh) { + ml_host_t *host = (ml_host_t *) rh->ml_host; + if(!host) + return false; + + return true; +} + +void ml_host_get_models(RRDHOST *rh, BUFFER *wb) +{ + UNUSED(rh); + UNUSED(wb); + + // TODO: To be implemented + netdata_log_error("Fetching KMeans models is not supported yet"); +} + +void ml_chart_new(RRDSET *rs) +{ + ml_host_t *host = (ml_host_t *) rs->rrdhost->ml_host; + if (!host) + return; + + ml_chart_t *chart = new ml_chart_t(); + + chart->rs = rs; + chart->mls = ml_machine_learning_stats_t(); + + rs->ml_chart = (rrd_ml_chart_t *) chart; +} + +void ml_chart_delete(RRDSET *rs) +{ + ml_host_t *host = (ml_host_t *) rs->rrdhost->ml_host; + if (!host) + return; + + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + + delete chart; + rs->ml_chart = NULL; +} + +bool ml_chart_update_begin(RRDSET *rs) +{ + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + if (!chart) + return false; + + chart->mls = {}; + return true; +} + +void ml_chart_update_end(RRDSET *rs) +{ + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + if (!chart) + return; +} + +void ml_dimension_new(RRDDIM *rd) +{ + ml_chart_t *chart = (ml_chart_t *) rd->rrdset->ml_chart; + if (!chart) + return; + + ml_dimension_t *dim = new ml_dimension_t(); + + dim->rd = rd; + + dim->mt = METRIC_TYPE_CONSTANT; + dim->ts = TRAINING_STATUS_UNTRAINED; + dim->last_training_time = 0; + dim->suppression_anomaly_counter = 0; + dim->suppression_window_counter = 0; + + ml_kmeans_init(&dim->kmeans); + + if (simple_pattern_matches(Cfg.sp_charts_to_skip, rrdset_name(rd->rrdset))) + dim->mls = MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART; + else + dim->mls = MACHINE_LEARNING_STATUS_ENABLED; + + spinlock_init(&dim->slock); + + dim->km_contexts.reserve(Cfg.num_models_to_use); + + rd->ml_dimension = (rrd_ml_dimension_t *) dim; + + metaqueue_ml_load_models(rd); +} + +void ml_dimension_delete(RRDDIM *rd) +{ + ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; + if (!dim) + return; + + delete dim; + rd->ml_dimension = NULL; +} + +void ml_dimension_received_anomaly(RRDDIM *rd, bool is_anomalous) { + ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; + if (!dim) + return; + + ml_host_t *host = (ml_host_t *) rd->rrdset->rrdhost->ml_host; + if (!host->ml_running) + return; + + ml_chart_t *chart = (ml_chart_t *) rd->rrdset->ml_chart; + + ml_chart_update_dimension(chart, dim, is_anomalous); +} + +bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool exists) +{ + ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; + if (!dim) + return false; + + ml_host_t *host = (ml_host_t *) rd->rrdset->rrdhost->ml_host; + if (!host->ml_running) + return false; + + ml_chart_t *chart = (ml_chart_t *) rd->rrdset->ml_chart; + + bool is_anomalous = ml_dimension_predict(dim, curr_time, value, exists); + ml_chart_update_dimension(chart, dim, is_anomalous); + + return is_anomalous; +} + +void ml_init() +{ + // Read config values + ml_config_load(&Cfg); + + if (!Cfg.enable_anomaly_detection) + return; + + // Generate random numbers to efficiently sample the features we need + // for KMeans clustering. + std::random_device RD; + std::mt19937 Gen(RD()); + + Cfg.random_nums.reserve(Cfg.max_train_samples); + for (size_t Idx = 0; Idx != Cfg.max_train_samples; Idx++) + Cfg.random_nums.push_back(Gen()); + + // init training thread-specific data + Cfg.workers.resize(Cfg.num_worker_threads); + for (size_t idx = 0; idx != Cfg.num_worker_threads; idx++) { + ml_worker_t *worker = &Cfg.workers[idx]; + + size_t max_elements_needed_for_training = (size_t) Cfg.max_train_samples * (size_t) (Cfg.lag_n + 1); + worker->training_cns = new calculated_number_t[max_elements_needed_for_training](); + worker->scratch_training_cns = new calculated_number_t[max_elements_needed_for_training](); + + worker->id = idx; + worker->queue = ml_queue_init(); + worker->pending_model_info.reserve(Cfg.flush_models_batch_size); + netdata_mutex_init(&worker->nd_mutex); + } + + // open sqlite db + char path[FILENAME_MAX]; + snprintfz(path, FILENAME_MAX - 1, "%s/%s", netdata_configured_cache_dir, "ml.db"); + int rc = sqlite3_open(path, &ml_db); + if (rc != SQLITE_OK) { + error_report("Failed to initialize database at %s, due to \"%s\"", path, sqlite3_errstr(rc)); + sqlite3_close(ml_db); + ml_db = NULL; + } + + // create table + if (ml_db) { + int target_version = perform_ml_database_migration(ml_db, ML_METADATA_VERSION); + if (configure_sqlite_database(ml_db, target_version, "ml_config")) { + error_report("Failed to setup ML database"); + sqlite3_close(ml_db); + ml_db = NULL; + } + else { + char *err = NULL; + int rc = sqlite3_exec(ml_db, db_models_create_table, NULL, NULL, &err); + if (rc != SQLITE_OK) { + error_report("Failed to create models table (%s, %s)", sqlite3_errstr(rc), err ? err : ""); + sqlite3_close(ml_db); + sqlite3_free(err); + ml_db = NULL; + } + } + } +} + +uint64_t sqlite_get_ml_space(void) +{ + return sqlite_get_db_space(ml_db); +} + +void ml_fini() { + if (!Cfg.enable_anomaly_detection || !ml_db) + return; + + sql_close_database(ml_db, "ML"); + ml_db = NULL; +} + +void ml_start_threads() { + if (!Cfg.enable_anomaly_detection) + return; + + // start detection & training threads + Cfg.detection_stop = false; + Cfg.training_stop = false; + + char tag[NETDATA_THREAD_TAG_MAX + 1]; + + snprintfz(tag, NETDATA_THREAD_TAG_MAX, "%s", "PREDICT"); + Cfg.detection_thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE, + ml_detect_main, NULL); + + for (size_t idx = 0; idx != Cfg.num_worker_threads; idx++) { + ml_worker_t *worker = &Cfg.workers[idx]; + snprintfz(tag, NETDATA_THREAD_TAG_MAX, "TRAIN[%zu]", worker->id); + worker->nd_thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE, + ml_train_main, worker); + } +} + +void ml_stop_threads() +{ + if (!Cfg.enable_anomaly_detection) + return; + + Cfg.detection_stop = true; + Cfg.training_stop = true; + + if (!Cfg.detection_thread) + return; + + nd_thread_join(Cfg.detection_thread); + Cfg.detection_thread = 0; + + // signal the worker queue of each thread + for (size_t idx = 0; idx != Cfg.num_worker_threads; idx++) { + ml_worker_t *worker = &Cfg.workers[idx]; + ml_queue_signal(worker->queue); + } + + // join worker threads + for (size_t idx = 0; idx != Cfg.num_worker_threads; idx++) { + ml_worker_t *worker = &Cfg.workers[idx]; + + nd_thread_join(worker->nd_thread); + } + + // clear worker thread data + for (size_t idx = 0; idx != Cfg.num_worker_threads; idx++) { + ml_worker_t *worker = &Cfg.workers[idx]; + + delete[] worker->training_cns; + delete[] worker->scratch_training_cns; + ml_queue_destroy(worker->queue); + netdata_mutex_destroy(&worker->nd_mutex); + } +} + +bool ml_model_received_from_child(RRDHOST *host, const char *json) +{ + UNUSED(host); + + bool ok = ml_dimension_deserialize_kmeans(json); + if (!ok) { + global_statistics_ml_models_deserialization_failures(); + } + + return ok; +} diff --git a/src/ml/ml.h b/src/ml/ml_public.h similarity index 64% rename from src/ml/ml.h rename to src/ml/ml_public.h index 5fe333bf7df264..514c47838d00f9 100644 --- a/src/ml/ml.h +++ b/src/ml/ml_public.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef NETDATA_ML_H -#define NETDATA_ML_H +#ifndef NETDATA_ML_PUBLIC_H +#define NETDATA_ML_PUBLIC_H #ifdef __cplusplus extern "C" { @@ -39,17 +39,27 @@ void ml_chart_update_end(RRDSET *rs); void ml_dimension_new(RRDDIM *rd); void ml_dimension_delete(RRDDIM *rd); bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool exists); +void ml_dimension_received_anomaly(RRDDIM *rd, bool is_anomalous); int ml_dimension_load_models(RRDDIM *rd, sqlite3_stmt **stmt); -void ml_update_global_statistics_charts(uint64_t models_consulted); +void ml_update_global_statistics_charts(uint64_t models_consulted, + uint64_t models_received, + uint64_t models_sent, + uint64_t models_ignored, + uint64_t models_deserialization_failures, + uint64_t memory_consumption, + uint64_t memory_new, + uint64_t memory_delete); bool ml_host_get_host_status(RRDHOST *rh, struct ml_metrics_statistics *mlm); bool ml_host_running(RRDHOST *rh); uint64_t sqlite_get_ml_space(void); +bool ml_model_received_from_child(RRDHOST *host, const char *json); + #ifdef __cplusplus }; #endif -#endif /* NETDATA_ML_H */ +#endif /* NETDATA_ML_PUBLIC_H */ diff --git a/src/ml/ml_queue.cc b/src/ml/ml_queue.cc new file mode 100644 index 00000000000000..2e22987eed01da --- /dev/null +++ b/src/ml/ml_queue.cc @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ml_private.h" + +ml_queue_t * ml_queue_init() +{ + ml_queue_t *q = new ml_queue_t(); + + netdata_mutex_init(&q->mutex); + pthread_cond_init(&q->cond_var, NULL); + q->exit = false; + return q; +} + +void ml_queue_destroy(ml_queue_t *q) +{ + netdata_mutex_destroy(&q->mutex); + pthread_cond_destroy(&q->cond_var); + delete q; +} + +void ml_queue_push(ml_queue_t *q, const ml_queue_item_t req) +{ + netdata_mutex_lock(&q->mutex); + q->internal.push(req); + pthread_cond_signal(&q->cond_var); + netdata_mutex_unlock(&q->mutex); +} + +ml_queue_item_t ml_queue_pop(ml_queue_t *q) +{ + netdata_mutex_lock(&q->mutex); + + ml_queue_item_t req; + req.type = ML_QUEUE_ITEM_STOP_REQUEST; + + while (q->internal.empty()) { + pthread_cond_wait(&q->cond_var, &q->mutex); + + if (q->exit) { + netdata_mutex_unlock(&q->mutex); + return req; + } + } + + req = q->internal.front(); + q->internal.pop(); + + netdata_mutex_unlock(&q->mutex); + return req; +} + +size_t ml_queue_size(ml_queue_t *q) +{ + netdata_mutex_lock(&q->mutex); + size_t size = q->internal.size(); + netdata_mutex_unlock(&q->mutex); + return size; +} + +void ml_queue_signal(ml_queue_t *q) +{ + netdata_mutex_lock(&q->mutex); + q->exit = true; + pthread_cond_signal(&q->cond_var); + netdata_mutex_unlock(&q->mutex); +} diff --git a/src/ml/ml_queue.h b/src/ml/ml_queue.h new file mode 100644 index 00000000000000..218771acbd56f5 --- /dev/null +++ b/src/ml/ml_queue.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ML_QUEUE_H +#define ML_QUEUE_H + +#include "ml_dimension.h" + +#include +#include + +typedef struct ml_request_create_new_model { + DimensionLookupInfo DLI; + + // Creation time of request + time_t request_time; + + // First/last entry of this dimension in DB + // at the point the request was made + time_t first_entry_on_request; + time_t last_entry_on_request; +} ml_request_create_new_model_t; + +typedef struct ml_request_add_existing_model { + DimensionLookupInfo DLI; + + ml_kmeans_inlined_t inlined_km; +} ml_request_add_existing_model_t; + +typedef struct ml_queue_item { + ml_queue_item_type type; + ml_request_create_new_model_t create_new_model; + ml_request_add_existing_model add_existing_model; +} ml_queue_item_t; + +struct ml_queue_t { + std::queue internal; + netdata_mutex_t mutex; + pthread_cond_t cond_var; + std::atomic exit; +}; + +typedef struct { + size_t queue_size; + size_t num_popped_items; + + usec_t allotted_ut; + usec_t consumed_ut; + usec_t remaining_ut; + + size_t item_result_ok; + size_t item_result_invalid_query_time_range; + size_t item_result_not_enough_collected_values; + size_t item_result_null_acquired_dimension; + size_t item_result_chart_under_replication; +} ml_queue_stats_t; + +ml_queue_t *ml_queue_init(); + +void ml_queue_destroy(ml_queue_t *q); + +void ml_queue_push(ml_queue_t *q, const ml_queue_item_t req); + +ml_queue_item_t ml_queue_pop(ml_queue_t *q); + +size_t ml_queue_size(ml_queue_t *q); + +void ml_queue_signal(ml_queue_t *q); + +#endif /* ML_QUEUE_H */ diff --git a/src/ml/ml_string_wrapper.h b/src/ml/ml_string_wrapper.h new file mode 100644 index 00000000000000..ca1b72cc8be7f9 --- /dev/null +++ b/src/ml/ml_string_wrapper.h @@ -0,0 +1,81 @@ +#ifndef ML_STRING_WRAPPER_H +#define ML_STRING_WRAPPER_H + +#include "libnetdata/libnetdata.h" + +#include + +class StringWrapper { +public: + StringWrapper() noexcept : Inner(nullptr) + { + } + + explicit StringWrapper(const char *S) noexcept : Inner(string_strdupz(S)) + { + } + + explicit StringWrapper(STRING *S) noexcept : Inner(string_dup(S)) + { + } + + StringWrapper(const StringWrapper &Other) noexcept : Inner(string_dup(Other.Inner)) + { + } + + StringWrapper &operator=(const StringWrapper &Other) noexcept + { + if (this != &Other) { + STRING *Tmp = string_dup(Other.Inner); + string_freez(Inner); + Inner = Tmp; + } + return *this; + } + + StringWrapper(StringWrapper &&Other) noexcept : Inner(Other.Inner) + { + Other.Inner = nullptr; + } + + StringWrapper &operator=(StringWrapper &&Other) noexcept + { + if (this != &Other) { + string_freez(Inner); + Inner = Other.Inner; + Other.Inner = nullptr; + } + return *this; + } + + ~StringWrapper() + { + string_freez(Inner); + } + + STRING *inner() const noexcept + { + return Inner; + } + + operator const char *() const noexcept + { + return string2str(Inner); + } + + void swap(StringWrapper &Other) noexcept + { + std::swap(Inner, Other.Inner); + } + +private: + STRING *Inner; +}; + +// Free swap function +inline void swap(StringWrapper &LHS, StringWrapper &RHS) noexcept +{ + LHS.swap(RHS); +} + +#endif /* ML_STRING_WRAPPER_H */ diff --git a/src/ml/ml_worker.h b/src/ml/ml_worker.h new file mode 100644 index 00000000000000..2663ffebbe685d --- /dev/null +++ b/src/ml/ml_worker.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ML_WORKER_H +#define ML_WORKER_H + +#include "ml_queue.h" + +typedef struct { + nd_uuid_t metric_uuid; + ml_kmeans_inlined_t inlined_kmeans; +} ml_model_info_t; + +typedef struct { + size_t id; + ND_THREAD *nd_thread; + netdata_mutex_t nd_mutex; + + ml_queue_t *queue; + ml_queue_stats_t queue_stats; + + calculated_number_t *training_cns; + calculated_number_t *scratch_training_cns; + std::vector training_samples; + + std::vector pending_model_info; + + RRDSET *queue_stats_rs; + RRDDIM *queue_stats_queue_size_rd; + RRDDIM *queue_stats_popped_items_rd; + + RRDSET *training_time_stats_rs; + RRDDIM *training_time_stats_allotted_rd; + RRDDIM *training_time_stats_consumed_rd; + RRDDIM *training_time_stats_remaining_rd; + + RRDSET *training_results_rs; + RRDDIM *training_results_ok_rd; + RRDDIM *training_results_invalid_query_time_range_rd; + RRDDIM *training_results_not_enough_collected_values_rd; + RRDDIM *training_results_null_acquired_dimension_rd; + RRDDIM *training_results_chart_under_replication_rd; + + size_t num_db_transactions; + size_t num_models_to_prune; +} ml_worker_t; + +#endif /* ML_WORKER_H */ diff --git a/src/plugins.d/README.md b/src/plugins.d/README.md index 90f8e5a450dbad..a0563020143f69 100644 --- a/src/plugins.d/README.md +++ b/src/plugins.d/README.md @@ -314,7 +314,7 @@ the template is: - `charttype` - one of `line`, `area` or `stacked`, + one of `line`, `area`, `stacked` or `heatmap`, if empty or missing, the `line` will be used - `priority` diff --git a/src/plugins.d/plugins_d.h b/src/plugins.d/plugins_d.h index 4da7448bf52bc4..e400aeee43a1a3 100644 --- a/src/plugins.d/plugins_d.h +++ b/src/plugins.d/plugins_d.h @@ -3,7 +3,9 @@ #ifndef NETDATA_PLUGINS_D_H #define NETDATA_PLUGINS_D_H 1 -#include "daemon/common.h" +#include "libnetdata/libnetdata.h" + +struct rrdhost; #define PLUGINSD_CMD_MAX (FILENAME_MAX*2) #define PLUGINSD_STOCK_PLUGINS_DIRECTORY_PATH 0 @@ -24,7 +26,7 @@ struct plugind { size_t serial_failures; // the number of times the plugin started // without collecting values - RRDHOST *host; // the host the plugin collects data for + struct rrdhost *host; // the host the plugin collects data for int update_every; // the plugin default data collection frequency struct { @@ -44,7 +46,7 @@ struct plugind { extern struct plugind *pluginsd_root; -size_t pluginsd_process(RRDHOST *host, struct plugind *cd, int fd_input, int fd_output, int trust_durations); +size_t pluginsd_process(struct rrdhost *host, struct plugind *cd, int fd_input, int fd_output, int trust_durations); struct parser; void pluginsd_process_cleanup(struct parser *parser); diff --git a/src/plugins.d/pluginsd_internals.c b/src/plugins.d/pluginsd_internals.c index c57362506155f2..dc0c650b3ee54a 100644 --- a/src/plugins.d/pluginsd_internals.c +++ b/src/plugins.d/pluginsd_internals.c @@ -11,38 +11,22 @@ ssize_t send_to_plugin(const char *txt, PARSER *parser) { return h2o_stream_write(parser->h2o_ctx, txt, strlen(txt)); #endif - errno_clear(); spinlock_lock(&parser->writer.spinlock); - ssize_t bytes = -1; - NETDATA_SSL *ssl = parser->ssl_output; - if(ssl) { + ND_SOCK tmp = { .fd = parser->fd_output, }; + ND_SOCK *s = parser->sock; // try the socket + if(!s) s = &tmp; // socket is not there, use the pipe - if(SSL_connection(ssl)) - bytes = netdata_ssl_write(ssl, (void *) txt, strlen(txt)); + if(s->fd != -1) { + // plugins pipe or socket (with or without SSL) - else - netdata_log_error("PLUGINSD: cannot send command (SSL)"); - - spinlock_unlock(&parser->writer.spinlock); - return bytes; - } - - if(parser->fd_output != -1) { - bytes = 0; - ssize_t total = (ssize_t)strlen(txt); - ssize_t sent; - - do { - sent = write(parser->fd_output, &txt[bytes], total - bytes); - if(sent <= 0) { - netdata_log_error("PLUGINSD: cannot send command (fd)"); - spinlock_unlock(&parser->writer.spinlock); - return -3; - } - bytes += sent; + size_t total = strlen(txt); + ssize_t bytes = nd_sock_write_persist(s, txt, total, 100); + if(bytes < (ssize_t)total) { + netdata_log_error("PLUGINSD: cannot send command (fd = %d, bytes = %zd out of %zu)", s->fd, bytes, total); + spinlock_unlock(&parser->writer.spinlock); + return -3; } - while(bytes < total); spinlock_unlock(&parser->writer.spinlock); return (int)bytes; @@ -83,15 +67,24 @@ void parser_destroy(PARSER *parser) { PARSER *parser_init(struct parser_user_object *user, int fd_input, int fd_output, - PARSER_INPUT_TYPE flags, void *ssl __maybe_unused) { + PARSER_INPUT_TYPE flags, ND_SOCK *sock) { PARSER *parser; parser = callocz(1, sizeof(*parser)); + if(user) parser->user = *user; - parser->fd_input = fd_input; - parser->fd_output = fd_output; - parser->ssl_output = ssl; + + if(sock) { + parser->fd_input = sock->fd; + parser->fd_output = sock->fd; + parser->sock = sock; + } + else { + parser->fd_input = fd_input; + parser->fd_output = fd_output; + } + parser->flags = flags; spinlock_init(&parser->writer.spinlock); diff --git a/src/plugins.d/pluginsd_internals.h b/src/plugins.d/pluginsd_internals.h index ed0714dd2e5982..26c1f18b90a9ff 100644 --- a/src/plugins.d/pluginsd_internals.h +++ b/src/plugins.d/pluginsd_internals.h @@ -282,34 +282,34 @@ static inline void pluginsd_rrdset_cache_put_to_slot(PARSER *parser, RRDSET *st, RRDHOST *host = st->rrdhost; - if(unlikely((size_t)slot > host->rrdpush.receive.pluginsd_chart_slots.size)) { - spinlock_lock(&host->rrdpush.receive.pluginsd_chart_slots.spinlock); - size_t old_slots = host->rrdpush.receive.pluginsd_chart_slots.size; + if(unlikely((size_t)slot > host->stream.rcv.pluginsd_chart_slots.size)) { + spinlock_lock(&host->stream.rcv.pluginsd_chart_slots.spinlock); + size_t old_slots = host->stream.rcv.pluginsd_chart_slots.size; size_t new_slots = (old_slots < PLUGINSD_MIN_RRDSET_POINTERS_CACHE) ? PLUGINSD_MIN_RRDSET_POINTERS_CACHE : old_slots * 2; if(new_slots < (size_t)slot) new_slots = slot; - host->rrdpush.receive.pluginsd_chart_slots.array = - reallocz(host->rrdpush.receive.pluginsd_chart_slots.array, new_slots * sizeof(RRDSET *)); + host->stream.rcv.pluginsd_chart_slots.array = + reallocz(host->stream.rcv.pluginsd_chart_slots.array, new_slots * sizeof(RRDSET *)); for(size_t i = old_slots; i < new_slots ;i++) - host->rrdpush.receive.pluginsd_chart_slots.array[i] = NULL; + host->stream.rcv.pluginsd_chart_slots.array[i] = NULL; - host->rrdpush.receive.pluginsd_chart_slots.size = new_slots; - spinlock_unlock(&host->rrdpush.receive.pluginsd_chart_slots.spinlock); + host->stream.rcv.pluginsd_chart_slots.size = new_slots; + spinlock_unlock(&host->stream.rcv.pluginsd_chart_slots.spinlock); } - host->rrdpush.receive.pluginsd_chart_slots.array[slot - 1] = st; + host->stream.rcv.pluginsd_chart_slots.array[slot - 1] = st; st->pluginsd.last_slot = (int32_t)slot - 1; parser->user.cleanup_slots = obsolete; } static inline RRDSET *pluginsd_rrdset_cache_get_from_slot(PARSER *parser, RRDHOST *host, const char *id, ssize_t slot, const char *keyword) { - if(unlikely(slot < 1 || (size_t)slot > host->rrdpush.receive.pluginsd_chart_slots.size)) + if(unlikely(slot < 1 || (size_t)slot > host->stream.rcv.pluginsd_chart_slots.size)) return pluginsd_find_chart(host, id, keyword); - RRDSET *st = host->rrdpush.receive.pluginsd_chart_slots.array[slot - 1]; + RRDSET *st = host->stream.rcv.pluginsd_chart_slots.array[slot - 1]; if(!st) { st = pluginsd_find_chart(host, id, keyword); diff --git a/src/plugins.d/pluginsd_parser.c b/src/plugins.d/pluginsd_parser.c index 62f56d3091f2b1..7cfb483dab8711 100644 --- a/src/plugins.d/pluginsd_parser.c +++ b/src/plugins.d/pluginsd_parser.c @@ -190,13 +190,13 @@ static inline PARSER_RC pluginsd_host_define_end(char **words __maybe_unused, si default_rrd_history_entries, default_rrd_memory_mode, health_plugin_enabled(), - stream_conf_send_enabled, - stream_conf_send_destination, - stream_conf_send_api_key, - stream_conf_send_charts_matching, - stream_conf_replication_enabled, - stream_conf_replication_period, - stream_conf_replication_step, + stream_send.enabled, + stream_send.parents.destination, + stream_send.api_key, + stream_send.send_charts_matching, + stream_receive.replication.enabled, + stream_receive.replication.period, + stream_receive.replication.step, rrdhost_labels_to_system_info(parser->user.host_define.rrdlabels), false); @@ -795,8 +795,8 @@ static inline PARSER_RC pluginsd_begin_v2(char **words, size_t num_words, PARSER // ------------------------------------------------------------------------ // propagate it forward in v2 - if(!parser->user.v2.stream_buffer.wb && rrdhost_has_rrdpush_sender_enabled(st->rrdhost)) - parser->user.v2.stream_buffer = rrdset_push_metric_initialize(parser->user.st, wall_clock_time); + if(!parser->user.v2.stream_buffer.wb && rrdhost_has_stream_sender_enabled(st->rrdhost)) + parser->user.v2.stream_buffer = stream_send_metrics_init(parser->user.st, wall_clock_time); if(parser->user.v2.stream_buffer.v2 && parser->user.v2.stream_buffer.wb) { // check receiver capabilities @@ -817,7 +817,7 @@ static inline PARSER_RC pluginsd_begin_v2(char **words, size_t num_words, PARSER if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, st->rrdpush.sender.chart_slot); + buffer_print_uint64_encoded(wb, integer_encoding, st->stream.snd.chart_slot); } buffer_fast_strcat(wb, " '", 2); @@ -922,20 +922,33 @@ static inline PARSER_RC pluginsd_set_v2(char **words, size_t num_words, PARSER * // ------------------------------------------------------------------------ // check value and ML - if (unlikely(!netdata_double_isnumber(value) || (flags == SN_EMPTY_SLOT))) { - value = NAN; - flags = SN_EMPTY_SLOT; + if(stream_has_capability(&parser->user, STREAM_CAP_ML_MODELS)) { + // we receive anomaly information, no need for prediction on this node + if (unlikely(!netdata_double_isnumber(value) || (flags == SN_EMPTY_SLOT))) { + value = NAN; + flags = SN_EMPTY_SLOT; + } if(parser->user.v2.ml_locked) - ml_dimension_is_anomalous(rd, parser->user.v2.end_time, 0, false); + ml_dimension_received_anomaly(rd, !(flags & SN_FLAG_NOT_ANOMALOUS)); } - else if(parser->user.v2.ml_locked) { - if (ml_dimension_is_anomalous(rd, parser->user.v2.end_time, value, true)) { - // clear anomaly bit: 0 -> is anomalous, 1 -> not anomalous - flags &= ~((storage_number) SN_FLAG_NOT_ANOMALOUS); + else { + // we don't receive anomaly information, we need to run prediction on this node + if (unlikely(!netdata_double_isnumber(value) || (flags == SN_EMPTY_SLOT))) { + value = NAN; + flags = SN_EMPTY_SLOT; + + if(parser->user.v2.ml_locked) + ml_dimension_is_anomalous(rd, parser->user.v2.end_time, 0, false); + } + else if(parser->user.v2.ml_locked) { + if (ml_dimension_is_anomalous(rd, parser->user.v2.end_time, value, true)) { + // clear anomaly bit: 0 -> is anomalous, 1 -> not anomalous + flags &= ~((storage_number) SN_FLAG_NOT_ANOMALOUS); + } + else + flags |= SN_FLAG_NOT_ANOMALOUS; } - else - flags |= SN_FLAG_NOT_ANOMALOUS; } timing_step(TIMING_STEP_SET2_ML); @@ -958,7 +971,7 @@ static inline PARSER_RC pluginsd_set_v2(char **words, size_t num_words, PARSER * if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, rd->rrdpush.sender.dim_slot); + buffer_print_uint64_encoded(wb, integer_encoding, rd->stream.snd.dim_slot); } buffer_fast_strcat(wb, " '", 2); @@ -1014,7 +1027,7 @@ static inline PARSER_RC pluginsd_end_v2(char **words __maybe_unused, size_t num_ // propagate the whole chart update in v1 if(unlikely(!parser->user.v2.stream_buffer.v2 && !parser->user.v2.stream_buffer.begin_v2_added && parser->user.v2.stream_buffer.wb)) - rrdset_push_metrics_v1(&parser->user.v2.stream_buffer, st); + stream_send_rrdset_metrics_v1(&parser->user.v2.stream_buffer, st); timing_step(TIMING_STEP_END2_PUSH_V1); @@ -1030,7 +1043,7 @@ static inline PARSER_RC pluginsd_end_v2(char **words __maybe_unused, size_t num_ // ------------------------------------------------------------------------ // propagate it forward - rrdset_push_metrics_finished(&parser->user.v2.stream_buffer, st); + stream_send_rrdset_metrics_finished(&parser->user.v2.stream_buffer, st); timing_step(TIMING_STEP_END2_PROPAGATE); @@ -1080,6 +1093,11 @@ static void pluginsd_json_stream_paths(PARSER *parser, void *action_data __maybe buffer_free(parser->defer.response); } +static void pluginsd_json_ml_model(PARSER *parser, void *action_data __maybe_unused) { + ml_model_received_from_child(parser->user.host, buffer_tostring(parser->defer.response)); + buffer_free(parser->defer.response); +} + static void pluginsd_json_dev_null(PARSER *parser, void *action_data __maybe_unused) { buffer_free(parser->defer.response); } @@ -1096,15 +1114,17 @@ static PARSER_RC pluginsd_json(char **words __maybe_unused, size_t num_words __m parser->defer.action_data = NULL; parser->flags |= PARSER_DEFER_UNTIL_KEYWORD; - if(strcmp(keyword, PLUGINSD_KEYWORD_STREAM_PATH) == 0) + if(strcmp(keyword, PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH) == 0) parser->defer.action = pluginsd_json_stream_paths; + else if(strcmp(keyword, PLUGINSD_KEYWORD_JSON_CMD_ML_MODEL) == 0) + parser->defer.action = pluginsd_json_ml_model; else netdata_log_error("PLUGINSD: invalid JSON payload keyword '%s'", keyword); return PARSER_RC_OK; } -PARSER_RC rrdpush_receiver_pluginsd_claimed_id(char **words, size_t num_words, PARSER *parser); +PARSER_RC stream_receiver_pluginsd_claimed_id(char **words, size_t num_words, PARSER *parser); // ---------------------------------------------------------------------------- @@ -1290,7 +1310,7 @@ PARSER_RC parser_execute(PARSER *parser, const PARSER_KEYWORD *keyword, char **w case PLUGINSD_KEYWORD_ID_VARIABLE: return pluginsd_variable(words, num_words, parser); case PLUGINSD_KEYWORD_ID_CLAIMED_ID: - return rrdpush_receiver_pluginsd_claimed_id(words, num_words, parser); + return stream_receiver_pluginsd_claimed_id(words, num_words, parser); case PLUGINSD_KEYWORD_ID_HOST: return pluginsd_host(words, num_words, parser); case PLUGINSD_KEYWORD_ID_HOST_DEFINE: diff --git a/src/plugins.d/pluginsd_parser.h b/src/plugins.d/pluginsd_parser.h index 983da7d13e8dc4..c3542fef9994ae 100644 --- a/src/plugins.d/pluginsd_parser.h +++ b/src/plugins.d/pluginsd_parser.h @@ -5,10 +5,10 @@ #include "daemon/common.h" -#define WORKER_PARSER_FIRST_JOB 3 +#define WORKER_PARSER_FIRST_JOB 34 -// this has to be in-sync with the same at receiver.c -#define WORKER_RECEIVER_JOB_REPLICATION_COMPLETION (WORKER_PARSER_FIRST_JOB - 3) +// this has to be in-sync with the same at stream-thread.c +#define WORKER_RECEIVER_JOB_REPLICATION_COMPLETION (WORKER_PARSER_FIRST_JOB - 9) // this controls the max response size of a function #define PLUGINSD_MAX_DEFERRED_SIZE (100 * 1024 * 1024) @@ -101,8 +101,7 @@ struct parser { uint32_t flags; int fd_input; int fd_output; - - NETDATA_SSL *ssl_output; + ND_SOCK *sock; #ifdef ENABLE_H2O void *h2o_ctx; // if set we use h2o_stream functions to send data @@ -133,7 +132,7 @@ struct parser { typedef struct parser PARSER; -PARSER *parser_init(struct parser_user_object *user, int fd_input, int fd_output, PARSER_INPUT_TYPE flags, void *ssl); +PARSER *parser_init(struct parser_user_object *user, int fd_input, int fd_output, PARSER_INPUT_TYPE flags, ND_SOCK *sock); void parser_init_repertoire(PARSER *parser, PARSER_REPERTOIRE repertoire); void parser_destroy(PARSER *working_parser); void pluginsd_cleanup_v2(PARSER *parser); diff --git a/src/plugins.d/pluginsd_replication.c b/src/plugins.d/pluginsd_replication.c index 8d09752109de43..2c3ad6a57dca48 100644 --- a/src/plugins.d/pluginsd_replication.c +++ b/src/plugins.d/pluginsd_replication.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "pluginsd_replication.h" +#include "streaming/stream-receiver-internals.h" PARSER_RC pluginsd_replay_begin(char **words, size_t num_words, PARSER *parser) { int idx = 1; @@ -311,9 +312,9 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, PARSER *parser) { time_t current = parser->user.replay.end_time; if(started && current > started) { - host->rrdpush_receiver_replication_percent = (NETDATA_DOUBLE) (current - started) * 100.0 / (NETDATA_DOUBLE) (now - started); + host->stream.rcv.status.replication.percent = (NETDATA_DOUBLE) (current - started) * 100.0 / (NETDATA_DOUBLE) (now - started); worker_set_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION, - host->rrdpush_receiver_replication_percent); + host->stream.rcv.status.replication.percent); } } @@ -354,8 +355,8 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, PARSER *parser) { pluginsd_clear_scope_chart(parser, PLUGINSD_KEYWORD_REPLAY_END); - host->rrdpush_receiver_replication_percent = 100.0; - worker_set_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION, host->rrdpush_receiver_replication_percent); + host->stream.rcv.status.replication.percent = 100.0; + worker_set_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION, host->stream.rcv.status.replication.percent); return PARSER_RC_OK; } diff --git a/src/streaming/PARENT-CLUSTERS.md b/src/streaming/PARENT-CLUSTERS.md new file mode 100644 index 00000000000000..09d3f70a12cd4a --- /dev/null +++ b/src/streaming/PARENT-CLUSTERS.md @@ -0,0 +1,143 @@ +# Notes on Netdata Active-Active Parent Clusters + +#### **Streaming Connection Overview** + +Each Netdata child node specifies its parent nodes through the +`[stream].destination` configuration in `stream.conf`. While a child can list +multiple parents, it will connect to only one at a time. If the connection to +the first parent fails, the child will try the next parent in the list, +continuing this process until a successful connection is established. If no +connection can be made, the child will retry the list in order. + +Once a Netdata parent receives data from its child nodes, it can also act as a +child to another parent (or "grandparent") to propagate the data further up the +hierarchy. + +#### **Active-Active Parent Clusters Overview** + +Active-active parent clusters involve circular data propagation among parent +nodes. For example, parent A streams its data to parent B (its grandparent), +while parent B streams back to parent A, creating redundancy. This configuration +ensures that each parent node has the same data, allowing child nodes to connect +to any available parent. + +This setup can be expanded to more than two parents by configuring all parents +as grandparents of each other. + +--- + +### **Data Replication** + +When a child node connects to a parent, it enters a negotiation phase to +announce the metrics it will stream, including their retention period. The +parent checks its database for missing data. If data gaps exist, the parent +requests replication of the missing metrics from the child before transitioning +to streaming fresh data. + +Replication occurs at the instance (metric group) level, meaning some metrics +may replicate historical data while others stream in real time. Only high- +resolution (`tier0`) data is replicated since higher-tier data can be derived +from `tier0`. Therefore, maintaining sufficient `tier0` retention on the child +is crucial to prevent gaps in the parent’s database. + +--- + +### **Challenges in Active-Active Clusters** + +#### **Adding a New Parent** + +Introducing a new parent to an active-active cluster involves two major +challenges: + +1. **Replicating Existing Data** + Since Netdata replication only propagates currently collected metrics, + archived data such as metrics from stopped containers or disconnected devices + will not be replicated. To ensure the new parent has complete historical + data: + - Copy the existing database from another parent (`/var/cache/netdata`) using + tools like `rsync` for dbengine files (safe for hot-copy) and `sqlite3` for + SQLite databases. + - Perform multiple copies and start the new parent promptly to minimize the + data gap. + +2. **Preventing Premature Connections** + Child nodes should not connect to the new parent until it has completed data + replication. Premature connections could lead to data gaps, as the child may + lack the necessary historical data. + + In Netdata v2.1+, a balancing feature allows children to query parent + retention and prioritize connections to parents with the most recent data. + However, children will still connect to the first available parent, + potentially introducing gaps to the new parent's database. + **Solution:** Keep the new parent isolated from children until its + replication process is complete. Only then should children be configured to + include the new parent. + +--- + +### **Resource Management in Clusters** + +Resource usage on parent nodes depends on three key factors: + +1. **Ingestion Rate** + All parent nodes ingest all data of all children (not just their own). The + resource load is the same across all parents. + +2. **Machine Learning** + Machine learning is CPU-intensive and affects memory usage. + - **Before Netdata 2.1:** Every node in a cluster independently trained + machine learning models for all children, increasing resource consumption + exponentially. + - **Netdata 2.1+:** The first node (child or parent) to train ML models + propagates the trained data to other nodes, significantly reducing resource + requirements. This allows flexibility: machine learning can either run at + the edge (child nodes) or on the first parent receiving the data. + +3. **Re-Streaming Rate** + Propagating data to other parents consumes CPU and bandwidth for formatting, + compressing, and transmitting data. Each parent except the last grandparent + in the chain contributes to this workload. + +--- + +### **Parent Balancing** + +Netdata v2.1 introduces a balancing algorithm for child nodes to optimize parent +connections. This feature is designed to ensure that child nodes connect to the +most suitable parent, balancing the load across the cluster and reducing the +likelihood of data gaps or resource bottlenecks. + +#### **Initial Balancing** + +Before establishing a connection, each child node queries its candidate parents +to retrieve their retention details. Based on this information, the child +evaluates the parents and prioritizes those with the most recent data. + +- **Retention Difference Threshold**: + Parents are considered equivalent if their retention times differ by less than + two minutes. In such cases, the child selects a parent randomly to avoid + overloading a single node. This randomness ensures an even distribution of + connections when all candidate parents are equally suitable. + +- **Disconnection Handling**: + To prevent overloading a parent during network disruptions, children + temporarily block a recently disconnected parent for a randomized duration + before attempting to reconnect. This cooldown period reduces the risk of + repeated disconnections and ensures smoother reconnections. + +#### **Re-balancing After Cluster Changes** + +Currently, the only way to re-balance the cluster (i.e. to break the existing +connections so that the children nodes will connect to both parents), is to +restart the children. + +--- + +### **Summary** + +Active-active parent clusters in Netdata provide robust data redundancy and +flexibility. Properly configuring replication, balancing resources, and managing +parent-child connections ensures optimal performance and data integrity. With +improvements in v2.1, including machine learning propagation and connection +balancing, Netdata clusters are more efficient and scalable, catering to complex +and dynamic environments. diff --git a/src/streaming/README.md b/src/streaming/README.md index ead317469af6d9..5789d3db16fb47 100644 --- a/src/streaming/README.md +++ b/src/streaming/README.md @@ -59,7 +59,7 @@ This section defines an API key for other Agents to connect to this Netdata. | [`allow from`](#allow-from) | `*` | A space-separated list of [Netdata simple patterns](/src/libnetdata/simple_pattern/README.md) matching the IPs of nodes that will stream metrics using this API key. [Read more →](#allow-from) | | `retention` | `1h` | The default amount of child metrics history to retain when using the `ram` db. | | [`db`](#default-memory-mode) | `dbengine` | The [database](/src/database/README.md) to use for all nodes using this `API_KEY`. Valid settings are `dbengine`, `ram`, or `none`. [Read more →](#default-memory-mode) | -| `health enabled by default` | `auto` | Whether alerts and notifications should be enabled for nodes using this `API_KEY`. `auto` enables alerts when the child is connected. `yes` enables alerts always, and `no` disables alerts. | +| `health enabled` | `auto` | Whether alerts and notifications should be enabled for nodes using this `API_KEY`. `auto` enables alerts when the child is connected. `yes` enables alerts always, and `no` disables alerts. | | `postpone alerts on connect` | `1m` | Postpone alerts and notifications for a period of time after the child connects. | | `health log retention` | `5d` | History of health log events (in seconds) kept in the database. | | `proxy enabled` | | Route metrics through a proxy. | @@ -461,7 +461,7 @@ On the parent, set the following in `stream.conf`: default memory = ram # alerts checks, only while the child is connected - health enabled by default = auto + health enabled = auto ``` On the child nodes, set the following in `stream.conf`: diff --git a/src/streaming/protocol/command-begin-set-end.c b/src/streaming/protocol/command-begin-set-end.c index 17daef776a3933..99c6d58afbea3c 100644 --- a/src/streaming/protocol/command-begin-set-end.c +++ b/src/streaming/protocol/command-begin-set-end.c @@ -1,14 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "commands.h" +#include "../stream-sender-internals.h" #include "plugins.d/pluginsd_internals.h" -static void rrdpush_send_chart_metrics(BUFFER *wb, RRDSET *st, struct sender_state *s __maybe_unused, RRDSET_FLAGS flags) { - buffer_fast_strcat(wb, "BEGIN \"", 7); +static void +stream_send_rrdset_metrics_v1_internal(BUFFER *wb, RRDSET *st, struct sender_state *s __maybe_unused, RRDSET_FLAGS flags) { + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_BEGIN " \"", 7); buffer_fast_strcat(wb, rrdset_id(st), string_strlen(st->id)); buffer_fast_strcat(wb, "\" ", 2); - if(st->last_collected_time.tv_sec > st->rrdpush.sender.resync_time_s) + if(st->last_collected_time.tv_sec > st->stream.snd.resync_time_s) buffer_print_uint64(wb, st->usec_since_last_update); else buffer_fast_strcat(wb, "0", 1); @@ -21,7 +23,7 @@ static void rrdpush_send_chart_metrics(BUFFER *wb, RRDSET *st, struct sender_sta continue; if(likely(rrddim_check_upstream_exposed_collector(rd))) { - buffer_fast_strcat(wb, "SET \"", 5); + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_SET " \"", 5); buffer_fast_strcat(wb, rrddim_id(rd), string_strlen(rd->id)); buffer_fast_strcat(wb, "\" = ", 4); buffer_print_int64(wb, rd->collector.collected_value); @@ -39,15 +41,15 @@ static void rrdpush_send_chart_metrics(BUFFER *wb, RRDSET *st, struct sender_sta if(unlikely(flags & RRDSET_FLAG_UPSTREAM_SEND_VARIABLES)) rrdvar_print_to_streaming_custom_chart_variables(st, wb); - buffer_fast_strcat(wb, "END\n", 4); + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_END "\n", 4); } -void rrdset_push_metrics_v1(RRDSET_STREAM_BUFFER *rsb, RRDSET *st) { +void stream_send_rrdset_metrics_v1(RRDSET_STREAM_BUFFER *rsb, RRDSET *st) { RRDHOST *host = st->rrdhost; - rrdpush_send_chart_metrics(rsb->wb, st, host->sender, rsb->rrdset_flags); + stream_send_rrdset_metrics_v1_internal(rsb->wb, st, host->sender, rsb->rrdset_flags); } -void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, SN_FLAGS flags) { +void stream_send_rrddim_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, SN_FLAGS flags) { if(!rsb->wb || !rsb->v2 || !netdata_double_isnumber(n) || !does_storage_number_exist(flags)) return; @@ -65,7 +67,7 @@ void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_ if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, rd->rrdset->rrdpush.sender.chart_slot); + buffer_print_uint64_encoded(wb, integer_encoding, rd->rrdset->stream.snd.chart_slot); } buffer_fast_strcat(wb, " '", 2); @@ -89,7 +91,7 @@ void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_ if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, rd->rrdpush.sender.dim_slot); + buffer_print_uint64_encoded(wb, integer_encoding, rd->stream.snd.dim_slot); } buffer_fast_strcat(wb, " '", 2); @@ -108,7 +110,7 @@ void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_ buffer_fast_strcat(wb, "\n", 1); } -void rrdset_push_metrics_finished(RRDSET_STREAM_BUFFER *rsb, RRDSET *st) { +void stream_send_rrdset_metrics_finished(RRDSET_STREAM_BUFFER *rsb, RRDSET *st) { if(!rsb->wb) return; diff --git a/src/streaming/protocol/command-chart-definition.c b/src/streaming/protocol/command-chart-definition.c index 864d13242b965c..d4c4c8772c64bc 100644 --- a/src/streaming/protocol/command-chart-definition.c +++ b/src/streaming/protocol/command-chart-definition.c @@ -1,25 +1,26 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "commands.h" +#include "../stream-sender-internals.h" #include "plugins.d/pluginsd_internals.h" // chart labels -static int send_clabels_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) { +static int stream_send_clabels_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) { BUFFER *wb = (BUFFER *)data; buffer_sprintf(wb, PLUGINSD_KEYWORD_CLABEL " \"%s\" \"%s\" %d\n", name, value, ls & ~(RRDLABEL_FLAG_INTERNAL)); return 1; } -static void rrdpush_send_clabels(BUFFER *wb, RRDSET *st) { +static void stream_send_clabels(BUFFER *wb, RRDSET *st) { if (st->rrdlabels) { - if(rrdlabels_walkthrough_read(st->rrdlabels, send_clabels_callback, wb) > 0) + if(rrdlabels_walkthrough_read(st->rrdlabels, stream_send_clabels_callback, wb) > 0) buffer_sprintf(wb, PLUGINSD_KEYWORD_CLABEL_COMMIT "\n"); } } // Send the current chart definition. // Assumes that collector thread has already called sender_start for mutex / buffer state. -bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { +bool stream_sender_send_rrdset_definition(BUFFER *wb, RRDSET *st) { uint32_t version = rrdset_metadata_version(st); RRDHOST *host = st->rrdhost; @@ -45,7 +46,7 @@ bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, st->rrdpush.sender.chart_slot); + buffer_print_uint64_encoded(wb, integer_encoding, st->stream.snd.chart_slot); } // send the chart @@ -53,24 +54,24 @@ bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { wb , " \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" %d %d \"%s %s %s\" \"%s\" \"%s\"\n" , rrdset_id(st) - , name + , name , rrdset_title(st) - , rrdset_units(st) - , rrdset_family(st) - , rrdset_context(st) - , rrdset_type_name(st->chart_type) - , st->priority + , rrdset_units(st) + , rrdset_family(st) + , rrdset_context(st) + , rrdset_type_name(st->chart_type) + , st->priority , st->update_every , rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE)?"obsolete":"" , rrdset_flag_check(st, RRDSET_FLAG_STORE_FIRST)?"store_first":"" , rrdset_flag_check(st, RRDSET_FLAG_HIDDEN)?"hidden":"" , rrdset_plugin_name(st) - , rrdset_module_name(st) + , rrdset_module_name(st) ); // send the chart labels if (stream_has_capability(host->sender, STREAM_CAP_CLABELS)) - rrdpush_send_clabels(wb, st); + stream_send_clabels(wb, st); // send the dimensions RRDDIM *rd; @@ -79,7 +80,7 @@ bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, rd->rrdpush.sender.dim_slot); + buffer_print_uint64_encoded(wb, integer_encoding, rd->stream.snd.dim_slot); } buffer_sprintf( @@ -99,7 +100,7 @@ bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { // send the chart functions if(stream_has_capability(host->sender, STREAM_CAP_FUNCTIONS)) - rrd_chart_functions_expose_rrdpush(st, wb); + stream_sender_send_rrdset_functions(st, wb); // send the chart local custom variables rrdvar_print_to_streaming_custom_chart_variables(st, wb); @@ -128,8 +129,6 @@ bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { #endif } - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); - // we can set the exposed flag, after we commit the buffer // because replication may pick it up prematurely rrddim_foreach_read(rd, st) { @@ -138,11 +137,11 @@ bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { rrddim_foreach_done(rd); rrdset_metadata_exposed_upstream(st, version); - st->rrdpush.sender.resync_time_s = st->last_collected_time.tv_sec + (stream_conf_initial_clock_resync_iterations * st->update_every); + st->stream.snd.resync_time_s = st->last_collected_time.tv_sec + (stream_send.initial_clock_resync_iterations * st->update_every); return replication_progress; } -bool should_send_chart_matching(RRDSET *st, RRDSET_FLAGS flags) { +bool should_send_rrdset_matching(RRDSET *st, RRDSET_FLAGS flags) { if(!(flags & RRDSET_FLAG_RECEIVER_REPLICATION_FINISHED)) return false; @@ -159,18 +158,18 @@ bool should_send_chart_matching(RRDSET *st, RRDSET_FLAGS flags) { int negative = 0, positive = 0; SIMPLE_PATTERN_RESULT r; - r = simple_pattern_matches_string_extract(host->rrdpush.send.charts_matching, st->context, NULL, 0); + r = simple_pattern_matches_string_extract(host->stream.snd.charts_matching, st->context, NULL, 0); if(r == SP_MATCHED_POSITIVE) positive++; else if(r == SP_MATCHED_NEGATIVE) negative++; if(!negative) { - r = simple_pattern_matches_string_extract(host->rrdpush.send.charts_matching, st->name, NULL, 0); + r = simple_pattern_matches_string_extract(host->stream.snd.charts_matching, st->name, NULL, 0); if (r == SP_MATCHED_POSITIVE) positive++; else if (r == SP_MATCHED_NEGATIVE) negative++; } if(!negative) { - r = simple_pattern_matches_string_extract(host->rrdpush.send.charts_matching, st->id, NULL, 0); + r = simple_pattern_matches_string_extract(host->stream.snd.charts_matching, st->id, NULL, 0); if (r == SP_MATCHED_POSITIVE) positive++; else if (r == SP_MATCHED_NEGATIVE) negative++; } @@ -189,18 +188,15 @@ bool should_send_chart_matching(RRDSET *st, RRDSET_FLAGS flags) { } // Called from the internal collectors to mark a chart obsolete. -bool rrdset_push_chart_definition_now(RRDSET *st) { +bool stream_sender_send_rrdset_definition_now(RRDSET *st) { RRDHOST *host = st->rrdhost; - if(unlikely(!rrdhost_can_send_definitions_to_parent(host) - || !should_send_chart_matching(st, rrdset_flag_get(st)))) { + if(unlikely(!rrdhost_can_stream_metadata_to_parent(host) || !should_send_rrdset_matching(st, rrdset_flag_get(st)))) return false; - } - BUFFER *wb = sender_start(host->sender); - rrdpush_send_chart_definition(wb, st); - sender_thread_buffer_free(); + CLEAN_BUFFER *wb = buffer_create(0, NULL); + stream_sender_send_rrdset_definition(wb, st); + sender_commit_clean_buffer(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); return true; } - diff --git a/src/streaming/protocol/command-claimed_id.c b/src/streaming/protocol/command-claimed_id.c index 5392e1d3b9799b..a615641addbada 100644 --- a/src/streaming/protocol/command-claimed_id.c +++ b/src/streaming/protocol/command-claimed_id.c @@ -3,7 +3,7 @@ #include "commands.h" #include "plugins.d/pluginsd_internals.h" -PARSER_RC rrdpush_receiver_pluginsd_claimed_id(char **words, size_t num_words, PARSER *parser) { +PARSER_RC stream_receiver_pluginsd_claimed_id(char **words, size_t num_words, PARSER *parser) { const char *machine_guid_str = get_word(words, num_words, 1); const char *claim_id_str = get_word(words, num_words, 2); @@ -47,20 +47,20 @@ PARSER_RC rrdpush_receiver_pluginsd_claimed_id(char **words, size_t num_words, P if(!uuid_is_null(claim_uuid)) { uuid_copy(host->aclk.claim_id_of_origin.uuid, claim_uuid); - rrdpush_sender_send_claimed_id(host); + stream_sender_send_claimed_id(host); } return PARSER_RC_OK; } -void rrdpush_sender_send_claimed_id(RRDHOST *host) { - if(!stream_has_capability(host->sender, STREAM_CAP_CLAIM)) +void stream_sender_send_claimed_id(RRDHOST *host) { + if(!stream_sender_has_capabilities(host, STREAM_CAP_CLAIM)) return; - if(unlikely(!rrdhost_can_send_definitions_to_parent(host))) + if(unlikely(!rrdhost_can_stream_metadata_to_parent(host))) return; - BUFFER *wb = sender_start(host->sender); + CLEAN_BUFFER *wb = buffer_create(0, NULL); char str[UUID_STR_LEN] = ""; ND_UUID uuid = host->aclk.claim_id_of_origin; @@ -72,7 +72,5 @@ void rrdpush_sender_send_claimed_id(RRDHOST *host) { buffer_sprintf(wb, PLUGINSD_KEYWORD_CLAIMED_ID " '%s' '%s'\n", host->machine_guid, str); - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); - - sender_thread_buffer_free(); + sender_commit_clean_buffer(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); } diff --git a/src/streaming/protocol/command-function.c b/src/streaming/protocol/command-function.c index d9b28eb4e9e026..313a453fc38bd6 100644 --- a/src/streaming/protocol/command-function.c +++ b/src/streaming/protocol/command-function.c @@ -1,20 +1,21 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "commands.h" +#include "../stream-sender-internals.h" #include "plugins.d/pluginsd_internals.h" -void rrdpush_send_global_functions(RRDHOST *host) { +void stream_send_global_functions(RRDHOST *host) { if(!stream_has_capability(host->sender, STREAM_CAP_FUNCTIONS)) return; - if(unlikely(!rrdhost_can_send_definitions_to_parent(host))) + if(unlikely(!rrdhost_can_stream_metadata_to_parent(host))) return; - BUFFER *wb = sender_start(host->sender); + CLEAN_BUFFER *wb = buffer_create(0, NULL); - rrd_global_functions_expose_rrdpush(host, wb, stream_has_capability(host->sender, STREAM_CAP_DYNCFG)); + stream_sender_send_global_rrdhost_functions(host, wb, stream_has_capability(host->sender, STREAM_CAP_DYNCFG)); - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_FUNCTIONS); - - sender_thread_buffer_free(); + // send it as STREAM_TRAFFIC_TYPE_METADATA, not STREAM_TRAFFIC_TYPE_FUNCTIONS + // this is just metadata not an interactive function call + sender_commit_clean_buffer(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); } diff --git a/src/streaming/protocol/command-host-labels.c b/src/streaming/protocol/command-host-labels.c index 7c2a2d0dd40c0b..4d9d6c3af768bf 100644 --- a/src/streaming/protocol/command-host-labels.c +++ b/src/streaming/protocol/command-host-labels.c @@ -1,25 +1,24 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "commands.h" +#include "../stream-sender-internals.h" #include "plugins.d/pluginsd_internals.h" -static int send_labels_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) { +static int send_host_labels_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) { BUFFER *wb = (BUFFER *)data; - buffer_sprintf(wb, "LABEL \"%s\" = %d \"%s\"\n", name, ls, value); + buffer_sprintf(wb, PLUGINSD_KEYWORD_LABEL " \"%s\" = %u \"%s\"\n", name, (unsigned)ls, value); return 1; } -void rrdpush_send_host_labels(RRDHOST *host) { - if(unlikely(!rrdhost_can_send_definitions_to_parent(host) +void stream_send_host_labels(RRDHOST *host) { + if(unlikely(!rrdhost_can_stream_metadata_to_parent(host) || !stream_has_capability(host->sender, STREAM_CAP_HLABELS))) return; - BUFFER *wb = sender_start(host->sender); + CLEAN_BUFFER *wb = buffer_create(0, NULL); - rrdlabels_walkthrough_read(host->rrdlabels, send_labels_callback, wb); - buffer_sprintf(wb, "OVERWRITE %s\n", "labels"); + rrdlabels_walkthrough_read(host->rrdlabels, send_host_labels_callback, wb); + buffer_sprintf(wb, PLUGINSD_KEYWORD_OVERWRITE " %s\n", "labels"); - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); - - sender_thread_buffer_free(); + sender_commit_clean_buffer(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); } diff --git a/src/streaming/protocol/command-host-variables.c b/src/streaming/protocol/command-host-variables.c index 83e4990d67d426..6951bd7ddb8782 100644 --- a/src/streaming/protocol/command-host-variables.c +++ b/src/streaming/protocol/command-host-variables.c @@ -3,23 +3,22 @@ #include "commands.h" #include "plugins.d/pluginsd_internals.h" -static inline void rrdpush_sender_add_host_variable_to_buffer(BUFFER *wb, const RRDVAR_ACQUIRED *rva) { +static inline void stream_sender_add_host_variable_to_buffer(BUFFER *wb, const RRDVAR_ACQUIRED *rva) { buffer_sprintf( wb - , "VARIABLE HOST %s = " NETDATA_DOUBLE_FORMAT "\n" + , PLUGINSD_KEYWORD_VARIABLE " HOST %s = " NETDATA_DOUBLE_FORMAT "\n" , rrdvar_name(rva) - , rrdvar2number(rva) + , rrdvar2number(rva) ); netdata_log_debug(D_STREAM, "RRDVAR pushed HOST VARIABLE %s = " NETDATA_DOUBLE_FORMAT, rrdvar_name(rva), rrdvar2number(rva)); } -void rrdpush_sender_send_this_host_variable_now(RRDHOST *host, const RRDVAR_ACQUIRED *rva) { - if(rrdhost_can_send_definitions_to_parent(host)) { - BUFFER *wb = sender_start(host->sender); - rrdpush_sender_add_host_variable_to_buffer(wb, rva); - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); - sender_thread_buffer_free(); +void stream_sender_send_this_host_variable_now(RRDHOST *host, const RRDVAR_ACQUIRED *rva) { + if(rrdhost_can_stream_metadata_to_parent(host)) { + CLEAN_BUFFER *wb = buffer_create(0, NULL); + stream_sender_add_host_variable_to_buffer(wb, rva); + sender_commit_clean_buffer(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); } } @@ -27,25 +26,24 @@ struct custom_host_variables_callback { BUFFER *wb; }; -static int rrdpush_sender_thread_custom_host_variables_callback(const DICTIONARY_ITEM *item __maybe_unused, void *rrdvar_ptr __maybe_unused, void *struct_ptr) { +static int stream_sender_thread_custom_host_variables_callback(const DICTIONARY_ITEM *item __maybe_unused, void *rrdvar_ptr __maybe_unused, void *struct_ptr) { const RRDVAR_ACQUIRED *rv = (const RRDVAR_ACQUIRED *)item; struct custom_host_variables_callback *tmp = struct_ptr; BUFFER *wb = tmp->wb; - rrdpush_sender_add_host_variable_to_buffer(wb, rv); + stream_sender_add_host_variable_to_buffer(wb, rv); return 1; } -void rrdpush_sender_thread_send_custom_host_variables(RRDHOST *host) { - if(rrdhost_can_send_definitions_to_parent(host)) { - BUFFER *wb = sender_start(host->sender); +void stream_sender_send_custom_host_variables(RRDHOST *host) { + if(rrdhost_can_stream_metadata_to_parent(host)) { + CLEAN_BUFFER *wb = buffer_create(0, NULL); struct custom_host_variables_callback tmp = { .wb = wb }; - int ret = rrdvar_walkthrough_read(host->rrdvars, rrdpush_sender_thread_custom_host_variables_callback, &tmp); + int ret = rrdvar_walkthrough_read(host->rrdvars, stream_sender_thread_custom_host_variables_callback, &tmp); (void)ret; - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); - sender_thread_buffer_free(); + sender_commit_clean_buffer(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); netdata_log_debug(D_STREAM, "RRDVAR sent %d VARIABLES", ret); } diff --git a/src/streaming/protocol/command-nodeid.c b/src/streaming/protocol/command-nodeid.c index 85ace83c8f48ed..c6c7110c69582e 100644 --- a/src/streaming/protocol/command-nodeid.c +++ b/src/streaming/protocol/command-nodeid.c @@ -1,19 +1,21 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "commands.h" +#include "../stream-receiver-internals.h" +#include "../stream-sender-internals.h" #include "plugins.d/pluginsd_internals.h" // the child disconnected from the parent, and it has to clear the parent's claim id -void rrdpush_sender_clear_parent_claim_id(RRDHOST *host) { +void stream_sender_clear_parent_claim_id(RRDHOST *host) { host->aclk.claim_id_of_parent = UUID_ZERO; } // the parent sends to the child its claim id, node id and cloud url -void rrdpush_receiver_send_node_and_claim_id_to_child(RRDHOST *host) { +void stream_receiver_send_node_and_claim_id_to_child(RRDHOST *host) { if(host == localhost || UUIDiszero(host->node_id)) return; - spinlock_lock(&host->receiver_lock); - if(host->receiver && stream_has_capability(host->receiver, STREAM_CAP_NODE_ID)) { + rrdhost_receiver_lock(host); + if(stream_has_capability(host->receiver, STREAM_CAP_NODE_ID)) { char node_id_str[UUID_STR_LEN] = ""; uuid_unparse_lower(host->node_id.uuid, node_id_str); @@ -32,16 +34,16 @@ void rrdpush_receiver_send_node_and_claim_id_to_child(RRDHOST *host) { PLUGINSD_KEYWORD_NODE_ID " '%s' '%s' '%s'\n", claim_id.str, node_id_str, cloud_config_url_get()); - send_to_plugin(buf, __atomic_load_n(&host->receiver->parser, __ATOMIC_RELAXED)); + send_to_plugin(buf, __atomic_load_n(&host->receiver->thread.parser, __ATOMIC_RELAXED)); } - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); } // the sender of the child receives node id, claim id and cloud url from the receiver of the parent -void rrdpush_sender_get_node_and_claim_id_from_parent(struct sender_state *s) { - char *claim_id_str = get_word(s->line.words, s->line.num_words, 1); - char *node_id_str = get_word(s->line.words, s->line.num_words, 2); - char *url = get_word(s->line.words, s->line.num_words, 3); +void stream_sender_get_node_and_claim_id_from_parent(struct sender_state *s) { + char *claim_id_str = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 1); + char *node_id_str = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 2); + char *url = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 3); bool claimed = is_agent_claimed(); bool update_node_id = false; @@ -118,10 +120,11 @@ void rrdpush_sender_get_node_and_claim_id_from_parent(struct sender_state *s) { } // we change the URL, to allow the agent dashboard to work with Netdata Cloud on-prem, if any. - cloud_config_url_set(url); + if(node_id_updated) + cloud_config_url_set(url); // send it down the line (to children) - rrdpush_receiver_send_node_and_claim_id_to_child(s->host); + stream_receiver_send_node_and_claim_id_to_child(s->host); if(node_id_updated) stream_path_node_id_updated(s->host); diff --git a/src/streaming/protocol/commands.c b/src/streaming/protocol/commands.c index e9e16bdac25177..6351ad282ea7aa 100644 --- a/src/streaming/protocol/commands.c +++ b/src/streaming/protocol/commands.c @@ -1,35 +1,43 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "commands.h" +#include "../stream-sender-internals.h" -RRDSET_STREAM_BUFFER rrdset_push_metric_initialize(RRDSET *st, time_t wall_clock_time) { +static BUFFER *preferred_sender_buffer(RRDHOST *host) { + if(host->stream.snd.commit.receiver_tid == gettid_cached()) + return sender_host_buffer(host); + else + return sender_thread_buffer(localhost->sender); +} + +RRDSET_STREAM_BUFFER stream_send_metrics_init(RRDSET *st, time_t wall_clock_time) { RRDHOST *host = st->rrdhost; // fetch the flags we need to check with one atomic operation RRDHOST_FLAGS host_flags = __atomic_load_n(&host->flags, __ATOMIC_SEQ_CST); // check if we are not connected - if(unlikely(!(host_flags & RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS))) { + if(unlikely(!(host_flags & RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS))) { - if(unlikely(!(host_flags & (RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN | RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED)))) - rrdpush_sender_thread_spawn(host); + if(unlikely(!(host_flags & (RRDHOST_FLAG_STREAM_SENDER_ADDED | RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED)))) + stream_sender_start_host(host); - if(unlikely(!(host_flags & RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS))) { - rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS); + if(unlikely(!(host_flags & RRDHOST_FLAG_STREAM_SENDER_LOGGED_STATUS))) { + rrdhost_flag_set(host, RRDHOST_FLAG_STREAM_SENDER_LOGGED_STATUS); nd_log_daemon(NDLP_NOTICE, "STREAM %s [send]: not ready - collected metrics are not sent to parent.", rrdhost_hostname(host)); } return (RRDSET_STREAM_BUFFER) { .wb = NULL, }; } - else if(unlikely(host_flags & RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS)) { + else if(unlikely(host_flags & RRDHOST_FLAG_STREAM_SENDER_LOGGED_STATUS)) { nd_log_daemon(NDLP_INFO, "STREAM %s [send]: sending metrics to parent...", rrdhost_hostname(host)); - rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS); + rrdhost_flag_clear(host, RRDHOST_FLAG_STREAM_SENDER_LOGGED_STATUS); } if(unlikely(host_flags & RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED)) { - BUFFER *wb = sender_start(host->sender); - rrd_global_functions_expose_rrdpush(host, wb, stream_has_capability(host->sender, STREAM_CAP_DYNCFG)); - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_FUNCTIONS); + BUFFER *wb = preferred_sender_buffer(host); + stream_sender_send_global_rrdhost_functions(host, wb, stream_has_capability(host->sender, STREAM_CAP_DYNCFG)); + sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); } bool exposed_upstream = rrdset_check_upstream_exposed(st); @@ -37,12 +45,13 @@ RRDSET_STREAM_BUFFER rrdset_push_metric_initialize(RRDSET *st, time_t wall_clock bool replication_in_progress = !(rrdset_flags & RRDSET_FLAG_SENDER_REPLICATION_FINISHED); if(unlikely((exposed_upstream && replication_in_progress) || - !should_send_chart_matching(st, rrdset_flags))) + !should_send_rrdset_matching(st, rrdset_flags))) return (RRDSET_STREAM_BUFFER) { .wb = NULL, }; if(unlikely(!exposed_upstream)) { - BUFFER *wb = sender_start(host->sender); - replication_in_progress = rrdpush_send_chart_definition(wb, st); + BUFFER *wb = preferred_sender_buffer(host); + replication_in_progress = stream_sender_send_rrdset_definition(wb, st); + sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA); } if(replication_in_progress) @@ -52,7 +61,7 @@ RRDSET_STREAM_BUFFER rrdset_push_metric_initialize(RRDSET *st, time_t wall_clock .capabilities = host->sender->capabilities, .v2 = stream_has_capability(host->sender, STREAM_CAP_INTERPOLATED), .rrdset_flags = rrdset_flags, - .wb = sender_start(host->sender), + .wb = preferred_sender_buffer(host), .wall_clock_time = wall_clock_time, }; } diff --git a/src/streaming/protocol/commands.h b/src/streaming/protocol/commands.h index 81344175c1e07b..a463603bc87573 100644 --- a/src/streaming/protocol/commands.h +++ b/src/streaming/protocol/commands.h @@ -4,7 +4,7 @@ #define NETDATA_STREAMING_PROTCOL_COMMANDS_H #include "database/rrd.h" -#include "../rrdpush.h" +#include "../stream.h" typedef struct rrdset_stream_buffer { STREAM_CAPABILITIES capabilities; @@ -16,26 +16,26 @@ typedef struct rrdset_stream_buffer { BUFFER *wb; } RRDSET_STREAM_BUFFER; -RRDSET_STREAM_BUFFER rrdset_push_metric_initialize(RRDSET *st, time_t wall_clock_time); +RRDSET_STREAM_BUFFER stream_send_metrics_init(RRDSET *st, time_t wall_clock_time); -void rrdpush_sender_get_node_and_claim_id_from_parent(struct sender_state *s); -void rrdpush_receiver_send_node_and_claim_id_to_child(RRDHOST *host); -void rrdpush_sender_clear_parent_claim_id(RRDHOST *host); +void stream_sender_get_node_and_claim_id_from_parent(struct sender_state *s); +void stream_receiver_send_node_and_claim_id_to_child(RRDHOST *host); +void stream_sender_clear_parent_claim_id(RRDHOST *host); -void rrdpush_sender_send_claimed_id(RRDHOST *host); +void stream_sender_send_claimed_id(RRDHOST *host); -void rrdpush_send_global_functions(RRDHOST *host); -void rrdpush_send_host_labels(RRDHOST *host); +void stream_send_global_functions(RRDHOST *host); +void stream_send_host_labels(RRDHOST *host); -void rrdpush_sender_thread_send_custom_host_variables(RRDHOST *host); -void rrdpush_sender_send_this_host_variable_now(RRDHOST *host, const RRDVAR_ACQUIRED *rva); +void stream_sender_send_custom_host_variables(RRDHOST *host); +void stream_sender_send_this_host_variable_now(RRDHOST *host, const RRDVAR_ACQUIRED *rva); -bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st); -bool rrdset_push_chart_definition_now(RRDSET *st); -bool should_send_chart_matching(RRDSET *st, RRDSET_FLAGS flags); +bool stream_sender_send_rrdset_definition(BUFFER *wb, RRDSET *st); +bool stream_sender_send_rrdset_definition_now(RRDSET *st); +bool should_send_rrdset_matching(RRDSET *st, RRDSET_FLAGS flags); -void rrdset_push_metrics_v1(RRDSET_STREAM_BUFFER *rsb, RRDSET *st); -void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, SN_FLAGS flags); -void rrdset_push_metrics_finished(RRDSET_STREAM_BUFFER *rsb, RRDSET *st); +void stream_send_rrdset_metrics_v1(RRDSET_STREAM_BUFFER *rsb, RRDSET *st); +void stream_send_rrddim_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, SN_FLAGS flags); +void stream_send_rrdset_metrics_finished(RRDSET_STREAM_BUFFER *rsb, RRDSET *st); #endif //NETDATA_STREAMING_PROTCOL_COMMANDS_H diff --git a/src/streaming/receiver.c b/src/streaming/receiver.c deleted file mode 100644 index 6c15004a308846..00000000000000 --- a/src/streaming/receiver.c +++ /dev/null @@ -1,1385 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "rrdpush.h" -#include "web/server/h2o/http_server.h" - -// When a child disconnects this is the maximum we will wait -// before we update the cloud that the child is offline -#define MAX_CHILD_DISC_DELAY (30000) -#define MAX_CHILD_DISC_TOLERANCE (125 / 100) - -void receiver_state_free(struct receiver_state *rpt) { - netdata_ssl_close(&rpt->ssl); - - if(rpt->fd != -1) { - internal_error(true, "closing socket..."); - close(rpt->fd); - } - - rrdpush_decompressor_destroy(&rpt->decompressor); - - if(rpt->system_info) - rrdhost_system_info_free(rpt->system_info); - - __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_receivers, sizeof(*rpt), __ATOMIC_RELAXED); - - freez(rpt->key); - freez(rpt->hostname); - freez(rpt->registry_hostname); - freez(rpt->machine_guid); - freez(rpt->os); - freez(rpt->timezone); - freez(rpt->abbrev_timezone); - freez(rpt->client_ip); - freez(rpt->client_port); - freez(rpt->program_name); - freez(rpt->program_version); - freez(rpt); -} - -#include "plugins.d/pluginsd_parser.h" - -// IMPORTANT: to add workers, you have to edit WORKER_PARSER_FIRST_JOB accordingly -#define WORKER_RECEIVER_JOB_BYTES_READ (WORKER_PARSER_FIRST_JOB - 1) -#define WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED (WORKER_PARSER_FIRST_JOB - 2) - -// this has to be the same at parser.h -#define WORKER_RECEIVER_JOB_REPLICATION_COMPLETION (WORKER_PARSER_FIRST_JOB - 3) - -#if WORKER_PARSER_FIRST_JOB < 1 -#error The define WORKER_PARSER_FIRST_JOB needs to be at least 1 -#endif - -static inline int read_stream(struct receiver_state *r, char* buffer, size_t size) { - if(unlikely(!size)) { - internal_error(true, "%s() asked to read zero bytes", __FUNCTION__); - return 0; - } - -#ifdef ENABLE_H2O - if (is_h2o_rrdpush(r)) { - if(nd_thread_signaled_to_cancel()) - return -4; - - return (int)h2o_stream_read(r->h2o_ctx, buffer, size); - } -#endif - - int tries = 100; - ssize_t bytes_read; - - do { - errno_clear(); - - switch(wait_on_socket_or_cancel_with_timeout( - &r->ssl, - r->fd, 0, POLLIN, NULL)) - { - case 0: // data are waiting - break; - - case 1: // timeout reached - netdata_log_error("STREAM: %s(): timeout while waiting for data on socket!", __FUNCTION__); - return -3; - - case -1: // thread cancelled - netdata_log_error("STREAM: %s(): thread has been cancelled timeout while waiting for data on socket!", __FUNCTION__); - return -4; - - default: - case 2: // error on socket - netdata_log_error("STREAM: %s() socket error!", __FUNCTION__); - return -2; - } - - if (SSL_connection(&r->ssl)) - bytes_read = netdata_ssl_read(&r->ssl, buffer, size); - else - bytes_read = read(r->fd, buffer, size); - - } while(bytes_read < 0 && errno == EINTR && tries--); - - if((bytes_read == 0 || bytes_read == -1) && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS)) { - netdata_log_error("STREAM: %s(): timeout while waiting for data on socket!", __FUNCTION__); - bytes_read = -3; - } - else if (bytes_read == 0) { - netdata_log_error("STREAM: %s(): EOF while reading data from socket!", __FUNCTION__); - bytes_read = -1; - } - else if (bytes_read < 0) { - netdata_log_error("STREAM: %s() failed to read from socket!", __FUNCTION__); - bytes_read = -2; - } - - return (int)bytes_read; -} - -static inline STREAM_HANDSHAKE read_stream_error_to_reason(int code) { - if(code > 0) - return 0; - - switch(code) { - case 0: - // asked to read zero bytes - return STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_READ_BUFFER; - - case -1: - // EOF - return STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF; - - case -2: - // failed to read - return STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED; - - case -3: - // timeout - return STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT; - - case -4: - // the thread is cancelled - return STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN; - - default: - // anything else - return STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR; - } -} - -static inline bool receiver_read_uncompressed(struct receiver_state *r, STREAM_HANDSHAKE *reason) { -#ifdef NETDATA_INTERNAL_CHECKS - if(r->reader.read_buffer[r->reader.read_len] != '\0') - fatal("%s(): read_buffer does not start with zero", __FUNCTION__ ); -#endif - - int bytes_read = read_stream(r, r->reader.read_buffer + r->reader.read_len, sizeof(r->reader.read_buffer) - r->reader.read_len - 1); - if(unlikely(bytes_read <= 0)) { - *reason = read_stream_error_to_reason(bytes_read); - return false; - } - - worker_set_metric(WORKER_RECEIVER_JOB_BYTES_READ, (NETDATA_DOUBLE)bytes_read); - worker_set_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes_read); - - r->reader.read_len += bytes_read; - r->reader.read_buffer[r->reader.read_len] = '\0'; - - return true; -} - -static inline bool receiver_read_compressed(struct receiver_state *r, STREAM_HANDSHAKE *reason) { - - internal_fatal(r->reader.read_buffer[r->reader.read_len] != '\0', - "%s: read_buffer does not start with zero #2", __FUNCTION__ ); - - // first use any available uncompressed data - if (likely(rrdpush_decompressed_bytes_in_buffer(&r->decompressor))) { - size_t available = sizeof(r->reader.read_buffer) - r->reader.read_len - 1; - if (likely(available)) { - size_t len = rrdpush_decompressor_get(&r->decompressor, r->reader.read_buffer + r->reader.read_len, available); - if (unlikely(!len)) { - internal_error(true, "decompressor returned zero length #1"); - return false; - } - - r->reader.read_len += (int)len; - r->reader.read_buffer[r->reader.read_len] = '\0'; - } - else - internal_fatal(true, "The line to read is too big! Already have %zd bytes in read_buffer.", r->reader.read_len); - - return true; - } - - // no decompressed data available - // read the compression signature of the next block - - if(unlikely(r->reader.read_len + r->decompressor.signature_size > sizeof(r->reader.read_buffer) - 1)) { - internal_error(true, "The last incomplete line does not leave enough room for the next compression header! " - "Already have %zd bytes in read_buffer.", r->reader.read_len); - return false; - } - - // read the compression signature from the stream - // we have to do a loop here, because read_stream() may return less than the data we need - int bytes_read = 0; - do { - int ret = read_stream(r, r->reader.read_buffer + r->reader.read_len + bytes_read, r->decompressor.signature_size - bytes_read); - if (unlikely(ret <= 0)) { - *reason = read_stream_error_to_reason(ret); - return false; - } - - bytes_read += ret; - } while(unlikely(bytes_read < (int)r->decompressor.signature_size)); - - worker_set_metric(WORKER_RECEIVER_JOB_BYTES_READ, (NETDATA_DOUBLE)bytes_read); - - if(unlikely(bytes_read != (int)r->decompressor.signature_size)) - fatal("read %d bytes, but expected compression signature of size %zu", bytes_read, r->decompressor.signature_size); - - size_t compressed_message_size = rrdpush_decompressor_start(&r->decompressor, r->reader.read_buffer + r->reader.read_len, bytes_read); - if (unlikely(!compressed_message_size)) { - internal_error(true, "multiplexed uncompressed data in compressed stream!"); - r->reader.read_len += bytes_read; - r->reader.read_buffer[r->reader.read_len] = '\0'; - return true; - } - - if(unlikely(compressed_message_size > COMPRESSION_MAX_MSG_SIZE)) { - netdata_log_error("received a compressed message of %zu bytes, which is bigger than the max compressed message size supported of %zu. Ignoring message.", - compressed_message_size, (size_t)COMPRESSION_MAX_MSG_SIZE); - return false; - } - - // delete compression header from our read buffer - r->reader.read_buffer[r->reader.read_len] = '\0'; - - // Read the entire compressed block of compressed data - char compressed[compressed_message_size]; - size_t compressed_bytes_read = 0; - do { - size_t start = compressed_bytes_read; - size_t remaining = compressed_message_size - start; - - int last_read_bytes = read_stream(r, &compressed[start], remaining); - if (unlikely(last_read_bytes <= 0)) { - *reason = read_stream_error_to_reason(last_read_bytes); - return false; - } - - compressed_bytes_read += last_read_bytes; - - } while(unlikely(compressed_message_size > compressed_bytes_read)); - - worker_set_metric(WORKER_RECEIVER_JOB_BYTES_READ, (NETDATA_DOUBLE)compressed_bytes_read); - - // decompress the compressed block - size_t bytes_to_parse = rrdpush_decompress(&r->decompressor, compressed, compressed_bytes_read); - if (unlikely(!bytes_to_parse)) { - internal_error(true, "no bytes to parse."); - return false; - } - - worker_set_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes_to_parse); - - // fill read buffer with decompressed data - size_t len = (int) rrdpush_decompressor_get(&r->decompressor, r->reader.read_buffer + r->reader.read_len, sizeof(r->reader.read_buffer) - r->reader.read_len - 1); - if (unlikely(!len)) { - internal_error(true, "decompressor returned zero length #2"); - return false; - } - r->reader.read_len += (int)len; - r->reader.read_buffer[r->reader.read_len] = '\0'; - - return true; -} - -bool plugin_is_enabled(struct plugind *cd); - -static void receiver_set_exit_reason(struct receiver_state *rpt, STREAM_HANDSHAKE reason, bool force) { - if(force || !rpt->exit.reason) - rpt->exit.reason = reason; -} - -static inline bool receiver_should_stop(struct receiver_state *rpt) { - static __thread size_t counter = 0; - - if(nd_thread_signaled_to_cancel()) { - receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN, false); - return true; - } - - if(unlikely(rpt->exit.shutdown)) { - receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN, false); - return true; - } - - if(unlikely(!service_running(SERVICE_STREAMING))) { - receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_NETDATA_EXIT, false); - return true; - } - - if(unlikely((counter++ % 1000) == 0)) - rpt->last_msg_t = now_monotonic_sec(); - - return false; -} - -static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, int fd, void *ssl) { - size_t result = 0; - - PARSER *parser = NULL; - { - PARSER_USER_OBJECT user = { - .enabled = plugin_is_enabled(cd), - .host = rpt->host, - .opaque = rpt, - .cd = cd, - .trust_durations = 1, - .capabilities = rpt->capabilities, - }; - - parser = parser_init(&user, fd, fd, PARSER_INPUT_SPLIT, ssl); - } - -#ifdef ENABLE_H2O - parser->h2o_ctx = rpt->h2o_ctx; -#endif - - pluginsd_keywords_init(parser, PARSER_INIT_STREAMING); - - rrd_collector_started(); - - bool compressed_connection = rrdpush_decompression_initialize(rpt); - buffered_reader_init(&rpt->reader); - -#ifdef NETDATA_LOG_STREAM_RECEIVE - { - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "/tmp/stream-receiver-%s.txt", rpt->host ? rrdhost_hostname( - rpt->host) : "unknown" - ); - parser->user.stream_log_fp = fopen(filename, "w"); - parser->user.stream_log_repertoire = PARSER_REP_METADATA; - } -#endif - - CLEAN_BUFFER *buffer = buffer_create(sizeof(rpt->reader.read_buffer), NULL); - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_CB(NDF_REQUEST, line_splitter_reconstruct_line, &parser->line), - ND_LOG_FIELD_CB(NDF_NIDL_NODE, parser_reconstruct_node, parser), - ND_LOG_FIELD_CB(NDF_NIDL_INSTANCE, parser_reconstruct_instance, parser), - ND_LOG_FIELD_CB(NDF_NIDL_CONTEXT, parser_reconstruct_context, parser), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - __atomic_store_n(&rpt->parser, parser, __ATOMIC_RELAXED); - rrdpush_receiver_send_node_and_claim_id_to_child(rpt->host); - - while(!receiver_should_stop(rpt)) { - - if(!buffered_reader_next_line(&rpt->reader, buffer)) { - STREAM_HANDSHAKE reason = STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR; - - bool have_new_data = compressed_connection ? receiver_read_compressed(rpt, &reason) - : receiver_read_uncompressed(rpt, &reason); - - if(unlikely(!have_new_data)) { - receiver_set_exit_reason(rpt, reason, false); - break; - } - - continue; - } - - if(unlikely(parser_action(parser, buffer->buffer))) { - receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, false); - break; - } - - buffer->len = 0; - buffer->buffer[0] = '\0'; - } - - // cleanup the sender buffer, because we may end-up reusing an incomplete buffer - sender_thread_buffer_free(); - parser->user.v2.stream_buffer.wb = NULL; - - // make sure send_to_plugin() will not write any data to the socket - spinlock_lock(&parser->writer.spinlock); - parser->fd_output = -1; - parser->ssl_output = NULL; - spinlock_unlock(&parser->writer.spinlock); - - result = parser->user.data_collections_count; - return result; -} - -static void rrdpush_receiver_replication_reset(RRDHOST *host) { - RRDSET *st; - rrdset_foreach_read(st, host) { - rrdset_flag_clear(st, RRDSET_FLAG_RECEIVER_REPLICATION_IN_PROGRESS); - rrdset_flag_set(st, RRDSET_FLAG_RECEIVER_REPLICATION_FINISHED); - } - rrdset_foreach_done(st); - rrdhost_receiver_replicating_charts_zero(host); -} - -static bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) { - bool signal_rrdcontext = false; - bool set_this = false; - - spinlock_lock(&host->receiver_lock); - - if (!host->receiver) { - rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN); - - host->rrdpush_receiver_connection_counter++; - __atomic_add_fetch(&localhost->connected_children_count, 1, __ATOMIC_RELAXED); - - host->receiver = rpt; - rpt->host = host; - - host->child_connect_time = now_realtime_sec(); - host->child_disconnected_time = 0; - host->child_last_chart_command = 0; - host->trigger_chart_obsoletion_check = 1; - - if (rpt->config.health_enabled != CONFIG_BOOLEAN_NO) { - if (rpt->config.alarms_delay > 0) { - host->health.health_delay_up_to = now_realtime_sec() + rpt->config.alarms_delay; - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "[%s]: Postponing health checks for %" PRId64 " seconds, because it was just connected.", - rrdhost_hostname(host), - (int64_t) rpt->config.alarms_delay); - } - } - - host->health_log.health_log_retention_s = rpt->config.alarms_history; - -// this is a test -// if(rpt->hops <= host->sender->hops) -// rrdpush_sender_thread_stop(host, "HOPS MISMATCH", false); - - signal_rrdcontext = true; - rrdpush_receiver_replication_reset(host); - - rrdhost_flag_clear(rpt->host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED); - aclk_queue_node_info(rpt->host, true); - - rrdpush_reset_destinations_postpone_time(host); - - set_this = true; - } - - spinlock_unlock(&host->receiver_lock); - - if(signal_rrdcontext) - rrdcontext_host_child_connected(host); - - return set_this; -} - -static void rrdhost_clear_receiver(struct receiver_state *rpt) { - RRDHOST *host = rpt->host; - if(!host) return; - - spinlock_lock(&host->receiver_lock); - { - // Make sure that we detach this thread and don't kill a freshly arriving receiver - - if (host->receiver == rpt) { - spinlock_unlock(&host->receiver_lock); - { - // run all these without having the receiver lock - - stream_path_child_disconnected(host); - rrdpush_sender_thread_stop(host, STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT, false); - rrdpush_receiver_replication_reset(host); - rrdcontext_host_child_disconnected(host); - - if (rpt->config.health_enabled) - rrdcalc_child_disconnected(host); - - rrdpush_reset_destinations_postpone_time(host); - } - spinlock_lock(&host->receiver_lock); - - // now we have the lock again - - __atomic_sub_fetch(&localhost->connected_children_count, 1, __ATOMIC_RELAXED); - rrdhost_flag_set(rpt->host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED); - - host->trigger_chart_obsoletion_check = 0; - host->child_connect_time = 0; - host->child_disconnected_time = now_realtime_sec(); - host->health.health_enabled = 0; - - host->rrdpush_last_receiver_exit_reason = rpt->exit.reason; - rrdhost_flag_set(host, RRDHOST_FLAG_ORPHAN); - host->receiver = NULL; - } - } - - // this must be cleared with the receiver lock - pluginsd_process_cleanup(rpt->parser); - __atomic_store_n(&rpt->parser, NULL, __ATOMIC_RELAXED); - - spinlock_unlock(&host->receiver_lock); -} - -bool stop_streaming_receiver(RRDHOST *host, STREAM_HANDSHAKE reason) { - bool ret = false; - - spinlock_lock(&host->receiver_lock); - - if(host->receiver) { - if(!host->receiver->exit.shutdown) { - host->receiver->exit.shutdown = true; - receiver_set_exit_reason(host->receiver, reason, true); - shutdown(host->receiver->fd, SHUT_RDWR); - } - - nd_thread_signal_cancel(host->receiver->thread); - } - - int count = 2000; - while (host->receiver && count-- > 0) { - spinlock_unlock(&host->receiver_lock); - - // let the lock for the receiver thread to exit - sleep_usec(1 * USEC_PER_MS); - - spinlock_lock(&host->receiver_lock); - } - - if(host->receiver) - netdata_log_error("STREAM '%s' [receive from [%s]:%s]: " - "thread %d takes too long to stop, giving up..." - , rrdhost_hostname(host) - , host->receiver->client_ip, host->receiver->client_port - , host->receiver->tid); - else - ret = true; - - spinlock_unlock(&host->receiver_lock); - - return ret; -} - -static void rrdpush_send_error_on_taken_over_connection(struct receiver_state *rpt, const char *msg) { - (void) send_timeout( - &rpt->ssl, - rpt->fd, - (char *)msg, - strlen(msg), - 0, - 5); -} - -static void rrdpush_receive_log_status(struct receiver_state *rpt, const char *msg, const char *status, ND_LOG_FIELD_PRIORITY priority) { - // this function may be called BEFORE we spawn the receiver thread - // so, we need to add the fields again (it does not harm) - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_SRC_IP, rpt->client_ip), - ND_LOG_FIELD_TXT(NDF_SRC_PORT, rpt->client_port), - ND_LOG_FIELD_TXT(NDF_NIDL_NODE, (rpt->hostname && *rpt->hostname) ? rpt->hostname : ""), - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, status), - ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_from_child_msgid), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - nd_log(NDLS_ACCESS, priority, "api_key:'%s' machine_guid:'%s' msg:'%s'" - , (rpt->key && *rpt->key)? rpt->key : "" - , (rpt->machine_guid && *rpt->machine_guid) ? rpt->machine_guid : "" - , msg); - - nd_log(NDLS_DAEMON, priority, "STREAM_RECEIVER for '%s': %s %s%s%s" - , (rpt->hostname && *rpt->hostname) ? rpt->hostname : "" - , msg - , rpt->exit.reason != STREAM_HANDSHAKE_NEVER?" (":"" - , stream_handshake_error_to_string(rpt->exit.reason) - , rpt->exit.reason != STREAM_HANDSHAKE_NEVER?")":"" - ); -} - -static void rrdpush_receive(struct receiver_state *rpt) -{ - rpt->config.mode = default_rrd_memory_mode; - rpt->config.history = default_rrd_history_entries; - - rpt->config.health_enabled = health_plugin_enabled(); - rpt->config.alarms_delay = 60; - rpt->config.alarms_history = HEALTH_LOG_RETENTION_DEFAULT; - - rpt->config.rrdpush_enabled = (int)stream_conf_send_enabled; - rpt->config.rrdpush_destination = stream_conf_send_destination; - rpt->config.rrdpush_api_key = stream_conf_send_api_key; - rpt->config.rrdpush_send_charts_matching = stream_conf_send_charts_matching; - - rpt->config.rrdpush_enable_replication = stream_conf_replication_enabled; - rpt->config.rrdpush_seconds_to_replicate = stream_conf_replication_period; - rpt->config.rrdpush_replication_step = stream_conf_replication_step; - - rpt->config.update_every = (int)appconfig_get_duration_seconds(&stream_config, rpt->machine_guid, "update every", rpt->config.update_every); - if(rpt->config.update_every < 0) rpt->config.update_every = 1; - - rpt->config.history = (int)appconfig_get_number(&stream_config, rpt->key, "retention", rpt->config.history); - rpt->config.history = (int)appconfig_get_number(&stream_config, rpt->machine_guid, "retention", rpt->config.history); - if(rpt->config.history < 5) rpt->config.history = 5; - - rpt->config.mode = rrd_memory_mode_id(appconfig_get(&stream_config, rpt->key, "db", rrd_memory_mode_name(rpt->config.mode))); - rpt->config.mode = rrd_memory_mode_id(appconfig_get(&stream_config, rpt->machine_guid, "db", rrd_memory_mode_name(rpt->config.mode))); - - if (unlikely(rpt->config.mode == RRD_MEMORY_MODE_DBENGINE && !dbengine_enabled)) { - netdata_log_error("STREAM '%s' [receive from %s:%s]: " - "dbengine is not enabled, falling back to default." - , rpt->hostname - , rpt->client_ip, rpt->client_port - ); - - rpt->config.mode = default_rrd_memory_mode; - } - - rpt->config.health_enabled = appconfig_get_boolean_ondemand(&stream_config, rpt->key, "health enabled by default", rpt->config.health_enabled); - rpt->config.health_enabled = appconfig_get_boolean_ondemand(&stream_config, rpt->machine_guid, "health enabled", rpt->config.health_enabled); - - rpt->config.alarms_delay = appconfig_get_duration_seconds(&stream_config, rpt->key, "postpone alerts on connect", rpt->config.alarms_delay); - rpt->config.alarms_delay = appconfig_get_duration_seconds(&stream_config, rpt->machine_guid, "postpone alerts on connect", rpt->config.alarms_delay); - - rpt->config.alarms_history = appconfig_get_duration_seconds(&stream_config, rpt->key, "health log retention", rpt->config.alarms_history); - rpt->config.alarms_history = appconfig_get_duration_seconds(&stream_config, rpt->machine_guid, "health log retention", rpt->config.alarms_history); - - rpt->config.rrdpush_enabled = appconfig_get_boolean(&stream_config, rpt->key, "proxy enabled", rpt->config.rrdpush_enabled); - rpt->config.rrdpush_enabled = appconfig_get_boolean(&stream_config, rpt->machine_guid, "proxy enabled", rpt->config.rrdpush_enabled); - - rpt->config.rrdpush_destination = appconfig_get(&stream_config, rpt->key, "proxy destination", rpt->config.rrdpush_destination); - rpt->config.rrdpush_destination = appconfig_get(&stream_config, rpt->machine_guid, "proxy destination", rpt->config.rrdpush_destination); - - rpt->config.rrdpush_api_key = appconfig_get(&stream_config, rpt->key, "proxy api key", rpt->config.rrdpush_api_key); - rpt->config.rrdpush_api_key = appconfig_get(&stream_config, rpt->machine_guid, "proxy api key", rpt->config.rrdpush_api_key); - - rpt->config.rrdpush_send_charts_matching = appconfig_get(&stream_config, rpt->key, "proxy send charts matching", rpt->config.rrdpush_send_charts_matching); - rpt->config.rrdpush_send_charts_matching = appconfig_get(&stream_config, rpt->machine_guid, "proxy send charts matching", rpt->config.rrdpush_send_charts_matching); - - rpt->config.rrdpush_enable_replication = appconfig_get_boolean(&stream_config, rpt->key, "enable replication", rpt->config.rrdpush_enable_replication); - rpt->config.rrdpush_enable_replication = appconfig_get_boolean(&stream_config, rpt->machine_guid, "enable replication", rpt->config.rrdpush_enable_replication); - - rpt->config.rrdpush_seconds_to_replicate = appconfig_get_duration_seconds(&stream_config, rpt->key, "replication period", rpt->config.rrdpush_seconds_to_replicate); - rpt->config.rrdpush_seconds_to_replicate = appconfig_get_duration_seconds(&stream_config, rpt->machine_guid, "replication period", rpt->config.rrdpush_seconds_to_replicate); - - rpt->config.rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->key, "replication step", rpt->config.rrdpush_replication_step); - rpt->config.rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->machine_guid, "replication step", rpt->config.rrdpush_replication_step); - - rpt->config.rrdpush_compression = stream_conf_compression_enabled; - rpt->config.rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->key, "enable compression", rpt->config.rrdpush_compression); - rpt->config.rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->machine_guid, "enable compression", rpt->config.rrdpush_compression); - - bool is_ephemeral = false; - is_ephemeral = appconfig_get_boolean(&stream_config, rpt->key, "is ephemeral node", CONFIG_BOOLEAN_NO); - is_ephemeral = appconfig_get_boolean(&stream_config, rpt->machine_guid, "is ephemeral node", is_ephemeral); - - if(rpt->config.rrdpush_compression) { - const char *order = appconfig_get(&stream_config, rpt->key, "compression algorithms order", RRDPUSH_COMPRESSION_ALGORITHMS_ORDER); - order = appconfig_get(&stream_config, rpt->machine_guid, "compression algorithms order", order); - rrdpush_parse_compression_order(rpt, order); - } - - // find the host for this receiver - { - // this will also update the host with our system_info - RRDHOST *host = rrdhost_find_or_create( - rpt->hostname, - rpt->registry_hostname, - rpt->machine_guid, - rpt->os, - rpt->timezone, - rpt->abbrev_timezone, - rpt->utc_offset, - rpt->program_name, - rpt->program_version, - rpt->config.update_every, - rpt->config.history, - rpt->config.mode, - (unsigned int)(rpt->config.health_enabled != CONFIG_BOOLEAN_NO), - (unsigned int)(rpt->config.rrdpush_enabled && rpt->config.rrdpush_destination && - *rpt->config.rrdpush_destination && rpt->config.rrdpush_api_key && - *rpt->config.rrdpush_api_key), - rpt->config.rrdpush_destination, - rpt->config.rrdpush_api_key, - rpt->config.rrdpush_send_charts_matching, - rpt->config.rrdpush_enable_replication, - rpt->config.rrdpush_seconds_to_replicate, - rpt->config.rrdpush_replication_step, - rpt->system_info, - 0); - - if(!host) { - rrdpush_receive_log_status( - rpt,"failed to find/create host structure, rejecting connection", - RRDPUSH_STATUS_INTERNAL_SERVER_ERROR, NDLP_ERR); - - rrdpush_send_error_on_taken_over_connection(rpt, START_STREAMING_ERROR_INTERNAL_ERROR); - goto cleanup; - } - - if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))) { - rrdpush_receive_log_status( - rpt, "host is initializing, retry later", - RRDPUSH_STATUS_INITIALIZATION_IN_PROGRESS, NDLP_NOTICE); - - rrdpush_send_error_on_taken_over_connection(rpt, START_STREAMING_ERROR_INITIALIZATION); - goto cleanup; - } - - // system_info has been consumed by the host structure - rpt->system_info = NULL; - - if(!rrdhost_set_receiver(host, rpt)) { - rrdpush_receive_log_status( - rpt, "host is already served by another receiver", - RRDPUSH_STATUS_DUPLICATE_RECEIVER, NDLP_INFO); - - rrdpush_send_error_on_taken_over_connection(rpt, START_STREAMING_ERROR_ALREADY_STREAMING); - goto cleanup; - } - } - -#ifdef NETDATA_INTERNAL_CHECKS - netdata_log_info("STREAM '%s' [receive from [%s]:%s]: " - "client willing to stream metrics for host '%s' with machine_guid '%s': " - "update every = %d, history = %d, memory mode = %s, health %s,%s" - , rpt->hostname - , rpt->client_ip - , rpt->client_port - , rrdhost_hostname(rpt->host) - , rpt->host->machine_guid - , rpt->host->rrd_update_every - , rpt->host->rrd_history_entries - , rrd_memory_mode_name(rpt->host->rrd_memory_mode) - , (rpt->config.health_enabled == CONFIG_BOOLEAN_NO)?"disabled":((rpt->config.health_enabled == CONFIG_BOOLEAN_YES)?"enabled":"auto") - , (rpt->ssl.conn != NULL) ? " SSL," : "" - ); -#endif // NETDATA_INTERNAL_CHECKS - - - struct plugind cd = { - .update_every = default_rrd_update_every, - .unsafe = { - .spinlock = NETDATA_SPINLOCK_INITIALIZER, - .running = true, - .enabled = true, - }, - .started_t = now_realtime_sec(), - }; - - // put the client IP and port into the buffers used by plugins.d - snprintfz(cd.id, CONFIG_MAX_NAME, "%s:%s", rpt->client_ip, rpt->client_port); - snprintfz(cd.filename, FILENAME_MAX, "%s:%s", rpt->client_ip, rpt->client_port); - snprintfz(cd.fullfilename, FILENAME_MAX, "%s:%s", rpt->client_ip, rpt->client_port); - snprintfz(cd.cmd, PLUGINSD_CMD_MAX, "%s:%s", rpt->client_ip, rpt->client_port); - - rrdpush_select_receiver_compression_algorithm(rpt); - - { - // netdata_log_info("STREAM %s [receive from [%s]:%s]: initializing communication...", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port); - char initial_response[HTTP_HEADER_SIZE]; - if (stream_has_capability(rpt, STREAM_CAP_VCAPS)) { - log_receiver_capabilities(rpt); - sprintf(initial_response, "%s%u", START_STREAMING_PROMPT_VN, rpt->capabilities); - } - else if (stream_has_capability(rpt, STREAM_CAP_VN)) { - log_receiver_capabilities(rpt); - sprintf(initial_response, "%s%d", START_STREAMING_PROMPT_VN, stream_capabilities_to_vn(rpt->capabilities)); - } - else if (stream_has_capability(rpt, STREAM_CAP_V2)) { - log_receiver_capabilities(rpt); - sprintf(initial_response, "%s", START_STREAMING_PROMPT_V2); - } - else { // stream_has_capability(rpt, STREAM_CAP_V1) - log_receiver_capabilities(rpt); - sprintf(initial_response, "%s", START_STREAMING_PROMPT_V1); - } - - netdata_log_debug(D_STREAM, "Initial response to %s: %s", rpt->client_ip, initial_response); -#ifdef ENABLE_H2O - if (is_h2o_rrdpush(rpt)) { - h2o_stream_write(rpt->h2o_ctx, initial_response, strlen(initial_response)); - } else { -#endif - ssize_t bytes_sent = send_timeout( - &rpt->ssl, - rpt->fd, initial_response, strlen(initial_response), 0, 60); - - if(bytes_sent != (ssize_t)strlen(initial_response)) { - internal_error(true, "Cannot send response, got %zd bytes, expecting %zu bytes", bytes_sent, strlen(initial_response)); - rrdpush_receive_log_status( - rpt, "cannot reply back, dropping connection", - RRDPUSH_STATUS_CANT_REPLY, NDLP_ERR); - goto cleanup; - } -#ifdef ENABLE_H2O - } -#endif - } - -#ifdef ENABLE_H2O - unless_h2o_rrdpush(rpt) -#endif - { - // remove the non-blocking flag from the socket - if(sock_delnonblock(rpt->fd) < 0) - netdata_log_error("STREAM '%s' [receive from [%s]:%s]: " - "cannot remove the non-blocking flag from socket %d" - , rrdhost_hostname(rpt->host) - , rpt->client_ip, rpt->client_port - , rpt->fd); - - struct timeval timeout; - timeout.tv_sec = 600; - timeout.tv_usec = 0; - if (unlikely(setsockopt(rpt->fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout) != 0)) - netdata_log_error("STREAM '%s' [receive from [%s]:%s]: " - "cannot set timeout for socket %d" - , rrdhost_hostname(rpt->host) - , rpt->client_ip, rpt->client_port - , rpt->fd); - } - - rrdpush_receive_log_status( - rpt, "connected and ready to receive data", - RRDPUSH_STATUS_CONNECTED, NDLP_INFO); - - // in case we have cloud connection we inform cloud - // new child connected - schedule_node_state_update(rpt->host, 300); - rrdhost_set_is_parent_label(); - - if (is_ephemeral) - rrdhost_option_set(rpt->host, RRDHOST_OPTION_EPHEMERAL_HOST); - - // let it reconnect to parent immediately - rrdpush_reset_destinations_postpone_time(rpt->host); - - // receive data - size_t count = streaming_parser(rpt, &cd, rpt->fd, (rpt->ssl.conn) ? &rpt->ssl : NULL); - - // the parser stopped - receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_EXIT, false); - - { - char msg[100 + 1]; - snprintfz(msg, sizeof(msg) - 1, "disconnected (completed %zu updates)", count); - rrdpush_receive_log_status(rpt, msg, RRDPUSH_STATUS_DISCONNECTED, NDLP_WARNING); - } - - // in case we have cloud connection we inform cloud - // a child disconnected - STREAM_PATH tmp = rrdhost_stream_path_fetch(rpt->host); - uint64_t total_reboot = (tmp.start_time + tmp.shutdown_time); - schedule_node_state_update(rpt->host, MIN((total_reboot * MAX_CHILD_DISC_TOLERANCE), MAX_CHILD_DISC_DELAY)); - -cleanup: - ; -} - -static bool stream_receiver_log_capabilities(BUFFER *wb, void *ptr) { - struct receiver_state *rpt = ptr; - if(!rpt) - return false; - - stream_capabilities_to_string(wb, rpt->capabilities); - return true; -} - -static bool stream_receiver_log_transport(BUFFER *wb, void *ptr) { - struct receiver_state *rpt = ptr; - if(!rpt) - return false; - - buffer_strcat(wb, SSL_connection(&rpt->ssl) ? "https" : "http"); - return true; -} - -void *rrdpush_receiver_thread(void *ptr) { - worker_register("STREAMRCV"); - - worker_register_job_custom_metric(WORKER_RECEIVER_JOB_BYTES_READ, - "received bytes", "bytes/s", - WORKER_METRIC_INCREMENT); - - worker_register_job_custom_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, - "uncompressed bytes", "bytes/s", - WORKER_METRIC_INCREMENT); - - worker_register_job_custom_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION, - "replication completion", "%", - WORKER_METRIC_ABSOLUTE); - - struct receiver_state *rpt = (struct receiver_state *) ptr; - rpt->tid = gettid_cached(); - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_SRC_IP, rpt->client_ip), - ND_LOG_FIELD_TXT(NDF_SRC_PORT, rpt->client_port), - ND_LOG_FIELD_TXT(NDF_NIDL_NODE, rpt->hostname), - ND_LOG_FIELD_CB(NDF_SRC_TRANSPORT, stream_receiver_log_transport, rpt), - ND_LOG_FIELD_CB(NDF_SRC_CAPABILITIES, stream_receiver_log_capabilities, rpt), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - netdata_log_info("STREAM %s [%s]:%s: receive thread started", rpt->hostname, rpt->client_ip - , rpt->client_port); - - rrdpush_receive(rpt); - - netdata_log_info("STREAM '%s' [receive from [%s]:%s]: " - "receive thread ended (task id %d)" - , rpt->hostname ? rpt->hostname : "-" - , rpt->client_ip ? rpt->client_ip : "-", rpt->client_port ? rpt->client_port : "-", gettid_cached()); - - worker_unregister(); - rrdhost_clear_receiver(rpt); - rrdhost_set_is_parent_label(); - receiver_state_free(rpt); - return NULL; -} - -int rrdpush_receiver_permission_denied(struct web_client *w) { - // we always respond with the same message and error code - // to prevent an attacker from gaining info about the error - buffer_flush(w->response.data); - buffer_strcat(w->response.data, START_STREAMING_ERROR_NOT_PERMITTED); - return HTTP_RESP_UNAUTHORIZED; -} - -int rrdpush_receiver_too_busy_now(struct web_client *w) { - // we always respond with the same message and error code - // to prevent an attacker from gaining info about the error - buffer_flush(w->response.data); - buffer_strcat(w->response.data, START_STREAMING_ERROR_BUSY_TRY_LATER); - return HTTP_RESP_SERVICE_UNAVAILABLE; -} - -static void rrdpush_receiver_takeover_web_connection(struct web_client *w, struct receiver_state *rpt) { - rpt->fd = w->ifd; - - rpt->ssl.conn = w->ssl.conn; - rpt->ssl.state = w->ssl.state; - - w->ssl = NETDATA_SSL_UNSET_CONNECTION; - - WEB_CLIENT_IS_DEAD(w); - - if(web_server_mode == WEB_SERVER_MODE_STATIC_THREADED) { - web_client_flag_set(w, WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET); - } - else { - if(w->ifd == w->ofd) - w->ifd = w->ofd = -1; - else - w->ifd = -1; - } - - buffer_flush(w->response.data); -} - -int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_string, void *h2o_ctx __maybe_unused) { - - if(!service_running(ABILITY_STREAMING_CONNECTIONS)) - return rrdpush_receiver_too_busy_now(w); - - struct receiver_state *rpt = callocz(1, sizeof(*rpt)); - rpt->connected_since_s = now_realtime_sec(); - rpt->last_msg_t = now_monotonic_sec(); - rpt->hops = 1; - - rpt->capabilities = STREAM_CAP_INVALID; - -#ifdef ENABLE_H2O - rpt->h2o_ctx = h2o_ctx; -#endif - - __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_receivers, sizeof(*rpt), __ATOMIC_RELAXED); - __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED); - - rpt->system_info = callocz(1, sizeof(struct rrdhost_system_info)); - rpt->system_info->hops = rpt->hops; - - rpt->fd = -1; - rpt->client_ip = strdupz(w->client_ip); - rpt->client_port = strdupz(w->client_port); - - rpt->ssl = NETDATA_SSL_UNSET_CONNECTION; - - rpt->config.update_every = default_rrd_update_every; - - // parse the parameters and fill rpt and rpt->system_info - - while(decoded_query_string) { - char *value = strsep_skip_consecutive_separators(&decoded_query_string, "&"); - if(!value || !*value) continue; - - char *name = strsep_skip_consecutive_separators(&value, "="); - if(!name || !*name) continue; - if(!value || !*value) continue; - - if(!strcmp(name, "key") && !rpt->key) - rpt->key = strdupz(value); - - else if(!strcmp(name, "hostname") && !rpt->hostname) - rpt->hostname = strdupz(value); - - else if(!strcmp(name, "registry_hostname") && !rpt->registry_hostname) - rpt->registry_hostname = strdupz(value); - - else if(!strcmp(name, "machine_guid") && !rpt->machine_guid) - rpt->machine_guid = strdupz(value); - - else if(!strcmp(name, "update_every")) - rpt->config.update_every = (int)strtoul(value, NULL, 0); - - else if(!strcmp(name, "os") && !rpt->os) - rpt->os = strdupz(value); - - else if(!strcmp(name, "timezone") && !rpt->timezone) - rpt->timezone = strdupz(value); - - else if(!strcmp(name, "abbrev_timezone") && !rpt->abbrev_timezone) - rpt->abbrev_timezone = strdupz(value); - - else if(!strcmp(name, "utc_offset")) - rpt->utc_offset = (int32_t)strtol(value, NULL, 0); - - else if(!strcmp(name, "hops")) - rpt->hops = rpt->system_info->hops = (uint16_t) strtoul(value, NULL, 0); - - else if(!strcmp(name, "ml_capable")) - rpt->system_info->ml_capable = strtoul(value, NULL, 0); - - else if(!strcmp(name, "ml_enabled")) - rpt->system_info->ml_enabled = strtoul(value, NULL, 0); - - else if(!strcmp(name, "mc_version")) - rpt->system_info->mc_version = strtoul(value, NULL, 0); - - else if(!strcmp(name, "ver") && (rpt->capabilities & STREAM_CAP_INVALID)) - rpt->capabilities = convert_stream_version_to_capabilities(strtoul(value, NULL, 0), NULL, false); - - else { - // An old Netdata child does not have a compatible streaming protocol, map to something sane. - if (!strcmp(name, "NETDATA_SYSTEM_OS_NAME")) - name = "NETDATA_HOST_OS_NAME"; - - else if (!strcmp(name, "NETDATA_SYSTEM_OS_ID")) - name = "NETDATA_HOST_OS_ID"; - - else if (!strcmp(name, "NETDATA_SYSTEM_OS_ID_LIKE")) - name = "NETDATA_HOST_OS_ID_LIKE"; - - else if (!strcmp(name, "NETDATA_SYSTEM_OS_VERSION")) - name = "NETDATA_HOST_OS_VERSION"; - - else if (!strcmp(name, "NETDATA_SYSTEM_OS_VERSION_ID")) - name = "NETDATA_HOST_OS_VERSION_ID"; - - else if (!strcmp(name, "NETDATA_SYSTEM_OS_DETECTION")) - name = "NETDATA_HOST_OS_DETECTION"; - - else if(!strcmp(name, "NETDATA_PROTOCOL_VERSION") && (rpt->capabilities & STREAM_CAP_INVALID)) - rpt->capabilities = convert_stream_version_to_capabilities(1, NULL, false); - - if (unlikely(rrdhost_set_system_info_variable(rpt->system_info, name, value))) { - nd_log_daemon(NDLP_NOTICE, "STREAM '%s' [receive from [%s]:%s]: " - "request has parameter '%s' = '%s', which is not used." - , (rpt->hostname && *rpt->hostname) ? rpt->hostname : "-" - , rpt->client_ip, rpt->client_port - , name, value); - } - } - } - - if (rpt->capabilities & STREAM_CAP_INVALID) - // no version is supplied, assume version 0; - rpt->capabilities = convert_stream_version_to_capabilities(0, NULL, false); - - // find the program name and version - if(w->user_agent && w->user_agent[0]) { - char *t = strchr(w->user_agent, '/'); - if(t && *t) { - *t = '\0'; - t++; - } - - rpt->program_name = strdupz(w->user_agent); - if(t && *t) rpt->program_version = strdupz(t); - } - - // check if we should accept this connection - - if(!rpt->key || !*rpt->key) { - rrdpush_receive_log_status( - rpt, "request without an API key, rejecting connection", - RRDPUSH_STATUS_NO_API_KEY, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - if(!rpt->hostname || !*rpt->hostname) { - rrdpush_receive_log_status( - rpt, "request without a hostname, rejecting connection", - RRDPUSH_STATUS_NO_HOSTNAME, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - if(!rpt->registry_hostname) - rpt->registry_hostname = strdupz(rpt->hostname); - - if(!rpt->machine_guid || !*rpt->machine_guid) { - rrdpush_receive_log_status( - rpt, "request without a machine GUID, rejecting connection", - RRDPUSH_STATUS_NO_MACHINE_GUID, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - { - char buf[GUID_LEN + 1]; - - if (regenerate_guid(rpt->key, buf) == -1) { - rrdpush_receive_log_status( - rpt, "API key is not a valid UUID (use the command uuidgen to generate one)", - RRDPUSH_STATUS_INVALID_API_KEY, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - if (regenerate_guid(rpt->machine_guid, buf) == -1) { - rrdpush_receive_log_status( - rpt, "machine GUID is not a valid UUID", - RRDPUSH_STATUS_INVALID_MACHINE_GUID, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - } - - const char *api_key_type = appconfig_get(&stream_config, rpt->key, "type", "api"); - if(!api_key_type || !*api_key_type) api_key_type = "unknown"; - if(strcmp(api_key_type, "api") != 0) { - rrdpush_receive_log_status( - rpt, "API key is a machine GUID", - RRDPUSH_STATUS_INVALID_API_KEY, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - if(!appconfig_get_boolean(&stream_config, rpt->key, "enabled", 0)) { - rrdpush_receive_log_status( - rpt, "API key is not enabled", - RRDPUSH_STATUS_API_KEY_DISABLED, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - { - SIMPLE_PATTERN *key_allow_from = simple_pattern_create( - appconfig_get(&stream_config, rpt->key, "allow from", "*"), - NULL, SIMPLE_PATTERN_EXACT, true); - - if(key_allow_from) { - if(!simple_pattern_matches(key_allow_from, w->client_ip)) { - simple_pattern_free(key_allow_from); - - rrdpush_receive_log_status( - rpt, "API key is not allowed from this IP", - RRDPUSH_STATUS_NOT_ALLOWED_IP, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - simple_pattern_free(key_allow_from); - } - } - - { - const char *machine_guid_type = appconfig_get(&stream_config, rpt->machine_guid, "type", "machine"); - if (!machine_guid_type || !*machine_guid_type) machine_guid_type = "unknown"; - - if (strcmp(machine_guid_type, "machine") != 0) { - rrdpush_receive_log_status( - rpt, "machine GUID is an API key", - RRDPUSH_STATUS_INVALID_MACHINE_GUID, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - } - - if(!appconfig_get_boolean(&stream_config, rpt->machine_guid, "enabled", 1)) { - rrdpush_receive_log_status( - rpt, "machine GUID is not enabled", - RRDPUSH_STATUS_MACHINE_GUID_DISABLED, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - { - SIMPLE_PATTERN *machine_allow_from = simple_pattern_create( - appconfig_get(&stream_config, rpt->machine_guid, "allow from", "*"), - NULL, SIMPLE_PATTERN_EXACT, true); - - if(machine_allow_from) { - if(!simple_pattern_matches(machine_allow_from, w->client_ip)) { - simple_pattern_free(machine_allow_from); - - rrdpush_receive_log_status( - rpt, "machine GUID is not allowed from this IP", - RRDPUSH_STATUS_NOT_ALLOWED_IP, NDLP_WARNING); - - receiver_state_free(rpt); - return rrdpush_receiver_permission_denied(w); - } - - simple_pattern_free(machine_allow_from); - } - } - - if (strcmp(rpt->machine_guid, localhost->machine_guid) == 0) { - - rrdpush_receiver_takeover_web_connection(w, rpt); - - rrdpush_receive_log_status( - rpt, "machine GUID is my own", - RRDPUSH_STATUS_LOCALHOST, NDLP_DEBUG); - - char initial_response[HTTP_HEADER_SIZE + 1]; - snprintfz(initial_response, HTTP_HEADER_SIZE, "%s", START_STREAMING_ERROR_SAME_LOCALHOST); - - if(send_timeout( - &rpt->ssl, - rpt->fd, initial_response, strlen(initial_response), 0, 60) != (ssize_t)strlen(initial_response)) { - - nd_log_daemon(NDLP_ERR, "STREAM '%s' [receive from [%s]:%s]: " - "failed to reply." - , rpt->hostname - , rpt->client_ip, rpt->client_port - ); - } - - receiver_state_free(rpt); - return HTTP_RESP_OK; - } - - if(unlikely(web_client_streaming_rate_t > 0)) { - static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; - static time_t last_stream_accepted_t = 0; - - time_t now = now_realtime_sec(); - spinlock_lock(&spinlock); - - if(unlikely(last_stream_accepted_t == 0)) - last_stream_accepted_t = now; - - if(now - last_stream_accepted_t < web_client_streaming_rate_t) { - spinlock_unlock(&spinlock); - - char msg[100 + 1]; - snprintfz(msg, sizeof(msg) - 1, - "rate limit, will accept new connection in %ld secs", - (long)(web_client_streaming_rate_t - (now - last_stream_accepted_t))); - - rrdpush_receive_log_status( - rpt, msg, - RRDPUSH_STATUS_RATE_LIMIT, NDLP_NOTICE); - - receiver_state_free(rpt); - return rrdpush_receiver_too_busy_now(w); - } - - last_stream_accepted_t = now; - spinlock_unlock(&spinlock); - } - - /* - * Quick path for rejecting multiple connections. The lock taken is fine-grained - it only protects the receiver - * pointer within the host (if a host exists). This protects against multiple concurrent web requests hitting - * separate threads within the web-server and landing here. The lock guards the thread-shutdown sequence that - * detaches the receiver from the host. If the host is being created (first time-access) then we also use the - * lock to prevent race-hazard (two threads try to create the host concurrently, one wins and the other does a - * lookup to the now-attached structure). - */ - - { - time_t age = 0; - bool receiver_stale = false; - bool receiver_working = false; - - rrd_rdlock(); - RRDHOST *host = rrdhost_find_by_guid(rpt->machine_guid); - if (unlikely(host && rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))) /* Ignore archived hosts. */ - host = NULL; - - if (host) { - spinlock_lock(&host->receiver_lock); - if (host->receiver) { - age = now_monotonic_sec() - host->receiver->last_msg_t; - - if (age < 30) - receiver_working = true; - else - receiver_stale = true; - } - spinlock_unlock(&host->receiver_lock); - } - rrd_rdunlock(); - - if (receiver_stale && stop_streaming_receiver(host, STREAM_HANDSHAKE_DISCONNECT_STALE_RECEIVER)) { - // we stopped the receiver - // we can proceed with this connection - receiver_stale = false; - - nd_log_daemon(NDLP_NOTICE, "STREAM '%s' [receive from [%s]:%s]: " - "stopped previous stale receiver to accept this one." - , rpt->hostname - , rpt->client_ip, rpt->client_port - ); - } - - if (receiver_working || receiver_stale) { - // another receiver is already connected - // try again later - - char msg[200 + 1]; - snprintfz(msg, sizeof(msg) - 1, - "multiple connections for same host, " - "old connection was last used %ld secs ago%s", - age, receiver_stale ? " (signaled old receiver to stop)" : " (new connection not accepted)"); - - rrdpush_receive_log_status( - rpt, msg, - RRDPUSH_STATUS_ALREADY_CONNECTED, NDLP_DEBUG); - - // Have not set WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET - caller should clean up - buffer_flush(w->response.data); - buffer_strcat(w->response.data, START_STREAMING_ERROR_ALREADY_STREAMING); - receiver_state_free(rpt); - return HTTP_RESP_CONFLICT; - } - } - - rrdpush_receiver_takeover_web_connection(w, rpt); - - char tag[NETDATA_THREAD_TAG_MAX + 1]; - snprintfz(tag, NETDATA_THREAD_TAG_MAX, THREAD_TAG_STREAM_RECEIVER "[%s]", rpt->hostname); - tag[NETDATA_THREAD_TAG_MAX] = '\0'; - - rpt->thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_DEFAULT, rrdpush_receiver_thread, (void *)rpt); - if(!rpt->thread) { - rrdpush_receive_log_status( - rpt, "can't create receiver thread", - RRDPUSH_STATUS_INTERNAL_SERVER_ERROR, NDLP_ERR); - - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "Can't handle this request"); - receiver_state_free(rpt); - return HTTP_RESP_INTERNAL_SERVER_ERROR; - } - - // prevent the caller from closing the streaming socket - return HTTP_RESP_OK; -} diff --git a/src/streaming/receiver.h b/src/streaming/receiver.h deleted file mode 100644 index a1f2086088c783..00000000000000 --- a/src/streaming/receiver.h +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_RECEIVER_H -#define NETDATA_RECEIVER_H - -#include "libnetdata/libnetdata.h" -#include "database/rrd.h" - -struct parser; - -struct receiver_state { - RRDHOST *host; - pid_t tid; - ND_THREAD *thread; - int fd; - char *key; - char *hostname; - char *registry_hostname; - char *machine_guid; - char *os; - char *timezone; // Unused? - char *abbrev_timezone; - int32_t utc_offset; - char *client_ip; // Duplicated in pluginsd - char *client_port; // Duplicated in pluginsd - char *program_name; // Duplicated in pluginsd - char *program_version; - struct rrdhost_system_info *system_info; - STREAM_CAPABILITIES capabilities; - time_t last_msg_t; - time_t connected_since_s; - - struct buffered_reader reader; - - uint16_t hops; - - struct { - bool shutdown; // signal the streaming parser to exit - STREAM_HANDSHAKE reason; - } exit; - - struct { - RRD_MEMORY_MODE mode; - int history; - int update_every; - int health_enabled; // CONFIG_BOOLEAN_YES, CONFIG_BOOLEAN_NO, CONFIG_BOOLEAN_AUTO - time_t alarms_delay; - uint32_t alarms_history; - int rrdpush_enabled; - const char *rrdpush_api_key; // DONT FREE - it is allocated in appconfig - const char *rrdpush_send_charts_matching; // DONT FREE - it is allocated in appconfig - bool rrdpush_enable_replication; - time_t rrdpush_seconds_to_replicate; - time_t rrdpush_replication_step; - const char *rrdpush_destination; // DONT FREE - it is allocated in appconfig - unsigned int rrdpush_compression; - STREAM_CAPABILITIES compression_priorities[COMPRESSION_ALGORITHM_MAX]; - } config; - - NETDATA_SSL ssl; - - time_t replication_first_time_t; - - struct decompressor_state decompressor; - /* - struct { - uint32_t count; - STREAM_NODE_INSTANCE *array; - } instances; -*/ - - // The parser pointer is safe to read and use, only when having the host receiver lock. - // Without this lock, the data pointed by the pointer may vanish randomly. - // Also, since the receiver sets it when it starts, it should be read with - // an atomic read. - struct parser *parser; - -#ifdef ENABLE_H2O - void *h2o_ctx; -#endif -}; - -#ifdef ENABLE_H2O -#define is_h2o_rrdpush(x) ((x)->h2o_ctx != NULL) -#define unless_h2o_rrdpush(x) if(!is_h2o_rrdpush(x)) -#endif - -int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_string, void *h2o_ctx); - -void receiver_state_free(struct receiver_state *rpt); -bool stop_streaming_receiver(RRDHOST *host, STREAM_HANDSHAKE reason); - -#endif //NETDATA_RECEIVER_H diff --git a/src/streaming/replication.c b/src/streaming/replication.c index 1f2c3140d1e2bc..588601ceba79d0 100644 --- a/src/streaming/replication.c +++ b/src/streaming/replication.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later +#include "stream-receiver-internals.h" +#include "stream-sender-internals.h" #include "replication.h" #include "Judy.h" @@ -31,6 +33,9 @@ #define ITERATIONS_IDLE_WITHOUT_PENDING_TO_RUN_SENDER_VERIFICATION 30 #define SECONDS_TO_RESET_POINT_IN_TIME 10 +#define MAX_REPLICATION_THREADS 32 +#define REQUESTS_AHEAD_PER_THREAD 1 // 1 = enable synchronous queries + static struct replication_query_statistics replication_queries = { .spinlock = NETDATA_SPINLOCK_INITIALIZER, .queries_started = 0, @@ -114,7 +119,8 @@ static struct replication_query *replication_query_prepare( time_t query_before, bool query_enable_streaming, time_t wall_clock_time, - STREAM_CAPABILITIES capabilities + STREAM_CAPABILITIES capabilities, + bool synchronous ) { size_t dimensions = rrdset_number_of_dimensions(st); struct replication_query *q = callocz(1, sizeof(struct replication_query) + dimensions * sizeof(struct replication_dimension)); @@ -184,8 +190,11 @@ static struct replication_query *replication_query_prepare( d->rda = dictionary_acquired_item_dup(rd_dfe.dict, rd_dfe.item); d->rd = rd; - storage_engine_query_init(q->backend, rd->tiers[0].smh, &d->handle, q->query.after, q->query.before, - q->query.locked_data_collection ? STORAGE_PRIORITY_HIGH : STORAGE_PRIORITY_LOW); + STORAGE_PRIORITY priority = q->query.locked_data_collection ? STORAGE_PRIORITY_HIGH : STORAGE_PRIORITY_LOW; + if(synchronous) priority = STORAGE_PRIORITY_SYNCHRONOUS; + + storage_engine_query_init(q->backend, rd->tiers[0].smh, &d->handle, + q->query.after, q->query.before, priority); d->enabled = true; d->skip = false; count++; @@ -223,7 +232,7 @@ static void replication_send_chart_collection_state(BUFFER *wb, RRDSET *st, STRE if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, rd->rrdpush.sender.dim_slot); + buffer_print_uint64_encoded(wb, integer_encoding, rd->stream.snd.dim_slot); } buffer_fast_strcat(wb, " '", 2); @@ -457,7 +466,7 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, q->st->rrdpush.sender.chart_slot); + buffer_print_uint64_encoded(wb, integer_encoding, q->st->stream.snd.chart_slot); } buffer_fast_strcat(wb, " '' ", 4); @@ -482,7 +491,7 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, d->rd->rrdpush.sender.dim_slot); + buffer_print_uint64_encoded(wb, integer_encoding, d->rd->stream.snd.dim_slot); } buffer_fast_strcat(wb, " \"", 2); @@ -546,7 +555,8 @@ static struct replication_query *replication_response_prepare( bool requested_enable_streaming, time_t requested_after, time_t requested_before, - STREAM_CAPABILITIES capabilities + STREAM_CAPABILITIES capabilities, + bool synchronous ) { time_t wall_clock_time = now_realtime_sec(); @@ -608,7 +618,7 @@ static struct replication_query *replication_response_prepare( db_first_entry, db_last_entry, requested_after, requested_before, requested_enable_streaming, query_after, query_before, query_enable_streaming, - wall_clock_time, capabilities); + wall_clock_time, capabilities, synchronous); } void replication_response_cancel_and_finalize(struct replication_query *q) { @@ -618,7 +628,7 @@ void replication_response_cancel_and_finalize(struct replication_query *q) { static bool sender_is_still_connected_for_this_request(struct replication_request *rq); -bool replication_response_execute_and_finalize(struct replication_query *q, size_t max_msg_size) { +bool replication_response_execute_and_finalize(struct replication_query *q, size_t max_msg_size, bool workers) { bool with_slots = (q->query.capabilities & STREAM_CAP_SLOTS) ? true : false; NUMBER_ENCODING integer_encoding = (q->query.capabilities & STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_DECIMAL; struct replication_request *rq = q->rq; @@ -628,13 +638,13 @@ bool replication_response_execute_and_finalize(struct replication_query *q, size // we might want to optimize this by filling a temporary buffer // and copying the result to the host's buffer in order to avoid // holding the host's buffer lock for too long - BUFFER *wb = sender_start(host->sender); + BUFFER *wb = sender_thread_buffer(host->sender); buffer_fast_strcat(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN, sizeof(PLUGINSD_KEYWORD_REPLAY_BEGIN) - 1); if(with_slots) { buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2); - buffer_print_uint64_encoded(wb, integer_encoding, q->st->rrdpush.sender.chart_slot); + buffer_print_uint64_encoded(wb, integer_encoding, q->st->stream.snd.chart_slot); } buffer_fast_strcat(wb, " '", 2); @@ -679,9 +689,9 @@ bool replication_response_execute_and_finalize(struct replication_query *q, size buffer_print_uint64_encoded(wb, integer_encoding, wall_clock_time); buffer_fast_strcat(wb, "\n", 1); - worker_is_busy(WORKER_JOB_BUFFER_COMMIT); + if(workers) worker_is_busy(WORKER_JOB_BUFFER_COMMIT); sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_REPLICATION); - worker_is_busy(WORKER_JOB_CLEANUP); + if(workers) worker_is_busy(WORKER_JOB_CLEANUP); if(enable_streaming) { if(sender_is_still_connected_for_this_request(rq)) { @@ -694,7 +704,7 @@ bool replication_response_execute_and_finalize(struct replication_query *q, size rrdhost_sender_replicating_charts_minus_one(st->rrdhost); if(!finished_with_gap) - st->rrdpush.sender.resync_time_s = 0; + st->stream.snd.resync_time_s = 0; #ifdef NETDATA_LOG_REPLICATION_REQUESTS internal_error(true, "STREAM_SENDER REPLAY: 'host:%s/chart:%s' streaming starts", @@ -892,7 +902,7 @@ bool replicate_chart_request(send_command callback, struct parser *parser, RRDHO r.gap.from = r.local_db.last_entry_t; else // we don't have any data, the gap is the max timeframe we are allowed to replicate - r.gap.from = r.local_db.wall_clock_time - r.host->rrdpush_seconds_to_replicate; + r.gap.from = r.local_db.wall_clock_time - r.host->stream.replication.period; } else { @@ -937,9 +947,9 @@ bool replicate_chart_request(send_command callback, struct parser *parser, RRDHO // ok, the child can fill the entire gap we have r.wanted.after = r.gap.from; - if(r.gap.to - r.wanted.after > host->rrdpush_replication_step) + if(r.gap.to - r.wanted.after > host->stream.replication.step) // the duration is too big for one request - let's take the first step - r.wanted.before = r.wanted.after + host->rrdpush_replication_step; + r.wanted.before = r.wanted.after + host->stream.replication.step; else // wow, we can do it in one request r.wanted.before = r.gap.to; @@ -956,7 +966,7 @@ bool replicate_chart_request(send_command callback, struct parser *parser, RRDHO } // the child should start streaming immediately if the wanted duration is small, or we reached the last entry of the child - r.wanted.start_streaming = (r.local_db.wall_clock_time - r.wanted.after <= host->rrdpush_replication_step || + r.wanted.start_streaming = (r.local_db.wall_clock_time - r.wanted.after <= host->stream.replication.step || r.wanted.before >= r.child_db.last_entry_t || r.wanted.before >= r.child_db.wall_clock_time || r.wanted.before >= r.local_db.wall_clock_time); @@ -1000,8 +1010,6 @@ struct replication_sort_entry { size_t unique_id; // used as a key to identify the sort entry - we never access its contents }; -#define MAX_REPLICATION_THREADS 20 // + 1 for the main thread - // the global variables for the replication thread static struct replication_thread { ARAL *aral_rse; @@ -1130,7 +1138,7 @@ static inline struct replication_sort_entry *replication_sort_entry_create(struc struct replication_sort_entry *rse = aral_mallocz(replication_globals.aral_rse); __atomic_add_fetch(&replication_globals.atomic.memory, sizeof(struct replication_sort_entry), __ATOMIC_RELAXED); - rrdpush_sender_pending_replication_requests_plus_one(rq->sender); + stream_sender_pending_replication_requests_plus_one(rq->sender); // copy the request rse->rq = rq; @@ -1150,7 +1158,7 @@ static void replication_sort_entry_destroy(struct replication_sort_entry *rse) { } static void replication_sort_entry_add(struct replication_request *rq) { - if(unlikely(rrdpush_sender_replication_buffer_full_get(rq->sender))) { + if(unlikely(stream_sender_replication_buffer_full_get(rq->sender))) { rq->indexed_in_judy = false; rq->not_indexed_buffer_full = true; rq->not_indexed_preprocessing = false; @@ -1218,7 +1226,7 @@ static bool replication_sort_entry_unlink_and_free_unsafe(struct replication_sor replication_globals.unsafe.removed++; replication_globals.unsafe.pending--; - rrdpush_sender_pending_replication_requests_minus_one(rse->rq->sender); + stream_sender_pending_replication_requests_minus_one(rse->rq->sender); rse->rq->indexed_in_judy = false; rse->rq->not_indexed_preprocessing = preprocessing; @@ -1359,7 +1367,7 @@ static void replication_request_react_callback(const DICTIONARY_ITEM *item __may replication_sort_entry_add(rq); // this request is about a unique chart for this sender - rrdpush_sender_replicating_charts_plus_one(s); + stream_sender_replicating_charts_plus_one(s); } static bool replication_request_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *sender_state) { @@ -1411,7 +1419,7 @@ static void replication_request_delete_callback(const DICTIONARY_ITEM *item __ma struct replication_request *rq = value; // this request is about a unique chart for this sender - rrdpush_sender_replicating_charts_minus_one(rq->sender); + stream_sender_replicating_charts_minus_one(rq->sender); if(rq->indexed_in_judy) replication_sort_entry_del(rq, false); @@ -1426,16 +1434,14 @@ static void replication_request_delete_callback(const DICTIONARY_ITEM *item __ma } static bool sender_is_still_connected_for_this_request(struct replication_request *rq) { - return rq->sender_last_flush_ut == rrdpush_sender_get_flush_time(rq->sender); + return rq->sender_last_flush_ut == stream_sender_get_flush_time(rq->sender); } static bool replication_execute_request(struct replication_request *rq, bool workers) { bool ret = false; if(!rq->st) { - if(likely(workers)) - worker_is_busy(WORKER_JOB_FIND_CHART); - + if(likely(workers)) worker_is_busy(WORKER_JOB_FIND_CHART); rq->st = rrdset_find(rq->sender->host, string2str(rq->chart_id)); } @@ -1447,24 +1453,21 @@ static bool replication_execute_request(struct replication_request *rq, bool wor } if(!rq->q) { - if(likely(workers)) - worker_is_busy(WORKER_JOB_PREPARE_QUERY); - + if(likely(workers)) worker_is_busy(WORKER_JOB_PREPARE_QUERY); rq->q = replication_response_prepare( rq->st, rq->start_streaming, rq->after, rq->before, - rq->sender->capabilities); + rq->sender->capabilities, true); } - if(likely(workers)) - worker_is_busy(WORKER_JOB_QUERYING); + if(likely(workers)) worker_is_busy(WORKER_JOB_QUERYING); // send the replication data rq->q->rq = rq; replication_response_execute_and_finalize( - rq->q, (size_t)((unsigned long long)rq->sender->host->sender->buffer->max_size * MAX_REPLICATION_MESSAGE_PERCENT_SENDER_BUFFER / 100ULL)); + rq->q, (size_t)((unsigned long long)rq->sender->host->sender->sbuf.cb->max_size * MAX_REPLICATION_MESSAGE_PERCENT_SENDER_BUFFER / 100ULL), workers); rq->q = NULL; @@ -1493,7 +1496,7 @@ void replication_add_request(struct sender_state *sender, const char *chart_id, .after = after, .before = before, .start_streaming = start_streaming, - .sender_last_flush_ut = rrdpush_sender_get_flush_time(sender), + .sender_last_flush_ut = stream_sender_get_flush_time(sender), .indexed_in_judy = false, .not_indexed_buffer_full = false, .not_indexed_preprocessing = false, @@ -1502,19 +1505,20 @@ void replication_add_request(struct sender_state *sender, const char *chart_id, if(!sender->replication.oldest_request_after_t || rq.after < sender->replication.oldest_request_after_t) sender->replication.oldest_request_after_t = rq.after; - if(start_streaming && rrdpush_sender_get_buffer_used_percent(sender) <= STREAMING_START_MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED) - replication_execute_request(&rq, false); - - else - dictionary_set(sender->replication.requests, chart_id, &rq, sizeof(struct replication_request)); +// if(start_streaming && rrdpush_sender_get_buffer_used_percent(sender) <= STREAMING_START_MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED) +// replication_execute_request(&rq, false); +// +// else + dictionary_set(sender->replication.requests, chart_id, &rq, sizeof(struct replication_request)); } void replication_sender_delete_pending_requests(struct sender_state *sender) { // allow the dictionary destructor to go faster on locks dictionary_flush(sender->replication.requests); + sender->replication.oldest_request_after_t = 0; } -void replication_init_sender(struct sender_state *sender) { +void replication_sender_init(struct sender_state *sender) { sender->replication.requests = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct replication_request)); @@ -1531,11 +1535,11 @@ void replication_cleanup_sender(struct sender_state *sender) { } void replication_recalculate_buffer_used_ratio_unsafe(struct sender_state *s) { - size_t available = cbuffer_available_size_unsafe(s->host->sender->buffer); - size_t percentage = (s->buffer->max_size - available) * 100 / s->buffer->max_size; + size_t available = cbuffer_available_size_unsafe(s->host->sender->sbuf.cb); + size_t percentage = (s->sbuf.cb->max_size - available) * 100 / s->sbuf.cb->max_size; - if(unlikely(percentage > MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED && !rrdpush_sender_replication_buffer_full_get(s))) { - rrdpush_sender_replication_buffer_full_set(s, true); + if(unlikely(percentage > MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED && !stream_sender_replication_buffer_full_get(s))) { + stream_sender_replication_buffer_full_set(s, true); struct replication_request *rq; dfe_start_read(s->replication.requests, rq) { @@ -1548,8 +1552,8 @@ void replication_recalculate_buffer_used_ratio_unsafe(struct sender_state *s) { replication_globals.unsafe.senders_full++; replication_recursive_unlock(); } - else if(unlikely(percentage < MIN_SENDER_BUFFER_PERCENTAGE_ALLOWED && rrdpush_sender_replication_buffer_full_get(s))) { - rrdpush_sender_replication_buffer_full_set(s, false); + else if(unlikely(percentage < MIN_SENDER_BUFFER_PERCENTAGE_ALLOWED && stream_sender_replication_buffer_full_get(s))) { + stream_sender_replication_buffer_full_set(s, false); struct replication_request *rq; dfe_start_read(s->replication.requests, rq) { @@ -1565,7 +1569,7 @@ void replication_recalculate_buffer_used_ratio_unsafe(struct sender_state *s) { replication_recursive_unlock(); } - rrdpush_sender_set_buffer_used_percent(s, percentage); + stream_sender_set_buffer_used_percent(s, percentage); } // ---------------------------------------------------------------------------- @@ -1574,11 +1578,11 @@ void replication_recalculate_buffer_used_ratio_unsafe(struct sender_state *s) { static size_t verify_host_charts_are_streaming_now(RRDHOST *host) { internal_error( host->sender && - !rrdpush_sender_pending_replication_requests(host->sender) && + !stream_sender_pending_replication_requests(host->sender) && dictionary_entries(host->sender->replication.requests) != 0, "REPLICATION SUMMARY: 'host:%s' reports %zu pending replication requests, but its chart replication index says there are %zu charts pending replication", rrdhost_hostname(host), - rrdpush_sender_pending_replication_requests(host->sender), + stream_sender_pending_replication_requests(host->sender), dictionary_entries(host->sender->replication.requests) ); @@ -1720,14 +1724,7 @@ static int replication_pipeline_execute_next(void) { struct replication_request *rq; if(unlikely(!rtp.rqs)) { - rtp.max_requests_ahead = (int)get_netdata_cpus() / 2; - - if(rtp.max_requests_ahead > libuv_worker_threads * 2) - rtp.max_requests_ahead = libuv_worker_threads * 2; - - if(rtp.max_requests_ahead < 2) - rtp.max_requests_ahead = 2; - + rtp.max_requests_ahead = REQUESTS_AHEAD_PER_THREAD; rtp.rqs = callocz(rtp.max_requests_ahead, sizeof(struct replication_request)); __atomic_add_fetch(&replication_buffers_allocated, rtp.max_requests_ahead * sizeof(struct replication_request), __ATOMIC_RELAXED); } @@ -1755,11 +1752,12 @@ static int replication_pipeline_execute_next(void) { if (rq->st && !rq->q) { worker_is_busy(WORKER_JOB_PREPARE_QUERY); rq->q = replication_response_prepare( - rq->st, - rq->start_streaming, - rq->after, - rq->before, - rq->sender->capabilities); + rq->st, + rq->start_streaming, + rq->after, + rq->before, + rq->sender->capabilities, + rtp.max_requests_ahead == 1); } rq->executed = false; @@ -1777,7 +1775,7 @@ static int replication_pipeline_execute_next(void) { if(rq->found) { internal_fatal(rq->executed, "REPLAY FATAL: query has already been executed!"); - if (rq->sender_last_flush_ut != rrdpush_sender_get_flush_time(rq->sender)) { + if (rq->sender_last_flush_ut != stream_sender_get_flush_time(rq->sender)) { // the sender has reconnected since this request was queued, // we can safely throw it away, since the parent will resend it replication_response_cancel_and_finalize(rq->q); @@ -1785,7 +1783,7 @@ static int replication_pipeline_execute_next(void) { rq->found = false; rq->q = NULL; } - else if (rrdpush_sender_replication_buffer_full_get(rq->sender)) { + else if (stream_sender_replication_buffer_full_get(rq->sender)) { // the sender buffer is full, so we can ignore this request, // it has already been marked as 'preprocessed' in the dictionary, // and the sender will put it back in when there is @@ -1840,7 +1838,7 @@ static void *replication_worker_thread(void *ptr __maybe_unused) { while (service_running(SERVICE_REPLICATION)) { if (unlikely(replication_pipeline_execute_next() == REQUEST_QUEUE_EMPTY)) { - sender_thread_buffer_free(); + sender_commit_thread_buffer_free(); worker_is_busy(WORKER_JOB_WAIT); worker_is_idle(); sleep_usec(1 * USEC_PER_SEC); @@ -1867,8 +1865,10 @@ static void replication_main_cleanup(void *pptr) { replication_globals.main_thread.threads_ptrs = NULL; __atomic_sub_fetch(&replication_buffers_allocated, threads * sizeof(ND_THREAD *), __ATOMIC_RELAXED); - aral_destroy(replication_globals.aral_rse); - replication_globals.aral_rse = NULL; + // we should not destroy aral on exit + // the sender threads may still be working on flushing senders replication requests + //aral_destroy(replication_globals.aral_rse); + //replication_globals.aral_rse = NULL; // custom code worker_unregister(); @@ -1877,20 +1877,34 @@ static void replication_main_cleanup(void *pptr) { } void replication_initialize(void) { - replication_globals.aral_rse = aral_create("rse", sizeof(struct replication_sort_entry), - 0, 65536, aral_by_size_statistics(), - NULL, NULL, false, false); + replication_globals.aral_rse = aral_by_size_acquire(sizeof(struct replication_sort_entry)); } -void *replication_thread_main(void *ptr __maybe_unused) { +void *replication_thread_main(void *ptr) { + CLEANUP_FUNCTION_REGISTER(replication_main_cleanup) cleanup_ptr = ptr; + replication_initialize_workers(true); - int threads = config_get_number(CONFIG_SECTION_DB, "replication threads", 1); - if(threads < 1 || threads > MAX_REPLICATION_THREADS) { + int nodes = (int)dictionary_entries(rrdhost_root_index); + int cpus = (int)get_netdata_cpus(); + int threads = MIN(cpus * 2 / 3, nodes / 5); + if (threads < 1) threads = 1; + else if (threads > MAX_REPLICATION_THREADS) threads = MAX_REPLICATION_THREADS; + + threads = config_get_number(CONFIG_SECTION_DB, "replication threads", threads); + if(threads < 1) { netdata_log_error("replication threads given %d is invalid, resetting to 1", threads); threads = 1; + config_set_number(CONFIG_SECTION_DB, "replication threads", threads); + } + else if(threads > MAX_REPLICATION_THREADS) { + netdata_log_error("replication threads given %d is invalid, resetting to %d", threads, (int)MAX_REPLICATION_THREADS); + threads = MAX_REPLICATION_THREADS; + config_set_number(CONFIG_SECTION_DB, "replication threads", threads); } + netdata_log_info("replication threads set to %d (cpu cores = %d, nodes = %d)", threads, cpus, nodes); + if(--threads) { replication_globals.main_thread.threads = threads; replication_globals.main_thread.threads_ptrs = mallocz(threads * sizeof(ND_THREAD *)); @@ -1906,8 +1920,6 @@ void *replication_thread_main(void *ptr __maybe_unused) { } } - CLEANUP_FUNCTION_REGISTER(replication_main_cleanup) cleanup_ptr = ptr; - // start from 100% completed worker_set_metric(WORKER_JOB_CUSTOM_METRIC_COMPLETION, 100.0); @@ -1982,7 +1994,6 @@ void *replication_thread_main(void *ptr __maybe_unused) { } if(unlikely(replication_pipeline_execute_next() == REQUEST_QUEUE_EMPTY)) { - worker_is_busy(WORKER_JOB_WAIT); replication_recursive_lock(); @@ -1992,7 +2003,7 @@ void *replication_thread_main(void *ptr __maybe_unused) { if(slow) { // no work to be done, wait for a request to come in timeout = 1000 * USEC_PER_MS; - sender_thread_buffer_free(); + sender_commit_thread_buffer_free(); } else if(replication_globals.unsafe.pending > 0) { diff --git a/src/streaming/replication.h b/src/streaming/replication.h index 27baeaf35c3393..592e6c19e65bb5 100644 --- a/src/streaming/replication.h +++ b/src/streaming/replication.h @@ -26,7 +26,7 @@ bool replicate_chart_request(send_command callback, struct parser *parser, time_t child_first_entry, time_t child_last_entry, time_t child_wall_clock_time, time_t response_first_start_time, time_t response_last_end_time); -void replication_init_sender(struct sender_state *sender); +void replication_sender_init(struct sender_state *sender); void replication_cleanup_sender(struct sender_state *sender); void replication_sender_delete_pending_requests(struct sender_state *sender); void replication_add_request(struct sender_state *sender, const char *chart_id, time_t after, time_t before, bool start_streaming); diff --git a/src/streaming/rrdhost-status.c b/src/streaming/rrdhost-status.c index 31b6a27e8de6eb..c830eb1d3b4abe 100644 --- a/src/streaming/rrdhost-status.c +++ b/src/streaming/rrdhost-status.c @@ -1,135 +1,82 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "rrdhost-status.h" - -const char *rrdhost_db_status_to_string(RRDHOST_DB_STATUS status) { - switch(status) { - default: - case RRDHOST_DB_STATUS_INITIALIZING: - return "initializing"; - - case RRDHOST_DB_STATUS_QUERYABLE: - return "online"; - } -} - -const char *rrdhost_db_liveness_to_string(RRDHOST_DB_LIVENESS status) { - switch(status) { - default: - case RRDHOST_DB_LIVENESS_STALE: - return "stale"; - - case RRDHOST_DB_LIVENESS_LIVE: - return "live"; - } -} - -const char *rrdhost_ingest_status_to_string(RRDHOST_INGEST_STATUS status) { - switch(status) { - case RRDHOST_INGEST_STATUS_ARCHIVED: - return "archived"; - - case RRDHOST_INGEST_STATUS_INITIALIZING: - return "initializing"; - - case RRDHOST_INGEST_STATUS_REPLICATING: - return "replicating"; - - case RRDHOST_INGEST_STATUS_ONLINE: - return "online"; - - default: - case RRDHOST_INGEST_STATUS_OFFLINE: - return "offline"; - } -} - -const char *rrdhost_ingest_type_to_string(RRDHOST_INGEST_TYPE type) { - switch(type) { - case RRDHOST_INGEST_TYPE_LOCALHOST: - return "localhost"; - - case RRDHOST_INGEST_TYPE_VIRTUAL: - return "virtual"; - - case RRDHOST_INGEST_TYPE_CHILD: - return "child"; - - default: - case RRDHOST_INGEST_TYPE_ARCHIVED: - return "archived"; - } -} - -const char *rrdhost_streaming_status_to_string(RRDHOST_STREAMING_STATUS status) { - switch(status) { - case RRDHOST_STREAM_STATUS_DISABLED: - return "disabled"; - - case RRDHOST_STREAM_STATUS_REPLICATING: - return "replicating"; - - case RRDHOST_STREAM_STATUS_ONLINE: - return "online"; - - default: - case RRDHOST_STREAM_STATUS_OFFLINE: - return "offline"; - } -} - -const char *rrdhost_ml_status_to_string(RRDHOST_ML_STATUS status) { - switch(status) { - case RRDHOST_ML_STATUS_RUNNING: - return "online"; - - case RRDHOST_ML_STATUS_OFFLINE: - return "offline"; - - default: - case RRDHOST_ML_STATUS_DISABLED: - return "disabled"; - } -} - -const char *rrdhost_ml_type_to_string(RRDHOST_ML_TYPE type) { - switch(type) { - case RRDHOST_ML_TYPE_SELF: - return "self"; - - case RRDHOST_ML_TYPE_RECEIVED: - return "received"; - - default: - case RRDHOST_ML_TYPE_DISABLED: - return "disabled"; - } -} - -const char *rrdhost_health_status_to_string(RRDHOST_HEALTH_STATUS status) { - switch(status) { - default: - case RRDHOST_HEALTH_STATUS_DISABLED: - return "disabled"; - - case RRDHOST_HEALTH_STATUS_INITIALIZING: - return "initializing"; - - case RRDHOST_HEALTH_STATUS_RUNNING: - return "online"; - } -} - -const char *rrdhost_dyncfg_status_to_string(RRDHOST_DYNCFG_STATUS status) { - switch(status) { - default: - case RRDHOST_DYNCFG_STATUS_UNAVAILABLE: - return "unavailable"; - - case RRDHOST_DYNCFG_STATUS_AVAILABLE: - return "online"; - } -} +#include "stream-receiver-internals.h" +#include "stream-sender-internals.h" + +ENUM_STR_MAP_DEFINE(RRDHOST_DB_STATUS) = { + { RRDHOST_DB_STATUS_INITIALIZING, "initializing" }, + { RRDHOST_DB_STATUS_QUERYABLE, "online" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_DB_LIVENESS) = { + { RRDHOST_DB_LIVENESS_STALE, "stale" }, + { RRDHOST_DB_LIVENESS_LIVE, "live" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_INGEST_STATUS) = { + { RRDHOST_INGEST_STATUS_ARCHIVED, "archived" }, + { RRDHOST_INGEST_STATUS_INITIALIZING, "initializing" }, + { RRDHOST_INGEST_STATUS_REPLICATING, "replicating" }, + { RRDHOST_INGEST_STATUS_ONLINE, "online" }, + { RRDHOST_INGEST_STATUS_OFFLINE, "offline" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_INGEST_TYPE) = { + { RRDHOST_INGEST_TYPE_LOCALHOST, "localhost" }, + { RRDHOST_INGEST_TYPE_VIRTUAL, "virtual" }, + { RRDHOST_INGEST_TYPE_CHILD, "child" }, + { RRDHOST_INGEST_TYPE_ARCHIVED, "archived" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_STREAMING_STATUS) = { + { RRDHOST_STREAM_STATUS_DISABLED, "disabled" }, + { RRDHOST_STREAM_STATUS_REPLICATING, "replicating" }, + { RRDHOST_STREAM_STATUS_ONLINE, "online" }, + { RRDHOST_STREAM_STATUS_OFFLINE, "offline" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_ML_STATUS) = { + { RRDHOST_ML_STATUS_DISABLED, "disabled" }, + { RRDHOST_ML_STATUS_OFFLINE, "offline" }, + { RRDHOST_ML_STATUS_RUNNING, "online" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_ML_TYPE) = { + { RRDHOST_ML_TYPE_DISABLED, "disabled" }, + { RRDHOST_ML_TYPE_SELF, "self" }, + { RRDHOST_ML_TYPE_RECEIVED, "received" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_HEALTH_STATUS) = { + { RRDHOST_HEALTH_STATUS_DISABLED, "disabled" }, + { RRDHOST_HEALTH_STATUS_INITIALIZING, "initializing" }, + { RRDHOST_HEALTH_STATUS_RUNNING, "online" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_MAP_DEFINE(RRDHOST_DYNCFG_STATUS) = { + { RRDHOST_DYNCFG_STATUS_UNAVAILABLE, "unavailable" }, + { RRDHOST_DYNCFG_STATUS_AVAILABLE, "online" }, + { 0, NULL } // Sentinel +}; + +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_DB_STATUS, RRDHOST_DB_STATUS_INITIALIZING, "initializing"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_DB_LIVENESS, RRDHOST_DB_LIVENESS_STALE, "stale"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_INGEST_STATUS, RRDHOST_INGEST_STATUS_OFFLINE, "offline"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_INGEST_TYPE, RRDHOST_INGEST_TYPE_ARCHIVED, "archived"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_STREAMING_STATUS, RRDHOST_STREAM_STATUS_OFFLINE, "offline"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_ML_STATUS, RRDHOST_ML_STATUS_DISABLED, "disabled"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_ML_TYPE, RRDHOST_ML_TYPE_DISABLED, "disabled"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_HEALTH_STATUS, RRDHOST_HEALTH_STATUS_DISABLED, "disabled"); +ENUM_STR_DEFINE_FUNCTIONS(RRDHOST_DYNCFG_STATUS, RRDHOST_DYNCFG_STATUS_UNAVAILABLE, "unavailable"); static NETDATA_DOUBLE rrdhost_sender_replication_completion_unsafe(RRDHOST *host, time_t now, size_t *instances) { size_t charts = rrdhost_sender_replicating_charts(host); @@ -179,23 +126,23 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) { // --- ingest --- - s->ingest.since = MAX(host->child_connect_time, host->child_disconnected_time); - s->ingest.reason = (online) ? STREAM_HANDSHAKE_NEVER : host->rrdpush_last_receiver_exit_reason; + s->ingest.since = MAX(host->stream.rcv.status.last_connected, host->stream.rcv.status.last_disconnected); + s->ingest.reason = (online) ? STREAM_HANDSHAKE_NEVER : host->stream.rcv.status.exit_reason; - spinlock_lock(&host->receiver_lock); - s->ingest.hops = (host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1); + rrdhost_receiver_lock(host); + s->ingest.hops = (int16_t)(host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1); bool has_receiver = false; - if (host->receiver && !rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED)) { + if (host->receiver && !rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED)) { has_receiver = true; s->ingest.replication.instances = rrdhost_receiver_replicating_charts(host); - s->ingest.replication.completion = host->rrdpush_receiver_replication_percent; + s->ingest.replication.completion = host->stream.rcv.status.replication.percent; s->ingest.replication.in_progress = s->ingest.replication.instances > 0; s->ingest.capabilities = host->receiver->capabilities; - s->ingest.peers = socket_peers(host->receiver->fd); - s->ingest.ssl = SSL_connection(&host->receiver->ssl); + s->ingest.peers = nd_sock_socket_peers(&host->receiver->sock); + s->ingest.ssl = nd_sock_is_ssl(&host->receiver->sock); } - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); if (online) { if(s->db.status == RRDHOST_DB_STATUS_INITIALIZING) @@ -235,7 +182,7 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) { else s->ingest.type = RRDHOST_INGEST_TYPE_ARCHIVED; - s->ingest.id = host->rrdpush_receiver_connection_counter; + s->ingest.id = host->stream.rcv.status.connections; if(!s->ingest.since) s->ingest.since = netdata_start_time; @@ -249,21 +196,21 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) { if (!host->sender) { s->stream.status = RRDHOST_STREAM_STATUS_DISABLED; - s->stream.hops = s->ingest.hops + 1; + s->stream.hops = (int16_t)(s->ingest.hops + 1); } else { - sender_lock(host->sender); + stream_sender_lock(host->sender); s->stream.since = host->sender->last_state_since_t; - s->stream.peers = socket_peers(host->sender->rrdpush_sender_socket); - s->stream.ssl = SSL_connection(&host->sender->ssl); + s->stream.peers = nd_sock_socket_peers(&host->sender->sock); + s->stream.ssl = nd_sock_is_ssl(&host->sender->sock); memcpy(s->stream.sent_bytes_on_this_connection_per_type, - host->sender->sent_bytes_on_this_connection_per_type, + host->sender->thread.bytes_sent_by_type, MIN(sizeof(s->stream.sent_bytes_on_this_connection_per_type), - sizeof(host->sender->sent_bytes_on_this_connection_per_type))); + sizeof(host->sender->thread.bytes_sent_by_type))); - if (rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED)) { + if (rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_CONNECTED)) { s->stream.hops = host->sender->hops; s->stream.reason = STREAM_HANDSHAKE_NEVER; s->stream.capabilities = host->sender->capabilities; @@ -280,14 +227,14 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) { } else { s->stream.status = RRDHOST_STREAM_STATUS_OFFLINE; - s->stream.hops = s->ingest.hops + 1; + s->stream.hops = (int16_t)(s->ingest.hops + 1); s->stream.reason = host->sender->exit.reason; } - sender_unlock(host->sender); + stream_sender_unlock(host->sender); } - s->stream.id = host->rrdpush_sender_connection_counter; + s->stream.id = host->stream.snd.status.connections; if(!s->stream.since) s->stream.since = netdata_start_time; @@ -295,17 +242,16 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) { // --- ml --- if(ml_host_get_host_status(host, &s->ml.metrics)) { - s->ml.type = RRDHOST_ML_TYPE_SELF; + if(stream_has_capability(&s->ingest, STREAM_CAP_ML_MODELS)) + s->ml.type = RRDHOST_ML_TYPE_RECEIVED; + else + s->ml.type = RRDHOST_ML_TYPE_SELF; if(s->ingest.status == RRDHOST_INGEST_STATUS_OFFLINE || s->ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED) s->ml.status = RRDHOST_ML_STATUS_OFFLINE; else s->ml.status = RRDHOST_ML_STATUS_RUNNING; } - else if(stream_has_capability(&s->ingest, STREAM_CAP_DATA_WITH_ML)) { - s->ml.type = RRDHOST_ML_TYPE_RECEIVED; - s->ml.status = RRDHOST_ML_STATUS_RUNNING; - } else { // does not receive ML, does not run ML s->ml.type = RRDHOST_ML_TYPE_DISABLED; @@ -314,45 +260,44 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) { // --- health --- - if(host->health.health_enabled) { + if(host->health.enabled) { if(flags & RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION) s->health.status = RRDHOST_HEALTH_STATUS_INITIALIZING; - else { + else s->health.status = RRDHOST_HEALTH_STATUS_RUNNING; - RRDCALC *rc; - foreach_rrdcalc_in_rrdhost_read(host, rc) { - if (unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) - continue; + RRDCALC *rc; + foreach_rrdcalc_in_rrdhost_read(host, rc) { + if (unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) + continue; - switch (rc->status) { - default: - case RRDCALC_STATUS_REMOVED: - break; + switch (rc->status) { + default: + case RRDCALC_STATUS_REMOVED: + break; - case RRDCALC_STATUS_CLEAR: - s->health.alerts.clear++; - break; + case RRDCALC_STATUS_CLEAR: + s->health.alerts.clear++; + break; - case RRDCALC_STATUS_WARNING: - s->health.alerts.warning++; - break; + case RRDCALC_STATUS_WARNING: + s->health.alerts.warning++; + break; - case RRDCALC_STATUS_CRITICAL: - s->health.alerts.critical++; - break; + case RRDCALC_STATUS_CRITICAL: + s->health.alerts.critical++; + break; - case RRDCALC_STATUS_UNDEFINED: - s->health.alerts.undefined++; - break; + case RRDCALC_STATUS_UNDEFINED: + s->health.alerts.undefined++; + break; - case RRDCALC_STATUS_UNINITIALIZED: - s->health.alerts.uninitialized++; - break; - } + case RRDCALC_STATUS_UNINITIALIZED: + s->health.alerts.uninitialized++; + break; } - foreach_rrdcalc_in_rrdhost_done(rc); } + foreach_rrdcalc_in_rrdhost_done(rc); } else s->health.status = RRDHOST_HEALTH_STATUS_DISABLED; diff --git a/src/streaming/rrdhost-status.h b/src/streaming/rrdhost-status.h index 6c9a920a90de81..fe873fed7274b5 100644 --- a/src/streaming/rrdhost-status.h +++ b/src/streaming/rrdhost-status.h @@ -4,24 +4,17 @@ #define NETDATA_RRDHOST_STATUS_H #include "libnetdata/libnetdata.h" -#include "stream-handshake.h" -#include "stream-capabilities.h" -#include "database/rrd.h" typedef enum __attribute__((packed)) { RRDHOST_DB_STATUS_INITIALIZING = 0, RRDHOST_DB_STATUS_QUERYABLE, } RRDHOST_DB_STATUS; -const char *rrdhost_db_status_to_string(RRDHOST_DB_STATUS status); - typedef enum __attribute__((packed)) { RRDHOST_DB_LIVENESS_STALE = 0, RRDHOST_DB_LIVENESS_LIVE, } RRDHOST_DB_LIVENESS; -const char *rrdhost_db_liveness_to_string(RRDHOST_DB_LIVENESS status); - typedef enum __attribute__((packed)) { RRDHOST_INGEST_STATUS_ARCHIVED = 0, RRDHOST_INGEST_STATUS_INITIALIZING, @@ -30,8 +23,6 @@ typedef enum __attribute__((packed)) { RRDHOST_INGEST_STATUS_OFFLINE, } RRDHOST_INGEST_STATUS; -const char *rrdhost_ingest_status_to_string(RRDHOST_INGEST_STATUS status); - typedef enum __attribute__((packed)) { RRDHOST_INGEST_TYPE_LOCALHOST = 0, RRDHOST_INGEST_TYPE_VIRTUAL, @@ -39,8 +30,6 @@ typedef enum __attribute__((packed)) { RRDHOST_INGEST_TYPE_ARCHIVED, } RRDHOST_INGEST_TYPE; -const char *rrdhost_ingest_type_to_string(RRDHOST_INGEST_TYPE type); - typedef enum __attribute__((packed)) { RRDHOST_STREAM_STATUS_DISABLED = 0, RRDHOST_STREAM_STATUS_REPLICATING, @@ -48,40 +37,54 @@ typedef enum __attribute__((packed)) { RRDHOST_STREAM_STATUS_OFFLINE, } RRDHOST_STREAMING_STATUS; -const char *rrdhost_streaming_status_to_string(RRDHOST_STREAMING_STATUS status); - typedef enum __attribute__((packed)) { RRDHOST_ML_STATUS_DISABLED = 0, RRDHOST_ML_STATUS_OFFLINE, RRDHOST_ML_STATUS_RUNNING, } RRDHOST_ML_STATUS; -const char *rrdhost_ml_status_to_string(RRDHOST_ML_STATUS status); - typedef enum __attribute__((packed)) { RRDHOST_ML_TYPE_DISABLED = 0, RRDHOST_ML_TYPE_SELF, RRDHOST_ML_TYPE_RECEIVED, } RRDHOST_ML_TYPE; -const char *rrdhost_ml_type_to_string(RRDHOST_ML_TYPE type); - typedef enum __attribute__((packed)) { RRDHOST_HEALTH_STATUS_DISABLED = 0, RRDHOST_HEALTH_STATUS_INITIALIZING, RRDHOST_HEALTH_STATUS_RUNNING, } RRDHOST_HEALTH_STATUS; -const char *rrdhost_health_status_to_string(RRDHOST_HEALTH_STATUS status); - typedef enum __attribute__((packed)) { RRDHOST_DYNCFG_STATUS_UNAVAILABLE = 0, RRDHOST_DYNCFG_STATUS_AVAILABLE, } RRDHOST_DYNCFG_STATUS; -const char *rrdhost_dyncfg_status_to_string(RRDHOST_DYNCFG_STATUS status); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_DB_STATUS); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_DB_LIVENESS); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_INGEST_STATUS); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_INGEST_TYPE); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_STREAMING_STATUS); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_ML_STATUS); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_ML_TYPE); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_HEALTH_STATUS); +ENUM_STR_DEFINE_FUNCTIONS_EXTERN(RRDHOST_DYNCFG_STATUS); + +#define rrdhost_db_status_to_string(status) RRDHOST_DB_STATUS_2str(status) +#define rrdhost_db_liveness_to_string(status) RRDHOST_DB_LIVENESS_2str(status) +#define rrdhost_ingest_status_to_string(status) RRDHOST_INGEST_STATUS_2str(status) +#define rrdhost_ingest_type_to_string(type) RRDHOST_INGEST_TYPE_2str(type) +#define rrdhost_streaming_status_to_string(status) RRDHOST_STREAMING_STATUS_2str(status) +#define rrdhost_ml_status_to_string(status) RRDHOST_ML_STATUS_2str(status) +#define rrdhost_ml_type_to_string(type) RRDHOST_ML_TYPE_2str(type) +#define rrdhost_health_status_to_string(status) RRDHOST_HEALTH_STATUS_2str(status) +#define rrdhost_dyncfg_status_to_string(status) RRDHOST_DYNCFG_STATUS_2str(status) + +#include "stream-handshake.h" +#include "stream-capabilities.h" +#include "database/rrd.h" -typedef struct { +typedef struct rrdhost_status { RRDHOST *host; time_t now; @@ -107,7 +110,7 @@ typedef struct { } ml; struct { - size_t hops; + int16_t hops; RRDHOST_INGEST_TYPE type; RRDHOST_INGEST_STATUS status; SOCKET_PEERS peers; @@ -131,7 +134,7 @@ typedef struct { } ingest; struct { - size_t hops; + int16_t hops; RRDHOST_STREAMING_STATUS status; SOCKET_PEERS peers; bool ssl; diff --git a/src/streaming/rrdpush.h b/src/streaming/rrdpush.h deleted file mode 100644 index 55d0c296c6af4a..00000000000000 --- a/src/streaming/rrdpush.h +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_RRDPUSH_H -#define NETDATA_RRDPUSH_H 1 - -#include "stream-handshake.h" -#include "stream-capabilities.h" -#include "stream-conf.h" -#include "stream-compression/compression.h" - -#include "sender.h" -#include "receiver.h" - -#include "rrdhost-status.h" -#include "protocol/commands.h" -#include "stream-path.h" - -#endif //NETDATA_RRDPUSH_H diff --git a/src/streaming/sender-commit.c b/src/streaming/sender-commit.c deleted file mode 100644 index 6ff7cb2ba766ed..00000000000000 --- a/src/streaming/sender-commit.c +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "sender-internals.h" - -static __thread BUFFER *sender_thread_buffer = NULL; -static __thread bool sender_thread_buffer_used = false; -static __thread time_t sender_thread_buffer_last_reset_s = 0; - -void sender_thread_buffer_free(void) { - buffer_free(sender_thread_buffer); - sender_thread_buffer = NULL; - sender_thread_buffer_used = false; -} - -// Collector thread starting a transmission -BUFFER *sender_start(struct sender_state *s) { - if(unlikely(sender_thread_buffer_used)) - fatal("STREAMING: thread buffer is used multiple times concurrently."); - - if(unlikely(rrdpush_sender_last_buffer_recreate_get(s) > sender_thread_buffer_last_reset_s)) { - if(unlikely(sender_thread_buffer && sender_thread_buffer->size > THREAD_BUFFER_INITIAL_SIZE)) { - buffer_free(sender_thread_buffer); - sender_thread_buffer = NULL; - } - } - - if(unlikely(!sender_thread_buffer)) { - sender_thread_buffer = buffer_create(THREAD_BUFFER_INITIAL_SIZE, &netdata_buffers_statistics.buffers_streaming); - sender_thread_buffer_last_reset_s = rrdpush_sender_last_buffer_recreate_get(s); - } - - sender_thread_buffer_used = true; - buffer_flush(sender_thread_buffer); - return sender_thread_buffer; -} - -#define SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE 3 - -// Collector thread finishing a transmission -void sender_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type) { - - if(unlikely(wb != sender_thread_buffer)) - fatal("STREAMING: sender is trying to commit a buffer that is not this thread's buffer."); - - if(unlikely(!sender_thread_buffer_used)) - fatal("STREAMING: sender is committing a buffer twice."); - - sender_thread_buffer_used = false; - - char *src = (char *)buffer_tostring(wb); - size_t src_len = buffer_strlen(wb); - - if(unlikely(!src || !src_len)) - return; - - sender_lock(s); - -#ifdef NETDATA_LOG_STREAM_SENDER - if(type == STREAM_TRAFFIC_TYPE_METADATA) { - if(!s->stream_log_fp) { - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "/tmp/stream-sender-%s.txt", s->host ? rrdhost_hostname(s->host) : "unknown"); - - s->stream_log_fp = fopen(filename, "w"); - } - - fprintf(s->stream_log_fp, "\n--- SEND MESSAGE START: %s ----\n" - "%s" - "--- SEND MESSAGE END ----------------------------------------\n" - , rrdhost_hostname(s->host), src - ); - } -#endif - - if(unlikely(s->buffer->max_size < (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE)) { - netdata_log_info("STREAM %s [send to %s]: max buffer size of %zu is too small for a data message of size %zu. Increasing the max buffer size to %d times the max data message size.", - rrdhost_hostname(s->host), s->connected_to, s->buffer->max_size, buffer_strlen(wb) + 1, SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE); - - s->buffer->max_size = (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE; - } - - if (s->compressor.initialized) { - while(src_len) { - size_t size_to_compress = src_len; - - if(unlikely(size_to_compress > COMPRESSION_MAX_MSG_SIZE)) { - if (stream_has_capability(s, STREAM_CAP_BINARY)) - size_to_compress = COMPRESSION_MAX_MSG_SIZE; - else { - if (size_to_compress > COMPRESSION_MAX_MSG_SIZE) { - // we need to find the last newline - // so that the decompressor will have a whole line to work with - - const char *t = &src[COMPRESSION_MAX_MSG_SIZE]; - while (--t >= src) - if (unlikely(*t == '\n')) - break; - - if (t <= src) { - size_to_compress = COMPRESSION_MAX_MSG_SIZE; - } else - size_to_compress = t - src + 1; - } - } - } - - const char *dst; - size_t dst_len = rrdpush_compress(&s->compressor, src, size_to_compress, &dst); - if (!dst_len) { - netdata_log_error("STREAM %s [send to %s]: COMPRESSION failed. Resetting compressor and re-trying", - rrdhost_hostname(s->host), s->connected_to); - - rrdpush_compression_initialize(s); - dst_len = rrdpush_compress(&s->compressor, src, size_to_compress, &dst); - if(!dst_len) { - netdata_log_error("STREAM %s [send to %s]: COMPRESSION failed again. Deactivating compression", - rrdhost_hostname(s->host), s->connected_to); - - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_NO_COMPRESSION); - rrdpush_compression_deactivate(s); - rrdpush_sender_thread_close_socket(s); - sender_unlock(s); - return; - } - } - - rrdpush_signature_t signature = rrdpush_compress_encode_signature(dst_len); - -#ifdef NETDATA_INTERNAL_CHECKS - // check if reversing the signature provides the same length - size_t decoded_dst_len = rrdpush_decompress_decode_signature((const char *)&signature, sizeof(signature)); - if(decoded_dst_len != dst_len) - fatal("RRDPUSH COMPRESSION: invalid signature, original payload %zu bytes, " - "compressed payload length %zu bytes, but signature says payload is %zu bytes", - size_to_compress, dst_len, decoded_dst_len); -#endif - - if(cbuffer_add_unsafe(s->buffer, (const char *)&signature, sizeof(signature))) - s->flags |= SENDER_FLAG_OVERFLOW; - else { - if(cbuffer_add_unsafe(s->buffer, dst, dst_len)) - s->flags |= SENDER_FLAG_OVERFLOW; - else - s->sent_bytes_on_this_connection_per_type[type] += dst_len + sizeof(signature); - } - - src = src + size_to_compress; - src_len -= size_to_compress; - } - } - else if(cbuffer_add_unsafe(s->buffer, src, src_len)) - s->flags |= SENDER_FLAG_OVERFLOW; - else - s->sent_bytes_on_this_connection_per_type[type] += src_len; - - replication_recalculate_buffer_used_ratio_unsafe(s); - - bool signal_sender = false; - if(!rrdpush_sender_pipe_has_pending_data(s)) { - rrdpush_sender_pipe_set_pending_data(s); - signal_sender = true; - } - - sender_unlock(s); - - if(signal_sender && (!stream_has_capability(s, STREAM_CAP_INTERPOLATED) || type != STREAM_TRAFFIC_TYPE_DATA)) - rrdpush_signal_sender_to_wake_up(s); -} diff --git a/src/streaming/sender-destinations.c b/src/streaming/sender-destinations.c deleted file mode 100644 index 5e67ca0397f4d6..00000000000000 --- a/src/streaming/sender-destinations.c +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "sender-internals.h" - -void rrdpush_reset_destinations_postpone_time(RRDHOST *host) { - uint32_t wait = (host->sender) ? host->sender->reconnect_delay : 5; - time_t now = now_realtime_sec(); - for (struct rrdpush_destinations *d = host->destinations; d; d = d->next) - d->postpone_reconnection_until = now + wait; -} - -void rrdpush_sender_ssl_init(RRDHOST *host) { - static SPINLOCK sp = NETDATA_SPINLOCK_INITIALIZER; - spinlock_lock(&sp); - - if(netdata_ssl_streaming_sender_ctx || !host) { - spinlock_unlock(&sp); - return; - } - - for(struct rrdpush_destinations *d = host->destinations; d ; d = d->next) { - if (d->ssl) { - // we need to initialize SSL - - netdata_ssl_initialize_ctx(NETDATA_SSL_STREAMING_SENDER_CTX); - ssl_security_location_for_context(netdata_ssl_streaming_sender_ctx, stream_conf_ssl_ca_file, stream_conf_ssl_ca_path); - - // stop the loop - break; - } - } - - spinlock_unlock(&sp); -} - -int connect_to_one_of_destinations( - RRDHOST *host, - int default_port, - struct timeval *timeout, - size_t *reconnects_counter, - char *connected_to, - size_t connected_to_size, - struct rrdpush_destinations **destination) -{ - int sock = -1; - - for (struct rrdpush_destinations *d = host->destinations; d; d = d->next) { - time_t now = now_realtime_sec(); - - if(nd_thread_signaled_to_cancel()) - return -1; - - if(d->postpone_reconnection_until > now) - continue; - - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "STREAM %s: connecting to '%s' (default port: %d)...", - rrdhost_hostname(host), string2str(d->destination), default_port); - - if (reconnects_counter) - *reconnects_counter += 1; - - d->since = now; - d->attempts++; - sock = connect_to_this(string2str(d->destination), default_port, timeout); - - if (sock != -1) { - if (connected_to && connected_to_size) - strncpyz(connected_to, string2str(d->destination), connected_to_size); - - *destination = d; - - // move the current item to the end of the list - // without this, this destination will break the loop again and again - // not advancing the destinations to find one that may work - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->destinations, d, prev, next); - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(host->destinations, d, prev, next); - - break; - } - } - - return sock; -} - -struct destinations_init_tmp { - RRDHOST *host; - struct rrdpush_destinations *list; - int count; -}; - -static bool destinations_init_add_one(char *entry, void *data) { - struct destinations_init_tmp *t = data; - - struct rrdpush_destinations *d = callocz(1, sizeof(struct rrdpush_destinations)); - char *colon_ssl = strstr(entry, ":SSL"); - if(colon_ssl) { - *colon_ssl = '\0'; - d->ssl = true; - } - else - d->ssl = false; - - d->destination = string_strdupz(entry); - - __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(struct rrdpush_destinations), __ATOMIC_RELAXED); - - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(t->list, d, prev, next); - - t->count++; - nd_log_daemon(NDLP_INFO, "STREAM: added streaming destination No %d: '%s' to host '%s'", t->count, string2str(d->destination), rrdhost_hostname(t->host)); - - return false; // we return false, so that we will get all defined destinations -} - -void rrdpush_destinations_init(RRDHOST *host) { - if(!host->rrdpush.send.destination) return; - - rrdpush_destinations_free(host); - - struct destinations_init_tmp t = { - .host = host, - .list = NULL, - .count = 0, - }; - - foreach_entry_in_connection_string(host->rrdpush.send.destination, destinations_init_add_one, &t); - - host->destinations = t.list; -} - -void rrdpush_destinations_free(RRDHOST *host) { - while (host->destinations) { - struct rrdpush_destinations *tmp = host->destinations; - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->destinations, tmp, prev, next); - string_freez(tmp->destination); - freez(tmp); - __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(struct rrdpush_destinations), __ATOMIC_RELAXED); - } - - host->destinations = NULL; -} - diff --git a/src/streaming/sender-destinations.h b/src/streaming/sender-destinations.h deleted file mode 100644 index e7c72cef7b3dde..00000000000000 --- a/src/streaming/sender-destinations.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_SENDER_DESTINATIONS_H -#define NETDATA_SENDER_DESTINATIONS_H - -#include "libnetdata/libnetdata.h" -#include "stream-handshake.h" -#include "database/rrd.h" - -struct rrdpush_destinations { - STRING *destination; - bool ssl; - uint32_t attempts; - time_t since; - time_t postpone_reconnection_until; - STREAM_HANDSHAKE reason; - - struct rrdpush_destinations *prev; - struct rrdpush_destinations *next; -}; - -void rrdpush_sender_ssl_init(RRDHOST *host); - -void rrdpush_reset_destinations_postpone_time(RRDHOST *host); - -void rrdpush_destinations_init(RRDHOST *host); -void rrdpush_destinations_free(RRDHOST *host); - -int connect_to_one_of_destinations( - RRDHOST *host, - int default_port, - struct timeval *timeout, - size_t *reconnects_counter, - char *connected_to, - size_t connected_to_size, - struct rrdpush_destinations **destination); - -#endif //NETDATA_SENDER_DESTINATIONS_H diff --git a/src/streaming/sender-internals.h b/src/streaming/sender-internals.h deleted file mode 100644 index 574369afa9c28b..00000000000000 --- a/src/streaming/sender-internals.h +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_SENDER_INTERNALS_H -#define NETDATA_SENDER_INTERNALS_H - -#include "rrdpush.h" -#include "h2o-common.h" -#include "aclk/https_client.h" - -#define WORKER_SENDER_JOB_CONNECT 0 -#define WORKER_SENDER_JOB_PIPE_READ 1 -#define WORKER_SENDER_JOB_SOCKET_RECEIVE 2 -#define WORKER_SENDER_JOB_EXECUTE 3 -#define WORKER_SENDER_JOB_SOCKET_SEND 4 -#define WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE 5 -#define WORKER_SENDER_JOB_DISCONNECT_OVERFLOW 6 -#define WORKER_SENDER_JOB_DISCONNECT_TIMEOUT 7 -#define WORKER_SENDER_JOB_DISCONNECT_POLL_ERROR 8 -#define WORKER_SENDER_JOB_DISCONNECT_SOCKET_ERROR 9 -#define WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR 10 -#define WORKER_SENDER_JOB_DISCONNECT_PARENT_CLOSED 11 -#define WORKER_SENDER_JOB_DISCONNECT_RECEIVE_ERROR 12 -#define WORKER_SENDER_JOB_DISCONNECT_SEND_ERROR 13 -#define WORKER_SENDER_JOB_DISCONNECT_NO_COMPRESSION 14 -#define WORKER_SENDER_JOB_BUFFER_RATIO 15 -#define WORKER_SENDER_JOB_BYTES_RECEIVED 16 -#define WORKER_SENDER_JOB_BYTES_SENT 17 -#define WORKER_SENDER_JOB_BYTES_COMPRESSED 18 -#define WORKER_SENDER_JOB_BYTES_UNCOMPRESSED 19 -#define WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO 20 -#define WORKER_SENDER_JOB_REPLAY_REQUEST 21 -#define WORKER_SENDER_JOB_FUNCTION_REQUEST 22 -#define WORKER_SENDER_JOB_REPLAY_DICT_SIZE 23 -#define WORKER_SENDER_JOB_DISCONNECT_CANT_UPGRADE_CONNECTION 24 - -#if WORKER_UTILIZATION_MAX_JOB_TYPES < 25 -#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 25 -#endif - -bool attempt_to_connect(struct sender_state *state); -void rrdpush_sender_on_connect(RRDHOST *host); -void rrdpush_sender_after_connect(RRDHOST *host); -void rrdpush_sender_thread_close_socket(struct sender_state *s); - -void rrdpush_sender_execute_commands_cleanup(struct sender_state *s); -void rrdpush_sender_execute_commands(struct sender_state *s); - -#endif //NETDATA_SENDER_INTERNALS_H diff --git a/src/streaming/sender.c b/src/streaming/sender.c deleted file mode 100644 index 666409b1c7fcce..00000000000000 --- a/src/streaming/sender.c +++ /dev/null @@ -1,671 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "sender-internals.h" - -// resets all the chart, so that their definitions -// will be resent to the central netdata -static void rrdpush_sender_thread_reset_all_charts(RRDHOST *host) { - RRDSET *st; - rrdset_foreach_read(st, host) { - rrdset_flag_clear(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS); - rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_FINISHED); - - st->rrdpush.sender.resync_time_s = 0; - - RRDDIM *rd; - rrddim_foreach_read(rd, st) - rrddim_metadata_exposed_upstream_clear(rd); - rrddim_foreach_done(rd); - - rrdset_metadata_updated(st); - } - rrdset_foreach_done(st); - - rrdhost_sender_replicating_charts_zero(host); -} - -void rrdpush_sender_cbuffer_recreate_timed(struct sender_state *s, time_t now_s, bool have_mutex, bool force) { - static __thread time_t last_reset_time_s = 0; - - if(!force && now_s - last_reset_time_s < 300) - return; - - if(!have_mutex) - sender_lock(s); - - rrdpush_sender_last_buffer_recreate_set(s, now_s); - last_reset_time_s = now_s; - - if(s->buffer && s->buffer->size > CBUFFER_INITIAL_SIZE) { - size_t max = s->buffer->max_size; - cbuffer_free(s->buffer); - s->buffer = cbuffer_new(CBUFFER_INITIAL_SIZE, max, &netdata_buffers_statistics.cbuffers_streaming); - } - - sender_thread_buffer_free(); - - if(!have_mutex) - sender_unlock(s); -} - -static void rrdpush_sender_cbuffer_flush(RRDHOST *host) { - rrdpush_sender_set_flush_time(host->sender); - - sender_lock(host->sender); - - // flush the output buffer from any data it may have - cbuffer_flush(host->sender->buffer); - rrdpush_sender_cbuffer_recreate_timed(host->sender, now_monotonic_sec(), true, true); - replication_recalculate_buffer_used_ratio_unsafe(host->sender); - - sender_unlock(host->sender); -} - -static void rrdpush_sender_charts_and_replication_reset(RRDHOST *host) { - rrdpush_sender_set_flush_time(host->sender); - - // stop all replication commands inflight - replication_sender_delete_pending_requests(host->sender); - - // reset the state of all charts - rrdpush_sender_thread_reset_all_charts(host); - - rrdpush_sender_replicating_charts_zero(host->sender); -} - -void rrdpush_sender_on_connect(RRDHOST *host) { - rrdpush_sender_cbuffer_flush(host); - rrdpush_sender_charts_and_replication_reset(host); -} - -void rrdpush_sender_after_connect(RRDHOST *host) { - rrdpush_sender_thread_send_custom_host_variables(host); -} - -static void rrdpush_sender_on_disconnect(RRDHOST *host) { - // we have been connected to this parent - let's cleanup - - rrdpush_sender_charts_and_replication_reset(host); - - // clear the parent's claim id - rrdpush_sender_clear_parent_claim_id(host); - rrdpush_receiver_send_node_and_claim_id_to_child(host); - stream_path_parent_disconnected(host); -} - -// TCP window is open, and we have data to transmit. -static ssize_t attempt_to_send(struct sender_state *s) { - ssize_t ret; - -#ifdef NETDATA_INTERNAL_CHECKS - struct circular_buffer *cb = s->buffer; -#endif - - sender_lock(s); - char *chunk; - size_t outstanding = cbuffer_next_unsafe(s->buffer, &chunk); - netdata_log_debug(D_STREAM, "STREAM: Sending data. Buffer r=%zu w=%zu s=%zu, next chunk=%zu", cb->read, cb->write, cb->size, outstanding); - - if(SSL_connection(&s->ssl)) - ret = netdata_ssl_write(&s->ssl, chunk, outstanding); - else - ret = send(s->rrdpush_sender_socket, chunk, outstanding, MSG_DONTWAIT); - - if (likely(ret > 0)) { - cbuffer_remove_unsafe(s->buffer, ret); - s->sent_bytes_on_this_connection += ret; - s->sent_bytes += ret; - netdata_log_debug(D_STREAM, "STREAM %s [send to %s]: Sent %zd bytes", rrdhost_hostname(s->host), s->connected_to, ret); - } - else if (ret == -1 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) - netdata_log_debug(D_STREAM, "STREAM %s [send to %s]: unavailable after polling POLLOUT", rrdhost_hostname(s->host), s->connected_to); - else if (ret == -1) { - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SEND_ERROR); - netdata_log_debug(D_STREAM, "STREAM: Send failed - closing socket..."); - netdata_log_error("STREAM %s [send to %s]: failed to send metrics - closing connection - we have sent %zu bytes on this connection.", rrdhost_hostname(s->host), s->connected_to, s->sent_bytes_on_this_connection); - rrdpush_sender_thread_close_socket(s); - } - else - netdata_log_debug(D_STREAM, "STREAM: send() returned 0 -> no error but no transmission"); - - replication_recalculate_buffer_used_ratio_unsafe(s); - sender_unlock(s); - - return ret; -} - -static ssize_t attempt_read(struct sender_state *s) { - ssize_t ret; - - if (SSL_connection(&s->ssl)) - ret = netdata_ssl_read(&s->ssl, s->read_buffer + s->read_len, sizeof(s->read_buffer) - s->read_len - 1); - else - ret = recv(s->rrdpush_sender_socket, s->read_buffer + s->read_len, sizeof(s->read_buffer) - s->read_len - 1,MSG_DONTWAIT); - - if (ret > 0) { - s->read_len += ret; - return ret; - } - - if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) - return ret; - - if (SSL_connection(&s->ssl)) - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR); - else if (ret == 0 || errno == ECONNRESET) { - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_PARENT_CLOSED); - netdata_log_error("STREAM %s [send to %s]: connection closed by far end.", rrdhost_hostname(s->host), s->connected_to); - } - else { - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_RECEIVE_ERROR); - netdata_log_error("STREAM %s [send to %s]: error during receive (%zd) - closing connection.", rrdhost_hostname(s->host), s->connected_to, ret); - } - - rrdpush_sender_thread_close_socket(s); - - return ret; -} - -static bool rrdpush_sender_pipe_close(RRDHOST *host, int *pipe_fds, bool reopen) { - static netdata_mutex_t mutex = NETDATA_MUTEX_INITIALIZER; - - bool ret = true; - - netdata_mutex_lock(&mutex); - - int new_pipe_fds[2]; - if(reopen) { - if(pipe(new_pipe_fds) != 0) { - netdata_log_error("STREAM %s [send]: cannot create required pipe.", rrdhost_hostname(host)); - new_pipe_fds[PIPE_READ] = -1; - new_pipe_fds[PIPE_WRITE] = -1; - ret = false; - } - } - - int old_pipe_fds[2]; - old_pipe_fds[PIPE_READ] = pipe_fds[PIPE_READ]; - old_pipe_fds[PIPE_WRITE] = pipe_fds[PIPE_WRITE]; - - if(reopen) { - pipe_fds[PIPE_READ] = new_pipe_fds[PIPE_READ]; - pipe_fds[PIPE_WRITE] = new_pipe_fds[PIPE_WRITE]; - } - else { - pipe_fds[PIPE_READ] = -1; - pipe_fds[PIPE_WRITE] = -1; - } - - if(old_pipe_fds[PIPE_READ] > 2) - close(old_pipe_fds[PIPE_READ]); - - if(old_pipe_fds[PIPE_WRITE] > 2) - close(old_pipe_fds[PIPE_WRITE]); - - netdata_mutex_unlock(&mutex); - return ret; -} - -void rrdpush_signal_sender_to_wake_up(struct sender_state *s) { - if(unlikely(s->tid == gettid_cached())) - return; - - RRDHOST *host = s->host; - - int pipe_fd = s->rrdpush_sender_pipe[PIPE_WRITE]; - - // signal the sender there are more data - if (pipe_fd != -1 && write(pipe_fd, " ", 1) == -1) { - netdata_log_error("STREAM %s [send]: cannot write to internal pipe.", rrdhost_hostname(host)); - rrdpush_sender_pipe_close(host, s->rrdpush_sender_pipe, true); - } -} - -static bool rrdhost_set_sender(RRDHOST *host) { - if(unlikely(!host->sender)) return false; - - bool ret = false; - sender_lock(host->sender); - if(!host->sender->tid) { - rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED | RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS); - rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN); - host->rrdpush_sender_connection_counter++; - host->sender->tid = gettid_cached(); - host->sender->last_state_since_t = now_realtime_sec(); - host->sender->exit.reason = STREAM_HANDSHAKE_NEVER; - ret = true; - } - sender_unlock(host->sender); - - rrdpush_reset_destinations_postpone_time(host); - - return ret; -} - -static void rrdhost_clear_sender___while_having_sender_mutex(RRDHOST *host) { - if(unlikely(!host->sender)) return; - - if(host->sender->tid == gettid_cached()) { - host->sender->tid = 0; - host->sender->exit.shutdown = false; - rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN | RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED | RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS); - host->sender->last_state_since_t = now_realtime_sec(); - if(host->destination) { - host->destination->since = host->sender->last_state_since_t; - host->destination->reason = host->sender->exit.reason; - } - } - - rrdpush_reset_destinations_postpone_time(host); -} - -bool rrdhost_sender_should_exit(struct sender_state *s) { - if(unlikely(nd_thread_signaled_to_cancel())) { - if(!s->exit.reason) - s->exit.reason = STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN; - return true; - } - - if(unlikely(!service_running(SERVICE_STREAMING))) { - if(!s->exit.reason) - s->exit.reason = STREAM_HANDSHAKE_DISCONNECT_NETDATA_EXIT; - return true; - } - - if(unlikely(!rrdhost_has_rrdpush_sender_enabled(s->host))) { - if(!s->exit.reason) - s->exit.reason = STREAM_HANDSHAKE_NON_STREAMABLE_HOST; - return true; - } - - if(unlikely(s->exit.shutdown)) { - if(!s->exit.reason) - s->exit.reason = STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN; - return true; - } - - if(unlikely(rrdhost_flag_check(s->host, RRDHOST_FLAG_ORPHAN))) { - if(!s->exit.reason) - s->exit.reason = STREAM_HANDSHAKE_DISCONNECT_ORPHAN_HOST; - return true; - } - - return false; -} - -static bool stream_sender_log_capabilities(BUFFER *wb, void *ptr) { - struct sender_state *state = ptr; - if(!state) - return false; - - stream_capabilities_to_string(wb, state->capabilities); - return true; -} - -static bool stream_sender_log_transport(BUFFER *wb, void *ptr) { - struct sender_state *state = ptr; - if(!state) - return false; - - buffer_strcat(wb, SSL_connection(&state->ssl) ? "https" : "http"); - return true; -} - -static bool stream_sender_log_dst_ip(BUFFER *wb, void *ptr) { - struct sender_state *state = ptr; - if(!state || state->rrdpush_sender_socket == -1) - return false; - - SOCKET_PEERS peers = socket_peers(state->rrdpush_sender_socket); - buffer_strcat(wb, peers.peer.ip); - return true; -} - -static bool stream_sender_log_dst_port(BUFFER *wb, void *ptr) { - struct sender_state *state = ptr; - if(!state || state->rrdpush_sender_socket == -1) - return false; - - SOCKET_PEERS peers = socket_peers(state->rrdpush_sender_socket); - buffer_print_uint64(wb, peers.peer.port); - return true; -} - -void *rrdpush_sender_thread(void *ptr) { - struct sender_state *s = ptr; - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_STR(NDF_NIDL_NODE, s->host->hostname), - ND_LOG_FIELD_CB(NDF_DST_IP, stream_sender_log_dst_ip, s), - ND_LOG_FIELD_CB(NDF_DST_PORT, stream_sender_log_dst_port, s), - ND_LOG_FIELD_CB(NDF_DST_TRANSPORT, stream_sender_log_transport, s), - ND_LOG_FIELD_CB(NDF_SRC_CAPABILITIES, stream_sender_log_capabilities, s), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - worker_register("STREAMSND"); - worker_register_job_name(WORKER_SENDER_JOB_CONNECT, "connect"); - worker_register_job_name(WORKER_SENDER_JOB_PIPE_READ, "pipe read"); - worker_register_job_name(WORKER_SENDER_JOB_SOCKET_RECEIVE, "receive"); - worker_register_job_name(WORKER_SENDER_JOB_EXECUTE, "execute"); - worker_register_job_name(WORKER_SENDER_JOB_SOCKET_SEND, "send"); - - // disconnection reasons - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_TIMEOUT, "disconnect timeout"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_POLL_ERROR, "disconnect poll error"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_SOCKET_ERROR, "disconnect socket error"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_OVERFLOW, "disconnect overflow"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR, "disconnect ssl error"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_PARENT_CLOSED, "disconnect parent closed"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_RECEIVE_ERROR, "disconnect receive error"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_SEND_ERROR, "disconnect send error"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_NO_COMPRESSION, "disconnect no compression"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, "disconnect bad handshake"); - worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_CANT_UPGRADE_CONNECTION, "disconnect cant upgrade"); - - worker_register_job_name(WORKER_SENDER_JOB_REPLAY_REQUEST, "replay request"); - worker_register_job_name(WORKER_SENDER_JOB_FUNCTION_REQUEST, "function"); - - worker_register_job_custom_metric(WORKER_SENDER_JOB_BUFFER_RATIO, "used buffer ratio", "%", WORKER_METRIC_ABSOLUTE); - worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_RECEIVED, "bytes received", "bytes/s", WORKER_METRIC_INCREMENT); - worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_SENT, "bytes sent", "bytes/s", WORKER_METRIC_INCREMENT); - worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_COMPRESSED, "bytes compressed", "bytes/s", WORKER_METRIC_INCREMENTAL_TOTAL); - worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_UNCOMPRESSED, "bytes uncompressed", "bytes/s", WORKER_METRIC_INCREMENTAL_TOTAL); - worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO, "cumulative compression savings ratio", "%", WORKER_METRIC_ABSOLUTE); - worker_register_job_custom_metric(WORKER_SENDER_JOB_REPLAY_DICT_SIZE, "replication dict entries", "entries", WORKER_METRIC_ABSOLUTE); - - if(!rrdhost_has_rrdpush_sender_enabled(s->host) || !s->host->rrdpush.send.destination || - !*s->host->rrdpush.send.destination || !s->host->rrdpush.send.api_key || - !*s->host->rrdpush.send.api_key) { - netdata_log_error("STREAM %s [send]: thread created (task id %d), but host has streaming disabled.", - rrdhost_hostname(s->host), gettid_cached()); - return NULL; - } - - if(!rrdhost_set_sender(s->host)) { - netdata_log_error("STREAM %s [send]: thread created (task id %d), but there is another sender running for this host.", - rrdhost_hostname(s->host), gettid_cached()); - return NULL; - } - - rrdpush_sender_ssl_init(s->host); - - netdata_log_info("STREAM %s [send]: thread created (task id %d)", rrdhost_hostname(s->host), gettid_cached()); - - s->timeout = (int)appconfig_get_duration_seconds( - &stream_config, CONFIG_SECTION_STREAM, "timeout", 600); - - s->default_port = (int)appconfig_get_number( - &stream_config, CONFIG_SECTION_STREAM, "default port", 19999); - - s->buffer->max_size = (size_t)appconfig_get_number( - &stream_config, CONFIG_SECTION_STREAM, "buffer size bytes", 1024 * 1024 * 10); - - s->reconnect_delay = (unsigned int)appconfig_get_duration_seconds( - &stream_config, CONFIG_SECTION_STREAM, "reconnect delay", 5); - - stream_conf_initial_clock_resync_iterations = (unsigned int)appconfig_get_number( - &stream_config, CONFIG_SECTION_STREAM, - "initial clock resync iterations", - stream_conf_initial_clock_resync_iterations); // TODO: REMOVE FOR SLEW / GAPFILLING - - s->parent_using_h2o = appconfig_get_boolean( - &stream_config, CONFIG_SECTION_STREAM, "parent using h2o", false); - - // initialize rrdpush globals - rrdhost_flag_clear(s->host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED | RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS); - - int pipe_buffer_size = 10 * 1024; -#ifdef F_GETPIPE_SZ - pipe_buffer_size = fcntl(s->rrdpush_sender_pipe[PIPE_READ], F_GETPIPE_SZ); -#endif - if(pipe_buffer_size < 10 * 1024) - pipe_buffer_size = 10 * 1024; - - if(!rrdpush_sender_pipe_close(s->host, s->rrdpush_sender_pipe, true)) { - netdata_log_error("STREAM %s [send]: cannot create inter-thread communication pipe. Disabling streaming.", - rrdhost_hostname(s->host)); - return NULL; - } - - char *pipe_buffer = mallocz(pipe_buffer_size); - - bool was_connected = false; - size_t iterations = 0; - time_t now_s = now_monotonic_sec(); - while(!rrdhost_sender_should_exit(s)) { - iterations++; - - // The connection attempt blocks (after which we use the socket in nonblocking) - if(unlikely(s->rrdpush_sender_socket == -1)) { - if(was_connected) - rrdpush_sender_on_disconnect(s->host); - - was_connected = rrdpush_sender_connect(s); - now_s = s->last_traffic_seen_t; - continue; - } - - if(iterations % 1000 == 0) - now_s = now_monotonic_sec(); - - // If the TCP window never opened then something is wrong, restart connection - if(unlikely(now_s - s->last_traffic_seen_t > s->timeout && - !rrdpush_sender_pending_replication_requests(s) && - !rrdpush_sender_replicating_charts(s) - )) { - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_TIMEOUT); - netdata_log_error("STREAM %s [send to %s]: could not send metrics for %d seconds - closing connection - we have sent %zu bytes on this connection via %zu send attempts.", rrdhost_hostname(s->host), s->connected_to, s->timeout, s->sent_bytes_on_this_connection, s->send_attempts); - rrdpush_sender_thread_close_socket(s); - continue; - } - - sender_lock(s); - size_t outstanding = cbuffer_next_unsafe(s->buffer, NULL); - size_t available = cbuffer_available_size_unsafe(s->buffer); - if (unlikely(!outstanding)) { - rrdpush_sender_pipe_clear_pending_data(s); - rrdpush_sender_cbuffer_recreate_timed(s, now_s, true, false); - } - - if(s->compressor.initialized) { - size_t bytes_uncompressed = s->compressor.sender_locked.total_uncompressed; - size_t bytes_compressed = s->compressor.sender_locked.total_compressed + s->compressor.sender_locked.total_compressions * sizeof(rrdpush_signature_t); - NETDATA_DOUBLE ratio = 100.0 - ((NETDATA_DOUBLE)bytes_compressed * 100.0 / (NETDATA_DOUBLE)bytes_uncompressed); - worker_set_metric(WORKER_SENDER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes_uncompressed); - worker_set_metric(WORKER_SENDER_JOB_BYTES_COMPRESSED, (NETDATA_DOUBLE)bytes_compressed); - worker_set_metric(WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO, ratio); - } - sender_unlock(s); - - worker_set_metric(WORKER_SENDER_JOB_BUFFER_RATIO, (NETDATA_DOUBLE)(s->buffer->max_size - available) * 100.0 / (NETDATA_DOUBLE)s->buffer->max_size); - - if(outstanding) - s->send_attempts++; - - if(unlikely(s->rrdpush_sender_pipe[PIPE_READ] == -1)) { - if(!rrdpush_sender_pipe_close(s->host, s->rrdpush_sender_pipe, true)) { - netdata_log_error("STREAM %s [send]: cannot create inter-thread communication pipe. " - "Disabling streaming.", rrdhost_hostname(s->host)); - rrdpush_sender_thread_close_socket(s); - break; - } - } - - worker_is_idle(); - - // Wait until buffer opens in the socket or a rrdset_done_push wakes us - enum { - Collector = 0, - Socket = 1, - }; - struct pollfd fds[2] = { - [Collector] = { - .fd = s->rrdpush_sender_pipe[PIPE_READ], - .events = POLLIN, - .revents = 0, - }, - [Socket] = { - .fd = s->rrdpush_sender_socket, - .events = POLLIN | (outstanding ? POLLOUT : 0 ), - .revents = 0, - } - }; - - int poll_rc = poll(fds, 2, 50); // timeout in milliseconds - - netdata_log_debug(D_STREAM, "STREAM: poll() finished collector=%d socket=%d (current chunk %zu bytes)...", - fds[Collector].revents, fds[Socket].revents, outstanding); - - if(unlikely(rrdhost_sender_should_exit(s))) - break; - - internal_error(fds[Collector].fd != s->rrdpush_sender_pipe[PIPE_READ], - "STREAM %s [send to %s]: pipe changed after poll().", rrdhost_hostname(s->host), s->connected_to); - - internal_error(fds[Socket].fd != s->rrdpush_sender_socket, - "STREAM %s [send to %s]: socket changed after poll().", rrdhost_hostname(s->host), s->connected_to); - - // Spurious wake-ups without error - loop again - if (poll_rc == 0 || ((poll_rc == -1) && (errno == EAGAIN || errno == EINTR))) { - netdata_log_debug(D_STREAM, "Spurious wakeup"); - now_s = now_monotonic_sec(); - continue; - } - - // Only errors from poll() are internal, but try restarting the connection - if(unlikely(poll_rc == -1)) { - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_POLL_ERROR); - netdata_log_error("STREAM %s [send to %s]: failed to poll(). Closing socket.", rrdhost_hostname(s->host), s->connected_to); - rrdpush_sender_pipe_close(s->host, s->rrdpush_sender_pipe, true); - rrdpush_sender_thread_close_socket(s); - continue; - } - - // If we have data and have seen the TCP window open then try to close it by a transmission. - if(likely(outstanding && (fds[Socket].revents & POLLOUT))) { - worker_is_busy(WORKER_SENDER_JOB_SOCKET_SEND); - ssize_t bytes = attempt_to_send(s); - if(bytes > 0) { - s->last_traffic_seen_t = now_monotonic_sec(); - worker_set_metric(WORKER_SENDER_JOB_BYTES_SENT, (NETDATA_DOUBLE)bytes); - } - } - - // If the collector woke us up then empty the pipe to remove the signal - if (fds[Collector].revents & (POLLIN|POLLPRI)) { - worker_is_busy(WORKER_SENDER_JOB_PIPE_READ); - netdata_log_debug(D_STREAM, "STREAM: Data added to send buffer (current buffer chunk %zu bytes)...", outstanding); - - if (read(fds[Collector].fd, pipe_buffer, pipe_buffer_size) == -1) - netdata_log_error("STREAM %s [send to %s]: cannot read from internal pipe.", rrdhost_hostname(s->host), s->connected_to); - } - - // Read as much as possible to fill the buffer, split into full lines for execution. - if (fds[Socket].revents & POLLIN) { - worker_is_busy(WORKER_SENDER_JOB_SOCKET_RECEIVE); - ssize_t bytes = attempt_read(s); - if(bytes > 0) { - s->last_traffic_seen_t = now_monotonic_sec(); - worker_set_metric(WORKER_SENDER_JOB_BYTES_RECEIVED, (NETDATA_DOUBLE)bytes); - } - } - - if(unlikely(s->read_len)) - rrdpush_sender_execute_commands(s); - - if(unlikely(fds[Collector].revents & (POLLERR|POLLHUP|POLLNVAL))) { - char *error = NULL; - - if (unlikely(fds[Collector].revents & POLLERR)) - error = "pipe reports errors (POLLERR)"; - else if (unlikely(fds[Collector].revents & POLLHUP)) - error = "pipe closed (POLLHUP)"; - else if (unlikely(fds[Collector].revents & POLLNVAL)) - error = "pipe is invalid (POLLNVAL)"; - - if(error) { - rrdpush_sender_pipe_close(s->host, s->rrdpush_sender_pipe, true); - netdata_log_error("STREAM %s [send to %s]: restarting internal pipe: %s.", - rrdhost_hostname(s->host), s->connected_to, error); - } - } - - if(unlikely(fds[Socket].revents & (POLLERR|POLLHUP|POLLNVAL))) { - char *error = NULL; - - if (unlikely(fds[Socket].revents & POLLERR)) - error = "socket reports errors (POLLERR)"; - else if (unlikely(fds[Socket].revents & POLLHUP)) - error = "connection closed by remote end (POLLHUP)"; - else if (unlikely(fds[Socket].revents & POLLNVAL)) - error = "connection is invalid (POLLNVAL)"; - - if(unlikely(error)) { - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SOCKET_ERROR); - netdata_log_error("STREAM %s [send to %s]: restarting connection: %s - %zu bytes transmitted.", - rrdhost_hostname(s->host), s->connected_to, error, s->sent_bytes_on_this_connection); - rrdpush_sender_thread_close_socket(s); - } - } - - // protection from overflow - if(unlikely(s->flags & SENDER_FLAG_OVERFLOW)) { - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_OVERFLOW); - errno_clear(); - netdata_log_error("STREAM %s [send to %s]: buffer full (allocated %zu bytes) after sending %zu bytes. Restarting connection", - rrdhost_hostname(s->host), s->connected_to, s->buffer->size, s->sent_bytes_on_this_connection); - rrdpush_sender_thread_close_socket(s); - } - - worker_set_metric(WORKER_SENDER_JOB_REPLAY_DICT_SIZE, (NETDATA_DOUBLE) dictionary_entries(s->replication.requests)); - } - - if(was_connected) - rrdpush_sender_on_disconnect(s->host); - - netdata_log_info("STREAM %s [send]: sending thread exits %s", - rrdhost_hostname(s->host), - s->exit.reason != STREAM_HANDSHAKE_NEVER ? stream_handshake_error_to_string(s->exit.reason) : ""); - - sender_lock(s); - { - rrdpush_sender_thread_close_socket(s); - rrdpush_sender_pipe_close(s->host, s->rrdpush_sender_pipe, false); - rrdpush_sender_execute_commands_cleanup(s); - - rrdhost_clear_sender___while_having_sender_mutex(s->host); - -#ifdef NETDATA_LOG_STREAM_SENDER - if (s->stream_log_fp) { - fclose(s->stream_log_fp); - s->stream_log_fp = NULL; - } -#endif - } - sender_unlock(s); - - freez(pipe_buffer); - worker_unregister(); - - return NULL; -} - -void rrdpush_sender_thread_spawn(RRDHOST *host) { - sender_lock(host->sender); - - if(!rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN)) { - char tag[NETDATA_THREAD_TAG_MAX + 1]; - snprintfz(tag, NETDATA_THREAD_TAG_MAX, THREAD_TAG_STREAM_SENDER "[%s]", rrdhost_hostname(host)); - - host->rrdpush_sender_thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_DEFAULT, - rrdpush_sender_thread, (void *)host->sender); - if(!host->rrdpush_sender_thread) - nd_log_daemon(NDLP_ERR, "STREAM %s [send]: failed to create new thread for client.", rrdhost_hostname(host)); - else - rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN); - } - - sender_unlock(host->sender); -} diff --git a/src/streaming/sender.h b/src/streaming/sender.h deleted file mode 100644 index 94d104f5f021d6..00000000000000 --- a/src/streaming/sender.h +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_SENDER_H -#define NETDATA_SENDER_H - -#include "libnetdata/libnetdata.h" - -#define CONNECTED_TO_SIZE 100 - -#define CBUFFER_INITIAL_SIZE (16 * 1024) -#define THREAD_BUFFER_INITIAL_SIZE (CBUFFER_INITIAL_SIZE / 2) - -typedef enum __attribute__((packed)) { - STREAM_TRAFFIC_TYPE_REPLICATION = 0, - STREAM_TRAFFIC_TYPE_FUNCTIONS, - STREAM_TRAFFIC_TYPE_METADATA, - STREAM_TRAFFIC_TYPE_DATA, - STREAM_TRAFFIC_TYPE_DYNCFG, - - // terminator - STREAM_TRAFFIC_TYPE_MAX, -} STREAM_TRAFFIC_TYPE; - -typedef enum __attribute__((packed)) { - SENDER_FLAG_OVERFLOW = (1 << 0), // The buffer has been overflown -} SENDER_FLAGS; - -typedef struct { - char *os_name; - char *os_id; - char *os_version; - char *kernel_name; - char *kernel_version; -} stream_encoded_t; - -#include "stream-handshake.h" -#include "stream-capabilities.h" -#include "stream-conf.h" -#include "stream-compression/compression.h" - -#include "sender-destinations.h" - -typedef void (*rrdpush_defer_action_t)(struct sender_state *s, void *data); -typedef void (*rrdpush_defer_cleanup_t)(struct sender_state *s, void *data); - -struct sender_state { - RRDHOST *host; - pid_t tid; // the thread id of the sender, from gettid_cached() - SENDER_FLAGS flags; - int timeout; - int default_port; - uint32_t reconnect_delay; - char connected_to[CONNECTED_TO_SIZE + 1]; // We don't know which proxy we connect to, passed back from socket.c - size_t begin; - size_t reconnects_counter; - size_t sent_bytes; - size_t sent_bytes_on_this_connection; - size_t send_attempts; - time_t last_traffic_seen_t; - time_t last_state_since_t; // the timestamp of the last state (online/offline) change - size_t not_connected_loops; - // Metrics are collected asynchronously by collector threads calling rrdset_done_push(). This can also trigger - // the lazy creation of the sender thread - both cases (buffer access and thread creation) are guarded here. - SPINLOCK spinlock; - struct circular_buffer *buffer; - char read_buffer[PLUGINSD_LINE_MAX + 1]; - ssize_t read_len; - STREAM_CAPABILITIES capabilities; - STREAM_CAPABILITIES disabled_capabilities; - - size_t sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_MAX]; - - int rrdpush_sender_pipe[2]; // collector to sender thread signaling - int rrdpush_sender_socket; - - uint16_t hops; - - struct line_splitter line; - struct compressor_state compressor; - -#ifdef NETDATA_LOG_STREAM_SENDER - FILE *stream_log_fp; -#endif - - NETDATA_SSL ssl; // structure used to encrypt the connection - - struct { - bool shutdown; - STREAM_HANDSHAKE reason; - } exit; - - struct { - DICTIONARY *requests; // de-duplication of replication requests, per chart - time_t oldest_request_after_t; // the timestamp of the oldest replication request - time_t latest_completed_before_t; // the timestamp of the latest replication request - - struct { - size_t pending_requests; // the currently outstanding replication requests - size_t charts_replicating; // the number of unique charts having pending replication requests (on every request one is added and is removed when we finish it - it does not track completion of the replication for this chart) - bool reached_max; // true when the sender buffer should not get more replication responses - } atomic; - - } replication; - - struct { - bool pending_data; - size_t buffer_used_percentage; // the current utilization of the sending buffer - usec_t last_flush_time_ut; // the last time the sender flushed the sending buffer in USEC - time_t last_buffer_recreate_s; // true when the sender buffer should be re-created - } atomic; - - struct { - const char *end_keyword; - BUFFER *payload; - rrdpush_defer_action_t action; - rrdpush_defer_cleanup_t cleanup; - void *action_data; - } defer; - - bool parent_using_h2o; -}; - -#define sender_lock(sender) spinlock_lock(&(sender)->spinlock) -#define sender_unlock(sender) spinlock_unlock(&(sender)->spinlock) - -#define rrdpush_sender_pipe_has_pending_data(sender) __atomic_load_n(&(sender)->atomic.pending_data, __ATOMIC_RELAXED) -#define rrdpush_sender_pipe_set_pending_data(sender) __atomic_store_n(&(sender)->atomic.pending_data, true, __ATOMIC_RELAXED) -#define rrdpush_sender_pipe_clear_pending_data(sender) __atomic_store_n(&(sender)->atomic.pending_data, false, __ATOMIC_RELAXED) - -#define rrdpush_sender_last_buffer_recreate_get(sender) __atomic_load_n(&(sender)->atomic.last_buffer_recreate_s, __ATOMIC_RELAXED) -#define rrdpush_sender_last_buffer_recreate_set(sender, value) __atomic_store_n(&(sender)->atomic.last_buffer_recreate_s, value, __ATOMIC_RELAXED) - -#define rrdpush_sender_replication_buffer_full_set(sender, value) __atomic_store_n(&((sender)->replication.atomic.reached_max), value, __ATOMIC_SEQ_CST) -#define rrdpush_sender_replication_buffer_full_get(sender) __atomic_load_n(&((sender)->replication.atomic.reached_max), __ATOMIC_SEQ_CST) - -#define rrdpush_sender_set_buffer_used_percent(sender, value) __atomic_store_n(&((sender)->atomic.buffer_used_percentage), value, __ATOMIC_RELAXED) -#define rrdpush_sender_get_buffer_used_percent(sender) __atomic_load_n(&((sender)->atomic.buffer_used_percentage), __ATOMIC_RELAXED) - -#define rrdpush_sender_set_flush_time(sender) __atomic_store_n(&((sender)->atomic.last_flush_time_ut), now_realtime_usec(), __ATOMIC_RELAXED) -#define rrdpush_sender_get_flush_time(sender) __atomic_load_n(&((sender)->atomic.last_flush_time_ut), __ATOMIC_RELAXED) - -#define rrdpush_sender_replicating_charts(sender) __atomic_load_n(&((sender)->replication.atomic.charts_replicating), __ATOMIC_RELAXED) -#define rrdpush_sender_replicating_charts_plus_one(sender) __atomic_add_fetch(&((sender)->replication.atomic.charts_replicating), 1, __ATOMIC_RELAXED) -#define rrdpush_sender_replicating_charts_minus_one(sender) __atomic_sub_fetch(&((sender)->replication.atomic.charts_replicating), 1, __ATOMIC_RELAXED) -#define rrdpush_sender_replicating_charts_zero(sender) __atomic_store_n(&((sender)->replication.atomic.charts_replicating), 0, __ATOMIC_RELAXED) - -#define rrdpush_sender_pending_replication_requests(sender) __atomic_load_n(&((sender)->replication.atomic.pending_requests), __ATOMIC_RELAXED) -#define rrdpush_sender_pending_replication_requests_plus_one(sender) __atomic_add_fetch(&((sender)->replication.atomic.pending_requests), 1, __ATOMIC_RELAXED) -#define rrdpush_sender_pending_replication_requests_minus_one(sender) __atomic_sub_fetch(&((sender)->replication.atomic.pending_requests), 1, __ATOMIC_RELAXED) -#define rrdpush_sender_pending_replication_requests_zero(sender) __atomic_store_n(&((sender)->replication.atomic.pending_requests), 0, __ATOMIC_RELAXED) - -BUFFER *sender_start(struct sender_state *s); -void sender_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type); - -void *rrdpush_sender_thread(void *ptr); -void rrdpush_sender_thread_stop(RRDHOST *host, STREAM_HANDSHAKE reason, bool wait); - -void sender_thread_buffer_free(void); - -void rrdpush_signal_sender_to_wake_up(struct sender_state *s); - -bool rrdpush_sender_connect(struct sender_state *s); -void rrdpush_sender_cbuffer_recreate_timed(struct sender_state *s, time_t now_s, bool have_mutex, bool force); -bool rrdhost_sender_should_exit(struct sender_state *s); -void rrdpush_sender_thread_spawn(RRDHOST *host); - -#include "replication.h" - -#endif //NETDATA_SENDER_H diff --git a/src/streaming/stream-capabilities.c b/src/streaming/stream-capabilities.c index b089e8f9dbcdaa..dfdde5a7f69585 100644 --- a/src/streaming/stream-capabilities.c +++ b/src/streaming/stream-capabilities.c @@ -1,13 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "rrdpush.h" +#include "stream.h" +#include "stream-receiver-internals.h" +#include "stream-sender-internals.h" -static STREAM_CAPABILITIES globally_disabled_capabilities = STREAM_CAP_NONE; +static STREAM_CAPABILITIES globally_disabled_capabilities = STREAM_CAP_ALWAYS_DISABLED; static struct { STREAM_CAPABILITIES cap; const char *str; } capability_names[] = { + // DO NOT CHANGE NAMES + // THEY ARE USED BY STREAM_PATH, SO CONNECTING OF DIFFERENT NODES WILL BREAK + {STREAM_CAP_V1, "V1" }, {STREAM_CAP_V2, "V2" }, {STREAM_CAP_VN, "VN" }, @@ -21,7 +26,8 @@ static struct { {STREAM_CAP_BINARY, "BINARY" }, {STREAM_CAP_INTERPOLATED, "INTERPOLATED" }, {STREAM_CAP_IEEE754, "IEEE754" }, - {STREAM_CAP_DATA_WITH_ML, "ML" }, + {STREAM_CAP_DATA_WITH_ML, "ML"}, // do not remove this - stream_path fails to parse old nodes + {STREAM_CAP_ML_MODELS, "MLMODELS" }, {STREAM_CAP_DYNCFG, "DYNCFG" }, {STREAM_CAP_SLOTS, "SLOTS" }, {STREAM_CAP_ZSTD, "ZSTD" }, @@ -30,6 +36,8 @@ static struct { {STREAM_CAP_PROGRESS, "PROGRESS" }, {STREAM_CAP_NODE_ID, "NODEID" }, {STREAM_CAP_PATHS, "PATHS" }, + + // terminator {0 , NULL }, }; @@ -95,12 +103,12 @@ STREAM_CAPABILITIES stream_our_capabilities(RRDHOST *host, bool sender) { // we have DATA_WITH_ML capability // we should remove the DATA_WITH_ML capability if our database does not have anomaly info // this can happen under these conditions: 1. we don't run ML, and 2. we don't receive ML - spinlock_lock(&host->receiver_lock); + rrdhost_receiver_lock(host); - if(!ml_host_running(host) && !stream_has_capability(host->receiver, STREAM_CAP_DATA_WITH_ML)) - disabled_capabilities |= STREAM_CAP_DATA_WITH_ML; + if (!ml_host_running(host) && !stream_has_capability(host->receiver, STREAM_CAP_ML_MODELS)) + disabled_capabilities |= STREAM_CAP_ML_MODELS; - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); if(host->sender) disabled_capabilities |= host->sender->disabled_capabilities; @@ -124,7 +132,7 @@ STREAM_CAPABILITIES stream_our_capabilities(RRDHOST *host, bool sender) { STREAM_CAP_NODE_ID | STREAM_CAP_PATHS | STREAM_CAP_IEEE754 | - STREAM_CAP_DATA_WITH_ML | + STREAM_CAP_ML_MODELS | 0) & ~disabled_capabilities; } @@ -151,7 +159,7 @@ STREAM_CAPABILITIES convert_stream_version_to_capabilities(int32_t version, RRDH if(!(common_caps & STREAM_CAP_INTERPOLATED)) // DATA WITH ML requires INTERPOLATED - common_caps &= ~STREAM_CAP_DATA_WITH_ML; + common_caps &= ~(STREAM_CAP_ML_MODELS); return common_caps; } diff --git a/src/streaming/stream-capabilities.h b/src/streaming/stream-capabilities.h index 90a0e2190cddea..826e2bda2223d8 100644 --- a/src/streaming/stream-capabilities.h +++ b/src/streaming/stream-capabilities.h @@ -25,9 +25,9 @@ typedef enum { STREAM_CAP_V1 = (1 << 3), // v1 = the oldest protocol STREAM_CAP_V2 = (1 << 4), // v2 = the second version of the protocol (with host labels) STREAM_CAP_VN = (1 << 5), // version negotiation supported (for versions 3, 4, 5 of the protocol) - // v3 = claiming supported - // v4 = chart labels supported - // v5 = lz4 compression supported + // v3 = claiming supported + // v4 = chart labels supported + // v5 = lz4 compression supported STREAM_CAP_VCAPS = (1 << 6), // capabilities negotiation supported STREAM_CAP_HLABELS = (1 << 7), // host labels supported STREAM_CAP_CLAIM = (1 << 8), // claiming supported @@ -38,7 +38,7 @@ typedef enum { STREAM_CAP_BINARY = (1 << 13), // streaming supports binary data STREAM_CAP_INTERPOLATED = (1 << 14), // streaming supports interpolated streaming of values STREAM_CAP_IEEE754 = (1 << 15), // streaming supports binary/hex transfer of double values - STREAM_CAP_DATA_WITH_ML = (1 << 16), // streaming supports transferring anomaly bit + STREAM_CAP_DATA_WITH_ML = (1 << 16), // leave this unused for as long as possible - NOT USED, BUT KEEP IT // STREAM_CAP_DYNCFG = (1 << 17), // leave this unused for as long as possible STREAM_CAP_SLOTS = (1 << 18), // the sender can appoint a unique slot for each chart STREAM_CAP_ZSTD = (1 << 19), // ZSTD compression supported @@ -48,12 +48,15 @@ typedef enum { STREAM_CAP_DYNCFG = (1 << 23), // support for DYNCFG STREAM_CAP_NODE_ID = (1 << 24), // support for sending NODE_ID back to the child STREAM_CAP_PATHS = (1 << 25), // support for sending PATHS upstream and downstream + STREAM_CAP_ML_MODELS = (1 << 26), // support for sending MODELS upstream STREAM_CAP_INVALID = (1 << 30), // used as an invalid value for capabilities when this is set // this must be signed int, so don't use the last bit // needed for negotiating errors between parent and child } STREAM_CAPABILITIES; +#define STREAM_CAP_ALWAYS_DISABLED (STREAM_CAP_DATA_WITH_ML) + #ifdef ENABLE_LZ4 #define STREAM_CAP_LZ4_AVAILABLE STREAM_CAP_LZ4 #else diff --git a/src/streaming/stream-compression/brotli.c b/src/streaming/stream-compression/brotli.c index c2c09cdc53cd1e..c556b622a44558 100644 --- a/src/streaming/stream-compression/brotli.c +++ b/src/streaming/stream-compression/brotli.c @@ -6,7 +6,7 @@ #include #include -void rrdpush_compressor_init_brotli(struct compressor_state *state) { +void stream_compressor_init_brotli(struct compressor_state *state) { if (!state->initialized) { state->initialized = true; state->stream = BrotliEncoderCreateInstance(NULL, NULL, NULL); @@ -21,14 +21,14 @@ void rrdpush_compressor_init_brotli(struct compressor_state *state) { } } -void rrdpush_compressor_destroy_brotli(struct compressor_state *state) { +void stream_compressor_destroy_brotli(struct compressor_state *state) { if (state->stream) { BrotliEncoderDestroyInstance(state->stream); state->stream = NULL; } } -size_t rrdpush_compress_brotli(struct compressor_state *state, const char *data, size_t size, const char **out) { +size_t stream_compress_brotli(struct compressor_state *state, const char *data, size_t size, const char **out) { if (unlikely(!state || !size || !out)) return 0; @@ -41,26 +41,26 @@ size_t rrdpush_compress_brotli(struct compressor_state *state, const char *data, uint8_t *next_out = (uint8_t *)state->output.data; if (!BrotliEncoderCompressStream(state->stream, BROTLI_OPERATION_FLUSH, &available_in, &next_in, &available_out, &next_out, NULL)) { - netdata_log_error("STREAM: Brotli compression failed."); + netdata_log_error("STREAM_COMPRESS: Brotli compression failed."); return 0; } if(available_in != 0) { - netdata_log_error("STREAM: BrotliEncoderCompressStream() did not use all the input buffer, %zu bytes out of %zu remain", + netdata_log_error("STREAM_COMPRESS: BrotliEncoderCompressStream() did not use all the input buffer, %zu bytes out of %zu remain", available_in, size); return 0; } size_t compressed_size = state->output.size - available_out; if(available_out == 0) { - netdata_log_error("STREAM: BrotliEncoderCompressStream() needs a bigger output buffer than the one we provided " + netdata_log_error("STREAM_COMPRESS: BrotliEncoderCompressStream() needs a bigger output buffer than the one we provided " "(output buffer %zu bytes, compressed payload %zu bytes)", state->output.size, size); return 0; } if(compressed_size == 0) { - netdata_log_error("STREAM: BrotliEncoderCompressStream() did not produce any output from the input provided " + netdata_log_error("STREAM_COMPRESS: BrotliEncoderCompressStream() did not produce any output from the input provided " "(input buffer %zu bytes)", size); return 0; @@ -74,7 +74,7 @@ size_t rrdpush_compress_brotli(struct compressor_state *state, const char *data, return compressed_size; } -void rrdpush_decompressor_init_brotli(struct decompressor_state *state) { +void stream_decompressor_init_brotli(struct decompressor_state *state) { if (!state->initialized) { state->initialized = true; state->stream = BrotliDecoderCreateInstance(NULL, NULL, NULL); @@ -83,14 +83,14 @@ void rrdpush_decompressor_init_brotli(struct decompressor_state *state) { } } -void rrdpush_decompressor_destroy_brotli(struct decompressor_state *state) { +void stream_decompressor_destroy_brotli(struct decompressor_state *state) { if (state->stream) { BrotliDecoderDestroyInstance(state->stream); state->stream = NULL; } } -size_t rrdpush_decompress_brotli(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { +size_t stream_decompress_brotli(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { if (unlikely(!state || !compressed_data || !compressed_size)) return 0; @@ -104,26 +104,26 @@ size_t rrdpush_decompress_brotli(struct decompressor_state *state, const char *c uint8_t *next_out = (uint8_t *)state->output.data; if (BrotliDecoderDecompressStream(state->stream, &available_in, &next_in, &available_out, &next_out, NULL) == BROTLI_DECODER_RESULT_ERROR) { - netdata_log_error("STREAM: Brotli decompression failed."); + netdata_log_error("STREAM_DECOMPRESS: Brotli decompression failed."); return 0; } if(available_in != 0) { - netdata_log_error("STREAM: BrotliDecoderDecompressStream() did not use all the input buffer, %zu bytes out of %zu remain", + netdata_log_error("STREAM_DECOMPRESS: BrotliDecoderDecompressStream() did not use all the input buffer, %zu bytes out of %zu remain", available_in, compressed_size); return 0; } size_t decompressed_size = state->output.size - available_out; if(available_out == 0) { - netdata_log_error("STREAM: BrotliDecoderDecompressStream() needs a bigger output buffer than the one we provided " + netdata_log_error("STREAM_DECOMPRESS: BrotliDecoderDecompressStream() needs a bigger output buffer than the one we provided " "(output buffer %zu bytes, compressed payload %zu bytes)", state->output.size, compressed_size); return 0; } if(decompressed_size == 0) { - netdata_log_error("STREAM: BrotliDecoderDecompressStream() did not produce any output from the input provided " + netdata_log_error("STREAM_DECOMPRESS: BrotliDecoderDecompressStream() did not produce any output from the input provided " "(input buffer %zu bytes)", compressed_size); return 0; diff --git a/src/streaming/stream-compression/brotli.h b/src/streaming/stream-compression/brotli.h index 4955e5a8215b1f..b387e984d7ca9f 100644 --- a/src/streaming/stream-compression/brotli.h +++ b/src/streaming/stream-compression/brotli.h @@ -5,11 +5,11 @@ #ifndef NETDATA_STREAMING_COMPRESSION_BROTLI_H #define NETDATA_STREAMING_COMPRESSION_BROTLI_H -void rrdpush_compressor_init_brotli(struct compressor_state *state); -void rrdpush_compressor_destroy_brotli(struct compressor_state *state); -size_t rrdpush_compress_brotli(struct compressor_state *state, const char *data, size_t size, const char **out); -size_t rrdpush_decompress_brotli(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); -void rrdpush_decompressor_init_brotli(struct decompressor_state *state); -void rrdpush_decompressor_destroy_brotli(struct decompressor_state *state); +void stream_compressor_init_brotli(struct compressor_state *state); +void stream_compressor_destroy_brotli(struct compressor_state *state); +size_t stream_compress_brotli(struct compressor_state *state, const char *data, size_t size, const char **out); +size_t stream_decompress_brotli(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); +void stream_decompressor_init_brotli(struct decompressor_state *state); +void stream_decompressor_destroy_brotli(struct decompressor_state *state); #endif //NETDATA_STREAMING_COMPRESSION_BROTLI_H diff --git a/src/streaming/stream-compression/compression.c b/src/streaming/stream-compression/compression.c index 3c99306563fd67..6d77cb4e34cc99 100644 --- a/src/streaming/stream-compression/compression.c +++ b/src/streaming/stream-compression/compression.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "compression.h" +#include "../stream-conf.h" +#include "../stream-receiver-internals.h" +#include "../stream-sender-internals.h" #include "gzip.h" @@ -16,18 +19,10 @@ #include "brotli.h" #endif -int rrdpush_compression_levels[COMPRESSION_ALGORITHM_MAX] = { - [COMPRESSION_ALGORITHM_NONE] = 0, - [COMPRESSION_ALGORITHM_ZSTD] = 3, // 1 (faster) - 22 (smaller) - [COMPRESSION_ALGORITHM_LZ4] = 1, // 1 (smaller) - 9 (faster) - [COMPRESSION_ALGORITHM_BROTLI] = 3, // 0 (faster) - 11 (smaller) - [COMPRESSION_ALGORITHM_GZIP] = 1, // 1 (faster) - 9 (smaller) -}; - -void rrdpush_parse_compression_order(struct receiver_state *rpt, const char *order) { +void stream_parse_compression_order(struct stream_receiver_config *config, const char *order) { // empty all slots for(size_t i = 0; i < COMPRESSION_ALGORITHM_MAX ;i++) - rpt->config.compression_priorities[i] = STREAM_CAP_NONE; + config->compression.priorities[i] = STREAM_CAP_NONE; char *s = strdupz(order); @@ -37,19 +32,19 @@ void rrdpush_parse_compression_order(struct receiver_state *rpt, const char *ord STREAM_CAPABILITIES added = STREAM_CAP_NONE; for(size_t i = 0; i < num_words && slot < COMPRESSION_ALGORITHM_MAX ;i++) { if((STREAM_CAP_ZSTD_AVAILABLE) && strcasecmp(words[i], "zstd") == 0 && !(added & STREAM_CAP_ZSTD)) { - rpt->config.compression_priorities[slot++] = STREAM_CAP_ZSTD; + config->compression.priorities[slot++] = STREAM_CAP_ZSTD; added |= STREAM_CAP_ZSTD; } else if((STREAM_CAP_LZ4_AVAILABLE) && strcasecmp(words[i], "lz4") == 0 && !(added & STREAM_CAP_LZ4)) { - rpt->config.compression_priorities[slot++] = STREAM_CAP_LZ4; + config->compression.priorities[slot++] = STREAM_CAP_LZ4; added |= STREAM_CAP_LZ4; } else if((STREAM_CAP_BROTLI_AVAILABLE) && strcasecmp(words[i], "brotli") == 0 && !(added & STREAM_CAP_BROTLI)) { - rpt->config.compression_priorities[slot++] = STREAM_CAP_BROTLI; + config->compression.priorities[slot++] = STREAM_CAP_BROTLI; added |= STREAM_CAP_BROTLI; } else if(strcasecmp(words[i], "gzip") == 0 && !(added & STREAM_CAP_GZIP)) { - rpt->config.compression_priorities[slot++] = STREAM_CAP_GZIP; + config->compression.priorities[slot++] = STREAM_CAP_GZIP; added |= STREAM_CAP_GZIP; } } @@ -58,24 +53,24 @@ void rrdpush_parse_compression_order(struct receiver_state *rpt, const char *ord // make sure all participate if((STREAM_CAP_ZSTD_AVAILABLE) && slot < COMPRESSION_ALGORITHM_MAX && !(added & STREAM_CAP_ZSTD)) - rpt->config.compression_priorities[slot++] = STREAM_CAP_ZSTD; + config->compression.priorities[slot++] = STREAM_CAP_ZSTD; if((STREAM_CAP_LZ4_AVAILABLE) && slot < COMPRESSION_ALGORITHM_MAX && !(added & STREAM_CAP_LZ4)) - rpt->config.compression_priorities[slot++] = STREAM_CAP_LZ4; + config->compression.priorities[slot++] = STREAM_CAP_LZ4; if((STREAM_CAP_BROTLI_AVAILABLE) && slot < COMPRESSION_ALGORITHM_MAX && !(added & STREAM_CAP_BROTLI)) - rpt->config.compression_priorities[slot++] = STREAM_CAP_BROTLI; + config->compression.priorities[slot++] = STREAM_CAP_BROTLI; if(slot < COMPRESSION_ALGORITHM_MAX && !(added & STREAM_CAP_GZIP)) - rpt->config.compression_priorities[slot++] = STREAM_CAP_GZIP; + config->compression.priorities[slot++] = STREAM_CAP_GZIP; } -void rrdpush_select_receiver_compression_algorithm(struct receiver_state *rpt) { - if (!rpt->config.rrdpush_compression) +void stream_select_receiver_compression_algorithm(struct receiver_state *rpt) { + if (!rpt->config.compression.enabled) rpt->capabilities &= ~STREAM_CAP_COMPRESSIONS_AVAILABLE; // select the right compression before sending our capabilities to the child if(stream_has_more_than_one_capability_of(rpt->capabilities, STREAM_CAP_COMPRESSIONS_AVAILABLE)) { STREAM_CAPABILITIES compressions = rpt->capabilities & STREAM_CAP_COMPRESSIONS_AVAILABLE; for(int i = 0; i < COMPRESSION_ALGORITHM_MAX; i++) { - STREAM_CAPABILITIES c = rpt->config.compression_priorities[i]; + STREAM_CAPABILITIES c = rpt->config.compression.priorities[i]; if(!(c & STREAM_CAP_COMPRESSIONS_AVAILABLE)) continue; @@ -91,8 +86,8 @@ void rrdpush_select_receiver_compression_algorithm(struct receiver_state *rpt) { } } -bool rrdpush_compression_initialize(struct sender_state *s) { - rrdpush_compressor_destroy(&s->compressor); +bool stream_compression_initialize(struct sender_state *s) { + stream_compressor_destroy(&s->compressor); // IMPORTANT // KEEP THE SAME ORDER IN DECOMPRESSION @@ -109,33 +104,33 @@ bool rrdpush_compression_initialize(struct sender_state *s) { s->compressor.algorithm = COMPRESSION_ALGORITHM_NONE; if(s->compressor.algorithm != COMPRESSION_ALGORITHM_NONE) { - s->compressor.level = rrdpush_compression_levels[s->compressor.algorithm]; - rrdpush_compressor_init(&s->compressor); + s->compressor.level = stream_send.compression.levels[s->compressor.algorithm]; + stream_compressor_init(&s->compressor); return true; } return false; } -bool rrdpush_decompression_initialize(struct receiver_state *rpt) { - rrdpush_decompressor_destroy(&rpt->decompressor); +bool stream_decompression_initialize(struct receiver_state *rpt) { + stream_decompressor_destroy(&rpt->thread.compressed.decompressor); // IMPORTANT // KEEP THE SAME ORDER IN COMPRESSION if(stream_has_capability(rpt, STREAM_CAP_ZSTD)) - rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_ZSTD; + rpt->thread.compressed.decompressor.algorithm = COMPRESSION_ALGORITHM_ZSTD; else if(stream_has_capability(rpt, STREAM_CAP_LZ4)) - rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_LZ4; + rpt->thread.compressed.decompressor.algorithm = COMPRESSION_ALGORITHM_LZ4; else if(stream_has_capability(rpt, STREAM_CAP_BROTLI)) - rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_BROTLI; + rpt->thread.compressed.decompressor.algorithm = COMPRESSION_ALGORITHM_BROTLI; else if(stream_has_capability(rpt, STREAM_CAP_GZIP)) - rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_GZIP; + rpt->thread.compressed.decompressor.algorithm = COMPRESSION_ALGORITHM_GZIP; else - rpt->decompressor.algorithm = COMPRESSION_ALGORITHM_NONE; + rpt->thread.compressed.decompressor.algorithm = COMPRESSION_ALGORITHM_NONE; - if(rpt->decompressor.algorithm != COMPRESSION_ALGORITHM_NONE) { - rrdpush_decompressor_init(&rpt->decompressor); + if(rpt->thread.compressed.decompressor.algorithm != COMPRESSION_ALGORITHM_NONE) { + stream_decompressor_init(&rpt->thread.compressed.decompressor); return true; } @@ -147,7 +142,7 @@ bool rrdpush_decompression_initialize(struct receiver_state *rpt) { * Inform the user through the error log file and * deactivate compression by downgrading the stream protocol. */ -void rrdpush_compression_deactivate(struct sender_state *s) { +void stream_compression_deactivate(struct sender_state *s) { switch(s->compressor.algorithm) { case COMPRESSION_ALGORITHM_MAX: case COMPRESSION_ALGORITHM_NONE: @@ -184,29 +179,29 @@ void rrdpush_compression_deactivate(struct sender_state *s) { // ---------------------------------------------------------------------------- // compressor public API -void rrdpush_compressor_init(struct compressor_state *state) { +void stream_compressor_init(struct compressor_state *state) { switch(state->algorithm) { #ifdef ENABLE_ZSTD case COMPRESSION_ALGORITHM_ZSTD: - rrdpush_compressor_init_zstd(state); + stream_compressor_init_zstd(state); break; #endif #ifdef ENABLE_LZ4 case COMPRESSION_ALGORITHM_LZ4: - rrdpush_compressor_init_lz4(state); + stream_compressor_init_lz4(state); break; #endif #ifdef ENABLE_BROTLI case COMPRESSION_ALGORITHM_BROTLI: - rrdpush_compressor_init_brotli(state); + stream_compressor_init_brotli(state); break; #endif default: case COMPRESSION_ALGORITHM_GZIP: - rrdpush_compressor_init_gzip(state); + stream_compressor_init_gzip(state); break; } @@ -214,29 +209,29 @@ void rrdpush_compressor_init(struct compressor_state *state) { simple_ring_buffer_reset(&state->output); } -void rrdpush_compressor_destroy(struct compressor_state *state) { +void stream_compressor_destroy(struct compressor_state *state) { switch(state->algorithm) { #ifdef ENABLE_ZSTD case COMPRESSION_ALGORITHM_ZSTD: - rrdpush_compressor_destroy_zstd(state); + stream_compressor_destroy_zstd(state); break; #endif #ifdef ENABLE_LZ4 case COMPRESSION_ALGORITHM_LZ4: - rrdpush_compressor_destroy_lz4(state); + stream_compressor_destroy_lz4(state); break; #endif #ifdef ENABLE_BROTLI case COMPRESSION_ALGORITHM_BROTLI: - rrdpush_compressor_destroy_brotli(state); + stream_compressor_destroy_brotli(state); break; #endif default: case COMPRESSION_ALGORITHM_GZIP: - rrdpush_compressor_destroy_gzip(state); + stream_compressor_destroy_gzip(state); break; } @@ -246,36 +241,36 @@ void rrdpush_compressor_destroy(struct compressor_state *state) { simple_ring_buffer_destroy(&state->output); } -size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, const char **out) { +size_t stream_compress(struct compressor_state *state, const char *data, size_t size, const char **out) { size_t ret = 0; switch(state->algorithm) { #ifdef ENABLE_ZSTD case COMPRESSION_ALGORITHM_ZSTD: - ret = rrdpush_compress_zstd(state, data, size, out); + ret = stream_compress_zstd(state, data, size, out); break; #endif #ifdef ENABLE_LZ4 case COMPRESSION_ALGORITHM_LZ4: - ret = rrdpush_compress_lz4(state, data, size, out); + ret = stream_compress_lz4(state, data, size, out); break; #endif #ifdef ENABLE_BROTLI case COMPRESSION_ALGORITHM_BROTLI: - ret = rrdpush_compress_brotli(state, data, size, out); + ret = stream_compress_brotli(state, data, size, out); break; #endif default: case COMPRESSION_ALGORITHM_GZIP: - ret = rrdpush_compress_gzip(state, data, size, out); + ret = stream_compress_gzip(state, data, size, out); break; } if(unlikely(ret >= COMPRESSION_MAX_CHUNK)) { - netdata_log_error("RRDPUSH_COMPRESS: compressed data is %zu bytes, which is >= than the max chunk size %d", + netdata_log_error("STREAM_COMPRESS: compressed data is %zu bytes, which is >= than the max chunk size %d", ret, COMPRESSION_MAX_CHUNK); return 0; } @@ -286,32 +281,32 @@ size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t // ---------------------------------------------------------------------------- // decompressor public API -void rrdpush_decompressor_destroy(struct decompressor_state *state) { +void stream_decompressor_destroy(struct decompressor_state *state) { if(unlikely(!state->initialized)) return; switch(state->algorithm) { #ifdef ENABLE_ZSTD case COMPRESSION_ALGORITHM_ZSTD: - rrdpush_decompressor_destroy_zstd(state); + stream_decompressor_destroy_zstd(state); break; #endif #ifdef ENABLE_LZ4 case COMPRESSION_ALGORITHM_LZ4: - rrdpush_decompressor_destroy_lz4(state); + stream_decompressor_destroy_lz4(state); break; #endif #ifdef ENABLE_BROTLI case COMPRESSION_ALGORITHM_BROTLI: - rrdpush_decompressor_destroy_brotli(state); + stream_decompressor_destroy_brotli(state); break; #endif default: case COMPRESSION_ALGORITHM_GZIP: - rrdpush_decompressor_destroy_gzip(state); + stream_decompressor_destroy_gzip(state); break; } @@ -320,71 +315,71 @@ void rrdpush_decompressor_destroy(struct decompressor_state *state) { state->initialized = false; } -void rrdpush_decompressor_init(struct decompressor_state *state) { +void stream_decompressor_init(struct decompressor_state *state) { switch(state->algorithm) { #ifdef ENABLE_ZSTD case COMPRESSION_ALGORITHM_ZSTD: - rrdpush_decompressor_init_zstd(state); + stream_decompressor_init_zstd(state); break; #endif #ifdef ENABLE_LZ4 case COMPRESSION_ALGORITHM_LZ4: - rrdpush_decompressor_init_lz4(state); + stream_decompressor_init_lz4(state); break; #endif #ifdef ENABLE_BROTLI case COMPRESSION_ALGORITHM_BROTLI: - rrdpush_decompressor_init_brotli(state); + stream_decompressor_init_brotli(state); break; #endif default: case COMPRESSION_ALGORITHM_GZIP: - rrdpush_decompressor_init_gzip(state); + stream_decompressor_init_gzip(state); break; } - state->signature_size = RRDPUSH_COMPRESSION_SIGNATURE_SIZE; + state->signature_size = STREAM_COMPRESSION_SIGNATURE_SIZE; simple_ring_buffer_reset(&state->output); } -size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { +size_t stream_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { if (unlikely(state->output.read_pos != state->output.write_pos)) - fatal("RRDPUSH_DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!"); + fatal("STREAM_DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!"); size_t ret = 0; switch(state->algorithm) { #ifdef ENABLE_ZSTD case COMPRESSION_ALGORITHM_ZSTD: - ret = rrdpush_decompress_zstd(state, compressed_data, compressed_size); + ret = stream_decompress_zstd(state, compressed_data, compressed_size); break; #endif #ifdef ENABLE_LZ4 case COMPRESSION_ALGORITHM_LZ4: - ret = rrdpush_decompress_lz4(state, compressed_data, compressed_size); + ret = stream_decompress_lz4(state, compressed_data, compressed_size); break; #endif #ifdef ENABLE_BROTLI case COMPRESSION_ALGORITHM_BROTLI: - ret = rrdpush_decompress_brotli(state, compressed_data, compressed_size); + ret = stream_decompress_brotli(state, compressed_data, compressed_size); break; #endif default: case COMPRESSION_ALGORITHM_GZIP: - ret = rrdpush_decompress_gzip(state, compressed_data, compressed_size); + ret = stream_decompress_gzip(state, compressed_data, compressed_size); break; } // for backwards compatibility we cannot check for COMPRESSION_MAX_MSG_SIZE, // because old children may send this big payloads. if(unlikely(ret > COMPRESSION_MAX_CHUNK)) { - netdata_log_error("RRDPUSH_DECOMPRESS: decompressed data is %zu bytes, which is bigger than the max msg size %d", + netdata_log_error("STREAM_DECOMPRESS: decompressed data is %zu bytes, which is bigger than the max msg size %d", ret, COMPRESSION_MAX_CHUNK); return 0; } @@ -478,7 +473,7 @@ void unittest_generate_message(BUFFER *wb, time_t now_s, size_t counter) { buffer_fast_strcat(wb, PLUGINSD_KEYWORD_END_V2 "\n", sizeof(PLUGINSD_KEYWORD_END_V2) - 1 + 1); } -int unittest_rrdpush_compression_speed(compression_algorithm_t algorithm, const char *name) { +int unittest_stream_compression_speed(compression_algorithm_t algorithm, const char *name) { fprintf(stderr, "\nTesting streaming compression speed with %s\n", name); struct compressor_state cctx = { @@ -490,8 +485,8 @@ int unittest_rrdpush_compression_speed(compression_algorithm_t algorithm, const .algorithm = algorithm, }; - rrdpush_compressor_init(&cctx); - rrdpush_decompressor_init(&dctx); + stream_compressor_init(&cctx); + stream_decompressor_init(&dctx); int errors = 0; @@ -518,7 +513,7 @@ int unittest_rrdpush_compression_speed(compression_algorithm_t algorithm, const bytes_uncompressed += txt_len; const char *out; - size_t size = rrdpush_compress(&cctx, txt, txt_len, &out); + size_t size = stream_compress(&cctx, txt, txt_len, &out); bytes_compressed += size; decompression_started_ut = now_monotonic_usec(); @@ -537,13 +532,12 @@ int unittest_rrdpush_compression_speed(compression_algorithm_t algorithm, const goto cleanup; } else { - size_t dtxt_len = rrdpush_decompress(&dctx, out, size); + size_t dtxt_len = stream_decompress(&dctx, out, size); char *dtxt = (char *) &dctx.output.data[dctx.output.read_pos]; - if(rrdpush_decompressed_bytes_in_buffer(&dctx) != dtxt_len) { - fprintf(stderr, "iteration %d: decompressed size %zu does not rrdpush_decompressed_bytes_in_buffer() %zu\n", - i, dtxt_len, rrdpush_decompressed_bytes_in_buffer(&dctx) - ); + if(stream_decompressed_bytes_in_buffer(&dctx) != dtxt_len) { + fprintf(stderr, "iteration %d: decompressed size %zu does not stream_decompressed_bytes_in_buffer() %zu\n", + i, dtxt_len, stream_decompressed_bytes_in_buffer(&dctx)); errors++; goto cleanup; } @@ -571,12 +565,12 @@ int unittest_rrdpush_compression_speed(compression_algorithm_t algorithm, const } // here we are supposed to copy the data and advance the position - dctx.output.read_pos += rrdpush_decompressed_bytes_in_buffer(&dctx); + dctx.output.read_pos += stream_decompressed_bytes_in_buffer(&dctx); } cleanup: - rrdpush_compressor_destroy(&cctx); - rrdpush_decompressor_destroy(&dctx); + stream_compressor_destroy(&cctx); + stream_decompressor_destroy(&dctx); if(errors) fprintf(stderr, "Compression with %s: FAILED (%d errors)\n", name, errors); @@ -590,7 +584,7 @@ int unittest_rrdpush_compression_speed(compression_algorithm_t algorithm, const return errors; } -int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char *name) { +int unittest_stream_compression(compression_algorithm_t algorithm, const char *name) { fprintf(stderr, "\nTesting streaming compression with %s\n", name); struct compressor_state cctx = { @@ -604,8 +598,8 @@ int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char * char txt[COMPRESSION_MAX_MSG_SIZE]; - rrdpush_compressor_init(&cctx); - rrdpush_decompressor_init(&dctx); + stream_compressor_init(&cctx); + stream_decompressor_init(&dctx); int errors = 0; @@ -616,7 +610,7 @@ int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char * size_t txt_len = i + 1; const char *out; - size_t size = rrdpush_compress(&cctx, txt, txt_len, &out); + size_t size = stream_compress(&cctx, txt, txt_len, &out); if(size == 0) { fprintf(stderr, "iteration %d: compressed size %zu is zero\n", @@ -631,12 +625,13 @@ int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char * goto cleanup; } else { - size_t dtxt_len = rrdpush_decompress(&dctx, out, size); + size_t dtxt_len = stream_decompress(&dctx, out, size); char *dtxt = (char *) &dctx.output.data[dctx.output.read_pos]; - if(rrdpush_decompressed_bytes_in_buffer(&dctx) != dtxt_len) { - fprintf(stderr, "iteration %d: decompressed size %zu does not rrdpush_decompressed_bytes_in_buffer() %zu\n", - i, dtxt_len, rrdpush_decompressed_bytes_in_buffer(&dctx) + if(stream_decompressed_bytes_in_buffer(&dctx) != dtxt_len) { + fprintf(stderr, "iteration %d: decompressed size %zu does not stream_decompressed_bytes_in_buffer() %zu\n", + i, dtxt_len, + stream_decompressed_bytes_in_buffer(&dctx) ); errors++; goto cleanup; @@ -671,12 +666,12 @@ int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char * memset((void *)out, 'x', size); // here we are supposed to copy the data and advance the position - dctx.output.read_pos += rrdpush_decompressed_bytes_in_buffer(&dctx); + dctx.output.read_pos += stream_decompressed_bytes_in_buffer(&dctx); } cleanup: - rrdpush_compressor_destroy(&cctx); - rrdpush_decompressor_destroy(&dctx); + stream_compressor_destroy(&cctx); + stream_decompressor_destroy(&dctx); if(errors) fprintf(stderr, "Compression with %s: FAILED (%d errors)\n", name, errors); @@ -686,18 +681,18 @@ int unittest_rrdpush_compression(compression_algorithm_t algorithm, const char * return errors; } -int unittest_rrdpush_compressions(void) { +int unittest_stream_compressions(void) { int ret = 0; - ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_ZSTD, "ZSTD"); - ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_LZ4, "LZ4"); - ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_BROTLI, "BROTLI"); - ret += unittest_rrdpush_compression(COMPRESSION_ALGORITHM_GZIP, "GZIP"); + ret += unittest_stream_compression(COMPRESSION_ALGORITHM_ZSTD, "ZSTD"); + ret += unittest_stream_compression(COMPRESSION_ALGORITHM_LZ4, "LZ4"); + ret += unittest_stream_compression(COMPRESSION_ALGORITHM_BROTLI, "BROTLI"); + ret += unittest_stream_compression(COMPRESSION_ALGORITHM_GZIP, "GZIP"); - ret += unittest_rrdpush_compression_speed(COMPRESSION_ALGORITHM_ZSTD, "ZSTD"); - ret += unittest_rrdpush_compression_speed(COMPRESSION_ALGORITHM_LZ4, "LZ4"); - ret += unittest_rrdpush_compression_speed(COMPRESSION_ALGORITHM_BROTLI, "BROTLI"); - ret += unittest_rrdpush_compression_speed(COMPRESSION_ALGORITHM_GZIP, "GZIP"); + ret += unittest_stream_compression_speed(COMPRESSION_ALGORITHM_ZSTD, "ZSTD"); + ret += unittest_stream_compression_speed(COMPRESSION_ALGORITHM_LZ4, "LZ4"); + ret += unittest_stream_compression_speed(COMPRESSION_ALGORITHM_BROTLI, "BROTLI"); + ret += unittest_stream_compression_speed(COMPRESSION_ALGORITHM_GZIP, "GZIP"); return ret; } diff --git a/src/streaming/stream-compression/compression.h b/src/streaming/stream-compression/compression.h index 37f589b8562e9e..4e65dfd908f26e 100644 --- a/src/streaming/stream-compression/compression.h +++ b/src/streaming/stream-compression/compression.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef NETDATA_RRDPUSH_COMPRESSION_H -#define NETDATA_RRDPUSH_COMPRESSION_H 1 +#ifndef NETDATA_STREAM_COMPRESSION_H +#define NETDATA_STREAM_COMPRESSION_H 1 #include "libnetdata/libnetdata.h" @@ -11,14 +11,14 @@ #error "COMPRESSION_MAX_MSG_SIZE >= (COMPRESSION_MAX_CHUNK - COMPRESSION_MAX_OVERHEAD)" #endif -typedef uint32_t rrdpush_signature_t; -#define RRDPUSH_COMPRESSION_SIGNATURE ((rrdpush_signature_t)('z' | 0x80) | (0x80 << 8) | (0x80 << 16) | ('\n' << 24)) -#define RRDPUSH_COMPRESSION_SIGNATURE_MASK ((rrdpush_signature_t) 0xffU | (0x80U << 8) | (0x80U << 16) | (0xffU << 24)) -#define RRDPUSH_COMPRESSION_SIGNATURE_SIZE sizeof(rrdpush_signature_t) +typedef uint32_t stream_compression_signature_t; +#define STREAM_COMPRESSION_SIGNATURE ((stream_compression_signature_t)('z' | 0x80) | (0x80 << 8) | (0x80 << 16) | ('\n' << 24)) +#define STREAM_COMPRESSION_SIGNATURE_MASK ((stream_compression_signature_t) 0xffU | (0x80U << 8) | (0x80U << 16) | (0xffU << 24)) +#define STREAM_COMPRESSION_SIGNATURE_SIZE sizeof(stream_compression_signature_t) -static inline rrdpush_signature_t rrdpush_compress_encode_signature(size_t compressed_data_size) { - rrdpush_signature_t len = ((compressed_data_size & 0x7f) | 0x80 | (((compressed_data_size & (0x7f << 7)) << 1) | 0x8000)) << 8; - return len | RRDPUSH_COMPRESSION_SIGNATURE; +static inline stream_compression_signature_t stream_compress_encode_signature(size_t compressed_data_size) { + stream_compression_signature_t len = ((compressed_data_size & 0x7f) | 0x80 | (((compressed_data_size & (0x7f << 7)) << 1) | 0x8000)) << 8; + return len | STREAM_COMPRESSION_SIGNATURE; } typedef enum { @@ -32,10 +32,8 @@ typedef enum { COMPRESSION_ALGORITHM_MAX, } compression_algorithm_t; -extern int rrdpush_compression_levels[COMPRESSION_ALGORITHM_MAX]; - // this defines the order the algorithms will be selected by the receiver (parent) -#define RRDPUSH_COMPRESSION_ALGORITHMS_ORDER "zstd lz4 brotli gzip" +#define STREAM_COMPRESSION_ALGORITHMS_ORDER "zstd lz4 brotli gzip" // ---------------------------------------------------------------------------- @@ -95,9 +93,9 @@ struct compressor_state { } sender_locked; }; -void rrdpush_compressor_init(struct compressor_state *state); -void rrdpush_compressor_destroy(struct compressor_state *state); -size_t rrdpush_compress(struct compressor_state *state, const char *data, size_t size, const char **out); +void stream_compressor_init(struct compressor_state *state); +void stream_compressor_destroy(struct compressor_state *state); +size_t stream_compress(struct compressor_state *state, const char *data, size_t size, const char **out); // ---------------------------------------------------------------------------- @@ -115,44 +113,44 @@ struct decompressor_state { void *stream; }; -void rrdpush_decompressor_destroy(struct decompressor_state *state); -void rrdpush_decompressor_init(struct decompressor_state *state); -size_t rrdpush_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); +void stream_decompressor_destroy(struct decompressor_state *state); +void stream_decompressor_init(struct decompressor_state *state); +size_t stream_decompress(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); -static inline size_t rrdpush_decompress_decode_signature(const char *data, size_t data_size) { +static inline size_t stream_decompress_decode_signature(const char *data, size_t data_size) { if (unlikely(!data || !data_size)) return 0; - if (unlikely(data_size != RRDPUSH_COMPRESSION_SIGNATURE_SIZE)) + if (unlikely(data_size != STREAM_COMPRESSION_SIGNATURE_SIZE)) return 0; - rrdpush_signature_t sign = *(rrdpush_signature_t *)data; - if (unlikely((sign & RRDPUSH_COMPRESSION_SIGNATURE_MASK) != RRDPUSH_COMPRESSION_SIGNATURE)) + stream_compression_signature_t sign = *(stream_compression_signature_t *)data; + if (unlikely((sign & STREAM_COMPRESSION_SIGNATURE_MASK) != STREAM_COMPRESSION_SIGNATURE)) return 0; size_t length = ((sign >> 8) & 0x7f) | ((sign >> 9) & (0x7f << 7)); return length; } -static inline size_t rrdpush_decompressor_start(struct decompressor_state *state, const char *header, size_t header_size) { +static inline size_t stream_decompressor_start(struct decompressor_state *state, const char *header, size_t header_size) { if(unlikely(state->output.read_pos != state->output.write_pos)) - fatal("RRDPUSH DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!"); + fatal("STREAM_DECOMPRESS: asked to decompress new data, while there are unread data in the decompression buffer!"); - return rrdpush_decompress_decode_signature(header, header_size); + return stream_decompress_decode_signature(header, header_size); } -static inline size_t rrdpush_decompressed_bytes_in_buffer(struct decompressor_state *state) { +static inline size_t stream_decompressed_bytes_in_buffer(struct decompressor_state *state) { if(unlikely(state->output.read_pos > state->output.write_pos)) - fatal("RRDPUSH DECOMPRESS: invalid read/write stream positions"); + fatal("STREAM_DECOMPRESS: invalid read/write stream positions"); return state->output.write_pos - state->output.read_pos; } -static inline size_t rrdpush_decompressor_get(struct decompressor_state *state, char *dst, size_t size) { +static inline size_t stream_decompressor_get(struct decompressor_state *state, char *dst, size_t size) { if (unlikely(!state || !size || !dst)) return 0; - size_t remaining = rrdpush_decompressed_bytes_in_buffer(state); + size_t remaining = stream_decompressed_bytes_in_buffer(state); if(unlikely(!remaining)) return 0; @@ -165,19 +163,21 @@ static inline size_t rrdpush_decompressor_get(struct decompressor_state *state, state->output.read_pos += bytes_to_return; if(unlikely(state->output.read_pos > state->output.write_pos)) - fatal("RRDPUSH DECOMPRESS: invalid read/write stream positions"); + fatal("STREAM_DECOMPRESS: invalid read/write stream positions"); return bytes_to_return; } // ---------------------------------------------------------------------------- -#include "../rrdpush.h" +struct sender_state; +struct receiver_state; +struct stream_receiver_config; -bool rrdpush_compression_initialize(struct sender_state *s); -bool rrdpush_decompression_initialize(struct receiver_state *rpt); -void rrdpush_parse_compression_order(struct receiver_state *rpt, const char *order); -void rrdpush_select_receiver_compression_algorithm(struct receiver_state *rpt); -void rrdpush_compression_deactivate(struct sender_state *s); +bool stream_compression_initialize(struct sender_state *s); +bool stream_decompression_initialize(struct receiver_state *rpt); +void stream_parse_compression_order(struct stream_receiver_config *config, const char *order); +void stream_select_receiver_compression_algorithm(struct receiver_state *rpt); +void stream_compression_deactivate(struct sender_state *s); -#endif // NETDATA_RRDPUSH_COMPRESSION_H 1 +#endif // NETDATA_STREAM_COMPRESSION_H 1 diff --git a/src/streaming/stream-compression/gzip.c b/src/streaming/stream-compression/gzip.c index d63e9afbe3e197..af059b553426d3 100644 --- a/src/streaming/stream-compression/gzip.c +++ b/src/streaming/stream-compression/gzip.c @@ -3,7 +3,7 @@ #include "gzip.h" #include -void rrdpush_compressor_init_gzip(struct compressor_state *state) { +void stream_compressor_init_gzip(struct compressor_state *state) { if (!state->initialized) { state->initialized = true; @@ -22,7 +22,7 @@ void rrdpush_compressor_init_gzip(struct compressor_state *state) { // int r = deflateInit2(strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); int r = deflateInit2(strm, state->level, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); if (r != Z_OK) { - netdata_log_error("Failed to initialize deflate with error: %d", r); + netdata_log_error("STREAM_COMPRESS: Failed to initialize deflate with error: %d", r); freez(state->stream); state->initialized = false; return; @@ -31,7 +31,7 @@ void rrdpush_compressor_init_gzip(struct compressor_state *state) { } } -void rrdpush_compressor_destroy_gzip(struct compressor_state *state) { +void stream_compressor_destroy_gzip(struct compressor_state *state) { if (state->stream) { deflateEnd(state->stream); freez(state->stream); @@ -39,7 +39,7 @@ void rrdpush_compressor_destroy_gzip(struct compressor_state *state) { } } -size_t rrdpush_compress_gzip(struct compressor_state *state, const char *data, size_t size, const char **out) { +size_t stream_compress_gzip(struct compressor_state *state, const char *data, size_t size, const char **out) { if (unlikely(!state || !size || !out)) return 0; @@ -53,18 +53,18 @@ size_t rrdpush_compress_gzip(struct compressor_state *state, const char *data, s int ret = deflate(strm, Z_SYNC_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) { - netdata_log_error("STREAM: deflate() failed with error %d", ret); + netdata_log_error("STREAM_COMPRESS: deflate() failed with error %d", ret); return 0; } if(strm->avail_in != 0) { - netdata_log_error("STREAM: deflate() did not use all the input buffer, %u bytes out of %zu remain", + netdata_log_error("STREAM_COMPRESS: deflate() did not use all the input buffer, %u bytes out of %zu remain", strm->avail_in, size); return 0; } if(strm->avail_out == 0) { - netdata_log_error("STREAM: deflate() needs a bigger output buffer than the one we provided " + netdata_log_error("STREAM_COMPRESS: deflate() needs a bigger output buffer than the one we provided " "(output buffer %zu bytes, compressed payload %zu bytes)", state->output.size, size); return 0; @@ -73,7 +73,7 @@ size_t rrdpush_compress_gzip(struct compressor_state *state, const char *data, s size_t compressed_data_size = state->output.size - strm->avail_out; if(compressed_data_size == 0) { - netdata_log_error("STREAM: deflate() did not produce any output " + netdata_log_error("STREAM_COMPRESS: deflate() did not produce any output " "(output buffer %zu bytes, compressed payload %zu bytes)", state->output.size, size); return 0; @@ -87,7 +87,7 @@ size_t rrdpush_compress_gzip(struct compressor_state *state, const char *data, s return compressed_data_size; } -void rrdpush_decompressor_init_gzip(struct decompressor_state *state) { +void stream_decompressor_init_gzip(struct decompressor_state *state) { if (!state->initialized) { state->initialized = true; @@ -99,7 +99,7 @@ void rrdpush_decompressor_init_gzip(struct decompressor_state *state) { int r = inflateInit2(strm, 15 + 16); if (r != Z_OK) { - netdata_log_error("Failed to initialize inflateInit2() with error: %d", r); + netdata_log_error("STREAM_DECOMPRESS: Failed to initialize inflateInit2() with error: %d", r); freez(state->stream); state->initialized = false; return; @@ -109,7 +109,7 @@ void rrdpush_decompressor_init_gzip(struct decompressor_state *state) { } } -void rrdpush_decompressor_destroy_gzip(struct decompressor_state *state) { +void stream_decompressor_destroy_gzip(struct decompressor_state *state) { if (state->stream) { inflateEnd(state->stream); freez(state->stream); @@ -117,7 +117,7 @@ void rrdpush_decompressor_destroy_gzip(struct decompressor_state *state) { } } -size_t rrdpush_decompress_gzip(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { +size_t stream_decompress_gzip(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { if (unlikely(!state || !compressed_data || !compressed_size)) return 0; @@ -133,19 +133,19 @@ size_t rrdpush_decompress_gzip(struct decompressor_state *state, const char *com int ret = inflate(strm, Z_SYNC_FLUSH); if (ret != Z_STREAM_END && ret != Z_OK) { - netdata_log_error("RRDPUSH DECOMPRESS: inflate() failed with error %d", ret); + netdata_log_error("STREAM_DECOMPRESS: inflate() failed with error %d", ret); return 0; } if(strm->avail_in != 0) { - netdata_log_error("RRDPUSH DECOMPRESS: inflate() did not use all compressed data we provided " + netdata_log_error("STREAM_DECOMPRESS: inflate() did not use all compressed data we provided " "(compressed payload %zu bytes, remaining to be uncompressed %u)" , compressed_size, strm->avail_in); return 0; } if(strm->avail_out == 0) { - netdata_log_error("RRDPUSH DECOMPRESS: inflate() needs a bigger output buffer than the one we provided " + netdata_log_error("STREAM_DECOMPRESS: inflate() needs a bigger output buffer than the one we provided " "(compressed payload %zu bytes, output buffer size %zu bytes)" , compressed_size, state->output.size); return 0; diff --git a/src/streaming/stream-compression/gzip.h b/src/streaming/stream-compression/gzip.h index 85f34bc6d7d8f5..97b4f224422e9b 100644 --- a/src/streaming/stream-compression/gzip.h +++ b/src/streaming/stream-compression/gzip.h @@ -5,11 +5,11 @@ #ifndef NETDATA_STREAMING_COMPRESSION_GZIP_H #define NETDATA_STREAMING_COMPRESSION_GZIP_H -void rrdpush_compressor_init_gzip(struct compressor_state *state); -void rrdpush_compressor_destroy_gzip(struct compressor_state *state); -size_t rrdpush_compress_gzip(struct compressor_state *state, const char *data, size_t size, const char **out); -size_t rrdpush_decompress_gzip(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); -void rrdpush_decompressor_init_gzip(struct decompressor_state *state); -void rrdpush_decompressor_destroy_gzip(struct decompressor_state *state); +void stream_compressor_init_gzip(struct compressor_state *state); +void stream_compressor_destroy_gzip(struct compressor_state *state); +size_t stream_compress_gzip(struct compressor_state *state, const char *data, size_t size, const char **out); +size_t stream_decompress_gzip(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); +void stream_decompressor_init_gzip(struct decompressor_state *state); +void stream_decompressor_destroy_gzip(struct decompressor_state *state); #endif //NETDATA_STREAMING_COMPRESSION_GZIP_H diff --git a/src/streaming/stream-compression/lz4.c b/src/streaming/stream-compression/lz4.c index 2841921539c495..2f7571a55bcf3f 100644 --- a/src/streaming/stream-compression/lz4.c +++ b/src/streaming/stream-compression/lz4.c @@ -1,14 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-or-later +#include "libnetdata/libnetdata.h" #include "lz4.h" #ifdef ENABLE_LZ4 -#include "lz4.h" +#include // ---------------------------------------------------------------------------- // compress -void rrdpush_compressor_init_lz4(struct compressor_state *state) { +void stream_compressor_init_lz4(struct compressor_state *state) { if(!state->initialized) { state->initialized = true; state->stream = LZ4_createStream(); @@ -19,7 +20,7 @@ void rrdpush_compressor_init_lz4(struct compressor_state *state) { } } -void rrdpush_compressor_destroy_lz4(struct compressor_state *state) { +void stream_compressor_destroy_lz4(struct compressor_state *state) { if (state->stream) { LZ4_freeStream(state->stream); state->stream = NULL; @@ -32,7 +33,7 @@ void rrdpush_compressor_destroy_lz4(struct compressor_state *state) { * Return the size of compressed data block as result and the pointer to internal buffer using the last argument * or 0 in case of error */ -size_t rrdpush_compress_lz4(struct compressor_state *state, const char *data, size_t size, const char **out) { +size_t stream_compress_lz4(struct compressor_state *state, const char *data, size_t size, const char **out) { if(unlikely(!state || !size || !out)) return 0; @@ -56,7 +57,7 @@ size_t rrdpush_compress_lz4(struct compressor_state *state, const char *data, si state->level); if (compressed_data_size <= 0) { - netdata_log_error("STREAM: LZ4_compress_fast_continue() returned %ld " + netdata_log_error("STREAM_COMPRESS: LZ4_compress_fast_continue() returned %ld " "(source is %zu bytes, output buffer can fit %zu bytes)", compressed_data_size, size, state->output.size); return 0; @@ -75,7 +76,7 @@ size_t rrdpush_compress_lz4(struct compressor_state *state, const char *data, si // ---------------------------------------------------------------------------- // decompress -void rrdpush_decompressor_init_lz4(struct decompressor_state *state) { +void stream_decompressor_init_lz4(struct decompressor_state *state) { if(!state->initialized) { state->initialized = true; state->stream = LZ4_createStreamDecode(); @@ -83,7 +84,7 @@ void rrdpush_decompressor_init_lz4(struct decompressor_state *state) { } } -void rrdpush_decompressor_destroy_lz4(struct decompressor_state *state) { +void stream_decompressor_destroy_lz4(struct decompressor_state *state) { if (state->stream) { LZ4_freeStreamDecode(state->stream); state->stream = NULL; @@ -94,7 +95,7 @@ void rrdpush_decompressor_destroy_lz4(struct decompressor_state *state) { * Decompress the compressed data in the internal buffer * Return the size of uncompressed data or 0 for error */ -size_t rrdpush_decompress_lz4(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { +size_t stream_decompress_lz4(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { if (unlikely(!state || !compressed_data || !compressed_size)) return 0; @@ -115,14 +116,14 @@ size_t rrdpush_decompress_lz4(struct decompressor_state *state, const char *comp ); if (unlikely(decompressed_size < 0)) { - netdata_log_error("RRDPUSH DECOMPRESS: LZ4_decompress_safe_continue() returned negative value: %ld " + netdata_log_error("STREAM_DECOMPRESS: LZ4_decompress_safe_continue() returned negative value: %ld " "(compressed chunk is %zu bytes)" , decompressed_size, compressed_size); return 0; } if(unlikely(decompressed_size + state->output.write_pos > state->output.size)) - fatal("RRDPUSH DECOMPRESS: LZ4_decompress_safe_continue() overflown the stream_buffer " + fatal("STREAM_DECOMPRESS: LZ4_decompress_safe_continue() overflown the stream_buffer " "(size: %zu, pos: %zu, added: %ld, exceeding the buffer by %zu)" , state->output.size , state->output.write_pos diff --git a/src/streaming/stream-compression/lz4.h b/src/streaming/stream-compression/lz4.h index 69f0fadccf259e..ba6e92a95f39e3 100644 --- a/src/streaming/stream-compression/lz4.h +++ b/src/streaming/stream-compression/lz4.h @@ -7,12 +7,12 @@ #ifdef ENABLE_LZ4 -void rrdpush_compressor_init_lz4(struct compressor_state *state); -void rrdpush_compressor_destroy_lz4(struct compressor_state *state); -size_t rrdpush_compress_lz4(struct compressor_state *state, const char *data, size_t size, const char **out); -size_t rrdpush_decompress_lz4(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); -void rrdpush_decompressor_init_lz4(struct decompressor_state *state); -void rrdpush_decompressor_destroy_lz4(struct decompressor_state *state); +void stream_compressor_init_lz4(struct compressor_state *state); +void stream_compressor_destroy_lz4(struct compressor_state *state); +size_t stream_compress_lz4(struct compressor_state *state, const char *data, size_t size, const char **out); +size_t stream_decompress_lz4(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); +void stream_decompressor_init_lz4(struct decompressor_state *state); +void stream_decompressor_destroy_lz4(struct decompressor_state *state); #endif // ENABLE_LZ4 diff --git a/src/streaming/stream-compression/zstd.c b/src/streaming/stream-compression/zstd.c index 0ce27c0d3b2c58..35494d5500b22e 100644 --- a/src/streaming/stream-compression/zstd.c +++ b/src/streaming/stream-compression/zstd.c @@ -5,7 +5,7 @@ #ifdef ENABLE_ZSTD #include -void rrdpush_compressor_init_zstd(struct compressor_state *state) { +void stream_compressor_init_zstd(struct compressor_state *state) { if(!state->initialized) { state->initialized = true; state->stream = ZSTD_createCStream(); @@ -18,21 +18,21 @@ void rrdpush_compressor_init_zstd(struct compressor_state *state) { size_t ret = ZSTD_initCStream(state->stream, state->level); if(ZSTD_isError(ret)) - netdata_log_error("STREAM: ZSTD_initCStream() returned error: %s", ZSTD_getErrorName(ret)); + netdata_log_error("STREAM_COMPRESS: ZSTD_initCStream() returned error: %s", ZSTD_getErrorName(ret)); // ZSTD_CCtx_setParameter(state->stream, ZSTD_c_compressionLevel, 1); // ZSTD_CCtx_setParameter(state->stream, ZSTD_c_strategy, ZSTD_fast); } } -void rrdpush_compressor_destroy_zstd(struct compressor_state *state) { +void stream_compressor_destroy_zstd(struct compressor_state *state) { if(state->stream) { ZSTD_freeCStream(state->stream); state->stream = NULL; } } -size_t rrdpush_compress_zstd(struct compressor_state *state, const char *data, size_t size, const char **out) { +size_t stream_compress_zstd(struct compressor_state *state, const char *data, size_t size, const char **out) { if(unlikely(!state || !size || !out)) return 0; @@ -56,12 +56,12 @@ size_t rrdpush_compress_zstd(struct compressor_state *state, const char *data, s // error handling if(ZSTD_isError(ret)) { - netdata_log_error("STREAM: ZSTD_compressStream() return error: %s", ZSTD_getErrorName(ret)); + netdata_log_error("STREAM_COMPRESS: ZSTD_compressStream() return error: %s", ZSTD_getErrorName(ret)); return 0; } if(inBuffer.pos < inBuffer.size) { - netdata_log_error("STREAM: ZSTD_compressStream() left unprocessed input (source payload %zu bytes, consumed %zu bytes)", + netdata_log_error("STREAM_COMPRESS: ZSTD_compressStream() left unprocessed input (source payload %zu bytes, consumed %zu bytes)", inBuffer.size, inBuffer.pos); return 0; } @@ -71,12 +71,12 @@ size_t rrdpush_compress_zstd(struct compressor_state *state, const char *data, s ret = ZSTD_flushStream(state->stream, &outBuffer); if(ZSTD_isError(ret)) { - netdata_log_error("STREAM: ZSTD_flushStream() return error: %s", ZSTD_getErrorName(ret)); + netdata_log_error("STREAM_COMPRESS: ZSTD_flushStream() return error: %s", ZSTD_getErrorName(ret)); return 0; } if(outBuffer.pos == 0) { - netdata_log_error("STREAM: ZSTD_compressStream() returned zero compressed bytes " + netdata_log_error("STREAM_COMPRESS: ZSTD_compressStream() returned zero compressed bytes " "(source is %zu bytes, output buffer can fit %zu bytes) " , size, outBuffer.size); return 0; @@ -92,27 +92,27 @@ size_t rrdpush_compress_zstd(struct compressor_state *state, const char *data, s return outBuffer.pos; } -void rrdpush_decompressor_init_zstd(struct decompressor_state *state) { +void stream_decompressor_init_zstd(struct decompressor_state *state) { if(!state->initialized) { state->initialized = true; state->stream = ZSTD_createDStream(); size_t ret = ZSTD_initDStream(state->stream); if(ZSTD_isError(ret)) - netdata_log_error("STREAM: ZSTD_initDStream() returned error: %s", ZSTD_getErrorName(ret)); + netdata_log_error("STREAM_DECOMPRESS: ZSTD_initDStream() returned error: %s", ZSTD_getErrorName(ret)); simple_ring_buffer_make_room(&state->output, MAX(COMPRESSION_MAX_CHUNK, ZSTD_DStreamOutSize())); } } -void rrdpush_decompressor_destroy_zstd(struct decompressor_state *state) { +void stream_decompressor_destroy_zstd(struct decompressor_state *state) { if (state->stream) { ZSTD_freeDStream(state->stream); state->stream = NULL; } } -size_t rrdpush_decompress_zstd(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { +size_t stream_decompress_zstd(struct decompressor_state *state, const char *compressed_data, size_t compressed_size) { if (unlikely(!state || !compressed_data || !compressed_size)) return 0; @@ -138,12 +138,12 @@ size_t rrdpush_decompress_zstd(struct decompressor_state *state, const char *com , &inBuffer); if(ZSTD_isError(ret)) { - netdata_log_error("STREAM: ZSTD_decompressStream() return error: %s", ZSTD_getErrorName(ret)); + netdata_log_error("STREAM_DECOMPRESS: ZSTD_decompressStream() return error: %s", ZSTD_getErrorName(ret)); return 0; } if(inBuffer.pos < inBuffer.size) - fatal("RRDPUSH DECOMPRESS: ZSTD ZSTD_decompressStream() decompressed %zu bytes, " + fatal("STREAM_DECOMPRESS: ZSTD ZSTD_decompressStream() decompressed %zu bytes, " "but %zu bytes of compressed data remain", inBuffer.pos, inBuffer.size); diff --git a/src/streaming/stream-compression/zstd.h b/src/streaming/stream-compression/zstd.h index bfabbf89d52330..499d6860dae724 100644 --- a/src/streaming/stream-compression/zstd.h +++ b/src/streaming/stream-compression/zstd.h @@ -7,12 +7,12 @@ #ifdef ENABLE_ZSTD -void rrdpush_compressor_init_zstd(struct compressor_state *state); -void rrdpush_compressor_destroy_zstd(struct compressor_state *state); -size_t rrdpush_compress_zstd(struct compressor_state *state, const char *data, size_t size, const char **out); -size_t rrdpush_decompress_zstd(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); -void rrdpush_decompressor_init_zstd(struct decompressor_state *state); -void rrdpush_decompressor_destroy_zstd(struct decompressor_state *state); +void stream_compressor_init_zstd(struct compressor_state *state); +void stream_compressor_destroy_zstd(struct compressor_state *state); +size_t stream_compress_zstd(struct compressor_state *state, const char *data, size_t size, const char **out); +size_t stream_decompress_zstd(struct decompressor_state *state, const char *compressed_data, size_t compressed_size); +void stream_decompressor_init_zstd(struct decompressor_state *state); +void stream_decompressor_destroy_zstd(struct decompressor_state *state); #endif // ENABLE_ZSTD diff --git a/src/streaming/stream-conf.c b/src/streaming/stream-conf.c index 8fc9e081965ee1..9630b02d04d170 100644 --- a/src/streaming/stream-conf.c +++ b/src/streaming/stream-conf.c @@ -1,28 +1,48 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "stream-conf.h" - -struct config stream_config = APPCONFIG_INITIALIZER; - -bool stream_conf_send_enabled = false; -bool stream_conf_compression_enabled = true; -bool stream_conf_replication_enabled = true; - -const char *stream_conf_send_destination = NULL; -const char *stream_conf_send_api_key = NULL; -const char *stream_conf_send_charts_matching = "*"; - -time_t stream_conf_replication_period = 86400; -time_t stream_conf_replication_step = 600; - -const char *stream_conf_ssl_ca_path = NULL; -const char *stream_conf_ssl_ca_file = NULL; - -// to have the remote netdata re-sync the charts -// to its current clock, we send for this many -// iterations a BEGIN line without microseconds -// this is for the first iterations of each chart -unsigned int stream_conf_initial_clock_resync_iterations = 60; +#include "daemon/common.h" +#include "stream-receiver-internals.h" +#include "stream-sender-internals.h" + +static struct config stream_config = APPCONFIG_INITIALIZER; + +struct _stream_send stream_send = { + .enabled = false, + .api_key = NULL, + .send_charts_matching = NULL, + .initial_clock_resync_iterations = 60, + + .buffer_max_size = CBUFFER_INITIAL_MAX_SIZE, + + .parents = { + .destination = NULL, + .default_port = 19999, + .h2o = false, + .timeout_s = 300, + .reconnect_delay_s = 15, + .ssl_ca_path = NULL, + .ssl_ca_file = NULL, + }, + + .compression = { + .enabled = true, + .levels = { + [COMPRESSION_ALGORITHM_NONE] = 0, + [COMPRESSION_ALGORITHM_ZSTD] = 3, // 1 (faster) - 22 (smaller) + [COMPRESSION_ALGORITHM_LZ4] = 1, // 1 (smaller) - 9 (faster) + [COMPRESSION_ALGORITHM_BROTLI] = 3, // 0 (faster) - 11 (smaller) + [COMPRESSION_ALGORITHM_GZIP] = 1, // 1 (faster) - 9 (smaller) + } + }, +}; + +struct _stream_receive stream_receive = { + .replication = { + .enabled = true, + .period = 86400, + .step = 600, + } +}; static void stream_conf_load() { errno_clear(); @@ -61,6 +81,7 @@ static void stream_conf_load() { appconfig_move_everywhere(&stream_config, "seconds per replication step", "replication step"); appconfig_move_everywhere(&stream_config, "default postpone alarms on connect seconds", "postpone alerts on connect"); appconfig_move_everywhere(&stream_config, "postpone alarms on connect seconds", "postpone alerts on connect"); + appconfig_move_everywhere(&stream_config, "health enabled by default", "health enabled"); } bool stream_conf_receiver_needs_dbengine(void) { @@ -68,70 +89,221 @@ bool stream_conf_receiver_needs_dbengine(void) { } bool stream_conf_init() { - // -------------------------------------------------------------------- - // load stream.conf stream_conf_load(); - stream_conf_send_enabled = - appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM, "enabled", stream_conf_send_enabled); + stream_send.enabled = + appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM, "enabled", stream_send.enabled); + + stream_send.parents.destination = + string_strdupz(appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "destination", "")); - stream_conf_send_destination = - appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "destination", ""); + stream_send.api_key = + string_strdupz(appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "api key", "")); - stream_conf_send_api_key = - appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "api key", ""); + stream_send.send_charts_matching = + string_strdupz(appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "send charts matching", "*")); - stream_conf_send_charts_matching = - appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "send charts matching", stream_conf_send_charts_matching); + stream_receive.replication.enabled = + config_get_boolean(CONFIG_SECTION_DB, "enable replication", + stream_receive.replication.enabled); - stream_conf_replication_enabled = - config_get_boolean(CONFIG_SECTION_DB, "enable replication", stream_conf_replication_enabled); + stream_receive.replication.period = + config_get_duration_seconds(CONFIG_SECTION_DB, "replication period", + stream_receive.replication.period); - stream_conf_replication_period = - config_get_duration_seconds(CONFIG_SECTION_DB, "replication period", stream_conf_replication_period); + stream_receive.replication.step = + config_get_duration_seconds(CONFIG_SECTION_DB, "replication step", + stream_receive.replication.step); - stream_conf_replication_step = - config_get_duration_seconds(CONFIG_SECTION_DB, "replication step", stream_conf_replication_step); + stream_send.buffer_max_size = (size_t)appconfig_get_number( + &stream_config, CONFIG_SECTION_STREAM, "buffer size bytes", + stream_send.buffer_max_size); - rrdhost_free_orphan_time_s = - config_get_duration_seconds(CONFIG_SECTION_DB, "cleanup orphan hosts after", rrdhost_free_orphan_time_s); + stream_send.parents.reconnect_delay_s = (unsigned int)appconfig_get_duration_seconds( + &stream_config, CONFIG_SECTION_STREAM, "reconnect delay", + stream_send.parents.reconnect_delay_s); + if(stream_send.parents.reconnect_delay_s < SENDER_MIN_RECONNECT_DELAY) + stream_send.parents.reconnect_delay_s = SENDER_MIN_RECONNECT_DELAY; - stream_conf_compression_enabled = - appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM, - "enable compression", stream_conf_compression_enabled); + stream_send.compression.enabled = + appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM, "enable compression", + stream_send.compression.enabled); - rrdpush_compression_levels[COMPRESSION_ALGORITHM_BROTLI] = (int)appconfig_get_number( + stream_send.compression.levels[COMPRESSION_ALGORITHM_BROTLI] = (int)appconfig_get_number( &stream_config, CONFIG_SECTION_STREAM, "brotli compression level", - rrdpush_compression_levels[COMPRESSION_ALGORITHM_BROTLI]); + stream_send.compression.levels[COMPRESSION_ALGORITHM_BROTLI]); - rrdpush_compression_levels[COMPRESSION_ALGORITHM_ZSTD] = (int)appconfig_get_number( + stream_send.compression.levels[COMPRESSION_ALGORITHM_ZSTD] = (int)appconfig_get_number( &stream_config, CONFIG_SECTION_STREAM, "zstd compression level", - rrdpush_compression_levels[COMPRESSION_ALGORITHM_ZSTD]); + stream_send.compression.levels[COMPRESSION_ALGORITHM_ZSTD]); - rrdpush_compression_levels[COMPRESSION_ALGORITHM_LZ4] = (int)appconfig_get_number( + stream_send.compression.levels[COMPRESSION_ALGORITHM_LZ4] = (int)appconfig_get_number( &stream_config, CONFIG_SECTION_STREAM, "lz4 compression acceleration", - rrdpush_compression_levels[COMPRESSION_ALGORITHM_LZ4]); + stream_send.compression.levels[COMPRESSION_ALGORITHM_LZ4]); - rrdpush_compression_levels[COMPRESSION_ALGORITHM_GZIP] = (int)appconfig_get_number( + stream_send.compression.levels[COMPRESSION_ALGORITHM_GZIP] = (int)appconfig_get_number( &stream_config, CONFIG_SECTION_STREAM, "gzip compression level", - rrdpush_compression_levels[COMPRESSION_ALGORITHM_GZIP]); + stream_send.compression.levels[COMPRESSION_ALGORITHM_GZIP]); - if(stream_conf_send_enabled && (!stream_conf_send_destination || !*stream_conf_send_destination || !stream_conf_send_api_key || !*stream_conf_send_api_key)) { - nd_log_daemon(NDLP_WARNING, "STREAM [send]: cannot enable sending thread - information is missing."); - stream_conf_send_enabled = false; - } + stream_send.parents.h2o = appconfig_get_boolean( + &stream_config, CONFIG_SECTION_STREAM, "parent using h2o", + stream_send.parents.h2o); + + stream_send.parents.timeout_s = (int)appconfig_get_duration_seconds( + &stream_config, CONFIG_SECTION_STREAM, "timeout", + stream_send.parents.timeout_s); + + stream_send.buffer_max_size = (size_t)appconfig_get_number( + &stream_config, CONFIG_SECTION_STREAM, "buffer size bytes", + stream_send.buffer_max_size); + + stream_send.parents.default_port = (int)appconfig_get_number( + &stream_config, CONFIG_SECTION_STREAM, "default port", + stream_send.parents.default_port); - netdata_ssl_validate_certificate_sender = !appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM, "ssl skip certificate verification", !netdata_ssl_validate_certificate); + stream_send.initial_clock_resync_iterations = (unsigned int)appconfig_get_number( + &stream_config, CONFIG_SECTION_STREAM, "initial clock resync iterations", + stream_send.initial_clock_resync_iterations); // TODO: REMOVE FOR SLEW / GAPFILLING + + netdata_ssl_validate_certificate_sender = !appconfig_get_boolean( + &stream_config, CONFIG_SECTION_STREAM, "ssl skip certificate verification", + !netdata_ssl_validate_certificate); if(!netdata_ssl_validate_certificate_sender) nd_log_daemon(NDLP_NOTICE, "SSL: streaming senders will skip SSL certificates verification."); - stream_conf_ssl_ca_path = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "CApath", NULL); - stream_conf_ssl_ca_file = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "CAfile", NULL); + stream_send.parents.ssl_ca_path = string_strdupz(appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "CApath", NULL)); + stream_send.parents.ssl_ca_file = string_strdupz(appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "CAfile", NULL)); + + if(stream_send.enabled && (!stream_send.parents.destination || !stream_send.api_key)) { + nd_log_daemon(NDLP_ERR, "STREAM [send]: cannot enable sending thread - information is missing."); + stream_send.enabled = false; + } - return stream_conf_send_enabled; + return stream_send.enabled; } bool stream_conf_configured_as_parent() { return stream_conf_has_uuid_section(&stream_config); } + +void stream_conf_receiver_config(struct receiver_state *rpt, struct stream_receiver_config *config, const char *api_key, const char *machine_guid) { + config->mode = rrd_memory_mode_id( + appconfig_get(&stream_config, machine_guid, "db", + appconfig_get(&stream_config, api_key, "db", + rrd_memory_mode_name(default_rrd_memory_mode)))); + + if (unlikely(config->mode == RRD_MEMORY_MODE_DBENGINE && !dbengine_enabled)) { + netdata_log_error("STREAM '%s' [receive from %s:%s]: " + "dbengine is not enabled, falling back to default." + , rpt->hostname + , rpt->client_ip, rpt->client_port + ); + config->mode = default_rrd_memory_mode; + } + + config->history = (int) + appconfig_get_number(&stream_config, machine_guid, "retention", + appconfig_get_number(&stream_config, api_key, "retention", + default_rrd_history_entries)); + if(config->history < 5) config->history = 5; + + config->health.enabled = + appconfig_get_boolean_ondemand(&stream_config, machine_guid, "health enabled", + appconfig_get_boolean_ondemand(&stream_config, api_key, "health enabled", + health_plugin_enabled())); + + config->health.delay = + appconfig_get_duration_seconds(&stream_config, machine_guid, "postpone alerts on connect", + appconfig_get_duration_seconds(&stream_config, api_key, "postpone alerts on connect", + 60)); + + config->update_every = (int)appconfig_get_duration_seconds(&stream_config, machine_guid, "update every", config->update_every); + if(config->update_every < 0) config->update_every = 1; + + config->health.history = + appconfig_get_duration_seconds(&stream_config, machine_guid, "health log retention", + appconfig_get_duration_seconds(&stream_config, api_key, "health log retention", + HEALTH_LOG_RETENTION_DEFAULT)); + + config->send.enabled = + appconfig_get_boolean(&stream_config, machine_guid, "proxy enabled", + appconfig_get_boolean(&stream_config, api_key, "proxy enabled", + stream_send.enabled)); + + config->send.parents = string_strdupz( + appconfig_get(&stream_config, machine_guid, "proxy destination", + appconfig_get(&stream_config, api_key, "proxy destination", + string2str(stream_send.parents.destination)))); + + config->send.api_key = string_strdupz( + appconfig_get(&stream_config, machine_guid, "proxy api key", + appconfig_get(&stream_config, api_key, "proxy api key", + string2str(stream_send.api_key)))); + + config->send.charts_matching = string_strdupz( + appconfig_get(&stream_config, machine_guid, "proxy send charts matching", + appconfig_get(&stream_config, api_key, "proxy send charts matching", + string2str(stream_send.send_charts_matching)))); + + config->replication.enabled = + appconfig_get_boolean(&stream_config, machine_guid, "enable replication", + appconfig_get_boolean(&stream_config, api_key, "enable replication", + stream_receive.replication.enabled)); + + config->replication.period = + appconfig_get_duration_seconds(&stream_config, machine_guid, "replication period", + appconfig_get_duration_seconds(&stream_config, api_key, "replication period", + stream_receive.replication.period)); + + config->replication.step = + appconfig_get_number(&stream_config, machine_guid, "replication step", + appconfig_get_number(&stream_config, api_key, "replication step", + stream_receive.replication.step)); + + config->compression.enabled = + appconfig_get_boolean(&stream_config, machine_guid, "enable compression", + appconfig_get_boolean(&stream_config, api_key, "enable compression", + stream_send.compression.enabled)); + + if(config->compression.enabled) { + stream_parse_compression_order( + config, + appconfig_get( + &stream_config, + machine_guid, + "compression algorithms order", + appconfig_get( + &stream_config, api_key, "compression algorithms order", STREAM_COMPRESSION_ALGORITHMS_ORDER))); + } + + config->ephemeral = + appconfig_get_boolean(&stream_config, machine_guid, "is ephemeral node", + appconfig_get_boolean(&stream_config, api_key, "is ephemeral node", + CONFIG_BOOLEAN_NO)); +} + +bool stream_conf_is_key_type(const char *api_key, const char *type) { + const char *api_key_type = appconfig_get(&stream_config, api_key, "type", type); + if(!api_key_type || !*api_key_type) api_key_type = "unknown"; + return strcmp(api_key_type, type) == 0; +} + +bool stream_conf_api_key_is_enabled(const char *api_key, bool enabled) { + return appconfig_get_boolean(&stream_config, api_key, "enabled", enabled); +} + +bool stream_conf_api_key_allows_client(const char *api_key, const char *client_ip) { + SIMPLE_PATTERN *key_allow_from = simple_pattern_create( + appconfig_get(&stream_config, api_key, "allow from", "*"), + NULL, SIMPLE_PATTERN_EXACT, true); + + bool rc = true; + + if(key_allow_from) { + rc = simple_pattern_matches(key_allow_from, client_ip); + simple_pattern_free(key_allow_from); + } + + return rc; +} diff --git a/src/streaming/stream-conf.h b/src/streaming/stream-conf.h index da7a8812350517..18ea383fb787de 100644 --- a/src/streaming/stream-conf.h +++ b/src/streaming/stream-conf.h @@ -4,25 +4,91 @@ #define NETDATA_STREAM_CONF_H #include "libnetdata/libnetdata.h" -#include "daemon/common.h" +#include "stream-compression/compression.h" +#include "stream-capabilities.h" +#include "database/rrd-database-mode.h" -extern bool stream_conf_send_enabled; -extern bool stream_conf_compression_enabled; -extern bool stream_conf_replication_enabled; +#define SENDER_MIN_RECONNECT_DELAY 5 -extern const char *stream_conf_send_destination; -extern const char *stream_conf_send_api_key; -extern const char *stream_conf_send_charts_matching; -extern time_t stream_conf_replication_period; -extern time_t stream_conf_replication_step; -extern unsigned int stream_conf_initial_clock_resync_iterations; +struct _stream_send { + bool enabled; -extern struct config stream_config; -extern const char *stream_conf_ssl_ca_path; -extern const char *stream_conf_ssl_ca_file; + STRING *api_key; + STRING *send_charts_matching; + + // to have the remote netdata re-sync the charts + // to its current clock, we send for this many + // iterations a BEGIN line without microseconds + // this is for the first iterations of each chart + uint16_t initial_clock_resync_iterations; + + uint32_t buffer_max_size; + + struct { + STRING *destination; + STRING *ssl_ca_path; + STRING *ssl_ca_file; + bool h2o; + uint16_t default_port; + time_t timeout_s; + time_t reconnect_delay_s; + } parents; + + struct { + bool enabled; + int levels[COMPRESSION_ALGORITHM_MAX]; + } compression; +}; +extern struct _stream_send stream_send; + +struct _stream_receive { + struct { + bool enabled; + time_t period; + time_t step; + } replication; +}; +extern struct _stream_receive stream_receive; + +struct stream_receiver_config { + RRD_MEMORY_MODE mode; + bool ephemeral; + int history; + int update_every; + + struct { + bool enabled; // enable replication on this child + time_t period; + time_t step; + } replication; + + struct { + int enabled; // CONFIG_BOOLEAN_YES, CONFIG_BOOLEAN_NO, CONFIG_BOOLEAN_AUTO + time_t delay; + uint32_t history; + } health; + + struct { + bool enabled; + STRING *api_key; + STRING *parents; + STRING *charts_matching; + } send; + + struct { + bool enabled; + STREAM_CAPABILITIES priorities[COMPRESSION_ALGORITHM_MAX]; + } compression; +}; + +void stream_conf_receiver_config(struct receiver_state *rpt, struct stream_receiver_config *config, const char *api_key, const char *machine_guid); bool stream_conf_init(); bool stream_conf_receiver_needs_dbengine(); bool stream_conf_configured_as_parent(); +bool stream_conf_is_key_type(const char *api_key, const char *type); +bool stream_conf_api_key_is_enabled(const char *api_key, bool enabled); +bool stream_conf_api_key_allows_client(const char *api_key, const char *client_ip); + #endif //NETDATA_STREAM_CONF_H diff --git a/src/streaming/sender-connect.c b/src/streaming/stream-connector.c similarity index 56% rename from src/streaming/sender-connect.c rename to src/streaming/stream-connector.c index ac5f392a0432f3..d3360f9a52a7e5 100644 --- a/src/streaming/sender-connect.c +++ b/src/streaming/stream-connector.c @@ -1,22 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "sender-internals.h" +#include "stream-sender-internals.h" -void rrdpush_sender_thread_close_socket(struct sender_state *s) { - rrdhost_flag_clear(s->host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED | RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS); +typedef struct { + char *os_name; + char *os_id; + char *os_version; + char *kernel_name; + char *kernel_version; +} stream_encoded_t; - netdata_ssl_close(&s->ssl); - - if(s->rrdpush_sender_socket != -1) { - close(s->rrdpush_sender_socket); - s->rrdpush_sender_socket = -1; - } - - // do not flush the circular buffer here - // this function is called sometimes with the sender lock, sometimes without the lock -} - -void rrdpush_encode_variable(stream_encoded_t *se, RRDHOST *host) { +static void rrdpush_encode_variable(stream_encoded_t *se, RRDHOST *host) { se->os_name = (host->system_info->host_os_name)?url_encode(host->system_info->host_os_name):strdupz(""); se->os_id = (host->system_info->host_os_id)?url_encode(host->system_info->host_os_id):strdupz(""); se->os_version = (host->system_info->host_os_version)?url_encode(host->system_info->host_os_version):strdupz(""); @@ -24,7 +18,7 @@ void rrdpush_encode_variable(stream_encoded_t *se, RRDHOST *host) { se->kernel_version = (host->system_info->kernel_version)?url_encode(host->system_info->kernel_version):strdupz(""); } -void rrdpush_clean_encoded(stream_encoded_t *se) { +static void rrdpush_clean_encoded(stream_encoded_t *se) { if (se->os_name) { freez(se->os_name); se->os_name = NULL; @@ -51,7 +45,7 @@ void rrdpush_clean_encoded(stream_encoded_t *se) { } } -struct { +static struct { const char *response; const char *status; size_t length; @@ -65,7 +59,7 @@ struct { { .response = START_STREAMING_PROMPT_VN, .length = sizeof(START_STREAMING_PROMPT_VN) - 1, - .status = RRDPUSH_STATUS_CONNECTED, + .status = STREAM_STATUS_CONNECTED, .version = STREAM_HANDSHAKE_OK_V3, // and above .dynamic = true, // dynamic = we will parse the version / capabilities .error = NULL, @@ -76,7 +70,7 @@ struct { { .response = START_STREAMING_PROMPT_V2, .length = sizeof(START_STREAMING_PROMPT_V2) - 1, - .status = RRDPUSH_STATUS_CONNECTED, + .status = STREAM_STATUS_CONNECTED, .version = STREAM_HANDSHAKE_OK_V2, .dynamic = false, .error = NULL, @@ -87,7 +81,7 @@ struct { { .response = START_STREAMING_PROMPT_V1, .length = sizeof(START_STREAMING_PROMPT_V1) - 1, - .status = RRDPUSH_STATUS_CONNECTED, + .status = STREAM_STATUS_CONNECTED, .version = STREAM_HANDSHAKE_OK_V1, .dynamic = false, .error = NULL, @@ -98,66 +92,66 @@ struct { { .response = START_STREAMING_ERROR_SAME_LOCALHOST, .length = sizeof(START_STREAMING_ERROR_SAME_LOCALHOST) - 1, - .status = RRDPUSH_STATUS_LOCALHOST, + .status = STREAM_STATUS_LOCALHOST, .version = STREAM_HANDSHAKE_ERROR_LOCALHOST, .dynamic = false, .error = "remote server rejected this stream, the host we are trying to stream is its localhost", - .worker_job_id = WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, + .worker_job_id = WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, .postpone_reconnect_seconds = 60 * 60, // the IP may change, try it every hour .priority = NDLP_DEBUG, }, { .response = START_STREAMING_ERROR_ALREADY_STREAMING, .length = sizeof(START_STREAMING_ERROR_ALREADY_STREAMING) - 1, - .status = RRDPUSH_STATUS_ALREADY_CONNECTED, + .status = STREAM_STATUS_ALREADY_CONNECTED, .version = STREAM_HANDSHAKE_ERROR_ALREADY_CONNECTED, .dynamic = false, .error = "remote server rejected this stream, the host we are trying to stream is already streamed to it", - .worker_job_id = WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, + .worker_job_id = WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, .postpone_reconnect_seconds = 2 * 60, // 2 minutes .priority = NDLP_DEBUG, }, { .response = START_STREAMING_ERROR_NOT_PERMITTED, .length = sizeof(START_STREAMING_ERROR_NOT_PERMITTED) - 1, - .status = RRDPUSH_STATUS_PERMISSION_DENIED, + .status = STREAM_STATUS_PERMISSION_DENIED, .version = STREAM_HANDSHAKE_ERROR_DENIED, .dynamic = false, .error = "remote server denied access, probably we don't have the right API key?", - .worker_job_id = WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, + .worker_job_id = WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, .postpone_reconnect_seconds = 1 * 60, // 1 minute .priority = NDLP_ERR, }, { .response = START_STREAMING_ERROR_BUSY_TRY_LATER, .length = sizeof(START_STREAMING_ERROR_BUSY_TRY_LATER) - 1, - .status = RRDPUSH_STATUS_RATE_LIMIT, + .status = STREAM_STATUS_RATE_LIMIT, .version = STREAM_HANDSHAKE_BUSY_TRY_LATER, .dynamic = false, .error = "remote server is currently busy, we should try later", - .worker_job_id = WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, + .worker_job_id = WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, .postpone_reconnect_seconds = 2 * 60, // 2 minutes .priority = NDLP_NOTICE, }, { .response = START_STREAMING_ERROR_INTERNAL_ERROR, .length = sizeof(START_STREAMING_ERROR_INTERNAL_ERROR) - 1, - .status = RRDPUSH_STATUS_INTERNAL_SERVER_ERROR, + .status = STREAM_STATUS_INTERNAL_SERVER_ERROR, .version = STREAM_HANDSHAKE_INTERNAL_ERROR, .dynamic = false, .error = "remote server is encountered an internal error, we should try later", - .worker_job_id = WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, + .worker_job_id = WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, .postpone_reconnect_seconds = 5 * 60, // 5 minutes .priority = NDLP_CRIT, }, { .response = START_STREAMING_ERROR_INITIALIZATION, .length = sizeof(START_STREAMING_ERROR_INITIALIZATION) - 1, - .status = RRDPUSH_STATUS_INITIALIZATION_IN_PROGRESS, + .status = STREAM_STATUS_INITIALIZATION_IN_PROGRESS, .version = STREAM_HANDSHAKE_INITIALIZATION, .dynamic = false, .error = "remote server is initializing, we should try later", - .worker_job_id = WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, + .worker_job_id = WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, .postpone_reconnect_seconds = 2 * 60, // 2 minute .priority = NDLP_NOTICE, }, @@ -166,134 +160,18 @@ struct { { .response = NULL, .length = 0, - .status = RRDPUSH_STATUS_BAD_HANDSHAKE, + .status = STREAM_STATUS_BAD_HANDSHAKE, .version = STREAM_HANDSHAKE_ERROR_BAD_HANDSHAKE, .dynamic = false, .error = "remote node response is not understood, is it Netdata?", - .worker_job_id = WORKER_SENDER_JOB_DISCONNECT_BAD_HANDSHAKE, + .worker_job_id = WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, .postpone_reconnect_seconds = 1 * 60, // 1 minute .priority = NDLP_ERR, } }; -static inline bool rrdpush_sender_validate_response(RRDHOST *host, struct sender_state *s, char *http, size_t http_length) { - int32_t version = STREAM_HANDSHAKE_ERROR_BAD_HANDSHAKE; - - int i; - for(i = 0; stream_responses[i].response ; i++) { - if(stream_responses[i].dynamic && - http_length > stream_responses[i].length && http_length < (stream_responses[i].length + 30) && - strncmp(http, stream_responses[i].response, stream_responses[i].length) == 0) { - - version = str2i(&http[stream_responses[i].length]); - break; - } - else if(http_length == stream_responses[i].length && strcmp(http, stream_responses[i].response) == 0) { - version = stream_responses[i].version; - - break; - } - } - - if(version >= STREAM_HANDSHAKE_OK_V1) { - host->destination->reason = version; - host->destination->postpone_reconnection_until = now_realtime_sec() + s->reconnect_delay; - s->capabilities = convert_stream_version_to_capabilities(version, host, true); - return true; - } - - ND_LOG_FIELD_PRIORITY priority = stream_responses[i].priority; - const char *error = stream_responses[i].error; - const char *status = stream_responses[i].status; - int worker_job_id = stream_responses[i].worker_job_id; - int delay = stream_responses[i].postpone_reconnect_seconds; - - worker_is_busy(worker_job_id); - rrdpush_sender_thread_close_socket(s); - host->destination->reason = version; - host->destination->postpone_reconnection_until = now_realtime_sec() + delay; - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, status), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - char buf[RFC3339_MAX_LENGTH]; - rfc3339_datetime_ut(buf, sizeof(buf), host->destination->postpone_reconnection_until * USEC_PER_SEC, 0, false); - - nd_log(NDLS_DAEMON, priority, - "STREAM %s [send to %s]: %s - will retry in %d secs, at %s", - rrdhost_hostname(host), s->connected_to, error, delay, buf); - - return false; -} - -unsigned char alpn_proto_list[] = { - 18, 'n', 'e', 't', 'd', 'a', 't', 'a', '_', 's', 't', 'r', 'e', 'a', 'm', '/', '2', '.', '0', - 8, 'h', 't', 't', 'p', '/', '1', '.', '1' -}; - #define CONN_UPGRADE_VAL "upgrade" - -static bool rrdpush_sender_connect_ssl(struct sender_state *s) { - RRDHOST *host = s->host; - bool ssl_required = host && host->destination && host->destination->ssl; - - netdata_ssl_close(&host->sender->ssl); - - if(!ssl_required) - return true; - - if (netdata_ssl_open_ext(&host->sender->ssl, netdata_ssl_streaming_sender_ctx, s->rrdpush_sender_socket, alpn_proto_list, sizeof(alpn_proto_list))) { - if(!netdata_ssl_connect(&host->sender->ssl)) { - // couldn't connect - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, RRDPUSH_STATUS_SSL_ERROR), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR); - rrdpush_sender_thread_close_socket(s); - host->destination->reason = STREAM_HANDSHAKE_ERROR_SSL_ERROR; - host->destination->postpone_reconnection_until = now_realtime_sec() + 5 * 60; - return false; - } - - if (netdata_ssl_validate_certificate_sender && - security_test_certificate(host->sender->ssl.conn)) { - // certificate is not valid - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, RRDPUSH_STATUS_INVALID_SSL_CERTIFICATE), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR); - netdata_log_error("SSL: closing the stream connection, because the server SSL certificate is not valid."); - rrdpush_sender_thread_close_socket(s); - host->destination->reason = STREAM_HANDSHAKE_ERROR_INVALID_CERTIFICATE; - host->destination->postpone_reconnection_until = now_realtime_sec() + 5 * 60; - return false; - } - - return true; - } - - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, RRDPUSH_STATUS_CANT_ESTABLISH_SSL_CONNECTION), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); - - netdata_log_error("SSL: failed to establish connection."); - return false; -} - -static int rrdpush_http_upgrade_prelude(RRDHOST *host, struct sender_state *s) { +static int stream_connect_upgrade_prelude(RRDHOST *host __maybe_unused, struct sender_state *s) { char http[HTTP_HEADER_SIZE + 1]; snprintfz(http, HTTP_HEADER_SIZE, @@ -302,22 +180,14 @@ static int rrdpush_http_upgrade_prelude(RRDHOST *host, struct sender_state *s) { "Connection: Upgrade" HTTP_HDR_END); - ssize_t bytes = send_timeout( - &host->sender->ssl, - s->rrdpush_sender_socket, - http, - strlen(http), - 0, - 1000); - - bytes = recv_timeout( - &host->sender->ssl, - s->rrdpush_sender_socket, - http, - HTTP_HEADER_SIZE, - 0, - 1000); + ssize_t bytes; + bytes = nd_sock_send_timeout(&s->sock, http, strlen(http), 0, 1000); + if (bytes <= 0) { + error_report("Error writing to remote"); + return 1; + } + bytes = nd_sock_recv_timeout(&s->sock, http, HTTP_HEADER_SIZE, 0, 1000); if (bytes <= 0) { error_report("Error reading from remote"); return 1; @@ -387,33 +257,85 @@ static int rrdpush_http_upgrade_prelude(RRDHOST *host, struct sender_state *s) { return 1; } -static bool sender_send_connection_request(RRDHOST *host, int default_port, int timeout, struct sender_state *s) { +static bool +stream_connect_validate_first_response(RRDHOST *host, struct sender_state *s, char *http, size_t http_length) { + int32_t version = STREAM_HANDSHAKE_ERROR_BAD_HANDSHAKE; + + int i; + for(i = 0; stream_responses[i].response ; i++) { + if(stream_responses[i].dynamic && + http_length > stream_responses[i].length && http_length < (stream_responses[i].length + 30) && + strncmp(http, stream_responses[i].response, stream_responses[i].length) == 0) { - struct timeval tv = { - .tv_sec = timeout, - .tv_usec = 0 + version = str2i(&http[stream_responses[i].length]); + break; + } + else if(http_length == stream_responses[i].length && strcmp(http, stream_responses[i].response) == 0) { + version = stream_responses[i].version; + + break; + } + } + + if(version >= STREAM_HANDSHAKE_OK_V1) { + stream_parent_set_reconnect_delay(host->stream.snd.parents.current, STREAM_HANDSHAKE_CONNECTED, + stream_send.parents.reconnect_delay_s); + s->capabilities = convert_stream_version_to_capabilities(version, host, true); + return true; + } + + ND_LOG_FIELD_PRIORITY priority = stream_responses[i].priority; + const char *error = stream_responses[i].error; + const char *status = stream_responses[i].status; + int worker_job_id = stream_responses[i].worker_job_id; + int delay = stream_responses[i].postpone_reconnect_seconds; + + worker_is_busy(worker_job_id); + stream_parent_set_reconnect_delay(host->stream.snd.parents.current, version, delay); + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, status), + ND_LOG_FIELD_END(), }; + ND_LOG_STACK_PUSH(lgs); + + char buf[RFC3339_MAX_LENGTH]; + rfc3339_datetime_ut(buf, sizeof(buf), stream_parent_get_reconnection_ut(host->stream.snd.parents.current), 0, false); + + nd_log(NDLS_DAEMON, priority, + "STREAM %s [send to %s]: %s - will retry in %d secs, at %s", + rrdhost_hostname(host), s->connected_to, error, delay, buf); + + return false; +} + +bool stream_connect(struct sender_state *s, uint16_t default_port, time_t timeout) { + worker_is_busy(WORKER_SENDER_CONNECTOR_JOB_CONNECTING); + + RRDHOST *host = s->host; // make sure the socket is closed - rrdpush_sender_thread_close_socket(s); - - s->rrdpush_sender_socket = connect_to_one_of_destinations( - host - , default_port - , &tv - , &s->reconnects_counter - , s->connected_to - , sizeof(s->connected_to)-1 - , &host->destination - ); + nd_sock_close(&s->sock); + + s->hops = (int16_t)(host->system_info->hops + 1); + + // reset this to make sure we have its current value + s->sock.verify_certificate = netdata_ssl_validate_certificate_sender; + s->sock.ctx = netdata_ssl_streaming_sender_ctx; - if(unlikely(s->rrdpush_sender_socket == -1)) { - // netdata_log_error("STREAM %s [send to %s]: could not connect to parent node at this time.", rrdhost_hostname(host), host->rrdpush_send_destination); + if(!stream_parent_connect_to_one( + &s->sock, host, default_port, timeout, + s->connected_to, sizeof(s->connected_to) - 1, + &host->stream.snd.parents.current)) { + + if(s->sock.error != ND_SOCK_ERR_NO_DESTINATION_AVAILABLE) + nd_log(NDLS_DAEMON, NDLP_WARNING, "can't connect to a parent, last error: %s", + ND_SOCK_ERROR_2str(s->sock.error)); + + nd_sock_close(&s->sock); return false; } - // netdata_log_info("STREAM %s [send to %s]: initializing communication...", rrdhost_hostname(host), s->connected_to); - // reset our capabilities to default s->capabilities = stream_our_capabilities(host, true); @@ -423,8 +345,6 @@ static bool sender_send_connection_request(RRDHOST *host, int default_port, int stream_encoded_t se; rrdpush_encode_variable(&se, host); - host->sender->hops = host->system_info->hops + 1; - char http[HTTP_HEADER_SIZE + 1]; int eol = snprintfz(http, HTTP_HEADER_SIZE, "STREAM " @@ -471,18 +391,18 @@ static bool sender_send_connection_request(RRDHOST *host, int default_port, int "&NETDATA_SYSTEM_TOTAL_DISK_SIZE=%s" "&NETDATA_PROTOCOL_VERSION=%s" HTTP_1_1 HTTP_ENDL - "User-Agent: %s/%s\r\n" - "Accept: */*\r\n\r\n" - , host->rrdpush.send.api_key + "User-Agent: %s/%s" HTTP_ENDL + "Accept: */*" HTTP_HDR_END + , string2str(host->stream.snd.api_key) , rrdhost_hostname(host) - , rrdhost_registry_hostname(host) - , host->machine_guid + , rrdhost_registry_hostname(host) + , host->machine_guid , default_rrd_update_every , rrdhost_os(host) - , rrdhost_timezone(host) - , rrdhost_abbrev_timezone(host) - , host->utc_offset - , host->sender->hops + , rrdhost_timezone(host) + , rrdhost_abbrev_timezone(host) + , host->utc_offset + , s->hops , host->system_info->ml_capable , host->system_info->ml_enabled , host->system_info->mc_version @@ -516,226 +436,321 @@ static bool sender_send_connection_request(RRDHOST *host, int default_port, int , (host->system_info->host_disk_space) ? host->system_info->host_disk_space : "" , STREAMING_PROTOCOL_VERSION , rrdhost_program_name(host) - , rrdhost_program_version(host) + , rrdhost_program_version(host) ); http[eol] = 0x00; rrdpush_clean_encoded(&se); - if(!rrdpush_sender_connect_ssl(s)) - return false; - - if (s->parent_using_h2o && rrdpush_http_upgrade_prelude(host, s)) { + if (s->parent_using_h2o && stream_connect_upgrade_prelude(host, s)) { ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, RRDPUSH_STATUS_CANT_UPGRADE_CONNECTION), + ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, STREAM_STATUS_CANT_UPGRADE_CONNECTION), ND_LOG_FIELD_END(), }; ND_LOG_STACK_PUSH(lgs); - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_CANT_UPGRADE_CONNECTION); - rrdpush_sender_thread_close_socket(s); - host->destination->reason = STREAM_HANDSHAKE_ERROR_HTTP_UPGRADE; - host->destination->postpone_reconnection_until = now_realtime_sec() + 1 * 60; + worker_is_busy(WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_CANT_UPGRADE_CONNECTION); + nd_sock_close(&s->sock); + stream_parent_set_reconnect_delay( + host->stream.snd.parents.current, STREAM_HANDSHAKE_ERROR_HTTP_UPGRADE, 60); return false; } ssize_t len = (ssize_t)strlen(http); - ssize_t bytes = send_timeout( - &host->sender->ssl, - s->rrdpush_sender_socket, - http, - len, - 0, - timeout); - + ssize_t bytes = nd_sock_send_timeout(&s->sock, http, len, 0, timeout); if(bytes <= 0) { // timeout is 0 ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, RRDPUSH_STATUS_TIMEOUT), + ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, STREAM_STATUS_TIMEOUT), ND_LOG_FIELD_END(), }; ND_LOG_STACK_PUSH(lgs); - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_TIMEOUT); - rrdpush_sender_thread_close_socket(s); + worker_is_busy(WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_TIMEOUT); + nd_sock_close(&s->sock); nd_log(NDLS_DAEMON, NDLP_ERR, "STREAM %s [send to %s]: failed to send HTTP header to remote netdata.", rrdhost_hostname(host), s->connected_to); - host->destination->reason = STREAM_HANDSHAKE_ERROR_SEND_TIMEOUT; - host->destination->postpone_reconnection_until = now_realtime_sec() + 1 * 60; + stream_parent_set_reconnect_delay( + host->stream.snd.parents.current, STREAM_HANDSHAKE_ERROR_SEND_TIMEOUT, 60); return false; } - bytes = recv_timeout( - &host->sender->ssl, - s->rrdpush_sender_socket, - http, - HTTP_HEADER_SIZE, - 0, - timeout); - + bytes = nd_sock_recv_timeout(&s->sock, http, HTTP_HEADER_SIZE, 0, timeout); if(bytes <= 0) { // timeout is 0 + nd_sock_close(&s->sock); + ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, RRDPUSH_STATUS_TIMEOUT), + ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, STREAM_STATUS_TIMEOUT), ND_LOG_FIELD_END(), }; ND_LOG_STACK_PUSH(lgs); - worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_TIMEOUT); - rrdpush_sender_thread_close_socket(s); + worker_is_busy(WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_TIMEOUT); nd_log(NDLS_DAEMON, NDLP_ERR, "STREAM %s [send to %s]: remote netdata does not respond.", rrdhost_hostname(host), s->connected_to); - host->destination->reason = STREAM_HANDSHAKE_ERROR_RECEIVE_TIMEOUT; - host->destination->postpone_reconnection_until = now_realtime_sec() + 30; + stream_parent_set_reconnect_delay( + host->stream.snd.parents.current, STREAM_HANDSHAKE_ERROR_RECEIVE_TIMEOUT, 30); + return false; } + http[bytes] = '\0'; - if(sock_setnonblock(s->rrdpush_sender_socket) < 0) + if(sock_setnonblock(s->sock.fd) < 0) nd_log(NDLS_DAEMON, NDLP_WARNING, "STREAM %s [send to %s]: cannot set non-blocking mode for socket.", rrdhost_hostname(host), s->connected_to); - sock_setcloexec(s->rrdpush_sender_socket); - if(sock_enlarge_out(s->rrdpush_sender_socket) < 0) + sock_setcloexec(s->sock.fd); + + if(sock_enlarge_out(s->sock.fd) < 0) nd_log(NDLS_DAEMON, NDLP_WARNING, "STREAM %s [send to %s]: cannot enlarge the socket buffer.", rrdhost_hostname(host), s->connected_to); - http[bytes] = '\0'; - if(!rrdpush_sender_validate_response(host, s, http, bytes)) + if(!stream_connect_validate_first_response(host, s, http, bytes)) { + nd_sock_close(&s->sock); return false; + } - rrdpush_compression_initialize(s); + stream_compression_initialize(s); log_sender_capabilities(s); ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, RRDPUSH_STATUS_CONNECTED), + ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, STREAM_STATUS_CONNECTED), ND_LOG_FIELD_END(), }; ND_LOG_STACK_PUSH(lgs); nd_log(NDLS_DAEMON, NDLP_DEBUG, - "STREAM %s: connected to %s...", + "STREAM [connector] %s: connected to %s...", rrdhost_hostname(host), s->connected_to); return true; } -bool attempt_to_connect(struct sender_state *state) { - ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), - ND_LOG_FIELD_END(), - }; - ND_LOG_STACK_PUSH(lgs); +#define MAX_CONNECTORS 1 - state->send_attempts = 0; +struct connector { + int8_t id; + pid_t tid; + ND_THREAD *thread; + struct completion completion; - // reset the bytes we have sent for this session - state->sent_bytes_on_this_connection = 0; - memset(state->sent_bytes_on_this_connection_per_type, 0, sizeof(state->sent_bytes_on_this_connection_per_type)); + size_t nodes; - if(sender_send_connection_request(state->host, state->default_port, state->timeout, state)) { - // reset the buffer, to properly send charts and metrics - rrdpush_sender_on_connect(state->host); + struct { + // the incoming queue of the connector thread + // all other threads leave new senders here, to be connected to their parents + SPINLOCK spinlock; + SENDERS_JudyLSet senders; + } queue; +}; - // send from the beginning - state->begin = 0; +static struct { + int id; + struct connector connectors[MAX_CONNECTORS]; +} connector_globals = { 0 }; - // make sure the next reconnection will be immediate - state->not_connected_loops = 0; +bool stream_connector_is_signaled_to_stop(struct sender_state *s) { + return __atomic_load_n(&s->exit.shutdown, __ATOMIC_RELAXED); +} - // let the data collection threads know we are ready - rrdhost_flag_set(state->host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED); +struct connector *stream_connector_get(struct sender_state *s) { + stream_sender_lock(s); + + if(s->connector.id < 0 || s->connector.id >= MAX_CONNECTORS) { + // assign this to the dispatcher with fewer nodes + + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; + spinlock_lock(&spinlock); + int min_slot = 0; + size_t min_nodes = __atomic_load_n(&connector_globals.connectors[0].nodes, __ATOMIC_RELAXED); + for(int i = 1; i < MAX_CONNECTORS ;i++) { + size_t nodes = __atomic_load_n(&connector_globals.connectors[i].nodes, __ATOMIC_RELAXED); + if(nodes < min_nodes) { + min_nodes = nodes; + min_slot = i; + } + } + __atomic_add_fetch(&connector_globals.connectors[min_slot].nodes, 1, __ATOMIC_RELAXED); + s->connector.id = min_slot; + spinlock_unlock(&spinlock); + } - rrdpush_sender_after_connect(state->host); + struct connector *sc = &connector_globals.connectors[s->connector.id]; + stream_sender_unlock(s); - return true; - } + return sc; +} - // we couldn't connect +void stream_connector_requeue(struct sender_state *s) { + struct connector *sc = stream_connector_get(s); - // increase the failed connections counter - state->not_connected_loops++; + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM [connector] [%s]: adding host in connector queue...", + rrdhost_hostname(s->host)); + + spinlock_lock(&sc->queue.spinlock); + internal_fatal(SENDERS_GET(&sc->queue.senders, (Word_t)s) != NULL, "Sender is already in the connector queue"); + SENDERS_SET(&sc->queue.senders, (Word_t)s, s); + spinlock_unlock(&sc->queue.spinlock); - // slow re-connection on repeating errors - usec_t now_ut = now_monotonic_usec(); - usec_t end_ut = now_ut + USEC_PER_SEC * state->reconnect_delay; - while(now_ut < end_ut) { - if(nd_thread_signaled_to_cancel()) - return false; + // signal the connector to catch the job + completion_mark_complete_a_job(&sc->completion); +} - sleep_usec(100 * USEC_PER_MS); // seconds - now_ut = now_monotonic_usec(); +void stream_connector_add(struct sender_state *s) { + // multiple threads may come here - only one should be able to pass through + stream_sender_lock(s); + if(!rrdhost_has_stream_sender_enabled(s->host) || !s->host->stream.snd.destination || !s->host->stream.snd.api_key) { + nd_log(NDLS_DAEMON, NDLP_ERR, "STREAM %s [send]: host has streaming disabled - not sending data to a parent.", + rrdhost_hostname(s->host)); + stream_sender_unlock(s); + return; + } + if(rrdhost_flag_check(s->host, RRDHOST_FLAG_STREAM_SENDER_ADDED)) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, "STREAM %s [send]: host has already added to sender - ignoring request", + rrdhost_hostname(s->host)); + stream_sender_unlock(s); + return; } + rrdhost_flag_set(s->host, RRDHOST_FLAG_STREAM_SENDER_ADDED); + rrdhost_flag_clear(s->host, RRDHOST_FLAG_STREAM_SENDER_CONNECTED | RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS); + stream_sender_unlock(s); - return false; + nd_sock_close(&s->sock); + s->sbuf.cb->max_size = stream_send.buffer_max_size; + s->parent_using_h2o = stream_send.parents.h2o; + + // do not call this with any locks held + stream_connector_requeue(s); } -bool rrdpush_sender_connect(struct sender_state *s) { - worker_is_busy(WORKER_SENDER_JOB_CONNECT); +static void stream_connector_remove(struct sender_state *s) { + nd_log(NDLS_DAEMON, NDLP_NOTICE, + "STREAM [connector] [%s]: stopped streaming connector for host: %s", + rrdhost_hostname(s->host), stream_handshake_error_to_string(s->exit.reason)); - time_t now_s = now_monotonic_sec(); - rrdpush_sender_cbuffer_recreate_timed(s, now_s, false, true); - rrdpush_sender_execute_commands_cleanup(s); + struct connector *sc = stream_connector_get(s); + __atomic_sub_fetch(&sc->nodes, 1, __ATOMIC_RELAXED); - rrdhost_flag_clear(s->host, RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS); - s->flags &= ~SENDER_FLAG_OVERFLOW; - s->read_len = 0; - s->buffer->read = 0; - s->buffer->write = 0; + stream_sender_remove(s); +} - if(!attempt_to_connect(s)) - return false; +static void *stream_connector_thread(void *ptr) { + struct connector *sc = ptr; + sc->tid = gettid_cached(); - if(rrdhost_sender_should_exit(s)) - return false; + worker_register("STREAMCNT"); + worker_register_job_name(WORKER_SENDER_CONNECTOR_JOB_CONNECTING, "connect"); + worker_register_job_name(WORKER_SENDER_CONNECTOR_JOB_CONNECTED, "connected"); + worker_register_job_name(WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE, "bad handshake"); + worker_register_job_name(WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_TIMEOUT, "timeout"); + worker_register_job_name(WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_CANT_UPGRADE_CONNECTION, "cant upgrade"); - s->last_traffic_seen_t = now_monotonic_sec(); - stream_path_send_to_parent(s->host); - rrdpush_sender_send_claimed_id(s->host); - rrdpush_send_host_labels(s->host); - rrdpush_send_global_functions(s->host); - s->replication.oldest_request_after_t = 0; + worker_register_job_custom_metric(WORKER_SENDER_CONNECTOR_JOB_QUEUED_NODES, "queued nodes", "nodes", WORKER_METRIC_ABSOLUTE); + worker_register_job_custom_metric(WORKER_SENDER_CONNECTOR_JOB_CONNECTED_NODES, "connected nodes", "nodes", WORKER_METRIC_ABSOLUTE); + worker_register_job_custom_metric(WORKER_SENDER_CONNECTOR_JOB_FAILED_NODES, "failed nodes", "nodes", WORKER_METRIC_ABSOLUTE); + worker_register_job_custom_metric(WORKER_SENDER_CONNECTOR_JOB_CANCELLED_NODES, "cancelled nodes", "nodes", WORKER_METRIC_ABSOLUTE); - rrdhost_flag_set(s->host, RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS); + unsigned job_id = 0; - nd_log(NDLS_DAEMON, NDLP_DEBUG, - "STREAM %s [send to %s]: enabling metrics streaming...", - rrdhost_hostname(s->host), s->connected_to); + while(!nd_thread_signaled_to_cancel() && service_running(SERVICE_STREAMING)) { + worker_is_idle(); + job_id = completion_wait_for_a_job_with_timeout(&sc->completion, job_id, 1000); + size_t nodes = 0, connected_nodes = 0, failed_nodes = 0, cancelled_nodes = 0; - return true; -} + spinlock_lock(&sc->queue.spinlock); + Word_t idx = 0; + for(struct sender_state *s = SENDERS_FIRST(&sc->queue.senders, &idx); + s; + s = SENDERS_NEXT(&sc->queue.senders, &idx)) { + nodes++; -// Either the receiver lost the connection or the host is being destroyed. -// The sender mutex guards thread creation, any spurious data is wiped on reconnection. -void rrdpush_sender_thread_stop(RRDHOST *host, STREAM_HANDSHAKE reason, bool wait) { - if (!host->sender) - return; + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, s->host->hostname), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); - sender_lock(host->sender); + if(stream_connector_is_signaled_to_stop(s)) { + cancelled_nodes++; + SENDERS_DEL(&sc->queue.senders, (Word_t)s); + stream_connector_remove(s); + continue; + } - if(rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN)) { + spinlock_unlock(&sc->queue.spinlock); + worker_is_busy(WORKER_SENDER_CONNECTOR_JOB_CONNECTING); + bool move_to_sender = stream_connect(s, stream_send.parents.default_port, stream_send.parents.timeout_s); + spinlock_lock(&sc->queue.spinlock); - host->sender->exit.shutdown = true; - host->sender->exit.reason = reason; + if(move_to_sender) { + connected_nodes++; + stream_sender_on_connect(s); - // signal it to cancel - nd_thread_signal_cancel(host->rrdpush_sender_thread); - } + worker_is_busy(WORKER_SENDER_CONNECTOR_JOB_CONNECTED); + SENDERS_DEL(&sc->queue.senders, (Word_t)s); + spinlock_unlock(&sc->queue.spinlock); + + // do not have the connector lock when calling this + stream_sender_add_to_queue(s); - sender_unlock(host->sender); + spinlock_lock(&sc->queue.spinlock); + } + else + failed_nodes++; - if(wait) { - sender_lock(host->sender); - while(host->sender->tid) { - sender_unlock(host->sender); - sleep_usec(10 * USEC_PER_MS); - sender_lock(host->sender); + worker_is_idle(); } - sender_unlock(host->sender); + spinlock_unlock(&sc->queue.spinlock); + + worker_set_metric(WORKER_SENDER_CONNECTOR_JOB_QUEUED_NODES, (NETDATA_DOUBLE)nodes); + worker_set_metric(WORKER_SENDER_CONNECTOR_JOB_CONNECTED_NODES, (NETDATA_DOUBLE)connected_nodes); + worker_set_metric(WORKER_SENDER_CONNECTOR_JOB_FAILED_NODES, (NETDATA_DOUBLE)failed_nodes); + worker_set_metric(WORKER_SENDER_CONNECTOR_JOB_CANCELLED_NODES, (NETDATA_DOUBLE)cancelled_nodes); + } + + return NULL; +} + +bool stream_connector_init(struct sender_state *s) { + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; + if(!s) return false; + + spinlock_lock(&spinlock); + + struct connector *sc = stream_connector_get(s); + + if(!sc->thread) { + sc->id = (int8_t)(sc - connector_globals.connectors); // find the slot number + if(&connector_globals.connectors[sc->id] != sc) + fatal("Connector ID and slot do not match!"); + + spinlock_init(&sc->queue.spinlock); + completion_init(&sc->completion); + + char tag[NETDATA_THREAD_TAG_MAX + 1]; + snprintfz(tag, NETDATA_THREAD_TAG_MAX, THREAD_TAG_STREAM_SENDER "-CN" "[%d]", + sc->id); + + sc->thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_DEFAULT, stream_connector_thread, sc); + if (!sc->thread) + nd_log_daemon(NDLP_ERR, "STREAM connector: failed to create new thread for client."); } + + spinlock_unlock(&spinlock); + + return sc->thread != NULL; +} + +void stream_connector_cancel_threads(void) { + for(int id = 0; id < MAX_CONNECTORS ; id++) + nd_thread_signal_cancel(connector_globals.connectors[id].thread); } diff --git a/src/streaming/stream-handshake.c b/src/streaming/stream-handshake.c index e338df950f1fc8..9946a7f2e98d3c 100644 --- a/src/streaming/stream-handshake.c +++ b/src/streaming/stream-handshake.c @@ -1,51 +1,66 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "rrdpush.h" +#include "stream.h" static struct { STREAM_HANDSHAKE err; const char *str; } handshake_errors[] = { - { STREAM_HANDSHAKE_OK_V3, "CONNECTED" }, - { STREAM_HANDSHAKE_OK_V2, "CONNECTED" }, - { STREAM_HANDSHAKE_OK_V1, "CONNECTED" }, - { STREAM_HANDSHAKE_NEVER, "" }, - { STREAM_HANDSHAKE_ERROR_BAD_HANDSHAKE, "BAD HANDSHAKE" }, - { STREAM_HANDSHAKE_ERROR_LOCALHOST, "LOCALHOST" }, - { STREAM_HANDSHAKE_ERROR_ALREADY_CONNECTED, "ALREADY CONNECTED" }, - { STREAM_HANDSHAKE_ERROR_DENIED, "DENIED" }, - { STREAM_HANDSHAKE_ERROR_SEND_TIMEOUT, "SEND TIMEOUT" }, - { STREAM_HANDSHAKE_ERROR_RECEIVE_TIMEOUT, "RECEIVE TIMEOUT" }, - { STREAM_HANDSHAKE_ERROR_INVALID_CERTIFICATE, "INVALID CERTIFICATE" }, - { STREAM_HANDSHAKE_ERROR_SSL_ERROR, "SSL ERROR" }, - { STREAM_HANDSHAKE_ERROR_CANT_CONNECT, "CANT CONNECT" }, - { STREAM_HANDSHAKE_BUSY_TRY_LATER, "BUSY TRY LATER" }, - { STREAM_HANDSHAKE_INTERNAL_ERROR, "INTERNAL ERROR" }, - { STREAM_HANDSHAKE_INITIALIZATION, "REMOTE IS INITIALIZING" }, - { STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP, "DISCONNECTED HOST CLEANUP" }, - { STREAM_HANDSHAKE_DISCONNECT_STALE_RECEIVER, "DISCONNECTED STALE RECEIVER" }, - { STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN, "DISCONNECTED SHUTDOWN REQUESTED" }, - { STREAM_HANDSHAKE_DISCONNECT_NETDATA_EXIT, "DISCONNECTED NETDATA EXIT" }, - { STREAM_HANDSHAKE_DISCONNECT_PARSER_EXIT, "DISCONNECTED PARSE ENDED" }, - {STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR, "DISCONNECTED UNKNOWN SOCKET READ ERROR" }, - { STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, "DISCONNECTED PARSE ERROR" }, - { STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT, "DISCONNECTED RECEIVER LEFT" }, - { STREAM_HANDSHAKE_DISCONNECT_ORPHAN_HOST, "DISCONNECTED ORPHAN HOST" }, - { STREAM_HANDSHAKE_NON_STREAMABLE_HOST, "NON STREAMABLE HOST" }, - { STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_READ_BUFFER, "DISCONNECTED NOT SUFFICIENT READ BUFFER" }, - {STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF, "DISCONNECTED SOCKET EOF" }, - {STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED, "DISCONNECTED SOCKET READ FAILED" }, - {STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT, "DISCONNECTED SOCKET READ TIMEOUT" }, + {STREAM_HANDSHAKE_OK_V3, "CONNECTED"}, + {STREAM_HANDSHAKE_OK_V2, "CONNECTED"}, + {STREAM_HANDSHAKE_OK_V1, "CONNECTED"}, + {STREAM_HANDSHAKE_NEVER, ""}, + {STREAM_HANDSHAKE_ERROR_BAD_HANDSHAKE, "BAD HANDSHAKE"}, + {STREAM_HANDSHAKE_ERROR_LOCALHOST, "LOCALHOST"}, + {STREAM_HANDSHAKE_ERROR_ALREADY_CONNECTED, "ALREADY CONNECTED"}, + {STREAM_HANDSHAKE_ERROR_DENIED, "DENIED"}, + {STREAM_HANDSHAKE_ERROR_SEND_TIMEOUT, "SEND TIMEOUT"}, + {STREAM_HANDSHAKE_ERROR_RECEIVE_TIMEOUT, "RECEIVE TIMEOUT"}, + {STREAM_HANDSHAKE_ERROR_INVALID_CERTIFICATE, "INVALID CERTIFICATE"}, + {STREAM_HANDSHAKE_ERROR_SSL_ERROR, "SSL ERROR"}, + {STREAM_HANDSHAKE_ERROR_CANT_CONNECT, "CANT CONNECT"}, + {STREAM_HANDSHAKE_BUSY_TRY_LATER, "BUSY TRY LATER"}, + {STREAM_HANDSHAKE_INTERNAL_ERROR, "INTERNAL ERROR"}, + {STREAM_HANDSHAKE_INITIALIZATION, "REMOTE IS INITIALIZING"}, + {STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP, "DISCONNECTED HOST CLEANUP"}, + {STREAM_HANDSHAKE_DISCONNECT_STALE_RECEIVER, "DISCONNECTED STALE RECEIVER"}, + {STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN, "DISCONNECTED SHUTDOWN REQUESTED"}, + {STREAM_HANDSHAKE_DISCONNECT_NETDATA_EXIT, "DISCONNECTED NETDATA EXIT"}, + {STREAM_HANDSHAKE_DISCONNECT_PARSER_EXIT, "DISCONNECTED PARSE ENDED"}, + {STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR, "DISCONNECTED UNKNOWN SOCKET READ ERROR"}, + {STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, "DISCONNECTED PARSE ERROR"}, + {STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT, "DISCONNECTED RECEIVER LEFT"}, + {STREAM_HANDSHAKE_DISCONNECT_ORPHAN_HOST, "DISCONNECTED ORPHAN HOST"}, + {STREAM_HANDSHAKE_NON_STREAMABLE_HOST, "NON STREAMABLE HOST"}, + {STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_RECEIVER_READ_BUFFER, "DISCONNECTED NOT SUFFICIENT RCV READ BUFFER"}, + {STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_SENDER_COMPRESSION_FAILED, "DISCONNECTED SND COMPRESSION FAILED"}, + {STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_SENDER_SEND_BUFFER, "DISCONNECTED NOT SUFFICIENT SEND BUFFER"}, + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF, "DISCONNECTED SOCKET EOF"}, + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED, "DISCONNECTED SOCKET READ FAILED"}, + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT, "DISCONNECTED SOCKET READ TIMEOUT"}, + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_ERROR, "DISCONNECT SOCKET ERROR"}, + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_WRITE_FAILED, "DISCONNECTED SOCKET WRITE FAILED"}, + {STREAM_HANDSHAKE_DISCONNECT_SOCKET_CLOSED_BY_PARENT, "DISCONNECTED SOCKET CLOSED BY PARENT"}, + {STREAM_HANDSHAKE_ERROR_HTTP_UPGRADE, "HTTP UPGRADE ERROR"}, + {STREAM_HANDSHAKE_NO_HOST_IN_DESTINATION, "NO HOST IN DESTINATION - CONFIG ERROR"}, + {STREAM_HANDSHAKE_CONNECT_TIMEOUT, "CONNECT TIMEOUT"}, + {STREAM_HANDSHAKE_CONNECTION_REFUSED, "CONNECTION REFUSED"}, + {STREAM_HANDSHAKE_CANT_RESOLVE_HOSTNAME, "CANT RESOLVE HOSTNAME"}, + {STREAM_HANDSHAKE_PREPARING, "PREPARING"}, + {STREAM_HANDSHAKE_CONNECTING, "CONNECTING"}, + {STREAM_HANDSHAKE_CONNECTED, "CONNECTED"}, + {STREAM_HANDSHAKE_EXITING, "EXITING"}, + {STREAM_HANDSHAKE_NO_STREAM_INFO, "NO STREAM INFO"}, { 0, NULL }, }; -const char *stream_handshake_error_to_string(STREAM_HANDSHAKE handshake_error) { - if(handshake_error >= STREAM_HANDSHAKE_OK_V1) +const char *stream_handshake_error_to_string(STREAM_HANDSHAKE reason) { + if(reason >= STREAM_HANDSHAKE_OK_V1) // handshake_error is the whole version / capabilities number return "CONNECTED"; for(size_t i = 0; handshake_errors[i].str ; i++) { - if(handshake_error == handshake_errors[i].err) + if(reason == handshake_errors[i].err) return handshake_errors[i].str; } diff --git a/src/streaming/stream-handshake.h b/src/streaming/stream-handshake.h index 9b66cab97c730a..87127f0207a1e8 100644 --- a/src/streaming/stream-handshake.h +++ b/src/streaming/stream-handshake.h @@ -17,30 +17,27 @@ #define START_STREAMING_ERROR_INTERNAL_ERROR "The server encountered an internal error. Try later." #define START_STREAMING_ERROR_INITIALIZATION "The server is initializing. Try later." -#define RRDPUSH_STATUS_CONNECTED "CONNECTED" -#define RRDPUSH_STATUS_ALREADY_CONNECTED "ALREADY CONNECTED" -#define RRDPUSH_STATUS_DISCONNECTED "DISCONNECTED" -#define RRDPUSH_STATUS_RATE_LIMIT "RATE LIMIT TRY LATER" -#define RRDPUSH_STATUS_INITIALIZATION_IN_PROGRESS "INITIALIZATION IN PROGRESS RETRY LATER" -#define RRDPUSH_STATUS_INTERNAL_SERVER_ERROR "INTERNAL SERVER ERROR DROPPING CONNECTION" -#define RRDPUSH_STATUS_DUPLICATE_RECEIVER "DUPLICATE RECEIVER DROPPING CONNECTION" -#define RRDPUSH_STATUS_CANT_REPLY "CANT REPLY DROPPING CONNECTION" -#define RRDPUSH_STATUS_NO_HOSTNAME "NO HOSTNAME PERMISSION DENIED" -#define RRDPUSH_STATUS_NO_API_KEY "NO API KEY PERMISSION DENIED" -#define RRDPUSH_STATUS_INVALID_API_KEY "INVALID API KEY PERMISSION DENIED" -#define RRDPUSH_STATUS_NO_MACHINE_GUID "NO MACHINE GUID PERMISSION DENIED" -#define RRDPUSH_STATUS_MACHINE_GUID_DISABLED "MACHINE GUID DISABLED PERMISSION DENIED" -#define RRDPUSH_STATUS_INVALID_MACHINE_GUID "INVALID MACHINE GUID PERMISSION DENIED" -#define RRDPUSH_STATUS_API_KEY_DISABLED "API KEY DISABLED PERMISSION DENIED" -#define RRDPUSH_STATUS_NOT_ALLOWED_IP "NOT ALLOWED IP PERMISSION DENIED" -#define RRDPUSH_STATUS_LOCALHOST "LOCALHOST PERMISSION DENIED" -#define RRDPUSH_STATUS_PERMISSION_DENIED "PERMISSION DENIED" -#define RRDPUSH_STATUS_BAD_HANDSHAKE "BAD HANDSHAKE" -#define RRDPUSH_STATUS_TIMEOUT "TIMEOUT" -#define RRDPUSH_STATUS_CANT_UPGRADE_CONNECTION "CANT UPGRADE CONNECTION" -#define RRDPUSH_STATUS_SSL_ERROR "SSL ERROR" -#define RRDPUSH_STATUS_INVALID_SSL_CERTIFICATE "INVALID SSL CERTIFICATE" -#define RRDPUSH_STATUS_CANT_ESTABLISH_SSL_CONNECTION "CANT ESTABLISH SSL CONNECTION" +#define STREAM_STATUS_CONNECTED "CONNECTED" +#define STREAM_STATUS_ALREADY_CONNECTED "ALREADY CONNECTED" +#define STREAM_STATUS_DISCONNECTED "DISCONNECTED" +#define STREAM_STATUS_RATE_LIMIT "RATE LIMIT TRY LATER" +#define STREAM_STATUS_INITIALIZATION_IN_PROGRESS "INITIALIZATION IN PROGRESS RETRY LATER" +#define STREAM_STATUS_INTERNAL_SERVER_ERROR "INTERNAL SERVER ERROR DROPPING CONNECTION" +#define STREAM_STATUS_DUPLICATE_RECEIVER "DUPLICATE RECEIVER DROPPING CONNECTION" +#define STREAM_STATUS_CANT_REPLY "CANT REPLY DROPPING CONNECTION" +#define STREAM_STATUS_NO_HOSTNAME "NO HOSTNAME PERMISSION DENIED" +#define STREAM_STATUS_NO_API_KEY "NO API KEY PERMISSION DENIED" +#define STREAM_STATUS_INVALID_API_KEY "INVALID API KEY PERMISSION DENIED" +#define STREAM_STATUS_NO_MACHINE_GUID "NO MACHINE GUID PERMISSION DENIED" +#define STREAM_STATUS_MACHINE_GUID_DISABLED "MACHINE GUID DISABLED PERMISSION DENIED" +#define STREAM_STATUS_INVALID_MACHINE_GUID "INVALID MACHINE GUID PERMISSION DENIED" +#define STREAM_STATUS_API_KEY_DISABLED "API KEY DISABLED PERMISSION DENIED" +#define STREAM_STATUS_NOT_ALLOWED_IP "NOT ALLOWED IP PERMISSION DENIED" +#define STREAM_STATUS_LOCALHOST "LOCALHOST PERMISSION DENIED" +#define STREAM_STATUS_PERMISSION_DENIED "PERMISSION DENIED" +#define STREAM_STATUS_BAD_HANDSHAKE "BAD HANDSHAKE" +#define STREAM_STATUS_TIMEOUT "TIMEOUT" +#define STREAM_STATUS_CANT_UPGRADE_CONNECTION "CANT UPGRADE CONNECTION" typedef enum { STREAM_HANDSHAKE_OK_V3 = 3, // v3+ @@ -69,14 +66,28 @@ typedef enum { STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT = -20, STREAM_HANDSHAKE_DISCONNECT_ORPHAN_HOST = -21, STREAM_HANDSHAKE_NON_STREAMABLE_HOST = -22, - STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_READ_BUFFER = -23, - STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF = -24, - STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED = -25, - STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT = -26, - STREAM_HANDSHAKE_ERROR_HTTP_UPGRADE = -27, + STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_RECEIVER_READ_BUFFER = -23, + STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_SENDER_COMPRESSION_FAILED = -24, + STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_SENDER_SEND_BUFFER = -25, + STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF = -26, + STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED = -27, + STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT = -28, + STREAM_HANDSHAKE_DISCONNECT_SOCKET_ERROR = -29, + STREAM_HANDSHAKE_DISCONNECT_SOCKET_WRITE_FAILED = -30, + STREAM_HANDSHAKE_DISCONNECT_SOCKET_CLOSED_BY_PARENT = -31, + STREAM_HANDSHAKE_ERROR_HTTP_UPGRADE = -32, + STREAM_HANDSHAKE_NO_HOST_IN_DESTINATION = -33, + STREAM_HANDSHAKE_CONNECT_TIMEOUT = -34, + STREAM_HANDSHAKE_CONNECTION_REFUSED = -35, + STREAM_HANDSHAKE_CANT_RESOLVE_HOSTNAME = -36, + STREAM_HANDSHAKE_PREPARING = -37, + STREAM_HANDSHAKE_CONNECTING = -38, + STREAM_HANDSHAKE_CONNECTED = -39, + STREAM_HANDSHAKE_EXITING = -40, + STREAM_HANDSHAKE_NO_STREAM_INFO = -41, } STREAM_HANDSHAKE; -const char *stream_handshake_error_to_string(STREAM_HANDSHAKE handshake_error); +const char *stream_handshake_error_to_string(STREAM_HANDSHAKE reason); #endif //NETDATA_STREAM_HANDSHAKE_H diff --git a/src/streaming/stream-parents.c b/src/streaming/stream-parents.c new file mode 100644 index 00000000000000..c288431b806869 --- /dev/null +++ b/src/streaming/stream-parents.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream-sender-internals.h" + +#define TIME_TO_CONSIDER_PARENTS_SIMILAR 120 + +struct stream_parent { + STRING *destination; // the parent destination + bool ssl; // the parent uses SSL + + bool banned_permanently; // when the parent is the origin of this host + bool banned_for_this_session; // when the parent is before us in the streaming path + bool banned_temporarily_erroneous; // when the parent is blocked by another node we host + STREAM_HANDSHAKE reason; + uint32_t attempts; // how many times we have tried to connect to this parent + usec_t since_ut; // the last time we tried to connect to it + usec_t postpone_until_ut; // based on the reason, a randomized time to wait for reconnection + + struct { + ND_UUID host_id; // the machine_guid of the agent + int status; // the response code of the stream_info call + uint32_t nonce; // a random 32-bit number + size_t nodes; // how many nodes the parent has + size_t receivers; // how many receivers the parent has + + // these are from RRDHOST_STATUS and can only be used when status == 200 + RRDHOST_DB_STATUS db_status; + RRDHOST_DB_LIVENESS db_liveness; + RRDHOST_INGEST_TYPE ingest_type; + RRDHOST_INGEST_STATUS ingest_status; + time_t db_first_time_s; // the oldest timestamp for us in the parent's database + time_t db_last_time_s; // the latest timestamp for us in the parent's database + } remote; + + struct { + size_t batch; // the batch priority (>= 1, 0 == excluded) + size_t order; // the final order of the parent (>= 1, 0 == excluded) + bool random; // this batch has more than 1 parents, so we flipped coins to select order + bool info; // we go stream info from the parent + bool skipped; // we skipped this parent for some reason + } selection; + + STREAM_PARENT *prev; + STREAM_PARENT *next; +}; + +// -------------------------------------------------------------------------------------------------------------------- +// block unresponsive parents for some time, to allow speeding up the connection of the rest + +struct blocked_parent { + STRING *destination; + usec_t until; +}; + +DEFINE_JUDYL_TYPED(BLOCKED_PARENTS, struct blocked_parent *); +static BLOCKED_PARENTS_JudyLSet blocked_parents_set = { 0 }; +static RW_SPINLOCK blocked_parents_spinlock = NETDATA_RW_SPINLOCK_INITIALIZER; + +static void block_parent_for_all_nodes(STREAM_PARENT *d, time_t duration_s) { + rw_spinlock_write_lock(&blocked_parents_spinlock); + + struct blocked_parent *p = BLOCKED_PARENTS_GET(&blocked_parents_set, (Word_t)d->destination); + if(!p) { + p = callocz(1, sizeof(*p)); + p->destination = string_dup(d->destination); + BLOCKED_PARENTS_SET(&blocked_parents_set, (Word_t)p->destination, p); + } + p->until = now_monotonic_usec() + duration_s * USEC_PER_SEC; + + rw_spinlock_write_unlock(&blocked_parents_spinlock); +} + +static bool is_a_blocked_parent(STREAM_PARENT *d) { + rw_spinlock_read_lock(&blocked_parents_spinlock); + + struct blocked_parent *p = BLOCKED_PARENTS_GET(&blocked_parents_set, (Word_t)d->destination); + bool ret = p && p->until > now_monotonic_usec(); + + rw_spinlock_read_unlock(&blocked_parents_spinlock); + return ret; +} + +// -------------------------------------------------------------------------------------------------------------------- + +STREAM_HANDSHAKE stream_parent_get_disconnect_reason(STREAM_PARENT *d) { + if(!d) return STREAM_HANDSHAKE_INTERNAL_ERROR; + return d->reason; +} + +void stream_parent_set_disconnect_reason(STREAM_PARENT *d, STREAM_HANDSHAKE reason, time_t since) { + if(!d) return; + d->since_ut = since * USEC_PER_SEC; + d->reason = reason; +} + +static inline usec_t randomize_wait_ut(time_t min, time_t max) { + min = (min < SENDER_MIN_RECONNECT_DELAY ? SENDER_MIN_RECONNECT_DELAY : min); + if(max < min) max = min; + + usec_t min_ut = min * USEC_PER_SEC; + usec_t max_ut = max * USEC_PER_SEC; + usec_t wait_ut = min_ut + os_random(max_ut - min_ut); + return now_realtime_usec() + wait_ut; +} + +void rrdhost_stream_parents_reset(RRDHOST *host, STREAM_HANDSHAKE reason) { + usec_t until_ut = randomize_wait_ut(5, stream_send.parents.reconnect_delay_s); + rw_spinlock_write_lock(&host->stream.snd.parents.spinlock); + for (STREAM_PARENT *d = host->stream.snd.parents.all; d; d = d->next) { + d->postpone_until_ut = until_ut; + d->banned_for_this_session = false; + d->reason = reason; + } + rw_spinlock_write_unlock(&host->stream.snd.parents.spinlock); +} + +void stream_parent_set_reconnect_delay(STREAM_PARENT *d, STREAM_HANDSHAKE reason, time_t secs) { + if(!d) return; + d->reason = reason; + d->postpone_until_ut = randomize_wait_ut(5, secs); +} + +usec_t stream_parent_get_reconnection_ut(STREAM_PARENT *d) { + return d ? d->postpone_until_ut : 0; +} + +bool stream_parent_is_ssl(STREAM_PARENT *d) { + return d ? d->ssl : false; +} + +usec_t stream_parent_handshake_error_to_json(BUFFER *wb, RRDHOST *host) { + usec_t last_attempt = 0; + rw_spinlock_read_lock(&host->stream.snd.parents.spinlock); + for(STREAM_PARENT *d = host->stream.snd.parents.all; d ; d = d->next) { + if(d->since_ut > last_attempt) + last_attempt = d->since_ut; + + buffer_json_add_array_item_string(wb, stream_handshake_error_to_string(d->reason)); + } + rw_spinlock_read_unlock(&host->stream.snd.parents.spinlock); + return last_attempt; +} + +void rrdhost_stream_parents_to_json(BUFFER *wb, RRDHOST_STATUS *s) { + char buf[1024]; + + rw_spinlock_read_lock(&s->host->stream.snd.parents.spinlock); + + usec_t now_ut = now_realtime_usec(); + STREAM_PARENT *d; + for (d = s->host->stream.snd.parents.all; d; d = d->next) { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_uint64(wb, "attempts", d->attempts); + { + if (d->ssl) { + snprintfz(buf, sizeof(buf) - 1, "%s:SSL", string2str(d->destination)); + buffer_json_member_add_string(wb, "destination", buf); + } + else + buffer_json_member_add_string(wb, "destination", string2str(d->destination)); + + buffer_json_member_add_datetime_rfc3339(wb, "since", d->since_ut, false); + buffer_json_member_add_duration_ut(wb, "age", d->since_ut < now_ut ? (int64_t)(now_ut - d->since_ut) : 0); + + if(!d->banned_for_this_session && !d->banned_permanently && !d->banned_temporarily_erroneous) { + buffer_json_member_add_string(wb, "last_handshake", stream_handshake_error_to_string(d->reason)); + + if (d->postpone_until_ut > now_ut) { + buffer_json_member_add_datetime_rfc3339(wb, "next_check", d->postpone_until_ut, false); + buffer_json_member_add_duration_ut(wb, "next_in", (int64_t)(d->postpone_until_ut - now_ut)); + } + + if(d->selection.batch) { + buffer_json_member_add_uint64(wb, "batch", d->selection.batch); + buffer_json_member_add_uint64(wb, "order", d->selection.order); + buffer_json_member_add_boolean(wb, "random", d->selection.random); + } + + buffer_json_member_add_boolean(wb, "info", d->selection.info); + buffer_json_member_add_boolean(wb, "skipped", d->selection.skipped); + } + else { + if(d->banned_permanently) + buffer_json_member_add_string(wb, "ban", "it is the localhost"); + else if(d->banned_for_this_session) + buffer_json_member_add_string(wb, "ban", "it is our parent"); + else if(d->banned_temporarily_erroneous) + buffer_json_member_add_string(wb, "ban", "it is erroneous"); + } + } + buffer_json_object_close(wb); // each candidate + } + + rw_spinlock_read_unlock(&s->host->stream.snd.parents.spinlock); +} + +void rrdhost_stream_parent_ssl_init(struct sender_state *s) { + static SPINLOCK sp = NETDATA_SPINLOCK_INITIALIZER; + spinlock_lock(&sp); + + if(netdata_ssl_streaming_sender_ctx || !s->host) { + spinlock_unlock(&sp); + goto cleanup; + } + + rw_spinlock_read_lock(&s->host->stream.snd.parents.spinlock); + + for(STREAM_PARENT *d = s->host->stream.snd.parents.all; d ; d = d->next) { + if (d->ssl) { + // we need to initialize SSL + + netdata_ssl_initialize_ctx(NETDATA_SSL_STREAMING_SENDER_CTX); + + ssl_security_location_for_context( + netdata_ssl_streaming_sender_ctx, + string2str(stream_send.parents.ssl_ca_file), + string2str(stream_send.parents.ssl_ca_path)); + + // stop the loop + break; + } + } + + rw_spinlock_read_unlock(&s->host->stream.snd.parents.spinlock); + spinlock_unlock(&sp); + +cleanup: + s->sock.ctx = netdata_ssl_streaming_sender_ctx; + s->sock.verify_certificate = netdata_ssl_validate_certificate_sender; +} + +static void stream_parent_nd_sock_error_to_reason(STREAM_PARENT *d, ND_SOCK *sock) { + switch (sock->error) { + case ND_SOCK_ERR_CONNECTION_REFUSED: + d->reason = STREAM_HANDSHAKE_CONNECTION_REFUSED; + d->postpone_until_ut = randomize_wait_ut(30, 60); + block_parent_for_all_nodes(d, 30); + break; + + case ND_SOCK_ERR_CANNOT_RESOLVE_HOSTNAME: + d->reason = STREAM_HANDSHAKE_CANT_RESOLVE_HOSTNAME; + d->postpone_until_ut = randomize_wait_ut(30, 60); + block_parent_for_all_nodes(d, 30); + break; + + case ND_SOCK_ERR_NO_HOST_IN_DEFINITION: + d->reason = STREAM_HANDSHAKE_NO_HOST_IN_DESTINATION; + d->banned_for_this_session = true; + d->postpone_until_ut = randomize_wait_ut(30, 60); + block_parent_for_all_nodes(d, 30); + break; + + case ND_SOCK_ERR_TIMEOUT: + d->reason = STREAM_HANDSHAKE_CONNECT_TIMEOUT; + d->postpone_until_ut = randomize_wait_ut(300, d->remote.nodes < 10 ? 600 : 900); + block_parent_for_all_nodes(d, 300); + break; + + case ND_SOCK_ERR_SSL_INVALID_CERTIFICATE: + d->reason = STREAM_HANDSHAKE_ERROR_INVALID_CERTIFICATE; + d->postpone_until_ut = randomize_wait_ut(300, 600); + block_parent_for_all_nodes(d, 300); + break; + + case ND_SOCK_ERR_SSL_CANT_ESTABLISH_SSL_CONNECTION: + case ND_SOCK_ERR_SSL_FAILED_TO_OPEN: + d->reason = STREAM_HANDSHAKE_ERROR_SSL_ERROR; + d->postpone_until_ut = randomize_wait_ut(60, 180); + block_parent_for_all_nodes(d, 60); + break; + + default: + case ND_SOCK_ERR_POLL_ERROR: + case ND_SOCK_ERR_FAILED_TO_CREATE_SOCKET: + case ND_SOCK_ERR_UNKNOWN_ERROR: + d->reason = STREAM_HANDSHAKE_INTERNAL_ERROR; + d->postpone_until_ut = randomize_wait_ut(30, 60); + break; + + case ND_SOCK_ERR_THREAD_CANCELLED: + case ND_SOCK_ERR_NO_DESTINATION_AVAILABLE: + d->reason = STREAM_HANDSHAKE_INTERNAL_ERROR; + d->postpone_until_ut = randomize_wait_ut(30, 60); + break; + } +} + +int stream_info_to_json_v1(BUFFER *wb, const char *machine_guid) { + buffer_reset(wb); + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT); + + RRDHOST_STATUS status = { 0 }; + int ret = HTTP_RESP_OK; + RRDHOST *host; + if(!machine_guid || !*machine_guid || !(host = rrdhost_find_by_guid(machine_guid))) + ret = HTTP_RESP_NOT_FOUND; + else + rrdhost_status(host, now_realtime_sec(), &status); + + buffer_json_member_add_uint64(wb, "version", 1); + buffer_json_member_add_uint64(wb, "status", ret); + buffer_json_member_add_uuid(wb, "host_id", localhost->host_id.uuid); + buffer_json_member_add_uint64(wb, "nodes", dictionary_entries(rrdhost_root_index)); + buffer_json_member_add_uint64(wb, "receivers", stream_receivers_currently_connected()); + buffer_json_member_add_uint64(wb, "nonce", os_random32()); + + if(ret == HTTP_RESP_OK) { + buffer_json_member_add_string(wb, "db_status", rrdhost_db_status_to_string(status.db.status)); + buffer_json_member_add_string(wb, "db_liveness", rrdhost_db_liveness_to_string(status.db.liveness)); + buffer_json_member_add_string(wb, "ingest_type", rrdhost_ingest_type_to_string(status.ingest.type)); + buffer_json_member_add_string(wb, "ingest_status", rrdhost_ingest_status_to_string(status.ingest.status)); + buffer_json_member_add_uint64(wb, "first_time_s", status.db.first_time_s); + buffer_json_member_add_uint64(wb, "last_time_s", status.db.last_time_s); + } + + buffer_json_finalize(wb); + return ret; +} + +static bool stream_info_json_parse_v1(struct json_object *jobj, const char *path, STREAM_PARENT *d, BUFFER *error) { + uint32_t version = 0; (void)version; + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "version", version, error, true); + + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "status", d->remote.status, error, true); + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "host_id", d->remote.host_id.uuid, error, true); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "nodes", d->remote.nodes, error, true); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "receivers", d->remote.receivers, error, true); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "nonce", d->remote.nonce, error, true); + + if(d->remote.status == HTTP_RESP_OK) { + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "first_time_s", d->remote.db_first_time_s, error, true); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "last_time_s", d->remote.db_last_time_s, error, true); + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "db_status", RRDHOST_DB_STATUS_2id, d->remote.db_status, error, true); + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "db_liveness", RRDHOST_DB_LIVENESS_2id, d->remote.db_liveness, error, true); + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "ingest_type", RRDHOST_INGEST_TYPE_2id, d->remote.ingest_type, error, true); + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "ingest_status", RRDHOST_INGEST_STATUS_2id, d->remote.ingest_status, error, true); + return true; + } + + d->remote.db_first_time_s = 0; + d->remote.db_last_time_s = 0; + d->remote.db_status = 0; + d->remote.db_liveness = 0; + d->remote.ingest_type = 0; + d->remote.ingest_status = 0; + return false; +} + +static bool stream_info_fetch(STREAM_PARENT *d, const char *uuid, int default_port, ND_SOCK *sender_sock, bool ssl, const char *hostname) { + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_DST_IP, d->destination), + ND_LOG_FIELD_I64(NDF_DST_PORT, default_port), + ND_LOG_FIELD_TXT(NDF_REQUEST_METHOD, "GET"), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + char buf[HTTP_HEADER_SIZE]; + CLEAN_ND_SOCK sock = ND_SOCK_INIT(sender_sock->ctx, sender_sock->verify_certificate); + + // Build HTTP request + snprintf(buf, sizeof(buf), + "GET /api/v3/stream_info?machine_guid=%s" HTTP_1_1 HTTP_ENDL + "Host: %s" HTTP_ENDL + "User-Agent: %s/%s" HTTP_ENDL + "Accept: */*" HTTP_ENDL + "Accept-Encoding: identity" HTTP_ENDL // disable chunked encoding + "TE: identity" HTTP_ENDL // disable chunked encoding + "Pragma: no-cache" HTTP_ENDL + "Cache-Control: no-cache" HTTP_ENDL + "Connection: close" HTTP_HDR_END, + uuid, + string2str(d->destination), + rrdhost_program_name(localhost), + rrdhost_program_version(localhost)); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: fetching stream info from '%s'...", + hostname, string2str(d->destination)); + + // Establish connection + d->reason = STREAM_HANDSHAKE_CONNECTING; + if (!nd_sock_connect_to_this(&sock, string2str(d->destination), default_port, 5, ssl)) { + d->selection.info = false; + stream_parent_nd_sock_error_to_reason(d, &sock); + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: failed to connect for stream info to '%s': %s", + hostname, string2str(d->destination), + ND_SOCK_ERROR_2str(sock.error)); + return false; + } + + // Send HTTP request + ssize_t sent = nd_sock_send_timeout(&sock, buf, strlen(buf), 0, 5); + if (sent <= 0) { + d->selection.info = false; + stream_parent_nd_sock_error_to_reason(d, &sock); + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: failed to send stream info request to '%s': %s", + hostname, string2str(d->destination), + ND_SOCK_ERROR_2str(sock.error)); + return false; + } + + // Receive HTTP response + size_t total_received = 0; + size_t payload_received = 0; + size_t content_length = 0; + char *payload_start = NULL; + + while (!payload_received || content_length < payload_received) { + size_t remaining = sizeof(buf) - total_received; + + if (remaining <= 1) { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: stream info receive buffer is full while receiving response from '%s'", + hostname, string2str(d->destination)); + d->selection.info = false; + d->reason = STREAM_HANDSHAKE_INTERNAL_ERROR; + return false; + } + + ssize_t received = nd_sock_recv_timeout(&sock, buf + total_received, remaining - 1, 0, 5); + if (received <= 0) { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: socket receive error while querying stream info on '%s' " + "(total received %zu, payload received %zu, content length %zu): %s", + hostname, string2str(d->destination), + total_received, payload_received, content_length, + ND_SOCK_ERROR_2str(sock.error)); + + d->selection.info = false; + stream_parent_nd_sock_error_to_reason(d, &sock); + return false; + } + + total_received += received; + buf[total_received] = '\0'; + + if(!payload_start) { + char *headers_end = strstr(buf, HTTP_HDR_END); + if (!headers_end) + // we have not received the whole header yet + continue; + + payload_start = headers_end + sizeof(HTTP_HDR_END) - 1; + } + + // the payload size so far + payload_received = total_received - (payload_start - buf); + + if(!content_length) { + char *content_length_ptr = strstr(buf, "Content-Length: "); + if (!content_length_ptr) { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: stream info response from '%s' does not have a Content-Length", + hostname, string2str(d->destination)); + + d->selection.info = false; + d->reason = STREAM_HANDSHAKE_INTERNAL_ERROR; + return false; + } + content_length = strtoul(content_length_ptr + strlen("Content-Length: "), NULL, 10); + if (!content_length) { + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: stream info response from '%s' has invalid Content-Length", + hostname, string2str(d->destination)); + + d->selection.info = false; + d->reason = STREAM_HANDSHAKE_INTERNAL_ERROR; + return false; + } + } + } + + // Parse HTTP response and extract JSON + CLEAN_JSON_OBJECT *jobj = json_tokener_parse(payload_start); + if (!jobj) { + d->selection.info = false; + d->reason = STREAM_HANDSHAKE_NO_STREAM_INFO; + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: failed to parse stream info response from '%s', JSON data: %s", + hostname, string2str(d->destination), payload_start); + return false; + } + + CLEAN_BUFFER *error = buffer_create(0, NULL); + + if(!stream_info_json_parse_v1(jobj, "", d, error)) { + d->selection.info = false; + d->reason = STREAM_HANDSHAKE_NO_STREAM_INFO; + nd_log(NDLS_DAEMON, NDLP_WARNING, + "STREAM PARENTS of %s: failed to extract fields from JSON stream info response from '%s': %s", + hostname, string2str(d->destination), + buffer_tostring(error)); + return false; + } + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: received stream_info data from '%s': " + "status: %d, nodes: %zu, receivers: %zu, first_time_s: %ld, last_time_s: %ld, " + "db status: %s, db liveness: %s, ingest type: %s, ingest status: %s", + hostname, string2str(d->destination), + d->remote.status, d->remote.nodes, d->remote.receivers, + d->remote.db_first_time_s, d->remote.db_last_time_s, + RRDHOST_DB_STATUS_2str(d->remote.db_status), + RRDHOST_DB_LIVENESS_2str(d->remote.db_liveness), + RRDHOST_INGEST_TYPE_2str(d->remote.ingest_type), + RRDHOST_INGEST_STATUS_2str(d->remote.ingest_status)); + + d->selection.info = true; + d->reason = STREAM_HANDSHAKE_NEVER; + return true; +} + +static int compare_last_time(const void *a, const void *b) { + STREAM_PARENT *parent_a = *(STREAM_PARENT **)a; + STREAM_PARENT *parent_b = *(STREAM_PARENT **)b; + + if (parent_a->remote.db_last_time_s < parent_b->remote.db_last_time_s) return 1; + else if (parent_a->remote.db_last_time_s > parent_b->remote.db_last_time_s) return -1; + else { + if(parent_a->since_ut < parent_b->since_ut) return -1; + else if(parent_a->since_ut > parent_b->since_ut) return 1; + else { + if(parent_a->attempts < parent_b->attempts) return -1; + else if(parent_a->attempts > parent_b->attempts) return 1; + else return 0; + } + } +} + +bool stream_parent_connect_to_one_unsafe( + ND_SOCK *sender_sock, + RRDHOST *host, + int default_port, + time_t timeout, + char *connected_to, + size_t connected_to_size, + STREAM_PARENT **destination) +{ + sender_sock->error = ND_SOCK_ERR_NO_DESTINATION_AVAILABLE; + + // count the parents + size_t size = 0; + for (STREAM_PARENT *d = host->stream.snd.parents.all; d; d = d->next) { + d->selection.order = 0; + d->selection.batch = 0; + d->selection.random = false; + d->selection.info = false; + d->selection.skipped = true; + size++; + } + + // do we have any parents? + if(!size) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, "STREAM PARENTS of %s: no parents configured", rrdhost_hostname(host)); + return false; + } + + STREAM_PARENT *array[size]; + usec_t now_ut = now_realtime_usec(); + + // fetch stream info for all of them and put them in the array + size_t count = 0, skipped_but_useful = 0, skipped_not_useful = 0; + for (STREAM_PARENT *d = host->stream.snd.parents.all; d && count < size ; d = d->next) { + if (nd_thread_signaled_to_cancel()) { + sender_sock->error = ND_SOCK_ERR_THREAD_CANCELLED; + return false; + } + + // make sure they all have a random number + // this is taken from the parent, but if the stream_info call fails + // we generate a random number for every parent here + d->remote.nonce = os_random32(); + d->banned_temporarily_erroneous = is_a_blocked_parent(d); + + if (d->banned_permanently || d->banned_for_this_session || d->banned_temporarily_erroneous) + continue; + + if (d->postpone_until_ut > now_ut) { + skipped_but_useful++; + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: skipping useful parent '%s': POSTPONED FOR %ld SECS MORE: %s", + rrdhost_hostname(host), + string2str(d->destination), + (time_t)((d->postpone_until_ut - now_ut) / USEC_PER_SEC), + stream_handshake_error_to_string(d->reason)); + continue; + } + + bool skip = false; + if(stream_info_fetch(d, host->machine_guid, default_port, + sender_sock, stream_parent_is_ssl(d), rrdhost_hostname(host))) { + switch(d->remote.ingest_type) { + case RRDHOST_INGEST_TYPE_VIRTUAL: + case RRDHOST_INGEST_TYPE_LOCALHOST: + d->reason = STREAM_HANDSHAKE_ERROR_LOCALHOST; + if(rrdhost_is_host_in_stream_path_before_us(host, d->remote.host_id, 1)) { + // we passed hops == 1, to make sure this succeeds only when the parent + // is the origin child of this node + d->since_ut = now_ut; + d->banned_permanently = true; + skipped_not_useful++; + nd_log(NDLS_DAEMON, NDLP_NOTICE, + "STREAM PARENTS of %s: destination '%s' is banned permanently because it is the origin server", + rrdhost_hostname(host), string2str(d->destination)); + continue; + } + else + skip = true; + break; + + default: + case RRDHOST_INGEST_TYPE_CHILD: + case RRDHOST_INGEST_TYPE_ARCHIVED: + break; + } + + switch(d->remote.ingest_status) { + case RRDHOST_INGEST_STATUS_INITIALIZING: + d->reason = STREAM_HANDSHAKE_INITIALIZATION; + skip = true; + break; + + case RRDHOST_INGEST_STATUS_REPLICATING: + case RRDHOST_INGEST_STATUS_ONLINE: + d->reason = STREAM_HANDSHAKE_ERROR_ALREADY_CONNECTED; + if(rrdhost_is_host_in_stream_path_before_us(host, d->remote.host_id, host->sender->hops)) { + d->since_ut = now_ut; + d->banned_for_this_session = true; + skipped_not_useful++; + nd_log(NDLS_DAEMON, NDLP_NOTICE, + "STREAM PARENTS of %s: destination '%s' is banned for this session, because it is in our path before us.", + rrdhost_hostname(host), string2str(d->destination)); + continue; + } + else + skip = true; + break; + + default: + case RRDHOST_INGEST_STATUS_OFFLINE: + break; + } + } + + if(skip) { + skipped_but_useful++; + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: skipping useful parent '%s': %s", + rrdhost_hostname(host), + string2str(d->destination), + stream_handshake_error_to_string(d->reason)); + } + else { + d->selection.skipped = false; + d->selection.batch = count + 1; + d->selection.order = count + 1; + array[count++] = d; + } + } + + // can we use any parent? + if(!count) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: no parents available (%zu skipped but useful, %zu skipped not useful)", + rrdhost_hostname(host), + skipped_but_useful, skipped_not_useful); + return false; + } + + // order the parents in the array the way we want to connect + if(count > 1) { + qsort(array, count, sizeof(STREAM_PARENT *), compare_last_time); + + size_t base = 0, batch = 0; + while (base < count) { + // find how many have similar db_last_time_s; + size_t similar = 1; + if(!array[base]->remote.nonce) array[base]->remote.nonce = os_random32(); + time_t tB = array[base]->remote.db_last_time_s; + for (size_t i = base + 1; i < count; i++) { + time_t tN = array[i]->remote.db_last_time_s; + if ((tN > tB && tN - tB <= TIME_TO_CONSIDER_PARENTS_SIMILAR) || + (tB - tN <= TIME_TO_CONSIDER_PARENTS_SIMILAR)) + similar++; + else + break; + } + + // if we have only 1 similar, move on + if (similar == 1) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: reordering keeps parent No %zu, '%s'", + rrdhost_hostname(host), base, string2str(array[base]->destination)); + array[base]->selection.order = base + 1; + array[base]->selection.batch = batch + 1; + array[base]->selection.random = false; + base++; + batch++; + continue; + } + else { + // reorder the parents who have similar db_last_time + + while (similar > 1) { + size_t chosen = base; + for(size_t i = base + 1 ; i < base + similar ;i++) { + uint32_t i_nonce = array[i]->remote.nonce | os_random32(); + uint32_t chosen_nonce = array[chosen]->remote.nonce | os_random32(); + if(i_nonce > chosen_nonce) chosen = i; + } + + if (chosen != base) + SWAP(array[base], array[chosen]); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: random reordering of %zu similar parents (slots %zu to %zu), No %zu is '%s'", + rrdhost_hostname(host), + similar, base, base + similar, + base, string2str(array[base]->destination)); + + array[base]->selection.order = base + 1; + array[base]->selection.batch = batch + 1; + array[base]->selection.random = true; + base++; + similar--; + } + + // the last one of the similar + array[base]->selection.order = base + 1; + array[base]->selection.batch = batch + 1; + array[base]->selection.random = true; + base++; + batch++; + } + } + } + else { + array[0]->selection.order = 1; + array[0]->selection.batch = 1; + array[0]->selection.random = false; + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: only 1 parent is available: '%s'", + rrdhost_hostname(host), string2str(array[0]->destination)); + } + + // now the parents are sorted based on preference of connection + for(size_t i = 0; i < count ;i++) { + STREAM_PARENT *d = array[i]; + + if(d->postpone_until_ut > now_ut) + continue; + + if(nd_thread_signaled_to_cancel()) { + sender_sock->error = ND_SOCK_ERR_THREAD_CANCELLED; + return false; + } + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: connecting to '%s' (default port: %d, parent %zu of %zu)...", + rrdhost_hostname(host), string2str(d->destination), default_port, + i + 1, count); + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_DST_IP, d->destination), + ND_LOG_FIELD_I64(NDF_DST_PORT, default_port), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + d->since_ut = now_ut; + d->attempts++; + if (nd_sock_connect_to_this(sender_sock, string2str(d->destination), + default_port, timeout, stream_parent_is_ssl(d))) { + + if (connected_to && connected_to_size) + strncpyz(connected_to, string2str(d->destination), connected_to_size); + + *destination = d; + + // move the current item to the end of the list + // without this, this destination will break the loop again and again + // not advancing the destinations to find one that may work + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->stream.snd.parents.all, d, prev, next); + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(host->stream.snd.parents.all, d, prev, next); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: connected to '%s' (default port: %d, fd %d)...", + rrdhost_hostname(host), string2str(d->destination), default_port, + sender_sock->fd); + + sender_sock->error = ND_SOCK_ERR_NONE; + return true; + } + else { + stream_parent_nd_sock_error_to_reason(d, sender_sock); + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: stream connection to '%s' failed (default port: %d): %s", + rrdhost_hostname(host), + string2str(d->destination), default_port, + ND_SOCK_ERROR_2str(sender_sock->error)); + } + } + + return false; +} + +bool stream_parent_connect_to_one( + ND_SOCK *sender_sock, + RRDHOST *host, + int default_port, + time_t timeout, + char *connected_to, + size_t connected_to_size, + STREAM_PARENT **destination) { + + rw_spinlock_read_lock(&host->stream.snd.parents.spinlock); + bool rc = stream_parent_connect_to_one_unsafe( + sender_sock, host, default_port, timeout, connected_to, connected_to_size, destination); + rw_spinlock_read_unlock(&host->stream.snd.parents.spinlock); + return rc; +} + +// -------------------------------------------------------------------------------------------------------------------- +// create stream parents linked list + +struct stream_parent_init_tmp { + RRDHOST *host; + STREAM_PARENT *list; + int count; +}; + +static bool stream_parent_add_one_unsafe(char *entry, void *data) { + struct stream_parent_init_tmp *t = data; + + STREAM_PARENT *d = callocz(1, sizeof(STREAM_PARENT)); + char *colon_ssl = strstr(entry, ":SSL"); + if(colon_ssl) { + *colon_ssl = '\0'; + d->ssl = true; + } + else + d->ssl = false; + + d->destination = string_strdupz(entry); + d->since_ut = now_realtime_usec(); + + __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(STREAM_PARENT), __ATOMIC_RELAXED); + + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(t->list, d, prev, next); + + t->count++; + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM PARENTS of %s: added streaming destination No %d: '%s'", + rrdhost_hostname(t->host), t->count, string2str(d->destination)); + + return false; // we return false, so that we will get all defined destinations +} + +void rrdhost_stream_parents_update_from_destination(RRDHOST *host) { + rw_spinlock_write_lock(&host->stream.snd.parents.spinlock); + rrdhost_stream_parents_free(host, true); + + if(host->stream.snd.destination) { + struct stream_parent_init_tmp t = { + .host = host, + .list = NULL, + .count = 0, + }; + foreach_entry_in_connection_string(string2str(host->stream.snd.destination), stream_parent_add_one_unsafe, &t); + host->stream.snd.parents.all = t.list; + } + + rw_spinlock_write_unlock(&host->stream.snd.parents.spinlock); +} + +void rrdhost_stream_parents_free(RRDHOST *host, bool having_write_lock) { + if(!having_write_lock) + rw_spinlock_write_lock(&host->stream.snd.parents.spinlock); + + while (host->stream.snd.parents.all) { + STREAM_PARENT *tmp = host->stream.snd.parents.all; + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->stream.snd.parents.all, tmp, prev, next); + string_freez(tmp->destination); + freez(tmp); + __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(STREAM_PARENT), __ATOMIC_RELAXED); + } + + host->stream.snd.parents.all = NULL; + + if(!having_write_lock) + rw_spinlock_write_unlock(&host->stream.snd.parents.spinlock); +} + +void rrdhost_stream_parents_init(RRDHOST *host) { + rw_spinlock_init(&host->stream.snd.parents.spinlock); +} diff --git a/src/streaming/stream-parents.h b/src/streaming/stream-parents.h new file mode 100644 index 00000000000000..fb867a1317e881 --- /dev/null +++ b/src/streaming/stream-parents.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_STREAM_PARENTS_H +#define NETDATA_STREAM_PARENTS_H + +#include "libnetdata/libnetdata.h" + +struct rrdhost; +struct rrdhost_status; +struct stream_parent; +typedef struct stream_parent STREAM_PARENT; + +typedef struct rrdhost_stream_parents { + RW_SPINLOCK spinlock; + STREAM_PARENT *all; // a linked list of possible destinations + STREAM_PARENT *current; // the current destination from the above list +} RRDHOST_STREAM_PARENTS; + +#include "stream-handshake.h" +#include "rrdhost-status.h" + +void rrdhost_stream_parent_ssl_init(struct sender_state *s); + +int stream_info_to_json_v1(BUFFER *wb, const char *machine_guid); + +void rrdhost_stream_parents_reset(RRDHOST *host, STREAM_HANDSHAKE reason); + +void rrdhost_stream_parents_update_from_destination(RRDHOST *host); +void rrdhost_stream_parents_free(struct rrdhost *host, bool having_write_lock); + +bool stream_parent_connect_to_one( + ND_SOCK *sender_sock, + struct rrdhost *host, + int default_port, + time_t timeout, + char *connected_to, + size_t connected_to_size, + STREAM_PARENT **destination); + +void rrdhost_stream_parents_to_json(BUFFER *wb, struct rrdhost_status *s); +STREAM_HANDSHAKE stream_parent_get_disconnect_reason(STREAM_PARENT *d); +void stream_parent_set_disconnect_reason(STREAM_PARENT *d, STREAM_HANDSHAKE reason, time_t since); +void stream_parent_set_reconnect_delay(STREAM_PARENT *d, STREAM_HANDSHAKE reason, time_t secs); +usec_t stream_parent_get_reconnection_ut(STREAM_PARENT *d); +bool stream_parent_is_ssl(STREAM_PARENT *d); + +usec_t stream_parent_handshake_error_to_json(BUFFER *wb, struct rrdhost *host); + +void rrdhost_stream_parents_init(RRDHOST *host); + +#endif //NETDATA_STREAM_PARENTS_H diff --git a/src/streaming/stream-path.c b/src/streaming/stream-path.c index 4824f5a6dbd865..c40a1d427d372b 100644 --- a/src/streaming/stream-path.c +++ b/src/streaming/stream-path.c @@ -1,9 +1,34 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "stream-path.h" -#include "rrdpush.h" +#include "stream.h" +#include "stream-receiver-internals.h" +#include "stream-sender-internals.h" #include "plugins.d/pluginsd_internals.h" +typedef enum __attribute__((packed)) { + STREAM_PATH_FLAG_NONE = 0, + STREAM_PATH_FLAG_ACLK = (1 << 0), + STREAM_PATH_FLAG_HEALTH = (1 << 1), + STREAM_PATH_FLAG_ML = (1 << 2), + STREAM_PATH_FLAG_EPHEMERAL = (1 << 3), + STREAM_PATH_FLAG_VIRTUAL = (1 << 4), +} STREAM_PATH_FLAGS; + +typedef struct stream_path { + STRING *hostname; // the hostname of the agent + ND_UUID host_id; // the machine guid of the agent + ND_UUID node_id; // the cloud node id of the agent + ND_UUID claim_id; // the cloud claim id of the agent + time_t since; // the timestamp of the last update + time_t first_time_t; // the oldest timestamp in the db + int16_t hops; // -1 = stale node, 0 = localhost, >0 the hops count + STREAM_PATH_FLAGS flags; // ACLK or NONE for the moment + STREAM_CAPABILITIES capabilities; // streaming connection capabilities + uint32_t start_time_ms; // median time in ms the agent needs to start + uint32_t shutdown_time_ms; // median time in ms the agent needs to shutdown +} STREAM_PATH; + ENUM_STR_MAP_DEFINE(STREAM_PATH_FLAGS) = { { .id = STREAM_PATH_FLAG_ACLK, .name = "aclk" }, { .id = STREAM_PATH_FLAG_HEALTH, .name = "health" }, @@ -17,7 +42,7 @@ ENUM_STR_MAP_DEFINE(STREAM_PATH_FLAGS) = { BITMAP_STR_DEFINE_FUNCTIONS(STREAM_PATH_FLAGS, STREAM_PATH_FLAG_NONE, ""); -static void stream_path_clear(STREAM_PATH *p) { +static void stream_path_cleanup(STREAM_PATH *p) { string_freez(p->hostname); p->hostname = NULL; p->host_id = UUID_ZERO; @@ -28,42 +53,45 @@ static void stream_path_clear(STREAM_PATH *p) { p->first_time_t = 0; p->capabilities = 0; p->flags = STREAM_PATH_FLAG_NONE; - p->start_time = 0; - p->shutdown_time = 0; + p->start_time_ms = 0; + p->shutdown_time_ms = 0; } static void rrdhost_stream_path_clear_unsafe(RRDHOST *host, bool destroy) { - for(size_t i = 0; i < host->rrdpush.path.used ; i++) - stream_path_clear(&host->rrdpush.path.array[i]); + for(size_t i = 0; i < host->stream.path.used ; i++) + stream_path_cleanup(&host->stream.path.array[i]); - host->rrdpush.path.used = 0; + host->stream.path.used = 0; if(destroy) { - freez(host->rrdpush.path.array); - host->rrdpush.path.array = NULL; - host->rrdpush.path.size = 0; + freez(host->stream.path.array); + host->stream.path.array = NULL; + host->stream.path.size = 0; } } void rrdhost_stream_path_clear(RRDHOST *host, bool destroy) { - spinlock_lock(&host->rrdpush.path.spinlock); + rw_spinlock_write_lock(&host->stream.path.spinlock); rrdhost_stream_path_clear_unsafe(host, destroy); - spinlock_unlock(&host->rrdpush.path.spinlock); + rw_spinlock_write_unlock(&host->stream.path.spinlock); } static void stream_path_to_json_object(BUFFER *wb, STREAM_PATH *p) { buffer_json_add_array_item_object(wb); - buffer_json_member_add_string(wb, "hostname", string2str(p->hostname)); - buffer_json_member_add_uuid(wb, "host_id", p->host_id.uuid); - buffer_json_member_add_uuid(wb, "node_id", p->node_id.uuid); - buffer_json_member_add_uuid(wb, "claim_id", p->claim_id.uuid); - buffer_json_member_add_int64(wb, "hops", p->hops); - buffer_json_member_add_uint64(wb, "since", p->since); - buffer_json_member_add_uint64(wb, "first_time_t", p->first_time_t); - buffer_json_member_add_uint64(wb, "start_time", p->start_time); - buffer_json_member_add_uint64(wb, "shutdown_time", p->shutdown_time); - stream_capabilities_to_json_array(wb, p->capabilities, "capabilities"); - STREAM_PATH_FLAGS_2json(wb, "flags", p->flags); + { + buffer_json_member_add_uint64(wb, "version", 1); + buffer_json_member_add_string(wb, "hostname", string2str(p->hostname)); + buffer_json_member_add_uuid(wb, "host_id", p->host_id.uuid); + buffer_json_member_add_uuid(wb, "node_id", p->node_id.uuid); + buffer_json_member_add_uuid(wb, "claim_id", p->claim_id.uuid); + buffer_json_member_add_int64(wb, "hops", p->hops); + buffer_json_member_add_uint64(wb, "since", p->since); + buffer_json_member_add_uint64(wb, "first_time_t", p->first_time_t); + buffer_json_member_add_uint64(wb, "start_time", p->start_time_ms); + buffer_json_member_add_uint64(wb, "shutdown_time", p->shutdown_time_ms); + stream_capabilities_to_json_array(wb, p->capabilities, "capabilities"); + STREAM_PATH_FLAGS_2json(wb, "flags", p->flags); + } buffer_json_object_close(wb); } @@ -76,8 +104,8 @@ static STREAM_PATH rrdhost_stream_path_self(RRDHOST *host) { p.host_id = localhost->host_id; p.node_id = localhost->node_id; p.claim_id = claim_id_get_uuid(); - p.start_time = get_agent_event_time_median(EVENT_AGENT_START_TIME) / USEC_PER_MS; - p.shutdown_time = get_agent_event_time_median(EVENT_AGENT_SHUTDOWN_TIME) / USEC_PER_MS; + p.start_time_ms = get_agent_event_time_median(EVENT_AGENT_START_TIME) / USEC_PER_MS; + p.shutdown_time_ms = get_agent_event_time_median(EVENT_AGENT_SHUTDOWN_TIME) / USEC_PER_MS; p.flags = STREAM_PATH_FLAG_NONE; if(!UUIDiszero(p.claim_id)) @@ -89,13 +117,13 @@ static STREAM_PATH rrdhost_stream_path_self(RRDHOST *host) { if(rrdhost_option_check(host, RRDHOST_OPTION_VIRTUAL_HOST)) p.flags |= STREAM_PATH_FLAG_VIRTUAL; - if(host->health.health_enabled) + if(host->health.enabled) p.flags |= STREAM_PATH_FLAG_HEALTH; if(ml_enabled(host)) p.flags |= STREAM_PATH_FLAG_ML; - spinlock_lock(&host->receiver_lock); + rrdhost_receiver_lock(host); if(host->receiver) { p.hops = (int16_t)host->receiver->hops; p.since = host->receiver->connected_since_s; @@ -104,7 +132,7 @@ static STREAM_PATH rrdhost_stream_path_self(RRDHOST *host) { p.hops = (is_localhost) ? 0 : -1; // -1 for stale nodes p.since = netdata_start_time; } - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); // the following may get the receiver lock again! p.capabilities = stream_our_capabilities(host, true); @@ -114,34 +142,51 @@ static STREAM_PATH rrdhost_stream_path_self(RRDHOST *host) { return p; } -STREAM_PATH rrdhost_stream_path_fetch(RRDHOST *host) { - STREAM_PATH p = { 0 }; +uint64_t rrdhost_stream_path_total_reboot_time_ms(RRDHOST *host) { + uint64_t total_ms = 0; - spinlock_lock(&host->rrdpush.path.spinlock); - for (size_t i = 0; i < host->rrdpush.path.used; i++) { - STREAM_PATH *tmp_path = &host->rrdpush.path.array[i]; + rw_spinlock_read_lock(&host->stream.path.spinlock); + for (size_t i = 0; i < host->stream.path.used; i++) { + STREAM_PATH *tmp_path = &host->stream.path.array[i]; if(UUIDeq(host->host_id, tmp_path->host_id)) { - p = *tmp_path; + total_ms = tmp_path->start_time_ms + tmp_path->shutdown_time_ms; break; } } - spinlock_unlock(&host->rrdpush.path.spinlock); - return p; + rw_spinlock_read_unlock(&host->stream.path.spinlock); + return total_ms; +} + +bool rrdhost_is_host_in_stream_path_before_us(struct rrdhost *host, ND_UUID remote_agent_host_id, int16_t our_hops) { + if(UUIDiszero(remote_agent_host_id)) return false; + if(UUIDeq(localhost->host_id, remote_agent_host_id)) return true; + + bool rc = false; + rw_spinlock_read_lock(&host->stream.path.spinlock); + for (size_t i = 0; i < host->stream.path.used; i++) { + STREAM_PATH *p = &host->stream.path.array[i]; + if(UUIDeq(remote_agent_host_id, p->host_id) && p->hops < our_hops) { + rc = true; + break; + } + } + rw_spinlock_read_unlock(&host->stream.path.spinlock); + return rc; } void rrdhost_stream_path_to_json(BUFFER *wb, struct rrdhost *host, const char *key, bool add_version) { if(add_version) buffer_json_member_add_uint64(wb, "version", 1); - spinlock_lock(&host->rrdpush.path.spinlock); + STREAM_PATH tmp = rrdhost_stream_path_self(host); + + rw_spinlock_read_lock(&host->stream.path.spinlock); buffer_json_member_add_array(wb, key); { { - STREAM_PATH tmp = rrdhost_stream_path_self(host); - bool found_self = false; - for (size_t i = 0; i < host->rrdpush.path.used; i++) { - STREAM_PATH *p = &host->rrdpush.path.array[i]; + for (size_t i = 0; i < host->stream.path.used; i++) { + STREAM_PATH *p = &host->stream.path.array[i]; if(UUIDeq(localhost->host_id, p->host_id)) { // this is us, use the current data p = &tmp; @@ -155,12 +200,12 @@ void rrdhost_stream_path_to_json(BUFFER *wb, struct rrdhost *host, const char *k // append us. stream_path_to_json_object(wb, &tmp); } - - stream_path_clear(&tmp); } } buffer_json_array_close(wb); // key - spinlock_unlock(&host->rrdpush.path.spinlock); + rw_spinlock_read_unlock(&host->stream.path.spinlock); + + stream_path_cleanup(&tmp); } static BUFFER *stream_path_payload(RRDHOST *host) { @@ -173,13 +218,15 @@ static BUFFER *stream_path_payload(RRDHOST *host) { void stream_path_send_to_parent(RRDHOST *host) { struct sender_state *s = host->sender; - if(!s || !stream_has_capability(s, STREAM_CAP_PATHS)) return; + if(!s || + !stream_has_capability(s, STREAM_CAP_PATHS) || + !rrdhost_can_stream_metadata_to_parent(host)) + return; CLEAN_BUFFER *payload = stream_path_payload(host); - - BUFFER *wb = sender_start(s); - buffer_sprintf(wb, PLUGINSD_KEYWORD_JSON " " PLUGINSD_KEYWORD_STREAM_PATH "\n%s\n" PLUGINSD_KEYWORD_JSON_END "\n", buffer_tostring(payload)); - sender_commit(s, wb, STREAM_TRAFFIC_TYPE_METADATA); + CLEAN_BUFFER *wb = buffer_create(0, NULL); + buffer_sprintf(wb, PLUGINSD_KEYWORD_JSON " " PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH "\n%s\n" PLUGINSD_KEYWORD_JSON_END "\n", buffer_tostring(payload)); + sender_commit_clean_buffer(s, wb, STREAM_TRAFFIC_TYPE_METADATA); } void stream_path_send_to_child(RRDHOST *host) { @@ -188,14 +235,15 @@ void stream_path_send_to_child(RRDHOST *host) { CLEAN_BUFFER *payload = stream_path_payload(host); - spinlock_lock(&host->receiver_lock); - if(host->receiver && stream_has_capability(host->receiver, STREAM_CAP_PATHS)) { + rrdhost_receiver_lock(host); + if(stream_has_capability(host->receiver, STREAM_CAP_PATHS) && + !rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED)) { CLEAN_BUFFER *wb = buffer_create(0, NULL); - buffer_sprintf(wb, PLUGINSD_KEYWORD_JSON " " PLUGINSD_KEYWORD_STREAM_PATH "\n%s\n" PLUGINSD_KEYWORD_JSON_END "\n", buffer_tostring(payload)); - send_to_plugin(buffer_tostring(wb), __atomic_load_n(&host->receiver->parser, __ATOMIC_RELAXED)); + buffer_sprintf(wb, PLUGINSD_KEYWORD_JSON " " PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH "\n%s\n" PLUGINSD_KEYWORD_JSON_END "\n", buffer_tostring(payload)); + send_to_plugin(buffer_tostring(wb), __atomic_load_n(&host->receiver->thread.parser, __ATOMIC_RELAXED)); } - spinlock_unlock(&host->receiver_lock); + rrdhost_receiver_unlock(host); } void stream_path_child_disconnected(RRDHOST *host) { @@ -203,17 +251,17 @@ void stream_path_child_disconnected(RRDHOST *host) { } void stream_path_parent_disconnected(RRDHOST *host) { - spinlock_lock(&host->rrdpush.path.spinlock); + rw_spinlock_write_lock(&host->stream.path.spinlock); size_t cleared = 0; - size_t used = host->rrdpush.path.used; + size_t used = host->stream.path.used; for (size_t i = 0; i < used; i++) { - STREAM_PATH *p = &host->rrdpush.path.array[i]; + STREAM_PATH *p = &host->stream.path.array[i]; if(UUIDeq(localhost->host_id, p->host_id)) { - host->rrdpush.path.used = i + 1; + host->stream.path.used = i + 1; for(size_t j = i + 1; j < used ;j++) { - stream_path_clear(&host->rrdpush.path.array[j]); + stream_path_cleanup(&host->stream.path.array[j]); cleared++; } @@ -221,7 +269,7 @@ void stream_path_parent_disconnected(RRDHOST *host) { } } - spinlock_unlock(&host->rrdpush.path.spinlock); + rw_spinlock_write_unlock(&host->stream.path.spinlock); if(cleared) stream_path_send_to_child(host); @@ -243,6 +291,9 @@ void stream_path_node_id_updated(RRDHOST *host) { static bool parse_single_path(json_object *jobj, const char *path, STREAM_PATH *p, BUFFER *error) { + uint32_t version = 0; + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "version", version, error, false); + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "hostname", p->hostname, error, true); JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "host_id", p->host_id.uuid, error, true); JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "node_id", p->node_id.uuid, error, true); @@ -250,8 +301,8 @@ static bool parse_single_path(json_object *jobj, const char *path, STREAM_PATH * JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "hops", p->hops, error, true); JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "since", p->since, error, true); JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "first_time_t", p->first_time_t, error, true); - JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "start_time", p->start_time, error, true); - JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "shutdown_time", p->shutdown_time, error, true); + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "start_time", p->start_time_ms, error, true); + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "shutdown_time", p->shutdown_time_ms, error, true); JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "flags", STREAM_PATH_FLAGS_2id_one, p->flags, error, true); JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "capabilities", stream_capabilities_parse_one, p->capabilities, error, true); @@ -284,10 +335,10 @@ static bool parse_single_path(json_object *jobj, const char *path, STREAM_PATH * } static XXH128_hash_t stream_path_hash_unsafe(RRDHOST *host) { - if(!host->rrdpush.path.used) + if(!host->stream.path.used) return (XXH128_hash_t){ 0 }; - return XXH3_128bits(host->rrdpush.path.array, sizeof(*host->rrdpush.path.array) * host->rrdpush.path.used); + return XXH3_128bits(host->stream.path.array, sizeof(*host->stream.path.array) * host->stream.path.used); } static int compare_by_hops(const void *a, const void *b) { @@ -313,7 +364,7 @@ bool stream_path_set_from_json(RRDHOST *host, const char *json, bool from_parent return false; } - spinlock_lock(&host->rrdpush.path.spinlock); + rw_spinlock_write_lock(&host->stream.path.spinlock); XXH128_hash_t old_hash = stream_path_hash_unsafe(host); rrdhost_stream_path_clear_unsafe(host, true); @@ -323,8 +374,8 @@ bool stream_path_set_from_json(RRDHOST *host, const char *json, bool from_parent if (json_object_object_get_ex(jobj, STREAM_PATH_JSON_MEMBER, &_jarray) && json_object_is_type(_jarray, json_type_array)) { size_t items = json_object_array_length(_jarray); - host->rrdpush.path.array = callocz(items, sizeof(*host->rrdpush.path.array)); - host->rrdpush.path.size = items; + host->stream.path.array = callocz(items, sizeof(*host->stream.path.array)); + host->stream.path.size = items; for (size_t i = 0; i < items; ++i) { json_object *joption = json_object_array_get_idx(_jarray, i); @@ -334,24 +385,24 @@ bool stream_path_set_from_json(RRDHOST *host, const char *json, bool from_parent continue; } - if(!parse_single_path(joption, "", &host->rrdpush.path.array[host->rrdpush.path.used], error)) { - stream_path_clear(&host->rrdpush.path.array[host->rrdpush.path.used]); + if(!parse_single_path(joption, "", &host->stream.path.array[host->stream.path.used], error)) { + stream_path_cleanup(&host->stream.path.array[host->stream.path.used]); nd_log(NDLS_DAEMON, NDLP_ERR, "STREAM PATH: Array item No %zu cannot be parsed: %s: %s", i, buffer_tostring(error), json); } else - host->rrdpush.path.used++; + host->stream.path.used++; } } - if(host->rrdpush.path.used > 1) { + if(host->stream.path.used > 1) { // sorting is required in order to support stream_path_parent_disconnected() - qsort(host->rrdpush.path.array, host->rrdpush.path.used, - sizeof(*host->rrdpush.path.array), compare_by_hops); + qsort(host->stream.path.array, host->stream.path.used, + sizeof(*host->stream.path.array), compare_by_hops); } XXH128_hash_t new_hash = stream_path_hash_unsafe(host); - spinlock_unlock(&host->rrdpush.path.spinlock); + rw_spinlock_write_unlock(&host->stream.path.spinlock); if(!XXH128_isEqual(old_hash, new_hash)) { if(!from_parent) @@ -362,5 +413,9 @@ bool stream_path_set_from_json(RRDHOST *host, const char *json, bool from_parent stream_path_send_to_child(host); } - return host->rrdpush.path.used > 0; + return host->stream.path.used > 0; +} + +void rrdhost_stream_path_init(RRDHOST *host) { + rw_spinlock_init(&host->stream.path.spinlock); } diff --git a/src/streaming/stream-path.h b/src/streaming/stream-path.h index 398bafcaa8e8c4..852e016039de71 100644 --- a/src/streaming/stream-path.h +++ b/src/streaming/stream-path.h @@ -7,39 +7,19 @@ #define STREAM_PATH_JSON_MEMBER "streaming_path" -typedef enum __attribute__((packed)) { - STREAM_PATH_FLAG_NONE = 0, - STREAM_PATH_FLAG_ACLK = (1 << 0), - STREAM_PATH_FLAG_HEALTH = (1 << 1), - STREAM_PATH_FLAG_ML = (1 << 2), - STREAM_PATH_FLAG_EPHEMERAL = (1 << 3), - STREAM_PATH_FLAG_VIRTUAL = (1 << 4), -} STREAM_PATH_FLAGS; - -typedef struct stream_path { - STRING *hostname; // the hostname of the agent - ND_UUID host_id; // the machine guid of the agent - ND_UUID node_id; // the cloud node id of the agent - ND_UUID claim_id; // the cloud claim id of the agent - time_t since; // the timestamp of the last update - time_t first_time_t; // the oldest timestamp in the db - int16_t hops; // -1 = stale node, 0 = localhost, >0 the hops count - STREAM_PATH_FLAGS flags; // ACLK or NONE for the moment - STREAM_CAPABILITIES capabilities; // streaming connection capabilities - uint32_t start_time; // median time in ms the agent needs to start - uint32_t shutdown_time; // median time in ms the agent needs to shutdown -} STREAM_PATH; +typedef struct stream_path STREAM_PATH; typedef struct rrdhost_stream_path { - SPINLOCK spinlock; + RW_SPINLOCK spinlock; uint16_t size; uint16_t used; STREAM_PATH *array; } RRDHOST_STREAM_PATH; - struct rrdhost; +void rrdhost_stream_path_init(struct rrdhost *host); + void stream_path_send_to_parent(struct rrdhost *host); void stream_path_send_to_child(struct rrdhost *host); @@ -51,8 +31,11 @@ void stream_path_node_id_updated(struct rrdhost *host); void stream_path_child_disconnected(struct rrdhost *host); void stream_path_parent_disconnected(struct rrdhost *host); -STREAM_PATH rrdhost_stream_path_fetch(struct rrdhost *host); + +uint64_t rrdhost_stream_path_total_reboot_time_ms(struct rrdhost *host); bool stream_path_set_from_json(struct rrdhost *host, const char *json, bool from_parent); +bool rrdhost_is_host_in_stream_path_before_us(struct rrdhost *host, ND_UUID remote_agent_host_id, int16_t our_hops); + #endif //NETDATA_STREAM_PATH_H diff --git a/src/streaming/stream-receiver-api.c b/src/streaming/stream-receiver-api.c new file mode 100644 index 00000000000000..e78b10e09f7f07 --- /dev/null +++ b/src/streaming/stream-receiver-api.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream-receiver-internals.h" + +char *stream_receiver_program_version_strdupz(RRDHOST *host) { + rrdhost_receiver_lock(host); + char *host_version = strdupz( + host->receiver && host->receiver->program_version ? host->receiver->program_version : + rrdhost_program_version(host)); + rrdhost_receiver_unlock(host); + + return host_version; +} + +bool receiver_has_capability(RRDHOST *host, STREAM_CAPABILITIES caps) { + rrdhost_receiver_lock(host); + bool rc = stream_has_capability(host->receiver, caps); + rrdhost_receiver_unlock(host); + return rc; +} diff --git a/src/streaming/stream-receiver-connection.c b/src/streaming/stream-receiver-connection.c new file mode 100644 index 00000000000000..33af0ee3b13a38 --- /dev/null +++ b/src/streaming/stream-receiver-connection.c @@ -0,0 +1,633 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream.h" +#include "stream-thread.h" +#include "stream-receiver-internals.h" +#include "web/server/h2o/http_server.h" + +// -------------------------------------------------------------------------------------------------------------------- + +void stream_receiver_log_status(struct receiver_state *rpt, const char *msg, const char *status, ND_LOG_FIELD_PRIORITY priority) { + // this function may be called BEFORE we spawn the receiver thread + // so, we need to add the fields again (it does not harm) + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_TXT(NDF_SRC_IP, rpt->client_ip), + ND_LOG_FIELD_TXT(NDF_SRC_PORT, rpt->client_port), + ND_LOG_FIELD_TXT(NDF_NIDL_NODE, (rpt->hostname && *rpt->hostname) ? rpt->hostname : ""), + ND_LOG_FIELD_TXT(NDF_RESPONSE_CODE, status), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_from_child_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + nd_log(NDLS_ACCESS, priority, "api_key:'%s' machine_guid:'%s' msg:'%s'" + , (rpt->key && *rpt->key)? rpt->key : "" + , (rpt->machine_guid && *rpt->machine_guid) ? rpt->machine_guid : "" + , msg); + + nd_log(NDLS_DAEMON, priority, "STREAM_RECEIVER for '%s': %s %s%s%s" + , (rpt->hostname && *rpt->hostname) ? rpt->hostname : "" + , msg + , rpt->exit.reason != STREAM_HANDSHAKE_NEVER?" (":"" + , stream_handshake_error_to_string(rpt->exit.reason) + , rpt->exit.reason != STREAM_HANDSHAKE_NEVER?")":"" + ); +} + +// -------------------------------------------------------------------------------------------------------------------- + +void stream_receiver_free(struct receiver_state *rpt) { + nd_sock_close(&rpt->sock); + stream_decompressor_destroy(&rpt->thread.compressed.decompressor); + + if(rpt->system_info) + rrdhost_system_info_free(rpt->system_info); + + __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_receivers, sizeof(*rpt), __ATOMIC_RELAXED); + + freez(rpt->key); + freez(rpt->hostname); + freez(rpt->registry_hostname); + freez(rpt->machine_guid); + freez(rpt->os); + freez(rpt->timezone); + freez(rpt->abbrev_timezone); + freez(rpt->client_ip); + freez(rpt->client_port); + freez(rpt->program_name); + freez(rpt->program_version); + + string_freez(rpt->config.send.api_key); + string_freez(rpt->config.send.parents); + string_freez(rpt->config.send.charts_matching); + + freez(rpt); +} + +// -------------------------------------------------------------------------------------------------------------------- + +static int stream_receiver_response_permission_denied(struct web_client *w) { + // we always respond with the same message and error code + // to prevent an attacker from gaining info about the error + buffer_flush(w->response.data); + buffer_strcat(w->response.data, START_STREAMING_ERROR_NOT_PERMITTED); + return HTTP_RESP_UNAUTHORIZED; +} + +static int stream_receiver_response_too_busy_now(struct web_client *w) { + // we always respond with the same message and error code + // to prevent an attacker from gaining info about the error + buffer_flush(w->response.data); + buffer_strcat(w->response.data, START_STREAMING_ERROR_BUSY_TRY_LATER); + return HTTP_RESP_SERVICE_UNAVAILABLE; +} + +static void stream_receiver_takeover_web_connection(struct web_client *w, struct receiver_state *rpt) { + rpt->sock.fd = w->ifd; + rpt->sock.ssl = w->ssl; + + w->ssl = NETDATA_SSL_UNSET_CONNECTION; + + WEB_CLIENT_IS_DEAD(w); + + if(web_server_mode == WEB_SERVER_MODE_STATIC_THREADED) { + web_client_flag_set(w, WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET); + } + else { + if(w->ifd == w->ofd) + w->ifd = w->ofd = -1; + else + w->ifd = -1; + } + + buffer_flush(w->response.data); +} + +static void stream_send_error_on_taken_over_connection(struct receiver_state *rpt, const char *msg) { + nd_sock_send_timeout(&rpt->sock, (char *)msg, strlen(msg), 0, 5); +} + +static bool stream_receiver_send_first_response(struct receiver_state *rpt) { + // find the host for this receiver + { + // this will also update the host with our system_info + RRDHOST *host = rrdhost_find_or_create( + rpt->hostname, + rpt->registry_hostname, + rpt->machine_guid, + rpt->os, + rpt->timezone, + rpt->abbrev_timezone, + rpt->utc_offset, + rpt->program_name, + rpt->program_version, + rpt->config.update_every, + rpt->config.history, + rpt->config.mode, + rpt->config.health.enabled != CONFIG_BOOLEAN_NO, + rpt->config.send.enabled && rpt->config.send.parents && rpt->config.send.api_key, + rpt->config.send.parents, + rpt->config.send.api_key, + rpt->config.send.charts_matching, + rpt->config.replication.enabled, + rpt->config.replication.period, + rpt->config.replication.step, + rpt->system_info, + 0); + + if(!host) { + stream_receiver_log_status( + rpt, + "failed to find/create host structure, rejecting connection", + STREAM_STATUS_INTERNAL_SERVER_ERROR, + NDLP_ERR); + + stream_send_error_on_taken_over_connection(rpt, START_STREAMING_ERROR_INTERNAL_ERROR); + return false; + } + + if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))) { + stream_receiver_log_status( + rpt, "host is initializing, retry later", STREAM_STATUS_INITIALIZATION_IN_PROGRESS, NDLP_NOTICE); + + stream_send_error_on_taken_over_connection(rpt, START_STREAMING_ERROR_INITIALIZATION); + return false; + } + + // system_info has been consumed by the host structure + rpt->system_info = NULL; + + if(!rrdhost_set_receiver(host, rpt)) { + stream_receiver_log_status( + rpt, "host is already served by another receiver", STREAM_STATUS_DUPLICATE_RECEIVER, NDLP_INFO); + + stream_send_error_on_taken_over_connection(rpt, START_STREAMING_ERROR_ALREADY_STREAMING); + return false; + } + } + +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("STREAM '%s' [receive from [%s]:%s]: " + "client willing to stream metrics for host '%s' with machine_guid '%s': " + "update every = %d, history = %d, memory mode = %s, health %s,%s" + , rpt->hostname + , rpt->client_ip + , rpt->client_port + , rrdhost_hostname(rpt->host) + , rpt->host->machine_guid + , rpt->host->rrd_update_every + , rpt->host->rrd_history_entries + , rrd_memory_mode_name(rpt->host->rrd_memory_mode) + , (rpt->config.health.enabled == CONFIG_BOOLEAN_NO)?"disabled":((rpt->config.health.enabled == CONFIG_BOOLEAN_YES)?"enabled":"auto") + , (rpt->sock.ssl.conn != NULL) ? " SSL," : "" + ); +#endif // NETDATA_INTERNAL_CHECKS + + stream_select_receiver_compression_algorithm(rpt); + + { + // netdata_log_info("STREAM %s [receive from [%s]:%s]: initializing communication...", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port); + char initial_response[HTTP_HEADER_SIZE]; + if (stream_has_capability(rpt, STREAM_CAP_VCAPS)) { + log_receiver_capabilities(rpt); + sprintf(initial_response, "%s%u", START_STREAMING_PROMPT_VN, rpt->capabilities); + } + else if (stream_has_capability(rpt, STREAM_CAP_VN)) { + log_receiver_capabilities(rpt); + sprintf(initial_response, "%s%d", START_STREAMING_PROMPT_VN, stream_capabilities_to_vn(rpt->capabilities)); + } + else if (stream_has_capability(rpt, STREAM_CAP_V2)) { + log_receiver_capabilities(rpt); + sprintf(initial_response, "%s", START_STREAMING_PROMPT_V2); + } + else { // stream_has_capability(rpt, STREAM_CAP_V1) + log_receiver_capabilities(rpt); + sprintf(initial_response, "%s", START_STREAMING_PROMPT_V1); + } + + // OUR FIRST RESPONSE IS READY! + + // web server sockets are non-blocking - set them to blocking mode +#ifdef ENABLE_H2O + unless_h2o_rrdpush(rpt) +#endif + { + // remove the non-blocking flag from the socket + if(sock_delnonblock(rpt->sock.fd) < 0) + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM '%s' [receive from [%s]:%s]: cannot remove the non-blocking flag from socket %d", + rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port, rpt->sock.fd); + + struct timeval timeout; + timeout.tv_sec = 600; + timeout.tv_usec = 0; + if (unlikely(setsockopt(rpt->sock.fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout) != 0)) + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM '%s' [receive from [%s]:%s]: cannot set timeout for socket %d", + rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port, rpt->sock.fd); + } + + netdata_log_debug(D_STREAM, "Initial response to %s: %s", rpt->client_ip, initial_response); +#ifdef ENABLE_H2O + if (is_h2o_rrdpush(rpt)) { + h2o_stream_write(rpt->h2o_ctx, initial_response, strlen(initial_response)); + } else { +#endif + ssize_t bytes_sent = nd_sock_send_timeout(&rpt->sock, initial_response, strlen(initial_response), 0, 60); + + if(bytes_sent != (ssize_t)strlen(initial_response)) { + internal_error(true, "Cannot send response, got %zd bytes, expecting %zu bytes", bytes_sent, strlen(initial_response)); + stream_receiver_log_status( + rpt, "cannot reply back, dropping connection", STREAM_STATUS_CANT_REPLY, NDLP_ERR); + rrdhost_clear_receiver(rpt); + return false; + } +#ifdef ENABLE_H2O + } +#endif + } + + return true; +} + +int stream_receiver_accept_connection(struct web_client *w, char *decoded_query_string, void *h2o_ctx __maybe_unused) { + + if(!service_running(ABILITY_STREAMING_CONNECTIONS)) + return stream_receiver_response_too_busy_now(w); + + struct receiver_state *rpt = callocz(1, sizeof(*rpt)); + rpt->connected_since_s = now_realtime_sec(); + rpt->last_msg_t = now_monotonic_sec(); + rpt->hops = 1; + + rpt->capabilities = STREAM_CAP_INVALID; + +#ifdef ENABLE_H2O + rpt->h2o_ctx = h2o_ctx; +#endif + + __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_receivers, sizeof(*rpt), __ATOMIC_RELAXED); + __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED); + + rpt->system_info = callocz(1, sizeof(struct rrdhost_system_info)); + rpt->system_info->hops = rpt->hops; + + nd_sock_init(&rpt->sock, netdata_ssl_web_server_ctx, false); + rpt->client_ip = strdupz(w->client_ip); + rpt->client_port = strdupz(w->client_port); + + rpt->config.update_every = default_rrd_update_every; + + // parse the parameters and fill rpt and rpt->system_info + + while(decoded_query_string) { + char *value = strsep_skip_consecutive_separators(&decoded_query_string, "&"); + if(!value || !*value) continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + if(!strcmp(name, "key") && !rpt->key) + rpt->key = strdupz(value); + + else if(!strcmp(name, "hostname") && !rpt->hostname) + rpt->hostname = strdupz(value); + + else if(!strcmp(name, "registry_hostname") && !rpt->registry_hostname) + rpt->registry_hostname = strdupz(value); + + else if(!strcmp(name, "machine_guid") && !rpt->machine_guid) + rpt->machine_guid = strdupz(value); + + else if(!strcmp(name, "update_every")) + rpt->config.update_every = (int)strtoul(value, NULL, 0); + + else if(!strcmp(name, "os") && !rpt->os) + rpt->os = strdupz(value); + + else if(!strcmp(name, "timezone") && !rpt->timezone) + rpt->timezone = strdupz(value); + + else if(!strcmp(name, "abbrev_timezone") && !rpt->abbrev_timezone) + rpt->abbrev_timezone = strdupz(value); + + else if(!strcmp(name, "utc_offset")) + rpt->utc_offset = (int32_t)strtol(value, NULL, 0); + + else if(!strcmp(name, "hops")) + rpt->hops = rpt->system_info->hops = (int16_t)strtol(value, NULL, 0); + + else if(!strcmp(name, "ml_capable")) + rpt->system_info->ml_capable = strtoul(value, NULL, 0); + + else if(!strcmp(name, "ml_enabled")) + rpt->system_info->ml_enabled = strtoul(value, NULL, 0); + + else if(!strcmp(name, "mc_version")) + rpt->system_info->mc_version = strtoul(value, NULL, 0); + + else if(!strcmp(name, "ver") && (rpt->capabilities & STREAM_CAP_INVALID)) + rpt->capabilities = convert_stream_version_to_capabilities(strtoul(value, NULL, 0), NULL, false); + + else { + // An old Netdata child does not have a compatible streaming protocol, map to something sane. + if (!strcmp(name, "NETDATA_SYSTEM_OS_NAME")) + name = "NETDATA_HOST_OS_NAME"; + + else if (!strcmp(name, "NETDATA_SYSTEM_OS_ID")) + name = "NETDATA_HOST_OS_ID"; + + else if (!strcmp(name, "NETDATA_SYSTEM_OS_ID_LIKE")) + name = "NETDATA_HOST_OS_ID_LIKE"; + + else if (!strcmp(name, "NETDATA_SYSTEM_OS_VERSION")) + name = "NETDATA_HOST_OS_VERSION"; + + else if (!strcmp(name, "NETDATA_SYSTEM_OS_VERSION_ID")) + name = "NETDATA_HOST_OS_VERSION_ID"; + + else if (!strcmp(name, "NETDATA_SYSTEM_OS_DETECTION")) + name = "NETDATA_HOST_OS_DETECTION"; + + else if(!strcmp(name, "NETDATA_PROTOCOL_VERSION") && (rpt->capabilities & STREAM_CAP_INVALID)) + rpt->capabilities = convert_stream_version_to_capabilities(1, NULL, false); + + if (unlikely(rrdhost_set_system_info_variable(rpt->system_info, name, value))) { + nd_log_daemon(NDLP_NOTICE, "STREAM '%s' [receive from [%s]:%s]: " + "request has parameter '%s' = '%s', which is not used." + , (rpt->hostname && *rpt->hostname) ? rpt->hostname : "-" + , rpt->client_ip, rpt->client_port + , name, value); + } + } + } + + if (rpt->capabilities & STREAM_CAP_INVALID) + // no version is supplied, assume version 0; + rpt->capabilities = convert_stream_version_to_capabilities(0, NULL, false); + + // find the program name and version + if(w->user_agent && w->user_agent[0]) { + char *t = strchr(w->user_agent, '/'); + if(t && *t) { + *t = '\0'; + t++; + } + + rpt->program_name = strdupz(w->user_agent); + if(t && *t) rpt->program_version = strdupz(t); + } + + // check if we should accept this connection + + if(!rpt->key || !*rpt->key) { + stream_receiver_log_status( + rpt, "request without an API key, rejecting connection", STREAM_STATUS_NO_API_KEY, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + if(!rpt->hostname || !*rpt->hostname) { + stream_receiver_log_status( + rpt, "request without a hostname, rejecting connection", STREAM_STATUS_NO_HOSTNAME, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + if(!rpt->registry_hostname) + rpt->registry_hostname = strdupz(rpt->hostname); + + if(!rpt->machine_guid || !*rpt->machine_guid) { + stream_receiver_log_status( + rpt, "request without a machine GUID, rejecting connection", STREAM_STATUS_NO_MACHINE_GUID, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + { + char buf[GUID_LEN + 1]; + + if (regenerate_guid(rpt->key, buf) == -1) { + stream_receiver_log_status( + rpt, + "API key is not a valid UUID (use the command uuidgen to generate one)", + STREAM_STATUS_INVALID_API_KEY, + NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + if (regenerate_guid(rpt->machine_guid, buf) == -1) { + stream_receiver_log_status( + rpt, "machine GUID is not a valid UUID", STREAM_STATUS_INVALID_MACHINE_GUID, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + } + + if(!stream_conf_is_key_type(rpt->key, "api")) { + stream_receiver_log_status(rpt, "API key is a machine GUID", STREAM_STATUS_INVALID_API_KEY, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + // the default for api keys is false, so that users + // have to enable them manually + if(!stream_conf_api_key_is_enabled(rpt->key, false)) { + stream_receiver_log_status(rpt, "API key is not enabled", STREAM_STATUS_API_KEY_DISABLED, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + if(!stream_conf_api_key_allows_client(rpt->key, w->client_ip)) { + stream_receiver_log_status( + rpt, "API key is not allowed from this IP", STREAM_STATUS_NOT_ALLOWED_IP, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + if (!stream_conf_is_key_type(rpt->machine_guid, "machine")) { + stream_receiver_log_status( + rpt, "machine GUID is an API key", STREAM_STATUS_INVALID_MACHINE_GUID, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + // the default for machine guids is true, so that users do not + // have to enable them manually + if(!stream_conf_api_key_is_enabled(rpt->machine_guid, true)) { + stream_receiver_log_status( + rpt, "machine GUID is not enabled", STREAM_STATUS_MACHINE_GUID_DISABLED, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + if(!stream_conf_api_key_allows_client(rpt->machine_guid, w->client_ip)) { + stream_receiver_log_status( + rpt, "machine GUID is not allowed from this IP", STREAM_STATUS_NOT_ALLOWED_IP, NDLP_WARNING); + + stream_receiver_free(rpt); + return stream_receiver_response_permission_denied(w); + } + + if (strcmp(rpt->machine_guid, localhost->machine_guid) == 0) { + stream_receiver_takeover_web_connection(w, rpt); + + stream_receiver_log_status(rpt, "machine GUID is my own", STREAM_STATUS_LOCALHOST, NDLP_DEBUG); + + char initial_response[HTTP_HEADER_SIZE + 1]; + snprintfz(initial_response, HTTP_HEADER_SIZE, "%s", START_STREAMING_ERROR_SAME_LOCALHOST); + + if(nd_sock_send_timeout(&rpt->sock, initial_response, strlen(initial_response), 0, 60) != + (ssize_t)strlen(initial_response)) { + + nd_log_daemon(NDLP_ERR, "STREAM '%s' [receive from [%s]:%s]: " + "failed to reply." + , rpt->hostname + , rpt->client_ip, rpt->client_port + ); + } + + stream_receiver_free(rpt); + return HTTP_RESP_OK; + } + + if(unlikely(web_client_streaming_rate_t > 0)) { + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; + static time_t last_stream_accepted_t = 0; + + time_t now = now_realtime_sec(); + spinlock_lock(&spinlock); + + if(unlikely(last_stream_accepted_t == 0)) + last_stream_accepted_t = now; + + if(now - last_stream_accepted_t < web_client_streaming_rate_t) { + spinlock_unlock(&spinlock); + + char msg[100 + 1]; + snprintfz(msg, sizeof(msg) - 1, + "rate limit, will accept new connection in %ld secs", + (long)(web_client_streaming_rate_t - (now - last_stream_accepted_t))); + + stream_receiver_log_status(rpt, msg, STREAM_STATUS_RATE_LIMIT, NDLP_NOTICE); + + stream_receiver_free(rpt); + return stream_receiver_response_too_busy_now(w); + } + + last_stream_accepted_t = now; + spinlock_unlock(&spinlock); + } + + /* + * Quick path for rejecting multiple connections. The lock taken is fine-grained - it only protects the receiver + * pointer within the host (if a host exists). This protects against multiple concurrent web requests hitting + * separate threads within the web-server and landing here. The lock guards the thread-shutdown sequence that + * detaches the receiver from the host. If the host is being created (first time-access) then we also use the + * lock to prevent race-hazard (two threads try to create the host concurrently, one wins and the other does a + * lookup to the now-attached structure). + */ + + { + time_t age = 0; + bool receiver_stale = false; + bool receiver_working = false; + + rrd_rdlock(); + RRDHOST *host = rrdhost_find_by_guid(rpt->machine_guid); + if (unlikely(host && rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))) /* Ignore archived hosts. */ + host = NULL; + + if (host) { + rrdhost_receiver_lock(host); + if (host->receiver) { + age = now_monotonic_sec() - host->receiver->last_msg_t; + + if (age < 30) + receiver_working = true; + else + receiver_stale = true; + } + rrdhost_receiver_unlock(host); + } + rrd_rdunlock(); + + if (receiver_stale && + stream_receiver_signal_to_stop_and_wait(host, STREAM_HANDSHAKE_DISCONNECT_STALE_RECEIVER)) { + // we stopped the receiver + // we can proceed with this connection + receiver_stale = false; + + nd_log_daemon(NDLP_NOTICE, "STREAM '%s' [receive from [%s]:%s]: " + "stopped previous stale receiver to accept this one." + , rpt->hostname + , rpt->client_ip, rpt->client_port + ); + } + + if (receiver_working || receiver_stale) { + // another receiver is already connected + // try again later + + char msg[200 + 1]; + snprintfz(msg, sizeof(msg) - 1, + "multiple connections for same host, " + "old connection was last used %ld secs ago%s", + age, receiver_stale ? " (signaled old receiver to stop)" : " (new connection not accepted)"); + + stream_receiver_log_status(rpt, msg, STREAM_STATUS_ALREADY_CONNECTED, NDLP_DEBUG); + + // Have not set WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET - caller should clean up + buffer_flush(w->response.data); + buffer_strcat(w->response.data, START_STREAMING_ERROR_ALREADY_STREAMING); + stream_receiver_free(rpt); + return HTTP_RESP_CONFLICT; + } + } + + stream_receiver_takeover_web_connection(w, rpt); + + // after this point, our response code is irrelevant + // the socket is now ours... + + // read the configuration for this receiver + stream_conf_receiver_config(rpt, &rpt->config, rpt->key, rpt->machine_guid); + + if(stream_receiver_send_first_response(rpt)) { + // we are the receiver of the node + + stream_receiver_log_status(rpt, "connected and ready to receive data", STREAM_STATUS_CONNECTED, NDLP_INFO); + + // in case we have cloud connection we inform cloud a new child connected + schedule_node_state_update(rpt->host, 300); + rrdhost_set_is_parent_label(); + + if (rpt->config.ephemeral) + rrdhost_option_set(rpt->host, RRDHOST_OPTION_EPHEMERAL_HOST); + + // let it reconnect to parents asap + rrdhost_stream_parents_reset(rpt->host, STREAM_HANDSHAKE_PREPARING); + + // add it to a stream thread queue + stream_receiver_add_to_queue(rpt); + } + else { + // we are not the receiver of the node + // the child has been notified (or we couldn't send a message to it) + stream_receiver_free(rpt); + } + + return HTTP_RESP_OK; +} diff --git a/src/streaming/stream-receiver-internals.h b/src/streaming/stream-receiver-internals.h new file mode 100644 index 00000000000000..45ce322bbd330f --- /dev/null +++ b/src/streaming/stream-receiver-internals.h @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_STREAM_RECEIVER_INTERNALS_H +#define NETDATA_STREAM_RECEIVER_INTERNALS_H + +#include "stream.h" +#include "stream-thread.h" +#include "stream-conf.h" +#include "database/rrd.h" +#include "plugins.d/plugins_d.h" + +struct parser; + +struct receiver_state { + RRDHOST *host; + ND_SOCK sock; + int16_t hops; + int32_t utc_offset; + STREAM_CAPABILITIES capabilities; + char *key; + char *hostname; + char *registry_hostname; + char *machine_guid; + char *os; + char *timezone; // Unused? + char *abbrev_timezone; + char *client_ip; // Duplicated in pluginsd + char *client_port; // Duplicated in pluginsd + char *program_name; // Duplicated in pluginsd + char *program_version; + struct rrdhost_system_info *system_info; + time_t last_msg_t; + time_t connected_since_s; + + struct buffered_reader reader; + + struct { + // The parser pointer is safe to read and use, only when having the host receiver lock. + // Without this lock, the data pointed by the pointer may vanish randomly. + // Also, since the receiver sets it when it starts, it should be read with + // an atomic read. + struct parser *parser; + struct plugind cd; + BUFFER *buffer; + + struct { + bool enabled; + size_t start; + size_t used; + char buf[COMPRESSION_MAX_CHUNK * 2]; + struct decompressor_state decompressor; + } compressed; + + struct pollfd_meta meta; + } thread; + + struct { + bool shutdown; // signal the streaming parser to exit + STREAM_HANDSHAKE reason; + } exit; + + struct stream_receiver_config config; + + time_t replication_first_time_t; + +#ifdef ENABLE_H2O + void *h2o_ctx; +#endif +}; + +#ifdef ENABLE_H2O +#define is_h2o_rrdpush(x) ((x)->h2o_ctx != NULL) +#define unless_h2o_rrdpush(x) if(!is_h2o_rrdpush(x)) +#endif + +bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt); +void rrdhost_clear_receiver(struct receiver_state *rpt); +void stream_receiver_log_status(struct receiver_state *rpt, const char *msg, const char *status, ND_LOG_FIELD_PRIORITY priority); + +void stream_receiver_free(struct receiver_state *rpt); +bool stream_receiver_signal_to_stop_and_wait(RRDHOST *host, STREAM_HANDSHAKE reason); + + +#endif //NETDATA_STREAM_RECEIVER_INTERNALS_H diff --git a/src/streaming/stream-receiver.c b/src/streaming/stream-receiver.c new file mode 100644 index 00000000000000..6dfdd4873ab567 --- /dev/null +++ b/src/streaming/stream-receiver.c @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream.h" +#include "stream-thread.h" +#include "stream-receiver-internals.h" +#include "web/server/h2o/http_server.h" + +// When a child disconnects this is the maximum we will wait +// before we update the cloud that the child is offline +#define MAX_CHILD_DISC_DELAY (30000) +#define MAX_CHILD_DISC_TOLERANCE (125 / 100) + +static uint32_t streaming_connected_receivers = 0; + +bool plugin_is_enabled(struct plugind *cd); + +uint32_t stream_receivers_currently_connected(void) { + return __atomic_load_n(&streaming_connected_receivers, __ATOMIC_RELAXED); +} + +static void streaming_receiver_connected(void) { + __atomic_add_fetch(&streaming_connected_receivers, 1, __ATOMIC_RELAXED); +} + +static void streaming_receiver_disconnected(void) { + __atomic_sub_fetch(&streaming_connected_receivers, 1, __ATOMIC_RELAXED); +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline ssize_t read_stream(struct receiver_state *r, char* buffer, size_t size) { + if(unlikely(!size)) { + internal_error(true, "%s() asked to read zero bytes", __FUNCTION__); + return -2; + } + +#ifdef ENABLE_H2O + if (is_h2o_rrdpush(r)) { + if(nd_thread_signaled_to_cancel()) + return -3; + + return (ssize_t)h2o_stream_read(r->h2o_ctx, buffer, size); + } +#endif + + ssize_t bytes_read = nd_sock_read(&r->sock, buffer, size, 0); + if(bytes_read <= 0) { + if (bytes_read == 0) + netdata_log_error("STREAM: %s(): EOF while reading data from socket!", __FUNCTION__); + else { + netdata_log_error("STREAM: %s() failed to read from socket!", __FUNCTION__); + bytes_read = -1; + } + } + + return bytes_read; +} + +static inline STREAM_HANDSHAKE read_stream_error_to_reason(ssize_t code) { + if(code > 0) + return 0; + + switch(code) { + case 0: + // EOF + return STREAM_HANDSHAKE_DISCONNECT_SOCKET_EOF; + + case -1: + // failed to read + return STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED; + + case -2: + // asked to read zero bytes + return STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_RECEIVER_READ_BUFFER; + + case -3: + // the thread is cancelled + return STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN; + + default: + // anything else + return STREAM_HANDSHAKE_DISCONNECT_UNKNOWN_SOCKET_READ_ERROR; + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline ssize_t receiver_read_uncompressed(struct receiver_state *r) { + internal_fatal(r->reader.read_buffer[r->reader.read_len] != '\0', + "%s: read_buffer does not start with zero #2", __FUNCTION__ ); + + ssize_t bytes = read_stream(r, r->reader.read_buffer + r->reader.read_len, sizeof(r->reader.read_buffer) - r->reader.read_len - 1); + if(bytes > 0) { + worker_set_metric(WORKER_RECEIVER_JOB_BYTES_READ, (NETDATA_DOUBLE)bytes); + worker_set_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes); + + r->reader.read_len += bytes; + r->reader.read_buffer[r->reader.read_len] = '\0'; + } + + return bytes; +} + +typedef enum { + DECOMPRESS_NEED_MORE_DATA, + DECOMPRESS_FAILED, + DECOMPRESS_OK, +} decompressor_status_t; + +static inline void receiver_move_compressed(struct receiver_state *r) { + size_t remaining = r->thread.compressed.used - r->thread.compressed.start; + if(remaining > 0) { + memmove(r->thread.compressed.buf, r->thread.compressed.buf + r->thread.compressed.start, remaining); + r->thread.compressed.start = 0; + r->thread.compressed.used = remaining; + } + else { + r->thread.compressed.start = 0; + r->thread.compressed.used = 0; + } +} + +static inline decompressor_status_t receiver_feed_decompressor(struct receiver_state *r) { + char *buf = r->thread.compressed.buf; + size_t start = r->thread.compressed.start; + size_t signature_size = r->thread.compressed.decompressor.signature_size; + size_t used = r->thread.compressed.used; + + if(start + signature_size > used) { + // incomplete header, we need to wait for more data + receiver_move_compressed(r); + return DECOMPRESS_NEED_MORE_DATA; + } + + size_t compressed_message_size = + stream_decompressor_start(&r->thread.compressed.decompressor, buf + start, signature_size); + + if (unlikely(!compressed_message_size)) { + nd_log(NDLS_DAEMON, NDLP_ERR, "multiplexed uncompressed data in compressed stream!"); + return DECOMPRESS_FAILED; + } + + if(unlikely(compressed_message_size > COMPRESSION_MAX_MSG_SIZE)) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "received a compressed message of %zu bytes, which is bigger than the max compressed message " + "size supported of %zu. Ignoring message.", + compressed_message_size, (size_t)COMPRESSION_MAX_MSG_SIZE); + return DECOMPRESS_FAILED; + } + + if(start + signature_size + compressed_message_size > used) { + // incomplete compressed message, we need to wait for more data + receiver_move_compressed(r); + return DECOMPRESS_NEED_MORE_DATA; + } + + size_t bytes_to_parse = + stream_decompress(&r->thread.compressed.decompressor, buf + start + signature_size, compressed_message_size); + + if (unlikely(!bytes_to_parse)) { + nd_log(NDLS_DAEMON, NDLP_ERR, "no bytes to parse."); + return DECOMPRESS_FAILED; + } + + worker_set_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes_to_parse); + + // move the header to the next message + r->thread.compressed.start += signature_size + compressed_message_size; + + return DECOMPRESS_OK; +} + +static inline decompressor_status_t receiver_get_decompressed(struct receiver_state *r) { + if (unlikely(!stream_decompressed_bytes_in_buffer(&r->thread.compressed.decompressor))) + return DECOMPRESS_NEED_MORE_DATA; + + size_t available = sizeof(r->reader.read_buffer) - r->reader.read_len - 1; + if (likely(available)) { + size_t len = stream_decompressor_get( + &r->thread.compressed.decompressor, r->reader.read_buffer + r->reader.read_len, available); + if (unlikely(!len)) { + internal_error(true, "decompressor returned zero length #1"); + return DECOMPRESS_FAILED; + } + + r->reader.read_len += (int)len; + r->reader.read_buffer[r->reader.read_len] = '\0'; + } + else { + internal_fatal(true, "The line to read is too big! Already have %zd bytes in read_buffer.", r->reader.read_len); + return DECOMPRESS_FAILED; + } + + return DECOMPRESS_OK; +} + +static inline ssize_t receiver_read_compressed(struct receiver_state *r) { + + internal_fatal(r->reader.read_buffer[r->reader.read_len] != '\0', + "%s: read_buffer does not start with zero #2", __FUNCTION__ ); + + ssize_t bytes_read = read_stream(r, r->thread.compressed.buf + r->thread.compressed.used, + sizeof(r->thread.compressed.buf) - r->thread.compressed.used); + + if(bytes_read > 0) { + r->thread.compressed.used += bytes_read; + worker_set_metric(WORKER_RECEIVER_JOB_BYTES_READ, (NETDATA_DOUBLE)bytes_read); + } + + return bytes_read; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static void receiver_set_exit_reason(struct receiver_state *rpt, STREAM_HANDSHAKE reason, bool force) { + if(force || !rpt->exit.reason) + rpt->exit.reason = reason; +} + +static inline bool receiver_should_stop(struct receiver_state *rpt) { + if(unlikely(__atomic_load_n(&rpt->exit.shutdown, __ATOMIC_RELAXED))) { + receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN, false); + return true; + } + + return false; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static void streaming_parser_init(struct receiver_state *rpt) { + rpt->thread.cd = (struct plugind){ + .update_every = default_rrd_update_every, + .unsafe = { + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + .running = true, + .enabled = true, + }, + .started_t = now_realtime_sec(), + }; + + // put the client IP and port into the buffers used by plugins.d + snprintfz(rpt->thread.cd.id, CONFIG_MAX_NAME, "%s:%s", rpt->client_ip, rpt->client_port); + snprintfz(rpt->thread.cd.filename, FILENAME_MAX, "%s:%s", rpt->client_ip, rpt->client_port); + snprintfz(rpt->thread.cd.fullfilename, FILENAME_MAX, "%s:%s", rpt->client_ip, rpt->client_port); + snprintfz(rpt->thread.cd.cmd, PLUGINSD_CMD_MAX, "%s:%s", rpt->client_ip, rpt->client_port); + + PARSER *parser = NULL; + { + PARSER_USER_OBJECT user = { + .enabled = plugin_is_enabled(&rpt->thread.cd), + .host = rpt->host, + .opaque = rpt, + .cd = &rpt->thread.cd, + .trust_durations = 1, + .capabilities = rpt->capabilities, + }; + + parser = parser_init(&user, -1, -1, PARSER_INPUT_SPLIT, &rpt->sock); + } + +#ifdef ENABLE_H2O + parser->h2o_ctx = rpt->h2o_ctx; +#endif + + pluginsd_keywords_init(parser, PARSER_INIT_STREAMING); + + rrd_collector_started(); + + rpt->thread.compressed.start = 0; + rpt->thread.compressed.used = 0; + rpt->thread.compressed.enabled = stream_decompression_initialize(rpt); + buffered_reader_init(&rpt->reader); + +#ifdef NETDATA_LOG_STREAM_RECEIVE + { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "/tmp/stream-receiver-%s.txt", rpt->host ? rrdhost_hostname( + rpt->host) : "unknown" + ); + parser->user.stream_log_fp = fopen(filename, "w"); + parser->user.stream_log_repertoire = PARSER_REP_METADATA; + } +#endif + + __atomic_store_n(&rpt->thread.parser, parser, __ATOMIC_RELAXED); + stream_receiver_send_node_and_claim_id_to_child(rpt->host); + + rpt->thread.buffer = buffer_create(sizeof(rpt->reader.read_buffer), NULL); + + // help rrdset_push_metric_initialize() select the right buffer + rpt->host->stream.snd.commit.receiver_tid = gettid_cached(); +} + +// -------------------------------------------------------------------------------------------------------------------- + +static bool stream_receiver_log_capabilities(BUFFER *wb, void *ptr) { + struct receiver_state *rpt = ptr; + if(!rpt) + return false; + + stream_capabilities_to_string(wb, rpt->capabilities); + return true; +} + +static bool stream_receiver_log_transport(BUFFER *wb, void *ptr) { + struct receiver_state *rpt = ptr; + if(!rpt) + return false; + + buffer_strcat(wb, nd_sock_is_ssl(&rpt->sock) ? "https" : "http"); + return true; +} + +// -------------------------------------------------------------------------------------------------------------------- + +void stream_receiver_move_queue_to_running_unsafe(struct stream_thread *sth) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + // process the queue + Word_t idx = 0; + for(struct receiver_state *rpt = RECEIVERS_FIRST(&sth->queue.receivers, &idx); + rpt; + rpt = RECEIVERS_NEXT(&sth->queue.receivers, &idx)) { + worker_is_busy(WORKER_STREAM_JOB_DEQUEUE); + + RECEIVERS_DEL(&sth->queue.receivers, (Word_t)rpt); + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, rpt->host->hostname), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM RECEIVE[%zu] [%s]: moving host from receiver queue to receiver running...", + sth->id, rrdhost_hostname(rpt->host)); + + internal_fatal(RECEIVERS_GET(&sth->rcv.receivers, (Word_t)rpt) != NULL, "Receiver to be added is already in the list of receivers"); + RECEIVERS_SET(&sth->rcv.receivers, (Word_t)rpt, rpt); + + streaming_parser_init(rpt); + + rpt->host->stream.rcv.status.tid = gettid_cached(); + rpt->thread.meta.type = POLLFD_TYPE_RECEIVER; + rpt->thread.meta.rpt = rpt; + if(!nd_poll_add(sth->run.ndpl, rpt->sock.fd, ND_POLL_READ, &rpt->thread.meta)) + internal_fatal(true, "Failed to add receiver socket to nd_poll()"); + } +} + +static void stream_receiver_on_disconnect(struct stream_thread *sth __maybe_unused, struct receiver_state *rpt) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + if(!rpt) return; + + buffer_free(rpt->thread.buffer); + rpt->thread.buffer = NULL; + + size_t count = 0; + PARSER *parser = __atomic_load_n(&rpt->thread.parser, __ATOMIC_RELAXED); + if(parser) { + parser->user.v2.stream_buffer.wb = NULL; + + // make sure send_to_plugin() will not write any data to the socket + spinlock_lock(&parser->writer.spinlock); + parser->fd_input = -1; + parser->fd_output = -1; + parser->sock = NULL; + spinlock_unlock(&parser->writer.spinlock); + + count = parser->user.data_collections_count; + } + + // the parser stopped + receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_EXIT, false); + + { + char msg[100 + 1]; + snprintfz(msg, sizeof(msg) - 1, "disconnected (completed %zu updates)", count); + stream_receiver_log_status(rpt, msg, STREAM_STATUS_DISCONNECTED, NDLP_WARNING); + } + + // in case we have cloud connection we inform cloud + // a child disconnected + uint64_t total_reboot = rrdhost_stream_path_total_reboot_time_ms(rpt->host); + schedule_node_state_update(rpt->host, MIN((total_reboot * MAX_CHILD_DISC_TOLERANCE), MAX_CHILD_DISC_DELAY)); + + rrdhost_clear_receiver(rpt); + rrdhost_set_is_parent_label(); + stream_receiver_free(rpt); +} + +static void stream_receiver_remove(struct stream_thread *sth, struct receiver_state *rpt, const char *why) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM RECEIVE[%zu] '%s' [from [%s]:%s]: " + "receiver disconnected: %s" + , sth->id + , rpt->hostname ? rpt->hostname : "-" + , rpt->client_ip ? rpt->client_ip : "-" + , rpt->client_port ? rpt->client_port : "-" + , why ? why : ""); + + internal_fatal(RECEIVERS_GET(&sth->rcv.receivers, (Word_t)rpt) == NULL, "Receiver to be removed is not found in the list of receivers"); + RECEIVERS_DEL(&sth->rcv.receivers, (Word_t)rpt); + if(!nd_poll_del(sth->run.ndpl, rpt->sock.fd)) + internal_fatal(true, "Failed to remove receiver socket from nd_poll()"); + + rpt->host->stream.rcv.status.tid = 0; + + stream_thread_node_removed(rpt->host); + + stream_receiver_on_disconnect(sth, rpt); + // DO NOT USE rpt after this point +} + +// process poll() events for streaming receivers +void stream_receive_process_poll_events(struct stream_thread *sth, struct receiver_state *rpt, nd_poll_event_t events __maybe_unused, time_t now_s) { + PARSER *parser = __atomic_load_n(&rpt->thread.parser, __ATOMIC_RELAXED); + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_TXT(NDF_SRC_IP, rpt->client_ip), + ND_LOG_FIELD_TXT(NDF_SRC_PORT, rpt->client_port), + ND_LOG_FIELD_TXT(NDF_NIDL_NODE, rpt->hostname), + ND_LOG_FIELD_CB(NDF_SRC_TRANSPORT, stream_receiver_log_transport, rpt), + ND_LOG_FIELD_CB(NDF_SRC_CAPABILITIES, stream_receiver_log_capabilities, rpt), + ND_LOG_FIELD_CB(NDF_REQUEST, line_splitter_reconstruct_line, &parser->line), + ND_LOG_FIELD_CB(NDF_NIDL_NODE, parser_reconstruct_node, parser), + ND_LOG_FIELD_CB(NDF_NIDL_INSTANCE, parser_reconstruct_instance, parser), + ND_LOG_FIELD_CB(NDF_NIDL_CONTEXT, parser_reconstruct_context, parser), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + if(receiver_should_stop(rpt)) { + receiver_set_exit_reason(rpt, rpt->exit.reason, false); + stream_receiver_remove(sth, rpt, "received stop signal"); + return; + } + + rpt->last_msg_t = now_s; + + if(rpt->thread.compressed.enabled) { + worker_is_busy(WORKER_STREAM_JOB_SOCKET_RECEIVE); + + ssize_t bytes = receiver_read_compressed(rpt); + if(unlikely(bytes <= 0)) { + if(bytes < 0 && (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR)) + return; + + worker_is_busy(WORKER_STREAM_JOB_SOCKET_ERROR); + receiver_set_exit_reason(rpt, read_stream_error_to_reason(bytes), false); + stream_receiver_remove(sth, rpt, "socket read error"); + return; + } + + bool node_removed = false; + while(!node_removed && !nd_thread_signaled_to_cancel() && service_running(SERVICE_STREAMING) && !receiver_should_stop(rpt)) { + worker_is_busy(WORKER_STREAM_JOB_DECOMPRESS); + + // feed the decompressor with the new data we just read + decompressor_status_t feed = receiver_feed_decompressor(rpt); + + if(likely(feed == DECOMPRESS_OK)) { + while (!node_removed) { + // feed our uncompressed data buffer with new data + decompressor_status_t rc = receiver_get_decompressed(rpt); + + if (likely(rc == DECOMPRESS_OK)) { + // loop through all the complete lines found in the uncompressed buffer + + while (buffered_reader_next_line(&rpt->reader, rpt->thread.buffer)) { + if (unlikely(parser_action(parser, rpt->thread.buffer->buffer))) { + receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, false); + stream_receiver_remove(sth, rpt, "parser failed"); + node_removed = true; + break; + } + + rpt->thread.buffer->len = 0; + rpt->thread.buffer->buffer[0] = '\0'; + } + } + else if (rc == DECOMPRESS_NEED_MORE_DATA) + break; + + else { + receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, false); + stream_receiver_remove(sth, rpt, "decompressor failed"); + node_removed = true; + break; + } + } + } + else if (feed == DECOMPRESS_NEED_MORE_DATA) + break; + else { + receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, false); + stream_receiver_remove(sth, rpt, "compressed data invalid"); + node_removed = true; + break; + } + } + + if(!node_removed && receiver_should_stop(rpt)) { + receiver_set_exit_reason(rpt, rpt->exit.reason, false); + stream_receiver_remove(sth, rpt, "received stop signal"); + return; + } + } + else { + worker_is_busy(WORKER_STREAM_JOB_SOCKET_RECEIVE); + + ssize_t bytes = receiver_read_uncompressed(rpt); + if(unlikely(bytes <= 0)) { + if(bytes < 0 && (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR)) + return; + + worker_is_busy(WORKER_STREAM_JOB_SOCKET_ERROR); + receiver_set_exit_reason(rpt, read_stream_error_to_reason(bytes), false); + stream_receiver_remove(sth, rpt, "socker read error"); + return; + } + + while(buffered_reader_next_line(&rpt->reader, rpt->thread.buffer)) { + if(unlikely(parser_action(parser, rpt->thread.buffer->buffer))) { + receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_FAILED, false); + stream_receiver_remove(sth, rpt, "parser failed"); + break; + } + + rpt->thread.buffer->len = 0; + rpt->thread.buffer->buffer[0] = '\0'; + } + } +} + +void stream_receiver_cleanup(struct stream_thread *sth) { + Word_t idx = 0; + for(struct receiver_state *rpt = RECEIVERS_FIRST(&sth->rcv.receivers, &idx); + rpt; + rpt = RECEIVERS_NEXT(&sth->rcv.receivers, &idx)) + stream_receiver_remove(sth, rpt, "shutdown"); + + RECEIVERS_FREE(&sth->rcv.receivers, NULL); +} + +static void stream_receiver_replication_reset(RRDHOST *host) { + RRDSET *st; + rrdset_foreach_read(st, host) { + rrdset_flag_clear(st, RRDSET_FLAG_RECEIVER_REPLICATION_IN_PROGRESS); + rrdset_flag_set(st, RRDSET_FLAG_RECEIVER_REPLICATION_FINISHED); + } + rrdset_foreach_done(st); + rrdhost_receiver_replicating_charts_zero(host); +} + +bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) { + bool signal_rrdcontext = false; + bool set_this = false; + + rrdhost_receiver_lock(host); + + if (!host->receiver) { + rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN); + + host->stream.rcv.status.connections++; + streaming_receiver_connected(); + + host->receiver = rpt; + rpt->host = host; + + __atomic_store_n(&rpt->exit.shutdown, false, __ATOMIC_RELAXED); + host->stream.rcv.status.last_connected = now_realtime_sec(); + host->stream.rcv.status.last_disconnected = 0; + host->stream.rcv.status.last_chart = 0; + host->stream.rcv.status.check_obsolete = true; + + if (rpt->config.health.enabled != CONFIG_BOOLEAN_NO) { + if (rpt->config.health.delay > 0) { + host->health.delay_up_to = now_realtime_sec() + rpt->config.health.delay; + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "[%s]: Postponing health checks for %" PRId64 " seconds, because it was just connected.", + rrdhost_hostname(host), + (int64_t) rpt->config.health.delay); + } + } + + host->health_log.health_log_retention_s = rpt->config.health.history; + +// this is a test +// if(rpt->hops <= host->sender->hops) +// stream_sender_thread_stop(host, "HOPS MISMATCH", false); + + signal_rrdcontext = true; + stream_receiver_replication_reset(host); + + rrdhost_flag_clear(rpt->host, RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED); + aclk_queue_node_info(rpt->host, true); + + rrdhost_stream_parents_reset(host, STREAM_HANDSHAKE_PREPARING); + + set_this = true; + } + + rrdhost_receiver_unlock(host); + + if(signal_rrdcontext) + rrdcontext_host_child_connected(host); + + return set_this; +} + +void rrdhost_clear_receiver(struct receiver_state *rpt) { + RRDHOST *host = rpt->host; + if(!host) return; + + rrdhost_receiver_lock(host); + { + // Make sure that we detach this thread and don't kill a freshly arriving receiver + + if (host->receiver == rpt) { + rrdhost_flag_set(host, RRDHOST_FLAG_STREAM_RECEIVER_DISCONNECTED); + rrdhost_receiver_unlock(host); + { + // run all these without having the receiver lock + + stream_path_child_disconnected(host); + stream_sender_signal_to_stop_and_wait(host, STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT, false); + stream_receiver_replication_reset(host); + rrdcontext_host_child_disconnected(host); + + if (rpt->config.health.enabled) + rrdcalc_child_disconnected(host); + + rrdhost_stream_parents_reset(host, STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT); + } + rrdhost_receiver_lock(host); + + // now we have the lock again + + streaming_receiver_disconnected(); + + __atomic_store_n(&host->receiver->exit.shutdown, false, __ATOMIC_RELAXED); + host->stream.rcv.status.check_obsolete = false; + host->stream.rcv.status.last_connected = 0; + host->stream.rcv.status.last_disconnected = now_realtime_sec(); + host->health.enabled = false; + + host->stream.rcv.status.exit_reason = rpt->exit.reason; + rrdhost_flag_set(host, RRDHOST_FLAG_ORPHAN); + host->receiver = NULL; + } + } + + // this must be cleared with the receiver lock + pluginsd_process_cleanup(rpt->thread.parser); + __atomic_store_n(&rpt->thread.parser, NULL, __ATOMIC_RELAXED); + + rrdhost_receiver_unlock(host); +} + +bool stream_receiver_signal_to_stop_and_wait(RRDHOST *host, STREAM_HANDSHAKE reason) { + bool ret = false; + + rrdhost_receiver_lock(host); + + if(host->receiver) { + if(!__atomic_load_n(&host->receiver->exit.shutdown, __ATOMIC_RELAXED)) { + __atomic_store_n(&host->receiver->exit.shutdown, true, __ATOMIC_RELAXED); + receiver_set_exit_reason(host->receiver, reason, true); + shutdown(host->receiver->sock.fd, SHUT_RDWR); + } + } + + int count = 2000; + while (host->receiver && count-- > 0) { + rrdhost_receiver_unlock(host); + + // let the lock for the receiver thread to exit + sleep_usec(1 * USEC_PER_MS); + + rrdhost_receiver_lock(host); + } + + if(host->receiver) + netdata_log_error("STREAM RECEIVE[x] '%s' [from [%s]:%s]: " + "streaming thread takes too long to stop, giving up..." + , rrdhost_hostname(host) + , host->receiver->client_ip, host->receiver->client_port); + else + ret = true; + + rrdhost_receiver_unlock(host); + + return ret; +} diff --git a/src/streaming/stream-sender-api.c b/src/streaming/stream-sender-api.c new file mode 100644 index 00000000000000..6bcf791340efa0 --- /dev/null +++ b/src/streaming/stream-sender-api.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream-sender-internals.h" + +bool stream_sender_has_capabilities(struct rrdhost *host, STREAM_CAPABILITIES capabilities) { + return host && stream_has_capability(host->sender, capabilities); +} + +bool stream_sender_is_connected_with_ssl(struct rrdhost *host) { + return host && rrdhost_can_stream_metadata_to_parent(host) && nd_sock_is_ssl(&host->sender->sock); +} + +bool stream_sender_has_compression(struct rrdhost *host) { + return host && host->sender && host->sender->compressor.initialized; +} + +void stream_sender_structures_init(RRDHOST *host, bool stream, STRING *parents, STRING *api_key, STRING *send_charts_matching) { + if(rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_INITIALIZED)) + return; + + if(!stream || !parents || !api_key) { + rrdhost_option_clear(host, RRDHOST_OPTION_SENDER_ENABLED); + return; + } + + rrdhost_flag_set(host, RRDHOST_FLAG_STREAM_SENDER_INITIALIZED); + + if (host->sender) return; + + host->sender = callocz(1, sizeof(*host->sender)); + __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(*host->sender), __ATOMIC_RELAXED); + + host->sender->connector.id = -1; + host->sender->host = host; + host->sender->sbuf.cb = cbuffer_new(CBUFFER_INITIAL_SIZE, CBUFFER_INITIAL_MAX_SIZE, &netdata_buffers_statistics.cbuffers_streaming); + host->sender->capabilities = stream_our_capabilities(host, true); + + nd_sock_init(&host->sender->sock, netdata_ssl_streaming_sender_ctx, netdata_ssl_validate_certificate_sender); + host->sender->disabled_capabilities = STREAM_CAP_NONE; + + if(!stream_send.compression.enabled) + host->sender->disabled_capabilities |= STREAM_CAP_COMPRESSIONS_AVAILABLE; + + spinlock_init(&host->sender->spinlock); + replication_sender_init(host->sender); + + host->stream.snd.destination = string_dup(parents); + rrdhost_stream_parents_update_from_destination(host); + + host->stream.snd.api_key = string_dup(api_key); + host->stream.snd.charts_matching = simple_pattern_create( + string2str(send_charts_matching), NULL, SIMPLE_PATTERN_EXACT, true); + + rrdhost_option_set(host, RRDHOST_OPTION_SENDER_ENABLED); +} + +void stream_sender_structures_free(struct rrdhost *host) { + rrdhost_option_clear(host, RRDHOST_OPTION_SENDER_ENABLED); + + if (unlikely(!host->sender)) return; + + // stop a possibly running thread + stream_sender_signal_to_stop_and_wait(host, STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP, true); + cbuffer_free(host->sender->sbuf.cb); + + stream_compressor_destroy(&host->sender->compressor); + + replication_cleanup_sender(host->sender); + + __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(*host->sender), __ATOMIC_RELAXED); + + freez(host->sender); + host->sender = NULL; + + sender_buffer_destroy(&host->stream.snd.commit); + + rrdhost_flag_clear(host, RRDHOST_FLAG_STREAM_SENDER_INITIALIZED); +} + +void stream_sender_start_host(struct rrdhost *host) { + internal_fatal(!rrdhost_has_stream_sender_enabled(host), + "Host '%s' does not have streaming enabled, but %s() was called", + rrdhost_hostname(host), __FUNCTION__); + + stream_sender_add_to_connector_queue(host); +} + +void *stream_sender_start_localhost(void *ptr __maybe_unused) { + if(!localhost) return NULL; + stream_sender_start_host(localhost); + return NULL; +} + +// Either the receiver lost the connection or the host is being destroyed. +// The sender mutex guards thread creation, any spurious data is wiped on reconnection. +void stream_sender_signal_to_stop_and_wait(struct rrdhost *host, STREAM_HANDSHAKE reason, bool wait) { + if (!host->sender) + return; + + stream_sender_lock(host->sender); + + if(rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_ADDED)) { + __atomic_store_n(&host->sender->exit.shutdown, true, __ATOMIC_RELAXED); + host->sender->exit.reason = reason; + } + + struct stream_opcode msg = host->sender->thread.msg; + stream_sender_unlock(host->sender); + + if(reason == STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT) + msg.opcode = STREAM_OPCODE_SENDER_STOP_RECEIVER_LEFT; + else + msg.opcode = STREAM_OPCODE_SENDER_STOP_HOST_CLEANUP; + stream_sender_send_msg_to_dispatcher(host->sender, msg); + + while(wait && rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_ADDED)) + sleep_usec(10 * USEC_PER_MS); +} diff --git a/src/streaming/stream-sender-commit.c b/src/streaming/stream-sender-commit.c new file mode 100644 index 00000000000000..24405c6959f5d3 --- /dev/null +++ b/src/streaming/stream-sender-commit.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream-thread.h" + +#define SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE 3 + +static __thread struct sender_buffer commit___thread = { 0 }; + +void sender_buffer_destroy(struct sender_buffer *commit) { + buffer_free(commit->wb); + commit->wb = NULL; + commit->used = false; + commit->our_recreates = 0; + commit->sender_recreates = 0; + commit->last_function = NULL; +} + +void sender_commit_thread_buffer_free(void) { + sender_buffer_destroy(&commit___thread); +} + +// Collector thread starting a transmission +BUFFER *sender_commit_start_with_trace(struct sender_state *s __maybe_unused, struct sender_buffer *commit, const char *func) { + if(unlikely(commit->used)) + fatal("STREAMING: thread buffer is used multiple times concurrently (%u). " + "It is already being used by '%s()', and now is called by '%s()'", + (unsigned)commit->used, + commit->last_function ? commit->last_function : "(null)", + func ? func : "(null)"); + + if(unlikely(commit->receiver_tid && commit->receiver_tid != gettid_cached())) + fatal("STREAMING: thread buffer is reserved for tid %d, but it used by thread %d function '%s()'.", + commit->receiver_tid, gettid_cached(), func ? func : "(null)"); + + if(unlikely(commit->wb && + commit->wb->size > THREAD_BUFFER_INITIAL_SIZE && + commit->our_recreates != commit->sender_recreates)) { + buffer_free(commit->wb); + commit->wb = NULL; + } + + if(unlikely(!commit->wb)) { + commit->wb = buffer_create(THREAD_BUFFER_INITIAL_SIZE, &netdata_buffers_statistics.buffers_streaming); + commit->our_recreates = commit->sender_recreates; + } + + commit->used = true; + buffer_flush(commit->wb); + return commit->wb; +} + +BUFFER *sender_thread_buffer_with_trace(struct sender_state *s __maybe_unused, const char *func) { + return sender_commit_start_with_trace(s, &commit___thread, func); +} + +BUFFER *sender_host_buffer_with_trace(struct rrdhost *host, const char *func) { + return sender_commit_start_with_trace(host->sender, &host->stream.snd.commit, func); +} + +// Collector thread finishing a transmission +void sender_buffer_commit(struct sender_state *s, BUFFER *wb, struct sender_buffer *commit, STREAM_TRAFFIC_TYPE type) { + struct stream_opcode msg; + + char *src = (char *)buffer_tostring(wb); + size_t src_len = buffer_strlen(wb); + + if (unlikely(!src || !src_len)) + return; + + size_t total_uncompressed_len = src_len; + size_t total_compressed_len = 0; + + stream_sender_lock(s); + + // copy the sequence number of sender buffer recreates, while having our lock + if(commit) + commit->sender_recreates = s->sbuf.recreates; + + if (!s->thread.msg.session) { + // the dispatcher is not there anymore - ignore these data + stream_sender_unlock(s); + if(commit) + sender_buffer_destroy(commit); + return; + } + + if (unlikely(s->sbuf.cb->max_size < (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE)) { + // adaptive sizing of the circular buffer is needed to get this. + + nd_log( + NDLS_DAEMON, + NDLP_NOTICE, + "STREAM %s [send to %s]: max buffer size of %zu is too small " + "for a data message of size %zu. Increasing the max buffer size " + "to %d times the max data message size.", + rrdhost_hostname(s->host), + s->connected_to, + s->sbuf.cb->max_size, + buffer_strlen(wb) + 1, + SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE); + + s->sbuf.cb->max_size = (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE; + } + +#ifdef NETDATA_LOG_STREAM_SENDER + if (type == STREAM_TRAFFIC_TYPE_METADATA) { + if (!s->stream_log_fp) { + char filename[FILENAME_MAX + 1]; + snprintfz( + filename, FILENAME_MAX, "/tmp/stream-sender-%s.txt", s->host ? rrdhost_hostname(s->host) : "unknown"); + + s->stream_log_fp = fopen(filename, "w"); + } + + fprintf( + s->stream_log_fp, + "\n--- SEND MESSAGE START: %s ----\n" + "%s" + "--- SEND MESSAGE END ----------------------------------------\n", + rrdhost_hostname(s->host), + src); + } +#endif + + if (s->compressor.initialized) { + // compressed traffic + if(rrdhost_is_this_a_stream_thread(s->host)) + worker_is_busy(WORKER_STREAM_JOB_COMPRESS); + + while (src_len) { + size_t size_to_compress = src_len; + + if (unlikely(size_to_compress > COMPRESSION_MAX_MSG_SIZE)) { + if (stream_has_capability(s, STREAM_CAP_BINARY)) + size_to_compress = COMPRESSION_MAX_MSG_SIZE; + else { + if (size_to_compress > COMPRESSION_MAX_MSG_SIZE) { + // we need to find the last newline + // so that the decompressor will have a whole line to work with + + const char *t = &src[COMPRESSION_MAX_MSG_SIZE]; + while (--t >= src) + if (unlikely(*t == '\n')) + break; + + if (t <= src) + size_to_compress = COMPRESSION_MAX_MSG_SIZE; + else + size_to_compress = t - src + 1; + } + } + } + + const char *dst; + size_t dst_len = stream_compress(&s->compressor, src, size_to_compress, &dst); + if (!dst_len) { + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM %s [send to %s]: COMPRESSION failed. Resetting compressor and re-trying", + rrdhost_hostname(s->host), s->connected_to); + + stream_compression_initialize(s); + dst_len = stream_compress(&s->compressor, src, size_to_compress, &dst); + if (!dst_len) + goto compression_failed_with_lock; + } + + stream_compression_signature_t signature = stream_compress_encode_signature(dst_len); + +#ifdef NETDATA_INTERNAL_CHECKS + // check if reversing the signature provides the same length + size_t decoded_dst_len = stream_decompress_decode_signature((const char *)&signature, sizeof(signature)); + if (decoded_dst_len != dst_len) + fatal( + "RRDPUSH COMPRESSION: invalid signature, original payload %zu bytes, " + "compressed payload length %zu bytes, but signature says payload is %zu bytes", + size_to_compress, dst_len, decoded_dst_len); +#endif + + total_compressed_len += dst_len + sizeof(signature); + + if (cbuffer_add_unsafe(s->sbuf.cb, (const char *)&signature, sizeof(signature)) || + cbuffer_add_unsafe(s->sbuf.cb, dst, dst_len)) + goto overflow_with_lock; + + src = src + size_to_compress; + src_len -= size_to_compress; + } + } + else { + // uncompressed traffic + + total_compressed_len = src_len; + + if (cbuffer_add_unsafe(s->sbuf.cb, src, src_len)) + goto overflow_with_lock; + } + + // update s->dispatcher entries + bool enable_sending = s->thread.bytes_outstanding == 0; + stream_sender_thread_data_added_data_unsafe(s, type, total_compressed_len, total_uncompressed_len); + + if (enable_sending) + msg = s->thread.msg; + + stream_sender_unlock(s); + + if (enable_sending) { + msg.opcode = STREAM_OPCODE_SENDER_POLLOUT; + stream_sender_send_msg_to_dispatcher(s, msg); + } + + return; + +overflow_with_lock: { + size_t buffer_size = s->sbuf.cb->size; + size_t buffer_max_size = s->sbuf.cb->max_size; + size_t buffer_available = cbuffer_available_size_unsafe(s->sbuf.cb); + msg = s->thread.msg; + stream_sender_unlock(s); + msg.opcode = STREAM_OPCODE_SENDER_BUFFER_OVERFLOW; + stream_sender_send_msg_to_dispatcher(s, msg); + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM %s [send to %s]: buffer overflow while adding %zu bytes (buffer size %zu, max size %zu, available %zu). " + "Restarting connection.", + rrdhost_hostname(s->host), s->connected_to, + total_compressed_len, buffer_size, buffer_max_size, buffer_available); + return; + } + +compression_failed_with_lock: { + stream_compression_deactivate(s); + msg = s->thread.msg; + stream_sender_unlock(s); + msg.opcode = STREAM_OPCODE_SENDER_RECONNECT_WITHOUT_COMPRESSION; + stream_sender_send_msg_to_dispatcher(s, msg); + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM %s [send to %s]: COMPRESSION failed (twice). Deactivating compression and restarting connection.", + rrdhost_hostname(s->host), s->connected_to); + } +} + +void sender_thread_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type, const char *func) { + struct sender_buffer *commit = (wb == commit___thread.wb) ? & commit___thread : &s->host->stream.snd.commit; + + if (unlikely(wb != commit->wb)) + fatal("STREAMING: function '%s()' is trying to commit an unknown commit buffer.", func); + + if (unlikely(!commit->used)) + fatal("STREAMING: function '%s()' is committing a sender buffer twice.", func); + + commit->used = false; + commit->last_function = NULL; + + sender_buffer_commit(s, wb, commit, type); +} diff --git a/src/streaming/stream-sender-commit.h b/src/streaming/stream-sender-commit.h new file mode 100644 index 00000000000000..71e344f04800aa --- /dev/null +++ b/src/streaming/stream-sender-commit.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_STREAM_SENDER_COMMIT_H +#define NETDATA_STREAM_SENDER_COMMIT_H + +#include "libnetdata/libnetdata.h" +#include "stream-traffic-types.h" + +struct rrdhost; +struct sender_state; +struct receiver_state; + +struct sender_buffer { + pid_t receiver_tid; + BUFFER *wb; + bool used; + size_t our_recreates; + size_t sender_recreates; + const char *last_function; +}; +void sender_buffer_destroy(struct sender_buffer *commit); + +// thread buffer for sending data upstream (to a parent) + +BUFFER *sender_thread_buffer_with_trace(struct sender_state *s, const char *func); +#define sender_thread_buffer(s) sender_thread_buffer_with_trace(s, __FUNCTION__) + +BUFFER *sender_host_buffer_with_trace(struct rrdhost *host, const char *func); +#define sender_host_buffer(host) sender_host_buffer_with_trace(host, __FUNCTION__) + +void sender_thread_commit(struct sender_state *s, BUFFER *wb, STREAM_TRAFFIC_TYPE type, const char *func); +#define sender_commit(s, wb, type) sender_thread_commit(s, wb, type, __FUNCTION__) + +void sender_buffer_commit(struct sender_state *s, BUFFER *wb, struct sender_buffer *commit, STREAM_TRAFFIC_TYPE type); +#define sender_commit_clean_buffer(s, wb, type) sender_buffer_commit(s, wb, NULL, type) + +void sender_commit_thread_buffer_free(void); + +#endif //NETDATA_STREAM_SENDER_COMMIT_H diff --git a/src/streaming/sender-execute.c b/src/streaming/stream-sender-execute.c similarity index 71% rename from src/streaming/sender-execute.c rename to src/streaming/stream-sender-execute.c index e180710e90b891..69af2072f71fbf 100644 --- a/src/streaming/sender-execute.c +++ b/src/streaming/stream-sender-execute.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "sender-internals.h" +#include "stream-thread.h" struct inflight_stream_function { struct sender_state *sender; @@ -12,20 +12,18 @@ static void stream_execute_function_callback(BUFFER *func_wb, int code, void *da struct inflight_stream_function *tmp = data; struct sender_state *s = tmp->sender; - if(rrdhost_can_send_definitions_to_parent(s->host)) { - BUFFER *wb = sender_start(s); + if(rrdhost_can_stream_metadata_to_parent(s->host)) { + // for functions we use a new buffer, to avoid keeping a big buffer in memory + CLEAN_BUFFER *wb = buffer_create(0, NULL); - pluginsd_function_result_begin_to_buffer(wb - , string2str(tmp->transaction) - , code - , content_type_id2string(func_wb->content_type) - , func_wb->expires); + pluginsd_function_result_begin_to_buffer( + wb, string2str(tmp->transaction), code, + content_type_id2string(func_wb->content_type), func_wb->expires); buffer_fast_strcat(wb, buffer_tostring(func_wb), buffer_strlen(func_wb)); pluginsd_function_result_end_to_buffer(wb); - sender_commit(s, wb, STREAM_TRAFFIC_TYPE_FUNCTIONS); - sender_thread_buffer_free(); + sender_commit_clean_buffer(s, wb, STREAM_TRAFFIC_TYPE_FUNCTIONS); internal_error(true, "STREAM %s [send to %s] FUNCTION transaction %s sending back response (%zu bytes, %"PRIu64" usec).", rrdhost_hostname(s->host), s->connected_to, @@ -43,18 +41,18 @@ static void stream_execute_function_progress_callback(void *data, size_t done, s struct inflight_stream_function *tmp = data; struct sender_state *s = tmp->sender; - if(rrdhost_can_send_definitions_to_parent(s->host)) { - BUFFER *wb = sender_start(s); + if(rrdhost_can_stream_metadata_to_parent(s->host)) { + CLEAN_BUFFER *wb = buffer_create(0, NULL); buffer_sprintf(wb, PLUGINSD_KEYWORD_FUNCTION_PROGRESS " '%s' %zu %zu\n", string2str(tmp->transaction), done, all); - sender_commit(s, wb, STREAM_TRAFFIC_TYPE_FUNCTIONS); + sender_commit_clean_buffer(s, wb, STREAM_TRAFFIC_TYPE_FUNCTIONS); } } static void execute_commands_function(struct sender_state *s, const char *command, const char *transaction, const char *timeout_s, const char *function, BUFFER *payload, const char *access, const char *source) { - worker_is_busy(WORKER_SENDER_JOB_FUNCTION_REQUEST); + worker_is_busy(WORKER_SENDER_JOB_EXECUTE_FUNCTION); nd_log(NDLS_ACCESS, NDLP_INFO, NULL); if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) { @@ -108,7 +106,7 @@ static void execute_deferred_function(struct sender_state *s, void *data) { static void execute_deferred_json(struct sender_state *s, void *data) { const char *keyword = data; - if(strcmp(keyword, PLUGINSD_KEYWORD_STREAM_PATH) == 0) + if(strcmp(keyword, PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH) == 0) stream_path_set_from_json(s->host, buffer_tostring(s->defer.payload), true); else nd_log(NDLS_DAEMON, NDLP_ERR, "STREAM: unknown JSON keyword '%s' with payload: %s", keyword, buffer_tostring(s->defer.payload)); @@ -141,21 +139,19 @@ static void cleanup_deferred_data(struct sender_state *s) { s->defer.action_data = NULL; } -void rrdpush_sender_execute_commands_cleanup(struct sender_state *s) { +void stream_sender_execute_commands_cleanup(struct sender_state *s) { cleanup_deferred_data(s); } // This is just a placeholder until the gap filling state machine is inserted -void rrdpush_sender_execute_commands(struct sender_state *s) { - worker_is_busy(WORKER_SENDER_JOB_EXECUTE); - +void stream_sender_execute_commands(struct sender_state *s) { ND_LOG_STACK lgs[] = { - ND_LOG_FIELD_CB(NDF_REQUEST, line_splitter_reconstruct_line, &s->line), + ND_LOG_FIELD_CB(NDF_REQUEST, line_splitter_reconstruct_line, &s->rbuf.line), ND_LOG_FIELD_END(), }; ND_LOG_STACK_PUSH(lgs); - char *start = s->read_buffer, *end = &s->read_buffer[s->read_len], *newline; + char *start = s->rbuf.b, *end = &s->rbuf.b[s->rbuf.read_len], *newline; *end = '\0'; for( ; start < end ; start = newline + 1) { newline = strchr(start, '\n'); @@ -169,7 +165,7 @@ void rrdpush_sender_execute_commands(struct sender_state *s) { } *newline = '\0'; - s->line.count++; + s->rbuf.line.count++; if(s->defer.end_keyword) { if(strcmp(start, s->defer.end_keyword) == 0) { @@ -184,25 +180,25 @@ void rrdpush_sender_execute_commands(struct sender_state *s) { continue; } - s->line.num_words = quoted_strings_splitter_whitespace(start, s->line.words, PLUGINSD_MAX_WORDS); - const char *command = get_word(s->line.words, s->line.num_words, 0); + s->rbuf.line.num_words = quoted_strings_splitter_whitespace(start, s->rbuf.line.words, PLUGINSD_MAX_WORDS); + const char *command = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 0); if(command && strcmp(command, PLUGINSD_CALL_FUNCTION) == 0) { - char *transaction = get_word(s->line.words, s->line.num_words, 1); - char *timeout_s = get_word(s->line.words, s->line.num_words, 2); - char *function = get_word(s->line.words, s->line.num_words, 3); - char *access = get_word(s->line.words, s->line.num_words, 4); - char *source = get_word(s->line.words, s->line.num_words, 5); + char *transaction = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 1); + char *timeout_s = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 2); + char *function = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 3); + char *access = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 4); + char *source = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 5); execute_commands_function(s, command, transaction, timeout_s, function, NULL, access, source); } else if(command && strcmp(command, PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN) == 0) { - char *transaction = get_word(s->line.words, s->line.num_words, 1); - char *timeout_s = get_word(s->line.words, s->line.num_words, 2); - char *function = get_word(s->line.words, s->line.num_words, 3); - char *access = get_word(s->line.words, s->line.num_words, 4); - char *source = get_word(s->line.words, s->line.num_words, 5); - char *content_type = get_word(s->line.words, s->line.num_words, 6); + char *transaction = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 1); + char *timeout_s = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 2); + char *function = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 3); + char *access = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 4); + char *source = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 5); + char *content_type = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 6); s->defer.end_keyword = PLUGINSD_CALL_FUNCTION_PAYLOAD_END; s->defer.payload = buffer_create(0, NULL); @@ -220,29 +216,31 @@ void rrdpush_sender_execute_commands(struct sender_state *s) { s->defer.action_data = dfd; } else if(command && strcmp(command, PLUGINSD_CALL_FUNCTION_CANCEL) == 0) { - worker_is_busy(WORKER_SENDER_JOB_FUNCTION_REQUEST); + worker_is_busy(WORKER_SENDER_JOB_EXECUTE_FUNCTION); nd_log(NDLS_ACCESS, NDLP_DEBUG, NULL); - char *transaction = get_word(s->line.words, s->line.num_words, 1); + char *transaction = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 1); if(transaction && *transaction) rrd_function_cancel(transaction); } else if(command && strcmp(command, PLUGINSD_CALL_FUNCTION_PROGRESS) == 0) { - worker_is_busy(WORKER_SENDER_JOB_FUNCTION_REQUEST); + worker_is_busy(WORKER_SENDER_JOB_EXECUTE_FUNCTION); nd_log(NDLS_ACCESS, NDLP_DEBUG, NULL); - char *transaction = get_word(s->line.words, s->line.num_words, 1); + char *transaction = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 1); if(transaction && *transaction) rrd_function_progress(transaction); } else if (command && strcmp(command, PLUGINSD_KEYWORD_REPLAY_CHART) == 0) { - worker_is_busy(WORKER_SENDER_JOB_REPLAY_REQUEST); - nd_log(NDLS_ACCESS, NDLP_DEBUG, NULL); + worker_is_busy(WORKER_SENDER_JOB_EXECUTE_REPLAY); - const char *chart_id = get_word(s->line.words, s->line.num_words, 1); - const char *start_streaming = get_word(s->line.words, s->line.num_words, 2); - const char *after = get_word(s->line.words, s->line.num_words, 3); - const char *before = get_word(s->line.words, s->line.num_words, 4); + // do not log replication commands received - way too many! + // nd_log(NDLS_ACCESS, NDLP_DEBUG, NULL); + + const char *chart_id = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 1); + const char *start_streaming = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 2); + const char *after = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 3); + const char *before = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 4); if (!chart_id || !start_streaming || !after || !before) { netdata_log_error("STREAM %s [send to %s] %s command is incomplete" @@ -263,10 +261,13 @@ void rrdpush_sender_execute_commands(struct sender_state *s) { } } else if(command && strcmp(command, PLUGINSD_KEYWORD_NODE_ID) == 0) { - rrdpush_sender_get_node_and_claim_id_from_parent(s); + worker_is_busy(WORKER_SENDER_JOB_EXECUTE_META); + stream_sender_get_node_and_claim_id_from_parent(s); } else if(command && strcmp(command, PLUGINSD_KEYWORD_JSON) == 0) { - char *keyword = get_word(s->line.words, s->line.num_words, 1); + worker_is_busy(WORKER_SENDER_JOB_EXECUTE_META); + + char *keyword = get_word(s->rbuf.line.words, s->rbuf.line.num_words, 1); s->defer.end_keyword = PLUGINSD_KEYWORD_JSON_END; s->defer.payload = buffer_create(0, NULL); @@ -276,19 +277,18 @@ void rrdpush_sender_execute_commands(struct sender_state *s) { } else { netdata_log_error("STREAM %s [send to %s] received unknown command over connection: %s", - rrdhost_hostname(s->host), s->connected_to, s->line.words[0]?s->line.words[0]:"(unset)"); + rrdhost_hostname(s->host), s->connected_to, s->rbuf.line.words[0]?s->rbuf.line.words[0]:"(unset)"); } - line_splitter_reset(&s->line); - worker_is_busy(WORKER_SENDER_JOB_EXECUTE); + line_splitter_reset(&s->rbuf.line); } if (start < end) { - memmove(s->read_buffer, start, end-start); - s->read_len = end - start; + memmove(s->rbuf.b, start, end-start); + s->rbuf.read_len = end - start; } else { - s->read_buffer[0] = '\0'; - s->read_len = 0; + s->rbuf.b[0] = '\0'; + s->rbuf.read_len = 0; } } diff --git a/src/streaming/stream-sender-internals.h b/src/streaming/stream-sender-internals.h new file mode 100644 index 00000000000000..07cd71ee9d2853 --- /dev/null +++ b/src/streaming/stream-sender-internals.h @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_STREAM_SENDER_INTERNALS_H +#define NETDATA_STREAM_SENDER_INTERNALS_H + +#include "stream.h" +#include "stream-thread.h" +#include "h2o-common.h" +#include "aclk/https_client.h" +#include "stream-parents.h" + +// connector thread +#define WORKER_SENDER_CONNECTOR_JOB_CONNECTING 0 +#define WORKER_SENDER_CONNECTOR_JOB_CONNECTED 1 +#define WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_BAD_HANDSHAKE 2 +#define WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_TIMEOUT 3 +#define WORKER_SENDER_CONNECTOR_JOB_DISCONNECT_CANT_UPGRADE_CONNECTION 4 +#define WORKER_SENDER_CONNECTOR_JOB_QUEUED_NODES 5 +#define WORKER_SENDER_CONNECTOR_JOB_CONNECTED_NODES 6 +#define WORKER_SENDER_CONNECTOR_JOB_FAILED_NODES 7 +#define WORKER_SENDER_CONNECTOR_JOB_CANCELLED_NODES 8 + +#define CONNECTED_TO_SIZE 100 + +#define CBUFFER_INITIAL_SIZE (16 * 1024) +#define CBUFFER_INITIAL_MAX_SIZE (10 * 1024 * 1024) +#define THREAD_BUFFER_INITIAL_SIZE (CBUFFER_INITIAL_SIZE / 2) + +#include "stream-compression/compression.h" +#include "stream-conf.h" + +typedef void (*stream_defer_action_t)(struct sender_state *s, void *data); +typedef void (*stream_defer_cleanup_t)(struct sender_state *s, void *data); + +struct sender_state { + SPINLOCK spinlock; + + RRDHOST *host; + STREAM_CAPABILITIES capabilities; + STREAM_CAPABILITIES disabled_capabilities; + int16_t hops; + + ND_SOCK sock; + + struct { + struct stream_opcode msg; // the template for sending a message to the dispatcher - protected by sender_lock() + + // this is a property of stream_sender_send_msg_to_dispatcher() + // protected by dispatcher->messages.spinlock + // DO NOT READ OR WRITE ANYWHERE + uint32_t msg_slot; // ensures a dispatcher queue that can never get full + + // statistics about our compression efficiency + size_t bytes_compressed; + size_t bytes_uncompressed; + + // the current buffer statistics + // these SHOULD ALWAYS BE CALCULATED ON EVERY sender_unlock() IF THE BUFFER WAS MODIFIED + size_t bytes_outstanding; + size_t bytes_available; + NETDATA_DOUBLE buffer_ratio; + + // statistics about successful sends + size_t sends; + size_t bytes_sent; + size_t bytes_sent_by_type[STREAM_TRAFFIC_TYPE_MAX]; + + struct pollfd_meta meta; + } thread; + + struct { + int8_t id; // the connector id - protected by sender_lock() + } connector; + + char connected_to[CONNECTED_TO_SIZE + 1]; // We don't know which proxy we connect to, passed back from socket.c + time_t last_traffic_seen_t; + time_t last_state_since_t; // the timestamp of the last state (online/offline) change + + struct { + struct circular_buffer *cb; + size_t recreates; + } sbuf; + + struct { + char b[PLUGINSD_LINE_MAX + 1]; + ssize_t read_len; + struct line_splitter line; + } rbuf; + + struct compressor_state compressor; + +#ifdef NETDATA_LOG_STREAM_SENDER + FILE *stream_log_fp; +#endif + + struct { + bool shutdown; // when set, the sender should stop sending this host + STREAM_HANDSHAKE reason; // the reason we decided to stop this sender + } exit; + + struct { + DICTIONARY *requests; // de-duplication of replication requests, per chart + time_t oldest_request_after_t; // the timestamp of the oldest replication request + time_t latest_completed_before_t; // the timestamp of the latest replication request + + struct { + size_t pending_requests; // the currently outstanding replication requests + size_t charts_replicating; // the number of unique charts having pending replication requests (on every request one is added and is removed when we finish it - it does not track completion of the replication for this chart) + bool reached_max; // true when the sender buffer should not get more replication responses + } atomic; + + } replication; + + struct { + size_t buffer_used_percentage; // the current utilization of the sending buffer + usec_t last_flush_time_ut; // the last time the sender flushed the sending buffer in USEC + } atomic; + + struct { + const char *end_keyword; + BUFFER *payload; + stream_defer_action_t action; + stream_defer_cleanup_t cleanup; + void *action_data; + } defer; + + bool parent_using_h2o; +}; + +#define stream_sender_lock(sender) spinlock_lock(&(sender)->spinlock) +#define stream_sender_unlock(sender) spinlock_unlock(&(sender)->spinlock) + +#define stream_sender_replication_buffer_full_set(sender, value) __atomic_store_n(&((sender)->replication.atomic.reached_max), value, __ATOMIC_SEQ_CST) +#define stream_sender_replication_buffer_full_get(sender) __atomic_load_n(&((sender)->replication.atomic.reached_max), __ATOMIC_SEQ_CST) + +#define stream_sender_set_buffer_used_percent(sender, value) __atomic_store_n(&((sender)->atomic.buffer_used_percentage), value, __ATOMIC_RELAXED) +#define stream_sender_get_buffer_used_percent(sender) __atomic_load_n(&((sender)->atomic.buffer_used_percentage), __ATOMIC_RELAXED) + +#define stream_sender_set_flush_time(sender) __atomic_store_n(&((sender)->atomic.last_flush_time_ut), now_realtime_usec(), __ATOMIC_RELAXED) +#define stream_sender_get_flush_time(sender) __atomic_load_n(&((sender)->atomic.last_flush_time_ut), __ATOMIC_RELAXED) + +#define stream_sender_replicating_charts(sender) __atomic_load_n(&((sender)->replication.atomic.charts_replicating), __ATOMIC_RELAXED) +#define stream_sender_replicating_charts_plus_one(sender) __atomic_add_fetch(&((sender)->replication.atomic.charts_replicating), 1, __ATOMIC_RELAXED) +#define stream_sender_replicating_charts_minus_one(sender) __atomic_sub_fetch(&((sender)->replication.atomic.charts_replicating), 1, __ATOMIC_RELAXED) +#define stream_sender_replicating_charts_zero(sender) __atomic_store_n(&((sender)->replication.atomic.charts_replicating), 0, __ATOMIC_RELAXED) + +#define stream_sender_pending_replication_requests(sender) __atomic_load_n(&((sender)->replication.atomic.pending_requests), __ATOMIC_RELAXED) +#define stream_sender_pending_replication_requests_plus_one(sender) __atomic_add_fetch(&((sender)->replication.atomic.pending_requests), 1, __ATOMIC_RELAXED) +#define stream_sender_pending_replication_requests_minus_one(sender) __atomic_sub_fetch(&((sender)->replication.atomic.pending_requests), 1, __ATOMIC_RELAXED) +#define stream_sender_pending_replication_requests_zero(sender) __atomic_store_n(&((sender)->replication.atomic.pending_requests), 0, __ATOMIC_RELAXED) + +void stream_sender_add_to_connector_queue(RRDHOST *host); + +void stream_sender_execute_commands_cleanup(struct sender_state *s); +void stream_sender_execute_commands(struct sender_state *s); + +bool stream_connect(struct sender_state *s, uint16_t default_port, time_t timeout); + +bool stream_sender_is_host_stopped(struct sender_state *s); + +void stream_sender_send_msg_to_dispatcher(struct sender_state *s, struct stream_opcode msg); + +void stream_sender_thread_data_added_data_unsafe(struct sender_state *s, STREAM_TRAFFIC_TYPE type, uint64_t bytes_compressed, uint64_t bytes_uncompressed); + +void stream_sender_add_to_queue(struct sender_state *s); + +// stream connector +bool stream_connector_init(struct sender_state *s); +void stream_connector_cancel_threads(void); +void stream_connector_add(struct sender_state *s); +void stream_connector_requeue(struct sender_state *s); +bool stream_connector_is_signaled_to_stop(struct sender_state *s); + +void stream_sender_on_connect(struct sender_state *s); + +void stream_sender_remove(struct sender_state *s); + +#endif //NETDATA_STREAM_SENDER_INTERNALS_H diff --git a/src/streaming/stream-sender.c b/src/streaming/stream-sender.c new file mode 100644 index 00000000000000..9d0ebab5295e2d --- /dev/null +++ b/src/streaming/stream-sender.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream-thread.h" +#include "stream-sender-internals.h" + +static void stream_sender_move_running_to_connector_or_remove(struct stream_thread *sth, struct sender_state *s, STREAM_HANDSHAKE reason, bool reconnect); + +// -------------------------------------------------------------------------------------------------------------------- + +static void stream_sender_cbuffer_recreate_timed_unsafe(struct sender_state *s, time_t now_s, bool force) { + static __thread time_t last_reset_time_s = 0; + + if(!force && now_s - last_reset_time_s < 300) + return; + + last_reset_time_s = now_s; + + s->sbuf.recreates++; // we increase even if we don't do it, to have sender_start() recreate its buffers + + if(s->sbuf.cb && s->sbuf.cb->size > CBUFFER_INITIAL_SIZE) { + cbuffer_free(s->sbuf.cb); + s->sbuf.cb = cbuffer_new(CBUFFER_INITIAL_SIZE, stream_send.buffer_max_size, &netdata_buffers_statistics.cbuffers_streaming); + } +} + +static void rrdpush_sender_cbuffer_flush(RRDHOST *host) { + stream_sender_set_flush_time(host->sender); + + stream_sender_lock(host->sender); + + // flush the output buffer from any data it may have + cbuffer_flush(host->sender->sbuf.cb); + stream_sender_cbuffer_recreate_timed_unsafe(host->sender, now_monotonic_sec(), true); + + stream_sender_unlock(host->sender); +} + +// -------------------------------------------------------------------------------------------------------------------- + +static void rrdpush_sender_charts_and_replication_reset(struct sender_state *s) { + stream_sender_set_flush_time(s); + + // stop all replication commands inflight + replication_sender_delete_pending_requests(s); + + // reset the state of all charts + RRDSET *st; + rrdset_foreach_read(st, s->host) { + rrdset_flag_clear(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS); + rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_FINISHED); + + st->stream.snd.resync_time_s = 0; + + RRDDIM *rd; + rrddim_foreach_read(rd, st) + rrddim_metadata_exposed_upstream_clear(rd); + rrddim_foreach_done(rd); + + rrdset_metadata_updated(st); + } + rrdset_foreach_done(st); + + rrdhost_sender_replicating_charts_zero(s->host); + stream_sender_replicating_charts_zero(s); +} + +// -------------------------------------------------------------------------------------------------------------------- + +void stream_sender_on_connect(struct sender_state *s) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM SEND [%s]: running on-connect hooks...", + rrdhost_hostname(s->host)); + + rrdhost_flag_set(s->host, RRDHOST_FLAG_STREAM_SENDER_CONNECTED); + + rrdpush_sender_charts_and_replication_reset(s); + rrdpush_sender_cbuffer_flush(s->host); + + s->last_traffic_seen_t = now_monotonic_sec(); + s->rbuf.read_len = 0; + s->sbuf.cb->read = 0; + s->sbuf.cb->write = 0; +} + +static void stream_sender_on_ready_to_dispatch(struct sender_state *s) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM SEND [%s]: running ready-to-dispatch hooks...", + rrdhost_hostname(s->host)); + + // set this flag before sending any data, or the data will not be sent + rrdhost_flag_set(s->host, RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS); + + stream_sender_execute_commands_cleanup(s); + stream_sender_send_custom_host_variables(s->host); + stream_path_send_to_parent(s->host); + stream_sender_send_claimed_id(s->host); + stream_send_host_labels(s->host); + stream_send_global_functions(s->host); +} + +static void stream_sender_on_disconnect(struct sender_state *s) { + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM SEND [%s]: running on-disconnect hooks...", + rrdhost_hostname(s->host)); + + stream_sender_execute_commands_cleanup(s); + rrdpush_sender_charts_and_replication_reset(s); + stream_sender_clear_parent_claim_id(s->host); + stream_receiver_send_node_and_claim_id_to_child(s->host); + stream_path_parent_disconnected(s->host); +} + +// -------------------------------------------------------------------------------------------------------------------- + +static bool stream_sender_log_capabilities(BUFFER *wb, void *ptr) { + struct sender_state *state = ptr; + if(!state) + return false; + + stream_capabilities_to_string(wb, state->capabilities); + return true; +} + +static bool stream_sender_log_transport(BUFFER *wb, void *ptr) { + struct sender_state *state = ptr; + if(!state) + return false; + + buffer_strcat(wb, nd_sock_is_ssl(&state->sock) ? "https" : "http"); + return true; +} + +static bool stream_sender_log_dst_ip(BUFFER *wb, void *ptr) { + struct sender_state *state = ptr; + if(!state || state->sock.fd == -1) + return false; + + SOCKET_PEERS peers = nd_sock_socket_peers(&state->sock); + buffer_strcat(wb, peers.peer.ip); + return true; +} + +static bool stream_sender_log_dst_port(BUFFER *wb, void *ptr) { + struct sender_state *state = ptr; + if(!state || state->sock.fd == -1) + return false; + + SOCKET_PEERS peers = nd_sock_socket_peers(&state->sock); + buffer_print_uint64(wb, peers.peer.port); + return true; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static void stream_sender_thread_data_reset_unsafe(struct sender_state *s) { + memset(s->thread.bytes_sent_by_type, 0, sizeof(s->thread.bytes_sent_by_type)); + + s->thread.bytes_uncompressed = 0; + s->thread.bytes_compressed = 0; + s->thread.bytes_outstanding = 0; + s->thread.bytes_available = 0; + s->thread.buffer_ratio = 0.0; + s->thread.sends = 0; + s->thread.bytes_sent = 0; + replication_recalculate_buffer_used_ratio_unsafe(s); +} + +static void stream_sender_thread_data_sent_data_unsafe(struct sender_state *s, uint64_t bytes_sent) { + s->thread.sends++; + s->thread.bytes_sent += bytes_sent; + s->thread.bytes_outstanding = cbuffer_next_unsafe(s->sbuf.cb, NULL); + s->thread.bytes_available = cbuffer_available_size_unsafe(s->sbuf.cb); + s->thread.buffer_ratio = (NETDATA_DOUBLE)(s->sbuf.cb->max_size - s->thread.bytes_available) * 100.0 / (NETDATA_DOUBLE)s->sbuf.cb->max_size; + replication_recalculate_buffer_used_ratio_unsafe(s); +} + +void stream_sender_thread_data_added_data_unsafe(struct sender_state *s, STREAM_TRAFFIC_TYPE type, uint64_t bytes_compressed, uint64_t bytes_uncompressed) { + // calculate the statistics for our dispatcher + s->thread.bytes_sent_by_type[type] += bytes_compressed; + + s->thread.bytes_uncompressed += bytes_uncompressed; + s->thread.bytes_compressed += bytes_compressed; + s->thread.bytes_outstanding = cbuffer_next_unsafe(s->sbuf.cb, NULL); + s->thread.bytes_available = cbuffer_available_size_unsafe(s->sbuf.cb); + s->thread.buffer_ratio = (NETDATA_DOUBLE)(s->sbuf.cb->max_size - s->thread.bytes_available) * 100.0 / (NETDATA_DOUBLE)s->sbuf.cb->max_size; + replication_recalculate_buffer_used_ratio_unsafe(s); +} + +// -------------------------------------------------------------------------------------------------------------------- +// opcodes + +void stream_sender_handle_op(struct stream_thread *sth, struct sender_state *s, struct stream_opcode *msg) { + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, s->host->hostname), + ND_LOG_FIELD_CB(NDF_DST_IP, stream_sender_log_dst_ip, s), + ND_LOG_FIELD_CB(NDF_DST_PORT, stream_sender_log_dst_port, s), + ND_LOG_FIELD_CB(NDF_DST_TRANSPORT, stream_sender_log_transport, s), + ND_LOG_FIELD_CB(NDF_SRC_CAPABILITIES, stream_sender_log_capabilities, s), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + if(msg->opcode & STREAM_OPCODE_SENDER_BUFFER_OVERFLOW) { + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_OVERFLOW); + errno_clear(); + stream_sender_lock(s); + size_t buffer_size = s->sbuf.cb->size; + size_t buffer_max_size = s->sbuf.cb->max_size; + size_t buffer_available = cbuffer_available_size_unsafe(s->sbuf.cb); + stream_sender_unlock(s); + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu] %s [to %s]: send buffer is full (buffer size %zu, max %zu, available %zu). " + "Restarting connection.", + sth->id, rrdhost_hostname(s->host), s->connected_to, + buffer_size, buffer_max_size, buffer_available); + + stream_sender_move_running_to_connector_or_remove( + sth, s, STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_SENDER_SEND_BUFFER, true); + return; + } + + if(msg->opcode & STREAM_OPCODE_SENDER_STOP_RECEIVER_LEFT) { + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_RECEIVER_LEFT); + stream_sender_move_running_to_connector_or_remove( + sth, s, STREAM_HANDSHAKE_DISCONNECT_RECEIVER_LEFT, false); + return; + } + + if(msg->opcode & STREAM_OPCODE_SENDER_RECONNECT_WITHOUT_COMPRESSION) { + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_COMPRESSION_ERROR); + errno_clear(); + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu] %s [send to %s]: restarting connection without compression.", + sth->id, rrdhost_hostname(s->host), s->connected_to); + + stream_sender_move_running_to_connector_or_remove( + sth, s, STREAM_HANDSHAKE_DISCONNECT_NOT_SUFFICIENT_SENDER_COMPRESSION_FAILED, true); + return; + } + + if(msg->opcode & STREAM_OPCODE_SENDER_STOP_HOST_CLEANUP) { + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_HOST_CLEANUP); + stream_sender_move_running_to_connector_or_remove( + sth, s, STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP, false); + return; + } + + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu]: invalid msg id %u", sth->id, (unsigned)msg->opcode); +} + + +// -------------------------------------------------------------------------------------------------------------------- + +void stream_sender_move_queue_to_running_unsafe(struct stream_thread *sth) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + // process the queue + Word_t idx = 0; + for(struct sender_state *s = SENDERS_FIRST(&sth->queue.senders, &idx); + s; + s = SENDERS_NEXT(&sth->queue.senders, &idx)) { + worker_is_busy(WORKER_STREAM_JOB_DEQUEUE); + + SENDERS_DEL(&sth->queue.senders, (Word_t)s); + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, s->host->hostname), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM SEND[%zu] [%s]: moving host from dispatcher queue to dispatcher running...", + sth->id, rrdhost_hostname(s->host)); + + internal_fatal(SENDERS_GET(&sth->snd.senders, (Word_t)s) != NULL, "Sender already exists in senders list"); + SENDERS_SET(&sth->snd.senders, (Word_t)s, s); + + stream_sender_lock(s); + s->thread.meta.type = POLLFD_TYPE_SENDER; + s->thread.meta.s = s; + if(!nd_poll_add(sth->run.ndpl, s->sock.fd, ND_POLL_READ, &s->thread.meta)) + internal_fatal(true, "Failed to add sender socket to nd_poll()"); + + s->thread.msg.thread_slot = (int32_t)sth->id; + s->thread.msg.session = os_random32(); + s->thread.msg.sender = s; + + s->host->stream.snd.status.tid = gettid_cached(); + s->host->stream.snd.status.connections++; + s->last_state_since_t = now_realtime_sec(); + + stream_sender_thread_data_reset_unsafe(s); + stream_sender_unlock(s); + + stream_sender_on_ready_to_dispatch(s); + } +} + +void stream_sender_remove(struct sender_state *s) { + // THIS FUNCTION IS USED BY THE CONNECTOR TOO + // when it gives up on a certain node + + nd_log(NDLS_DAEMON, NDLP_NOTICE, + "STREAM SEND [%s]: streaming sender removed host: %s", + rrdhost_hostname(s->host), stream_handshake_error_to_string(s->exit.reason)); + + stream_sender_lock(s); + + __atomic_store_n(&s->exit.shutdown, false, __ATOMIC_RELAXED); + rrdhost_flag_clear(s->host, + RRDHOST_FLAG_STREAM_SENDER_ADDED | RRDHOST_FLAG_STREAM_SENDER_CONNECTED | + RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS); + + s->last_state_since_t = now_realtime_sec(); + stream_parent_set_disconnect_reason(s->host->stream.snd.parents.current, s->exit.reason, s->last_state_since_t); + s->connector.id = -1; + + stream_sender_unlock(s); + + rrdhost_stream_parents_reset(s->host, STREAM_HANDSHAKE_EXITING); + +#ifdef NETDATA_LOG_STREAM_SENDER + if (s->stream_log_fp) { + fclose(s->stream_log_fp); + s->stream_log_fp = NULL; + } +#endif +} + +static void stream_sender_move_running_to_connector_or_remove(struct stream_thread *sth, struct sender_state *s, STREAM_HANDSHAKE reason, bool reconnect) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + internal_fatal(SENDERS_GET(&sth->snd.senders, (Word_t)s) == NULL, "Sender to be removed is not in the list of senders"); + SENDERS_DEL(&sth->snd.senders, (Word_t)s); + if(!nd_poll_del(sth->run.ndpl, s->sock.fd)) + internal_fatal(true, "Failed to remove sender socket from nd_poll()"); + + // clear this flag asap, to stop other threads from pushing metrics for this node + rrdhost_flag_clear(s->host, RRDHOST_FLAG_STREAM_SENDER_CONNECTED | RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS); + + // clear these asap, to make sender_commit() stop processing data for this host + stream_sender_lock(s); + + s->thread.msg.session = 0; + s->thread.msg.sender = NULL; + + s->host->stream.snd.status.tid = 0; + stream_sender_unlock(s); + + nd_log(NDLS_DAEMON, NDLP_NOTICE, + "STREAM SEND [%s]: disconnected from parent, reason: %s", + rrdhost_hostname(s->host), stream_handshake_error_to_string(reason)); + + nd_sock_close(&s->sock); + + stream_parent_set_disconnect_reason(s->host->stream.snd.parents.current, reason, now_realtime_sec()); + stream_sender_on_disconnect(s); + + bool should_remove = !reconnect || stream_connector_is_signaled_to_stop(s); + + stream_thread_node_removed(s->host); + + if (should_remove) + stream_sender_remove(s); + else + stream_connector_requeue(s); +} + +void stream_sender_check_all_nodes_from_poll(struct stream_thread *sth) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + usec_t now_ut = now_monotonic_usec(); + time_t now_s = (time_t)(now_ut / USEC_PER_SEC); + + size_t bytes_uncompressed = 0; + size_t bytes_compressed = 0; + NETDATA_DOUBLE buffer_ratio = 0.0; + + Word_t idx = 0; + for(struct sender_state *s = SENDERS_FIRST(&sth->snd.senders, &idx); + s; + s = SENDERS_NEXT(&sth->snd.senders, &idx)) { + + // If the TCP window never opened, then something is wrong, restart connection + if(unlikely(now_s - s->last_traffic_seen_t > stream_send.parents.timeout_s && + !stream_sender_pending_replication_requests(s) && + !stream_sender_replicating_charts(s) + )) { + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, s->host->hostname), + ND_LOG_FIELD_CB(NDF_DST_IP, stream_sender_log_dst_ip, s), + ND_LOG_FIELD_CB(NDF_DST_PORT, stream_sender_log_dst_port, s), + ND_LOG_FIELD_CB(NDF_DST_TRANSPORT, stream_sender_log_transport, s), + ND_LOG_FIELD_CB(NDF_SRC_CAPABILITIES, stream_sender_log_capabilities, s), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_TIMEOUT); + + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu] %s [send to %s]: could not send metrics for %ld seconds - closing connection - " + "we have sent %zu bytes on this connection via %zu send attempts.", + sth->id, rrdhost_hostname(s->host), s->connected_to, stream_send.parents.timeout_s, + s->thread.bytes_sent, s->thread.sends); + + stream_sender_move_running_to_connector_or_remove(sth, s, STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_TIMEOUT, true); + continue; + } + + stream_sender_lock(s); + { + bytes_compressed += s->thread.bytes_compressed; + bytes_uncompressed += s->thread.bytes_uncompressed; + uint64_t outstanding = s->thread.bytes_outstanding; + if (s->thread.buffer_ratio > buffer_ratio) + buffer_ratio = s->thread.buffer_ratio; + + if(!nd_poll_upd(sth->run.ndpl, s->sock.fd, ND_POLL_READ | (outstanding ? ND_POLL_WRITE : 0), &s->thread.meta)) + internal_fatal(true, "Failed to update sender socket in nd_poll()"); + } + stream_sender_unlock(s); + } + + if (bytes_compressed && bytes_uncompressed) { + NETDATA_DOUBLE compression_ratio = 100.0 - ((NETDATA_DOUBLE)bytes_compressed * 100.0 / (NETDATA_DOUBLE)bytes_uncompressed); + worker_set_metric(WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO, compression_ratio); + } + + worker_set_metric(WORKER_SENDER_JOB_BYTES_UNCOMPRESSED, (NETDATA_DOUBLE)bytes_uncompressed); + worker_set_metric(WORKER_SENDER_JOB_BYTES_COMPRESSED, (NETDATA_DOUBLE)bytes_compressed); + worker_set_metric(WORKER_SENDER_JOB_BUFFER_RATIO, buffer_ratio); +} + +void stream_sender_process_poll_events(struct stream_thread *sth, struct sender_state *s, nd_poll_event_t events, time_t now_s) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, s->host->hostname), + ND_LOG_FIELD_CB(NDF_DST_IP, stream_sender_log_dst_ip, s), + ND_LOG_FIELD_CB(NDF_DST_PORT, stream_sender_log_dst_port, s), + ND_LOG_FIELD_CB(NDF_DST_TRANSPORT, stream_sender_log_transport, s), + ND_LOG_FIELD_CB(NDF_SRC_CAPABILITIES, stream_sender_log_capabilities, s), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + if(unlikely(events & ND_POLL_ERROR)) { + // we have errors on this socket + + worker_is_busy(WORKER_STREAM_JOB_SOCKET_ERROR); + + char *error = "unknown error"; + + if (events & ND_POLL_ERROR) + error = "socket reports errors"; + else if (events & ND_POLL_HUP) + error = "connection closed by remote end (HUP)"; + else if (events & ND_POLL_INVALID) + error = "connection is invalid"; + + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SOCKET_ERROR); + + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu] %s [send to %s]: %s restarting connection - %zu bytes transmitted.", + sth->id, rrdhost_hostname(s->host), s->connected_to, error, s->thread.bytes_sent); + + stream_sender_move_running_to_connector_or_remove(sth, s, STREAM_HANDSHAKE_DISCONNECT_SOCKET_ERROR, true); + return; + } + + if(events & ND_POLL_WRITE) { + // we can send data on this socket + + worker_is_busy(WORKER_STREAM_JOB_SOCKET_SEND); + + bool disconnect = false; + stream_sender_lock(s); + { + char *chunk; + size_t outstanding = cbuffer_next_unsafe(s->sbuf.cb, &chunk); + ssize_t bytes = nd_sock_send_nowait(&s->sock, chunk, outstanding); + if (likely(bytes > 0)) { + cbuffer_remove_unsafe(s->sbuf.cb, bytes); + stream_sender_thread_data_sent_data_unsafe(s, bytes); + s->last_traffic_seen_t = now_s; + sth->snd.bytes_sent += bytes; + + if(!s->thread.bytes_outstanding) { + // we sent them all - remove POLLOUT + if(!nd_poll_upd(sth->run.ndpl, s->sock.fd, ND_POLL_READ, &s->thread.meta)) + internal_fatal(true, "Failed to update sender socket in nd_poll()"); + + // recreate the circular buffer if we have to + stream_sender_cbuffer_recreate_timed_unsafe(s, now_s, false); + } + } + else if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) + disconnect = true; + } + stream_sender_unlock(s); + + if(disconnect) { + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SEND_ERROR); + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu] %s [send to %s]: failed to send metrics - restarting connection - " + "we have sent %zu bytes on this connection.", + sth->id, rrdhost_hostname(s->host), s->connected_to, s->thread.bytes_sent); + stream_sender_move_running_to_connector_or_remove( + sth, s, STREAM_HANDSHAKE_DISCONNECT_SOCKET_WRITE_FAILED, true); + return; + } + } + + if(events & POLLIN) { + // we can receive data from this socket + + worker_is_busy(WORKER_STREAM_JOB_SOCKET_RECEIVE); + ssize_t bytes = nd_sock_revc_nowait(&s->sock, s->rbuf.b + s->rbuf.read_len, sizeof(s->rbuf.b) - s->rbuf.read_len - 1); + if (bytes > 0) { + s->rbuf.read_len += bytes; + s->last_traffic_seen_t = now_s; + sth->snd.bytes_received += bytes; + } + else if (bytes == 0 || errno == ECONNRESET) { + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_PARENT_CLOSED); + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu] %s [send to %s]: connection (fd %d) closed by far end.", + sth->id, rrdhost_hostname(s->host), s->connected_to, s->sock.fd); + stream_sender_move_running_to_connector_or_remove( + sth, s, STREAM_HANDSHAKE_DISCONNECT_SOCKET_CLOSED_BY_PARENT, true); + return; + } + else if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) { + worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_RECEIVE_ERROR); + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM SEND[%zu] %s [send to %s]: error during receive (%zd, on fd %d) - restarting connection.", + sth->id, rrdhost_hostname(s->host), s->connected_to, bytes, s->sock.fd); + stream_sender_move_running_to_connector_or_remove( + sth, s, STREAM_HANDSHAKE_DISCONNECT_SOCKET_READ_FAILED, true); + return; + } + } + + if(unlikely(s->rbuf.read_len)) { + worker_is_busy(WORKER_SENDER_JOB_EXECUTE); + stream_sender_execute_commands(s); + } +} + +void stream_sender_cleanup(struct stream_thread *sth) { + // stop all hosts + Word_t idx = 0; + for(struct sender_state *s = SENDERS_FIRST(&sth->snd.senders, &idx); + s; + s = SENDERS_NEXT(&sth->snd.senders, &idx)) { + + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, s->host->hostname), + ND_LOG_FIELD_CB(NDF_DST_IP, stream_sender_log_dst_ip, s), + ND_LOG_FIELD_CB(NDF_DST_PORT, stream_sender_log_dst_port, s), + ND_LOG_FIELD_CB(NDF_DST_TRANSPORT, stream_sender_log_transport, s), + ND_LOG_FIELD_CB(NDF_SRC_CAPABILITIES, stream_sender_log_capabilities, s), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + stream_sender_move_running_to_connector_or_remove(sth, s, STREAM_HANDSHAKE_DISCONNECT_SHUTDOWN, false); + } + + // cleanup + SENDERS_FREE(&sth->snd.senders, NULL); +} + diff --git a/src/streaming/stream-thread.c b/src/streaming/stream-thread.c new file mode 100644 index 00000000000000..cdbbb57c765f00 --- /dev/null +++ b/src/streaming/stream-thread.c @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stream-thread.h" + +struct stream_thread_globals stream_thread_globals = { + .assign = { + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// pipe messages + +static void stream_thread_handle_op(struct stream_thread *sth, struct stream_opcode *msg) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + sth->messages.processed++; + + struct sender_state *s = msg->sender ? SENDERS_GET(&sth->snd.senders, (Word_t)msg->sender) : NULL; + + if (msg->session && // there is a session + s && // there is a sender + (size_t)msg->thread_slot == sth->id) // same thread + { + if(msg->opcode & STREAM_OPCODE_SENDER_POLLOUT) { + if(!nd_poll_upd(sth->run.ndpl, s->sock.fd, ND_POLL_READ|ND_POLL_WRITE, &s->thread.meta)) + internal_fatal(true, "Failed to update sender socket in nd_poll()"); + msg->opcode &= ~(STREAM_OPCODE_SENDER_POLLOUT); + } + + if(msg->opcode) + stream_sender_handle_op(sth, s, msg); + } + else { + // this may happen if we receive a POLLOUT opcode, but the sender has been disconnected + nd_log(NDLS_DAEMON, NDLP_DEBUG, "STREAM THREAD[%zu]: OPCODE %u ignored.", sth->id, (unsigned)msg->opcode); + } +} + +void stream_sender_send_msg_to_dispatcher(struct sender_state *s, struct stream_opcode msg) { + if (!msg.session || !msg.sender || !s) + return; + + internal_fatal(msg.sender != s, "the sender pointer in the message does not match this sender"); + + struct stream_thread *sth = stream_thread_by_slot_id(msg.thread_slot); + if(!sth) { + internal_fatal(true, + "STREAM SEND[x] [%s] thread pointer in the opcode message does not match the expected", + rrdhost_hostname(s->host)); + return; + } + + bool send_pipe_msg = false; + + // check if we can execute the message now + if(sth->tid == gettid_cached()) { + // we are running at the dispatcher thread + // no need for locks or queuing + sth->messages.bypassed++; + stream_thread_handle_op(sth, &msg); + return; + } + + // add it to the message queue of the thread + spinlock_lock(&sth->messages.spinlock); + { + sth->messages.added++; + if (s->thread.msg_slot >= sth->messages.used || sth->messages.array[s->thread.msg_slot].sender != s) { + if (unlikely(sth->messages.used >= sth->messages.size)) { + // this should never happen, but let's find the root cause + + if (!sth->messages.size) { + // we are exiting + spinlock_unlock(&sth->messages.spinlock); + return; + } + + // try to find us in the list + for (size_t i = 0; i < sth->messages.size; i++) { + if (sth->messages.array[i].sender == s) { + s->thread.msg_slot = i; + sth->messages.array[s->thread.msg_slot].opcode |= msg.opcode; + spinlock_unlock(&sth->messages.spinlock); + internal_fatal(true, "the dispatcher message queue is full, but this sender is already on slot %zu", i); + return; + } + } + + fatal("the dispatcher message queue is full, but this should never happen"); + } + + // let's use a new slot + send_pipe_msg = !sth->messages.used; // write to the pipe, only when the queue was empty before this msg + s->thread.msg_slot = sth->messages.used++; + sth->messages.array[s->thread.msg_slot] = msg; + } + else + // the existing slot is good + sth->messages.array[s->thread.msg_slot].opcode |= msg.opcode; + } + spinlock_unlock(&sth->messages.spinlock); + + // signal the streaming thread to wake up and process messages + if(send_pipe_msg && + sth->pipe.fds[PIPE_WRITE] != -1 && + write(sth->pipe.fds[PIPE_WRITE], " ", 1) != 1) { + nd_log_limit_static_global_var(erl, 1, 1 * USEC_PER_MS); + nd_log_limit(&erl, NDLS_DAEMON, NDLP_ERR, + "STREAM SEND [%s]: cannot write to signal pipe", + rrdhost_hostname(s->host)); + } +} + +static void stream_thread_read_pipe_messages(struct stream_thread *sth) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + if(read(sth->pipe.fds[PIPE_READ], sth->pipe.buffer, sth->pipe.size * sizeof(*sth->pipe.buffer)) <= 0) + nd_log(NDLS_DAEMON, NDLP_ERR, "STREAM THREAD[%zu]: signal pipe read error", sth->id); + + size_t used = 0; + spinlock_lock(&sth->messages.spinlock); + if(sth->messages.used) { + used = sth->messages.used; + memcpy(sth->messages.copy, sth->messages.array, used * sizeof(*sth->messages.copy)); + sth->messages.used = 0; + } + spinlock_unlock(&sth->messages.spinlock); + + for(size_t i = 0; i < used ;i++) { + struct stream_opcode *msg = &sth->messages.copy[i]; + stream_thread_handle_op(sth, msg); + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +static int set_pipe_size(int pipe_fd, int new_size) { + int default_size = new_size; + int result = new_size; + +#ifdef F_GETPIPE_SZ + // get the current size of the pipe + result = fcntl(pipe_fd, F_GETPIPE_SZ); + if(result > 0) + default_size = result; +#endif + +#ifdef F_SETPIPE_SZ + // set the new size to the pipe + if(result <= new_size) { + result = fcntl(pipe_fd, F_SETPIPE_SZ, new_size); + if (result <= 0) + return default_size; + } +#endif + + // we return either: + // 1. the new_size (after setting it) + // 2. the current size (if we can't set it, but we can read it) + // 3. the new_size (without setting it when we can't read the current size) + return result; // Returns the new pipe size +} + +// -------------------------------------------------------------------------------------------------------------------- + +static void stream_thread_messages_resize_unsafe(struct stream_thread *sth) { + internal_fatal(sth->tid != gettid_cached(), "Function %s() should only be used by the dispatcher thread", __FUNCTION__ ); + + if(sth->nodes_count >= sth->messages.size) { + size_t new_size = sth->messages.size ? sth->messages.size * 2 : 2; + sth->messages.array = reallocz(sth->messages.array, new_size * sizeof(*sth->messages.array)); + sth->messages.copy = reallocz(sth->messages.copy, new_size * sizeof(*sth->messages.copy)); + sth->messages.size = new_size; + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +static bool stream_thread_process_poll_slot(struct stream_thread *sth, nd_poll_result_t *ev, time_t now_s, size_t *replay_entries) { + struct pollfd_meta *m = ev->data; + internal_fatal(!m, "Failed to get meta from event"); + + switch(m->type) { + case POLLFD_TYPE_SENDER: { + struct sender_state *s = m->s; + internal_fatal(SENDERS_GET(&sth->snd.senders, (Word_t)s) == NULL, "Sender is not found in the senders list"); + stream_sender_process_poll_events(sth, s, ev->events, now_s); + *replay_entries += dictionary_entries(s->replication.requests); + break; + } + + case POLLFD_TYPE_RECEIVER: { + struct receiver_state *rpt = m->rpt; + internal_fatal(RECEIVERS_GET(&sth->rcv.receivers, (Word_t)rpt) == NULL, "Receiver is not found in the receiver list"); + stream_receive_process_poll_events(sth, rpt, ev->events, now_s); + break; + } + + case POLLFD_TYPE_PIPE: + if (likely(ev->events & ND_POLL_READ)) { + worker_is_busy(WORKER_SENDER_JOB_PIPE_READ); + stream_thread_read_pipe_messages(sth); + } + else if(unlikely(ev->events & ND_POLL_ERROR)) { + // we have errors on this pipe + nd_log(NDLS_DAEMON, NDLP_ERR, + "STREAM THREAD[%zu]: got errors on pipe - exiting to be restarted.", sth->id); + return true; + } + break; + + case POLLFD_TYPE_EMPTY: + // should never happen - but make sure it never happens again + internal_fatal(true, "What is this?"); + break; + } + + return false; +} + +void *stream_thread(void *ptr) { + struct stream_thread *sth = ptr; + + worker_register("STREAM"); + + // stream thread main event loop + worker_register_job_name(WORKER_STREAM_JOB_LIST, "list"); + worker_register_job_name(WORKER_STREAM_JOB_DEQUEUE, "dequeue"); + worker_register_job_name(WORKER_STREAM_JOB_PREP, "prep"); + worker_register_job_name(WORKER_STREAM_JOB_POLL_ERROR, "poll error"); + worker_register_job_name(WORKER_SENDER_JOB_PIPE_READ, "pipe read"); + + // both sender and receiver + worker_register_job_name(WORKER_STREAM_JOB_SOCKET_RECEIVE, "receive"); + worker_register_job_name(WORKER_STREAM_JOB_SOCKET_SEND, "send"); + worker_register_job_name(WORKER_STREAM_JOB_SOCKET_ERROR, "sock error"); + + // receiver + worker_register_job_name(WORKER_STREAM_JOB_COMPRESS, "compress"); + worker_register_job_name(WORKER_STREAM_JOB_DECOMPRESS, "decompress"); + + // sender + worker_register_job_name(WORKER_SENDER_JOB_EXECUTE, "execute"); + worker_register_job_name(WORKER_SENDER_JOB_EXECUTE_REPLAY, "replay"); + worker_register_job_name(WORKER_SENDER_JOB_EXECUTE_FUNCTION, "function"); + worker_register_job_name(WORKER_SENDER_JOB_EXECUTE_META, "meta"); + + // disconnection reasons + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_OVERFLOW, "disconnect overflow"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_TIMEOUT, "disconnect timeout"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_SOCKET_ERROR, "disconnect socket error"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_PARENT_CLOSED, "disconnect parent closed"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_RECEIVE_ERROR, "disconnect receive error"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_SEND_ERROR, "disconnect send error"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_COMPRESSION_ERROR, "disconnect compression error"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_RECEIVER_LEFT, "disconnect receiver left"); + worker_register_job_name(WORKER_SENDER_JOB_DISCONNECT_HOST_CLEANUP, "disconnect host cleanup"); + + // metrics + worker_register_job_custom_metric(WORKER_STREAM_METRIC_NODES, + "nodes", "nodes", + WORKER_METRIC_ABSOLUTE); + + worker_register_job_custom_metric(WORKER_RECEIVER_JOB_BYTES_READ, + "receiver received bytes", "bytes/s", + WORKER_METRIC_INCREMENT); + + worker_register_job_custom_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, + "receiver received uncompressed bytes", "bytes/s", + WORKER_METRIC_INCREMENT); + + worker_register_job_custom_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION, + "receiver replication completion", "%", + WORKER_METRIC_ABSOLUTE); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_BUFFER_RATIO, + "sender used buffer ratio", "%", + WORKER_METRIC_ABSOLUTE); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_RECEIVED, + "sender bytes received", "bytes/s", + WORKER_METRIC_INCREMENT); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_SENT, + "sender bytes sent", "bytes/s", + WORKER_METRIC_INCREMENT); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_COMPRESSED, + "sender bytes compressed", "bytes/s", + WORKER_METRIC_INCREMENTAL_TOTAL); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_UNCOMPRESSED, + "sender bytes uncompressed", "bytes/s", + WORKER_METRIC_INCREMENTAL_TOTAL); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO, + "sender cumulative compression savings ratio", "%", + WORKER_METRIC_ABSOLUTE); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_REPLAY_DICT_SIZE, + "sender replication dict entries", "entries", + WORKER_METRIC_ABSOLUTE); + + worker_register_job_custom_metric(WORKER_SENDER_JOB_MESSAGES, + "ops processed", "messages", + WORKER_METRIC_INCREMENTAL_TOTAL); + + if(pipe(sth->pipe.fds) != 0) { + nd_log(NDLS_DAEMON, NDLP_ERR, "STREAM THREAD[%zu]: cannot create required pipe.", sth->id); + sth->pipe.fds[PIPE_READ] = -1; + sth->pipe.fds[PIPE_WRITE] = -1; + return NULL; + } + + sth->tid = gettid_cached(); + + sth->pipe.size = set_pipe_size(sth->pipe.fds[PIPE_READ], 65536 * sizeof(*sth->pipe.buffer)) / sizeof(*sth->pipe.buffer); + sth->pipe.buffer = mallocz(sth->pipe.size * sizeof(*sth->pipe.buffer)); + + usec_t last_check_all_nodes_ut = 0; + usec_t last_dequeue_ut = 0; + + sth->run.pipe = (struct pollfd_meta){ + .type = POLLFD_TYPE_PIPE, + }; + sth->run.ndpl = nd_poll_create(); + if(!sth->run.ndpl) + fatal("Cannot create nd_poll()"); + + if(!nd_poll_add(sth->run.ndpl, sth->pipe.fds[PIPE_READ], ND_POLL_READ, &sth->run.pipe)) + internal_fatal(true, "Failed to add pipe to nd_poll()"); + + bool exit_thread = false; + size_t replay_entries = 0; + sth->snd.bytes_received = 0; + sth->snd.bytes_sent = 0; + + while(!exit_thread && !nd_thread_signaled_to_cancel() && service_running(SERVICE_STREAMING)) { + usec_t now_ut = now_monotonic_usec(); + + if(now_ut - last_dequeue_ut >= 100 * USEC_PER_MS) { + worker_is_busy(WORKER_STREAM_JOB_DEQUEUE); + + // move any pending hosts in the inbound queue, to the running list + spinlock_lock(&sth->queue.spinlock); + stream_thread_messages_resize_unsafe(sth); + stream_receiver_move_queue_to_running_unsafe(sth); + stream_sender_move_queue_to_running_unsafe(sth); + spinlock_unlock(&sth->queue.spinlock); + last_dequeue_ut = now_ut; + } + + if(now_ut - last_check_all_nodes_ut >= USEC_PER_SEC) { + worker_is_busy(WORKER_STREAM_JOB_LIST); + + // periodically check the entire list of nodes + // this detects unresponsive parents too (timeout) + stream_sender_check_all_nodes_from_poll(sth); + worker_set_metric(WORKER_SENDER_JOB_MESSAGES, (NETDATA_DOUBLE)(sth->messages.processed)); + worker_set_metric(WORKER_STREAM_METRIC_NODES, (NETDATA_DOUBLE)sth->nodes_count); + + worker_set_metric(WORKER_SENDER_JOB_BYTES_RECEIVED, (NETDATA_DOUBLE)sth->snd.bytes_received); + worker_set_metric(WORKER_SENDER_JOB_BYTES_SENT, (NETDATA_DOUBLE)sth->snd.bytes_sent); + worker_set_metric(WORKER_SENDER_JOB_REPLAY_DICT_SIZE, (NETDATA_DOUBLE)replay_entries); + replay_entries = 0; + sth->snd.bytes_received = 0; + sth->snd.bytes_sent = 0; + + last_check_all_nodes_ut = now_ut; + } + + worker_is_idle(); + + nd_poll_result_t ev; + int poll_rc = nd_poll_wait(sth->run.ndpl, 100, &ev); + + worker_is_busy(WORKER_STREAM_JOB_PREP); + + if (poll_rc == 0) + // nd_poll() timed out - just loop again + continue; + + if(unlikely(poll_rc == -1)) { + // nd_poll() returned an error + internal_fatal(true, "nd_poll() failed"); + worker_is_busy(WORKER_STREAM_JOB_POLL_ERROR); + nd_log_limit_static_thread_var(erl, 1, 1 * USEC_PER_MS); + nd_log_limit(&erl, NDLS_DAEMON, NDLP_ERR, "STREAM THREAD[%zu] poll() returned error", sth->id); + continue; + } + + time_t now_s = now_monotonic_sec(); + + if(nd_thread_signaled_to_cancel() || !service_running(SERVICE_STREAMING)) + break; + + exit_thread = stream_thread_process_poll_slot(sth, &ev, now_s, &replay_entries); + } + + // dequeue + spinlock_lock(&sth->queue.spinlock); + stream_sender_move_queue_to_running_unsafe(sth); + stream_receiver_move_queue_to_running_unsafe(sth); + spinlock_unlock(&sth->queue.spinlock); + + // cleanup receiver and dispatcher + stream_sender_cleanup(sth); + stream_receiver_cleanup(sth); + + // cleanup the thread structures + spinlock_lock(&sth->messages.spinlock); + freez(sth->messages.array); + sth->messages.array = NULL; + sth->messages.size = 0; + sth->messages.used = 0; + spinlock_unlock(&sth->messages.spinlock); + + freez(sth->pipe.buffer); + sth->pipe.buffer = NULL; + sth->pipe.size = 0; + + nd_poll_destroy(sth->run.ndpl); + sth->run.ndpl = NULL; + + close(sth->pipe.fds[PIPE_READ]); + close(sth->pipe.fds[PIPE_WRITE]); + sth->pipe.fds[PIPE_READ] = -1; + sth->pipe.fds[PIPE_WRITE] = -1; + + sth->thread = NULL; + sth->tid = 0; + + worker_unregister(); + + return NULL; +} + +// -------------------------------------------------------------------------------------------------------------------- + +void stream_thread_node_queued(RRDHOST *host) { + spinlock_lock(&stream_thread_globals.assign.spinlock); + host->stream.refcount++; + internal_fatal(host->stream.refcount > 2, "invalid stream refcount %u (while adding node)", host->stream.refcount); + spinlock_unlock(&stream_thread_globals.assign.spinlock); +} + +void stream_thread_node_removed(RRDHOST *host) { + spinlock_lock(&stream_thread_globals.assign.spinlock); + internal_fatal(!host->stream.refcount, "invalid stream refcount %u (while stopping node)", host->stream.refcount); + + if(--host->stream.refcount == 0) { + struct stream_thread *sth = host->stream.thread; + sth->nodes_count--; + host->stream.thread = NULL; + } + + spinlock_unlock(&stream_thread_globals.assign.spinlock); +} + +static struct stream_thread *stream_thread_get_unsafe(RRDHOST *host) { + if(host->stream.thread) + return host->stream.thread; + + if(!stream_thread_globals.assign.cores) { + stream_thread_globals.assign.cores = get_netdata_cpus() - 1; + if(stream_thread_globals.assign.cores < 4) + stream_thread_globals.assign.cores = 4; + else if(stream_thread_globals.assign.cores > STREAM_MAX_THREADS) + stream_thread_globals.assign.cores = STREAM_MAX_THREADS; + } + + size_t selected_thread_slot = 0; + size_t min_nodes = stream_thread_globals.threads[0].nodes_count; + for(size_t i = 1; i < stream_thread_globals.assign.cores ; i++) { + if(stream_thread_globals.threads[i].nodes_count < min_nodes) { + selected_thread_slot = i; + min_nodes = stream_thread_globals.threads[i].nodes_count; + } + } + + struct stream_thread *sth = host->stream.thread = &stream_thread_globals.threads[selected_thread_slot]; + host->stream.refcount = 0; + sth->nodes_count++; + + return host->stream.thread; +} + +static struct stream_thread * stream_thread_assign_and_start(RRDHOST *host) { + spinlock_lock(&stream_thread_globals.assign.spinlock); + + struct stream_thread *sth = stream_thread_get_unsafe(host); + + if(!sth->thread) { + sth->id = (sth - stream_thread_globals.threads); // find the slot number + if(&stream_thread_globals.threads[sth->id] != sth) + fatal("STREAM THREAD[x] [%s]: thread id and slot do not match!", rrdhost_hostname(host)); + + sth->pipe.fds[PIPE_READ] = -1; + sth->pipe.fds[PIPE_WRITE] = -1; + spinlock_init(&sth->pipe.spinlock); + spinlock_init(&sth->queue.spinlock); + spinlock_init(&sth->messages.spinlock); + sth->messages.used = 0; + + char tag[NETDATA_THREAD_TAG_MAX + 1]; + snprintfz(tag, NETDATA_THREAD_TAG_MAX, THREAD_TAG_STREAM "[%zu]", sth->id); + + sth->thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_DEFAULT, stream_thread, sth); + if (!sth->thread) + nd_log_daemon(NDLP_ERR, "STREAM THREAD[%zu]: failed to create new thread for client.", sth->id); + } + + spinlock_unlock(&stream_thread_globals.assign.spinlock); + + return sth; +} + +void stream_sender_add_to_connector_queue(RRDHOST *host) { + ND_LOG_STACK lgs[] = { + ND_LOG_FIELD_STR(NDF_NIDL_NODE, host->hostname), + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &streaming_to_parent_msgid), + ND_LOG_FIELD_END(), + }; + ND_LOG_STACK_PUSH(lgs); + + stream_connector_init(host->sender); + rrdhost_stream_parent_ssl_init(host->sender); + stream_connector_add(host->sender); +} + +void stream_receiver_add_to_queue(struct receiver_state *rpt) { + struct stream_thread *sth = stream_thread_assign_and_start(rpt->host); + + stream_thread_node_queued(rpt->host); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM RECEIVE[%zu] [%s]: moving host to receiver queue...", + sth->id, rrdhost_hostname(rpt->host)); + + spinlock_lock(&sth->queue.spinlock); + internal_fatal(RECEIVERS_GET(&sth->queue.receivers, (Word_t)rpt) != NULL, "Receiver is already in the receivers queue"); + RECEIVERS_SET(&sth->queue.receivers, (Word_t)rpt, rpt); + spinlock_unlock(&sth->queue.spinlock); +} + +void stream_sender_add_to_queue(struct sender_state *s) { + struct stream_thread *sth = stream_thread_assign_and_start(s->host); + + stream_thread_node_queued(s->host); + + nd_log(NDLS_DAEMON, NDLP_DEBUG, + "STREAM THREAD[%zu] [%s]: moving host to dispatcher queue...", + sth->id, rrdhost_hostname(s->host)); + + spinlock_lock(&sth->queue.spinlock); + internal_fatal(SENDERS_GET(&sth->queue.senders, (Word_t)s) != NULL, "Sender is already in the senders queue"); + SENDERS_SET(&sth->queue.senders, (Word_t)s, s); + spinlock_unlock(&sth->queue.spinlock); +} + +void stream_threads_cancel(void) { + stream_connector_cancel_threads(); + for(size_t i = 0; i < STREAM_MAX_THREADS ;i++) + nd_thread_signal_cancel(stream_thread_globals.threads[i].thread); +} + +struct stream_thread *stream_thread_by_slot_id(size_t thread_slot) { + if(thread_slot < STREAM_MAX_THREADS && stream_thread_globals.threads[thread_slot].thread) + return &stream_thread_globals.threads[thread_slot]; + + return NULL; +} diff --git a/src/streaming/stream-thread.h b/src/streaming/stream-thread.h new file mode 100644 index 00000000000000..8f6c8a9dd9e6fc --- /dev/null +++ b/src/streaming/stream-thread.h @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_STREAM_THREAD_H +#define NETDATA_STREAM_THREAD_H + +#include "libnetdata/libnetdata.h" + +struct stream_thread; +struct pollfd_slotted { + struct stream_thread *sth; + int32_t slot; + int fd; +}; + +#define PFD_EMPTY (struct pollfd_slotted){ .sth = NULL, .fd = -1, .slot = -1, } + +typedef enum __attribute__((packed)) { + STREAM_OPCODE_NONE = 0, + STREAM_OPCODE_SENDER_POLLOUT = (1 << 0), // move traffic around as soon as possible + STREAM_OPCODE_SENDER_BUFFER_OVERFLOW = (1 << 1), // reconnect the node, it has buffer overflow + STREAM_OPCODE_SENDER_RECONNECT_WITHOUT_COMPRESSION = (1 << 2), // reconnect the node, but disable compression + STREAM_OPCODE_SENDER_STOP_RECEIVER_LEFT = (1 << 3), // disconnect the node, the receiver left + STREAM_OPCODE_SENDER_STOP_HOST_CLEANUP = (1 << 4), // disconnect the node, it is being de-allocated +} STREAM_OPCODE; + +struct stream_opcode { + int32_t thread_slot; // the dispatcher id this message refers to + uint32_t session; // random number used to verify that the message the dispatcher receives is for this sender + STREAM_OPCODE opcode; // the actual message to be delivered + struct sender_state *sender; +}; + +// IMPORTANT: to add workers, you have to edit WORKER_PARSER_FIRST_JOB accordingly + +// stream thread events +#define WORKER_STREAM_JOB_LIST (WORKER_PARSER_FIRST_JOB - 34) +#define WORKER_STREAM_JOB_DEQUEUE (WORKER_PARSER_FIRST_JOB - 33) +#define WORKER_STREAM_JOB_PREP (WORKER_PARSER_FIRST_JOB - 32) +#define WORKER_STREAM_JOB_POLL_ERROR (WORKER_PARSER_FIRST_JOB - 31) +#define WORKER_SENDER_JOB_PIPE_READ (WORKER_PARSER_FIRST_JOB - 30) + +// socket operations +#define WORKER_STREAM_JOB_SOCKET_RECEIVE (WORKER_PARSER_FIRST_JOB - 29) +#define WORKER_STREAM_JOB_SOCKET_SEND (WORKER_PARSER_FIRST_JOB - 28) +#define WORKER_STREAM_JOB_SOCKET_ERROR (WORKER_PARSER_FIRST_JOB - 27) + +// compression +#define WORKER_STREAM_JOB_COMPRESS (WORKER_PARSER_FIRST_JOB - 26) +#define WORKER_STREAM_JOB_DECOMPRESS (WORKER_PARSER_FIRST_JOB - 25) + +// receiver events +#define WORKER_RECEIVER_JOB_BYTES_READ (WORKER_PARSER_FIRST_JOB - 24) +#define WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED (WORKER_PARSER_FIRST_JOB - 23) + +// sender received commands +#define WORKER_SENDER_JOB_EXECUTE (WORKER_PARSER_FIRST_JOB - 22) +#define WORKER_SENDER_JOB_EXECUTE_REPLAY (WORKER_PARSER_FIRST_JOB - 21) +#define WORKER_SENDER_JOB_EXECUTE_FUNCTION (WORKER_PARSER_FIRST_JOB - 20) +#define WORKER_SENDER_JOB_EXECUTE_META (WORKER_PARSER_FIRST_JOB - 19) + +#define WORKER_SENDER_JOB_DISCONNECT_OVERFLOW (WORKER_PARSER_FIRST_JOB - 18) +#define WORKER_SENDER_JOB_DISCONNECT_TIMEOUT (WORKER_PARSER_FIRST_JOB - 17) +#define WORKER_SENDER_JOB_DISCONNECT_SOCKET_ERROR (WORKER_PARSER_FIRST_JOB - 16) +#define WORKER_SENDER_JOB_DISCONNECT_PARENT_CLOSED (WORKER_PARSER_FIRST_JOB - 15) +#define WORKER_SENDER_JOB_DISCONNECT_RECEIVE_ERROR (WORKER_PARSER_FIRST_JOB - 14) +#define WORKER_SENDER_JOB_DISCONNECT_SEND_ERROR (WORKER_PARSER_FIRST_JOB - 13) +#define WORKER_SENDER_JOB_DISCONNECT_COMPRESSION_ERROR (WORKER_PARSER_FIRST_JOB - 12) +#define WORKER_SENDER_JOB_DISCONNECT_RECEIVER_LEFT (WORKER_PARSER_FIRST_JOB - 11) +#define WORKER_SENDER_JOB_DISCONNECT_HOST_CLEANUP (WORKER_PARSER_FIRST_JOB - 10) + +// dispatcher metrics +// this has to be the same at pluginsd_parser.h +#define WORKER_RECEIVER_JOB_REPLICATION_COMPLETION (WORKER_PARSER_FIRST_JOB - 9) +#define WORKER_STREAM_METRIC_NODES (WORKER_PARSER_FIRST_JOB - 8) +#define WORKER_SENDER_JOB_BUFFER_RATIO (WORKER_PARSER_FIRST_JOB - 7) +#define WORKER_SENDER_JOB_BYTES_RECEIVED (WORKER_PARSER_FIRST_JOB - 6) +#define WORKER_SENDER_JOB_BYTES_SENT (WORKER_PARSER_FIRST_JOB - 5) +#define WORKER_SENDER_JOB_BYTES_COMPRESSED (WORKER_PARSER_FIRST_JOB - 4) +#define WORKER_SENDER_JOB_BYTES_UNCOMPRESSED (WORKER_PARSER_FIRST_JOB - 3) +#define WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO (WORKER_PARSER_FIRST_JOB - 2) +#define WORKER_SENDER_JOB_REPLAY_DICT_SIZE (WORKER_PARSER_FIRST_JOB - 1) +#define WORKER_SENDER_JOB_MESSAGES (WORKER_PARSER_FIRST_JOB - 0) + +#if WORKER_UTILIZATION_MAX_JOB_TYPES < 35 +#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 34 +#endif + +#define STREAM_MAX_THREADS 2048 +#define THREAD_TAG_STREAM "STREAM" + +typedef enum { + POLLFD_TYPE_EMPTY, + POLLFD_TYPE_SENDER, + POLLFD_TYPE_RECEIVER, + POLLFD_TYPE_PIPE, +} POLLFD_TYPE; + +struct pollfd_meta { + POLLFD_TYPE type; + union { + struct receiver_state *rpt; + struct sender_state *s; + }; +}; + +DEFINE_JUDYL_TYPED(SENDERS, struct sender_state *); +DEFINE_JUDYL_TYPED(RECEIVERS, struct receiver_state *); + +struct stream_thread { + ND_THREAD *thread; + + pid_t tid; + size_t id; + size_t nodes_count; + + struct { + SENDERS_JudyLSet senders; + size_t bytes_received; + size_t bytes_sent; + } snd; + + struct { + RECEIVERS_JudyLSet receivers; + size_t bytes_received; + size_t bytes_received_uncompressed; + NETDATA_DOUBLE replication_completion; + } rcv; + + struct { + SPINLOCK spinlock; // ensure a single writer at a time + int fds[2]; + size_t size; + char *buffer; + } pipe; + + struct { + // the incoming queue of the dispatcher thread + // the connector thread leaves the connected senders in this list, for the dispatcher to pick them up + SPINLOCK spinlock; + SENDERS_JudyLSet senders; + RECEIVERS_JudyLSet receivers; + } queue; + + struct { + SPINLOCK spinlock; + size_t added; + size_t processed; + size_t bypassed; + size_t size; + size_t used; + struct stream_opcode *array; // the array of messages from the senders + struct stream_opcode *copy; // a copy of the array of messages from the senders, to work on + } messages; + + struct { + nd_poll_t *ndpl; + struct pollfd_meta pipe; + } run; +}; + +struct stream_thread_globals { + struct { + SPINLOCK spinlock; + size_t id; + size_t cores; + } assign; + + struct stream_thread threads[STREAM_MAX_THREADS]; +}; + +struct rrdhost; +extern struct stream_thread_globals stream_thread_globals; + +void stream_sender_move_queue_to_running_unsafe(struct stream_thread *sth); +void stream_receiver_move_queue_to_running_unsafe(struct stream_thread *sth); +void stream_sender_check_all_nodes_from_poll(struct stream_thread *sth); + +void stream_receiver_add_to_queue(struct receiver_state *rpt); +void stream_sender_add_to_connector_queue(struct rrdhost *host); + +void stream_sender_process_poll_events(struct stream_thread *sth, struct sender_state *s, nd_poll_event_t events, time_t now_s); +void stream_receive_process_poll_events(struct stream_thread *sth, struct receiver_state *rpt, nd_poll_event_t events, time_t now_s); + +void stream_sender_cleanup(struct stream_thread *sth); +void stream_receiver_cleanup(struct stream_thread *sth); +void stream_sender_handle_op(struct stream_thread *sth, struct sender_state *s, struct stream_opcode *msg); + +struct stream_thread *stream_thread_by_slot_id(size_t thread_slot); + +void stream_thread_node_queued(struct rrdhost *host); +void stream_thread_node_removed(struct rrdhost *host); + +#include "stream-sender-internals.h" +#include "stream-receiver-internals.h" +#include "plugins.d/pluginsd_parser.h" + +static inline bool rrdhost_is_this_a_stream_thread(RRDHOST *host) { + pid_t tid = gettid_cached(); + return host->stream.rcv.status.tid == tid || host->stream.snd.status.tid == tid; +} + +#endif //NETDATA_STREAM_THREAD_H diff --git a/src/streaming/stream-traffic-types.h b/src/streaming/stream-traffic-types.h new file mode 100644 index 00000000000000..4871c26a519f16 --- /dev/null +++ b/src/streaming/stream-traffic-types.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_STREAM_TRAFFIC_TYPES_H +#define NETDATA_STREAM_TRAFFIC_TYPES_H + +typedef enum __attribute__((packed)) { + STREAM_TRAFFIC_TYPE_REPLICATION = 0, + STREAM_TRAFFIC_TYPE_FUNCTIONS, + STREAM_TRAFFIC_TYPE_METADATA, + STREAM_TRAFFIC_TYPE_DATA, + + // terminator + STREAM_TRAFFIC_TYPE_MAX, +} STREAM_TRAFFIC_TYPE; + +#endif //NETDATA_STREAM_TRAFFIC_TYPES_H diff --git a/src/streaming/stream.conf b/src/streaming/stream.conf index 659bd830df56ff..7232b3a7ab9120 100644 --- a/src/streaming/stream.conf +++ b/src/streaming/stream.conf @@ -62,7 +62,7 @@ #enable compression = yes # The timeout to connect and send metrics - #timeout = 1m + #timeout = 5m # If the destination line above does not specify a port, use this #default port = 19999 @@ -83,8 +83,8 @@ #buffer size bytes = 10485760 # If the connection fails, or it disconnects, - # retry after that many seconds. - #reconnect delay = 5s + # retry after that many seconds (randomized from 5s to whatever is here). + #reconnect delay = 15s # Sync the clock of the charts for that many iterations, when starting. # It is ignored when replication is enabled @@ -149,7 +149,7 @@ # Health monitoring will be disabled as soon as the connection is closed. # You can also set it per host, below. # The default is taken from [health].enabled of netdata.conf - #health enabled by default = auto + #health enabled = auto # postpone alerts for a short period after the sender is connected #postpone alerts on connect = 1m @@ -176,7 +176,7 @@ # Enable replication for all hosts using this api key. Default: enabled #enable replication = yes - # How many seconds to replicate from each child. Default: a day + # How many seconds to replicate from each child. Default: configured in netdata.conf (1d) #replication period = 1d # The duration we want to replicate per each step. @@ -249,10 +249,10 @@ #enable compression = yes # Replication - # Enable replication for all hosts using this api key. + # Enable replication for this child. #enable replication = yes - # How many seconds to replicate from each child. + # How many seconds to replicate from this child. #replication period = 1d # The duration we want to replicate per each step. diff --git a/src/streaming/stream.h b/src/streaming/stream.h new file mode 100644 index 00000000000000..27ed5493c232ae --- /dev/null +++ b/src/streaming/stream.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_STREAM_H +#define NETDATA_STREAM_H 1 + +#include "libnetdata/libnetdata.h" +#include "stream-traffic-types.h" + +struct rrdhost; +struct sender_state; +struct receiver_state; + +#include "stream-conf.h" +#include "stream-handshake.h" +#include "stream-capabilities.h" +#include "stream-parents.h" + +// starting and stopping senders +void *stream_sender_start_localhost(void *ptr); +void stream_sender_start_host(struct rrdhost *host); +void stream_sender_signal_to_stop_and_wait(struct rrdhost *host, STREAM_HANDSHAKE reason, bool wait); + +// managing host sender structures +void stream_sender_structures_init(RRDHOST *host, bool stream, STRING *parents, STRING *api_key, STRING *send_charts_matching); +void stream_sender_structures_free(struct rrdhost *host); + +// querying host sender information +bool stream_sender_is_connected_with_ssl(struct rrdhost *host); +bool stream_sender_has_compression(struct rrdhost *host); +bool stream_sender_has_capabilities(struct rrdhost *host, STREAM_CAPABILITIES capabilities); + +// receiver API +uint32_t stream_receivers_currently_connected(void); +struct web_client; +int stream_receiver_accept_connection(struct web_client *w, char *decoded_query_string, void *h2o_ctx); +bool receiver_has_capability(struct rrdhost *host, STREAM_CAPABILITIES caps); +void stream_receiver_free(struct receiver_state *rpt); +bool stream_receiver_signal_to_stop_and_wait(struct rrdhost *host, STREAM_HANDSHAKE reason); +char *stream_receiver_program_version_strdupz(struct rrdhost *host); + +#include "replication.h" +#include "rrdhost-status.h" +#include "protocol/commands.h" +#include "stream-path.h" + +void stream_threads_cancel(void); + +#endif //NETDATA_STREAM_H diff --git a/src/web/api/functions/function-streaming.c b/src/web/api/functions/function-streaming.c index 04bdd7619b57aa..4d8b926f0b26a1 100644 --- a/src/web/api/functions/function-streaming.c +++ b/src/web/api/functions/function-streaming.c @@ -80,7 +80,7 @@ int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER * buffer_json_add_array_item_string(wb, NULL); // InAge } buffer_json_add_array_item_string(wb, stream_handshake_error_to_string(s.ingest.reason)); // InReason - buffer_json_add_array_item_uint64(wb, s.ingest.hops); // InHops + buffer_json_add_array_item_int64(wb, s.ingest.hops); // InHops buffer_json_add_array_item_double(wb, s.ingest.replication.completion); // InReplCompletion buffer_json_add_array_item_uint64(wb, s.ingest.replication.instances); // InReplInstances buffer_json_add_array_item_string(wb, s.ingest.peers.local.ip); // InLocalIP @@ -120,13 +120,7 @@ int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER * buffer_json_add_array_item_uint64(wb, s.stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_FUNCTIONS]); buffer_json_add_array_item_array(wb); // OutAttemptHandshake - time_t last_attempt = 0; - for(struct rrdpush_destinations *d = host->destinations; d ; d = d->next) { - if(d->since > last_attempt) - last_attempt = d->since; - - buffer_json_add_array_item_string(wb, stream_handshake_error_to_string(d->reason)); - } + usec_t last_attempt = stream_parent_handshake_error_to_json(wb, host); buffer_json_array_close(wb); // // OutAttemptHandshake if(!last_attempt) { @@ -134,8 +128,8 @@ int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER * buffer_json_add_array_item_string(wb, NULL); // OutAttemptAge } else { - buffer_json_add_array_item_uint64(wb, last_attempt * 1000); // OutAttemptSince - buffer_json_add_array_item_time_t(wb, s.now - last_attempt); // OutAttemptAge + buffer_json_add_array_item_uint64(wb, last_attempt / USEC_PER_MS); // OutAttemptSince + buffer_json_add_array_item_time_t(wb, s.now - (time_t)(last_attempt / USEC_PER_SEC)); // OutAttemptAge } // ML diff --git a/src/web/api/queries/query.c b/src/web/api/queries/query.c index 6854300f3d71ea..005572f0c9a5e8 100644 --- a/src/web/api/queries/query.c +++ b/src/web/api/queries/query.c @@ -2001,7 +2001,7 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s long before_wanted = smaller_tier_last_time; struct rrddim_tier *tmp = &rd->tiers[read_tier]; - storage_engine_query_init(tmp->seb, tmp->smh, &seqh, after_wanted, before_wanted, STORAGE_PRIORITY_HIGH); + storage_engine_query_init(tmp->seb, tmp->smh, &seqh, after_wanted, before_wanted, STORAGE_PRIORITY_SYNCHRONOUS); size_t points_read = 0; @@ -2018,7 +2018,7 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s storage_engine_query_finalize(&seqh); store_metric_collection_completed(); - global_statistics_backfill_query_completed(points_read); + telemetry_queries_backfill_query_completed(points_read); //internal_error(true, "DBENGINE: backfilled chart '%s', dimension '%s', tier %d, from %ld to %ld, with %zu points from tier %d", // rd->rrdset->name, rd->name, tier, after_wanted, before_wanted, points, tr); @@ -3592,11 +3592,11 @@ RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) { continue; } - global_statistics_rrdr_query_completed( - 1, - r_tmp->stats.db_points_read - last_db_points_read, - r_tmp->stats.result_points_generated - last_result_points_generated, - qt->request.query_source); + telemetry_queries_rrdr_query_completed( + 1, + r_tmp->stats.db_points_read - last_db_points_read, + r_tmp->stats.result_points_generated - last_result_points_generated, + qt->request.query_source); last_db_points_read = r_tmp->stats.db_points_read; last_result_points_generated = r_tmp->stats.result_points_generated; diff --git a/src/web/api/v1/api_v1_info.c b/src/web/api/v1/api_v1_info.c index 2395cea5987252..7d64afb585280d 100644 --- a/src/web/api/v1/api_v1_info.c +++ b/src/web/api/v1/api_v1_info.c @@ -161,10 +161,9 @@ static int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) buffer_json_member_add_uint64(wb, "page-cache-size", default_rrdeng_page_cache_mb); #endif // ENABLE_DBENGINE buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE); - buffer_json_member_add_boolean(wb, "stream-enabled", stream_conf_send_enabled); + buffer_json_member_add_boolean(wb, "stream-enabled", stream_send.enabled); - buffer_json_member_add_boolean(wb, "stream-compression", - host->sender && host->sender->compressor.initialized); + buffer_json_member_add_boolean(wb, "stream-compression", stream_sender_has_compression(host)); buffer_json_member_add_boolean(wb, "https-enabled", true); diff --git a/src/web/api/v3/api_v3_calls.h b/src/web/api/v3/api_v3_calls.h index a13eb44efe9cad..6ddfda4790f96a 100644 --- a/src/web/api/v3/api_v3_calls.h +++ b/src/web/api/v3/api_v3_calls.h @@ -10,6 +10,7 @@ int api_v2_contexts_internal(RRDHOST *host, struct web_client *w, char *url, CON int api_v3_settings(RRDHOST *host, struct web_client *w, char *url); int api_v3_me(RRDHOST *host, struct web_client *w, char *url); +int api_v3_stream_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url); int api_v3_stream_path(RRDHOST *host __maybe_unused, struct web_client *w, char *url); #endif //NETDATA_API_V3_CALLS_H diff --git a/src/web/api/v3/api_v3_stream_info.c b/src/web/api/v3/api_v3_stream_info.c new file mode 100644 index 00000000000000..1763f63b2a9663 --- /dev/null +++ b/src/web/api/v3/api_v3_stream_info.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "api_v3_calls.h" + +int api_v3_stream_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) { + const char *machine_guid = NULL; + + while(url) { + char *value = strsep_skip_consecutive_separators(&url, "&"); + if(!value || !*value) continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + // name and value are now the parameters + // they are not null and not empty + + if(!strcmp(name, "machine_guid")) + machine_guid = value; + } + + return stream_info_to_json_v1(w->response.data, machine_guid); +} diff --git a/src/web/api/web_api_v3.c b/src/web/api/web_api_v3.c index f97b27aa85736f..aa23d9a16d9699 100644 --- a/src/web/api/web_api_v3.c +++ b/src/web/api/web_api_v3.c @@ -193,6 +193,15 @@ static struct web_api_command api_commands_v3[] = { .allow_subpaths = 0 }, + { + .api = "stream_info", + .hash = 0, + .acl = HTTP_ACL_NOCHECK, + .access = HTTP_ACCESS_NONE, + .callback = api_v3_stream_info, + .allow_subpaths = 0 + }, + // WebRTC APIs { .api = "rtc_offer", diff --git a/src/web/server/h2o/rrdpush.c b/src/web/server/h2o/rrdpush.c index 515ec8fd49a3b8..dd19342f27c45c 100644 --- a/src/web/server/h2o/rrdpush.c +++ b/src/web/server/h2o/rrdpush.c @@ -301,7 +301,7 @@ void stream_process(h2o_stream_conn_t *conn, int initial) w.client_ip[cpy_len - 1] = 0; w.user_agent = conn->user_agent; - rc = rrdpush_receiver_thread_spawn(&w, conn->url, conn); + rc = stream_receiver_accept_connection(&w, conn->url, conn); if (rc != HTTP_RESP_OK) { error_report("HTTPD Failed to spawn the receiver thread %d", rc); conn->state = STREAM_CLOSE; diff --git a/src/web/server/static/static-threaded.c b/src/web/server/static/static-threaded.c index 01c9817fbaa4e0..c874f56fd9ccde 100644 --- a/src/web/server/static/static-threaded.c +++ b/src/web/server/static/static-threaded.c @@ -123,7 +123,7 @@ static void web_server_file_del_callback(POLLINFO *pi) { web_server_log_connection(w, "DISCONNECTED"); web_client_request_done(w); web_client_release_to_cache(w); - global_statistics_web_client_disconnected(); + telemetry_web_client_disconnected(); } worker_is_idle(); @@ -269,7 +269,7 @@ static void web_server_del_callback(POLLINFO *pi) { web_server_log_connection(w, "DISCONNECTED"); web_client_request_done(w); web_client_release_to_cache(w); - global_statistics_web_client_disconnected(); + telemetry_web_client_disconnected(); } worker_is_idle(); diff --git a/src/web/server/web_client.c b/src/web/server/web_client.c index 8a79b61f0189d4..a688f95be0d4f0 100644 --- a/src/web/server/web_client.c +++ b/src/web/server/web_client.c @@ -225,11 +225,8 @@ void web_client_log_completed_request(struct web_client *w, bool update_web_stat size_t sent = w->response.zoutput ? (size_t)w->response.zstream.total_out : size; if(update_web_stats) - global_statistics_web_request_completed(dt_usec(&tv, &w->timings.tv_in), - w->statistics.received_bytes, - w->statistics.sent_bytes, - size, - sent); + telemetry_web_request_completed( + dt_usec(&tv, &w->timings.tv_in), w->statistics.received_bytes, w->statistics.sent_bytes, size, sent); usec_t prep_ut = w->timings.tv_ready.tv_sec ? dt_usec(&w->timings.tv_ready, &w->timings.tv_in) : 0; usec_t sent_ut = w->timings.tv_ready.tv_sec ? dt_usec(&tv, &w->timings.tv_ready) : 0; @@ -1346,7 +1343,8 @@ void web_client_process_request_from_web_server(struct web_client *w) { return; } - w->response.code = rrdpush_receiver_thread_spawn(w, (char *)buffer_tostring(w->url_query_string_decoded), NULL); + w->response.code = stream_receiver_accept_connection( + w, (char *)buffer_tostring(w->url_query_string_decoded), NULL); return; case HTTP_REQUEST_MODE_OPTIONS: diff --git a/src/web/server/web_client_cache.c b/src/web/server/web_client_cache.c index ebc428894670e5..2a0ade755a6917 100644 --- a/src/web/server/web_client_cache.c +++ b/src/web/server/web_client_cache.c @@ -103,7 +103,7 @@ struct web_client *web_client_get_from_cache(void) { w = web_client_create(&netdata_buffers_statistics.buffers_web); spinlock_lock(&web_clients_cache.used.spinlock); - w->id = global_statistics_web_client_connected(); + w->id = telemetry_web_client_connected(); web_clients_cache.used.allocated++; }