diff options
Diffstat (limited to '')
-rw-r--r-- | daemon/analytics.c | 885 | ||||
-rw-r--r-- | daemon/analytics.h | 90 | ||||
-rwxr-xr-x | daemon/anonymous-statistics.sh.in | 72 | ||||
-rw-r--r-- | daemon/buildinfo.c | 47 | ||||
-rw-r--r-- | daemon/common.h | 3 | ||||
-rw-r--r-- | daemon/config/README.md | 2 | ||||
-rw-r--r-- | daemon/global_statistics.c | 155 | ||||
-rw-r--r-- | daemon/global_statistics.h | 11 | ||||
-rw-r--r-- | daemon/main.c | 235 | ||||
-rw-r--r-- | daemon/signals.c | 2 | ||||
-rw-r--r-- | daemon/unit_test.c | 2 |
11 files changed, 1228 insertions, 276 deletions
diff --git a/daemon/analytics.c b/daemon/analytics.c new file mode 100644 index 000000000..08923a3cb --- /dev/null +++ b/daemon/analytics.c @@ -0,0 +1,885 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common.h" + +struct analytics_data analytics_data; +extern void analytics_exporting_connectors (BUFFER *b); +extern void analytics_build_info (BUFFER *b); +extern int aclk_connected; + +struct collector { + char *plugin; + char *module; +}; + +struct array_printer { + int c; + BUFFER *both; +}; + +/* + * Debug logging + */ +void analytics_log_data(void) +{ + debug(D_ANALYTICS, "NETDATA_CONFIG_STREAM_ENABLED : [%s]", analytics_data.netdata_config_stream_enabled); + debug(D_ANALYTICS, "NETDATA_CONFIG_MEMORY_MODE : [%s]", analytics_data.netdata_config_memory_mode); + debug(D_ANALYTICS, "NETDATA_CONFIG_EXPORTING_ENABLED : [%s]", analytics_data.netdata_config_exporting_enabled); + debug(D_ANALYTICS, "NETDATA_EXPORTING_CONNECTORS : [%s]", analytics_data.netdata_exporting_connectors); + debug(D_ANALYTICS, "NETDATA_ALLMETRICS_PROMETHEUS_USED : [%s]", analytics_data.netdata_allmetrics_prometheus_used); + debug(D_ANALYTICS, "NETDATA_ALLMETRICS_SHELL_USED : [%s]", analytics_data.netdata_allmetrics_shell_used); + debug(D_ANALYTICS, "NETDATA_ALLMETRICS_JSON_USED : [%s]", analytics_data.netdata_allmetrics_json_used); + debug(D_ANALYTICS, "NETDATA_DASHBOARD_USED : [%s]", analytics_data.netdata_dashboard_used); + debug(D_ANALYTICS, "NETDATA_COLLECTORS : [%s]", analytics_data.netdata_collectors); + debug(D_ANALYTICS, "NETDATA_COLLECTORS_COUNT : [%s]", analytics_data.netdata_collectors_count); + debug(D_ANALYTICS, "NETDATA_BUILDINFO : [%s]", analytics_data.netdata_buildinfo); + debug(D_ANALYTICS, "NETDATA_CONFIG_PAGE_CACHE_SIZE : [%s]", analytics_data.netdata_config_page_cache_size); + debug(D_ANALYTICS, "NETDATA_CONFIG_MULTIDB_DISK_QUOTA : [%s]", analytics_data.netdata_config_multidb_disk_quota); + debug(D_ANALYTICS, "NETDATA_CONFIG_HTTPS_ENABLED : [%s]", analytics_data.netdata_config_https_enabled); + debug(D_ANALYTICS, "NETDATA_CONFIG_WEB_ENABLED : [%s]", analytics_data.netdata_config_web_enabled); + debug(D_ANALYTICS, "NETDATA_CONFIG_RELEASE_CHANNEL : [%s]", analytics_data.netdata_config_release_channel); + debug(D_ANALYTICS, "NETDATA_MIRRORED_HOST_COUNT : [%s]", analytics_data.netdata_mirrored_host_count); + debug(D_ANALYTICS, "NETDATA_MIRRORED_HOSTS_REACHABLE : [%s]", analytics_data.netdata_mirrored_hosts_reachable); + debug(D_ANALYTICS, "NETDATA_MIRRORED_HOSTS_UNREACHABLE : [%s]", analytics_data.netdata_mirrored_hosts_unreachable); + debug(D_ANALYTICS, "NETDATA_NOTIFICATION_METHODS : [%s]", analytics_data.netdata_notification_methods); + debug(D_ANALYTICS, "NETDATA_ALARMS_NORMAL : [%s]", analytics_data.netdata_alarms_normal); + debug(D_ANALYTICS, "NETDATA_ALARMS_WARNING : [%s]", analytics_data.netdata_alarms_warning); + debug(D_ANALYTICS, "NETDATA_ALARMS_CRITICAL : [%s]", analytics_data.netdata_alarms_critical); + debug(D_ANALYTICS, "NETDATA_CHARTS_COUNT : [%s]", analytics_data.netdata_charts_count); + debug(D_ANALYTICS, "NETDATA_METRICS_COUNT : [%s]", analytics_data.netdata_metrics_count); + debug(D_ANALYTICS, "NETDATA_CONFIG_IS_PARENT : [%s]", analytics_data.netdata_config_is_parent); + debug(D_ANALYTICS, "NETDATA_CONFIG_HOSTS_AVAILABLE : [%s]", analytics_data.netdata_config_hosts_available); + debug(D_ANALYTICS, "NETDATA_HOST_CLOUD_AVAILABLE : [%s]", analytics_data.netdata_host_cloud_available); + debug(D_ANALYTICS, "NETDATA_HOST_ACLK_AVAILABLE : [%s]", analytics_data.netdata_host_aclk_available); + debug(D_ANALYTICS, "NETDATA_HOST_ACLK_IMPLEMENTATION : [%s]", analytics_data.netdata_host_aclk_implementation); + debug(D_ANALYTICS, "NETDATA_HOST_AGENT_CLAIMED : [%s]", analytics_data.netdata_host_agent_claimed); + debug(D_ANALYTICS, "NETDATA_HOST_CLOUD_ENABLED : [%s]", analytics_data.netdata_host_cloud_enabled); +} + +/* + * Free data + */ +void analytics_free_data(void) +{ + freez(analytics_data.netdata_config_stream_enabled); + freez(analytics_data.netdata_config_memory_mode); + freez(analytics_data.netdata_config_exporting_enabled); + freez(analytics_data.netdata_exporting_connectors); + freez(analytics_data.netdata_allmetrics_prometheus_used); + freez(analytics_data.netdata_allmetrics_shell_used); + freez(analytics_data.netdata_allmetrics_json_used); + freez(analytics_data.netdata_dashboard_used); + freez(analytics_data.netdata_collectors); + freez(analytics_data.netdata_collectors_count); + freez(analytics_data.netdata_buildinfo); + freez(analytics_data.netdata_config_page_cache_size); + freez(analytics_data.netdata_config_multidb_disk_quota); + freez(analytics_data.netdata_config_https_enabled); + freez(analytics_data.netdata_config_web_enabled); + freez(analytics_data.netdata_config_release_channel); + freez(analytics_data.netdata_mirrored_host_count); + freez(analytics_data.netdata_mirrored_hosts_reachable); + freez(analytics_data.netdata_mirrored_hosts_unreachable); + freez(analytics_data.netdata_notification_methods); + freez(analytics_data.netdata_alarms_normal); + freez(analytics_data.netdata_alarms_warning); + freez(analytics_data.netdata_alarms_critical); + freez(analytics_data.netdata_charts_count); + freez(analytics_data.netdata_metrics_count); + freez(analytics_data.netdata_config_is_parent); + freez(analytics_data.netdata_config_hosts_available); + freez(analytics_data.netdata_host_cloud_available); + freez(analytics_data.netdata_host_aclk_available); + freez(analytics_data.netdata_host_aclk_implementation); + freez(analytics_data.netdata_host_agent_claimed); + freez(analytics_data.netdata_host_cloud_enabled); +} + +/* + * Set a numeric/boolean data with a value + */ +void analytics_set_data(char **name, char *value) +{ + if (*name) { + analytics_data.data_length -= strlen(*name); + freez(*name); + } + *name = strdupz(value); + analytics_data.data_length += strlen(*name); +} + +/* + * Set a string data with a value + */ +void analytics_set_data_str(char **name, char *value) +{ + size_t value_string_len; + if (*name) { + analytics_data.data_length -= strlen(*name); + freez(*name); + } + value_string_len = strlen(value) + 4; + *name = mallocz(sizeof(char) * value_string_len); + snprintfz(*name, value_string_len - 1, "\"%s\"", value); + analytics_data.data_length += strlen(*name); +} + +/* + * Get data, used by web api v1 + */ +void analytics_get_data(char *name, BUFFER *wb) +{ + buffer_strcat(wb, name); +} + +/* + * Log hits on the allmetrics page, with prometheus parameter + */ +void analytics_log_prometheus(void) +{ + if (likely(analytics_data.prometheus_hits < ANALYTICS_MAX_PROMETHEUS_HITS)) { + analytics_data.prometheus_hits++; + char b[7]; + snprintfz(b, 6, "%d", analytics_data.prometheus_hits); + analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, b); + } +} + +/* + * Log hits on the allmetrics page, with shell parameter (or default) + */ +void analytics_log_shell(void) +{ + if (likely(analytics_data.shell_hits < ANALYTICS_MAX_SHELL_HITS)) { + analytics_data.shell_hits++; + char b[7]; + snprintfz(b, 6, "%d", analytics_data.shell_hits); + analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, b); + } +} + +/* + * Log hits on the allmetrics page, with json parameter + */ +void analytics_log_json(void) +{ + if (likely(analytics_data.json_hits < ANALYTICS_MAX_JSON_HITS)) { + analytics_data.json_hits++; + char b[7]; + snprintfz(b, 6, "%d", analytics_data.json_hits); + analytics_set_data(&analytics_data.netdata_allmetrics_json_used, b); + } +} + +/* + * Log hits on the dashboard, (when calling HELLO). + */ +void analytics_log_dashboard(void) +{ + if (likely(analytics_data.dashboard_hits < ANALYTICS_MAX_DASHBOARD_HITS)) { + analytics_data.dashboard_hits++; + char b[7]; + snprintfz(b, 6, "%d", analytics_data.dashboard_hits); + analytics_set_data(&analytics_data.netdata_dashboard_used, b); + } +} + +void analytics_mirrored_hosts(void) +{ + RRDHOST *host; + int count = 0; + int reachable = 0; + int unreachable = 0; + char b[11]; + + rrd_rdlock(); + rrdhost_foreach_read(host) + { + if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) + continue; + + netdata_mutex_lock(&host->receiver_lock); + ((host->receiver || host == localhost) ? reachable++ : unreachable++); + netdata_mutex_unlock(&host->receiver_lock); + + count++; + } + rrd_unlock(); + + snprintfz(b, 10, "%d", count); + analytics_set_data(&analytics_data.netdata_mirrored_host_count, b); + snprintfz(b, 10, "%d", reachable); + analytics_set_data(&analytics_data.netdata_mirrored_hosts_reachable, b); + snprintfz(b, 10, "%d", unreachable); + analytics_set_data(&analytics_data.netdata_mirrored_hosts_unreachable, b); +} + +void analytics_exporters(void) +{ + //when no exporters are available, an empty string will be sent + //decide if something else is more suitable (but propably not null) + BUFFER *bi = buffer_create(1000); + analytics_exporting_connectors(bi); + analytics_set_data_str(&analytics_data.netdata_exporting_connectors, (char *)buffer_tostring(bi)); + buffer_free(bi); +} + +int collector_counter_callb(void *entry, void *data) +{ + struct array_printer *ap = (struct array_printer *)data; + struct collector *col = (struct collector *)entry; + + BUFFER *bt = ap->both; + + if (likely(ap->c)) { + buffer_strcat(bt, ","); + } + + buffer_strcat(bt, "{"); + buffer_strcat(bt, " \"plugin\": \""); + buffer_strcat(bt, col->plugin); + buffer_strcat(bt, "\", \"module\":\""); + buffer_strcat(bt, col->module); + buffer_strcat(bt, "\" }"); + + (ap->c)++; + + return 0; +} + +/* + * Create a JSON array of available collectors, same as in api/v1/info + */ +void analytics_collectors(void) +{ + RRDSET *st; + DICTIONARY *dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED); + char name[500]; + BUFFER *bt = buffer_create(1000); + + rrdset_foreach_read(st, localhost) + { + if (rrdset_is_available_for_viewers(st)) { + struct collector col = { .plugin = st->plugin_name ? st->plugin_name : "", + .module = st->module_name ? st->module_name : "" }; + snprintfz(name, 499, "%s:%s", col.plugin, col.module); + dictionary_set(dict, name, &col, sizeof(struct collector)); + } + } + + struct array_printer ap; + ap.c = 0; + ap.both = bt; + + dictionary_get_all(dict, collector_counter_callb, &ap); + dictionary_destroy(dict); + + analytics_set_data(&analytics_data.netdata_collectors, (char *)buffer_tostring(ap.both)); + + { + char b[7]; + snprintfz(b, 6, "%d", ap.c); + analytics_set_data(&analytics_data.netdata_collectors_count, b); + } + + buffer_free(bt); +} + +/* + * Run alarm-notify.sh script using the dump_methods parameter + * SEND_CUSTOM is always available + */ +void analytics_alarms_notifications(void) +{ + char *script; + script = mallocz( + sizeof(char) * (strlen(netdata_configured_primary_plugins_dir) + strlen("alarm-notify.sh dump_methods") + 2)); + sprintf(script, "%s/%s", netdata_configured_primary_plugins_dir, "alarm-notify.sh"); + if (unlikely(access(script, R_OK) != 0)) { + info("Alarm notify script %s not found.", script); + freez(script); + return; + } + + strcat(script, " dump_methods"); + + pid_t command_pid; + + debug(D_ANALYTICS, "Executing %s", script); + + BUFFER *b = buffer_create(1000); + int cnt = 0; + FILE *fp = mypopen(script, &command_pid); + if (fp) { + char line[200 + 1]; + + while (fgets(line, 200, fp) != NULL) { + char *end = line; + while (*end && *end != '\n') + end++; + *end = '\0'; + + if (likely(cnt)) + buffer_strcat(b, "|"); + + buffer_strcat(b, line); + + cnt++; + } + mypclose(fp, command_pid); + } + freez(script); + + analytics_set_data_str(&analytics_data.netdata_notification_methods, (char *)buffer_tostring(b)); + + buffer_free(b); +} + +void analytics_charts(void) +{ + RRDSET *st; + int c = 0; + rrdset_foreach_read(st, localhost) + { + if (rrdset_is_available_for_viewers(st)) { + c++; + } + } + { + char b[7]; + snprintfz(b, 6, "%d", c); + analytics_set_data(&analytics_data.netdata_charts_count, b); + } +} + +void analytics_metrics(void) +{ + RRDSET *st; + long int dimensions = 0; + RRDDIM *rd; + rrdset_foreach_read(st, localhost) + { + rrdset_rdlock(st); + rrddim_foreach_read(rd, st) + { + if (rrddim_flag_check(rd, RRDDIM_FLAG_HIDDEN) || rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) + continue; + dimensions++; + } + rrdset_unlock(st); + } + { + char b[7]; + snprintfz(b, 6, "%ld", dimensions); + analytics_set_data(&analytics_data.netdata_metrics_count, b); + } +} + +void analytics_alarms(void) +{ + int alarm_warn = 0, alarm_crit = 0, alarm_normal = 0; + char b[10]; + RRDCALC *rc; + for (rc = localhost->alarms; rc; rc = rc->next) { + if (unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) + continue; + + switch (rc->status) { + case RRDCALC_STATUS_WARNING: + alarm_warn++; + break; + case RRDCALC_STATUS_CRITICAL: + alarm_crit++; + break; + default: + alarm_normal++; + } + } + + snprintfz(b, 9, "%d", alarm_normal); + analytics_set_data(&analytics_data.netdata_alarms_normal, b); + snprintfz(b, 9, "%d", alarm_warn); + analytics_set_data(&analytics_data.netdata_alarms_warning, b); + snprintfz(b, 9, "%d", alarm_crit); + analytics_set_data(&analytics_data.netdata_alarms_critical, b); +} + +/* + * Misc attributes to get (run from meta) + */ +void analytics_misc(void) +{ +#ifdef ENABLE_ACLK + analytics_set_data(&analytics_data.netdata_host_cloud_available, "true"); +#ifdef ACLK_NG + analytics_set_data_str(&analytics_data.netdata_host_aclk_implementation, "Next Generation"); +#else + analytics_set_data_str(&analytics_data.netdata_host_aclk_implementation, "legacy"); +#endif +#else + analytics_set_data(&analytics_data.netdata_host_cloud_available, "false"); +#endif + +#ifdef ENABLE_ACLK + if (aclk_connected) + analytics_set_data(&analytics_data.netdata_host_aclk_available, "true"); + else +#endif + analytics_set_data(&analytics_data.netdata_host_aclk_available, "false"); +} + +/* + * Get the meta data, called from the thread once after the original delay + * These are values that won't change between agent restarts, and therefore + * don't try to read them on each META event send + */ +void analytics_gather_immutable_meta_data(void) +{ + analytics_misc(); + analytics_exporters(); +} + +/* + * Get the meta data, called from the thread on every heartbeat, and right before the EXIT event + * These are values that can change between agent restarts, and therefore + * try to read them on each META event send + */ +void analytics_gather_mutable_meta_data(void) +{ + rrdhost_rdlock(localhost); + + analytics_collectors(); + analytics_alarms(); + analytics_charts(); + analytics_metrics(); + + rrdhost_unlock(localhost); + + analytics_mirrored_hosts(); + analytics_alarms_notifications(); + + analytics_set_data( + &analytics_data.netdata_config_is_parent, (localhost->next || configured_as_parent()) ? "true" : "false"); + + char *claim_id = is_agent_claimed(); + analytics_set_data(&analytics_data.netdata_host_agent_claimed, claim_id ? "true" : "false"); + freez(claim_id); + + { + char b[7]; + snprintfz(b, 6, "%d", analytics_data.prometheus_hits); + analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, b); + + snprintfz(b, 6, "%d", analytics_data.shell_hits); + analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, b); + + snprintfz(b, 6, "%d", analytics_data.json_hits); + analytics_set_data(&analytics_data.netdata_allmetrics_json_used, b); + + snprintfz(b, 6, "%d", analytics_data.dashboard_hits); + analytics_set_data(&analytics_data.netdata_dashboard_used, b); + + snprintfz(b, 6, "%zu", rrd_hosts_available); + analytics_set_data(&analytics_data.netdata_config_hosts_available, b); + } +} + +void analytics_main_cleanup(void *ptr) +{ + struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; + static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; + + debug(D_ANALYTICS, "Cleaning up..."); + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; +} + +/* + * The analytics thread. Sleep for ANALYTICS_INIT_SLEEP_SEC, + * gather the data, and then go to a loop where every ANALYTICS_HEARTBEAT + * it will send a new META event after gathering data that could be changed + * while the agent is running + */ +void *analytics_main(void *ptr) +{ + netdata_thread_cleanup_push(analytics_main_cleanup, ptr); + unsigned int sec = 0; + heartbeat_t hb; + heartbeat_init(&hb); + usec_t step_ut = USEC_PER_SEC; + + debug(D_ANALYTICS, "Analytics thread starts"); + + //first delay after agent start + while (!netdata_exit && likely(sec <= ANALYTICS_INIT_SLEEP_SEC)) { + heartbeat_next(&hb, step_ut); + sec++; + } + + if (unlikely(netdata_exit)) + goto cleanup; + + analytics_gather_immutable_meta_data(); + analytics_gather_mutable_meta_data(); + send_statistics("META", "-", "-"); + analytics_log_data(); + + sec = 0; + while (1) { + heartbeat_next(&hb, step_ut * 2); + sec += 2; + + if (unlikely(netdata_exit)) + break; + + if (likely(sec < ANALYTICS_HEARTBEAT)) + continue; + + analytics_gather_mutable_meta_data(); + send_statistics("META", "-", "-"); + analytics_log_data(); + sec = 0; + } + +cleanup: + netdata_thread_cleanup_pop(1); + return NULL; +} + +static const char *verify_required_directory(const char *dir) +{ + if (chdir(dir) == -1) + fatal("Cannot change directory to '%s'", dir); + + DIR *d = opendir(dir); + if (!d) + fatal("Cannot examine the contents of directory '%s'", dir); + closedir(d); + + return dir; +} + +/* + * This is called after the rrdinit + * These values will be sent on the START event + */ +void set_late_global_environment() +{ + analytics_set_data(&analytics_data.netdata_config_stream_enabled, default_rrdpush_enabled ? "true" : "false"); + analytics_set_data_str(&analytics_data.netdata_config_memory_mode, (char *)rrd_memory_mode_name(default_rrd_memory_mode)); + analytics_set_data(&analytics_data.netdata_config_exporting_enabled, appconfig_get_boolean(&exporting_config, CONFIG_SECTION_EXPORTING, "enabled", CONFIG_BOOLEAN_NO) ? "true" : "false"); + +#ifdef DISABLE_CLOUD + analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "false"); +#else + analytics_set_data( + &analytics_data.netdata_host_cloud_enabled, + appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", CONFIG_BOOLEAN_YES) ? "true" : "false"); +#endif + +#ifdef ENABLE_DBENGINE + { + char b[16]; + snprintfz(b, 15, "%d", default_rrdeng_page_cache_mb); + analytics_set_data(&analytics_data.netdata_config_page_cache_size, b); + + snprintfz(b, 15, "%d", default_multidb_disk_quota_mb); + analytics_set_data(&analytics_data.netdata_config_multidb_disk_quota, b); + } +#endif + +#ifdef ENABLE_HTTPS + analytics_set_data(&analytics_data.netdata_config_https_enabled, "true"); +#else + analytics_set_data(&analytics_data.netdata_config_https_enabled, "false"); +#endif + + if (web_server_mode == WEB_SERVER_MODE_NONE) + analytics_set_data(&analytics_data.netdata_config_web_enabled, "false"); + else + analytics_set_data(&analytics_data.netdata_config_web_enabled, "true"); + + analytics_set_data_str(&analytics_data.netdata_config_release_channel, (char *)get_release_channel()); + + { + BUFFER *bi = buffer_create(1000); + analytics_build_info(bi); + analytics_set_data_str(&analytics_data.netdata_buildinfo, (char *)buffer_tostring(bi)); + buffer_free(bi); + } +} + +static void get_system_timezone(void) +{ + // avoid flood calls to stat(/etc/localtime) + // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux + const char *tz = getenv("TZ"); + if (!tz || !*tz) + setenv("TZ", config_get(CONFIG_SECTION_GLOBAL, "TZ environment variable", ":/etc/localtime"), 0); + + char buffer[FILENAME_MAX + 1] = ""; + const char *timezone = NULL; + ssize_t ret; + + // use the TZ variable + if (tz && *tz && *tz != ':') { + timezone = tz; + info("TIMEZONE: using TZ variable '%s'", timezone); + } + + // use the contents of /etc/timezone + if (!timezone && !read_file("/etc/timezone", buffer, FILENAME_MAX)) { + timezone = buffer; + info("TIMEZONE: using the contents of /etc/timezone: '%s'", timezone); + } + + // read the link /etc/localtime + if (!timezone) { + ret = readlink("/etc/localtime", buffer, FILENAME_MAX); + + if (ret > 0) { + buffer[ret] = '\0'; + + char *cmp = "/usr/share/zoneinfo/"; + size_t cmp_len = strlen(cmp); + + char *s = strstr(buffer, cmp); + if (s && s[cmp_len]) { + timezone = &s[cmp_len]; + info("TIMEZONE: using the link of /etc/localtime: '%s'", timezone); + } + } else + buffer[0] = '\0'; + } + + // find the timezone from strftime() + if (!timezone) { + time_t t; + struct tm *tmp, tmbuf; + + t = now_realtime_sec(); + tmp = localtime_r(&t, &tmbuf); + + if (tmp != NULL) { + if (strftime(buffer, FILENAME_MAX, "%Z", tmp) == 0) + buffer[0] = '\0'; + else { + buffer[FILENAME_MAX] = '\0'; + timezone = buffer; + info("TIMEZONE: using strftime(): '%s'", timezone); + } + } + } + + if (timezone && *timezone) { + // make sure it does not have illegal characters + // info("TIMEZONE: fixing '%s'", timezone); + + size_t len = strlen(timezone); + char tmp[len + 1]; + char *d = tmp; + *d = '\0'; + + while (*timezone) { + if (isalnum(*timezone) || *timezone == '_' || *timezone == '/') + *d++ = *timezone++; + else + timezone++; + } + *d = '\0'; + strncpyz(buffer, tmp, len); + timezone = buffer; + info("TIMEZONE: fixed as '%s'", timezone); + } + + if (!timezone || !*timezone) + timezone = "unknown"; + + netdata_configured_timezone = config_get(CONFIG_SECTION_GLOBAL, "timezone", timezone); +} + +void set_global_environment() +{ + { + char b[16]; + snprintfz(b, 15, "%d", default_rrd_update_every); + setenv("NETDATA_UPDATE_EVERY", b, 1); + } + + setenv("NETDATA_VERSION", program_version, 1); + setenv("NETDATA_HOSTNAME", netdata_configured_hostname, 1); + setenv("NETDATA_CONFIG_DIR", verify_required_directory(netdata_configured_user_config_dir), 1); + setenv("NETDATA_USER_CONFIG_DIR", verify_required_directory(netdata_configured_user_config_dir), 1); + setenv("NETDATA_STOCK_CONFIG_DIR", verify_required_directory(netdata_configured_stock_config_dir), 1); + setenv("NETDATA_PLUGINS_DIR", verify_required_directory(netdata_configured_primary_plugins_dir), 1); + setenv("NETDATA_WEB_DIR", verify_required_directory(netdata_configured_web_dir), 1); + setenv("NETDATA_CACHE_DIR", verify_required_directory(netdata_configured_cache_dir), 1); + setenv("NETDATA_LIB_DIR", verify_required_directory(netdata_configured_varlib_dir), 1); + setenv("NETDATA_LOCK_DIR", netdata_configured_lock_dir, 1); + setenv("NETDATA_LOG_DIR", verify_required_directory(netdata_configured_log_dir), 1); + setenv("HOME", verify_required_directory(netdata_configured_home_dir), 1); + setenv("NETDATA_HOST_PREFIX", netdata_configured_host_prefix, 1); + + analytics_data.data_length = 0; + analytics_set_data(&analytics_data.netdata_config_stream_enabled, "null"); + analytics_set_data(&analytics_data.netdata_config_memory_mode, "null"); + analytics_set_data(&analytics_data.netdata_config_exporting_enabled, "null"); + analytics_set_data(&analytics_data.netdata_exporting_connectors, "null"); + analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, "null"); + analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, "null"); + analytics_set_data(&analytics_data.netdata_allmetrics_json_used, "null"); + analytics_set_data(&analytics_data.netdata_dashboard_used, "null"); + analytics_set_data(&analytics_data.netdata_collectors, "null"); + analytics_set_data(&analytics_data.netdata_collectors_count, "null"); + analytics_set_data(&analytics_data.netdata_buildinfo, "null"); + analytics_set_data(&analytics_data.netdata_config_page_cache_size, "null"); + analytics_set_data(&analytics_data.netdata_config_multidb_disk_quota, "null"); + analytics_set_data(&analytics_data.netdata_config_https_enabled, "null"); + analytics_set_data(&analytics_data.netdata_config_web_enabled, "null"); + analytics_set_data(&analytics_data.netdata_config_release_channel, "null"); + analytics_set_data(&analytics_data.netdata_mirrored_host_count, "null"); + analytics_set_data(&analytics_data.netdata_mirrored_hosts_reachable, "null"); + analytics_set_data(&analytics_data.netdata_mirrored_hosts_unreachable, "null"); + analytics_set_data(&analytics_data.netdata_notification_methods, "null"); + analytics_set_data(&analytics_data.netdata_alarms_normal, "null"); + analytics_set_data(&analytics_data.netdata_alarms_warning, "null"); + analytics_set_data(&analytics_data.netdata_alarms_critical, "null"); + analytics_set_data(&analytics_data.netdata_charts_count, "null"); + analytics_set_data(&analytics_data.netdata_metrics_count, "null"); + analytics_set_data(&analytics_data.netdata_config_is_parent, "null"); + analytics_set_data(&analytics_data.netdata_config_hosts_available, "null"); + analytics_set_data(&analytics_data.netdata_host_cloud_available, "null"); + analytics_set_data(&analytics_data.netdata_host_aclk_implementation, "null"); + analytics_set_data(&analytics_data.netdata_host_aclk_available, "null"); + analytics_set_data(&analytics_data.netdata_host_agent_claimed, "null"); + analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "null"); + + analytics_data.prometheus_hits = 0; + analytics_data.shell_hits = 0; + analytics_data.json_hits = 0; + analytics_data.dashboard_hits = 0; + + char *default_port = appconfig_get(&netdata_config, CONFIG_SECTION_WEB, "default port", NULL); + int clean = 0; + if (!default_port) { + default_port = strdupz("19999"); + clean = 1; + } + + setenv("NETDATA_LISTEN_PORT", default_port, 1); + if (clean) + freez(default_port); + + get_system_timezone(); + + // set the path we need + char path[1024 + 1], *p = getenv("PATH"); + if (!p) + p = "/bin:/usr/bin"; + snprintfz(path, 1024, "%s:%s", p, "/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin"); + setenv("PATH", config_get(CONFIG_SECTION_PLUGINS, "PATH environment variable", path), 1); + + // python options + p = getenv("PYTHONPATH"); + if (!p) + p = ""; + setenv("PYTHONPATH", config_get(CONFIG_SECTION_PLUGINS, "PYTHONPATH environment variable", p), 1); + + // disable buffering for python plugins + setenv("PYTHONUNBUFFERED", "1", 1); + + // switch to standard locale for plugins + setenv("LC_ALL", "C", 1); +} + +void send_statistics(const char *action, const char *action_result, const char *action_data) +{ + static char *as_script; + + if (netdata_anonymous_statistics_enabled == -1) { + char *optout_file = mallocz( + sizeof(char) * + (strlen(netdata_configured_user_config_dir) + strlen(".opt-out-from-anonymous-statistics") + 2)); + sprintf(optout_file, "%s/%s", netdata_configured_user_config_dir, ".opt-out-from-anonymous-statistics"); + if (likely(access(optout_file, R_OK) != 0)) { + as_script = mallocz( + sizeof(char) * + (strlen(netdata_configured_primary_plugins_dir) + strlen("anonymous-statistics.sh") + 2)); + sprintf(as_script, "%s/%s", netdata_configured_primary_plugins_dir, "anonymous-statistics.sh"); + if (unlikely(access(as_script, R_OK) != 0)) { + netdata_anonymous_statistics_enabled = 0; + info("Anonymous statistics script %s not found.", as_script); + freez(as_script); + } else { + netdata_anonymous_statistics_enabled = 1; + } + } else { + netdata_anonymous_statistics_enabled = 0; + as_script = NULL; + } + freez(optout_file); + } + if (!netdata_anonymous_statistics_enabled) + return; + if (!action) + return; + if (!action_result) + action_result = ""; + if (!action_data) + action_data = ""; + char *command_to_run = mallocz( + sizeof(char) * (strlen(action) + strlen(action_result) + strlen(action_data) + strlen(as_script) + + analytics_data.data_length + (ANALYTICS_NO_OF_ITEMS * 3) + 15)); + pid_t command_pid; + + sprintf( + command_to_run, + "%s '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' ", + as_script, + action, + action_result, + action_data, + analytics_data.netdata_config_stream_enabled, + analytics_data.netdata_config_memory_mode, + analytics_data.netdata_config_exporting_enabled, + analytics_data.netdata_exporting_connectors, + analytics_data.netdata_allmetrics_prometheus_used, + analytics_data.netdata_allmetrics_shell_used, + analytics_data.netdata_allmetrics_json_used, + analytics_data.netdata_dashboard_used, + analytics_data.netdata_collectors, + analytics_data.netdata_collectors_count, + analytics_data.netdata_buildinfo, + analytics_data.netdata_config_page_cache_size, + analytics_data.netdata_config_multidb_disk_quota, + analytics_data.netdata_config_https_enabled, + analytics_data.netdata_config_web_enabled, + analytics_data.netdata_config_release_channel, + analytics_data.netdata_mirrored_host_count, + analytics_data.netdata_mirrored_hosts_reachable, + analytics_data.netdata_mirrored_hosts_unreachable, + analytics_data.netdata_notification_methods, + analytics_data.netdata_alarms_normal, + analytics_data.netdata_alarms_warning, + analytics_data.netdata_alarms_critical, + analytics_data.netdata_charts_count, + analytics_data.netdata_metrics_count, + analytics_data.netdata_config_is_parent, + analytics_data.netdata_config_hosts_available, + analytics_data.netdata_host_cloud_available, + analytics_data.netdata_host_aclk_available, + analytics_data.netdata_host_aclk_implementation, + analytics_data.netdata_host_agent_claimed, + analytics_data.netdata_host_cloud_enabled); + + info("%s '%s' '%s' '%s'", as_script, action, action_result, action_data); + + FILE *fp = mypopen(command_to_run, &command_pid); + if (fp) { + char buffer[100 + 1]; + while (fgets(buffer, 100, fp) != NULL) + ; + mypclose(fp, command_pid); + } + freez(command_to_run); +} diff --git a/daemon/analytics.h b/daemon/analytics.h new file mode 100644 index 000000000..e888297df --- /dev/null +++ b/daemon/analytics.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_ANALYTICS_H +#define NETDATA_ANALYTICS_H 1 + +#include "../daemon/common.h" + +/* Max number of seconds before the first META analytics is sent */ +#define ANALYTICS_INIT_SLEEP_SEC 120 + +/* Send a META event every X seconds */ +#define ANALYTICS_HEARTBEAT 7200 + +/* Maximum number of hits to log */ +#define ANALYTICS_MAX_PROMETHEUS_HITS 255 +#define ANALYTICS_MAX_SHELL_HITS 255 +#define ANALYTICS_MAX_JSON_HITS 255 +#define ANALYTICS_MAX_DASHBOARD_HITS 255 + +#define NETDATA_PLUGIN_HOOK_ANALYTICS \ + { \ + .name = "ANALYTICS", \ + .config_section = NULL, \ + .config_name = NULL, \ + .enabled = 0, \ + .thread = NULL, \ + .init_routine = NULL, \ + .start_routine = analytics_main \ + }, + +/* Needed to calculate the space needed for parameters */ +#define ANALYTICS_NO_OF_ITEMS 32 + +struct analytics_data { + char *netdata_config_stream_enabled; + char *netdata_config_memory_mode; + char *netdata_exporting_connectors; + char *netdata_config_exporting_enabled; + char *netdata_allmetrics_prometheus_used; + char *netdata_allmetrics_shell_used; + char *netdata_allmetrics_json_used; + char *netdata_dashboard_used; + char *netdata_collectors; + char *netdata_collectors_count; + char *netdata_buildinfo; + char *netdata_config_page_cache_size; + char *netdata_config_multidb_disk_quota; + char *netdata_config_https_enabled; + char *netdata_config_web_enabled; + char *netdata_config_release_channel; + char *netdata_mirrored_host_count; + char *netdata_mirrored_hosts_reachable; + char *netdata_mirrored_hosts_unreachable; + char *netdata_notification_methods; + char *netdata_alarms_normal; + char *netdata_alarms_warning; + char *netdata_alarms_critical; + char *netdata_charts_count; + char *netdata_metrics_count; + char *netdata_config_is_parent; + char *netdata_config_hosts_available; + char *netdata_host_cloud_available; + char *netdata_host_aclk_available; + char *netdata_host_aclk_implementation; + char *netdata_host_agent_claimed; + char *netdata_host_cloud_enabled; + + size_t data_length; + + uint8_t prometheus_hits; + uint8_t shell_hits; + uint8_t json_hits; + uint8_t dashboard_hits; +}; + +extern void *analytics_main(void *ptr); +extern void analytics_get_data(char *name, BUFFER *wb); +extern void set_late_global_environment(void); +extern void analytics_free_data(void); +extern void set_global_environment(void); +extern void send_statistics(const char *action, const char *action_result, const char *action_data); +extern void analytics_log_shell(void); +extern void analytics_log_json(void); +extern void analytics_log_prometheus(void); +extern void analytics_log_dashboard(void); +extern void analytics_gather_mutable_meta_data(void); + +extern struct analytics_data analytics_data; + +#endif //NETDATA_ANALYTICS_H diff --git a/daemon/anonymous-statistics.sh.in b/daemon/anonymous-statistics.sh.in index 47004f3d0..bd22963d9 100755 --- a/daemon/anonymous-statistics.sh.in +++ b/daemon/anonymous-statistics.sh.in @@ -26,6 +26,40 @@ fi NETDATA_VERSION=$(echo "${NETDATA_VERSION}" | sed 's/-.*//g' | tr -d 'v') # ------------------------------------------------------------------------------------------------- +# Get the extra variables + +NETDATA_CONFIG_STREAM_ENABLED="${4}" +NETDATA_CONFIG_MEMORY_MODE="${5}" +NETDATA_CONFIG_EXPORTING_ENABLED="${6}" +NETDATA_EXPORTING_CONNECTORS="${7}" +NETDATA_ALLMETRICS_PROMETHEUS_USED="${8}" +NETDATA_ALLMETRICS_SHELL_USED="${9}" +NETDATA_ALLMETRICS_JSON_USED="${10}" +NETDATA_DASHBOARD_USED="${11}" +NETDATA_COLLECTORS="${12}" +NETDATA_COLLECTORS_COUNT="${13}" +NETDATA_BUILDINFO="${14}" +NETDATA_CONFIG_PAGE_CACHE_SIZE="${15}" +NETDATA_CONFIG_MULTIDB_DISK_QUOTA="${16}" +NETDATA_CONFIG_HTTPS_ENABLED="${17}" +NETDATA_CONFIG_WEB_ENABLED="${18}" +NETDATA_CONFIG_RELEASE_CHANNEL="${19}" +NETDATA_MIRRORED_HOST_COUNT="${20}" +NETDATA_MIRRORED_HOSTS_REACHABLE="${21}" +NETDATA_MIRRORED_HOSTS_UNREACHABLE="${22}" +NETDATA_NOTIFICATION_METHODS="${23}" +NETDATA_ALARMS_NORMAL="${24}" +NETDATA_ALARMS_WARNING="${25}" +NETDATA_ALARMS_CRITICAL="${26}" +NETDATA_CHARTS_COUNT="${27}" +NETDATA_METRICS_COUNT="${28}" +NETDATA_CONFIG_IS_PARENT="${29}" +NETDATA_CONFIG_HOSTS_AVAILABLE="${30}" +NETDATA_HOST_CLOUD_AVAILABLE="${31}" +NETDATA_HOST_ACLK_AVAILABLE="${32}" +NETDATA_HOST_ACLK_IMPLEMENTATION="${33}" +NETDATA_HOST_AGENT_CLAIMED="${34}" +NETDATA_HOST_CLOUD_ENABLED="${35}" # define body of request to be sent REQ_BODY="$(cat << EOF @@ -44,6 +78,8 @@ REQ_BODY="$(cat << EOF "action_data": "${ACTION_DATA}", "netdata_machine_guid": "${NETDATA_REGISTRY_UNIQUE_ID}", "netdata_version": "${NETDATA_VERSION}", + "netdata_buildinfo": ${NETDATA_BUILDINFO}, + "netdata_release_channel": ${NETDATA_CONFIG_RELEASE_CHANNEL}, "host_os_name": "${NETDATA_HOST_OS_NAME}", "host_os_id": "${NETDATA_HOST_OS_ID}", "host_os_id_like": "${NETDATA_HOST_OS_ID_LIKE}", @@ -72,7 +108,39 @@ REQ_BODY="$(cat << EOF "system_disk_detection": "${NETDATA_SYSTEM_DISK_DETECTION}", "system_ram_detection": "${NETDATA_SYSTEM_RAM_DETECTION}", "system_total_disk_size": "${NETDATA_SYSTEM_TOTAL_DISK_SIZE}", - "system_total_ram": "${NETDATA_SYSTEM_TOTAL_RAM}" + "system_total_ram": "${NETDATA_SYSTEM_TOTAL_RAM}", + "config_stream_enabled": ${NETDATA_CONFIG_STREAM_ENABLED}, + "config_memory_mode": ${NETDATA_CONFIG_MEMORY_MODE}, + "config_page_cache_size": ${NETDATA_CONFIG_PAGE_CACHE_SIZE}, + "config_multidb_disk_quota": ${NETDATA_CONFIG_MULTIDB_DISK_QUOTA}, + "config_https_enabled": ${NETDATA_CONFIG_HTTPS_ENABLED}, + "config_web_enabled": ${NETDATA_CONFIG_WEB_ENABLED}, + "config_exporting_enabled": ${NETDATA_CONFIG_EXPORTING_ENABLED}, + "config_is_parent": ${NETDATA_CONFIG_IS_PARENT}, + "config_hosts_available": ${NETDATA_CONFIG_HOSTS_AVAILABLE}, + "alarms_normal": ${NETDATA_ALARMS_NORMAL}, + "alarms_warning": ${NETDATA_ALARMS_WARNING}, + "alarms_critical": ${NETDATA_ALARMS_CRITICAL}, + "host_charts_count": ${NETDATA_CHARTS_COUNT}, + "host_metrics_count": ${NETDATA_METRICS_COUNT}, + "host_collectors":[ + ${NETDATA_COLLECTORS} + ], + "host_collectors_count": ${NETDATA_COLLECTORS_COUNT}, + "host_notification_methods": ${NETDATA_NOTIFICATION_METHODS}, + "host_allmetrics_prometheus_used": ${NETDATA_ALLMETRICS_PROMETHEUS_USED}, + "host_allmetrics_shell_used": ${NETDATA_ALLMETRICS_SHELL_USED}, + "host_allmetrics_json_used": ${NETDATA_ALLMETRICS_JSON_USED}, + "host_dashboard_used": ${NETDATA_DASHBOARD_USED}, + "host_cloud_available": ${NETDATA_HOST_CLOUD_AVAILABLE}, + "host_cloud_enabled": ${NETDATA_HOST_CLOUD_ENABLED}, + "host_agent_claimed": ${NETDATA_HOST_AGENT_CLAIMED}, + "host_aclk_available": ${NETDATA_HOST_ACLK_AVAILABLE}, + "host_aclk_implementation": ${NETDATA_HOST_ACLK_IMPLEMENTATION}, + "mirrored_host_count": ${NETDATA_MIRRORED_HOST_COUNT}, + "mirrored_hosts_reachable": ${NETDATA_MIRRORED_HOSTS_REACHABLE}, + "mirrored_hosts_unreachable": ${NETDATA_MIRRORED_HOSTS_UNREACHABLE}, + "exporting_connectors": ${NETDATA_EXPORTING_CONNECTORS} } } EOF @@ -80,7 +148,7 @@ EOF # send the anonymous statistics to the Netdata PostHog if [ -n "$(command -v curl 2> /dev/null)" ]; then - curl -X POST --header "Content-Type: application/json" -d "${REQ_BODY}" https://posthog.netdata.cloud/capture/ > /dev/null 2>&1 + curl -X POST --max-time 2 --header "Content-Type: application/json" -d "${REQ_BODY}" https://posthog.netdata.cloud/capture/ > /dev/null 2>&1 else wget -q -O - --no-check-certificate \ --method POST \ diff --git a/daemon/buildinfo.c b/daemon/buildinfo.c index b16390544..ebeaa996d 100644 --- a/daemon/buildinfo.c +++ b/daemon/buildinfo.c @@ -2,6 +2,7 @@ #include <stdio.h> #include "./config.h" +#include "common.h" // Optional features @@ -312,3 +313,49 @@ void print_build_info_json(void) { printf(" }\n"); printf("}\n"); }; + +//return a list of enabled features for use in analytics +//find a way to have proper | +void analytics_build_info(BUFFER *b) { + if(FEAT_DBENGINE) buffer_strcat (b, "dbengine"); + if(FEAT_NATIVE_HTTPS) buffer_strcat (b, "|Native HTTPS"); + if(FEAT_CLOUD) buffer_strcat (b, "|Netdata Cloud"); + if(FEAT_TLS_HOST_VERIFY) buffer_strcat (b, "|TLS Host Verification"); + + if(FEAT_JEMALLOC) buffer_strcat (b, "|jemalloc"); + if(FEAT_JSONC) buffer_strcat (b, "|JSON-C"); + if(FEAT_LIBCAP) buffer_strcat (b, "|libcap"); + if(FEAT_CRYPTO) buffer_strcat (b, "|libcrypto"); + if(FEAT_LIBM) buffer_strcat (b, "|libm"); + +#ifndef ACLK_NG +#if defined(ENABLE_ACLK) + { + char buf[20]; + snprintfz(buf, 19, "|LWS v%d.%d.%d", LWS_LIBRARY_VERSION_MAJOR, LWS_LIBRARY_VERSION_MINOR, LWS_LIBRARY_VERSION_PATCH); + if(FEAT_LWS) buffer_strcat(b, buf); + } +#else + if(FEAT_LWS) buffer_strcat(b, "|LWS"); +#endif + if(FEAT_MOSQUITTO) buffer_strcat(b, "|mosquitto"); +#endif + if(FEAT_TCMALLOC) buffer_strcat(b, "|tcalloc"); + if(FEAT_ZLIB) buffer_strcat(b, "|zlib"); + + if(FEAT_APPS_PLUGIN) buffer_strcat(b, "|apps"); + if(FEAT_CGROUP_NET) buffer_strcat(b, "|cgroup Network Tracking"); + if(FEAT_CUPS) buffer_strcat(b, "|CUPS"); + if(FEAT_EBPF) buffer_strcat(b, "|EBPF"); + if(FEAT_IPMI) buffer_strcat(b, "|IPMI"); + if(FEAT_NFACCT) buffer_strcat(b, "|NFACCT"); + if(FEAT_PERF) buffer_strcat(b, "|perf"); + if(FEAT_SLABINFO) buffer_strcat(b, "|slabinfo"); + if(FEAT_XEN) buffer_strcat(b, "|Xen"); + if(FEAT_XEN_VBD_ERROR) buffer_strcat(b, "|Xen VBD Error Tracking"); + + if(FEAT_KINESIS) buffer_strcat(b, "|AWS Kinesis"); + if(FEAT_PUBSUB) buffer_strcat(b, "|GCP PubSub"); + if(FEAT_MONGO) buffer_strcat(b, "|MongoDB"); + if(FEAT_REMOTE_WRITE) buffer_strcat(b, "|Prometheus Remote Write"); +} diff --git a/daemon/common.h b/daemon/common.h index 1a58ddda8..4cb54010c 100644 --- a/daemon/common.h +++ b/daemon/common.h @@ -77,11 +77,12 @@ // netdata agent spawn server #include "spawn/spawn.h" -// the netdata deamon +// the netdata daemon #include "daemon.h" #include "main.h" #include "signals.h" #include "commands.h" +#include "analytics.h" // global netdata daemon variables extern char *netdata_configured_hostname; diff --git a/daemon/config/README.md b/daemon/config/README.md index b1e790a21..cc755af78 100644 --- a/daemon/config/README.md +++ b/daemon/config/README.md @@ -50,7 +50,7 @@ Please note that your data history will be lost if you have modified `history` p | setting|default|info||| |:-----:|:-----:|:---|---|---| | process scheduling policy|`keep`|See [Netdata process scheduling policy](/daemon/README.md#netdata-process-scheduling-policy)||| -| OOM score|`1000`|See [OOM score](../#oom-score)||| +| OOM score|`1000`|See [OOM score](/daemon/README.md#oom-score)||| | glibc malloc arena max for plugins|`1`|See [Virtual memory](/daemon/README.md#virtual-memory).||| | glibc malloc arena max for Netdata|`1`|See [Virtual memory](/daemon/README.md#virtual-memory).||| | hostname|auto-detected|The hostname of the computer running Netdata.||| diff --git a/daemon/global_statistics.c b/daemon/global_statistics.c index 7e7835513..edd261476 100644 --- a/daemon/global_statistics.c +++ b/daemon/global_statistics.c @@ -4,6 +4,7 @@ #define GLOBAL_STATS_RESET_WEB_USEC_MAX 0x01 +#define CONFIG_SECTION_GLOBAL_STATISTICS "global statistics" static struct global_statistics { volatile uint16_t connected_clients; @@ -180,63 +181,17 @@ void global_statistics_charts(void) { static collected_number compression_ratio = -1, average_response_time = -1; + static time_t netdata_start_time = 0; + if (!netdata_start_time) + netdata_start_time = now_boottime_sec(); + time_t netdata_uptime = now_boottime_sec() - netdata_start_time; + struct global_statistics gs; - struct rusage me, thread; + struct rusage me; global_statistics_copy(&gs, GLOBAL_STATS_RESET_WEB_USEC_MAX); - getrusage(RUSAGE_THREAD, &thread); getrusage(RUSAGE_SELF, &me); - { - static RRDSET *st_cpu_thread = NULL; - static RRDDIM *rd_cpu_thread_user = NULL, - *rd_cpu_thread_system = NULL; - -#ifdef __FreeBSD__ - if (unlikely(!st_cpu_thread)) { - st_cpu_thread = rrdset_create_localhost( - "netdata" - , "plugin_freebsd_cpu" - , NULL - , "freebsd" - , NULL - , "NetData FreeBSD Plugin CPU usage" - , "milliseconds/s" - , "netdata" - , "stats" - , 132000 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); -#else - if (unlikely(!st_cpu_thread)) { - st_cpu_thread = rrdset_create_localhost( - "netdata" - , "plugin_proc_cpu" - , NULL - , "proc" - , NULL - , "NetData Proc Plugin CPU usage" - , "milliseconds/s" - , "netdata" - , "stats" - , 132000 - , localhost->rrd_update_every - , RRDSET_TYPE_STACKED - ); -#endif - - rd_cpu_thread_user = rrddim_add(st_cpu_thread, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL); - rd_cpu_thread_system = rrddim_add(st_cpu_thread, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL); - } - else - rrdset_next(st_cpu_thread); - - rrddim_set_by_pointer(st_cpu_thread, rd_cpu_thread_user, thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec); - rrddim_set_by_pointer(st_cpu_thread, rd_cpu_thread_system, thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec); - rrdset_done(st_cpu_thread); - } - // ---------------------------------------------------------------- { @@ -251,7 +206,7 @@ void global_statistics_charts(void) { , NULL , "netdata" , NULL - , "NetData CPU usage" + , "Netdata CPU usage" , "milliseconds/s" , "netdata" , "stats" @@ -274,6 +229,35 @@ void global_statistics_charts(void) { // ---------------------------------------------------------------- { + static RRDSET *st_uptime = NULL; + static RRDDIM *rd_uptime = NULL; + + if (unlikely(!st_uptime)) { + st_uptime = rrdset_create_localhost( + "netdata", + "uptime", + NULL, + "netdata", + NULL, + "Netdata uptime", + "seconds", + "netdata", + "stats", + 130100, + localhost->rrd_update_every, + RRDSET_TYPE_LINE); + + rd_uptime = rrddim_add(st_uptime, "uptime", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } else + rrdset_next(st_uptime); + + rrddim_set_by_pointer(st_uptime, rd_uptime, netdata_uptime); + rrdset_done(st_uptime); + } + + // ---------------------------------------------------------------- + + { static RRDSET *st_clients = NULL; static RRDDIM *rd_clients = NULL; @@ -284,7 +268,7 @@ void global_statistics_charts(void) { , NULL , "netdata" , NULL - , "NetData Web Clients" + , "Netdata Web Clients" , "connected clients" , "netdata" , "stats" @@ -315,7 +299,7 @@ void global_statistics_charts(void) { , NULL , "netdata" , NULL - , "NetData Web Requests" + , "Netdata Web Requests" , "requests/s" , "netdata" , "stats" @@ -347,7 +331,7 @@ void global_statistics_charts(void) { , NULL , "netdata" , NULL - , "NetData Network Traffic" + , "Netdata Network Traffic" , "kilobits/s" , "netdata" , "stats" @@ -381,7 +365,7 @@ void global_statistics_charts(void) { , NULL , "netdata" , NULL - , "NetData API Response Time" + , "Netdata API Response Time" , "milliseconds/request" , "netdata" , "stats" @@ -430,7 +414,7 @@ void global_statistics_charts(void) { , NULL , "netdata" , NULL - , "NetData API Responses Compression Savings Ratio" + , "Netdata API Responses Compression Savings Ratio" , "percentage" , "netdata" , "stats" @@ -477,7 +461,7 @@ void global_statistics_charts(void) { , NULL , "queries" , NULL - , "NetData API Queries" + , "Netdata API Queries" , "queries/s" , "netdata" , "stats" @@ -510,7 +494,7 @@ void global_statistics_charts(void) { , NULL , "queries" , NULL - , "NetData API Points" + , "Netdata API Points" , "points/s" , "netdata" , "stats" @@ -579,7 +563,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData DB engine data extents' compression savings ratio" + , "Netdata DB engine data extents' compression savings ratio" , "percentage" , "netdata" , "stats" @@ -621,7 +605,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData DB engine page cache hit ratio" + , "Netdata DB engine page cache hit ratio" , "percentage" , "netdata" , "stats" @@ -676,7 +660,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData dbengine page cache statistics" + , "Netdata dbengine page cache statistics" , "pages" , "netdata" , "stats" @@ -721,7 +705,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData dbengine long-term page statistics" + , "Netdata dbengine long-term page statistics" , "pages" , "netdata" , "stats" @@ -761,7 +745,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData DB engine I/O throughput" + , "Netdata DB engine I/O throughput" , "MiB/s" , "netdata" , "stats" @@ -795,7 +779,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData DB engine I/O operations" + , "Netdata DB engine I/O operations" , "operations/s" , "netdata" , "stats" @@ -830,7 +814,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData DB engine errors" + , "Netdata DB engine errors" , "errors/s" , "netdata" , "stats" @@ -867,7 +851,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData DB engine File Descriptors" + , "Netdata DB engine File Descriptors" , "descriptors" , "netdata" , "stats" @@ -906,7 +890,7 @@ void global_statistics_charts(void) { , NULL , "dbengine" , NULL - , "NetData DB engine RAM usage" + , "Netdata DB engine RAM usage" , "MiB" , "netdata" , "stats" @@ -948,3 +932,36 @@ void global_statistics_charts(void) { #endif } + +static void global_statistics_cleanup(void *ptr) +{ + struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; + static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; + + info("cleaning up..."); + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; +} + +void *global_statistics_main(void *ptr) +{ + netdata_thread_cleanup_push(global_statistics_cleanup, ptr); + + int update_every = + (int)config_get_number("CONFIG_SECTION_GLOBAL_STATISTICS", "update every", localhost->rrd_update_every); + if (update_every < localhost->rrd_update_every) + update_every = localhost->rrd_update_every; + + usec_t step = update_every * USEC_PER_SEC; + heartbeat_t hb; + heartbeat_init(&hb); + while (!netdata_exit) { + heartbeat_next(&hb, step); + + global_statistics_charts(); + registry_statistics(); + } + + netdata_thread_cleanup_pop(1); + return NULL; +} diff --git a/daemon/global_statistics.h b/daemon/global_statistics.h index 9dd7db51a..c200a693b 100644 --- a/daemon/global_statistics.h +++ b/daemon/global_statistics.h @@ -8,6 +8,17 @@ // ---------------------------------------------------------------------------- // global statistics +#define NETDATA_PLUGIN_HOOK_GLOBAL_STATISTICS \ + {.name = "GLOBAL_STATS", \ + .config_section = NULL, \ + .config_name = NULL, \ + .enabled = 1, \ + .thread = NULL, \ + .init_routine = NULL, \ + .start_routine = global_statistics_main}, + +extern void *global_statistics_main(void *ptr); + extern void rrdr_query_completed(uint64_t db_points_read, uint64_t result_points_generated); extern void finished_web_request_statistics(uint64_t dt, diff --git a/daemon/main.c b/daemon/main.c index 93ec78b6a..61041f540 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -28,10 +28,13 @@ void netdata_cleanup_and_exit(int ret) { info("EXIT: netdata prepares to exit with code %d...", ret); send_statistics("EXIT", ret?"ERROR":"OK","-"); + analytics_free_data(); char agent_crash_file[FILENAME_MAX + 1]; + char agent_incomplete_shutdown_file[FILENAME_MAX + 1]; snprintfz(agent_crash_file, FILENAME_MAX, "%s/.agent_crash", netdata_configured_varlib_dir); - (void) unlink(agent_crash_file); + snprintfz(agent_incomplete_shutdown_file, FILENAME_MAX, "%s/.agent_incomplete_shutdown", netdata_configured_varlib_dir); + (void) rename(agent_crash_file, agent_incomplete_shutdown_file); // cleanup/save the database and exit info("EXIT: cleaning up the database..."); @@ -67,10 +70,12 @@ void netdata_cleanup_and_exit(int ret) { security_clean_openssl(); #endif info("EXIT: all done - netdata is now exiting - bye bye..."); + (void) unlink(agent_incomplete_shutdown_file); exit(ret); } struct netdata_static_thread static_threads[] = { + NETDATA_PLUGIN_HOOK_GLOBAL_STATISTICS NETDATA_PLUGIN_HOOK_CHECKS NETDATA_PLUGIN_HOOK_FREEBSD @@ -79,6 +84,7 @@ struct netdata_static_thread static_threads[] = { // linux internal plugins NETDATA_PLUGIN_HOOK_LINUX_PROC NETDATA_PLUGIN_HOOK_LINUX_DISKSPACE + NETDATA_PLUGIN_HOOK_LINUX_TIMEX NETDATA_PLUGIN_HOOK_LINUX_CGROUPS NETDATA_PLUGIN_HOOK_LINUX_TC @@ -97,6 +103,7 @@ struct netdata_static_thread static_threads[] = { NETDATA_PLUGIN_HOOK_PLUGINSD NETDATA_PLUGIN_HOOK_HEALTH + NETDATA_PLUGIN_HOOK_ANALYTICS {NULL, NULL, NULL, 0, NULL, NULL, NULL} }; @@ -395,18 +402,6 @@ void remove_option(int opt_index, int *argc, char **argv) { } while(argv[i][0] != '-' && opt_index >= *argc); } -static const char *verify_required_directory(const char *dir) { - if(chdir(dir) == -1) - fatal("Cannot cd to directory '%s'", dir); - - DIR *d = opendir(dir); - if(!d) - fatal("Cannot examine the contents of directory '%s'", dir); - closedir(d); - - return dir; -} - #ifdef ENABLE_HTTPS static void security_init(){ char filename[FILENAME_MAX + 1]; @@ -610,147 +605,6 @@ static void get_netdata_configured_variables() { } -static void get_system_timezone(void) { - // avoid flood calls to stat(/etc/localtime) - // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux - const char *tz = getenv("TZ"); - if(!tz || !*tz) - setenv("TZ", config_get(CONFIG_SECTION_GLOBAL, "TZ environment variable", ":/etc/localtime"), 0); - - char buffer[FILENAME_MAX + 1] = ""; - const char *timezone = NULL; - ssize_t ret; - - // use the TZ variable - if(tz && *tz && *tz != ':') { - timezone = tz; - // info("TIMEZONE: using TZ variable '%s'", timezone); - } - - // use the contents of /etc/timezone - if(!timezone && !read_file("/etc/timezone", buffer, FILENAME_MAX)) { - timezone = buffer; - // info("TIMEZONE: using the contents of /etc/timezone: '%s'", timezone); - } - - // read the link /etc/localtime - if(!timezone) { - ret = readlink("/etc/localtime", buffer, FILENAME_MAX); - - if(ret > 0) { - buffer[ret] = '\0'; - - char *cmp = "/usr/share/zoneinfo/"; - size_t cmp_len = strlen(cmp); - - char *s = strstr(buffer, cmp); - if (s && s[cmp_len]) { - timezone = &s[cmp_len]; - // info("TIMEZONE: using the link of /etc/localtime: '%s'", timezone); - } - } - else - buffer[0] = '\0'; - } - - // find the timezone from strftime() - if(!timezone) { - time_t t; - struct tm *tmp, tmbuf; - - t = now_realtime_sec(); - tmp = localtime_r(&t, &tmbuf); - - if (tmp != NULL) { - if(strftime(buffer, FILENAME_MAX, "%Z", tmp) == 0) - buffer[0] = '\0'; - else { - buffer[FILENAME_MAX] = '\0'; - timezone = buffer; - // info("TIMEZONE: using strftime(): '%s'", timezone); - } - } - } - - if(timezone && *timezone) { - // make sure it does not have illegal characters - // info("TIMEZONE: fixing '%s'", timezone); - - size_t len = strlen(timezone); - char tmp[len + 1]; - char *d = tmp; - *d = '\0'; - - while(*timezone) { - if(isalnum(*timezone) || *timezone == '_' || *timezone == '/') - *d++ = *timezone++; - else - timezone++; - } - *d = '\0'; - strncpyz(buffer, tmp, len); - timezone = buffer; - // info("TIMEZONE: fixed as '%s'", timezone); - } - - if(!timezone || !*timezone) - timezone = "unknown"; - - netdata_configured_timezone = config_get(CONFIG_SECTION_GLOBAL, "timezone", timezone); -} - -void set_global_environment() { - { - char b[16]; - snprintfz(b, 15, "%d", default_rrd_update_every); - setenv("NETDATA_UPDATE_EVERY", b, 1); - } - - setenv("NETDATA_VERSION" , program_version, 1); - setenv("NETDATA_HOSTNAME" , netdata_configured_hostname, 1); - setenv("NETDATA_CONFIG_DIR" , verify_required_directory(netdata_configured_user_config_dir), 1); - setenv("NETDATA_USER_CONFIG_DIR" , verify_required_directory(netdata_configured_user_config_dir), 1); - setenv("NETDATA_STOCK_CONFIG_DIR" , verify_required_directory(netdata_configured_stock_config_dir), 1); - setenv("NETDATA_PLUGINS_DIR" , verify_required_directory(netdata_configured_primary_plugins_dir), 1); - setenv("NETDATA_WEB_DIR" , verify_required_directory(netdata_configured_web_dir), 1); - setenv("NETDATA_CACHE_DIR" , verify_required_directory(netdata_configured_cache_dir), 1); - setenv("NETDATA_LIB_DIR" , verify_required_directory(netdata_configured_varlib_dir), 1); - setenv("NETDATA_LOCK_DIR" , netdata_configured_lock_dir, 1); - setenv("NETDATA_LOG_DIR" , verify_required_directory(netdata_configured_log_dir), 1); - setenv("HOME" , verify_required_directory(netdata_configured_home_dir), 1); - setenv("NETDATA_HOST_PREFIX" , netdata_configured_host_prefix, 1); - - char *default_port = appconfig_get(&netdata_config, CONFIG_SECTION_WEB, "default port", NULL); - int clean = 0; - if (!default_port) { - default_port = strdupz("19999"); - clean = 1; - } - - setenv("NETDATA_LISTEN_PORT" , default_port, 1); - if(clean) - freez(default_port); - - get_system_timezone(); - - // set the path we need - char path[1024 + 1], *p = getenv("PATH"); - if(!p) p = "/bin:/usr/bin"; - snprintfz(path, 1024, "%s:%s", p, "/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin"); - setenv("PATH", config_get(CONFIG_SECTION_PLUGINS, "PATH environment variable", path), 1); - - // python options - p = getenv("PYTHONPATH"); - if(!p) p = ""; - setenv("PYTHONPATH", config_get(CONFIG_SECTION_PLUGINS, "PYTHONPATH environment variable", p), 1); - - // disable buffering for python plugins - setenv("PYTHONUNBUFFERED", "1", 1); - - // switch to standard locale for plugins - setenv("LC_ALL", "C", 1); -} - static int load_netdata_conf(char *filename, char overwrite_used) { errno = 0; @@ -833,47 +687,6 @@ int get_system_info(struct rrdhost_system_info *system_info) { return 0; } -void send_statistics( const char *action, const char *action_result, const char *action_data) { - static char *as_script; - - if (netdata_anonymous_statistics_enabled == -1) { - char *optout_file = mallocz(sizeof(char) * (strlen(netdata_configured_user_config_dir) +strlen(".opt-out-from-anonymous-statistics") + 2)); - sprintf(optout_file, "%s/%s", netdata_configured_user_config_dir, ".opt-out-from-anonymous-statistics"); - if (likely(access(optout_file, R_OK) != 0)) { - as_script = mallocz(sizeof(char) * (strlen(netdata_configured_primary_plugins_dir) + strlen("anonymous-statistics.sh") + 2)); - sprintf(as_script, "%s/%s", netdata_configured_primary_plugins_dir, "anonymous-statistics.sh"); - if (unlikely(access(as_script, R_OK) != 0)) { - netdata_anonymous_statistics_enabled=0; - info("Anonymous statistics script %s not found.",as_script); - freez(as_script); - } else { - netdata_anonymous_statistics_enabled=1; - } - } else { - netdata_anonymous_statistics_enabled = 0; - as_script = NULL; - } - freez(optout_file); - } - if(!netdata_anonymous_statistics_enabled) return; - if (!action) return; - if (!action_result) action_result=""; - if (!action_data) action_data=""; - char *command_to_run=mallocz(sizeof(char) * (strlen(action) + strlen(action_result) + strlen(action_data) + strlen(as_script) + 10)); - pid_t command_pid; - - sprintf(command_to_run,"%s '%s' '%s' '%s'", as_script, action, action_result, action_data); - info("%s", command_to_run); - - FILE *fp = mypopen(command_to_run, &command_pid); - if(fp) { - char buffer[100 + 1]; - while (fgets(buffer, 100, fp) != NULL); - mypclose(fp, command_pid); - } - freez(command_to_run); -} - void set_silencers_filename() { char filename[FILENAME_MAX + 1]; snprintfz(filename, FILENAME_MAX, "%s/health.silencers.json", netdata_configured_varlib_dir); @@ -920,7 +733,7 @@ int main(int argc, char **argv) { // set the name for logging program_name = "netdata"; - // parse depercated options + // parse deprecated options // TODO: Remove this block with the next major release. { i = 1; @@ -1106,21 +919,21 @@ int main(int argc, char **argv) { return 1; } - const char *heystack = argv[optind]; + const char *haystack = argv[optind]; const char *needle = argv[optind + 1]; size_t len = strlen(needle) + 1; char wildcarded[len]; - SIMPLE_PATTERN *p = simple_pattern_create(heystack, NULL, SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *p = simple_pattern_create(haystack, NULL, SIMPLE_PATTERN_EXACT); int ret = simple_pattern_matches_extract(p, needle, wildcarded, len); simple_pattern_free(p); if(ret) { - fprintf(stdout, "RESULT: MATCHED - pattern '%s' matches '%s', wildcarded '%s'\n", heystack, needle, wildcarded); + fprintf(stdout, "RESULT: MATCHED - pattern '%s' matches '%s', wildcarded '%s'\n", haystack, needle, wildcarded); return 0; } else { - fprintf(stdout, "RESULT: NOT MATCHED - pattern '%s' does not match '%s', wildcarded '%s'\n", heystack, needle, wildcarded); + fprintf(stdout, "RESULT: NOT MATCHED - pattern '%s' does not match '%s', wildcarded '%s'\n", haystack, needle, wildcarded); return 1; } } @@ -1437,7 +1250,7 @@ int main(int argc, char **argv) { netdata_threads_init_after_fork((size_t)config_get_number(CONFIG_SECTION_GLOBAL, "pthread stack size", (long)default_stacksize)); - // initialyze internal registry + // initialize internal registry registry_init(); // fork the spawn server spawn_init(); @@ -1461,6 +1274,9 @@ int main(int argc, char **argv) { fatal("Cannot initialize localhost instance with name '%s'.", netdata_configured_hostname); char agent_crash_file[FILENAME_MAX + 1]; + char agent_incomplete_shutdown_file[FILENAME_MAX + 1]; + snprintfz(agent_incomplete_shutdown_file, FILENAME_MAX, "%s/.agent_incomplete_shutdown", netdata_configured_varlib_dir); + int incomplete_shutdown_detected = (unlink(agent_incomplete_shutdown_file) == 0); snprintfz(agent_crash_file, FILENAME_MAX, "%s/.agent_crash", netdata_configured_varlib_dir); int crash_detected = (unlink(agent_crash_file) == 0); int fd = open(agent_crash_file, O_WRONLY | O_CREAT | O_TRUNC, 444); @@ -1509,9 +1325,26 @@ int main(int argc, char **argv) { info("netdata initialization completed. Enjoy real-time performance monitoring!"); netdata_ready = 1; + set_late_global_environment(); + send_statistics("START", "-", "-"); if (crash_detected) send_statistics("CRASH", "-", "-"); + if (incomplete_shutdown_detected) + send_statistics("INCOMPLETE_SHUTDOWN", "-", "-"); + + //check if ANALYTICS needs to start + if (netdata_anonymous_statistics_enabled == 1) { + for (i = 0; static_threads[i].name != NULL; i++) { + if (!strncmp(static_threads[i].name, "ANALYTICS", 9)) { + struct netdata_static_thread *st = &static_threads[i]; + st->thread = mallocz(sizeof(netdata_thread_t)); + st->enabled = 1; + debug(D_SYSTEM, "Starting thread %s.", st->name); + netdata_thread_create(st->thread, st->name, NETDATA_THREAD_OPTION_DEFAULT, st->start_routine, st); + } + } + } // ------------------------------------------------------------------------ // Report ACLK build failure diff --git a/daemon/signals.c b/daemon/signals.c index 9e30bf19d..b991d46bf 100644 --- a/daemon/signals.c +++ b/daemon/signals.c @@ -44,7 +44,7 @@ static void signal_handler(int signo) { if(signals_waiting[i].action == NETDATA_SIGNAL_FATAL) { char buffer[200 + 1]; - snprintfz(buffer, 200, "\nSIGNAL HANLDER: received: %s. Oops! This is bad!\n", signals_waiting[i].name); + snprintfz(buffer, 200, "\nSIGNAL HANDLER: received: %s. Oops! This is bad!\n", signals_waiting[i].name); if(write(STDERR_FILENO, buffer, strlen(buffer)) == -1) { // nothing to do - we cannot write but there is no way to complain about it ; diff --git a/daemon/unit_test.c b/daemon/unit_test.c index 9a17aa762..81090736e 100644 --- a/daemon/unit_test.c +++ b/daemon/unit_test.c @@ -1515,7 +1515,7 @@ static RRDHOST *dbengine_rrdhost_find_or_create(char *name) ); } -// costants for test_dbengine +// constants for test_dbengine static const int CHARTS = 64; static const int DIMS = 16; // That gives us 64 * 16 = 1024 metrics #define REGIONS (3) // 3 regions of update_every |