diff --git a/CMakeLists.txt b/CMakeLists.txt index dd803848ce..fe0dcb331e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -556,6 +556,19 @@ set(RRD_PLUGIN_FILES database/engine/pagecache.h database/engine/rrdenglocking.c database/engine/rrdenglocking.h + database/engine/metadata_log/metadatalog.c + database/engine/metadata_log/metadatalog.h + database/engine/metadata_log/metadatalogapi.c + database/engine/metadata_log/metadatalogapi.h + database/engine/metadata_log/logfile.h + database/engine/metadata_log/logfile.c + database/engine/metadata_log/metadatalogprotocol.h + database/engine/metadata_log/metalogpluginsd.c + database/engine/metadata_log/metalogpluginsd.h + database/engine/metadata_log/compaction.c + database/engine/metadata_log/compaction.h + database/engine/global_uuid_map/global_uuid_map.c + database/engine/global_uuid_map/global_uuid_map.h ) set(WEB_PLUGIN_FILES diff --git a/Makefile.am b/Makefile.am index 889a3d8569..9f72ede368 100644 --- a/Makefile.am +++ b/Makefile.am @@ -375,6 +375,19 @@ if ENABLE_DBENGINE database/engine/pagecache.h \ database/engine/rrdenglocking.c \ database/engine/rrdenglocking.h \ + database/engine/metadata_log/metadatalog.c \ + database/engine/metadata_log/metadatalog.h \ + database/engine/metadata_log/metadatalogapi.c \ + database/engine/metadata_log/metadatalogapi.h \ + database/engine/metadata_log/logfile.h \ + database/engine/metadata_log/logfile.c \ + database/engine/metadata_log/metadatalogprotocol.h \ + database/engine/metadata_log/metalogpluginsd.c \ + database/engine/metadata_log/metalogpluginsd.h \ + database/engine/metadata_log/compaction.c \ + database/engine/metadata_log/compaction.h \ + database/engine/global_uuid_map/global_uuid_map.c \ + database/engine/global_uuid_map/global_uuid_map.h \ $(NULL) endif diff --git a/aclk/agent_cloud_link.c b/aclk/agent_cloud_link.c index cc366a8332..7b5aad5c4a 100644 --- a/aclk/agent_cloud_link.c +++ b/aclk/agent_cloud_link.c @@ -680,6 +680,9 @@ static struct _collector *_add_collector(const char *hostname, const char *plugi void aclk_add_collector(const char *hostname, const char *plugin_name, const char *module_name) { struct _collector *tmp_collector; + if (unlikely(!netdata_ready)) { + return; + } COLLECTOR_LOCK; @@ -711,6 +714,9 @@ void aclk_add_collector(const char *hostname, const char *plugin_name, const cha void aclk_del_collector(const char *hostname, const char *plugin_name, const char *module_name) { struct _collector *tmp_collector; + if (unlikely(!netdata_ready)) { + return; + } COLLECTOR_LOCK; @@ -1752,7 +1758,7 @@ int aclk_send_info_metadata() debug(D_ACLK, "Metadata %s with info has %zu bytes", msg_id, local_buffer->len); buffer_sprintf(local_buffer, ", \n\t \"charts\" : "); - charts2json(localhost, local_buffer, 1); + charts2json(localhost, local_buffer, 1, 0); buffer_sprintf(local_buffer, "\n}\n}"); debug(D_ACLK, "Metadata %s with chart has %zu bytes", msg_id, local_buffer->len); @@ -1859,6 +1865,9 @@ int aclk_update_chart(RRDHOST *host, char *chart_name, ACLK_CMD aclk_cmd) UNUSED(chart_name); return 0; #else + if (unlikely(!netdata_ready)) + return 0; + if (!netdata_cloud_setting) return 0; @@ -1886,6 +1895,9 @@ int aclk_update_alarm(RRDHOST *host, ALARM_ENTRY *ae) { BUFFER *local_buffer = NULL; + if (unlikely(!netdata_ready)) + return 0; + if (host != localhost) return 0; diff --git a/collectors/diskspace.plugin/plugin_diskspace.c b/collectors/diskspace.plugin/plugin_diskspace.c index eab607d84f..374f7cdf1c 100644 --- a/collectors/diskspace.plugin/plugin_diskspace.c +++ b/collectors/diskspace.plugin/plugin_diskspace.c @@ -254,7 +254,7 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) { netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) { if(unlikely(!m->st_space)) { m->do_space = CONFIG_BOOLEAN_YES; - m->st_space = rrdset_find_bytype_localhost("disk_space", disk); + m->st_space = rrdset_find_active_bytype_localhost("disk_space", disk); if(unlikely(!m->st_space)) { char title[4096 + 1]; snprintfz(title, 4096, "Disk Space Usage for %s [%s]", family, mi->mount_source); @@ -296,7 +296,7 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) { netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) { if(unlikely(!m->st_inodes)) { m->do_inodes = CONFIG_BOOLEAN_YES; - m->st_inodes = rrdset_find_bytype_localhost("disk_inodes", disk); + m->st_inodes = rrdset_find_active_bytype_localhost("disk_inodes", disk); if(unlikely(!m->st_inodes)) { char title[4096 + 1]; snprintfz(title, 4096, "Disk Files (inodes) Usage for %s [%s]", family, mi->mount_source); diff --git a/collectors/freebsd.plugin/plugin_freebsd.c b/collectors/freebsd.plugin/plugin_freebsd.c index 5cde371131..bee8395f53 100644 --- a/collectors/freebsd.plugin/plugin_freebsd.c +++ b/collectors/freebsd.plugin/plugin_freebsd.c @@ -129,7 +129,7 @@ void *freebsd_main(void *ptr) { static RRDSET *st = NULL; if(unlikely(!st)) { - st = rrdset_find_bytype_localhost("netdata", "plugin_freebsd_modules"); + st = rrdset_find_active_bytype_localhost("netdata", "plugin_freebsd_modules"); if(!st) { st = rrdset_create_localhost( diff --git a/collectors/macos.plugin/macos_fw.c b/collectors/macos.plugin/macos_fw.c index f253489a5b..d0b3e0fd2c 100644 --- a/collectors/macos.plugin/macos_fw.c +++ b/collectors/macos.plugin/macos_fw.c @@ -145,7 +145,7 @@ int do_macos_iokit(int update_every, usec_t dt) { total_disk_writes += diskstat.bytes_write; } - st = rrdset_find_bytype_localhost("disk", diskstat.name); + st = rrdset_find_active_bytype_localhost("disk", diskstat.name); if (unlikely(!st)) { st = rrdset_create_localhost( "disk" @@ -183,7 +183,7 @@ int do_macos_iokit(int update_every, usec_t dt) { CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.writes); } - st = rrdset_find_bytype_localhost("disk_ops", diskstat.name); + st = rrdset_find_active_bytype_localhost("disk_ops", diskstat.name); if (unlikely(!st)) { st = rrdset_create_localhost( "disk_ops" @@ -222,7 +222,7 @@ int do_macos_iokit(int update_every, usec_t dt) { CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.time_write); } - st = rrdset_find_bytype_localhost("disk_util", diskstat.name); + st = rrdset_find_active_bytype_localhost("disk_util", diskstat.name); if (unlikely(!st)) { st = rrdset_create_localhost( "disk_util" @@ -260,7 +260,7 @@ int do_macos_iokit(int update_every, usec_t dt) { CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.latency_write); } - st = rrdset_find_bytype_localhost("disk_iotime", diskstat.name); + st = rrdset_find_active_bytype_localhost("disk_iotime", diskstat.name); if (unlikely(!st)) { st = rrdset_create_localhost( "disk_iotime" @@ -297,7 +297,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("disk_await", diskstat.name); + st = rrdset_find_active_bytype_localhost("disk_await", diskstat.name); if (unlikely(!st)) { st = rrdset_create_localhost( "disk_await" @@ -328,7 +328,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("disk_avgsz", diskstat.name); + st = rrdset_find_active_bytype_localhost("disk_avgsz", diskstat.name); if (unlikely(!st)) { st = rrdset_create_localhost( "disk_avgsz" @@ -359,7 +359,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("disk_svctm", diskstat.name); + st = rrdset_find_active_bytype_localhost("disk_svctm", diskstat.name); if (unlikely(!st)) { st = rrdset_create_localhost( "disk_svctm" @@ -401,7 +401,7 @@ int do_macos_iokit(int update_every, usec_t dt) { } if (likely(do_io)) { - st = rrdset_find_bytype_localhost("system", "io"); + st = rrdset_find_active_bytype_localhost("system", "io"); if (unlikely(!st)) { st = rrdset_create_localhost( "system" @@ -453,7 +453,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------------- if (likely(do_space)) { - st = rrdset_find_bytype_localhost("disk_space", mntbuf[i].f_mntonname); + st = rrdset_find_active_bytype_localhost("disk_space", mntbuf[i].f_mntonname); if (unlikely(!st)) { snprintfz(title, 4096, "Disk Space Usage for %s [%s]", mntbuf[i].f_mntonname, mntbuf[i].f_mntfromname); st = rrdset_create_localhost( @@ -486,7 +486,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------------- if (likely(do_inodes)) { - st = rrdset_find_bytype_localhost("disk_inodes", mntbuf[i].f_mntonname); + st = rrdset_find_active_bytype_localhost("disk_inodes", mntbuf[i].f_mntonname); if (unlikely(!st)) { snprintfz(title, 4096, "Disk Files (inodes) Usage for %s [%s]", mntbuf[i].f_mntonname, mntbuf[i].f_mntfromname); st = rrdset_create_localhost( @@ -533,7 +533,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("net", ifa->ifa_name); + st = rrdset_find_active_bytype_localhost("net", ifa->ifa_name); if (unlikely(!st)) { st = rrdset_create_localhost( "net" @@ -561,7 +561,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("net_packets", ifa->ifa_name); + st = rrdset_find_active_bytype_localhost("net_packets", ifa->ifa_name); if (unlikely(!st)) { st = rrdset_create_localhost( "net_packets" @@ -594,7 +594,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("net_errors", ifa->ifa_name); + st = rrdset_find_active_bytype_localhost("net_errors", ifa->ifa_name); if (unlikely(!st)) { st = rrdset_create_localhost( "net_errors" @@ -623,7 +623,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("net_drops", ifa->ifa_name); + st = rrdset_find_active_bytype_localhost("net_drops", ifa->ifa_name); if (unlikely(!st)) { st = rrdset_create_localhost( "net_drops" @@ -650,7 +650,7 @@ int do_macos_iokit(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("net_events", ifa->ifa_name); + st = rrdset_find_active_bytype_localhost("net_events", ifa->ifa_name); if (unlikely(!st)) { st = rrdset_create_localhost( "net_events" diff --git a/collectors/macos.plugin/macos_mach_smi.c b/collectors/macos.plugin/macos_mach_smi.c index 800b2ce56b..250186cef7 100644 --- a/collectors/macos.plugin/macos_mach_smi.c +++ b/collectors/macos.plugin/macos_mach_smi.c @@ -55,7 +55,7 @@ int do_macos_mach_smi(int update_every, usec_t dt) { error("DISABLED: system.cpu"); } else { - st = rrdset_find_bytype_localhost("system", "cpu"); + st = rrdset_find_active_bytype_localhost("system", "cpu"); if (unlikely(!st)) { st = rrdset_create_localhost( "system" diff --git a/collectors/macos.plugin/macos_sysctl.c b/collectors/macos.plugin/macos_sysctl.c index dddafc9f51..80b66963b5 100644 --- a/collectors/macos.plugin/macos_sysctl.c +++ b/collectors/macos.plugin/macos_sysctl.c @@ -230,7 +230,7 @@ int do_macos_sysctl(int update_every, usec_t dt) { error("DISABLED: system.load"); } else { - st = rrdset_find_bytype_localhost("system", "load"); + st = rrdset_find_active_bytype_localhost("system", "load"); if (unlikely(!st)) { st = rrdset_create_localhost( "system" diff --git a/collectors/plugins.d/plugins_d.h b/collectors/plugins.d/plugins_d.h index d8bb1b955f..fd99b35843 100644 --- a/collectors/plugins.d/plugins_d.h +++ b/collectors/plugins.d/plugins_d.h @@ -31,10 +31,10 @@ #define PLUGINSD_KEYWORD_VARIABLE "VARIABLE" #define PLUGINSD_KEYWORD_LABEL "LABEL" #define PLUGINSD_KEYWORD_OVERWRITE "OVERWRITE" -#define PLUGINSD_KEYWORD_CONTEXT "CONTEXT" #define PLUGINSD_KEYWORD_GUID "GUID" -#define PLUGINSD_KEYWORD_HOST "HOST" +#define PLUGINSD_KEYWORD_CONTEXT "CONTEXT" #define PLUGINSD_KEYWORD_TOMBSTONE "TOMBSTONE" +#define PLUGINSD_KEYWORD_HOST "HOST" #define PLUGINSD_LINE_MAX 1024 diff --git a/collectors/plugins.d/pluginsd_parser.c b/collectors/plugins.d/pluginsd_parser.c index 90558f7e58..ce5bf5fcf0 100644 --- a/collectors/plugins.d/pluginsd_parser.c +++ b/collectors/plugins.d/pluginsd_parser.c @@ -561,6 +561,71 @@ PARSER_RC pluginsd_overwrite(char **words, void *user, PLUGINSD_ACTION *plugins return PARSER_RC_OK; } +PARSER_RC pluginsd_guid(char **words, void *user, PLUGINSD_ACTION *plugins_action) +{ + char *uuid_str = words[1]; + uuid_t uuid; + + if (unlikely(!uuid_str)) { + error("requested a GUID, without a uuid."); + return PARSER_RC_ERROR; + } + if (unlikely(strlen(uuid_str) != GUID_LEN || uuid_parse(uuid_str, uuid) == -1)) { + error("requested a GUID, without a valid uuid string."); + return PARSER_RC_ERROR; + } + + debug(D_PLUGINSD, "Parsed uuid=%s", uuid_str); + if (plugins_action->guid_action) { + return plugins_action->guid_action(user, &uuid); + } + + return PARSER_RC_OK; +} + +PARSER_RC pluginsd_context(char **words, void *user, PLUGINSD_ACTION *plugins_action) +{ + char *uuid_str = words[1]; + uuid_t uuid; + + if (unlikely(!uuid_str)) { + error("requested a CONTEXT, without a uuid."); + return PARSER_RC_ERROR; + } + if (unlikely(strlen(uuid_str) != GUID_LEN || uuid_parse(uuid_str, uuid) == -1)) { + error("requested a CONTEXT, without a valid uuid string."); + return PARSER_RC_ERROR; + } + + debug(D_PLUGINSD, "Parsed uuid=%s", uuid_str); + if (plugins_action->context_action) { + return plugins_action->context_action(user, &uuid); + } + + return PARSER_RC_OK; +} + +PARSER_RC pluginsd_tombstone(char **words, void *user, PLUGINSD_ACTION *plugins_action) +{ + char *uuid_str = words[1]; + uuid_t uuid; + + if (unlikely(!uuid_str)) { + error("requested a TOMBSTONE, without a uuid."); + return PARSER_RC_ERROR; + } + if (unlikely(strlen(uuid_str) != GUID_LEN || uuid_parse(uuid_str, uuid) == -1)) { + error("requested a TOMBSTONE, without a valid uuid string."); + return PARSER_RC_ERROR; + } + + debug(D_PLUGINSD, "Parsed uuid=%s", uuid_str); + if (plugins_action->tombstone_action) { + return plugins_action->tombstone_action(user, &uuid); + } + + return PARSER_RC_OK; +} // New plugins.d parser diff --git a/collectors/plugins.d/pluginsd_parser.h b/collectors/plugins.d/pluginsd_parser.h index ea9ef40b58..ba79373cd6 100644 --- a/collectors/plugins.d/pluginsd_parser.h +++ b/collectors/plugins.d/pluginsd_parser.h @@ -16,19 +16,23 @@ typedef struct parser_user_object { struct label *new_labels; size_t count; int enabled; + void *private; // the user can set this for private use } PARSER_USER_OBJECT; -PARSER_RC pluginsd_set_action(void *user, RRDSET *st, RRDDIM *rd, long long int value); -PARSER_RC pluginsd_flush_action(void *user, RRDSET *st); -PARSER_RC pluginsd_begin_action(void *user, RRDSET *st, usec_t microseconds, int trust_durations); -PARSER_RC pluginsd_end_action(void *user, RRDSET *st); -PARSER_RC pluginsd_chart_action(void *user, char *type, char *id, char *name, char *family, char *context, char *title, char *units, char *plugin, - char *module, int priority, int update_every, RRDSET_TYPE chart_type, char *options); -PARSER_RC pluginsd_disable_action(void *user); -PARSER_RC pluginsd_variable_action(void *user, RRDHOST *host, RRDSET *st, char *name, int global, calculated_number value); -PARSER_RC pluginsd_dimension_action(void *user, RRDSET *st, char *id, char *name, char *algorithm, long multiplier, long divisor, char *options, - RRD_ALGORITHM algorithm_type); -PARSER_RC pluginsd_label_action(void *user, char *key, char *value, LABEL_SOURCE source); -PARSER_RC pluginsd_overwrite_action(void *user, RRDHOST *host, struct label *new_labels); +extern PARSER_RC pluginsd_set_action(void *user, RRDSET *st, RRDDIM *rd, long long int value); +extern PARSER_RC pluginsd_flush_action(void *user, RRDSET *st); +extern PARSER_RC pluginsd_begin_action(void *user, RRDSET *st, usec_t microseconds, int trust_durations); +extern PARSER_RC pluginsd_end_action(void *user, RRDSET *st); +extern PARSER_RC pluginsd_chart_action(void *user, char *type, char *id, char *name, char *family, char *context, + char *title, char *units, char *plugin, char *module, int priority, + int update_every, RRDSET_TYPE chart_type, char *options); +extern PARSER_RC pluginsd_disable_action(void *user); +extern PARSER_RC pluginsd_variable_action(void *user, RRDHOST *host, RRDSET *st, char *name, int global, + calculated_number value); +extern PARSER_RC pluginsd_dimension_action(void *user, RRDSET *st, char *id, char *name, char *algorithm, + long multiplier, long divisor, char *options, RRD_ALGORITHM algorithm_type); +extern PARSER_RC pluginsd_label_action(void *user, char *key, char *value, LABEL_SOURCE source); +extern PARSER_RC pluginsd_overwrite_action(void *user, RRDHOST *host, struct label *new_labels); + #endif //NETDATA_PLUGINSD_PARSER_H diff --git a/collectors/proc.plugin/plugin_proc.c b/collectors/proc.plugin/plugin_proc.c index c9eef2c257..1334f5d210 100644 --- a/collectors/proc.plugin/plugin_proc.c +++ b/collectors/proc.plugin/plugin_proc.c @@ -148,7 +148,7 @@ void *proc_main(void *ptr) { static RRDSET *st = NULL; if(unlikely(!st)) { - st = rrdset_find_bytype_localhost("netdata", "plugin_proc_modules"); + st = rrdset_find_active_bytype_localhost("netdata", "plugin_proc_modules"); if(!st) { st = rrdset_create_localhost( diff --git a/collectors/proc.plugin/proc_net_softnet_stat.c b/collectors/proc.plugin/proc_net_softnet_stat.c index 7ec783e77d..a29ccccd1a 100644 --- a/collectors/proc.plugin/proc_net_softnet_stat.c +++ b/collectors/proc.plugin/proc_net_softnet_stat.c @@ -81,7 +81,7 @@ int do_proc_net_softnet_stat(int update_every, usec_t dt) { // -------------------------------------------------------------------- - st = rrdset_find_bytype_localhost("system", "softnet_stat"); + st = rrdset_find_active_bytype_localhost("system", "softnet_stat"); if(unlikely(!st)) { st = rrdset_create_localhost( "system" @@ -114,7 +114,7 @@ int do_proc_net_softnet_stat(int update_every, usec_t dt) { char id[50+1]; snprintfz(id, 50, "cpu%zu_softnet_stat", l); - st = rrdset_find_bytype_localhost("cpu", id); + st = rrdset_find_active_bytype_localhost("cpu", id); if(unlikely(!st)) { char title[100+1]; snprintfz(title, 100, "CPU%zu softnet_stat", l); diff --git a/collectors/statsd.plugin/statsd.c b/collectors/statsd.plugin/statsd.c index df69031dea..1e69d53460 100644 --- a/collectors/statsd.plugin/statsd.c +++ b/collectors/statsd.plugin/statsd.c @@ -1461,6 +1461,8 @@ static inline RRDSET *statsd_private_rrdset_create( , chart_type // chart type , memory_mode // memory mode , history // history + , 0 // not archived + , NULL // no known UUID ); rrdset_flag_set(st, RRDSET_FLAG_STORE_FIRST); @@ -1999,6 +2001,8 @@ static inline void statsd_update_app_chart(STATSD_APP *app, STATSD_APP_CHART *ch , chart->chart_type // chart type , app->rrd_memory_mode // memory mode , app->rrd_history_entries // history + , 0 // not archived + , NULL // no known UUID ); rrdset_flag_set(chart->st, RRDSET_FLAG_STORE_FIRST); diff --git a/configure.ac b/configure.ac index 8fa593c001..4bebda621d 100644 --- a/configure.ac +++ b/configure.ac @@ -1437,6 +1437,8 @@ AC_CONFIG_FILES([ daemon/Makefile database/Makefile database/engine/Makefile + database/engine/metadata_log/Makefile + database/engine/global_uuid_map/Makefile diagrams/Makefile exporting/Makefile exporting/graphite/Makefile diff --git a/daemon/common.h b/daemon/common.h index 742ca4a766..f35c6a8828 100644 --- a/daemon/common.h +++ b/daemon/common.h @@ -68,9 +68,15 @@ // netdata agent cloud link #include "aclk/agent_cloud_link.h" +// global GUID map functions + // netdata agent spawn server #include "spawn/spawn.h" +#ifdef ENABLE_DBENGINE +#include "database/engine/global_uuid_map/global_uuid_map.h" +#endif + // the netdata deamon #include "daemon.h" #include "main.h" diff --git a/daemon/main.c b/daemon/main.c index dd556af640..2087d651ff 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -1400,6 +1400,9 @@ int main(int argc, char **argv) { struct rrdhost_system_info *system_info = calloc(1, sizeof(struct rrdhost_system_info)); get_system_info(system_info); +#ifdef ENABLE_DBENGINE + init_global_guid_map(); +#endif if(rrd_init(netdata_configured_hostname, system_info)) fatal("Cannot initialize localhost instance with name '%s'.", netdata_configured_hostname); @@ -1417,6 +1420,9 @@ int main(int argc, char **argv) { // Load host labels reload_host_labels(); +#ifdef ENABLE_DBENGINE + metalog_commit_update_host(localhost); +#endif // ------------------------------------------------------------------------ // spawn the threads diff --git a/daemon/unit_test.c b/daemon/unit_test.c index 323ae285a3..116fe7061d 100644 --- a/daemon/unit_test.c +++ b/daemon/unit_test.c @@ -1833,9 +1833,10 @@ int test_dbengine(void) } } error_out: - rrdeng_exit(host->rrdeng_ctx); rrd_wrlock(); + rrdeng_prepare_exit(host->rrdeng_ctx); rrdhost_delete_charts(host); + rrdeng_exit(host->rrdeng_ctx); rrd_unlock(); return errors; @@ -2222,9 +2223,10 @@ void dbengine_stress_test(unsigned TEST_DURATION_SEC, unsigned DSET_CHARTS, unsi freez(query_threads[i]); } freez(query_threads); - rrdeng_exit(host->rrdeng_ctx); rrd_wrlock(); + rrdeng_prepare_exit(host->rrdeng_ctx); rrdhost_delete_charts(host); + rrdeng_exit(host->rrdeng_ctx); rrd_unlock(); } diff --git a/database/engine/Makefile.am b/database/engine/Makefile.am index 161784b8f6..90fdc6bac7 100644 --- a/database/engine/Makefile.am +++ b/database/engine/Makefile.am @@ -3,6 +3,11 @@ AUTOMAKE_OPTIONS = subdir-objects MAINTAINERCLEANFILES = $(srcdir)/Makefile.in +SUBDIRS = \ + metadata_log \ + global_uuid_map \ + $(NULL) + dist_noinst_DATA = \ README.md \ $(NULL) diff --git a/database/engine/global_uuid_map/Makefile.am b/database/engine/global_uuid_map/Makefile.am new file mode 100644 index 0000000000..161784b8f6 --- /dev/null +++ b/database/engine/global_uuid_map/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/database/engine/global_uuid_map/README.md b/database/engine/global_uuid_map/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/database/engine/global_uuid_map/global_uuid_map.c b/database/engine/global_uuid_map/global_uuid_map.c new file mode 100644 index 0000000000..340a353528 --- /dev/null +++ b/database/engine/global_uuid_map/global_uuid_map.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "global_uuid_map.h" + +static Pvoid_t JGUID_map = (Pvoid_t) NULL; +static Pvoid_t JGUID_object_map = (Pvoid_t) NULL; +static uv_rwlock_t guid_lock; +static uv_rwlock_t object_lock; +static uv_rwlock_t global_lock; + + +void dump_object(uuid_t *index, void *object) +{ + char uuid_s[36 + 1]; + uuid_unparse_lower(*index, uuid_s); + char local_object[3 * 36 + 2 + 1]; + + switch (*(char *) object) { + case GUID_TYPE_CHAR: + debug(D_GUIDLOG, "OBJECT GUID %s on [%s]", uuid_s, (char *)object + 1); + break; + case GUID_TYPE_CHART: + uuid_unparse_lower((const unsigned char *)object + 1, local_object); + uuid_unparse_lower((const unsigned char *)object + 17, local_object+37); + local_object[36] = ':'; + local_object[74] = '\0'; + debug(D_GUIDLOG, "CHART GUID %s on [%s]", uuid_s, local_object); + break; + case GUID_TYPE_DIMENSION: + uuid_unparse_lower((const unsigned char *)object + 1, local_object); + uuid_unparse_lower((const unsigned char *)object + 17, local_object + 37); + uuid_unparse_lower((const unsigned char *)object + 33, local_object + 74); + local_object[36] = ':'; + local_object[73] = ':'; + local_object[110] = '\0'; + debug(D_GUIDLOG, "DIM GUID %s on [%s]", uuid_s, local_object); + break; + default: + debug(D_GUIDLOG, "Unknown object"); + } +} + +/* Returns 0 if it successfully stores the uuid-object mapping or if an identical mapping already exists */ +static inline int guid_store_nolock(uuid_t *uuid, void *object, GUID_TYPE object_type) +{ + char *existing_object; + GUID_TYPE existing_object_type; + + if (unlikely(!object) || uuid == NULL) + return 0; + + Pvoid_t *PValue; + + PValue = JudyHSIns(&JGUID_map, (void *) uuid, (Word_t) sizeof(uuid_t), PJE0); + if (PPJERR == PValue) + fatal("JudyHSIns() fatal error."); + if (*PValue) { + existing_object = *PValue; + existing_object_type = existing_object[0]; + if (existing_object_type != object_type) + return 1; + switch (existing_object_type) { + case GUID_TYPE_DIMENSION: + if (memcmp(existing_object, object, 1 + 16 + 16 + 16)) + return 1; + break; + case GUID_TYPE_CHART: + if (memcmp(existing_object, object, 1 + 16 + 16)) + return 1; + break; + case GUID_TYPE_CHAR: + if (strcmp(existing_object + 1, (char *)object)) + return 1; + break; + default: + return 1; + } + freez(existing_object); + } + + *PValue = (Pvoid_t *) object; + + PValue = JudyHSIns(&JGUID_object_map, (void *)object, (Word_t) object_type?(object_type * 16)+1:strlen((char *) object+1)+2, PJE0); + if (PPJERR == PValue) + fatal("JudyHSIns() fatal error."); + if (*PValue == NULL) { + uuid_t *value = (uuid_t *) mallocz(sizeof(uuid_t)); + uuid_copy(*value, *uuid); + *PValue = value; + } + +#ifdef NETDATA_INTERNAL_CHECKS + static uint32_t count = 0; + count++; + char uuid_s[36 + 1]; + uuid_unparse_lower(*uuid, uuid_s); + debug(D_GUIDLOG,"GUID added item %" PRIu32" [%s] as:", count, uuid_s); + dump_object(uuid, object); +#endif + return 0; +} + + +inline int guid_store(uuid_t *uuid, char *object, GUID_TYPE object_type) +{ + uv_rwlock_wrlock(&global_lock); + int rc = guid_store_nolock(uuid, object, object_type); + uv_rwlock_wrunlock(&global_lock); + return rc; +} + +/* + * This can be used to bulk load entries into the global map + * + * A lock must be aquired since it will call guid_store_nolock + * with a "no lock" parameter. + * + * Note: object memory must be allocated by caller and not released + */ +int guid_bulk_load(char *uuid, char *object) +{ + uuid_t target_uuid; + if (likely(!uuid_parse(uuid, target_uuid))) { +#ifdef NETDATA_INTERNAL_CHECKS + debug(D_GUIDLOG,"Mapping GUID [%s] on [%s]", uuid, object); +#endif + return guid_store_nolock(&target_uuid, object, GUID_TYPE_CHAR); + } + return 1; +} + +/* + * Given a GUID, find if an object is stored + * - Optionally return the object + */ + +GUID_TYPE find_object_by_guid(uuid_t *uuid, char *object, size_t max_bytes) +{ + Pvoid_t *PValue; + GUID_TYPE value_type; + + uv_rwlock_rdlock(&global_lock); + PValue = JudyHSGet(JGUID_map, (void *) uuid, (Word_t) sizeof(uuid_t)); + if (unlikely(!PValue)) { + uv_rwlock_rdunlock(&global_lock); + return GUID_TYPE_NOTFOUND; + } + + value_type = *(char *) *PValue; + + if (likely(object && max_bytes)) { + switch (value_type) { + case GUID_TYPE_CHAR: + if (unlikely(max_bytes - 1 < strlen((char *) *PValue+1))) + return GUID_TYPE_NOSPACE; + strncpyz(object, (char *) *PValue+1, max_bytes - 1); + break; + case GUID_TYPE_CHART: + case GUID_TYPE_DIMENSION: + if (unlikely(max_bytes < (size_t) value_type * 16)) + return GUID_TYPE_NOSPACE; + memcpy(object, *PValue+1, value_type * 16); + break; + default: + uv_rwlock_rdunlock(&global_lock); + return GUID_TYPE_NOTFOUND; + } + } + +#ifdef NETDATA_INTERNAL_CHECKS + dump_object(uuid, *PValue); +#endif + uv_rwlock_rdunlock(&global_lock); + return value_type; +} + +/* + * Find a GUID of an object + * - Optionally return the GUID + * + */ + +int find_guid_by_object(char *object, uuid_t *uuid, GUID_TYPE object_type) +{ + Pvoid_t *PValue; + + uv_rwlock_rdlock(&global_lock); + PValue = JudyHSGet(JGUID_object_map, (void *)object, (Word_t)object_type?object_type*16+1:strlen(object+1)+2); + if (unlikely(!PValue)) { + uv_rwlock_rdunlock(&global_lock); + return 1; + } + + if (likely(uuid)) + uuid_copy(*uuid, *PValue); + uv_rwlock_rdunlock(&global_lock); + return 0; +} + +int find_or_generate_guid(void *object, uuid_t *uuid, GUID_TYPE object_type, int replace_instead_of_generate) +{ + char *target_object; + uuid_t temp_uuid; + int rc; + + switch (object_type) { + case GUID_TYPE_DIMENSION: + if (unlikely(find_or_generate_guid((void *) ((RRDDIM *)object)->id, &temp_uuid, GUID_TYPE_CHAR, 0))) + return 1; + target_object = mallocz(49); + target_object[0] = object_type; + memcpy(target_object + 1, ((RRDDIM *)object)->rrdset->rrdhost->host_uuid, 16); + memcpy(target_object + 17, ((RRDDIM *)object)->rrdset->chart_uuid, 16); + memcpy(target_object + 33, temp_uuid, 16); + break; + case GUID_TYPE_CHART: + if (unlikely(find_or_generate_guid((void *) ((RRDSET *)object)->id, &temp_uuid, GUID_TYPE_CHAR, 0))) + return 1; + target_object = mallocz(33); + target_object[0] = object_type; + memcpy(target_object + 1, (((RRDSET *)object))->rrdhost->host_uuid, 16); + memcpy(target_object + 17, temp_uuid, 16); + break; + case GUID_TYPE_CHAR: + target_object = mallocz(strlen((char *) object)+2); + target_object[0] = object_type; + strcpy(target_object+1, (char *) object); + break; + default: + return 1; + } + rc = find_guid_by_object(target_object, uuid, object_type); + if (rc) { + if (!replace_instead_of_generate) /* else take *uuid as user input */ + uuid_generate(*uuid); + uv_rwlock_wrlock(&global_lock); + int rc = guid_store_nolock(uuid, target_object, object_type); + uv_rwlock_wrunlock(&global_lock); + return rc; + } + //uv_rwlock_wrunlock(&global_lock); +#ifdef NETDATA_INTERNAL_CHECKS + dump_object(uuid, target_object); +#endif + return 0; +} + +void init_global_guid_map() +{ + static int init = 0; + + if (init) + return; + + init = 1; + info("Configuring locking mechanism for global GUID map"); + assert(0 == uv_rwlock_init(&guid_lock)); + assert(0 == uv_rwlock_init(&object_lock)); + assert(0 == uv_rwlock_init(&global_lock)); + +// int rc = guid_bulk_load("6fc56a64-05d7-47a7-bc82-7f3235d8cbda","d6b37186-74db-11ea-88b2-0bf5095b1f9e/cgroup_qemu_ubuntu18.04.cpu_per_core/cpu3"); +// rc = guid_bulk_load("75c6fa02-97cc-40c1-aacd-a0132190472e","d6b37186-74db-11ea-88b2-0bf5095b1f9e/services.throttle_io_ops_write/system.slice_setvtrgb.service"); +// if (rc == 0) +// info("BULK GUID load successful"); + + return; +} + + diff --git a/database/engine/global_uuid_map/global_uuid_map.h b/database/engine/global_uuid_map/global_uuid_map.h new file mode 100644 index 0000000000..3def854baf --- /dev/null +++ b/database/engine/global_uuid_map/global_uuid_map.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_GLOBAL_UUID_MAP_H +#define NETDATA_GLOBAL_UUID_MAP_H + +#include "libnetdata/libnetdata.h" +#include <Judy.h> +#include "../../rrd.h" + +typedef enum guid_type { + GUID_TYPE_CHAR, + GUID_TYPE_HOST, + GUID_TYPE_CHART, + GUID_TYPE_DIMENSION, + GUID_TYPE_NOTFOUND, + GUID_TYPE_NOSPACE +} GUID_TYPE; + +extern int guid_store(uuid_t *uuid, char *object, GUID_TYPE); +extern GUID_TYPE find_object_by_guid(uuid_t *uuid, char *object, size_t max_bytes); +extern int find_guid_by_object(char *object, uuid_t *uuid, GUID_TYPE); +extern void init_global_guid_map(); +extern int find_or_generate_guid(void *object, uuid_t *uuid, GUID_TYPE object_type, int replace_instead_of_generate); + + +#endif //NETDATA_GLOBAL_UUID_MAP_H diff --git a/database/engine/metadata_log/Makefile.am b/database/engine/metadata_log/Makefile.am new file mode 100644 index 0000000000..161784b8f6 --- /dev/null +++ b/database/engine/metadata_log/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/database/engine/metadata_log/README.md b/database/engine/metadata_log/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/database/engine/metadata_log/compaction.c b/database/engine/metadata_log/compaction.c new file mode 100644 index 0000000000..9113fdd940 --- /dev/null +++ b/database/engine/metadata_log/compaction.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#define NETDATA_RRD_INTERNALS + +#include "metadatalog.h" + +void after_compact_old_records(struct metalog_worker_config* wc) +{ + struct metalog_instance *ctx = wc->ctx; + int error; + + mlf_flush_records_buffer(wc, &ctx->compaction_state.records_log, &ctx->compaction_state.new_metadata_logfiles); + uv_run(wc->loop, UV_RUN_DEFAULT); + + error = uv_thread_join(wc->now_compacting_files); + if (error) { + error("uv_thread_join(): %s", uv_strerror(error)); + } + freez(wc->now_compacting_files); + /* unfreeze command processing */ + wc->now_compacting_files = NULL; + + wc->cleanup_thread_compacting_files = 0; + + /* interrupt event loop */ + uv_stop(wc->loop); + + info("Finished metadata log compaction (id:%"PRIu32").", ctx->current_compaction_id); +} + +static void metalog_flush_compaction_records(struct metalog_instance *ctx) +{ + struct metalog_cmd cmd; + struct completion compaction_completion; + + init_completion(&compaction_completion); + + cmd.opcode = METALOG_COMPACTION_FLUSH; + cmd.record_io_descr.completion = &compaction_completion; + metalog_enq_cmd(&ctx->worker_config, &cmd); + + wait_for_completion(&compaction_completion); + destroy_completion(&compaction_completion); +} + +/* The caller must have called metalog_flush_compaction_records() before to synchronize and quiesce the event loop. */ +static void compaction_test_quota(struct metalog_worker_config *wc) +{ + struct metalog_instance *ctx = wc->ctx; + struct logfile_compaction_state *compaction_state; + struct metadata_logfile *oldmetalogfile, *newmetalogfile; + unsigned current_size; + int ret; + + compaction_state = &ctx->compaction_state; + newmetalogfile = compaction_state->new_metadata_logfiles.last; + + oldmetalogfile = ctx->metadata_logfiles.first; + + current_size = newmetalogfile->pos; + if (unlikely(current_size >= MAX_METALOGFILE_SIZE && newmetalogfile->starting_fileno < oldmetalogfile->fileno)) { + /* It's safe to finalize the compacted metadata log file and create a new one since it has already replaced + * an older one. */ + + /* Finalize as the immediately previous file than the currently compacted one. */ + ret = rename_metadata_logfile(newmetalogfile, 0, newmetalogfile->fileno - 1); + if (ret < 0) + return; + + ret = add_new_metadata_logfile(ctx, &compaction_state->new_metadata_logfiles, + ctx->metadata_logfiles.first->fileno, ctx->metadata_logfiles.first->fileno); + + if (likely(!ret)) { + compaction_state->fileno = ctx->metadata_logfiles.first->fileno; + } + } +} + + +static void compact_record_by_uuid(struct metalog_instance *ctx, uuid_t *uuid) +{ + GUID_TYPE ret; + RRDHOST *host = ctx->rrdeng_ctx->host; + RRDSET *st; + RRDDIM *rd; + BUFFER *buffer; + + ret = find_object_by_guid(uuid, NULL, 0); + switch (ret) { + case GUID_TYPE_CHAR: + assert(0); + break; + case GUID_TYPE_CHART: + st = metalog_get_chart_from_uuid(ctx, uuid); + if (st) { + if (ctx->current_compaction_id > st->compaction_id) { + st->compaction_id = ctx->current_compaction_id; + buffer = metalog_update_chart_buffer(st, ctx->current_compaction_id); + metalog_commit_record(ctx, buffer, METALOG_COMMIT_CREATION_RECORD, uuid, 1); + } else { + debug(D_METADATALOG, "Chart has already been compacted, ignoring record."); + } + } else { + debug(D_METADATALOG, "Ignoring nonexistent chart metadata record."); + } + break; + case GUID_TYPE_DIMENSION: + rd = metalog_get_dimension_from_uuid(ctx, uuid); + if (rd) { + if (ctx->current_compaction_id > rd->state->compaction_id) { + rd->state->compaction_id = ctx->current_compaction_id; + buffer = metalog_update_dimension_buffer(rd); + metalog_commit_record(ctx, buffer, METALOG_COMMIT_CREATION_RECORD, uuid, 1); + } else { + debug(D_METADATALOG, "Dimension has already been compacted, ignoring record."); + } + } else { + debug(D_METADATALOG, "Ignoring nonexistent dimension metadata record."); + } + break; + case GUID_TYPE_HOST: + if (ctx->current_compaction_id > host->compaction_id) { + host->compaction_id = ctx->current_compaction_id; + buffer = metalog_update_host_buffer(host); + metalog_commit_record(ctx, buffer, METALOG_COMMIT_CREATION_RECORD, uuid, 1); + } else { + debug(D_METADATALOG, "Host has already been compacted, ignoring record."); + } + break; + case GUID_TYPE_NOTFOUND: + debug(D_METADATALOG, "Ignoring nonexistent metadata record."); + break; + default: + assert(0); + break; + } +} + +/* Returns 0 on success. */ +static int compact_metadata_logfile_records(struct metalog_instance *ctx, struct metadata_logfile *metalogfile) +{ + struct metalog_worker_config* wc = &ctx->worker_config; + struct logfile_compaction_state *compaction_state; + struct metalog_record *record; + struct metalog_record_block *record_block, *prev_record_block; + int ret; + unsigned iterated_records; +#define METADATA_LOG_RECORD_BATCH 128 /* Flush I/O and check sizes whenever this many records have been iterated */ + + info("Compacting metadata log file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL METALOG_EXTENSION"\".", + ctx->rrdeng_ctx->dbfiles_path, metalogfile->starting_fileno, metalogfile->fileno); + + compaction_state = &ctx->compaction_state; + record_block = prev_record_block = NULL; + iterated_records = 0; + for (record = mlf_record_get_first(metalogfile) ; record != NULL ; record = mlf_record_get_next(metalogfile)) { + if ((record_block = metalogfile->records.iterator.current) != prev_record_block) { + if (prev_record_block) { /* Deallocate iterated record blocks */ + rrd_atomic_fetch_add(&ctx->records_nr, -prev_record_block->records_nr); + freez(prev_record_block); + } + prev_record_block = record_block; + } + compact_record_by_uuid(ctx, &record->uuid); + if (0 == ++iterated_records % METADATA_LOG_RECORD_BATCH) { + metalog_flush_compaction_records(ctx); + if (compaction_state->throttle) { + (void)sleep_usec(10000); /* 10 msec throttle compaction */ + } + compaction_test_quota(wc); + } + } + if (prev_record_block) { /* Deallocate iterated record blocks */ + rrd_atomic_fetch_add(&ctx->records_nr, -prev_record_block->records_nr); + freez(prev_record_block); + } + + info("Compacted metadata log file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL METALOG_EXTENSION"\".", + ctx->rrdeng_ctx->dbfiles_path, metalogfile->starting_fileno, metalogfile->fileno); + + metadata_logfile_list_delete(&ctx->metadata_logfiles, metalogfile); + ret = destroy_metadata_logfile(metalogfile); + if (!ret) { + info("Deleted file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL METALOG_EXTENSION"\".", + ctx->rrdeng_ctx->dbfiles_path, metalogfile->starting_fileno, metalogfile->fileno); + rrd_atomic_fetch_add(&ctx->disk_space, -metalogfile->pos); + } else { + error("Failed to delete file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL METALOG_EXTENSION"\".", + ctx->rrdeng_ctx->dbfiles_path, metalogfile->starting_fileno, metalogfile->fileno); + } + freez(metalogfile); + + return ret; +} + +static void compact_old_records(void *arg) +{ + struct metalog_instance *ctx = arg; + struct metalog_worker_config* wc = &ctx->worker_config; + struct logfile_compaction_state *compaction_state; + struct metadata_logfile *metalogfile, *nextmetalogfile, *newmetalogfile; + int ret; + + compaction_state = &ctx->compaction_state; + + nextmetalogfile = NULL; + for (metalogfile = ctx->metadata_logfiles.first ; + metalogfile != compaction_state->last_original_logfile ; + metalogfile = nextmetalogfile) { + nextmetalogfile = metalogfile->next; + + newmetalogfile = compaction_state->new_metadata_logfiles.last; + ret = rename_metadata_logfile(newmetalogfile, newmetalogfile->starting_fileno, metalogfile->fileno); + if (ret < 0) { + error("Failed to rename file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL METALOG_EXTENSION"\".", + ctx->rrdeng_ctx->dbfiles_path, newmetalogfile->starting_fileno, newmetalogfile->fileno); + } + + ret = compact_metadata_logfile_records(ctx, metalogfile); + if (ret) { + error("Metadata log compaction failed, cancelling."); + break; + } + } + assert(nextmetalogfile); /* There are always more than 1 metadata log files during compaction */ + + newmetalogfile = compaction_state->new_metadata_logfiles.last; + if (newmetalogfile->starting_fileno != 0) { /* Must rename the last compacted file */ + ret = rename_metadata_logfile(newmetalogfile, 0, nextmetalogfile->fileno - 1); + if (ret < 0) { + error("Failed to rename file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL METALOG_EXTENSION"\".", + ctx->rrdeng_ctx->dbfiles_path, newmetalogfile->starting_fileno, newmetalogfile->fileno); + } + } + /* Connect the compacted files to the metadata log */ + newmetalogfile->next = nextmetalogfile; + ctx->metadata_logfiles.first = compaction_state->new_metadata_logfiles.first; + + wc->cleanup_thread_compacting_files = 1; + /* wake up event loop */ + assert(0 == uv_async_send(&wc->async)); +} + +/* Returns 0 on success. */ +static int init_compaction_state(struct metalog_instance *ctx) +{ + struct metadata_logfile *newmetalogfile; + struct logfile_compaction_state *compaction_state; + int ret; + + compaction_state = &ctx->compaction_state; + compaction_state->new_metadata_logfiles.first = NULL; + compaction_state->new_metadata_logfiles.last = NULL; + compaction_state->starting_fileno = ctx->metadata_logfiles.first->fileno; + compaction_state->fileno = ctx->metadata_logfiles.first->fileno; + compaction_state->last_original_logfile = ctx->metadata_logfiles.last; + compaction_state->throttle = 0; + + ret = add_new_metadata_logfile(ctx, &compaction_state->new_metadata_logfiles, compaction_state->starting_fileno, + compaction_state->fileno); + if (unlikely(ret)) { + error("Cannot create new metadata log files, compaction aborted."); + return ret; + } + newmetalogfile = compaction_state->new_metadata_logfiles.first; + assert(newmetalogfile == compaction_state->new_metadata_logfiles.last); + init_metadata_record_log(&compaction_state->records_log); + + return 0; +} + +void metalog_do_compaction(struct metalog_worker_config *wc) +{ + struct metalog_instance *ctx = wc->ctx; + int error; + + if (wc->now_compacting_files) { + /* already compacting metadata log files */ + return; + } + wc->now_compacting_files = mallocz(sizeof(*wc->now_compacting_files)); + wc->cleanup_thread_compacting_files = 0; + metalog_try_link_new_metadata_logfile(wc); + + error = init_compaction_state(ctx); + if (unlikely(error)) { + error("Cannot create new metadata log files, compaction aborted."); + return; + } + ++ctx->current_compaction_id; /* Signify a new compaction */ + + info("Starting metadata log compaction (id:%"PRIu32").", ctx->current_compaction_id); + error = uv_thread_create(wc->now_compacting_files, compact_old_records, ctx); + if (error) { + error("uv_thread_create(): %s", uv_strerror(error)); + freez(wc->now_compacting_files); + wc->now_compacting_files = NULL; + } + +} + +/* Return 0 on success. */ +int compaction_failure_recovery(struct metalog_instance *ctx, struct metadata_logfile **metalogfiles, + unsigned *matched_files) +{ + int ret; + unsigned starting_fileno, fileno, i, j, recovered_files; + struct metadata_logfile *metalogfile, *compactionfile, **tmp_metalogfiles; + char *dbfiles_path = ctx->rrdeng_ctx->dbfiles_path; + + for (i = 0 ; i < *matched_files ; ++i) { + metalogfile = metalogfiles[i]; + if (0 == metalogfile->starting_fileno) + continue; /* skip standard metadata log files */ + break; /* this is a compaction temporary file */ + } + if (i == *matched_files) /* no recovery needed */ + return 0; + info("Starting metadata log file failure recovery procedure in \"%s\".", dbfiles_path); + + if (*matched_files - i > 1) { /* Can't have more than 1 temporary compaction files */ + error("Metadata log files are in an invalid state. Cannot proceed."); + return 1; + } + compactionfile = metalogfile; + starting_fileno = compactionfile->starting_fileno; + fileno = compactionfile->fileno; + /* scratchpad space to move file pointers around */ + tmp_metalogfiles = callocz(*matched_files, sizeof(*tmp_metalogfiles)); + + for (j = 0, recovered_files = 0 ; j < i ; ++j) { + metalogfile = metalogfiles[j]; + assert(0 == metalogfile->starting_fileno); + if (metalogfile->fileno < starting_fileno) { + tmp_metalogfiles[recovered_files++] = metalogfile; + continue; + } + break; /* reached compaction file serial number */ + } + + if ((j == i) /* Shouldn't be possible, invalid compaction temporary file */ || + (metalogfile->fileno == starting_fileno && metalogfile->fileno == fileno)) { + error("Deleting invalid compaction temporary file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL + METALOG_EXTENSION"\"", dbfiles_path, starting_fileno, fileno); + unlink_metadata_logfile(compactionfile); + freez(compactionfile); + freez(tmp_metalogfiles); + --*matched_files; /* delete the last one */ + + info("Finished metadata log file failure recovery procedure in \"%s\".", dbfiles_path); + return 0; + } + + for ( ; j < i ; ++j) { /* continue iterating through normal metadata log files */ + metalogfile = metalogfiles[j]; + assert(0 == metalogfile->starting_fileno); + if (metalogfile->fileno < fileno) { /* It has already been compacted */ + error("Deleting invalid metadata log file \"%s/"METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL + METALOG_EXTENSION"\"", dbfiles_path, 0U, metalogfile->fileno); + unlink_metadata_logfile(metalogfile); + freez(metalogfile); + continue; + } + tmp_metalogfiles[recovered_files++] = metalogfile; + } + + /* compaction temporary file is valid */ + tmp_metalogfiles[recovered_files++] = compactionfile; + ret = rename_metadata_logfile(compactionfile, 0, starting_fileno); + if (ret < 0) { + error("Cannot rename temporary compaction files. Cannot proceed."); + freez(tmp_metalogfiles); + return 1; + } + + memcpy(metalogfiles, tmp_metalogfiles, recovered_files * sizeof(*metalogfiles)); + *matched_files = recovered_files; + freez(tmp_metalogfiles); + + info("Finished metadata log file failure recovery procedure in \"%s\".", dbfiles_path); + return 0; +} diff --git a/database/engine/metadata_log/compaction.h b/database/engine/metadata_log/compaction.h new file mode 100644 index 0000000000..da4765eeb5 --- /dev/null +++ b/database/engine/metadata_log/compaction.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_COMPACTION_H +#define NETDATA_COMPACTION_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include "../rrdengine.h" + +struct logfile_compaction_state { + unsigned fileno; /* Starts at 1 */ + unsigned starting_fileno; /* 0 for normal files, staring number during compaction */ + + struct metadata_record_commit_log records_log; + struct metadata_logfile_list new_metadata_logfiles; + struct metadata_logfile *last_original_logfile; /* Marks the end of compaction */ + uint8_t throttle; /* set non-zero to throttle compaction */ +}; + +extern int compaction_failure_recovery(struct metalog_instance *ctx, struct metadata_logfile **metalogfiles, + unsigned *matched_files); +extern void metalog_do_compaction(struct metalog_worker_config *wc); +extern void after_compact_old_records(struct metalog_worker_config* wc); + +#endif /* NETDATA_COMPACTION_H */ diff --git a/database/engine/metadata_log/logfile.c b/database/engine/metadata_log/logfile.c new file mode 100644 index 0000000000..9ae2866298 --- /dev/null +++ b/database/engine/metadata_log/logfile.c @@ -0,0 +1,790 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#include "metadatalog.h" +#include "metalogpluginsd.h" + +static void mlf_record_block_insert(struct metadata_logfile *metalogfile, struct metalog_record_block *record_block) +{ + + if (likely(NULL != metalogfile->records.last)) { + metalogfile->records.last->next = record_block; + } + if (unlikely(NULL == metalogfile->records.first)) { + metalogfile->records.first = record_block; + } + metalogfile->records.last = record_block; +} + +void mlf_record_insert(struct metadata_logfile *metalogfile, struct metalog_record *record) +{ + struct metalog_record_block *record_block; + struct metalog_instance *ctx = metalogfile->ctx; + + record_block = metalogfile->records.last; + if (likely(NULL != record_block && record_block->records_nr < MAX_METALOG_RECORDS_PER_BLOCK)) { + record_block->record_array[record_block->records_nr++] = *record; + } else { /* Create new record block, the last one filled up */ + record_block = mallocz(sizeof(*record_block)); + record_block->records_nr = 1; + record_block->record_array[0] = *record; + record_block->next = NULL; + + mlf_record_block_insert(metalogfile, record_block); + } + rrd_atomic_fetch_add(&ctx->records_nr, 1); +} + +struct metalog_record *mlf_record_get_first(struct metadata_logfile *metalogfile) +{ + struct metalog_records *records = &metalogfile->records; + struct metalog_record_block *record_block = metalogfile->records.first; + + records->iterator.current = record_block; + records->iterator.record_i = 0; + + if (unlikely(NULL == record_block || !record_block->records_nr)) { + error("Cannot iterate empty metadata log file %u-%u.", metalogfile->starting_fileno, metalogfile->fileno); + return NULL; + } + + return &record_block->record_array[0]; +} + +/* Must have called mlf_record_get_first before calling this function. */ +struct metalog_record *mlf_record_get_next(struct metadata_logfile *metalogfile) +{ + struct metalog_records *records = &metalogfile->records; + struct metalog_record_block *record_block = records->iterator.current; + + if (unlikely(NULL == record_block)) { + return NULL; + } + if (++records->iterator.record_i >= record_block->records_nr) { + record_block = record_block->next; + if (unlikely(NULL == record_block || !record_block->records_nr)) { + return NULL; + } + records->iterator.current = record_block; + records->iterator.record_i = 0; + return &record_block->record_array[0]; + } + return &record_block->record_array[records->iterator.record_i]; +} + +static void flush_records_buffer_cb(uv_fs_t* req) +{ + struct generic_io_descriptor *io_descr = req->data; + struct metalog_worker_config *wc = req->loop->data; + struct metalog_instance *ctx = wc->ctx; + + debug(D_METADATALOG, "%s: Metadata log file block was written to disk.", __func__); + if (req->result < 0) { + ++ctx->stats.io_errors; + rrd_stat_atomic_add(&global_io_errors, 1); + error("%s: uv_fs_write: %s", __func__, uv_strerror((int)req->result)); + } else { + debug(D_METADATALOG, "%s: Metadata log file block was written to disk.", __func__); + } + + uv_fs_req_cleanup(req); + free(io_descr->buf); + freez(io_descr); +} + +/* Careful to always call this before creating a new metadata log file to finish writing the old one */ +void mlf_flush_records_buffer(struct metalog_worker_config *wc, struct metadata_record_commit_log *records_log, + struct metadata_logfile_list *metadata_logfiles) +{ + struct metalog_instance *ctx = wc->ctx; + int ret; + struct generic_io_descriptor *io_descr; + unsigned pos, size; + struct metadata_logfile *metalogfile; + + if (unlikely(NULL == records_log->buf || 0 == records_log->buf_pos)) { + return; + } + /* care with outstanding records when switching metadata log files */ + metalogfile = metadata_logfiles->last; + + io_descr = mallocz(sizeof(*io_descr)); + pos = records_log->buf_pos; + size = pos; /* no need to align the I/O when doing buffered writes */ + io_descr->buf = records_log->buf; + io_descr->bytes = size; + io_descr->pos = metalogfile->pos; + io_descr->req.data = io_descr; + io_descr->completion = NULL; + + io_descr->iov = uv_buf_init((void *)io_descr->buf, size); + ret = uv_fs_write(wc->loop, &io_descr->req, metalogfile->file, &io_descr->iov, 1, + metalogfile->pos, flush_records_buffer_cb); + assert (-1 != ret); + metalogfile->pos += size; + rrd_atomic_fetch_add(&ctx->disk_space, size); + records_log->buf = NULL; + ctx->stats.io_write_bytes += size; + ++ctx->stats.io_write_requests; +} + +void *mlf_get_records_buffer(struct metalog_worker_config *wc, struct metadata_record_commit_log *records_log, + struct metadata_logfile_list *metadata_logfiles, unsigned size) +{ + int ret; + unsigned buf_pos, buf_size; + + assert(size); + if (records_log->buf) { + unsigned remaining; + + buf_pos = records_log->buf_pos; + buf_size = records_log->buf_size; + remaining = buf_size - buf_pos; + if (size > remaining) { + /* we need a new buffer */ + mlf_flush_records_buffer(wc, records_log, metadata_logfiles); + } + } + if (NULL == records_log->buf) { + buf_size = ALIGN_BYTES_CEILING(size); + ret = posix_memalign((void *)&records_log->buf, RRDFILE_ALIGNMENT, buf_size); + if (unlikely(ret)) { + fatal("posix_memalign:%s", strerror(ret)); + } + buf_pos = records_log->buf_pos = 0; + records_log->buf_size = buf_size; + } + records_log->buf_pos += size; + + return records_log->buf + buf_pos; +} + + +void metadata_logfile_list_insert(struct metadata_logfile_list *metadata_logfiles, struct metadata_logfile *metalogfile) +{ + if (likely(NULL != metadata_logfiles->last)) { + metadata_logfiles->last->next = metalogfile; + } + if (unlikely(NULL == metadata_logfiles->first)) { + metadata_logfiles->first = metalogfile; + } + metadata_logfiles->last = metalogfile; +} + +void metadata_logfile_list_delete(struct metadata_logfile_list *metadata_logfiles, struct metadata_logfile *metalogfile) +{ + struct metadata_logfile *next; + + next = metalogfile->next; + assert((NULL != next) && (metadata_logfiles->first == metalogfile) && + (metadata_logfiles->last != metalogfile)); + metadata_logfiles->first = next; +} + +void generate_metadata_logfile_path(struct metadata_logfile *metalogfile, char *str, size_t maxlen) +{ + (void) snprintf(str, maxlen, "%s/" METALOG_PREFIX METALOG_FILE_NUMBER_PRINT_TMPL METALOG_EXTENSION, + metalogfile->ctx->rrdeng_ctx->dbfiles_path, metalogfile->starting_fileno, metalogfile->fileno); +} + +void metadata_logfile_init(struct metadata_logfile *metalogfile, struct metalog_instance *ctx, unsigned starting_fileno, + unsigned fileno) +{ + metalogfile->starting_fileno = starting_fileno; + metalogfile->fileno = fileno; + metalogfile->file = (uv_file)0; + metalogfile->pos = 0; + metalogfile->records.first = metalogfile->records.last = NULL; + metalogfile->next = NULL; + metalogfile->ctx = ctx; +} + +int rename_metadata_logfile(struct metadata_logfile *metalogfile, unsigned new_starting_fileno, unsigned new_fileno) +{ + struct metalog_instance *ctx = metalogfile->ctx; + uv_fs_t req; + int ret; + char oldpath[RRDENG_PATH_MAX], newpath[RRDENG_PATH_MAX]; + unsigned backup_starting_fileno, backup_fileno; + + backup_starting_fileno = metalogfile->starting_fileno; + backup_fileno = metalogfile->fileno; + generate_metadata_logfile_path(metalogfile, oldpath, sizeof(oldpath)); + metalogfile->starting_fileno = new_starting_fileno; + metalogfile->fileno = new_fileno; + generate_metadata_logfile_path(metalogfile, newpath, sizeof(newpath)); + + info("Renaming metadata log file \"%s\" to \"%s\".", oldpath, newpath); + ret = uv_fs_rename(NULL, &req, oldpath, newpath, NULL); + if (ret < 0) { + error("uv_fs_rename(%s): %s", oldpath, uv_strerror(ret)); + ++ctx->stats.fs_errors; /* this is racy, may miss some errors */ + rrd_stat_atomic_add(&global_fs_errors, 1); + /* restore previous values */ + metalogfile->starting_fileno = backup_starting_fileno; + metalogfile->fileno = backup_fileno; + } + uv_fs_req_cleanup(&req); + + return ret; +} + +int close_metadata_logfile(struct metadata_logfile *metalogfile) +{ + struct metalog_instance *ctx = metalogfile->ctx; + uv_fs_t req; + int ret; + char path[RRDENG_PATH_MAX]; + + generate_metadata_logfile_path(metalogfile, path, sizeof(path)); + + ret = uv_fs_close(NULL, &req, metalogfile->file, NULL); + if (ret < 0) { + error("uv_fs_close(%s): %s", path, uv_strerror(ret)); + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + } + uv_fs_req_cleanup(&req); + + return ret; +} + +int unlink_metadata_logfile(struct metadata_logfile *metalogfile) +{ + struct metalog_instance *ctx = metalogfile->ctx; + uv_fs_t req; + int ret; + char path[RRDENG_PATH_MAX]; + + generate_metadata_logfile_path(metalogfile, path, sizeof(path)); + + ret = uv_fs_unlink(NULL, &req, path, NULL); + if (ret < 0) { + error("uv_fs_fsunlink(%s): %s", path, uv_strerror(ret)); + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + } + uv_fs_req_cleanup(&req); + + return ret; +} + +int destroy_metadata_logfile(struct metadata_logfile *metalogfile) +{ + struct metalog_instance *ctx = metalogfile->ctx; + uv_fs_t req; + int ret; + char path[RRDENG_PATH_MAX]; + + generate_metadata_logfile_path(metalogfile, path, sizeof(path)); + + ret = uv_fs_ftruncate(NULL, &req, metalogfile->file, 0, NULL); + if (ret < 0) { + error("uv_fs_ftruncate(%s): %s", path, uv_strerror(ret)); + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + } + uv_fs_req_cleanup(&req); + + ret = uv_fs_close(NULL, &req, metalogfile->file, NULL); + if (ret < 0) { + error("uv_fs_close(%s): %s", path, uv_strerror(ret)); + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + } + uv_fs_req_cleanup(&req); + + ret = uv_fs_unlink(NULL, &req, path, NULL); + if (ret < 0) { + error("uv_fs_fsunlink(%s): %s", path, uv_strerror(ret)); + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + } + uv_fs_req_cleanup(&req); + +// ++ctx->stats.metadata_logfile_deletions; + + return ret; +} + +int create_metadata_logfile(struct metadata_logfile *metalogfile) +{ + struct metalog_instance *ctx = metalogfile->ctx; + uv_fs_t req; + uv_file file; + int ret, fd; + struct rrdeng_metalog_sb *superblock; + uv_buf_t iov; + char path[RRDENG_PATH_MAX]; + + generate_metadata_logfile_path(metalogfile, path, sizeof(path)); + fd = open_file_buffered_io(path, O_CREAT | O_RDWR | O_TRUNC, &file); + if (fd < 0) { + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + return fd; + } + metalogfile->file = file; +// ++ctx->stats.metadata_logfile_creations; + + ret = posix_memalign((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock)); + if (unlikely(ret)) { + fatal("posix_memalign:%s", strerror(ret)); + } + (void) strncpy(superblock->magic_number, RRDENG_METALOG_MAGIC, RRDENG_MAGIC_SZ); + superblock->version = RRDENG_METALOG_VER; + + iov = uv_buf_init((void *)superblock, sizeof(*superblock)); + + ret = uv_fs_write(NULL, &req, file, &iov, 1, 0, NULL); + if (ret < 0) { + assert(req.result < 0); + error("uv_fs_write: %s", uv_strerror(ret)); + ++ctx->stats.io_errors; + rrd_stat_atomic_add(&global_io_errors, 1); + } + uv_fs_req_cleanup(&req); + free(superblock); + if (ret < 0) { + destroy_metadata_logfile(metalogfile); + return ret; + } + + metalogfile->pos = sizeof(*superblock); + ctx->stats.io_write_bytes += sizeof(*superblock); + ++ctx->stats.io_write_requests; + + return 0; +} + +static int check_metadata_logfile_superblock(uv_file file) +{ + int ret; + struct rrdeng_metalog_sb *superblock; + uv_buf_t iov; + uv_fs_t req; + + ret = posix_memalign((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock)); + if (unlikely(ret)) { + fatal("posix_memalign:%s", strerror(ret)); + } + iov = uv_buf_init((void *)superblock, sizeof(*superblock)); + + ret = uv_fs_read(NULL, &req, file, &iov, 1, 0, NULL); + if (ret < 0) { + error("uv_fs_read: %s", uv_strerror(ret)); + uv_fs_req_cleanup(&req); + goto error; + } + assert(req.result >= 0); + uv_fs_req_cleanup(&req); + + if (strncmp(superblock->magic_number, RRDENG_METALOG_MAGIC, RRDENG_MAGIC_SZ)) { + error("File has invalid superblock."); + ret = UV_EINVAL; + } else { + ret = 0; + } + if (superblock->version > RRDENG_METALOG_VER) { + error("File has unknown version %"PRIu16". Compatibility is not guaranteed.", superblock->version); + } +error: + free(superblock); + return ret; +} + +void replay_record(struct metadata_logfile *metalogfile, struct rrdeng_metalog_record_header *header, void *payload) +{ + struct metalog_instance *ctx = metalogfile->ctx; + char *line, *nextline, *record_end; + int ret; + + debug(D_METADATALOG, "RECORD contents: %.*s", (int)header->payload_length, (char *)payload); + record_end = (char *)payload + header->payload_length - 1; + *record_end = '\0'; + + for (line = payload ; line ; line = nextline) { + nextline = strchr(line, '\n'); + if (nextline) { + *nextline++ = '\0'; + } + ret = parser_action(ctx->metalog_parser_object->parser, line); + debug(D_METADATALOG, "parser_action ret:%d", ret); + if (ret) + return; /* skip record due to error */ + }; +} + +/* This function only works with buffered I/O */ +static inline int metalogfile_read(struct metadata_logfile *metalogfile, void *buf, size_t len, uint64_t offset) +{ + struct metalog_instance *ctx; + uv_file file; + uv_buf_t iov; + uv_fs_t req; + int ret; + + ctx = metalogfile->ctx; + file = metalogfile->file; + iov = uv_buf_init(buf, len); + ret = uv_fs_read(NULL, &req, file, &iov, 1, offset, NULL); + if (unlikely(ret < 0 && ret != req.result)) { + fatal("uv_fs_read: %s", uv_strerror(ret)); + } + if (req.result < 0) { + ++ctx->stats.io_errors; + rrd_stat_atomic_add(&global_io_errors, 1); + error("%s: uv_fs_read - %s - record at offset %"PRIu64"(%u) in metadata logfile %u-%u.", __func__, + uv_strerror((int)req.result), offset, (unsigned)len, metalogfile->starting_fileno, metalogfile->fileno); + } + uv_fs_req_cleanup(&req); + ctx->stats.io_read_bytes += len; + ++ctx->stats.io_read_requests; + + return ret; +} + +/* Return 0 on success */ +static int metadata_record_integrity_check(void *record) +{ + int ret; + uint32_t data_size; + struct rrdeng_metalog_record_header *header; + struct rrdeng_metalog_record_trailer *trailer; + uLong crc; + + header = record; + data_size = header->header_length + header->payload_length; + trailer = record + data_size; + + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, record, data_size); + ret = crc32cmp(trailer->checksum, crc); + + return ret; +} + +#define MAX_READ_BYTES (RRDENG_BLOCK_SIZE * 32) /* no record should be over 128KiB in this version */ + +/* + * Iterates metadata log file records and creates database objects (host/chart/dimension) + */ +static void iterate_records(struct metadata_logfile *metalogfile) +{ + uint32_t file_size, pos, bytes_remaining, record_size; + void *buf; + struct rrdeng_metalog_record_header *header; + struct metalog_instance *ctx = metalogfile->ctx; + struct metalog_pluginsd_state *state = ctx->metalog_parser_object->private; + const size_t min_header_size = offsetof(struct rrdeng_metalog_record_header, header_length) + + sizeof(header->header_length); + + file_size = metalogfile->pos; + state->metalogfile = metalogfile; + + buf = mallocz(MAX_READ_BYTES); + + for (pos = sizeof(struct rrdeng_metalog_sb) ; pos < file_size ; pos += record_size) { + bytes_remaining = file_size - pos; + if (bytes_remaining < min_header_size) { + error("%s: unexpected end of file in metadata logfile %u-%u.", __func__, metalogfile->starting_fileno, + metalogfile->fileno); + break; + } + if (metalogfile_read(metalogfile, buf, min_header_size, pos) < 0) + break; + header = (struct rrdeng_metalog_record_header *)buf; + if (METALOG_STORE_PADDING == header->type) { + info("%s: Skipping padding in metadata logfile %u-%u.", __func__, metalogfile->starting_fileno, + metalogfile->fileno); + record_size = ALIGN_BYTES_FLOOR(pos + RRDENG_BLOCK_SIZE) - pos; + continue; + } + if (metalogfile_read(metalogfile, buf + min_header_size, sizeof(*header) - min_header_size, + pos + min_header_size) < 0) + break; + record_size = header->header_length + header->payload_length + sizeof(struct rrdeng_metalog_record_trailer); + if (header->header_length < min_header_size || record_size > bytes_remaining) { + error("%s: Corrupted record in metadata logfile %u-%u.", __func__, metalogfile->starting_fileno, + metalogfile->fileno); + break; + } + if (record_size > MAX_READ_BYTES) { + error("%s: Record is too long (%u bytes) in metadata logfile %u-%u.", __func__, record_size, + metalogfile->starting_fileno, metalogfile->fileno); + continue; + } + if (metalogfile_read(metalogfile, buf + sizeof(*header), record_size - sizeof(*header), + pos + sizeof(*header)) < 0) + break; + if (metadata_record_integrity_check(buf)) { + error("%s: Record at offset %"PRIu32" was read from disk. CRC32 check: FAILED", __func__, pos); + continue; + } + debug(D_METADATALOG, "%s: Record at offset %"PRIu32" was read from disk. CRC32 check: SUCCEEDED", __func__, + pos); + + replay_record(metalogfile, header, buf + header->header_length); + if (!uuid_is_null(state->uuid)) { /* It's a valid object */ + struct metalog_record record; + + uuid_copy(record.uuid, state->uuid); + mlf_record_insert(metalogfile, &record); + uuid_clear(state->uuid); /* Clear state for parsing of next record */ + } + } + + freez(buf); +} + +int load_metadata_logfile(struct metalog_instance *ctx, struct metadata_logfile *metalogfile) +{ + uv_fs_t req; + uv_file file; + int ret, fd, error; + uint64_t file_size; + char path[RRDENG_PATH_MAX]; + + generate_metadata_logfile_path(metalogfile, path, sizeof(path)); + fd = open_file_buffered_io(path, O_RDWR, &file); + if (fd < 0) { + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + return fd; + } + info("Loading metadata log \"%s\".", path); + + ret = check_file_properties(file, &file_size, sizeof(struct rrdeng_metalog_sb)); + if (ret) + goto error; + + ret = check_metadata_logfile_superblock(file); + if (ret) + goto error; + ctx->stats.io_read_bytes += sizeof(struct rrdeng_jf_sb); + ++ctx->stats.io_read_requests; + + metalogfile->file = file; + metalogfile->pos = file_size; + + iterate_records(metalogfile); + + info("Metadata log \"%s\" loaded (size:%"PRIu64").", path, file_size); + return 0; + +error: + error = ret; + ret = uv_fs_close(NULL, &req, file, NULL); + if (ret < 0) { + error("uv_fs_close(%s): %s", path, uv_strerror(ret)); + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + } + uv_fs_req_cleanup(&req); + return error; +} + +void init_metadata_record_log(struct metadata_record_commit_log *records_log) +{ + records_log->buf = NULL; + records_log->buf_pos = 0; + records_log->record_id = 1; +} + +static int scan_metalog_files_cmp(const void *a, const void *b) +{ + struct metadata_logfile *file1, *file2; + char path1[RRDENG_PATH_MAX], path2[RRDENG_PATH_MAX]; + + file1 = *(struct metadata_logfile **)a; + file2 = *(struct metadata_logfile **)b; + generate_metadata_logfile_path(file1, path1, sizeof(path1)); + generate_metadata_logfile_path(file2, path2, sizeof(path2)); + return strcmp(path1, path2); +} + +/* Returns number of metadata logfiles that were loaded or < 0 on error */ +static int scan_metalog_files(struct metalog_instance *ctx) +{ + int ret; + unsigned starting_no, no, matched_files, i, failed_to_load; + static uv_fs_t req; + uv_dirent_t dent; + struct metadata_logfile **metalogfiles, *metalogfile; + char *dbfiles_path = ctx->rrdeng_ctx->dbfiles_path; + + ret = uv_fs_scandir(NULL, &req, dbfiles_path, 0, NULL); + if (ret < 0) { + assert(req.result < 0); + uv_fs_req_cleanup(&req); + error("uv_fs_scandir(%s): %s", dbfiles_path, uv_strerror(ret)); + ++ctx->stats.fs_errors; + rrd_stat_atomic_add(&global_fs_errors, 1); + return ret; + } + info("Found %d files in path %s", ret, dbfiles_path); + + metalogfiles = callocz(MIN(ret, MAX_DATAFILES), sizeof(*metalogfiles)); + for (matched_files = 0 ; UV_EOF != uv_fs_scandir_next(&req, &dent) && matched_files < MAX_DATAFILES ; ) { + info("Scanning file \"%s/%s\"", dbfiles_path, dent.name); + ret = sscanf(dent.name, METALOG_PREFIX METALOG_FILE_NUMBER_SCAN_TMPL METALOG_EXTENSION, &starting_no, &no); + if (2 == ret) { + info("Matched file \"%s/%s\"", dbfiles_path, dent.name); + metalogfile = mallocz(sizeof(*metalogfile)); + metadata_logfile_init(metalogfile, ctx, starting_no, no); + metalogfiles[matched_files++] = metalogfile; + } + } + uv_fs_req_cleanup(&req); + + if (0 == matched_files) { + freez(metalogfiles); + return 0; + } + if (matched_files == MAX_DATAFILES) { + error("Warning: hit maximum database engine file limit of %d files", MAX_DATAFILES); + } + qsort(metalogfiles, matched_files, sizeof(*metalogfiles), scan_metalog_files_cmp); + ret = compaction_failure_recovery(ctx, metalogfiles, &matched_files); + if (ret) { /* If the files are corrupted fail */ + for (i = 0 ; i < matched_files ; ++i) { + freez(metalogfiles[i]); + } + freez(metalogfiles); + return UV_EINVAL; + } + ctx->last_fileno = metalogfiles[matched_files - 1]->fileno; + + struct plugind cd = { + .enabled = 1, + .update_every = 0, + .pid = 0, + .serial_failures = 0, + .successful_collections = 0, + .obsolete = 0, + .started_t = INVALID_TIME, + .next = NULL, + .version = 0, + }; + + struct metalog_pluginsd_state metalog_parser_state; + metalog_pluginsd_state_init(&metalog_parser_state, ctx); + + PARSER_USER_OBJECT metalog_parser_object; + metalog_parser_object.enabled = cd.enabled; + metalog_parser_object.host = ctx->rrdeng_ctx->host; + metalog_parser_object.cd = &cd; + metalog_parser_object.trust_durations = 0; + metalog_parser_object.private = &metalog_parser_state; + + PARSER *parser = parser_init(metalog_parser_object.host, &metalog_parser_object, NULL, PARSER_INPUT_SPLIT); + if (unlikely(!parser)) { + error("Failed to initialize metadata log parser."); + failed_to_load = matched_files; + goto after_failed_to_parse; + } + parser_add_keyword(parser, PLUGINSD_KEYWORD_GUID, pluginsd_guid); + parser_add_keyword(parser, PLUGINSD_KEYWORD_CONTEXT, pluginsd_context); + parser_add_keyword(parser, PLUGINSD_KEYWORD_TOMBSTONE, pluginsd_tombstone); + parser->plugins_action->dimension_action = &metalog_pluginsd_dimension_action; + parser->plugins_action->chart_action = &metalog_pluginsd_chart_action; + parser->plugins_action->guid_action = &metalog_pluginsd_guid_action; + parser->plugins_action->context_action = &metalog_pluginsd_context_action; + parser->plugins_action->tombstone_action = &metalog_pluginsd_tombstone_action; + + metalog_parser_object.parser = parser; + ctx->metalog_parser_object = &metalog_parser_object; + + for (failed_to_load = 0, i = 0 ; i < matched_files ; ++i) { + metalogfile = metalogfiles[i]; + ret = load_metadata_logfile(ctx, metalogfile); + if (0 != ret) { + freez(metalogfile); + ++failed_to_load; + break; + } + metadata_logfile_list_insert(&ctx->metadata_logfiles, metalogfile); + rrd_atomic_fetch_add(&ctx->disk_space, metalogfile->pos); + } + debug(D_METADATALOG, "PARSER ended"); + + parser_destroy(parser); + + size_t count = metalog_parser_object.count; + + debug(D_METADATALOG, "Parsing count=%u", (unsigned)count); +after_failed_to_parse: + + freez(metalogfiles); + if (failed_to_load) { + error("%u metadata log files failed to load.", failed_to_load); + finalize_metalog_files(ctx); + return UV_EIO; + } + + return matched_files; +} + +/* Creates a metadata log file */ +int add_new_metadata_logfile(struct metalog_instance *ctx, struct metadata_logfile_list *logfile_list, + unsigned starting_fileno, unsigned fileno) +{ + struct metadata_logfile *metalogfile; + int ret; + char path[RRDENG_PATH_MAX]; + + info("Creating new metadata log file in path %s", ctx->rrdeng_ctx->dbfiles_path); + metalogfile = mallocz(sizeof(*metalogfile)); + metadata_logfile_init(metalogfile, ctx, starting_fileno, fileno); + ret = create_metadata_logfile(metalogfile); + if (!ret) { + generate_metadata_logfile_path(metalogfile, path, sizeof(path)); + info("Created metadata log file \"%s\".", path); + } else { + freez(metalogfile); + return ret; + } + metadata_logfile_list_insert(logfile_list, metalogfile); + rrd_atomic_fetch_add(&ctx->disk_space, metalogfile->pos); + + return 0; +} + +/* Return 0 on success. */ +int init_metalog_files(struct metalog_instance *ctx) +{ + int ret; + char *dbfiles_path = ctx->rrdeng_ctx->dbfiles_path; + + ret = scan_metalog_files(ctx); + if (ret < 0) { + error("Failed to scan path \"%s\".", dbfiles_path); + return ret; + } else if (0 == ret) { + info("Metadata log files not found, creating in path \"%s\".", dbfiles_path); + ret = add_new_metadata_logfile(ctx, &ctx->metadata_logfiles, 0, 1); + if (ret) { + error("Failed to create metadata log file in path \"%s\".", dbfiles_path); + return ret; + } + ctx->last_fileno = 1; + } + + return 0; +} + +void finalize_metalog_files(struct metalog_instance *ctx) +{ + struct metadata_logfile *metalogfile, *next_metalogfile; + struct metalog_record_block *record_block, *next_record_block; + + for (metalogfile = ctx->metadata_logfiles.first ; metalogfile != NULL ; metalogfile = next_metalogfile) { + next_metalogfile = metalogfile->next; + + for (record_block = metalogfile->records.first ; record_block != NULL ; record_block = next_record_block) { + next_record_block = record_block->next; + freez(record_block); + } + close_metadata_logfile(metalogfile); + freez(metalogfile); + } +} diff --git a/database/engine/metadata_log/logfile.h b/database/engine/metadata_log/logfile.h new file mode 100644 index 0000000000..d4e26da883 --- /dev/null +++ b/database/engine/metadata_log/logfile.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_LOGFILE_H +#define NETDATA_LOGFILE_H + +#include "metadatalogprotocol.h" +#include "../rrdengine.h" + +/* Forward declarations */ +struct metadata_logfile; +struct metalog_worker_config; + +#define METALOG_PREFIX "metadatalog-" +#define METALOG_EXTENSION ".mlf" + +#define MAX_METALOGFILE_SIZE (524288LU) + +/* Deletions are ignored during compaction, so only creation UUIDs are stored */ +struct metalog_record { + uuid_t uuid; +}; + +#define MAX_METALOG_RECORDS_PER_BLOCK (1024LU) +struct metalog_record_block { + uint64_t file_offset; + uint32_t io_size; + + struct metalog_record record_array[MAX_METALOG_RECORDS_PER_BLOCK]; + uint16_t records_nr; + + struct metalog_record_block *next; +}; + +struct metalog_records { + /* the record block list is sorted based on disk offset */ + struct metalog_record_block *first; + struct metalog_record_block *last; + struct { + struct metalog_record_block *current; + uint16_t record_i; + } iterator; +}; + +/* only one event loop is supported for now */ +struct metadata_logfile { + unsigned fileno; /* Starts at 1 */ + unsigned starting_fileno; /* 0 for normal files, staring number during compaction */ + uv_file file; + uint64_t pos; + struct metalog_instance *ctx; + struct metalog_records records; + struct metadata_logfile *next; +}; + +struct metadata_logfile_list { + struct metadata_logfile *first; /* oldest */ + struct metadata_logfile *last; /* newest */ +}; + +struct metadata_record_commit_log { + uint64_t record_id; + + /* outstanding record buffer */ + void *buf; + unsigned buf_pos; + unsigned buf_size; +}; + +extern void mlf_record_insert(struct metadata_logfile *metalogfile, struct metalog_record *record); +extern struct metalog_record *mlf_record_get_first(struct metadata_logfile *metalogfile); +extern struct metalog_record *mlf_record_get_next(struct metadata_logfile *metalogfile); +extern void mlf_flush_records_buffer(struct metalog_worker_config *wc, struct metadata_record_commit_log *records_log, + struct metadata_logfile_list *metadata_logfiles); +extern void *mlf_get_records_buffer(struct metalog_worker_config *wc, struct metadata_record_commit_log *records_log, + struct metadata_logfile_list *metadata_logfiles, unsigned size); +extern void metadata_logfile_list_insert(struct metadata_logfile_list *metadata_logfiles, + struct metadata_logfile *metalogfile); +extern void metadata_logfile_list_delete(struct metadata_logfile_list *metadata_logfiles, + struct metadata_logfile *metalogfile); +extern void generate_metadata_logfile_path(struct metadata_logfile *metadatalog, char *str, size_t maxlen); +extern void metadata_logfile_init(struct metadata_logfile *metadatalog, struct metalog_instance *ctx, + unsigned tier, unsigned fileno); +extern int rename_metadata_logfile(struct metadata_logfile *metalogfile, unsigned new_starting_fileno, + unsigned new_fileno); +extern int close_metadata_logfile(struct metadata_logfile *metadatalog); +extern int unlink_metadata_logfile(struct metadata_logfile *metalogfile); +extern int destroy_metadata_logfile(struct metadata_logfile *metalogfile); +extern int create_metadata_logfile(struct metadata_logfile *metalogfile); +extern int load_metadata_logfile(struct metalog_instance *ctx, struct metadata_logfile *logfile); +extern void init_metadata_record_log(struct metadata_record_commit_log *records_log); +extern int add_new_metadata_logfile(struct metalog_instance *ctx, struct metadata_logfile_list *logfile_list, + unsigned tier, unsigned fileno); +extern int init_metalog_files(struct metalog_instance *ctx); +extern void finalize_metalog_files(struct metalog_instance *ctx); + + +#endif /* NETDATA_LOGFILE_H */ diff --git a/database/engine/metadata_log/metadatalog.c b/database/engine/metadata_log/metadatalog.c new file mode 100644 index 0000000000..b436cb105a --- /dev/null +++ b/database/engine/metadata_log/metadatalog.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#define NETDATA_RRD_INTERNALS + +#include "metadatalog.h" + +static void sanity_check(void) +{ + /* Magic numbers must fit in the super-blocks */ + BUILD_BUG_ON(strlen(RRDENG_METALOG_MAGIC) > RRDENG_MAGIC_SZ); + + /* Metadata log file super-block cannot be larger than RRDENG_BLOCK_SIZE */ + BUILD_BUG_ON(RRDENG_METALOG_SB_PADDING_SZ < 0); + + /* Object duplication factor cannot be less than 1, or too close to 1 */ + BUILD_BUG_ON(MAX_DUPLICATION_PERCENTAGE < 110); +} + +char *get_metalog_statistics(struct metalog_instance *ctx, char *str, size_t size) +{ + snprintfz(str, size, + "io_write_bytes: %ld\n" + "io_write_requests: %ld\n" + "io_read_bytes: %ld\n" + "io_read_requests: %ld\n" + "io_write_record_bytes: %ld\n" + "io_write_records: %ld\n" + "io_read_record_bytes: %ld\n" + "io_read_records: %ld\n" + "metadata_logfile_creations: %ld\n" + "metadata_logfile_deletions: %ld\n" + "io_errors: %ld\n" + "fs_errors: %ld\n", + (long)ctx->stats.io_write_bytes, + (long)ctx->stats.io_write_requests, + (long)ctx->stats.io_read_bytes, + (long)ctx->stats.io_read_requests, + (long)ctx->stats.io_write_record_bytes, + (long)ctx->stats.io_write_records, + (long)ctx->stats.io_read_record_bytes, + (long)ctx->stats.io_read_records, + (long)ctx->stats.metadata_logfile_creations, + (long)ctx->stats.metadata_logfile_deletions, + (long)ctx->stats.io_errors, + (long)ctx->stats.fs_errors + ); + return str; +} + +/* The buffer must not be empty */ +void metalog_commit_record(struct metalog_instance *ctx, BUFFER *buffer, enum metalog_opcode opcode, uuid_t *uuid, + int compacting) +{ + struct metalog_cmd cmd; + + assert(buffer_strlen(buffer)); + assert(opcode == METALOG_COMMIT_CREATION_RECORD || opcode == METALOG_COMMIT_DELETION_RECORD); + + cmd.opcode = opcode; + cmd.record_io_descr.buffer = buffer; + cmd.record_io_descr.compacting = compacting; + if (!uuid) + uuid_clear(cmd.record_io_descr.uuid); + else + uuid_copy(cmd.record_io_descr.uuid, *uuid); + metalog_enq_cmd(&ctx->worker_config, &cmd); +} + +static void commit_record(struct metalog_worker_config* wc, struct metalog_record_io_descr *io_descr, uint8_t type) +{ + struct metalog_instance *ctx = wc->ctx; + unsigned payload_length, size_bytes; + void *buf, *mlf_payload; + /* persistent structures */ + struct rrdeng_metalog_record_header *mlf_header; + struct rrdeng_metalog_record_trailer *mlf_trailer; + uLong crc; + + payload_length = buffer_strlen(io_descr->buffer); + size_bytes = sizeof(*mlf_header) + payload_length + sizeof(*mlf_trailer); + + if (io_descr->compacting) + buf = mlf_get_records_buffer(wc, &ctx->compaction_state.records_log, + &ctx->compaction_state.new_metadata_logfiles, size_bytes); + else + buf = mlf_get_records_buffer(wc, &ctx->records_log, &ctx->metadata_logfiles, size_bytes); + + mlf_header = buf; + mlf_header->type = type; + mlf_header->header_length = sizeof(*mlf_header); + mlf_header->payload_length = payload_length; + + mlf_payload = buf + sizeof(*mlf_header); + memcpy(mlf_payload, buffer_tostring(io_descr->buffer), payload_length); + + mlf_trailer = buf + sizeof(*mlf_header) + payload_length; + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, buf, sizeof(*mlf_header) + payload_length); + crc32set(mlf_trailer->checksum, crc); + + buffer_free(io_descr->buffer); +} + +static void do_commit_record(struct metalog_worker_config* wc, uint8_t type, void *data) +{ + struct metalog_record_io_descr *io_descr = (struct metalog_record_io_descr *)data; + switch (type) { + case METALOG_CREATE_OBJECT: + if (!uuid_is_null(io_descr->uuid)) { /* It's a valid object */ + struct metalog_record record; + + uuid_copy(record.uuid, io_descr->uuid); + if (io_descr->compacting) + mlf_record_insert(wc->ctx->compaction_state.new_metadata_logfiles.last, &record); + else + mlf_record_insert(wc->ctx->metadata_logfiles.last, &record); + } /* fall through */ + case METALOG_DELETE_OBJECT: + commit_record(wc, (struct metalog_record_io_descr *)data, type); + break; + default: + fatal("Unknown metadata log file record type, possible memory corruption."); + break; + } +} + +/* Only creates a new metadata file and links it to the metadata log if the last one is non empty. */ +void metalog_try_link_new_metadata_logfile(struct metalog_worker_config *wc) +{ + struct metalog_instance *ctx = wc->ctx; + struct metadata_logfile *metalogfile; + int ret; + + metalogfile = ctx->metadata_logfiles.last; + if (metalogfile->records.first) { /* it has records */ + /* Finalize metadata log file and create a new one */ + mlf_flush_records_buffer(wc, &ctx->records_log, &ctx->metadata_logfiles); + ret = add_new_metadata_logfile(ctx, &ctx->metadata_logfiles, 0, ctx->last_fileno + 1); + if (likely(!ret)) { + ++ctx->last_fileno; + } + } +} + +void metalog_test_quota(struct metalog_worker_config *wc) +{ + struct metalog_instance *ctx = wc->ctx; + struct metadata_logfile *metalogfile; + unsigned current_size; + uint8_t only_one_metalogfile; + + metalogfile = ctx->metadata_logfiles.last; + current_size = metalogfile->pos; + if (unlikely(current_size >= MAX_METALOGFILE_SIZE)) { + metalog_try_link_new_metadata_logfile(wc); + } + + metalogfile = ctx->metadata_logfiles.last; + only_one_metalogfile = (metalogfile == ctx->metadata_logfiles.first) ? 1 : 0; + debug(D_METADATALOG, "records=%lu objects=%lu", (long unsigned)ctx->records_nr, + (long unsigned)ctx->rrdeng_ctx->host->objects_nr); + if (unlikely(!only_one_metalogfile && + ctx->records_nr > (ctx->rrdeng_ctx->host->objects_nr * (uint64_t)MAX_DUPLICATION_PERCENTAGE) / 100) && + NO_QUIESCE == ctx->quiesce) { + metalog_do_compaction(wc); + } +} + +static inline int metalog_threads_alive(struct metalog_worker_config* wc) +{ + if (wc->cleanup_thread_compacting_files) { + return 1; + } + + return 0; +} + +static void metalog_cleanup_finished_threads(struct metalog_worker_config *wc) +{ + struct metalog_instance *ctx = wc->ctx; + + if (unlikely(wc->cleanup_thread_compacting_files)) { + after_compact_old_records(wc); + } + if (unlikely(SET_QUIESCE == ctx->quiesce && !metalog_threads_alive(wc))) { + ctx->quiesce = QUIESCED; + complete(&ctx->metalog_completion); + } +} + +static void metalog_init_cmd_queue(struct metalog_worker_config *wc) +{ + wc->cmd_queue.head = wc->cmd_queue.tail = 0; + wc->queue_size = 0; + assert(0 == uv_cond_init(&wc->cmd_cond)); + assert(0 == uv_mutex_init(&wc->cmd_mutex)); +} + +void metalog_enq_cmd(struct metalog_worker_config *wc, struct metalog_cmd *cmd) +{ + unsigned queue_size; + + /* wait for free space in queue */ + uv_mutex_lock(&wc->cmd_mutex); + while ((queue_size = wc->queue_size) == METALOG_CMD_Q_MAX_SIZE) { + uv_cond_wait(&wc->cmd_cond, &wc->cmd_mutex); + } + assert(queue_size < METALOG_CMD_Q_MAX_SIZE); + /* enqueue command */ + wc->cmd_queue.cmd_array[wc->cmd_queue.tail] = *cmd; + wc->cmd_queue.tail = wc->cmd_queue.tail != METALOG_CMD_Q_MAX_SIZE - 1 ? + wc->cmd_queue.tail + 1 : 0; + wc->queue_size = queue_size + 1; + uv_mutex_unlock(&wc->cmd_mutex); + + /* wake up event loop */ + assert(0 == uv_async_send(&wc->async)); +} + +struct metalog_cmd metalog_deq_cmd(struct metalog_worker_config *wc) +{ + struct metalog_cmd ret; + unsigned queue_size; + + uv_mutex_lock(&wc->cmd_mutex); + queue_size = wc->queue_size; + if (queue_size == 0) { + ret.opcode = METALOG_NOOP; + } else { + /* dequeue command */ + ret = wc->cmd_queue.cmd_array[wc->cmd_queue.head]; + if (queue_size == 1) { + wc->cmd_queue.head = wc->cmd_queue.tail = 0; + } else { + wc->cmd_queue.head = wc->cmd_queue.head != RRDENG_CMD_Q_MAX_SIZE - 1 ? + wc->cmd_queue.head + 1 : 0; + } + wc->queue_size = queue_size - 1; + + /* wake up producers */ + uv_cond_signal(&wc->cmd_cond); + } + uv_mutex_unlock(&wc->cmd_mutex); + + return ret; +} + +static void async_cb(uv_async_t *handle) +{ + uv_stop(handle->loop); + uv_update_time(handle->loop); + debug(D_METADATALOG, "%s called, active=%d.", __func__, uv_is_active((uv_handle_t *)handle)); +} + +/* Flushes metadata log when timer expires */ +#define TIMER_PERIOD_MS (5000) + +static void timer_cb(uv_timer_t* handle) +{ + struct metalog_worker_config* wc = handle->data; + struct metalog_instance *ctx = wc->ctx; + + uv_stop(handle->loop); + uv_update_time(handle->loop); + metalog_test_quota(wc); + debug(D_METADATALOG, "%s: timeout reached.", __func__); +#ifdef NETDATA_INTERNAL_CHECKS + { + char buf[4096]; + debug(D_METADATALOG, "%s", get_metalog_statistics(wc->ctx, buf, sizeof(buf))); + } +#endif + mlf_flush_records_buffer(wc, &ctx->records_log, &ctx->metadata_logfiles); +} + +#define MAX_CMD_BATCH_SIZE (256) + +void metalog_worker(void* arg) +{ + struct metalog_worker_config *wc = arg; + struct metalog_instance *ctx = wc->ctx; + uv_loop_t* loop; + int shutdown, ret; + enum metalog_opcode opcode; + uv_timer_t timer_req; + struct metalog_cmd cmd; + unsigned cmd_batch_size; + + metalog_init_cmd_queue(wc); + + loop = wc->loop = mallocz(sizeof(uv_loop_t)); + ret = uv_loop_init(loop); + if (ret) { + error("uv_loop_init(): %s", uv_strerror(ret)); + goto error_after_loop_init; + } + loop->data = wc; + + ret = uv_async_init(wc->loop, &wc->async, async_cb); + if (ret) { + error("uv_async_init(): %s", uv_strerror(ret)); + goto error_after_async_init; + } + wc->async.data = wc; + + wc->now_compacting_files = NULL; + wc->cleanup_thread_compacting_files = 0; + + /* quota check timer */ + ret = uv_timer_init(loop, &timer_req); + if (ret) { + error("uv_timer_init(): %s", uv_strerror(ret)); + goto error_after_timer_init; + } + timer_req.data = wc; + + wc->error = 0; + /* wake up initialization thread */ + complete(&ctx->metalog_completion); + + assert(0 == uv_timer_start(&timer_req, timer_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS)); + shutdown = 0; + while (likely(shutdown == 0 || metalog_threads_alive(wc))) { + uv_run(loop, UV_RUN_DEFAULT); + metalog_cleanup_finished_threads(wc); + + /* wait for commands */ + cmd_batch_size = 0; + do { + /* + * Avoid starving the loop when there are too many commands coming in. + * timer_cb will interrupt the loop again to allow serving more commands. + */ + if (unlikely(cmd_batch_size >= MAX_CMD_BATCH_SIZE)) + break; + + cmd = metalog_deq_cmd(wc); + opcode = cmd.opcode; + ++cmd_batch_size; + + switch (opcode) { + case METALOG_NOOP: + /* the command queue was empty, do nothing */ + break; + case METALOG_SHUTDOWN: + shutdown = 1; + break; + case METALOG_QUIESCE: + ctx->quiesce = SET_QUIESCE; + assert(0 == uv_timer_stop(&timer_req)); + uv_close((uv_handle_t *)&timer_req, NULL); + mlf_flush_records_buffer(wc, &ctx->records_log, &ctx->metadata_logfiles); + if (!metalog_threads_alive(wc)) { + ctx->quiesce = QUIESCED; + complete(&ctx->metalog_completion); + } + break; + case METALOG_COMMIT_CREATION_RECORD: + do_commit_record(wc, METALOG_CREATE_OBJECT, &cmd.record_io_descr); + break; + case METALOG_COMMIT_DELETION_RECORD: + do_commit_record(wc, METALOG_DELETE_OBJECT, &cmd.record_io_descr); + break; + case METALOG_COMPACTION_FLUSH: + mlf_flush_records_buffer(wc, &ctx->compaction_state.records_log, + &ctx->compaction_state.new_metadata_logfiles); + complete(cmd.record_io_descr.completion); + break; + default: + debug(D_METADATALOG, "%s: default.", __func__); + break; + } + } while (opcode != METALOG_NOOP); + } + + /* cleanup operations of the event loop */ + info("Shutting down RRD metadata log event loop."); + + /* + * uv_async_send after uv_close does not seem to crash in linux at the moment, + * it is however undocumented behaviour and we need to be aware if this becomes + * an issue in the future. + */ + uv_close((uv_handle_t *)&wc->async, NULL); + + mlf_flush_records_buffer(wc, &ctx->records_log, &ctx->metadata_logfiles); + uv_run(loop, UV_RUN_DEFAULT); + + info("Shutting down RRD metadata log loop complete."); + /* TODO: don't let the API block by waiting to enqueue commands */ + uv_cond_destroy(&wc->cmd_cond); +/* uv_mutex_destroy(&wc->cmd_mutex); */ + assert(0 == uv_loop_close(loop)); + freez(loop); + + return; + +error_after_timer_init: + uv_close((uv_handle_t *)&wc->async, NULL); +error_after_async_init: + assert(0 == uv_loop_close(loop)); +error_after_loop_init: + freez(loop); + + wc->error = UV_EAGAIN; + /* wake up initialization thread */ + complete(&ctx->metalog_completion); +} diff --git a/database/engine/metadata_log/metadatalog.h b/database/engine/metadata_log/metadatalog.h new file mode 100644 index 0000000000..004eec7ee3 --- /dev/null +++ b/database/engine/metadata_log/metadatalog.h @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_METADATALOG_H +#define NETDATA_METADATALOG_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include "../rrdengine.h" +#include "metadatalogprotocol.h" +#include "logfile.h" +#include "metadatalogapi.h" +#include "compaction.h" + +/* Forward declerations */ +struct metalog_instance; +struct parser_user_object; + +#define MAX_PAGES_PER_EXTENT (64) /* TODO: can go higher only when journal supports bigger than 4KiB transactions */ + +#define METALOG_FILE_NUMBER_SCAN_TMPL "%5u-%5u" +#define METALOG_FILE_NUMBER_PRINT_TMPL "%5.5u-%5.5u" + +#define MAX_DUPLICATION_PERCENTAGE 150 /* the maximum duplication factor of objects in metadata log records */ + +typedef enum { + METALOG_STATUS_UNINITIALIZED = 0, + METALOG_STATUS_INITIALIZING, + METALOG_STATUS_INITIALIZED +} metalog_state_t; + +struct metalog_record_io_descr { + BUFFER *buffer; + struct completion *completion; + int compacting; /* When 0 append at the end of the metadata log file list. + When 1 append to the temporary compaction metadata log file list. */ + uuid_t uuid; +}; + +enum metalog_opcode { + /* can be used to return empty status or flush the command queue */ + METALOG_NOOP = 0, + + METALOG_SHUTDOWN, + METALOG_COMMIT_CREATION_RECORD, + METALOG_COMMIT_DELETION_RECORD, + METALOG_COMPACTION_FLUSH, + METALOG_QUIESCE, + + METALOG_MAX_OPCODE +}; + +struct metalog_cmd { + enum metalog_opcode opcode; + struct metalog_record_io_descr record_io_descr; +}; + +#define METALOG_CMD_Q_MAX_SIZE (2048) + +struct metalog_cmdqueue { + unsigned head, tail; + struct metalog_cmd cmd_array[METALOG_CMD_Q_MAX_SIZE]; +}; + +struct metalog_worker_config { + struct metalog_instance *ctx; + + uv_thread_t thread; + uv_loop_t *loop; + uv_async_t async; + + /* metadata log file comapaction thread */ + uv_thread_t *now_compacting_files; + unsigned long cleanup_thread_compacting_files; /* set to 0 when now_compacting_files is still running */ + + /* FIFO command queue */ + uv_mutex_t cmd_mutex; + uv_cond_t cmd_cond; + volatile unsigned queue_size; + struct metalog_cmdqueue cmd_queue; + + int error; +}; + +/* + * Debug statistics not used by code logic. + * They only describe operations since DB engine instance load time. + */ +struct metalog_statistics { + rrdeng_stats_t io_write_bytes; + rrdeng_stats_t io_write_requests; + rrdeng_stats_t io_read_bytes; + rrdeng_stats_t io_read_requests; + rrdeng_stats_t io_write_record_bytes; + rrdeng_stats_t io_write_records; + rrdeng_stats_t io_read_record_bytes; + rrdeng_stats_t io_read_records; + rrdeng_stats_t metadata_logfile_creations; + rrdeng_stats_t metadata_logfile_deletions; + rrdeng_stats_t io_errors; + rrdeng_stats_t fs_errors; +}; + +struct metalog_instance { + struct rrdengine_instance *rrdeng_ctx; + struct metalog_worker_config worker_config; + struct completion metalog_completion; + struct metadata_record_commit_log records_log; + struct metadata_logfile_list metadata_logfiles; + struct parser_user_object *metalog_parser_object; + struct logfile_compaction_state compaction_state; + uint32_t current_compaction_id; /* Every compaction run increments this by 1 */ + unsigned long disk_space; + unsigned long records_nr; + unsigned last_fileno; /* newest index of metadata log file */ + + uint8_t quiesce; /* + * 0 initial state when all operations function normally + * 1 set it before shutting down the instance, quiesce long running operations + * 2 is set after all threads have finished running + */ + + struct metalog_statistics stats; +}; + +extern void metalog_commit_record(struct metalog_instance *ctx, BUFFER *buffer, enum metalog_opcode opcode, + uuid_t *uuid, int compacting); + extern int init_metadata_logfiles(struct metalog_instance *ctx); +extern void finalize_metadata_logfiles(struct metalog_instance *ctx); +extern void metalog_try_link_new_metadata_logfile(struct metalog_worker_config *wc); +extern void metalog_test_quota(struct metalog_worker_config *wc); +extern void metalog_worker(void* arg); +extern void metalog_enq_cmd(struct metalog_worker_config *wc, struct metalog_cmd *cmd); +extern struct metalog_cmd metalog_deq_cmd(struct metalog_worker_config *wc); + +#endif /* NETDATA_METADATALOG_H */ diff --git a/database/engine/metadata_log/metadatalogapi.c b/database/engine/metadata_log/metadatalogapi.c new file mode 100755 index 0000000000..6bd09b4d9e --- /dev/null +++ b/database/engine/metadata_log/metadatalogapi.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#define NETDATA_RRD_INTERNALS + +#include "metadatalog.h" + +static inline int metalog_is_initialized(struct metalog_instance *ctx) +{ + return ctx->rrdeng_ctx->metalog_ctx != NULL; +} + +static inline void metalog_commit_creation_record(struct metalog_instance *ctx, BUFFER *buffer, uuid_t *uuid) +{ + metalog_commit_record(ctx, buffer, METALOG_COMMIT_CREATION_RECORD, uuid, 0); +} + +static inline void metalog_commit_deletion_record(struct metalog_instance *ctx, BUFFER *buffer) +{ + metalog_commit_record(ctx, buffer, METALOG_COMMIT_DELETION_RECORD, NULL, 0); +} + +BUFFER *metalog_update_host_buffer(RRDHOST *host) +{ + BUFFER *buffer; + buffer = buffer_create(4096); /* This will be freed after it has been committed to the metadata log buffer */ + + rrdhost_rdlock(host); + + buffer_sprintf(buffer, + "HOST \"%s\" \"%s\" \"%s\" %d \"%s\" \"%s\" \"%s\"\n", +// "\"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"" /* system */ +// "\"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"", /* info */ + host->machine_guid, + host->hostname, + host->registry_hostname, + default_rrd_update_every, + host->os, + host->timezone, + (host->tags) ? host->tags : ""); + + netdata_rwlock_rdlock(&host->labels_rwlock); + struct label *labels = host->labels; + while (labels) { + buffer_sprintf(buffer + , "LABEL \"%s\" = %d %s\n" + , labels->key + , (int)labels->label_source + , labels->value); + + labels = labels->next; + } + netdata_rwlock_unlock(&host->labels_rwlock); + + buffer_strcat(buffer, "OVERWRITE labels\n"); + + rrdhost_unlock(host); + return buffer; +} + +void metalog_commit_update_host(RRDHOST *host) +{ + struct metalog_instance *ctx; + BUFFER *buffer; + + /* Metadata are only available with dbengine */ + if (!host->rrdeng_ctx) + return; + + ctx = host->rrdeng_ctx->metalog_ctx; + if (!ctx) /* metadata log has not been initialized yet */ + return; + + buffer = metalog_update_host_buffer(host); + + metalog_commit_creation_record(ctx, buffer, &host->host_uuid); +} + +/* compaction_id 0 means it was not called by compaction logic */ +BUFFER *metalog_update_chart_buffer(RRDSET *st, uint32_t compaction_id) +{ + BUFFER *buffer; + RRDHOST *host = st->rrdhost; + + buffer = buffer_create(1024); /* This will be freed after it has been committed to the metadata log buffer */ + + rrdset_rdlock(st); + + buffer_sprintf(buffer, "CONTEXT %s\n", host->machine_guid); + + char uuid_str[37]; + uuid_unparse_lower(*st->chart_uuid, uuid_str); + buffer_sprintf(buffer, "GUID %s\n", uuid_str); + + // properly set the name for the remote end to parse it + char *name = ""; + if(likely(st->name)) { + if(unlikely(strcmp(st->id, st->name))) { + // they differ + name = strchr(st->name, '.'); + if(name) + name++; + else + name = ""; + } + } + + // send the chart + buffer_sprintf( + buffer + , "CHART \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" %ld %d \"%s %s %s %s\" \"%s\" \"%s\"\n" + , st->id + , name + , st->title + , st->units + , st->family + , st->context + , 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_DETAIL)?"detail":"" + , rrdset_flag_check(st, RRDSET_FLAG_STORE_FIRST)?"store_first":"" + , rrdset_flag_check(st, RRDSET_FLAG_HIDDEN)?"hidden":"" + , (st->plugin_name)?st->plugin_name:"" + , (st->module_name)?st->module_name:"" + ); + + // send the dimensions + RRDDIM *rd; + rrddim_foreach_read(rd, st) { + char uuid_str[37]; + + uuid_unparse_lower(*rd->state->metric_uuid, uuid_str); + buffer_sprintf(buffer, "GUID %s\n", uuid_str); + + buffer_sprintf( + buffer + , "DIMENSION \"%s\" \"%s\" \"%s\" " COLLECTED_NUMBER_FORMAT " " COLLECTED_NUMBER_FORMAT " \"%s %s %s\"\n" + , rd->id + , rd->name + , rrd_algorithm_name(rd->algorithm) + , rd->multiplier + , rd->divisor + , rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)?"obsolete":"" + , rrddim_flag_check(rd, RRDDIM_FLAG_HIDDEN)?"hidden":"" + , rrddim_flag_check(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS)?"noreset":"" + ); + if (compaction_id && compaction_id > rd->state->compaction_id) { + /* No need to use this dimension again during this compaction cycle */ + rd->state->compaction_id = compaction_id; + } + } + rrdset_unlock(st); + return buffer; +} + +void metalog_commit_update_chart(RRDSET *st) +{ + struct metalog_instance *ctx; + BUFFER *buffer; + RRDHOST *host = st->rrdhost; + + /* Metadata are only available with dbengine */ + if (!host->rrdeng_ctx || RRD_MEMORY_MODE_DBENGINE != st->rrd_memory_mode) + return; + + ctx = host->rrdeng_ctx->metalog_ctx; + if (!ctx) /* metadata log has not been initialized yet */ + return; + + buffer = metalog_update_chart_buffer(st, 0); + + metalog_commit_creation_record(ctx, buffer, st->chart_uuid); +} + +void metalog_commit_delete_chart(RRDSET *st) +{ + struct metalog_instance *ctx; + BUFFER *buffer; + RRDHOST *host = st->rrdhost; + char uuid_str[37]; + + /* Metadata are only available with dbengine */ + if (!host->rrdeng_ctx || RRD_MEMORY_MODE_DBENGINE != st->rrd_memory_mode) + return; + + ctx = host->rrdeng_ctx->metalog_ctx; + if (!ctx) /* metadata log has not been initialized yet */ + return; + buffer = buffer_create(64); /* This will be freed after it has been committed to the metadata log buffer */ + + uuid_unparse_lower(*st->chart_uuid, uuid_str); + buffer_sprintf(buffer, "TOMBSTONE %s\n", uuid_str); + + metalog_commit_deletion_record(ctx, buffer); +} + +BUFFER *metalog_update_dimension_buffer(RRDDIM *rd) +{ + BUFFER *buffer; + RRDSET *st = rd->rrdset; + char uuid_str[37]; + + buffer = buffer_create(128); /* This will be freed after it has been committed to the metadata log buffer */ + + uuid_unparse_lower(*st->chart_uuid, uuid_str); + buffer_sprintf(buffer, "CONTEXT %s\n", uuid_str); + // Activate random GUID + uuid_unparse_lower(*rd->state->metric_uuid, uuid_str); + buffer_sprintf(buffer, "GUID %s\n", uuid_str); + + buffer_sprintf( + buffer + , "DIMENSION \"%s\" \"%s\" \"%s\" " COLLECTED_NUMBER_FORMAT " " COLLECTED_NUMBER_FORMAT " \"%s %s %s\"\n" + , rd->id + , rd->name + , rrd_algorithm_name(rd->algorithm) + , rd->multiplier + , rd->divisor + , rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)?"obsolete":"" + , rrddim_flag_check(rd, RRDDIM_FLAG_HIDDEN)?"hidden":"" + , rrddim_flag_check(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS)?"noreset":"" + ); + return buffer; +} + +void metalog_commit_update_dimension(RRDDIM *rd) +{ + struct metalog_instance *ctx; + BUFFER *buffer; + RRDSET *st = rd->rrdset; + RRDHOST *host = st->rrdhost; + + /* Metadata are only available with dbengine */ + if (!host->rrdeng_ctx || RRD_MEMORY_MODE_DBENGINE != st->rrd_memory_mode) + return; + + ctx = host->rrdeng_ctx->metalog_ctx; + if (!ctx) /* metadata log has not been initialized yet */ + return; + + buffer = metalog_update_dimension_buffer(rd); + + metalog_commit_creation_record(ctx, buffer, rd->state->metric_uuid); +} + +void metalog_commit_delete_dimension(RRDDIM *rd) +{ + struct metalog_instance *ctx; + BUFFER *buffer; + RRDSET *st = rd->rrdset; + RRDHOST *host = st->rrdhost; + char uuid_str[37]; + + /* Metadata are only available with dbengine */ + if (!host->rrdeng_ctx || RRD_MEMORY_MODE_DBENGINE != st->rrd_memory_mode) + return; + + ctx = host->rrdeng_ctx->metalog_ctx; + if (!ctx) /* metadata log has not been initialized yet */ + return; + buffer = buffer_create(64); /* This will be freed after it has been committed to the metadata log buffer */ + + uuid_unparse_lower(*rd->state->metric_uuid, uuid_str); + buffer_sprintf(buffer, "TOMBSTONE %s\n", uuid_str); + + metalog_commit_deletion_record(ctx, buffer); +} + +RRDSET *metalog_get_chart_from_uuid(struct metalog_instance *ctx, uuid_t *chart_uuid) +{ + GUID_TYPE ret; + char chart_object[33], chart_fullid[RRD_ID_LENGTH_MAX + 1]; + uuid_t *machine_guid, *chart_char_guid; + + ret = find_object_by_guid(chart_uuid, chart_object, 33); + assert(GUID_TYPE_CHART == ret); + + machine_guid = (uuid_t *)chart_object; + RRDHOST *host = ctx->rrdeng_ctx->host; + assert(!uuid_compare(host->host_uuid, *machine_guid)); + + chart_char_guid = (uuid_t *)(chart_object + 16); + + ret = find_object_by_guid(chart_char_guid, chart_fullid, RRD_ID_LENGTH_MAX + 1); + assert(GUID_TYPE_CHAR == ret); + RRDSET *st = rrdset_find(host, chart_fullid); + + return st; +} + +RRDDIM *metalog_get_dimension_from_uuid(struct metalog_instance *ctx, uuid_t *metric_uuid) +{ + GUID_TYPE ret; + char dim_object[49], chart_object[33], id_str[PLUGINSD_LINE_MAX], chart_fullid[RRD_ID_LENGTH_MAX + 1]; + uuid_t *machine_guid, *chart_guid, *chart_char_guid, *dim_char_guid; + + ret = find_object_by_guid(metric_uuid, dim_object, sizeof(dim_object)); + if (GUID_TYPE_DIMENSION != ret) /* not found */ + return NULL; + + machine_guid = (uuid_t *)dim_object; + RRDHOST *host = ctx->rrdeng_ctx->host; + assert(!uuid_compare(host->host_uuid, *machine_guid)); + + chart_guid = (uuid_t *)(dim_object + 16); + dim_char_guid = (uuid_t *)(dim_object + 16 + 16); + + ret = find_object_by_guid(dim_char_guid, id_str, sizeof(id_str)); + assert(GUID_TYPE_CHAR == ret); + + ret = find_object_by_guid(chart_guid, chart_object, sizeof(chart_object)); + assert(GUID_TYPE_CHART == ret); + chart_char_guid = (uuid_t *)(chart_object + 16); + + ret = find_object_by_guid(chart_char_guid, chart_fullid, RRD_ID_LENGTH_MAX + 1); + assert(GUID_TYPE_CHAR == ret); + RRDSET *st = rrdset_find(host, chart_fullid); + assert(st); + + RRDDIM *rd = rrddim_find(st, id_str); + + return rd; +} + +/* This function is called by dbengine rotation logic when the metric has no writers */ +void metalog_delete_dimension_by_uuid(struct metalog_instance *ctx, uuid_t *metric_uuid) +{ + RRDDIM *rd; + RRDSET *st; + RRDHOST *host; + uint8_t empty_chart; + + rd = metalog_get_dimension_from_uuid(ctx, metric_uuid); + if (!rd) { /* in the case of legacy UUID convert to multihost and try again */ + uuid_t multihost_uuid; + + rrdeng_convert_legacy_uuid_to_multihost(ctx->rrdeng_ctx->host->machine_guid, metric_uuid, &multihost_uuid); + rd = metalog_get_dimension_from_uuid(ctx, &multihost_uuid); + } + if(!rd) { + info("Rotated unknown archived metric."); + return; + } + st = rd->rrdset; + host = st->rrdhost; + + /* Since the metric has no writer it will not be commited to the metadata log by rrddim_free_custom(). + * It must be commited explicitly before calling rrddim_free_custom(). */ + metalog_commit_delete_dimension(rd); + + rrdset_wrlock(st); + rrddim_free_custom(st, rd, 1); + empty_chart = (NULL == st->dimensions); + rrdset_unlock(st); + + if (empty_chart) { + rrdhost_wrlock(host); + rrdset_rdlock(st); + rrdset_delete_custom(st, 1); + rrdset_unlock(st); + rrdset_free(st); + rrdhost_unlock(host); + } +} + +/* + * Returns 0 on success, negative on error + */ +int metalog_init(struct rrdengine_instance *rrdeng_parent_ctx) +{ + struct metalog_instance *ctx; + int error; + + ctx = callocz(1, sizeof(*ctx)); + ctx->records_nr = 0; + ctx->current_compaction_id = 0; + ctx->quiesce = NO_QUIESCE; + + memset(&ctx->worker_config, 0, sizeof(ctx->worker_config)); + ctx->rrdeng_ctx = rrdeng_parent_ctx; + ctx->worker_config.ctx = ctx; + init_metadata_record_log(&ctx->records_log); + error = init_metalog_files(ctx); + if (error) { + goto error_after_init_rrd_files; + } + + init_completion(&ctx->metalog_completion); + assert(0 == uv_thread_create(&ctx->worker_config.thread, metalog_worker, &ctx->worker_config)); + /* wait for worker thread to initialize */ + wait_for_completion(&ctx->metalog_completion); + destroy_completion(&ctx->metalog_completion); + uv_thread_set_name_np(ctx->worker_config.thread, "METALOG"); + if (ctx->worker_config.error) { + goto error_after_rrdeng_worker; + } + rrdeng_parent_ctx->metalog_ctx = ctx; /* notify dbengine that the metadata log has finished initializing */ + return 0; + +error_after_rrdeng_worker: + finalize_metalog_files(ctx); +error_after_init_rrd_files: + freez(ctx); + return UV_EIO; +} + +/* + * Returns 0 on success, 1 on error + */ +int metalog_exit(struct metalog_instance *ctx) +{ + struct metalog_cmd cmd; + + if (NULL == ctx) { + return 1; + } + + cmd.opcode = METALOG_SHUTDOWN; + metalog_enq_cmd(&ctx->worker_config, &cmd); + + assert(0 == uv_thread_join(&ctx->worker_config.thread)); + + finalize_metalog_files(ctx); + freez(ctx); + + return 0; +} + +void metalog_prepare_exit(struct metalog_instance *ctx) +{ + struct metalog_cmd cmd; + + if (NULL == ctx) { + return; + } + + init_completion(&ctx->metalog_completion); + cmd.opcode = METALOG_QUIESCE; + metalog_enq_cmd(&ctx->worker_config, &cmd); + + /* wait for metadata log to quiesce */ + wait_for_completion(&ctx->metalog_completion); + destroy_completion(&ctx->metalog_completion); +} diff --git a/database/engine/metadata_log/metadatalogapi.h b/database/engine/metadata_log/metadatalogapi.h new file mode 100644 index 0000000000..d9dd589848 --- /dev/null +++ b/database/engine/metadata_log/metadatalogapi.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_METADATALOGAPI_H +#define NETDATA_METADATALOGAPI_H + +#include "metadatalog.h" + +extern BUFFER *metalog_update_host_buffer(RRDHOST *host); +extern void metalog_commit_update_host(RRDHOST *host); +extern BUFFER *metalog_update_chart_buffer(RRDSET *st, uint32_t compaction_id); +extern void metalog_commit_update_chart(RRDSET *st); +extern void metalog_commit_delete_chart(RRDSET *st); +extern BUFFER *metalog_update_dimension_buffer(RRDDIM *rd); +extern void metalog_commit_update_dimension(RRDDIM *rd); +extern void metalog_commit_delete_dimension(RRDDIM *rd); + +extern RRDSET *metalog_get_chart_from_uuid(struct metalog_instance *ctx, uuid_t *chart_uuid); +extern RRDDIM *metalog_get_dimension_from_uuid(struct metalog_instance *ctx, uuid_t *metric_uuid); +extern void metalog_delete_dimension_by_uuid(struct metalog_instance *ctx, uuid_t *metric_uuid); + +/* must call once before using anything */ +extern int metalog_init(struct rrdengine_instance *rrdeng_parent_ctx); +extern int metalog_exit(struct metalog_instance *ctx); +extern void metalog_prepare_exit(struct metalog_instance *ctx); + +#endif /* NETDATA_METADATALOGAPI_H */ diff --git a/database/engine/metadata_log/metadatalogprotocol.h b/database/engine/metadata_log/metadatalogprotocol.h new file mode 100644 index 0000000000..1017213ae2 --- /dev/null +++ b/database/engine/metadata_log/metadatalogprotocol.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_METADATALOGPROTOCOL_H +#define NETDATA_METADATALOGPROTOCOL_H + +#include "../rrddiskprotocol.h" + +#define RRDENG_METALOG_MAGIC "netdata-metadata-log" + +#define RRDENG_METALOG_VER (1) + +#define RRDENG_METALOG_SB_PADDING_SZ (RRDENG_BLOCK_SIZE - (RRDENG_MAGIC_SZ + sizeof(uint16_t))) +/* + * Metadata log persistent super-block + */ +struct rrdeng_metalog_sb { + char magic_number[RRDENG_MAGIC_SZ]; + uint16_t version; + uint8_t padding[RRDENG_METALOG_SB_PADDING_SZ]; +} __attribute__ ((packed)); + +/* + * Metadata log record types + */ +#define METALOG_STORE_PADDING (0) +#define METALOG_CREATE_OBJECT (1) +#define METALOG_DELETE_OBJECT (2) +#define METALOG_OTHER (3) /* reserved */ + +/* + * Metadata log record header + */ +struct rrdeng_metalog_record_header { + /* when set to METALOG_STORE_PADDING jump to start of next block */ + uint8_t type; + + uint16_t header_length; + uint32_t payload_length; + /****************************************************** + * No fields above this point can ever change. * + ****************************************************** + * All fields below this point are subject to change. * + ******************************************************/ +} __attribute__ ((packed)); + +/* + * Metadata log record trailer + */ +struct rrdeng_metalog_record_trailer { + uint8_t checksum[CHECKSUM_SZ]; /* CRC32 */ +} __attribute__ ((packed)); + +#endif /* NETDATA_METADATALOGPROTOCOL_H */ diff --git a/database/engine/metadata_log/metalogpluginsd.c b/database/engine/metadata_log/metalogpluginsd.c new file mode 100755 index 0000000000..23027af7b2 --- /dev/null +++ b/database/engine/metadata_log/metalogpluginsd.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#define NETDATA_RRD_INTERNALS + +#include "metadatalog.h" +#include "metalogpluginsd.h" + +PARSER_RC metalog_pluginsd_chart_action(void *user, char *type, char *id, char *name, char *family, char *context, + char *title, char *units, char *plugin, char *module, int priority, + int update_every, RRDSET_TYPE chart_type, char *options) +{ + struct metalog_pluginsd_state *state = ((PARSER_USER_OBJECT *)user)->private; + RRDSET *st = NULL; + RRDHOST *host = ((PARSER_USER_OBJECT *) user)->host; + uuid_t *chart_uuid; + + chart_uuid = uuid_is_null(state->uuid) ? NULL : &state->uuid; + st = rrdset_create_custom( + host, type, id, name, family, context, title, units, + plugin, module, priority, update_every, + chart_type, RRD_MEMORY_MODE_DBENGINE, (host)->rrd_history_entries, 1, chart_uuid); + + if (options && *options) { + if (strstr(options, "obsolete")) + rrdset_is_obsolete(st); + else + rrdset_isnot_obsolete(st); + + if (strstr(options, "detail")) + rrdset_flag_set(st, RRDSET_FLAG_DETAIL); + else + rrdset_flag_clear(st, RRDSET_FLAG_DETAIL); + + if (strstr(options, "hidden")) + rrdset_flag_set(st, RRDSET_FLAG_HIDDEN); + else + rrdset_flag_clear(st, RRDSET_FLAG_HIDDEN); + + if (strstr(options, "store_first")) + rrdset_flag_set(st, RRDSET_FLAG_STORE_FIRST); + else + rrdset_flag_clear(st, RRDSET_FLAG_STORE_FIRST); + } else { + rrdset_isnot_obsolete(st); + rrdset_flag_clear(st, RRDSET_FLAG_DETAIL); + rrdset_flag_clear(st, RRDSET_FLAG_STORE_FIRST); + } + ((PARSER_USER_OBJECT *)user)->st = st; + + if (chart_uuid) { /* It's a valid object */ + struct metalog_record record; + struct metadata_logfile *metalogfile = state->metalogfile; + + uuid_copy(record.uuid, state->uuid); + mlf_record_insert(metalogfile, &record); + uuid_clear(state->uuid); /* Consume UUID */ + } + return PARSER_RC_OK; +} + +PARSER_RC metalog_pluginsd_dimension_action(void *user, RRDSET *st, char *id, char *name, char *algorithm, + long multiplier, long divisor, char *options, RRD_ALGORITHM algorithm_type) +{ + struct metalog_pluginsd_state *state = ((PARSER_USER_OBJECT *)user)->private; + UNUSED(user); + UNUSED(algorithm); + uuid_t *dim_uuid; + + dim_uuid = uuid_is_null(state->uuid) ? NULL : &state->uuid; + + RRDDIM *rd = rrddim_add_custom(st, id, name, multiplier, divisor, algorithm_type, RRD_MEMORY_MODE_DBENGINE, 1, + dim_uuid); + rrddim_flag_clear(rd, RRDDIM_FLAG_HIDDEN); + rrddim_flag_clear(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS); + if (options && *options) { + if (strstr(options, "obsolete") != NULL) + rrddim_is_obsolete(st, rd); + else + rrddim_isnot_obsolete(st, rd); + if (strstr(options, "hidden") != NULL) + rrddim_flag_set(rd, RRDDIM_FLAG_HIDDEN); + if (strstr(options, "noreset") != NULL) + rrddim_flag_set(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS); + if (strstr(options, "nooverflow") != NULL) + rrddim_flag_set(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS); + } else { + rrddim_isnot_obsolete(st, rd); + } + if (dim_uuid) { /* It's a valid object */ + struct metalog_record record; + struct metadata_logfile *metalogfile = state->metalogfile; + + uuid_copy(record.uuid, state->uuid); + mlf_record_insert(metalogfile, &record); + uuid_clear(state->uuid); /* Consume UUID */ + } + return PARSER_RC_OK; +} + +PARSER_RC metalog_pluginsd_guid_action(void *user, uuid_t *uuid) +{ + struct metalog_pluginsd_state *state = ((PARSER_USER_OBJECT *)user)->private; + + uuid_copy(state->uuid, *uuid); + + return PARSER_RC_OK; +} + +PARSER_RC metalog_pluginsd_context_action(void *user, uuid_t *uuid) +{ + GUID_TYPE ret; + struct metalog_pluginsd_state *state = ((PARSER_USER_OBJECT *)user)->private; + struct metalog_instance *ctx = state->ctx; + char object[49], chart_object[33], id_str[1024]; + uuid_t *chart_guid, *chart_char_guid; + RRDHOST *host; + + ret = find_object_by_guid(uuid, object, 49); + switch (ret) { + case GUID_TYPE_CHAR: + assert(0); + break; + case GUID_TYPE_CHART: + case GUID_TYPE_DIMENSION: + host = ctx->rrdeng_ctx->host; + switch (ret) { + case GUID_TYPE_CHART: + chart_char_guid = (uuid_t *)(object + 16); + + ret = find_object_by_guid(chart_char_guid, id_str, RRD_ID_LENGTH_MAX + 1); + assert(GUID_TYPE_CHAR == ret); + ((PARSER_USER_OBJECT *) user)->st = rrdset_find(host, id_str); + break; + case GUID_TYPE_DIMENSION: + chart_guid = (uuid_t *)(object + 16); + + ret = find_object_by_guid(chart_guid, chart_object, 33); + assert(GUID_TYPE_CHART == ret); + chart_char_guid = (uuid_t *)(chart_object + 16); + + ret = find_object_by_guid(chart_char_guid, id_str, RRD_ID_LENGTH_MAX + 1); + assert(GUID_TYPE_CHAR == ret); + ((PARSER_USER_OBJECT *) user)->st = rrdset_find(host, id_str); + break; + default: + assert(0); + break; + } + break; + case GUID_TYPE_HOST: + /* Ignore for now */ + break; + default: + break; + } + + return PARSER_RC_OK; +} + +PARSER_RC metalog_pluginsd_tombstone_action(void *user, uuid_t *uuid) +{ + GUID_TYPE ret; + struct metalog_pluginsd_state *state = ((PARSER_USER_OBJECT *)user)->private; + struct metalog_instance *ctx = state->ctx; + RRDHOST *host = ctx->rrdeng_ctx->host; + RRDSET *st; + RRDDIM *rd; + + ret = find_object_by_guid(uuid, NULL, 0); + switch (ret) { + case GUID_TYPE_CHAR: + assert(0); + break; + case GUID_TYPE_CHART: + st = metalog_get_chart_from_uuid(ctx, uuid); + if (st) { + rrdhost_wrlock(host); + rrdset_free(st); + rrdhost_unlock(host); + } + break; + case GUID_TYPE_DIMENSION: + rd = metalog_get_dimension_from_uuid(ctx, uuid); + if (rd) { + st = rd->rrdset; + rrdset_wrlock(st); + rrddim_free_custom(st, rd, 0); + rrdset_unlock(st); + } + break; + case GUID_TYPE_HOST: + /* Ignore for now */ + break; + default: + break; + } + + return PARSER_RC_OK; +} + +void metalog_pluginsd_state_init(struct metalog_pluginsd_state *state, struct metalog_instance *ctx) +{ + state->ctx = ctx; + state->skip_record = 0; + uuid_clear(state->uuid); + state->metalogfile = NULL; +} \ No newline at end of file diff --git a/database/engine/metadata_log/metalogpluginsd.h b/database/engine/metadata_log/metalogpluginsd.h new file mode 100644 index 0000000000..b29a9db33a --- /dev/null +++ b/database/engine/metadata_log/metalogpluginsd.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_METALOGPLUGINSD_H +#define NETDATA_METALOGPLUGINSD_H + +#include "../../../collectors/plugins.d/pluginsd_parser.h" +#include "../../../collectors/plugins.d/plugins_d.h" +#include "../../../parser/parser.h" + +struct metalog_pluginsd_state { + struct metalog_instance *ctx; + uuid_t uuid; + uint8_t skip_record; /* skip this record due to errors in parsing */ + struct metadata_logfile *metalogfile; /* current metadata log file being replayed */ +}; + +extern void metalog_pluginsd_state_init(struct metalog_pluginsd_state *state, struct metalog_instance *ctx); + +extern PARSER_RC metalog_pluginsd_chart_action(void *user, char *type, char *id, char *name, char *family, + char *context, char *title, char *units, char *plugin, char *module, + int priority, int update_every, RRDSET_TYPE chart_type, char *options); +extern PARSER_RC metalog_pluginsd_dimension_action(void *user, RRDSET *st, char *id, char *name, char *algorithm, + long multiplier, long divisor, char *options, + RRD_ALGORITHM algorithm_type); +extern PARSER_RC metalog_pluginsd_guid_action(void *user, uuid_t *uuid); +extern PARSER_RC metalog_pluginsd_context_action(void *user, uuid_t *uuid); +extern PARSER_RC metalog_pluginsd_tombstone_action(void *user, uuid_t *uuid); + +#endif /* NETDATA_METALOGPLUGINSD_H */ diff --git a/database/engine/pagecache.c b/database/engine/pagecache.c index 4534921267..f1f3a5be85 100644 --- a/database/engine/pagecache.c +++ b/database/engine/pagecache.c @@ -383,17 +383,26 @@ static int pg_cache_try_evict_one_page_unsafe(struct rrdengine_instance *ctx) return 0; } -/* - * Callers of this function need to make sure they're not deleting the same descriptor concurrently +/** + * Deletes a page from the database. + * Callers of this function need to make sure they're not deleting the same descriptor concurrently. + * @param ctx is the database instance. + * @param descr is the page descriptor. + * @param remove_dirty must be non-zero if the page to be deleted is dirty. + * @param is_exclusive_holder must be non-zero if the caller holds an exclusive page reference. + * @param metric_id is set to the metric the page belongs to, if it's safe to delete the metric and metric_id is not + * NULL. Otherwise, metric_id is not set. + * @return 1 if it's safe to delete the metric, 0 otherwise. */ -void pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr, uint8_t remove_dirty, - uint8_t is_exclusive_holder) +uint8_t pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr, uint8_t remove_dirty, + uint8_t is_exclusive_holder, uuid_t *metric_id) { struct page_cache *pg_cache = &ctx->pg_cache; struct page_cache_descr *pg_cache_descr = NULL; Pvoid_t *PValue; struct pg_cache_page_index *page_index; int ret; + uint8_t can_delete_metric = 0; uv_rwlock_rdlock(&pg_cache->metrics_index.lock); PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, descr->id, sizeof(uuid_t)); @@ -403,14 +412,22 @@ void pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_desc uv_rwlock_wrlock(&page_index->lock); ret = JudyLDel(&page_index->JudyL_array, (Word_t)(descr->start_time / USEC_PER_SEC), PJE0); - uv_rwlock_wrunlock(&page_index->lock); if (unlikely(0 == ret)) { + uv_rwlock_wrunlock(&page_index->lock); error("Page under deletion was not in index."); if (unlikely(debug_flags & D_RRDENGINE)) { print_page_descr(descr); } goto destroy; } + --page_index->page_count; + if (!page_index->writers && !page_index->page_count) { + can_delete_metric = 1; + if (metric_id) { + memcpy(metric_id, page_index->id, sizeof(uuid_t)); + } + } + uv_rwlock_wrunlock(&page_index->lock); assert(1 == ret); uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock); @@ -459,6 +476,8 @@ void pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_desc destroy: freez(descr); pg_cache_update_metric_times(page_index); + + return can_delete_metric; } static inline int is_page_in_time_range(struct rrdeng_page_descr *descr, usec_t start_time, usec_t end_time) @@ -588,6 +607,7 @@ void pg_cache_insert(struct rrdengine_instance *ctx, struct pg_cache_page_index uv_rwlock_wrlock(&page_index->lock); PValue = JudyLIns(&page_index->JudyL_array, (Word_t)(descr->start_time / USEC_PER_SEC), PJE0); *PValue = descr; + ++page_index->page_count; pg_cache_add_new_metric_time(page_index, descr); uv_rwlock_wrunlock(&page_index->lock); @@ -1032,6 +1052,8 @@ struct pg_cache_page_index *create_page_index(uuid_t *id) page_index->oldest_time = INVALID_TIME; page_index->latest_time = INVALID_TIME; page_index->prev = NULL; + page_index->page_count = 0; + page_index->writers = 0; return page_index; } diff --git a/database/engine/pagecache.h b/database/engine/pagecache.h index 3722a7e1cc..4ca57cd667 100644 --- a/database/engine/pagecache.h +++ b/database/engine/pagecache.h @@ -85,6 +85,8 @@ struct pg_cache_page_index { * TODO: examine if we want to support better granularity than seconds */ Pvoid_t JudyL_array; + Word_t page_count; + unsigned short writers; uv_rwlock_t lock; /* @@ -163,8 +165,8 @@ extern void pg_cache_put_unsafe(struct rrdeng_page_descr *descr); extern void pg_cache_put(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr); extern void pg_cache_insert(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, struct rrdeng_page_descr *descr); -extern void pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr, uint8_t remove_dirty, - uint8_t is_exclusive_holder); +extern uint8_t pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr, + uint8_t remove_dirty, uint8_t is_exclusive_holder, uuid_t *metric_id); extern usec_t pg_cache_oldest_time_in_range(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time, usec_t end_time); extern void pg_cache_get_filtered_info_prev(struct rrdengine_instance *ctx, struct pg_cache_page_index *page_index, diff --git a/database/engine/rrdengine.c b/database/engine/rrdengine.c index b15239e6c4..bf59242711 100644 --- a/database/engine/rrdengine.c +++ b/database/engine/rrdengine.c @@ -304,7 +304,7 @@ static void invalidate_oldest_committed(void *arg) goto out; } - pg_cache_punch_hole(ctx, descr, 1, 1); + pg_cache_punch_hole(ctx, descr, 1, 1, NULL); uv_rwlock_wrlock(&pg_cache->committed_page_index.lock); nr_committed_pages = --pg_cache->committed_page_index.nr_committed_pages; @@ -326,6 +326,9 @@ void rrdeng_invalidate_oldest_committed(struct rrdengine_worker_config* wc) unsigned nr_committed_pages; int error; + if (unlikely(ctx->quiesce != NO_QUIESCE)) /* Shutting down */ + return; + uv_rwlock_rdlock(&pg_cache->committed_page_index.lock); nr_committed_pages = pg_cache->committed_page_index.nr_committed_pages; uv_rwlock_rdunlock(&pg_cache->committed_page_index.lock); @@ -630,6 +633,8 @@ static void delete_old_data(void *arg) struct extent_info *extent, *next; struct rrdeng_page_descr *descr; unsigned count, i; + uint8_t can_delete_metric; + uuid_t metric_id; /* Safe to use since it will be deleted after we are done */ datafile = ctx->datafiles.first; @@ -638,7 +643,14 @@ static void delete_old_data(void *arg) count = extent->number_of_pages; for (i = 0 ; i < count ; ++i) { descr = extent->pages[i]; - pg_cache_punch_hole(ctx, descr, 0, 0); + can_delete_metric = pg_cache_punch_hole(ctx, descr, 0, 0, &metric_id); + if (unlikely(can_delete_metric && ctx->metalog_ctx)) { + /* + * If the metric is empty, has no active writers and if the metadata log has been initialized then + * attempt to delete the corresponding netdata dimension. + */ + metalog_delete_dimension_by_uuid(ctx->metalog_ctx, &metric_id); + } } next = extent->next; freez(extent); @@ -674,7 +686,7 @@ void rrdeng_test_quota(struct rrdengine_worker_config* wc) ++ctx->last_fileno; } } - if (unlikely(out_of_space)) { + if (unlikely(out_of_space && NO_QUIESCE == ctx->quiesce)) { /* delete old data */ if (wc->now_deleting_files) { /* already deleting data */ @@ -710,12 +722,18 @@ static inline int rrdeng_threads_alive(struct rrdengine_worker_config* wc) static void rrdeng_cleanup_finished_threads(struct rrdengine_worker_config* wc) { + struct rrdengine_instance *ctx = wc->ctx; + if (unlikely(wc->cleanup_thread_invalidating_dirty_pages)) { after_invalidate_oldest_committed(wc); } if (unlikely(wc->cleanup_thread_deleting_files)) { after_delete_old_data(wc); } + if (unlikely(SET_QUIESCE == ctx->quiesce && !rrdeng_threads_alive(wc))) { + ctx->quiesce = QUIESCED; + complete(&ctx->rrdengine_completion); + } } /* return 0 on success */ @@ -799,14 +817,16 @@ void async_cb(uv_async_t *handle) void timer_cb(uv_timer_t* handle) { struct rrdengine_worker_config* wc = handle->data; + struct rrdengine_instance *ctx = wc->ctx; uv_stop(handle->loop); uv_update_time(handle->loop); + if (unlikely(!ctx->metalog_ctx)) + return; /* Wait for the metadata log to initialize */ rrdeng_test_quota(wc); debug(D_RRDENGINE, "%s: timeout reached.", __func__); if (likely(!wc->now_deleting_files && !wc->now_invalidating_dirty_pages)) { /* There is free space so we can write to disk and we are not actively deleting dirty buffers */ - struct rrdengine_instance *ctx = wc->ctx; struct page_cache *pg_cache = &ctx->pg_cache; unsigned long total_bytes, bytes_written, nr_committed_pages, bytes_to_write = 0, producers, low_watermark, high_watermark; @@ -920,7 +940,20 @@ void rrdeng_worker(void* arg) break; case RRDENG_SHUTDOWN: shutdown = 1; + break; + case RRDENG_QUIESCE: ctx->drop_metrics_under_page_cache_pressure = 0; + ctx->quiesce = SET_QUIESCE; + assert(0 == uv_timer_stop(&timer_req)); + uv_close((uv_handle_t *)&timer_req, NULL); + while (do_flush_pages(wc, 1, NULL)) { + ; /* Force flushing of all committed pages. */ + } + wal_flush_transaction_buffer(wc); + if (!rrdeng_threads_alive(wc)) { + ctx->quiesce = QUIESCED; + complete(&ctx->rrdengine_completion); + } break; case RRDENG_READ_PAGE: do_read_extent(wc, &cmd.read_page.page_cache_descr, 1, 0); @@ -959,8 +992,6 @@ void rrdeng_worker(void* arg) * an issue in the future. */ uv_close((uv_handle_t *)&wc->async, NULL); - assert(0 == uv_timer_stop(&timer_req)); - uv_close((uv_handle_t *)&timer_req, NULL); while (do_flush_pages(wc, 1, NULL)) { ; /* Force flushing of all committed pages. */ @@ -998,7 +1029,7 @@ void rrdengine_main(void) struct rrdengine_instance *ctx; sanity_check(); - ret = rrdeng_init(&ctx, "/tmp", RRDENG_MIN_PAGE_CACHE_SIZE_MB, RRDENG_MIN_DISK_SPACE_MB); + ret = rrdeng_init(NULL, &ctx, "/tmp", RRDENG_MIN_PAGE_CACHE_SIZE_MB, RRDENG_MIN_DISK_SPACE_MB); if (ret) { exit(ret); } diff --git a/database/engine/rrdengine.h b/database/engine/rrdengine.h index d97a34aa30..e5d474a74b 100644 --- a/database/engine/rrdengine.h +++ b/database/engine/rrdengine.h @@ -17,6 +17,7 @@ #include "rrdenginelib.h" #include "datafile.h" #include "journalfile.h" +#include "metadata_log/metadatalog.h" #include "rrdengineapi.h" #include "pagecache.h" #include "rrdenglocking.h" @@ -50,6 +51,7 @@ enum rrdeng_opcode { RRDENG_FLUSH_PAGES, RRDENG_SHUTDOWN, RRDENG_INVALIDATE_OLDEST_MEMORY_PAGE, + RRDENG_QUIESCE, RRDENG_MAX_OPCODE }; @@ -169,7 +171,13 @@ extern rrdeng_stats_t rrdeng_reserved_file_descriptors; extern rrdeng_stats_t global_pg_cache_over_half_dirty_events; extern rrdeng_stats_t global_flushing_pressure_page_deletions; /* number of deleted pages */ +#define NO_QUIESCE (0) /* initial state when all operations function normally */ +#define SET_QUIESCE (1) /* set it before shutting down the instance, quiesce long running operations */ +#define QUIESCED (2) /* is set after all threads have finished running */ + struct rrdengine_instance { + RRDHOST *host; + struct metalog_instance *metalog_ctx; struct rrdengine_worker_config worker_config; struct completion rrdengine_completion; struct page_cache pg_cache; @@ -185,6 +193,8 @@ struct rrdengine_instance { unsigned long cache_pages_low_watermark; unsigned long metric_API_max_producers; + uint8_t quiesce; /* set to SET_QUIESCE before shutdown of the engine */ + struct rrdengine_statistics stats; }; diff --git a/database/engine/rrdengineapi.c b/database/engine/rrdengineapi.c index d8b5539795..b09d12c0e6 100755 --- a/database/engine/rrdengineapi.c +++ b/database/engine/rrdengineapi.c @@ -9,6 +9,114 @@ int default_rrdeng_disk_quota_mb = 256; /* Default behaviour is to unblock data collection if the page cache is full of dirty pages by dropping metrics */ uint8_t rrdeng_drop_metrics_under_page_cache_pressure = 1; +/* This UUID is not unique across hosts */ +void rrdeng_generate_legacy_uuid(const char *dim_id, char *chart_id, uuid_t *ret_uuid) +{ + EVP_MD_CTX *evpctx; + unsigned char hash_value[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + + evpctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(evpctx, EVP_sha256(), NULL); + EVP_DigestUpdate(evpctx, dim_id, strlen(dim_id)); + EVP_DigestUpdate(evpctx, chart_id, strlen(chart_id)); + EVP_DigestFinal_ex(evpctx, hash_value, &hash_len); + EVP_MD_CTX_destroy(evpctx); + assert(hash_len > sizeof(uuid_t)); + memcpy(ret_uuid, hash_value, sizeof(uuid_t)); +} + +/* Transform legacy UUID to be unique across hosts deterministacally */ +void rrdeng_convert_legacy_uuid_to_multihost(char machine_guid[GUID_LEN + 1], uuid_t *legacy_uuid, uuid_t *ret_uuid) +{ + EVP_MD_CTX *evpctx; + unsigned char hash_value[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + + evpctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(evpctx, EVP_sha256(), NULL); + EVP_DigestUpdate(evpctx, machine_guid, GUID_LEN); + EVP_DigestUpdate(evpctx, *legacy_uuid, sizeof(uuid_t)); + EVP_DigestFinal_ex(evpctx, hash_value, &hash_len); + EVP_MD_CTX_destroy(evpctx); + assert(hash_len > sizeof(uuid_t)); + memcpy(ret_uuid, hash_value, sizeof(uuid_t)); +} + +void rrdeng_metric_init(RRDDIM *rd, uuid_t *dim_uuid) +{ + struct page_cache *pg_cache; + struct rrdengine_instance *ctx; + uuid_t legacy_uuid; + Pvoid_t *PValue; + struct pg_cache_page_index *page_index; + int replace_instead_of_generate = 0; + + ctx = rd->rrdset->rrdhost->rrdeng_ctx; + pg_cache = &ctx->pg_cache; + + rrdeng_generate_legacy_uuid(rd->id, rd->rrdset->id, &legacy_uuid); + rd->state->metric_uuid = callocz(1, sizeof(uuid_t)); + + uv_rwlock_rdlock(&pg_cache->metrics_index.lock); + PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, &legacy_uuid, sizeof(uuid_t)); + if (likely(NULL != PValue)) { + page_index = *PValue; + } + uv_rwlock_rdunlock(&pg_cache->metrics_index.lock); + if (NULL == PValue) { + /* First time we see the legacy UUID, drop legacy support, normal path */ + + if (NULL != dim_uuid) { + replace_instead_of_generate = 1; + uuid_copy(*rd->state->metric_uuid, *dim_uuid); + } + if (unlikely(find_or_generate_guid(rd, rd->state->metric_uuid, GUID_TYPE_DIMENSION, + replace_instead_of_generate))) { + errno = 0; + error("FAILED to generate GUID for %s", rd->id); + freez(rd->state->metric_uuid); + rd->state->metric_uuid = NULL; + assert(0); + } + + uv_rwlock_rdlock(&pg_cache->metrics_index.lock); + PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, rd->state->metric_uuid, sizeof(uuid_t)); + if (likely(NULL != PValue)) { + page_index = *PValue; + } + uv_rwlock_rdunlock(&pg_cache->metrics_index.lock); + if (NULL == PValue) { + uv_rwlock_wrlock(&pg_cache->metrics_index.lock); + PValue = JudyHSIns(&pg_cache->metrics_index.JudyHS_array, rd->state->metric_uuid, sizeof(uuid_t), PJE0); + assert(NULL == *PValue); /* TODO: figure out concurrency model */ + *PValue = page_index = create_page_index(rd->state->metric_uuid); + page_index->prev = pg_cache->metrics_index.last_page_index; + pg_cache->metrics_index.last_page_index = page_index; + uv_rwlock_wrunlock(&pg_cache->metrics_index.lock); + } + } else { + /* There are legacy UUIDs in the database, implement backward compatibility */ + + + rrdeng_convert_legacy_uuid_to_multihost(rd->rrdset->rrdhost->machine_guid, &legacy_uuid, + rd->state->metric_uuid); + if (dim_uuid && uuid_compare(*rd->state->metric_uuid, *dim_uuid)) { + error("Mismatch of metadata log DIMENSION GUID with dbengine metric GUID."); + } + if (unlikely(find_or_generate_guid(rd, rd->state->metric_uuid, GUID_TYPE_DIMENSION, 1))) { + errno = 0; + error("FAILED to generate GUID for %s", rd->id); + freez(rd->state->metric_uuid); + rd->state->metric_uuid = NULL; + assert(0); + } + } + rd->state->rrdeng_uuid = &page_index->id; + rd->state->page_index = page_index; + rd->state->compaction_id = 0; +} + /* * Gets a handle for storing metrics to the database. * The handle must be released with rrdeng_store_metric_final(). @@ -16,53 +124,21 @@ uint8_t rrdeng_drop_metrics_under_page_cache_pressure = 1; void rrdeng_store_metric_init(RRDDIM *rd) { struct rrdeng_collect_handle *handle; - struct page_cache *pg_cache; struct rrdengine_instance *ctx; - uuid_t temp_id; - Pvoid_t *PValue; struct pg_cache_page_index *page_index; - EVP_MD_CTX *evpctx; - unsigned char hash_value[EVP_MAX_MD_SIZE]; - unsigned int hash_len; - - //&default_global_ctx; TODO: test this use case or remove it? ctx = rd->rrdset->rrdhost->rrdeng_ctx; - pg_cache = &ctx->pg_cache; handle = &rd->state->handle.rrdeng; handle->ctx = ctx; - evpctx = EVP_MD_CTX_create(); - EVP_DigestInit_ex(evpctx, EVP_sha256(), NULL); - EVP_DigestUpdate(evpctx, rd->id, strlen(rd->id)); - EVP_DigestUpdate(evpctx, rd->rrdset->id, strlen(rd->rrdset->id)); - EVP_DigestFinal_ex(evpctx, hash_value, &hash_len); - EVP_MD_CTX_destroy(evpctx); - assert(hash_len > sizeof(temp_id)); - memcpy(&temp_id, hash_value, sizeof(temp_id)); - handle->descr = NULL; handle->prev_descr = NULL; handle->unaligned_page = 0; - uv_rwlock_rdlock(&pg_cache->metrics_index.lock); - PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, &temp_id, sizeof(uuid_t)); - if (likely(NULL != PValue)) { - page_index = *PValue; - } - uv_rwlock_rdunlock(&pg_cache->metrics_index.lock); - if (NULL == PValue) { - /* First time we see the UUID */ - uv_rwlock_wrlock(&pg_cache->metrics_index.lock); - PValue = JudyHSIns(&pg_cache->metrics_index.JudyHS_array, &temp_id, sizeof(uuid_t), PJE0); - assert(NULL == *PValue); /* TODO: figure out concurrency model */ - *PValue = page_index = create_page_index(&temp_id); - page_index->prev = pg_cache->metrics_index.last_page_index; - pg_cache->metrics_index.last_page_index = page_index; - uv_rwlock_wrunlock(&pg_cache->metrics_index.lock); - } - rd->state->rrdeng_uuid = &page_index->id; - handle->page_index = page_index; + page_index = rd->state->page_index; + uv_rwlock_wrlock(&page_index->lock); + ++page_index->writers; + uv_rwlock_wrunlock(&page_index->lock); } /* The page must be populated and referenced */ @@ -109,7 +185,7 @@ void rrdeng_store_metric_flush_current_page(RRDDIM *rd) if (unlikely(debug_flags & D_RRDENGINE)) print_page_cache_descr(descr); pg_cache_put(ctx, descr); - pg_cache_punch_hole(ctx, descr, 1, 0); + pg_cache_punch_hole(ctx, descr, 1, 0, NULL); handle->prev_descr = NULL; } else { /* added 1 extra reference to keep 2 dirty pages pinned per metric, expected refcnt = 2 */ @@ -170,7 +246,7 @@ void rrdeng_store_metric_next(RRDDIM *rd, usec_t point_in_time, storage_number n must_flush_unaligned_page)) { rrdeng_store_metric_flush_current_page(rd); - page = rrdeng_create_page(ctx, &handle->page_index->id, &descr); + page = rrdeng_create_page(ctx, &rd->state->page_index->id, &descr); assert(page); handle->descr = descr; @@ -204,27 +280,38 @@ void rrdeng_store_metric_next(RRDDIM *rd, usec_t point_in_time, storage_number n } } - pg_cache_insert(ctx, handle->page_index, descr); + pg_cache_insert(ctx, rd->state->page_index, descr); } else { - pg_cache_add_new_metric_time(handle->page_index, descr); + pg_cache_add_new_metric_time(rd->state->page_index, descr); } } /* * Releases the database reference from the handle for storing metrics. + * Returns 1 if it's safe to delete the dimension. */ -void rrdeng_store_metric_finalize(RRDDIM *rd) +int rrdeng_store_metric_finalize(RRDDIM *rd) { struct rrdeng_collect_handle *handle; struct rrdengine_instance *ctx; + struct pg_cache_page_index *page_index; + uint8_t can_delete_metric = 0; handle = &rd->state->handle.rrdeng; ctx = handle->ctx; + page_index = rd->state->page_index; rrdeng_store_metric_flush_current_page(rd); if (handle->prev_descr) { /* unpin old second page */ pg_cache_put(ctx, handle->prev_descr); } + uv_rwlock_wrlock(&page_index->lock); + if (!--page_index->writers && !page_index->page_count) { + can_delete_metric = 1; + } + uv_rwlock_wrunlock(&page_index->lock); + + return can_delete_metric; } /* Returns 1 if the data collection interval is well defined, 0 otherwise */ @@ -577,21 +664,17 @@ void rrdeng_load_metric_finalize(struct rrddim_query_handle *rrdimm_handle) time_t rrdeng_metric_latest_time(RRDDIM *rd) { - struct rrdeng_collect_handle *handle; struct pg_cache_page_index *page_index; - handle = &rd->state->handle.rrdeng; - page_index = handle->page_index; + page_index = rd->state->page_index; return page_index->latest_time / USEC_PER_SEC; } time_t rrdeng_metric_oldest_time(RRDDIM *rd) { - struct rrdeng_collect_handle *handle; struct pg_cache_page_index *page_index; - handle = &rd->state->handle.rrdeng; - page_index = handle->page_index; + page_index = rd->state->page_index; return page_index->oldest_time / USEC_PER_SEC; } @@ -765,7 +848,8 @@ void rrdeng_put_page(struct rrdengine_instance *ctx, void *handle) /* * Returns 0 on success, negative on error */ -int rrdeng_init(struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb, unsigned disk_space_mb) +int rrdeng_init(RRDHOST *host, struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb, + unsigned disk_space_mb) { struct rrdengine_instance *ctx; int error; @@ -776,8 +860,9 @@ int rrdeng_init(struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned p /* reserve RRDENG_FD_BUDGET_PER_INSTANCE file descriptors for this instance */ rrd_stat_atomic_add(&rrdeng_reserved_file_descriptors, RRDENG_FD_BUDGET_PER_INSTANCE); if (rrdeng_reserved_file_descriptors > max_open_files) { - error("Exceeded the budget of available file descriptors (%u/%u), cannot create new dbengine instance.", - (unsigned)rrdeng_reserved_file_descriptors, (unsigned)max_open_files); + error( + "Exceeded the budget of available file descriptors (%u/%u), cannot create new dbengine instance.", + (unsigned)rrdeng_reserved_file_descriptors, (unsigned)max_open_files); rrd_stat_atomic_add(&global_fs_errors, 1); rrd_stat_atomic_add(&rrdeng_reserved_file_descriptors, -RRDENG_FD_BUDGET_PER_INSTANCE); @@ -804,6 +889,9 @@ int rrdeng_init(struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned p ctx->dbfiles_path[sizeof(ctx->dbfiles_path) - 1] = '\0'; ctx->drop_metrics_under_page_cache_pressure = rrdeng_drop_metrics_under_page_cache_pressure; ctx->metric_API_max_producers = 0; + ctx->quiesce = NO_QUIESCE; + ctx->metalog_ctx = NULL; /* only set this after the metadata log has finished initializing */ + ctx->host = host; memset(&ctx->worker_config, 0, sizeof(ctx->worker_config)); ctx->worker_config.ctx = ctx; @@ -823,6 +911,11 @@ int rrdeng_init(struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned p if (ctx->worker_config.error) { goto error_after_rrdeng_worker; } + error = metalog_init(ctx); + if(error) { + error("Failed to initialize metadata log file event loop."); + goto error_after_rrdeng_worker; + } return 0; error_after_rrdeng_worker: @@ -855,6 +948,7 @@ int rrdeng_exit(struct rrdengine_instance *ctx) assert(0 == uv_thread_join(&ctx->worker_config.thread)); finalize_rrd_files(ctx); + metalog_exit(ctx->metalog_ctx); free_page_cache(ctx); if (ctx != &default_global_ctx) { @@ -863,3 +957,23 @@ int rrdeng_exit(struct rrdengine_instance *ctx) rrd_stat_atomic_add(&rrdeng_reserved_file_descriptors, -RRDENG_FD_BUDGET_PER_INSTANCE); return 0; } + +void rrdeng_prepare_exit(struct rrdengine_instance *ctx) +{ + struct rrdeng_cmd cmd; + + if (NULL == ctx) { + return; + } + + init_completion(&ctx->rrdengine_completion); + cmd.opcode = RRDENG_QUIESCE; + rrdeng_enq_cmd(&ctx->worker_config, &cmd); + + /* wait for dbengine to quiesce */ + wait_for_completion(&ctx->rrdengine_completion); + destroy_completion(&ctx->rrdengine_completion); + + metalog_prepare_exit(ctx->metalog_ctx); +} + diff --git a/database/engine/rrdengineapi.h b/database/engine/rrdengineapi.h index 1f5be09d77..42382717ea 100644 --- a/database/engine/rrdengineapi.h +++ b/database/engine/rrdengineapi.h @@ -28,10 +28,17 @@ extern void rrdeng_commit_page(struct rrdengine_instance *ctx, struct rrdeng_pag extern void *rrdeng_get_latest_page(struct rrdengine_instance *ctx, uuid_t *id, void **handle); extern void *rrdeng_get_page(struct rrdengine_instance *ctx, uuid_t *id, usec_t point_in_time, void **handle); extern void rrdeng_put_page(struct rrdengine_instance *ctx, void *handle); + +extern void rrdeng_generate_legacy_uuid(const char *dim_id, char *chart_id, uuid_t *ret_uuid); +extern void rrdeng_convert_legacy_uuid_to_multihost(char machine_guid[GUID_LEN + 1], uuid_t *legacy_uuid, + uuid_t *ret_uuid); + + +extern void rrdeng_metric_init(RRDDIM *rd, uuid_t *dim_uuid); extern void rrdeng_store_metric_init(RRDDIM *rd); extern void rrdeng_store_metric_flush_current_page(RRDDIM *rd); extern void rrdeng_store_metric_next(RRDDIM *rd, usec_t point_in_time, storage_number number); -extern void rrdeng_store_metric_finalize(RRDDIM *rd); +extern int rrdeng_store_metric_finalize(RRDDIM *rd); extern unsigned rrdeng_variable_step_boundaries(RRDSET *st, time_t start_time, time_t end_time, struct rrdeng_region_info **region_info_arrayp, unsigned *max_intervalp); @@ -45,9 +52,10 @@ extern time_t rrdeng_metric_oldest_time(RRDDIM *rd); extern void rrdeng_get_37_statistics(struct rrdengine_instance *ctx, unsigned long long *array); /* must call once before using anything */ -extern int rrdeng_init(struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb, +extern int rrdeng_init(RRDHOST *host, struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb, unsigned disk_space_mb); extern int rrdeng_exit(struct rrdengine_instance *ctx); +extern void rrdeng_prepare_exit(struct rrdengine_instance *ctx); #endif /* NETDATA_RRDENGINEAPI_H */ \ No newline at end of file diff --git a/database/engine/rrdenginelib.c b/database/engine/rrdenginelib.c index dffcf9c347..26696fb25d 100644 --- a/database/engine/rrdenginelib.c +++ b/database/engine/rrdenginelib.c @@ -78,17 +78,22 @@ int check_file_properties(uv_file file, uint64_t *file_size, size_t min_size) return 0; } -/* - * Tries to open a file in direct I/O mode, falls back to buffered mode if not possible. - * Returns UV error number that is < 0 on failure. - * On success sets (*file) to be the uv_file that was opened. +/** + * Open file for I/O. + * + * @param path The full path of the file. + * @param flags Same flags as the open() system call uses. + * @param file On success sets (*file) to be the uv_file that was opened. + * @param direct Tries to open a file in direct I/O mode when direct=1, falls back to buffered mode if not possible. + * @return Returns UV error number that is < 0 on failure. 0 on success. */ -int open_file_direct_io(char *path, int flags, uv_file *file) +int open_file_for_io(char *path, int flags, uv_file *file, int direct) { uv_fs_t req; - int fd, current_flags, direct; + int fd, current_flags; - for (direct = 1 ; direct >= 0 ; --direct) { + assert(0 == direct || 1 == direct); + for ( ; direct >= 0 ; --direct) { #ifdef __APPLE__ /* Apple OS does not support O_DIRECT */ direct = 0; diff --git a/database/engine/rrdenginelib.h b/database/engine/rrdenginelib.h index 91836b63b9..7962cbc018 100644 --- a/database/engine/rrdenginelib.h +++ b/database/engine/rrdenginelib.h @@ -100,7 +100,15 @@ static inline void crc32set(void *crcp, uLong crc) extern void print_page_cache_descr(struct rrdeng_page_descr *page_cache_descr); extern void print_page_descr(struct rrdeng_page_descr *descr); extern int check_file_properties(uv_file file, uint64_t *file_size, size_t min_size); -extern int open_file_direct_io(char *path, int flags, uv_file *file); +extern int open_file_for_io(char *path, int flags, uv_file *file, int direct); +static inline int open_file_direct_io(char *path, int flags, uv_file *file) +{ + return open_file_for_io(path, flags, file, 1); +} +static inline int open_file_buffered_io(char *path, int flags, uv_file *file) +{ + return open_file_for_io(path, flags, file, 0); +} extern char *get_rrdeng_statistics(struct rrdengine_instance *ctx, char *str, size_t size); #endif /* NETDATA_RRDENGINELIB_H */ \ No newline at end of file diff --git a/database/rrd.h b/database/rrd.h index 7e1df252a8..b18c828b03 100644 --- a/database/rrd.h +++ b/database/rrd.h @@ -16,6 +16,7 @@ typedef struct alarm_entry ALARM_ENTRY; // forward declarations struct rrddim_volatile; +struct rrdset_volatile; #ifdef ENABLE_DBENGINE struct rrdeng_page_descr; struct rrdengine_instance; @@ -31,6 +32,11 @@ struct pg_cache_page_index; #include "rrdcalctemplate.h" #include "../streaming/rrdpush.h" +#define META_CHART_UPDATED 1 +#define META_PLUGIN_UPDATED 2 +#define META_MODULE_UPDATED 4 +#define META_CHART_ACTIVATED 8 + #define UPDATE_EVERY 1 #define UPDATE_EVERY_MAX 3600 @@ -136,7 +142,10 @@ typedef enum rrddim_flags { RRDDIM_FLAG_NONE = 0, RRDDIM_FLAG_HIDDEN = (1 << 0), // this dimension will not be offered to callers RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS = (1 << 1), // do not offer RESET or OVERFLOW info to callers - RRDDIM_FLAG_OBSOLETE = (1 << 2) // this is marked by the collector/module as obsolete + RRDDIM_FLAG_OBSOLETE = (1 << 2), // this is marked by the collector/module as obsolete + // No new values have been collected for this dimension since agent start or it was marked RRDDIM_FLAG_OBSOLETE at + // least rrdset_free_obsolete_time seconds ago. + RRDDIM_FLAG_ARCHIVED = (1 << 3) } RRDDIM_FLAGS; #ifdef HAVE_C___ATOMIC @@ -274,7 +283,6 @@ union rrddim_collect_handle { struct rrdeng_page_descr *descr, *prev_descr; unsigned long page_correlation_id; struct rrdengine_instance *ctx; - struct pg_cache_page_index *page_index; // set to 1 when this dimension is not page aligned with the other dimensions in the chart uint8_t unaligned_page; } rrdeng; // state the database engine uses @@ -312,6 +320,9 @@ struct rrddim_query_handle { struct rrddim_volatile { #ifdef ENABLE_DBENGINE uuid_t *rrdeng_uuid; // database engine metric UUID + uuid_t *metric_uuid; // global UUID for this metric (unique_across hosts) + struct pg_cache_page_index *page_index; + uint32_t compaction_id; // The last metadata log compaction procedure that has processed this object. #endif union rrddim_collect_handle handle; // ------------------------------------------------------------------------ @@ -324,7 +335,8 @@ struct rrddim_volatile { void (*store_metric)(RRDDIM *rd, usec_t point_in_time, storage_number number); // an finalization function to run after collection is over - void (*finalize)(RRDDIM *rd); + // returns 1 if it's safe to delete the dimension + int (*finalize)(RRDDIM *rd); } collect_ops; // function pointers that handle database queries @@ -349,6 +361,14 @@ struct rrddim_volatile { } query_ops; }; +// ---------------------------------------------------------------------------- +// volatile state per chart +struct rrdset_volatile { + char *old_title; + char *old_family; + char *old_context; +}; + // ---------------------------------------------------------------------------- // these loop macros make sure the linked list is accessed with the right lock @@ -382,7 +402,10 @@ typedef enum rrdset_flags { RRDSET_FLAG_HOMOGENEOUS_CHECK = 1 << 11, // if set, the chart should be checked to determine if the dimensions are homogeneous RRDSET_FLAG_HIDDEN = 1 << 12, // if set, do not show this chart on the dashboard, but use it for backends RRDSET_FLAG_SYNC_CLOCK = 1 << 13, // if set, microseconds on next data collection will be ignored (the chart will be synced to now) - RRDSET_FLAG_OBSOLETE_DIMENSIONS = 1 << 14 // this is marked by the collector/module when a chart has obsolete dimensions + RRDSET_FLAG_OBSOLETE_DIMENSIONS = 1 << 14, // this is marked by the collector/module when a chart has obsolete dimensions + // No new values have been collected for this chart since agent start or it was marked RRDSET_FLAG_OBSOLETE at + // least rrdset_free_obsolete_time seconds ago. + RRDSET_FLAG_ARCHIVED = 1 << 15 } RRDSET_FLAGS; #ifdef HAVE_C___ATOMIC @@ -459,8 +482,11 @@ struct rrdset { char *plugin_name; // the name of the plugin that generated this char *module_name; // the name of the plugin module that generated this - - size_t unused[5]; + uuid_t *chart_uuid; // Store the global GUID for this chart + size_t compaction_id; // The last metadata log compaction procedure that has processed + // this object. + struct rrdset_volatile *state; // volatile state that is not persistently stored + size_t unused[2]; size_t rrddim_page_alignment; // keeps metric pages in alignment when using dbengine @@ -783,6 +809,10 @@ struct rrdhost { #ifdef ENABLE_DBENGINE struct rrdengine_instance *rrdeng_ctx; // DB engine instance for this host + uuid_t host_uuid; // Global GUID for this host + unsigned long objects_nr; // Number of charts and dimensions in this host + uint32_t compaction_id; // The last metadata log compaction procedure that has processed + // this object. #endif #ifdef ENABLE_HTTPS @@ -847,6 +877,26 @@ extern RRDHOST *rrdhost_find_or_create( , struct rrdhost_system_info *system_info ); +extern void rrdhost_update(RRDHOST *host + , const char *hostname + , const char *registry_hostname + , const char *guid + , const char *os + , const char *timezone + , const char *tags + , const char *program_name + , const char *program_version + , int update_every + , long history + , RRD_MEMORY_MODE mode + , unsigned int health_enabled + , unsigned int rrdpush_enabled + , char *rrdpush_destination + , char *rrdpush_api_key + , char *rrdpush_send_charts_matching + , struct rrdhost_system_info *system_info +); + extern int rrdhost_set_system_info_variable(struct rrdhost_system_info *system_info, char *name, char *value); #if defined(NETDATA_INTERNAL_CHECKS) && defined(NETDATA_VERIFY_LOCKS) @@ -892,10 +942,12 @@ extern RRDSET *rrdset_create_custom(RRDHOST *host , int update_every , RRDSET_TYPE chart_type , RRD_MEMORY_MODE memory_mode - , long history_entries); + , long history_entries + , int is_archived + , uuid_t *chart_uuid); #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) + 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, 0, NULL) #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) @@ -919,6 +971,14 @@ extern RRDSET *rrdset_find(RRDHOST *host, const char *id); extern RRDSET *rrdset_find_bytype(RRDHOST *host, const char *type, const char *id); #define rrdset_find_bytype_localhost(type, id) rrdset_find_bytype(localhost, type, id) +/* This will not return charts that are archived */ +static inline RRDSET *rrdset_find_active_bytype_localhost(const char *type, const char *id) +{ + RRDSET *st = rrdset_find_bytype_localhost(type, id); + if (unlikely(st && rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED))) + return NULL; + return st; +} extern RRDSET *rrdset_find_byname(RRDHOST *host, const char *name); #define rrdset_find_byname_localhost(name) rrdset_find_byname(localhost, name) @@ -933,8 +993,9 @@ extern void rrdset_is_obsolete(RRDSET *st); extern void rrdset_isnot_obsolete(RRDSET *st); // checks if the RRDSET should be offered to viewers -#define rrdset_is_available_for_viewers(st) (rrdset_flag_check(st, RRDSET_FLAG_ENABLED) && !rrdset_flag_check(st, RRDSET_FLAG_HIDDEN) && !rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && (st)->dimensions && (st)->rrd_memory_mode != RRD_MEMORY_MODE_NONE) -#define rrdset_is_available_for_backends(st) (rrdset_flag_check(st, RRDSET_FLAG_ENABLED) && !rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && (st)->dimensions) +#define rrdset_is_available_for_viewers(st) (rrdset_flag_check(st, RRDSET_FLAG_ENABLED) && !rrdset_flag_check(st, RRDSET_FLAG_HIDDEN) && !rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && !rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && (st)->dimensions && (st)->rrd_memory_mode != RRD_MEMORY_MODE_NONE) +#define rrdset_is_available_for_backends(st) (rrdset_flag_check(st, RRDSET_FLAG_ENABLED) && !rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && !rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && (st)->dimensions) +#define rrdset_is_archived(st) (rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && (st)->dimensions) // get the total duration in seconds of the round robin database #define rrdset_duration(st) ((time_t)( (((st)->counter >= ((unsigned long)(st)->entries))?(unsigned long)(st)->entries:(st)->counter) * (st)->update_every )) @@ -1062,8 +1123,11 @@ static inline time_t rrdset_slot2time(RRDSET *st, size_t slot) { // RRD DIMENSION functions extern void rrdcalc_link_to_rrddim(RRDDIM *rd, RRDSET *st, RRDHOST *host); -extern RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collected_number multiplier, collected_number divisor, RRD_ALGORITHM algorithm, RRD_MEMORY_MODE memory_mode); -#define rrddim_add(st, id, name, multiplier, divisor, algorithm) rrddim_add_custom(st, id, name, multiplier, divisor, algorithm, (st)->rrd_memory_mode) +extern RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collected_number multiplier, + collected_number divisor, RRD_ALGORITHM algorithm, RRD_MEMORY_MODE memory_mode, + int is_archived, uuid_t *dim_uuid); +#define rrddim_add(st, id, name, multiplier, divisor, algorithm) rrddim_add_custom(st, id, name, multiplier, divisor, \ + algorithm, (st)->rrd_memory_mode, 0, NULL) extern int rrddim_set_name(RRDSET *st, RRDDIM *rd, const char *name); extern int rrddim_set_algorithm(RRDSET *st, RRDDIM *rd, RRD_ALGORITHM algorithm); @@ -1071,6 +1135,15 @@ extern int rrddim_set_multiplier(RRDSET *st, RRDDIM *rd, collected_number multip extern int rrddim_set_divisor(RRDSET *st, RRDDIM *rd, collected_number divisor); extern RRDDIM *rrddim_find(RRDSET *st, const char *id); +/* This will not return dimensions that are archived */ +static inline RRDDIM *rrddim_find_active(RRDSET *st, const char *id) +{ + RRDDIM *rd = rrddim_find(st, id); + if (unlikely(rd && rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED))) + return NULL; + return rd; +} + extern int rrddim_hide(RRDSET *st, const char *id); extern int rrddim_unhide(RRDSET *st, const char *id); @@ -1099,7 +1172,8 @@ extern avl_tree_lock rrdhost_root_index; extern char *rrdset_strncpyz_name(char *to, const char *from, size_t length); extern char *rrdset_cache_dir(RRDHOST *host, const char *id, const char *config_section); -extern void rrddim_free(RRDSET *st, RRDDIM *rd); +#define rrddim_free(st, rd) rrddim_free_custom(st, rd, 0) +extern void rrddim_free_custom(RRDSET *st, RRDDIM *rd, int db_rotated); extern int rrddim_compare(void* a, void* b); extern int rrdset_compare(void* a, void* b); @@ -1116,7 +1190,8 @@ extern RRDSET *rrdset_index_del_name(RRDHOST *host, RRDSET *st); extern void rrdset_free(RRDSET *st); extern void rrdset_reset(RRDSET *st); extern void rrdset_save(RRDSET *st); -extern void rrdset_delete(RRDSET *st); +#define rrdset_delete(st) rrdset_delete_custom(st, 0) +extern void rrdset_delete_custom(RRDSET *st, int db_rotated); extern void rrdset_delete_obsolete_dimensions(RRDSET *st); extern void rrdhost_cleanup_obsolete_charts(RRDHOST *host); diff --git a/database/rrddim.c b/database/rrddim.c index 3e94021e78..27769b99d5 100644 --- a/database/rrddim.c +++ b/database/rrddim.c @@ -100,10 +100,10 @@ static void rrddim_collect_store_metric(RRDDIM *rd, usec_t point_in_time, storag rd->values[rd->rrdset->current_entry] = number; } -static void rrddim_collect_finalize(RRDDIM *rd) { +static int rrddim_collect_finalize(RRDDIM *rd) { (void)rd; - return; + return 0; } // ---------------------------------------------------------------------------- @@ -189,7 +189,9 @@ void rrdcalc_link_to_rrddim(RRDDIM *rd, RRDSET *st, RRDHOST *host) { #endif } -RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collected_number multiplier, collected_number divisor, RRD_ALGORITHM algorithm, RRD_MEMORY_MODE memory_mode) { +RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collected_number multiplier, + collected_number divisor, RRD_ALGORITHM algorithm, RRD_MEMORY_MODE memory_mode, + int is_archived, uuid_t *dim_uuid) { RRDHOST *host = st->rrdhost; rrdset_wrlock(st); @@ -200,11 +202,21 @@ RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collecte if(unlikely(rd)) { debug(D_RRD_CALLS, "Cannot create rrd dimension '%s/%s', it already exists.", st->id, name?name:"<NONAME>"); - rrddim_set_name(st, rd, name); - rrddim_set_algorithm(st, rd, algorithm); - rrddim_set_multiplier(st, rd, multiplier); - rrddim_set_divisor(st, rd, divisor); - + int rc = rrddim_set_name(st, rd, name); + rc += rrddim_set_algorithm(st, rd, algorithm); + rc += rrddim_set_multiplier(st, rd, multiplier); + rc += rrddim_set_divisor(st, rd, divisor); + if (!is_archived && rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) { + rd->state->collect_ops.init(rd); + rrddim_flag_clear(rd, RRDDIM_FLAG_ARCHIVED); + } + // DBENGINE available and activated? +#ifdef ENABLE_DBENGINE + if (likely(!is_archived && rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) && unlikely(rc)) { + debug(D_METADATALOG, "DIMENSION [%s] metadata updated", rd->id); + metalog_commit_update_dimension(rd); + } +#endif rrdset_unlock(st); return rd; } @@ -301,7 +313,6 @@ RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collecte else rd->rrd_memory_mode = (memory_mode == RRD_MEMORY_MODE_NONE) ? RRD_MEMORY_MODE_NONE : RRD_MEMORY_MODE_ALLOC; } - rd->memsize = size; strcpy(rd->magic, RRDDIMENSION_MAGIC); @@ -350,15 +361,16 @@ RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collecte rd->state = mallocz(sizeof(*rd->state)); if(memory_mode == RRD_MEMORY_MODE_DBENGINE) { #ifdef ENABLE_DBENGINE - rd->state->collect_ops.init = rrdeng_store_metric_init; + rrdeng_metric_init(rd, dim_uuid); + rd->state->collect_ops.init = rrdeng_store_metric_init; rd->state->collect_ops.store_metric = rrdeng_store_metric_next; - rd->state->collect_ops.finalize = rrdeng_store_metric_finalize; - rd->state->query_ops.init = rrdeng_load_metric_init; - rd->state->query_ops.next_metric = rrdeng_load_metric_next; - rd->state->query_ops.is_finished = rrdeng_load_metric_is_finished; - rd->state->query_ops.finalize = rrdeng_load_metric_finalize; - rd->state->query_ops.latest_time = rrdeng_metric_latest_time; - rd->state->query_ops.oldest_time = rrdeng_metric_oldest_time; + rd->state->collect_ops.finalize = rrdeng_store_metric_finalize; + rd->state->query_ops.init = rrdeng_load_metric_init; + rd->state->query_ops.next_metric = rrdeng_load_metric_next; + rd->state->query_ops.is_finished = rrdeng_load_metric_is_finished; + rd->state->query_ops.finalize = rrdeng_load_metric_finalize; + rd->state->query_ops.latest_time = rrdeng_metric_latest_time; + rd->state->query_ops.oldest_time = rrdeng_metric_oldest_time; #endif } else { rd->state->collect_ops.init = rrddim_collect_init; @@ -371,7 +383,10 @@ RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collecte rd->state->query_ops.latest_time = rrddim_query_latest_time; rd->state->query_ops.oldest_time = rrddim_query_oldest_time; } - rd->state->collect_ops.init(rd); + if (is_archived) + rrddim_flag_set(rd, RRDDIM_FLAG_ARCHIVED); + else + rd->state->collect_ops.init(rd); // only initialize if a collector created this dimension // append this dimension if(!st->dimensions) st->dimensions = rd; @@ -432,17 +447,30 @@ RRDDIM *rrddim_add_custom(RRDSET *st, const char *id, const char *name, collecte if (netdata_cloud_setting) aclk_update_chart(host, st->id, ACLK_CMD_CHART); #endif +#ifdef ENABLE_DBENGINE + rrd_atomic_fetch_add(&st->rrdhost->objects_nr, 1); + metalog_commit_update_dimension(rd); +#endif + return(rd); } // ---------------------------------------------------------------------------- // RRDDIM remove / free a dimension -void rrddim_free(RRDSET *st, RRDDIM *rd) +void rrddim_free_custom(RRDSET *st, RRDDIM *rd, int db_rotated) { debug(D_RRD_CALLS, "rrddim_free() %s.%s", st->name, rd->name); - rd->state->collect_ops.finalize(rd); + if (!rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) { + uint8_t can_delete_metric = rd->state->collect_ops.finalize(rd); + if (can_delete_metric && rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { +#ifdef ENABLE_DBENGINE + /* This metric has no data and no references */ + metalog_commit_delete_dimension(rd); +#endif + } + } freez(rd->state); if(rd == st->dimensions) @@ -486,9 +514,12 @@ void rrddim_free(RRDSET *st, RRDDIM *rd) break; } #ifdef ENABLE_ACLK - if (netdata_cloud_setting) + if ((netdata_cloud_setting) && (db_rotated || RRD_MEMORY_MODE_DBENGINE != rd->rrd_memory_mode)) aclk_update_chart(st->rrdhost, st->id, ACLK_CMD_CHART); #endif +#ifdef ENABLE_DBENGINE + rrd_atomic_fetch_add(&st->rrdhost->objects_nr, -1); +#endif } @@ -541,6 +572,10 @@ inline void rrddim_is_obsolete(RRDSET *st, RRDDIM *rd) { if (netdata_cloud_setting) aclk_update_chart(st->rrdhost, st->id, ACLK_CMD_CHART); #endif +#ifdef ENABLE_DBENGINE + metalog_commit_update_dimension(rd); +#endif + } inline void rrddim_isnot_obsolete(RRDSET *st __maybe_unused, RRDDIM *rd) { @@ -551,6 +586,9 @@ inline void rrddim_isnot_obsolete(RRDSET *st __maybe_unused, RRDDIM *rd) { if (netdata_cloud_setting) aclk_update_chart(st->rrdhost, st->id, ACLK_CMD_CHART); #endif +#ifdef ENABLE_DBENGINE + metalog_commit_update_dimension(rd); +#endif } // ---------------------------------------------------------------------------- diff --git a/database/rrdhost.c b/database/rrdhost.c index c4613e3642..9c51f361ad 100644 --- a/database/rrdhost.c +++ b/database/rrdhost.c @@ -245,6 +245,9 @@ RRDHOST *rrdhost_create(const char *hostname, } if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { #ifdef ENABLE_DBENGINE + uuid_parse(host->machine_guid, host->host_uuid); + host->objects_nr = 1; + host->compaction_id = 0; char dbenginepath[FILENAME_MAX + 1]; int ret; @@ -253,7 +256,7 @@ RRDHOST *rrdhost_create(const char *hostname, if(ret != 0 && errno != EEXIST) error("Host '%s': cannot create directory '%s'", host->hostname, dbenginepath); else - ret = rrdeng_init(&host->rrdeng_ctx, dbenginepath, host->page_cache_mb, host->disk_space_mb); + ret = rrdeng_init(host, &host->rrdeng_ctx, dbenginepath, host->page_cache_mb, host->disk_space_mb); if(ret) { error("Host '%s': cannot initialize host with machine guid '%s'. Failed to initialize DB engine at '%s'.", host->hostname, host->machine_guid, host->cache_dir); @@ -361,9 +364,91 @@ RRDHOST *rrdhost_create(const char *hostname, rrd_hosts_available++; +#ifdef ENABLE_DBENGINE + if (likely(!is_localhost && host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)) + metalog_commit_update_host(host); +#endif return host; } +void rrdhost_update(RRDHOST *host + , const char *hostname + , const char *registry_hostname + , const char *guid + , const char *os + , const char *timezone + , const char *tags + , const char *program_name + , const char *program_version + , int update_every + , long history + , RRD_MEMORY_MODE mode + , unsigned int health_enabled + , unsigned int rrdpush_enabled + , char *rrdpush_destination + , char *rrdpush_api_key + , char *rrdpush_send_charts_matching + , struct rrdhost_system_info *system_info +) +{ + UNUSED(guid); + UNUSED(rrdpush_enabled); + UNUSED(rrdpush_destination); + UNUSED(rrdpush_api_key); + UNUSED(rrdpush_send_charts_matching); + + host->health_enabled = health_enabled; + //host->stream_version = STREAMING_PROTOCOL_CURRENT_VERSION; Unused? + + rrdhost_system_info_free(host->system_info); + host->system_info = system_info; + + rrdhost_init_os(host, os); + rrdhost_init_timezone(host, timezone); + + freez(host->registry_hostname); + host->registry_hostname = strdupz((registry_hostname && *registry_hostname)?registry_hostname:hostname); + + if(strcmp(host->hostname, hostname) != 0) { + info("Host '%s' has been renamed to '%s'. If this is not intentional it may mean multiple hosts are using the same machine_guid.", host->hostname, hostname); + char *t = host->hostname; + host->hostname = strdupz(hostname); + host->hash_hostname = simple_hash(host->hostname); + freez(t); + } + + if(strcmp(host->program_name, program_name) != 0) { + info("Host '%s' switched program name from '%s' to '%s'", host->hostname, host->program_name, program_name); + char *t = host->program_name; + host->program_name = strdupz(program_name); + freez(t); + } + + if(strcmp(host->program_version, program_version) != 0) { + info("Host '%s' switched program version from '%s' to '%s'", host->hostname, host->program_version, program_version); + char *t = host->program_version; + host->program_version = strdupz(program_version); + freez(t); + } + + if(host->rrd_update_every != update_every) + error("Host '%s' has an update frequency of %d seconds, but the wanted one is %d seconds. Restart netdata here to apply the new settings.", host->hostname, host->rrd_update_every, update_every); + + if(host->rrd_history_entries < history) + error("Host '%s' has history of %ld entries, but the wanted one is %ld entries. Restart netdata here to apply the new settings.", host->hostname, host->rrd_history_entries, history); + + if(host->rrd_memory_mode != mode) + error("Host '%s' has memory mode '%s', but the wanted one is '%s'. Restart netdata here to apply the new settings.", host->hostname, rrd_memory_mode_name(host->rrd_memory_mode), rrd_memory_mode_name(mode)); + + // update host tags + rrdhost_init_tags(host, tags); +#ifdef ENABLE_DBENGINE + if (likely(host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)) + metalog_commit_update_host(host); +#endif + return; +} + RRDHOST *rrdhost_find_or_create( const char *hostname , const char *registry_hostname @@ -410,42 +495,24 @@ RRDHOST *rrdhost_find_or_create( ); } else { - host->health_enabled = health_enabled; - //host->stream_version = STREAMING_PROTOCOL_CURRENT_VERSION; Unused? - - if(strcmp(host->hostname, hostname) != 0) { - info("Host '%s' has been renamed to '%s'. If this is not intentional it may mean multiple hosts are using the same machine_guid.", host->hostname, hostname); - char *t = host->hostname; - host->hostname = strdupz(hostname); - host->hash_hostname = simple_hash(host->hostname); - freez(t); - } - - if(strcmp(host->program_name, program_name) != 0) { - info("Host '%s' switched program name from '%s' to '%s'", host->hostname, host->program_name, program_name); - char *t = host->program_name; - host->program_name = strdupz(program_name); - freez(t); - } - - if(strcmp(host->program_version, program_version) != 0) { - info("Host '%s' switched program version from '%s' to '%s'", host->hostname, host->program_version, program_version); - char *t = host->program_version; - host->program_version = strdupz(program_version); - freez(t); - } - - if(host->rrd_update_every != update_every) - error("Host '%s' has an update frequency of %d seconds, but the wanted one is %d seconds. Restart netdata here to apply the new settings.", host->hostname, host->rrd_update_every, update_every); - - if(host->rrd_history_entries < history) - error("Host '%s' has history of %ld entries, but the wanted one is %ld entries. Restart netdata here to apply the new settings.", host->hostname, host->rrd_history_entries, history); - - if(host->rrd_memory_mode != mode) - error("Host '%s' has memory mode '%s', but the wanted one is '%s'. Restart netdata here to apply the new settings.", host->hostname, rrd_memory_mode_name(host->rrd_memory_mode), rrd_memory_mode_name(mode)); - - // update host tags - rrdhost_init_tags(host, tags); + rrdhost_update(host + , hostname + , registry_hostname + , guid + , os + , timezone + , tags + , program_name + , program_version + , update_every + , history + , mode + , health_enabled + , rrdpush_enabled + , rrdpush_destination + , rrdpush_api_key + , rrdpush_send_charts_matching + , system_info); } rrdhost_cleanup_orphan_hosts_nolock(host); @@ -454,7 +521,6 @@ RRDHOST *rrdhost_find_or_create( return host; } - inline int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected, time_t now) { if(host != protected && host != localhost @@ -618,6 +684,9 @@ void rrdhost_free(RRDHOST *host) { // ------------------------------------------------------------------------ // release its children resources +#ifdef ENABLE_DBENGINE + rrdeng_prepare_exit(host->rrdeng_ctx); +#endif while(host->rrdset_root) rrdset_free(host->rrdset_root); @@ -1322,7 +1391,17 @@ restart_after_removal: && st->last_updated.tv_sec + rrdset_free_obsolete_time < now && st->last_collected_time.tv_sec + rrdset_free_obsolete_time < now )) { - +#ifdef ENABLE_DBENGINE + if(st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { + rrdset_flag_set(st, RRDSET_FLAG_ARCHIVED); + rrdset_flag_clear(st, RRDSET_FLAG_OBSOLETE); + if (st->dimensions) { + /* If the chart still has dimensions don't delete it from the metadata log */ + continue; + } + metalog_commit_delete_chart(st); + } +#endif rrdset_rdlock(st); if(rrdhost_delete_obsolete_charts) diff --git a/database/rrdset.c b/database/rrdset.c index d0554f0f30..7819c2a62e 100644 --- a/database/rrdset.c +++ b/database/rrdset.c @@ -2,6 +2,7 @@ #define NETDATA_RRD_INTERNALS #include "rrd.h" +#include <sched.h> void __rrdset_check_rdlock(RRDSET *st, const char *file, const char *function, const unsigned long line) { debug(D_RRD_CALLS, "Checking read lock on chart '%s'", st->id); @@ -260,7 +261,7 @@ void rrdset_reset(RRDSET *st) { rd->collections_counter = 0; // memset(rd->values, 0, rd->entries * sizeof(storage_number)); #ifdef ENABLE_DBENGINE - if (RRD_MEMORY_MODE_DBENGINE == st->rrd_memory_mode) { + if (RRD_MEMORY_MODE_DBENGINE == st->rrd_memory_mode && !rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) { rrdeng_store_metric_flush_current_page(rd); } #endif @@ -366,6 +367,10 @@ void rrdset_free(RRDSET *st) { freez(st->config_section); freez(st->plugin_name); freez(st->module_name); + freez(st->state->old_title); + freez(st->state->old_family); + freez(st->state->old_context); + freez(st->state); switch(st->rrd_memory_mode) { case RRD_MEMORY_MODE_SAVE: @@ -381,6 +386,10 @@ void rrdset_free(RRDSET *st) { freez(st); break; } +#ifdef ENABLE_DBENGINE + rrd_atomic_fetch_add(&host->objects_nr, -1); +#endif + } void rrdset_save(RRDSET *st) { @@ -402,7 +411,7 @@ void rrdset_save(RRDSET *st) { } } -void rrdset_delete(RRDSET *st) { +void rrdset_delete_custom(RRDSET *st, int db_rotated) { RRDDIM *rd; rrdset_check_rdlock(st); @@ -425,11 +434,12 @@ void rrdset_delete(RRDSET *st) { recursively_delete_dir(st->cache_dir, "left-over chart"); #ifdef ENABLE_ACLK - if (netdata_cloud_setting) { + if ((netdata_cloud_setting) && (db_rotated || RRD_MEMORY_MODE_DBENGINE != st->rrd_memory_mode)) { aclk_del_collector(st->rrdhost->hostname, st->plugin_name, st->module_name); aclk_update_chart(st->rrdhost, st->id, ACLK_CMD_CHARTDEL); } #endif + } void rrdset_delete_obsolete_dimensions(RRDSET *st) { @@ -480,6 +490,8 @@ RRDSET *rrdset_create_custom( , RRDSET_TYPE chart_type , RRD_MEMORY_MODE memory_mode , long history_entries + , int is_archived + , uuid_t *chart_uuid ) { 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'." @@ -516,15 +528,139 @@ RRDSET *rrdset_create_custom( snprintfz(fullid, RRD_ID_LENGTH_MAX, "%s.%s", type, id); RRDSET *st = rrdset_find_on_create(host, fullid); - if(st) { + if (st) { + int mark_rebuild = 0; rrdset_flag_set(st, RRDSET_FLAG_SYNC_CLOCK); rrdset_flag_clear(st, RRDSET_FLAG_UPSTREAM_EXPOSED); + if (!is_archived && rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED)) { + rrdset_flag_clear(st, RRDSET_FLAG_ARCHIVED); + mark_rebuild |= META_CHART_ACTIVATED; + } + char *old_plugin = NULL, *old_module = NULL, *old_title = NULL, *old_family = NULL, *old_context = NULL, + *old_title_v = NULL, *old_family_v = NULL, *old_context_v = NULL; + const char *new_name = name ? name : id; - if(unlikely(name)) - rrdset_set_name(st, name); - else - rrdset_set_name(st, id); + if (unlikely((st->name && !strcmp(st->name, new_name)) || !st->name)) { + mark_rebuild |= META_CHART_UPDATED; + rrdset_set_name(st, new_name); + } + if (unlikely(st->priority != priority)) { + st->priority = priority; + mark_rebuild |= META_CHART_UPDATED; + } + if (unlikely(st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE && st->update_every != update_every)) { + st->update_every = update_every; + mark_rebuild |= META_CHART_UPDATED; + } + + if (plugin && st->plugin_name) { + if (unlikely(strcmp(plugin, st->plugin_name))) { + old_plugin = st->plugin_name; + st->plugin_name = strdupz(plugin); + mark_rebuild |= META_PLUGIN_UPDATED; + } + } else { + if (plugin != st->plugin_name) { // one is NULL? + old_plugin = st->plugin_name; + st->plugin_name = plugin ? strdupz(plugin) : NULL; + mark_rebuild |= META_PLUGIN_UPDATED; + } + } + + if (module && st->module_name) { + if (unlikely(strcmp(module, st->module_name))) { + old_module = st->module_name; + st->module_name = strdupz(module); + mark_rebuild |= META_MODULE_UPDATED; + } + } else { + if (module != st->module_name) { + if (st->module_name && *st->module_name) { + old_module = st->module_name; + st->module_name = module ? strdupz(module) : NULL; + mark_rebuild |= META_MODULE_UPDATED; + } + } + } + + if (unlikely(title && st->state->old_title && strcmp(st->state->old_title, title))) { + char *new_title = strdupz(title); + old_title_v = st->state->old_title; + st->state->old_title = strdupz(title); + json_fix_string(new_title); + old_title = st->title; + st->title = new_title; + mark_rebuild |= META_CHART_UPDATED; + } + + RRDSET_TYPE new_chart_type = + rrdset_type_id(config_get(st->config_section, "chart type", rrdset_type_name(chart_type))); + if (st->chart_type != new_chart_type) { + st->chart_type = new_chart_type; + mark_rebuild |= META_CHART_UPDATED; + } + + if (unlikely(family && st->state->old_family && strcmp(st->state->old_family, family))) { + char *new_family = strdupz(family); + old_family_v = st->state->old_family; + st->state->old_family = strdupz(family); + json_fix_string(new_family); + old_family = st->family; + rrdfamily_free(host, st->rrdfamily); + st->family = new_family; + st->rrdfamily = rrdfamily_create(host, st->family); + mark_rebuild |= META_CHART_UPDATED; + } + + if (unlikely(context && st->state->old_context && strcmp(st->state->old_context, context))) { + char *new_context = strdupz(context); + old_context_v = st->state->old_context; + st->state->old_context = strdupz(context); + json_fix_string(new_context); + old_context = st->context; + st->context = new_context; + st->hash_context = simple_hash(st->context); + mark_rebuild |= META_CHART_UPDATED; + } + + if (mark_rebuild) { +#ifdef ENABLE_ACLK + if (netdata_cloud_setting) { + if (mark_rebuild & META_CHART_ACTIVATED) { + aclk_add_collector(host->hostname, st->plugin_name, st->module_name); + } + else { + if (mark_rebuild & (META_PLUGIN_UPDATED | META_MODULE_UPDATED)) { + aclk_del_collector( + host->hostname, mark_rebuild & META_PLUGIN_UPDATED ? old_plugin : st->plugin_name, + mark_rebuild & META_MODULE_UPDATED ? old_module : st->module_name); + aclk_add_collector(host->hostname, st->plugin_name, st->module_name); + } + } + aclk_update_chart(host, st->id, ACLK_CMD_CHART); + } +#endif + freez(old_plugin); + freez(old_module); + freez(old_title); + freez(old_family); + freez(old_context); + freez(old_title_v); + freez(old_family_v); + freez(old_context_v); + if (mark_rebuild != META_CHART_ACTIVATED) { + info("Collector updated metadata for chart %s", st->id); + sched_yield(); + } + } +#ifdef ENABLE_DBENGINE + if (is_archived == 0 && st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE && + (mark_rebuild & (META_CHART_UPDATED | META_PLUGIN_UPDATED | META_MODULE_UPDATED))) { + debug(D_METADATALOG, "CHART [%s] metadata updated", st->id); + metalog_commit_update_chart(st); + } +#endif return st; } @@ -535,6 +671,9 @@ RRDSET *rrdset_create_custom( rrdhost_unlock(host); rrdset_flag_set(st, RRDSET_FLAG_SYNC_CLOCK); rrdset_flag_clear(st, RRDSET_FLAG_UPSTREAM_EXPOSED); + if (!is_archived && rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED)) { + rrdset_flag_clear(st, RRDSET_FLAG_ARCHIVED); + } return st; } @@ -664,6 +803,8 @@ RRDSET *rrdset_create_custom( else st->rrd_memory_mode = (memory_mode == RRD_MEMORY_MODE_NONE) ? RRD_MEMORY_MODE_NONE : RRD_MEMORY_MODE_ALLOC; } + if (is_archived) + rrdset_flag_set(st, RRDSET_FLAG_ARCHIVED); st->plugin_name = plugin?strdupz(plugin):NULL; st->module_name = module?strdupz(module):NULL; @@ -687,13 +828,16 @@ RRDSET *rrdset_create_custom( st->chart_type = rrdset_type_id(config_get(st->config_section, "chart type", rrdset_type_name(chart_type))); st->type = config_get(st->config_section, "type", type); + st->state = mallocz(sizeof(*st->state)); st->family = config_get(st->config_section, "family", family?family:st->type); + st->state->old_family = strdupz(st->family); json_fix_string(st->family); st->units = config_get(st->config_section, "units", units?units:""); json_fix_string(st->units); st->context = config_get(st->config_section, "context", context?context:st->id); + st->state->old_context = strdupz(st->context); json_fix_string(st->context); st->hash_context = simple_hash(st->context); @@ -745,6 +889,7 @@ RRDSET *rrdset_create_custom( rrdset_set_name(st, id); st->title = config_get(st->config_section, "title", title); + st->state->old_title = strdupz(st->title); json_fix_string(st->title); st->rrdfamily = rrdfamily_create(host, st->family); @@ -765,6 +910,26 @@ RRDSET *rrdset_create_custom( rrdsetcalc_link_matching(st); rrdcalctemplate_link_matching(st); +#ifdef ENABLE_DBENGINE + if (st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { + int replace_instead_of_generate = 0; + + st->chart_uuid = callocz(1, sizeof(uuid_t)); + if (NULL != chart_uuid) { + replace_instead_of_generate = 1; + uuid_copy(*st->chart_uuid, *chart_uuid); + } + if (unlikely( + find_or_generate_guid((void *) st, st->chart_uuid, GUID_TYPE_CHART, replace_instead_of_generate))) { + errno = 0; + error("FAILED to generate GUID for %s", st->id); + freez(st->chart_uuid); + st->chart_uuid = NULL; + assert(0); + } + st->compaction_id = 0; + } +#endif rrdhost_cleanup_obsolete_charts(host); @@ -775,6 +940,11 @@ RRDSET *rrdset_create_custom( aclk_update_chart(host, st->id, ACLK_CMD_CHART); } #endif +#ifdef ENABLE_DBENGINE + rrd_atomic_fetch_add(&st->rrdhost->objects_nr, 1); + metalog_commit_update_chart(st); +#endif + return(st); } @@ -1006,6 +1176,9 @@ static inline size_t rrdset_done_interpolate( last_ut = next_store_ut; rrddim_foreach_read(rd, st) { + if (rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) + continue; + calculated_number new_value; switch(rd->algorithm) { @@ -1374,6 +1547,8 @@ void rrdset_done(RRDSET *st) { int dimensions = 0; st->collected_total = 0; rrddim_foreach_read(rd, st) { + if (rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) + continue; dimensions++; if(likely(rd->updated)) st->collected_total += rd->collected_value; @@ -1385,6 +1560,8 @@ void rrdset_done(RRDSET *st) { // based on the collected figures only // at this stage we do not interpolate anything rrddim_foreach_read(rd, st) { + if (rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) + continue; if(unlikely(!rd->updated)) { rd->calculated_value = 0; @@ -1630,6 +1807,8 @@ void rrdset_done(RRDSET *st) { st->last_collected_total = st->collected_total; rrddim_foreach_read(rd, st) { + if (rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) + continue; if(unlikely(!rd->updated)) continue; @@ -1692,7 +1871,7 @@ void rrdset_done(RRDSET *st) { // find if there are any obsolete dimensions time_t now = now_realtime_sec(); - if(unlikely(rrddim_flag_check(st, RRDSET_FLAG_OBSOLETE_DIMENSIONS))) { + if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE_DIMENSIONS))) { rrddim_foreach_read(rd, st) if(unlikely(rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE))) break; @@ -1714,6 +1893,21 @@ void rrdset_done(RRDSET *st) { error("Cannot delete dimension file '%s'", rd->cache_filename); } +#ifdef ENABLE_DBENGINE + if (rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { + rrddim_flag_set(rd, RRDDIM_FLAG_ARCHIVED); + rrddim_flag_clear(rd, RRDDIM_FLAG_OBSOLETE); + /* only a collector can mark a chart as obsolete, so we must remove the reference */ + uint8_t can_delete_metric = rd->state->collect_ops.finalize(rd); + if (can_delete_metric) { + /* This metric has no data and no references */ + metalog_commit_delete_dimension(rd); + } else { + /* Do not delete this dimension */ + continue; + } + } +#endif if(unlikely(!last)) { rrddim_free(st, rd); rd = st->dimensions; diff --git a/exporting/tests/netdata_doubles.c b/exporting/tests/netdata_doubles.c index f4da7769f6..f24c746618 100644 --- a/exporting/tests/netdata_doubles.c +++ b/exporting/tests/netdata_doubles.c @@ -100,7 +100,9 @@ RRDSET *rrdset_create_custom( int update_every, RRDSET_TYPE chart_type, RRD_MEMORY_MODE memory_mode, - long history_entries) + long history_entries, + int is_archived, + uuid_t *chart_uuid) { check_expected_ptr(host); check_expected_ptr(type); @@ -117,6 +119,8 @@ RRDSET *rrdset_create_custom( check_expected(chart_type); UNUSED(memory_mode); UNUSED(history_entries); + UNUSED(is_archived); + UNUSED(chart_uuid); function_called(); diff --git a/health/health.c b/health/health.c index 0bf79e27d1..a0cbd884bc 100644 --- a/health/health.c +++ b/health/health.c @@ -488,6 +488,11 @@ static inline int rrdcalc_isrunnable(RRDCALC *rc, time_t now, time_t *next_run) return 0; } + if(unlikely(rrdset_flag_check(rc->rrdset, RRDSET_FLAG_ARCHIVED))) { + debug(D_HEALTH, "Health not running alarm '%s.%s'. The chart has been marked as archived", rc->chart?rc->chart:"NOCHART", rc->name); + return 0; + } + if(unlikely(!rc->rrdset->last_collected_time.tv_sec || rc->rrdset->counter_done < 2)) { debug(D_HEALTH, "Health not running alarm '%s.%s'. Chart is not fully collected yet.", rc->chart?rc->chart:"NOCHART", rc->name); return 0; diff --git a/libnetdata/log/log.h b/libnetdata/log/log.h index 582ebafe03..8b54f28de1 100644 --- a/libnetdata/log/log.h +++ b/libnetdata/log/log.h @@ -38,6 +38,8 @@ #define D_STREAM 0x0000000040000000 #define D_RRDENGINE 0x0000000100000000 #define D_ACLK 0x0000000200000000 +#define D_METADATALOG 0x0000000400000000 +#define D_GUIDLOG 0x0000000800000000 #define D_SYSTEM 0x8000000000000000 //#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS) diff --git a/parser/parser.h b/parser/parser.h index 9c16fbf12a..64127a71d1 100644 --- a/parser/parser.h +++ b/parser/parser.h @@ -31,6 +31,10 @@ typedef struct pluginsd_action { PARSER_RC (*variable_action)(void *user, RRDHOST *host, RRDSET *st, char *name, int global, calculated_number value); PARSER_RC (*label_action)(void *user, char *key, char *value, LABEL_SOURCE source); PARSER_RC (*overwrite_action)(void *user, RRDHOST *host, struct label *new_labels); + + PARSER_RC (*guid_action)(void *user, uuid_t *uuid); + PARSER_RC (*context_action)(void *user, uuid_t *uuid); + PARSER_RC (*tombstone_action)(void *user, uuid_t *uuid); } PLUGINSD_ACTION; typedef enum parser_input_type { @@ -101,5 +105,8 @@ extern PARSER_RC pluginsd_flush(char **words, void *user, PLUGINSD_ACTION *plug extern PARSER_RC pluginsd_disable(char **words, void *user, PLUGINSD_ACTION *plugins_action); extern PARSER_RC pluginsd_label(char **words, void *user, PLUGINSD_ACTION *plugins_action); extern PARSER_RC pluginsd_overwrite(char **words, void *user, PLUGINSD_ACTION *plugins_action); +extern PARSER_RC pluginsd_guid(char **words, void *user, PLUGINSD_ACTION *plugins_action); +extern PARSER_RC pluginsd_context(char **words, void *user, PLUGINSD_ACTION *plugins_action); +extern PARSER_RC pluginsd_tombstone(char **words, void *user, PLUGINSD_ACTION *plugins_action); #endif diff --git a/streaming/receiver.c b/streaming/receiver.c index 380d49137c..c8e10330e1 100644 --- a/streaming/receiver.c +++ b/streaming/receiver.c @@ -279,6 +279,25 @@ static int rrdpush_receive(struct receiver_state *rpt) } netdata_mutex_unlock(&rpt->host->receiver_lock); } + else rrdhost_update(rpt->host + , rpt->hostname + , rpt->registry_hostname + , rpt->machine_guid + , rpt->os + , rpt->timezone + , rpt->tags + , program_name + , program_version + , rpt->update_every + , history + , mode + , (unsigned int)(health_enabled != CONFIG_BOOLEAN_NO) + , (unsigned int)(rrdpush_enabled && rrdpush_destination && *rrdpush_destination && rrdpush_api_key && *rrdpush_api_key) + , rrdpush_destination + , rrdpush_api_key + , rrdpush_send_charts_matching + , rpt->system_info); + int ssl = 0; #ifdef ENABLE_HTTPS diff --git a/web/api/formatters/charts2json.c b/web/api/formatters/charts2json.c index 864387ed51..9ea74df0ba 100644 --- a/web/api/formatters/charts2json.c +++ b/web/api/formatters/charts2json.c @@ -36,7 +36,7 @@ static inline const char* get_release_channel() { return (use_stable)?"stable":"nightly"; } -void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile) { +void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived) { static char *custom_dashboard_info_js_filename = NULL; size_t c, dimensions = 0, memory = 0, alarms = 0; RRDSET *st; @@ -71,7 +71,7 @@ void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile) { c = 0; rrdhost_rdlock(host); rrdset_foreach_read(st, host) { - if(rrdset_is_available_for_viewers(st)) { + if ((!show_archived && rrdset_is_available_for_viewers(st)) || (show_archived && rrdset_is_archived(st))) { if(c) buffer_strcat(wb, ","); buffer_strcat(wb, "\n\t\t\""); buffer_strcat(wb, st->id); diff --git a/web/api/formatters/charts2json.h b/web/api/formatters/charts2json.h index 4f0d57422a..8e3ff1ab42 100644 --- a/web/api/formatters/charts2json.h +++ b/web/api/formatters/charts2json.h @@ -5,7 +5,7 @@ #include "rrd2json.h" -extern void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile); +extern void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived); extern void chartcollectors2json(RRDHOST *host, BUFFER *wb); #endif //NETDATA_API_FORMATTER_CHARTS2JSON_H diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c index b2a3028afa..be358e66bb 100644 --- a/web/api/web_api_v1.c +++ b/web/api/web_api_v1.c @@ -349,7 +349,16 @@ inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, buffer_flush(w->response.data); w->response.data->contenttype = CT_APPLICATION_JSON; - charts2json(host, w->response.data, 0); + charts2json(host, w->response.data, 0, 0); + return HTTP_RESP_OK; +} + +inline int web_client_api_request_v1_archivedcharts(RRDHOST *host, struct web_client *w, char *url) { + (void)url; + + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; + charts2json(host, w->response.data, 0, 1); return HTTP_RESP_OK; } @@ -915,6 +924,7 @@ static struct api_command { { "data", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_data }, { "chart", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_chart }, { "charts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_charts }, + { "archivedcharts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_archivedcharts }, // registry checks the ACL by itself, so we allow everything { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry }, diff --git a/web/api/web_api_v1.h b/web/api/web_api_v1.h index 2748023851..445b0e4a5c 100644 --- a/web/api/web_api_v1.h +++ b/web/api/web_api_v1.h @@ -19,6 +19,7 @@ extern int web_client_api_request_single_chart(RRDHOST *host, struct web_client extern int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url); extern int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url); extern int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url); +extern int web_client_api_request_v1_archivedcharts(RRDHOST *host, struct web_client *w, char *url); extern int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url); extern int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url); extern int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url);