| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "analytics.h" |
| 4 | #include "common.h" |
| 5 | #include "buildinfo.h" |
| 6 | |
| 7 | struct analytics_data analytics_data; |
| 8 | extern void analytics_exporting_connectors (BUFFER *b); |
| 9 | extern void analytics_exporting_connectors_ssl (BUFFER *b); |
| 10 | extern void analytics_build_info (BUFFER *b); |
| 11 | |
| 12 | struct collector { |
| 13 | const char *plugin; |
| 14 | const char *module; |
| 15 | }; |
| 16 | |
| 17 | struct array_printer { |
| 18 | int c; |
| 19 | BUFFER *both; |
| 20 | }; |
| 21 | |
| 22 | /* |
| 23 | * Debug logging |
| 24 | */ |
| 25 | void analytics_log_data(void) |
| 26 | { |
| 27 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_STREAM_ENABLED : [%s]", analytics_data.netdata_config_stream_enabled); |
| 28 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_MEMORY_MODE : [%s]", analytics_data.netdata_config_memory_mode); |
| 29 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_EXPORTING_ENABLED : [%s]", analytics_data.netdata_config_exporting_enabled); |
| 30 | netdata_log_debug(D_ANALYTICS, "NETDATA_EXPORTING_CONNECTORS : [%s]", analytics_data.netdata_exporting_connectors); |
| 31 | netdata_log_debug(D_ANALYTICS, "NETDATA_ALLMETRICS_PROMETHEUS_USED : [%s]", analytics_data.netdata_allmetrics_prometheus_used); |
| 32 | netdata_log_debug(D_ANALYTICS, "NETDATA_ALLMETRICS_SHELL_USED : [%s]", analytics_data.netdata_allmetrics_shell_used); |
| 33 | netdata_log_debug(D_ANALYTICS, "NETDATA_ALLMETRICS_JSON_USED : [%s]", analytics_data.netdata_allmetrics_json_used); |
| 34 | netdata_log_debug(D_ANALYTICS, "NETDATA_DASHBOARD_USED : [%s]", analytics_data.netdata_dashboard_used); |
| 35 | netdata_log_debug(D_ANALYTICS, "NETDATA_COLLECTORS : [%s]", analytics_data.netdata_collectors); |
| 36 | netdata_log_debug(D_ANALYTICS, "NETDATA_COLLECTORS_COUNT : [%s]", analytics_data.netdata_collectors_count); |
| 37 | netdata_log_debug(D_ANALYTICS, "NETDATA_BUILDINFO : [%s]", analytics_data.netdata_buildinfo); |
| 38 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_PAGE_CACHE_SIZE : [%s]", analytics_data.netdata_config_page_cache_size); |
| 39 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_MULTIDB_DISK_QUOTA : [%s]", analytics_data.netdata_config_multidb_disk_quota); |
| 40 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_HTTPS_ENABLED : [%s]", analytics_data.netdata_config_https_enabled); |
| 41 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_WEB_ENABLED : [%s]", analytics_data.netdata_config_web_enabled); |
| 42 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_RELEASE_CHANNEL : [%s]", analytics_data.netdata_config_release_channel); |
| 43 | netdata_log_debug(D_ANALYTICS, "NETDATA_MIRRORED_HOST_COUNT : [%s]", analytics_data.netdata_mirrored_host_count); |
| 44 | netdata_log_debug(D_ANALYTICS, "NETDATA_MIRRORED_HOSTS_REACHABLE : [%s]", analytics_data.netdata_mirrored_hosts_reachable); |
| 45 | netdata_log_debug(D_ANALYTICS, "NETDATA_MIRRORED_HOSTS_UNREACHABLE : [%s]", analytics_data.netdata_mirrored_hosts_unreachable); |
| 46 | netdata_log_debug(D_ANALYTICS, "NETDATA_NOTIFICATION_METHODS : [%s]", analytics_data.netdata_notification_methods); |
| 47 | netdata_log_debug(D_ANALYTICS, "NETDATA_ALARMS_NORMAL : [%s]", analytics_data.netdata_alarms_normal); |
| 48 | netdata_log_debug(D_ANALYTICS, "NETDATA_ALARMS_WARNING : [%s]", analytics_data.netdata_alarms_warning); |
| 49 | netdata_log_debug(D_ANALYTICS, "NETDATA_ALARMS_CRITICAL : [%s]", analytics_data.netdata_alarms_critical); |
| 50 | netdata_log_debug(D_ANALYTICS, "NETDATA_CHARTS_COUNT : [%s]", analytics_data.netdata_charts_count); |
| 51 | netdata_log_debug(D_ANALYTICS, "NETDATA_METRICS_COUNT : [%s]", analytics_data.netdata_metrics_count); |
| 52 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_IS_PARENT : [%s]", analytics_data.netdata_config_is_parent); |
| 53 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_HOSTS_AVAILABLE : [%s]", analytics_data.netdata_config_hosts_available); |
| 54 | netdata_log_debug(D_ANALYTICS, "NETDATA_HOST_CLOUD_AVAILABLE : [%s]", analytics_data.netdata_host_cloud_available); |
| 55 | netdata_log_debug(D_ANALYTICS, "NETDATA_HOST_ACLK_AVAILABLE : [%s]", analytics_data.netdata_host_aclk_available); |
| 56 | netdata_log_debug(D_ANALYTICS, "NETDATA_HOST_ACLK_PROTOCOL : [%s]", analytics_data.netdata_host_aclk_protocol); |
| 57 | netdata_log_debug(D_ANALYTICS, "NETDATA_HOST_ACLK_IMPLEMENTATION : [%s]", analytics_data.netdata_host_aclk_implementation); |
| 58 | netdata_log_debug(D_ANALYTICS, "NETDATA_HOST_AGENT_CLAIMED : [%s]", analytics_data.netdata_host_agent_claimed); |
| 59 | netdata_log_debug(D_ANALYTICS, "NETDATA_HOST_CLOUD_ENABLED : [%s]", analytics_data.netdata_host_cloud_enabled); |
| 60 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_HTTPS_AVAILABLE : [%s]", analytics_data.netdata_config_https_available); |
| 61 | netdata_log_debug(D_ANALYTICS, "NETDATA_INSTALL_TYPE : [%s]", analytics_data.netdata_install_type); |
| 62 | netdata_log_debug(D_ANALYTICS, "NETDATA_PREBUILT_DISTRO : [%s]", analytics_data.netdata_prebuilt_distro); |
| 63 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_IS_PRIVATE_REGISTRY : [%s]", analytics_data.netdata_config_is_private_registry); |
| 64 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_USE_PRIVATE_REGISTRY: [%s]", analytics_data.netdata_config_use_private_registry); |
| 65 | netdata_log_debug(D_ANALYTICS, "NETDATA_CONFIG_OOM_SCORE : [%s]", analytics_data.netdata_config_oom_score); |
| 66 | } |
| 67 | |
| 68 | /* |
| 69 | * Free data |
| 70 | */ |
| 71 | void analytics_free_data(void) |
| 72 | { |
| 73 | freez(analytics_data.netdata_config_stream_enabled); |
| 74 | analytics_data.netdata_config_stream_enabled = NULL; |
| 75 | |
| 76 | freez(analytics_data.netdata_config_memory_mode); |
| 77 | analytics_data.netdata_config_memory_mode = NULL; |
| 78 | |
| 79 | freez(analytics_data.netdata_config_exporting_enabled); |
| 80 | analytics_data.netdata_config_exporting_enabled = NULL; |
| 81 | |
| 82 | freez(analytics_data.netdata_exporting_connectors); |
| 83 | analytics_data.netdata_exporting_connectors = NULL; |
| 84 | |
| 85 | freez(analytics_data.netdata_allmetrics_prometheus_used); |
| 86 | analytics_data.netdata_allmetrics_prometheus_used = NULL; |
| 87 | |
| 88 | freez(analytics_data.netdata_allmetrics_shell_used); |
| 89 | analytics_data.netdata_allmetrics_shell_used = NULL; |
| 90 | |
| 91 | freez(analytics_data.netdata_allmetrics_json_used); |
| 92 | analytics_data.netdata_allmetrics_json_used = NULL; |
| 93 | |
| 94 | freez(analytics_data.netdata_dashboard_used); |
| 95 | analytics_data.netdata_dashboard_used = NULL; |
| 96 | |
| 97 | freez(analytics_data.netdata_collectors); |
| 98 | analytics_data.netdata_collectors = NULL; |
| 99 | |
| 100 | freez(analytics_data.netdata_collectors_count); |
| 101 | analytics_data.netdata_collectors_count = NULL; |
| 102 | |
| 103 | freez(analytics_data.netdata_buildinfo); |
| 104 | analytics_data.netdata_buildinfo = NULL; |
| 105 | |
| 106 | freez(analytics_data.netdata_config_page_cache_size); |
| 107 | analytics_data.netdata_config_page_cache_size = NULL; |
| 108 | |
| 109 | freez(analytics_data.netdata_config_multidb_disk_quota); |
| 110 | analytics_data.netdata_config_multidb_disk_quota = NULL; |
| 111 | |
| 112 | freez(analytics_data.netdata_config_https_enabled); |
| 113 | analytics_data.netdata_config_https_enabled = NULL; |
| 114 | |
| 115 | freez(analytics_data.netdata_config_web_enabled); |
| 116 | analytics_data.netdata_config_web_enabled = NULL; |
| 117 | |
| 118 | freez(analytics_data.netdata_config_release_channel); |
| 119 | analytics_data.netdata_config_release_channel = NULL; |
| 120 | |
| 121 | freez(analytics_data.netdata_mirrored_host_count); |
| 122 | analytics_data.netdata_mirrored_host_count = NULL; |
| 123 | |
| 124 | freez(analytics_data.netdata_mirrored_hosts_reachable); |
| 125 | analytics_data.netdata_mirrored_hosts_reachable = NULL; |
| 126 | |
| 127 | freez(analytics_data.netdata_mirrored_hosts_unreachable); |
| 128 | analytics_data.netdata_mirrored_hosts_unreachable = NULL; |
| 129 | |
| 130 | freez(analytics_data.netdata_notification_methods); |
| 131 | analytics_data.netdata_notification_methods = NULL; |
| 132 | |
| 133 | freez(analytics_data.netdata_alarms_normal); |
| 134 | analytics_data.netdata_alarms_normal = NULL; |
| 135 | |
| 136 | freez(analytics_data.netdata_alarms_warning); |
| 137 | analytics_data.netdata_alarms_warning = NULL; |
| 138 | |
| 139 | freez(analytics_data.netdata_alarms_critical); |
| 140 | analytics_data.netdata_alarms_critical = NULL; |
| 141 | |
| 142 | freez(analytics_data.netdata_charts_count); |
| 143 | analytics_data.netdata_charts_count = NULL; |
| 144 | |
| 145 | freez(analytics_data.netdata_metrics_count); |
| 146 | analytics_data.netdata_metrics_count = NULL; |
| 147 | |
| 148 | freez(analytics_data.netdata_config_is_parent); |
| 149 | analytics_data.netdata_config_is_parent = NULL; |
| 150 | |
| 151 | freez(analytics_data.netdata_config_hosts_available); |
| 152 | analytics_data.netdata_config_hosts_available = NULL; |
| 153 | |
| 154 | freez(analytics_data.netdata_host_cloud_available); |
| 155 | analytics_data.netdata_host_cloud_available = NULL; |
| 156 | |
| 157 | freez(analytics_data.netdata_host_aclk_available); |
| 158 | analytics_data.netdata_host_aclk_available = NULL; |
| 159 | |
| 160 | freez(analytics_data.netdata_host_aclk_protocol); |
| 161 | analytics_data.netdata_host_aclk_protocol = NULL; |
| 162 | |
| 163 | freez(analytics_data.netdata_host_aclk_implementation); |
| 164 | analytics_data.netdata_host_aclk_implementation = NULL; |
| 165 | |
| 166 | freez(analytics_data.netdata_host_agent_claimed); |
| 167 | analytics_data.netdata_host_agent_claimed = NULL; |
| 168 | |
| 169 | freez(analytics_data.netdata_host_cloud_enabled); |
| 170 | analytics_data.netdata_host_cloud_enabled = NULL; |
| 171 | |
| 172 | freez(analytics_data.netdata_config_https_available); |
| 173 | analytics_data.netdata_config_https_available = NULL; |
| 174 | |
| 175 | freez(analytics_data.netdata_install_type); |
| 176 | analytics_data.netdata_install_type = NULL; |
| 177 | |
| 178 | freez(analytics_data.netdata_config_is_private_registry); |
| 179 | analytics_data.netdata_config_is_private_registry = NULL; |
| 180 | |
| 181 | freez(analytics_data.netdata_config_use_private_registry); |
| 182 | analytics_data.netdata_config_use_private_registry = NULL; |
| 183 | |
| 184 | freez(analytics_data.netdata_config_oom_score); |
| 185 | analytics_data.netdata_config_oom_score = NULL; |
| 186 | |
| 187 | freez(analytics_data.netdata_prebuilt_distro); |
| 188 | analytics_data.netdata_prebuilt_distro = NULL; |
| 189 | |
| 190 | freez(analytics_data.netdata_fail_reason); |
| 191 | analytics_data.netdata_fail_reason = NULL; |
| 192 | |
| 193 | } |
| 194 | |
| 195 | /* |
| 196 | * Set a numeric/boolean data with a value |
| 197 | */ |
| 198 | void analytics_set_data(char **name, char *value) |
| 199 | { |
| 200 | spinlock_lock(&analytics_data.spinlock); |
| 201 | if (*name) { |
| 202 | analytics_data.data_length -= strlen(*name); |
| 203 | freez(*name); |
| 204 | } |
| 205 | *name = strdupz(value); |
| 206 | analytics_data.data_length += strlen(*name); |
| 207 | spinlock_unlock(&analytics_data.spinlock); |
| 208 | } |
| 209 | |
| 210 | /* |
| 211 | * Set a string data with a value |
| 212 | */ |
| 213 | void analytics_set_data_str(char **name, const char *value) |
| 214 | { |
| 215 | size_t value_string_len; |
| 216 | spinlock_lock(&analytics_data.spinlock); |
| 217 | if (*name) { |
| 218 | analytics_data.data_length -= strlen(*name); |
| 219 | freez(*name); |
| 220 | } |
| 221 | value_string_len = strlen(value) + 4; |
| 222 | *name = mallocz(sizeof(char) * value_string_len); |
| 223 | snprintfz(*name, value_string_len - 1, "\"%s\"", value); |
| 224 | analytics_data.data_length += strlen(*name); |
| 225 | spinlock_unlock(&analytics_data.spinlock); |
| 226 | } |
| 227 | |
| 228 | /* |
| 229 | * Log hits on the allmetrics page, with prometheus parameter |
| 230 | */ |
| 231 | void analytics_log_prometheus(void) |
| 232 | { |
| 233 | if (netdata_anonymous_statistics_enabled && likely(analytics_data.prometheus_hits < ANALYTICS_MAX_PROMETHEUS_HITS)) { |
| 234 | analytics_data.prometheus_hits++; |
| 235 | char b[21]; |
| 236 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.prometheus_hits); |
| 237 | analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, b); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | /* |
| 242 | * Log hits on the allmetrics page, with shell parameter (or default) |
| 243 | */ |
| 244 | void analytics_log_shell(void) |
| 245 | { |
| 246 | if (netdata_anonymous_statistics_enabled && likely(analytics_data.shell_hits < ANALYTICS_MAX_SHELL_HITS)) { |
| 247 | analytics_data.shell_hits++; |
| 248 | char b[21]; |
| 249 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.shell_hits); |
| 250 | analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, b); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | /* |
| 255 | * Log hits on the allmetrics page, with json parameter |
| 256 | */ |
| 257 | void analytics_log_json(void) |
| 258 | { |
| 259 | if (netdata_anonymous_statistics_enabled && likely(analytics_data.json_hits < ANALYTICS_MAX_JSON_HITS)) { |
| 260 | analytics_data.json_hits++; |
| 261 | char b[21]; |
| 262 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.json_hits); |
| 263 | analytics_set_data(&analytics_data.netdata_allmetrics_json_used, b); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | /* |
| 268 | * Log hits on the dashboard, (when calling HELLO). |
| 269 | */ |
| 270 | void analytics_log_dashboard(void) |
| 271 | { |
| 272 | if (netdata_anonymous_statistics_enabled && likely(analytics_data.dashboard_hits < ANALYTICS_MAX_DASHBOARD_HITS)) { |
| 273 | analytics_data.dashboard_hits++; |
| 274 | char b[21]; |
| 275 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.dashboard_hits); |
| 276 | analytics_set_data(&analytics_data.netdata_dashboard_used, b); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | /* |
| 281 | * Called when setting the oom score |
| 282 | */ |
| 283 | void analytics_report_oom_score(long long int score){ |
| 284 | char b[21]; |
| 285 | snprintfz(b, sizeof(b) - 1, "%lld", score); |
| 286 | analytics_set_data(&analytics_data.netdata_config_oom_score, b); |
| 287 | } |
| 288 | |
| 289 | void analytics_mirrored_hosts(void) |
| 290 | { |
| 291 | RRDHOST *host; |
| 292 | size_t count = 0; |
| 293 | size_t reachable = 0; |
| 294 | size_t unreachable = 0; |
| 295 | char b[21]; |
| 296 | |
| 297 | rrd_rdlock(); |
| 298 | rrdhost_foreach_read(host) |
| 299 | { |
| 300 | if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) |
| 301 | continue; |
| 302 | |
| 303 | ((host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)) ? reachable++ : unreachable++); |
| 304 | |
| 305 | count++; |
| 306 | } |
| 307 | rrd_rdunlock(); |
| 308 | |
| 309 | snprintfz(b, sizeof(b) - 1, "%zu", count); |
| 310 | analytics_set_data(&analytics_data.netdata_mirrored_host_count, b); |
| 311 | snprintfz(b, sizeof(b) - 1, "%zu", reachable); |
| 312 | analytics_set_data(&analytics_data.netdata_mirrored_hosts_reachable, b); |
| 313 | snprintfz(b, sizeof(b) - 1, "%zu", unreachable); |
| 314 | analytics_set_data(&analytics_data.netdata_mirrored_hosts_unreachable, b); |
| 315 | } |
| 316 | |
| 317 | void analytics_exporters(void) |
| 318 | { |
| 319 | //when no exporters are available, an empty string will be sent |
| 320 | //decide if something else is more suitable (but probably not null) |
| 321 | BUFFER *bi = buffer_create(1000, NULL); |
| 322 | analytics_exporting_connectors(bi); |
| 323 | analytics_set_data_str(&analytics_data.netdata_exporting_connectors, (char *)buffer_tostring(bi)); |
| 324 | buffer_free(bi); |
| 325 | } |
| 326 | |
| 327 | int collector_counter_callb(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data) { |
| 328 | |
| 329 | struct array_printer *ap = (struct array_printer *)data; |
| 330 | struct collector *col = (struct collector *)entry; |
| 331 | |
| 332 | BUFFER *bt = ap->both; |
| 333 | |
| 334 | if (likely(ap->c)) { |
| 335 | buffer_strcat(bt, ","); |
| 336 | } |
| 337 | |
| 338 | buffer_strcat(bt, "{"); |
| 339 | buffer_strcat(bt, " \"plugin\": \""); |
| 340 | buffer_strcat(bt, col->plugin); |
| 341 | buffer_strcat(bt, "\", \"module\":\""); |
| 342 | buffer_strcat(bt, col->module); |
| 343 | buffer_strcat(bt, "\" }"); |
| 344 | |
| 345 | (ap->c)++; |
| 346 | |
| 347 | return 0; |
| 348 | } |
| 349 | |
| 350 | /* |
| 351 | * Create a JSON array of available collectors, same as in api/v1/info |
| 352 | */ |
| 353 | void analytics_collectors(void) |
| 354 | { |
| 355 | RRDSET *st; |
| 356 | DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED); |
| 357 | char name[500]; |
| 358 | BUFFER *bt = buffer_create(1000, NULL); |
| 359 | |
| 360 | rrdset_foreach_read(st, localhost) { |
| 361 | if(!rrdset_is_available_for_viewers(st)) |
| 362 | continue; |
| 363 | |
| 364 | struct collector col = { |
| 365 | .plugin = rrdset_plugin_name(st), |
| 366 | .module = rrdset_module_name(st) |
| 367 | }; |
| 368 | snprintfz(name, sizeof(name) - 1, "%s:%s", col.plugin, col.module); |
| 369 | dictionary_set(dict, name, &col, sizeof(struct collector)); |
| 370 | } |
| 371 | rrdset_foreach_done(st); |
| 372 | |
| 373 | struct array_printer ap; |
| 374 | ap.c = 0; |
| 375 | ap.both = bt; |
| 376 | |
| 377 | dictionary_walkthrough_read(dict, collector_counter_callb, &ap); |
| 378 | dictionary_destroy(dict); |
| 379 | |
| 380 | analytics_set_data(&analytics_data.netdata_collectors, (char *)buffer_tostring(ap.both)); |
| 381 | |
| 382 | { |
| 383 | char b[21]; |
| 384 | snprintfz(b, sizeof(b) - 1, "%d", ap.c); |
| 385 | analytics_set_data(&analytics_data.netdata_collectors_count, b); |
| 386 | } |
| 387 | |
| 388 | buffer_free(bt); |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | * Run alarm-notify.sh script using the dump_methods parameter |
| 393 | * SEND_CUSTOM is always available |
| 394 | */ |
| 395 | void analytics_alarms_notifications(void) |
| 396 | { |
| 397 | char *script; |
| 398 | script = mallocz( |
| 399 | sizeof(char) * (strlen(netdata_configured_primary_plugins_dir) + strlen("alarm-notify.sh dump_methods") + 2)); |
| 400 | sprintf(script, "%s/%s", netdata_configured_primary_plugins_dir, "alarm-notify.sh"); |
| 401 | if (unlikely(access(script, R_OK) != 0)) { |
| 402 | netdata_log_info("Alarm notify script %s not found.", script); |
| 403 | freez(script); |
| 404 | return; |
| 405 | } |
| 406 | |
| 407 | strcat(script, " dump_methods"); |
| 408 | |
| 409 | netdata_log_debug(D_ANALYTICS, "Executing %s", script); |
| 410 | |
| 411 | BUFFER *b = buffer_create(1000, NULL); |
| 412 | int cnt = 0; |
| 413 | POPEN_INSTANCE *instance = spawn_popen_run(script); |
| 414 | if (instance) { |
| 415 | char line[200 + 1]; |
| 416 | |
| 417 | while (fgets(line, 200, spawn_popen_stdout(instance)) != NULL) { |
| 418 | char *end = line; |
| 419 | while (*end && *end != '\n') |
| 420 | end++; |
| 421 | *end = '\0'; |
| 422 | |
| 423 | if (likely(cnt)) |
| 424 | buffer_strcat(b, "|"); |
| 425 | |
| 426 | buffer_strcat(b, line); |
| 427 | |
| 428 | cnt++; |
| 429 | } |
| 430 | spawn_popen_wait(instance); |
| 431 | } |
| 432 | freez(script); |
| 433 | |
| 434 | analytics_set_data_str(&analytics_data.netdata_notification_methods, (char *)buffer_tostring(b)); |
| 435 | |
| 436 | buffer_free(b); |
| 437 | } |
| 438 | |
| 439 | static void analytics_get_install_type(struct rrdhost_system_info *system_info) { |
| 440 | const char *install_type = rrdhost_system_info_install_type(system_info); |
| 441 | if(!install_type) |
| 442 | install_type = "unknown"; |
| 443 | |
| 444 | analytics_set_data_str(&analytics_data.netdata_install_type, install_type); |
| 445 | |
| 446 | const char *prebuilt_dist = rrdhost_system_info_prebuilt_dist(system_info); |
| 447 | if(prebuilt_dist) |
| 448 | analytics_set_data_str(&analytics_data.netdata_prebuilt_distro, prebuilt_dist); |
| 449 | } |
| 450 | |
| 451 | /* |
| 452 | * Pick up if https is actually used |
| 453 | */ |
| 454 | void analytics_https(void) |
| 455 | { |
| 456 | BUFFER *b = buffer_create(30, NULL); |
| 457 | analytics_exporting_connectors_ssl(b); |
| 458 | |
| 459 | buffer_strcat(b, stream_sender_is_connected_with_ssl(localhost) ? "streaming|" : "|"); |
| 460 | buffer_strcat(b, netdata_ssl_web_server_ctx ? "web" : ""); |
| 461 | |
| 462 | analytics_set_data_str(&analytics_data.netdata_config_https_available, (char *)buffer_tostring(b)); |
| 463 | buffer_free(b); |
| 464 | } |
| 465 | |
| 466 | void analytics_charts(void) |
| 467 | { |
| 468 | RRDSET *st; |
| 469 | size_t c = 0; |
| 470 | |
| 471 | rrdset_foreach_read(st, localhost) |
| 472 | if(rrdset_is_available_for_viewers(st)) c++; |
| 473 | rrdset_foreach_done(st); |
| 474 | |
| 475 | analytics_data.charts_count = c; |
| 476 | { |
| 477 | char b[21]; |
| 478 | snprintfz(b, sizeof(b) - 1, "%zu", c); |
| 479 | analytics_set_data(&analytics_data.netdata_charts_count, b); |
| 480 | } |
| 481 | } |
| 482 | |
| 483 | void analytics_metrics(void) |
| 484 | { |
| 485 | RRDSET *st; |
| 486 | size_t dimensions = 0; |
| 487 | rrdset_foreach_read(st, localhost) { |
| 488 | if (rrdset_is_available_for_viewers(st)) { |
| 489 | RRDDIM *rd; |
| 490 | rrddim_foreach_read(rd, st) { |
| 491 | if (rrddim_option_check(rd, RRDDIM_OPTION_HIDDEN) || rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) |
| 492 | continue; |
| 493 | dimensions++; |
| 494 | } |
| 495 | rrddim_foreach_done(rd); |
| 496 | } |
| 497 | } |
| 498 | rrdset_foreach_done(st); |
| 499 | |
| 500 | analytics_data.metrics_count = dimensions; |
| 501 | { |
| 502 | char b[21]; |
| 503 | snprintfz(b, sizeof(b) - 1, "%zu", dimensions); |
| 504 | analytics_set_data(&analytics_data.netdata_metrics_count, b); |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | void analytics_alarms(void) |
| 509 | { |
| 510 | size_t alarm_warn = 0, alarm_crit = 0, alarm_normal = 0; |
| 511 | char b[21]; |
| 512 | RRDCALC *rc; |
| 513 | foreach_rrdcalc_in_rrdhost_read(localhost, rc) { |
| 514 | if (unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) |
| 515 | continue; |
| 516 | |
| 517 | switch (rc->status) { |
| 518 | case RRDCALC_STATUS_WARNING: |
| 519 | alarm_warn++; |
| 520 | break; |
| 521 | case RRDCALC_STATUS_CRITICAL: |
| 522 | alarm_crit++; |
| 523 | break; |
| 524 | default: |
| 525 | alarm_normal++; |
| 526 | } |
| 527 | } |
| 528 | foreach_rrdcalc_in_rrdhost_done(rc); |
| 529 | |
| 530 | snprintfz(b, sizeof(b) - 1, "%zu", alarm_normal); |
| 531 | analytics_set_data(&analytics_data.netdata_alarms_normal, b); |
| 532 | snprintfz(b, sizeof(b) - 1, "%zu", alarm_warn); |
| 533 | analytics_set_data(&analytics_data.netdata_alarms_warning, b); |
| 534 | snprintfz(b, sizeof(b) - 1, "%zu", alarm_crit); |
| 535 | analytics_set_data(&analytics_data.netdata_alarms_critical, b); |
| 536 | } |
| 537 | |
| 538 | /* |
| 539 | * Misc attributes to get (run from start) |
| 540 | */ |
| 541 | void analytics_misc(void) |
| 542 | { |
| 543 | analytics_set_data(&analytics_data.netdata_host_cloud_available, "true"); |
| 544 | analytics_set_data_str(&analytics_data.netdata_host_aclk_implementation, "Next Generation"); |
| 545 | |
| 546 | analytics_data.exporting_enabled = inicfg_get_boolean(&exporting_config, CONFIG_SECTION_EXPORTING, "enabled", CONFIG_BOOLEAN_NO); |
| 547 | analytics_set_data(&analytics_data.netdata_config_exporting_enabled, analytics_data.exporting_enabled ? "true" : "false"); |
| 548 | |
| 549 | analytics_set_data(&analytics_data.netdata_config_is_private_registry, "false"); |
| 550 | analytics_set_data(&analytics_data.netdata_config_use_private_registry, "false"); |
| 551 | |
| 552 | if (strcmp( |
| 553 | inicfg_get(&netdata_config, CONFIG_SECTION_REGISTRY, "registry to announce", "https://registry.my-netdata.io"), |
| 554 | "https://registry.my-netdata.io") != 0) |
| 555 | analytics_set_data(&analytics_data.netdata_config_use_private_registry, "true"); |
| 556 | |
| 557 | //do we need both registry to announce and enabled to indicate that this is a private registry ? |
| 558 | if (inicfg_get_boolean(&netdata_config, CONFIG_SECTION_REGISTRY, "enabled", CONFIG_BOOLEAN_NO) && |
| 559 | web_server_mode != WEB_SERVER_MODE_NONE) |
| 560 | analytics_set_data(&analytics_data.netdata_config_is_private_registry, "true"); |
| 561 | } |
| 562 | |
| 563 | void analytics_aclk(void) |
| 564 | { |
| 565 | if (aclk_online()) { |
| 566 | analytics_set_data(&analytics_data.netdata_host_aclk_available, "true"); |
| 567 | analytics_set_data_str(&analytics_data.netdata_host_aclk_protocol, "New"); |
| 568 | } |
| 569 | else |
| 570 | analytics_set_data(&analytics_data.netdata_host_aclk_available, "false"); |
| 571 | } |
| 572 | |
| 573 | /* |
| 574 | * Get the meta data, called from the thread once after the original delay |
| 575 | * These are values that won't change during agent runtime, and therefore |
| 576 | * don't try to read them on each META event send |
| 577 | */ |
| 578 | void analytics_gather_immutable_meta_data(void) |
| 579 | { |
| 580 | analytics_misc(); |
| 581 | analytics_exporters(); |
| 582 | analytics_https(); |
| 583 | } |
| 584 | |
| 585 | /* |
| 586 | * Get the meta data, called from the thread on every heartbeat, and right before the EXIT event |
| 587 | * These are values that can change between agent restarts, and therefore |
| 588 | * try to read them on each META event send |
| 589 | */ |
| 590 | void analytics_gather_mutable_meta_data(void) |
| 591 | { |
| 592 | analytics_collectors(); |
| 593 | analytics_alarms(); |
| 594 | analytics_charts(); |
| 595 | analytics_metrics(); |
| 596 | analytics_aclk(); |
| 597 | analytics_mirrored_hosts(); |
| 598 | analytics_alarms_notifications(); |
| 599 | |
| 600 | analytics_set_data( |
| 601 | &analytics_data.netdata_config_is_parent, (rrdhost_hosts_available() > 1 || netdata_conf_is_parent()) ? "true" : "false"); |
| 602 | |
| 603 | analytics_set_data(&analytics_data.netdata_host_agent_claimed, is_agent_claimed() ? "true" : "false"); |
| 604 | |
| 605 | { |
| 606 | char b[21]; |
| 607 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.prometheus_hits); |
| 608 | analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, b); |
| 609 | |
| 610 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.shell_hits); |
| 611 | analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, b); |
| 612 | |
| 613 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.json_hits); |
| 614 | analytics_set_data(&analytics_data.netdata_allmetrics_json_used, b); |
| 615 | |
| 616 | snprintfz(b, sizeof(b) - 1, "%zu", analytics_data.dashboard_hits); |
| 617 | analytics_set_data(&analytics_data.netdata_dashboard_used, b); |
| 618 | |
| 619 | snprintfz(b, sizeof(b) - 1, "%zu", rrdhost_hosts_available()); |
| 620 | analytics_set_data(&analytics_data.netdata_config_hosts_available, b); |
| 621 | } |
| 622 | } |
| 623 | |
| 624 | void analytics_main_cleanup(void *pptr) |
| 625 | { |
| 626 | struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr); |
| 627 | if(!static_thread) return; |
| 628 | |
| 629 | static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; |
| 630 | |
| 631 | analytics_free_data(); |
| 632 | |
| 633 | static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; |
| 634 | } |
| 635 | |
| 636 | /* |
| 637 | * The analytics thread. Sleep for ANALYTICS_INIT_SLEEP_SEC, |
| 638 | * gather the data, and then go to a loop where every ANALYTICS_HEARTBEAT |
| 639 | * it will send a new META event after gathering data that could be changed |
| 640 | * while the agent is running |
| 641 | */ |
| 642 | void *analytics_main(void *ptr) |
| 643 | { |
| 644 | CLEANUP_FUNCTION_REGISTER(analytics_main_cleanup) cleanup_ptr = ptr; |
| 645 | unsigned int sec = 0; |
| 646 | heartbeat_t hb; |
| 647 | heartbeat_init(&hb, USEC_PER_SEC); |
| 648 | |
| 649 | netdata_log_debug(D_ANALYTICS, "Analytics thread starts"); |
| 650 | |
| 651 | // first delay after agent start |
| 652 | while (service_running(SERVICE_ANALYTICS) && likely(sec <= ANALYTICS_INIT_SLEEP_SEC)) { |
| 653 | heartbeat_next(&hb); |
| 654 | sec++; |
| 655 | if (sec == ANALYTICS_INIT_IMMUTABLE_DATA_SEC) |
| 656 | analytics_gather_immutable_meta_data(); |
| 657 | } |
| 658 | |
| 659 | if (unlikely(!service_running(SERVICE_ANALYTICS))) |
| 660 | goto cleanup; |
| 661 | |
| 662 | analytics_gather_mutable_meta_data(); |
| 663 | analytics_log_data(); |
| 664 | |
| 665 | sec = 0; |
| 666 | while (1) { |
| 667 | heartbeat_next(&hb); |
| 668 | sec++; |
| 669 | |
| 670 | if (unlikely(!service_running(SERVICE_ANALYTICS))) |
| 671 | break; |
| 672 | |
| 673 | if (likely(sec < ANALYTICS_HEARTBEAT)) |
| 674 | continue; |
| 675 | |
| 676 | analytics_gather_mutable_meta_data(); |
| 677 | analytics_log_data(); |
| 678 | |
| 679 | sec = 0; |
| 680 | } |
| 681 | |
| 682 | cleanup: |
| 683 | return NULL; |
| 684 | } |
| 685 | |
| 686 | /* |
| 687 | * This is called after the rrdinit |
| 688 | * These values will be sent on the START event |
| 689 | */ |
| 690 | void set_late_analytics_variables(struct rrdhost_system_info *system_info) |
| 691 | { |
| 692 | analytics_set_data(&analytics_data.netdata_config_stream_enabled, stream_send.enabled ? "true" : "false"); |
| 693 | analytics_set_data_str(&analytics_data.netdata_config_memory_mode, (char *)rrd_memory_mode_name(default_rrd_memory_mode)); |
| 694 | analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "true"); |
| 695 | |
| 696 | #ifdef ENABLE_DBENGINE |
| 697 | { |
| 698 | char b[16]; |
| 699 | snprintfz(b, sizeof(b) - 1, "%d", default_rrdeng_page_cache_mb); |
| 700 | analytics_set_data(&analytics_data.netdata_config_page_cache_size, b); |
| 701 | |
| 702 | snprintfz(b, sizeof(b) - 1, "%d", default_multidb_disk_quota_mb); |
| 703 | analytics_set_data(&analytics_data.netdata_config_multidb_disk_quota, b); |
| 704 | } |
| 705 | #endif |
| 706 | |
| 707 | analytics_set_data(&analytics_data.netdata_config_https_enabled, "true"); |
| 708 | |
| 709 | if (web_server_mode == WEB_SERVER_MODE_NONE) |
| 710 | analytics_set_data(&analytics_data.netdata_config_web_enabled, "false"); |
| 711 | else |
| 712 | analytics_set_data(&analytics_data.netdata_config_web_enabled, "true"); |
| 713 | |
| 714 | analytics_set_data_str(&analytics_data.netdata_config_release_channel, (char *)get_release_channel()); |
| 715 | |
| 716 | { |
| 717 | BUFFER *bi = buffer_create(1000, NULL); |
| 718 | analytics_build_info(bi); |
| 719 | analytics_set_data_str(&analytics_data.netdata_buildinfo, (char *)buffer_tostring(bi)); |
| 720 | buffer_free(bi); |
| 721 | } |
| 722 | |
| 723 | analytics_get_install_type(system_info); |
| 724 | } |
| 725 | |
| 726 | #ifdef OS_WINDOWS |
| 727 | static void get_timezone_win_id(char *win_id, DWORD win_size) |
| 728 | { |
| 729 | win_id[0] = '\0'; |
| 730 | HKEY hKey; |
| 731 | LSTATUS ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, |
| 732 | "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", |
| 733 | 0, |
| 734 | KEY_READ, |
| 735 | &hKey); |
| 736 | if (ret != ERROR_SUCCESS) |
| 737 | return; |
| 738 | |
| 739 | DWORD valueType; |
| 740 | RegQueryValueExA(hKey, "TimeZoneKeyName", NULL, &valueType, (LPBYTE)win_id, &win_size); |
| 741 | |
| 742 | RegCloseKey(hKey); |
| 743 | } |
| 744 | |
| 745 | static void get_win_geoiso(char *geo_name, int length) { |
| 746 | GEOID id = GetUserGeoID(GEOCLASS_NATION); |
| 747 | int res = GetGeoInfoA(id, GEO_ISO2, geo_name, length, 0); |
| 748 | if (!res) |
| 749 | geo_name[0] = '\0'; |
| 750 | } |
| 751 | |
| 752 | static int map_windows_tz_to_iana(char *out, char *win_id, char *geo_name) { |
| 753 | if (*win_id == '\0') |
| 754 | return -1; |
| 755 | |
| 756 | FILE *fp = fopen("c:\\Windows\\Globalization\\Time Zone\\TimezoneMapping.xml", "r"); |
| 757 | if (!fp) |
| 758 | return -1; |
| 759 | |
| 760 | char buffer[CONFIG_FILE_LINE_MAX + 1]; |
| 761 | char win_id_match[512]; |
| 762 | bool copied = 0; |
| 763 | |
| 764 | snprintfz(win_id_match, sizeof(win_id_match), "\"%s\"", win_id); |
| 765 | |
| 766 | while (fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) { |
| 767 | buffer[CONFIG_FILE_LINE_MAX] = '\0'; |
| 768 | |
| 769 | char *s = strstr(buffer, win_id_match); |
| 770 | if (!s) { |
| 771 | if (!copied) |
| 772 | continue; |
| 773 | else // Country codes do not match, but we found the zone |
| 774 | break; |
| 775 | } |
| 776 | |
| 777 | //Escape:' <MapTZ TZID="' |
| 778 | s = &buffer[15]; |
| 779 | char *end = strchr(s, '"'); |
| 780 | if (!end) |
| 781 | continue; |
| 782 | |
| 783 | *end = '\0'; |
| 784 | |
| 785 | strncpyz(out, s, strlen(s)); |
| 786 | |
| 787 | //Escape:" Region=" |
| 788 | char *cmpregion = end+ 10; |
| 789 | if (!strncmp(cmpregion, geo_name, 2)) |
| 790 | break; |
| 791 | |
| 792 | copied = 1; |
| 793 | } |
| 794 | |
| 795 | fclose(fp); |
| 796 | return 0; |
| 797 | } |
| 798 | #endif |
| 799 | |
| 800 | // Detect the current IANA timezone name from the system. |
| 801 | // Returns a pointer into the provided buffer, or NULL if detection fails. |
| 802 | const char *detect_system_timezone_name(char *buffer, size_t buffer_size) { |
| 803 | const char *timezone = NULL; |
| 804 | |
| 805 | #ifdef OS_WINDOWS |
| 806 | char geo_name[128]; |
| 807 | char win_zone[256]; |
| 808 | get_timezone_win_id(win_zone, 256); |
| 809 | get_win_geoiso(geo_name, 128); |
| 810 | if (!map_windows_tz_to_iana(buffer, win_zone, geo_name)) |
| 811 | timezone = buffer; |
| 812 | #else |
| 813 | // read the /etc/localtime symlink first — this is the authoritative source |
| 814 | // on modern Linux (timedatectl always updates it, but /etc/timezone can lag) |
| 815 | { |
| 816 | ssize_t ret = readlink("/etc/localtime", buffer, buffer_size - 1); |
| 817 | if (ret > 0) { |
| 818 | buffer[ret] = '\0'; |
| 819 | |
| 820 | const char *cmp = "/usr/share/zoneinfo/"; |
| 821 | size_t cmp_len = strlen(cmp); |
| 822 | |
| 823 | char *s = strstr(buffer, cmp); |
| 824 | if (s && s[cmp_len]) |
| 825 | timezone = &s[cmp_len]; |
| 826 | } |
| 827 | } |
| 828 | |
| 829 | // fall back to /etc/timezone (Debian/Ubuntu) |
| 830 | if (!timezone && !read_txt_file("/etc/timezone", buffer, buffer_size)) { |
| 831 | timezone = buffer; |
| 832 | } |
| 833 | #endif |
| 834 | |
| 835 | if (timezone && *timezone) { |
| 836 | // sanitize in-place: keep only alnum, '_', '/', '-', '+' |
| 837 | char *d = buffer; |
| 838 | const char *src = timezone; |
| 839 | const char *end = buffer + buffer_size - 1; |
| 840 | while (*src && d < end) { |
| 841 | if (isalnum((uint8_t)*src) || *src == '_' || *src == '/' || *src == '-' || *src == '+') |
| 842 | *d++ = *src; |
| 843 | src++; |
| 844 | } |
| 845 | *d = '\0'; |
| 846 | timezone = buffer; |
| 847 | } |
| 848 | |
| 849 | return (timezone && *timezone) ? timezone : NULL; |
| 850 | } |
| 851 | |
| 852 | // Set at startup: true when the user explicitly set "timezone" in netdata.conf. |
| 853 | static bool timezone_user_configured = false; |
| 854 | |
| 855 | // True when the timezone name came from a proper source (config, |
| 856 | // /etc/localtime, /etc/timezone, TZ env var) rather than from the strftime("%Z") |
| 857 | // fallback which only produces bare abbreviations like "CEST" or "PST". |
| 858 | static bool timezone_is_tzdb_name = false; |
| 859 | |
| 860 | static bool timezone_abbrev_normalize(char *dst, size_t dst_size, const char *src); |
| 861 | |
| 862 | bool system_timezone_is_user_configured(void) { |
| 863 | return timezone_user_configured; |
| 864 | } |
| 865 | |
| 866 | bool system_timezone_is_tzdb_name(void) { |
| 867 | return timezone_is_tzdb_name; |
| 868 | } |
| 869 | |
| 870 | void get_system_timezone(void) |
| 871 | { |
| 872 | char buffer[FILENAME_MAX + 1] = ""; |
| 873 | const char *timezone = NULL; |
| 874 | |
| 875 | #ifndef OS_WINDOWS |
| 876 | // avoid flood calls to stat(/etc/localtime) |
| 877 | // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux |
| 878 | const char *tz = getenv("TZ"); |
| 879 | if (!tz || !*tz) { |
| 880 | setenv("TZ", inicfg_get(&netdata_config, CONFIG_SECTION_ENV_VARS, "TZ", ":/etc/localtime"), 1); |
| 881 | tz = getenv("TZ"); |
| 882 | } |
| 883 | |
| 884 | // use the TZ variable if it's an explicit IANA name (not a path starting with ':') |
| 885 | if (tz && *tz && *tz != ':') { |
| 886 | timezone = tz; |
| 887 | timezone_is_tzdb_name = true; |
| 888 | netdata_log_info("TIMEZONE: using TZ variable '%s'", timezone); |
| 889 | } |
| 890 | #endif |
| 891 | |
| 892 | // Detect from system sources (/etc/localtime symlink, /etc/timezone, Windows API) |
| 893 | if (!timezone) { |
| 894 | timezone = detect_system_timezone_name(buffer, sizeof(buffer)); |
| 895 | if (timezone) { |
| 896 | timezone_is_tzdb_name = true; |
| 897 | netdata_log_info("TIMEZONE: detected '%s'", timezone); |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | // Last resort: use strftime %Z (gives abbreviation, not IANA name). |
| 902 | // timezone_is_tzdb_name stays false so refresh_system_timezone() won't |
| 903 | // try to resolve it via tzalloc() or the tzfile parser. |
| 904 | if (!timezone) { |
| 905 | time_t t; |
| 906 | struct tm *tmp, tmbuf; |
| 907 | |
| 908 | t = now_realtime_sec(); |
| 909 | tmp = localtime_r(&t, &tmbuf); |
| 910 | |
| 911 | if (tmp != NULL) { |
| 912 | if (strftime(buffer, FILENAME_MAX, "%Z", tmp) != 0) { |
| 913 | buffer[FILENAME_MAX] = '\0'; |
| 914 | timezone = buffer; |
| 915 | netdata_log_info("TIMEZONE: using strftime(): '%s'", timezone); |
| 916 | } |
| 917 | } |
| 918 | } |
| 919 | |
| 920 | if (!timezone || !*timezone) |
| 921 | timezone = "unknown"; |
| 922 | |
| 923 | // Check if the user explicitly set "timezone" in netdata.conf BEFORE |
| 924 | // inicfg_get auto-populates it with the detected default. |
| 925 | timezone_user_configured = inicfg_exists(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone"); |
| 926 | |
| 927 | char safe_timezone[FILENAME_MAX + 1]; |
| 928 | const char *default_timezone = timezone; |
| 929 | |
| 930 | if (timezone_is_tzdb_name) { |
| 931 | if (!timezone_name_is_safe_tzdb_path(timezone)) { |
| 932 | netdata_log_error("TIMEZONE: detected unsafe tzdb timezone '%s', ignoring", timezone); |
| 933 | default_timezone = "unknown"; |
| 934 | } |
| 935 | } else if (!timezone_abbrev_normalize(safe_timezone, sizeof(safe_timezone), timezone)) { |
| 936 | default_timezone = "unknown"; |
| 937 | } else { |
| 938 | default_timezone = safe_timezone; |
| 939 | } |
| 940 | |
| 941 | // inicfg_get returns a config-system-owned pointer, stable for the process lifetime |
| 942 | const char *configured_tz = inicfg_get(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone", default_timezone); |
| 943 | |
| 944 | // Treat "timezone =" (empty value) as not user-configured, |
| 945 | // and fall back to the auto-detected timezone. |
| 946 | if (timezone_user_configured && (!configured_tz || !*configured_tz)) { |
| 947 | timezone_user_configured = false; |
| 948 | configured_tz = default_timezone; |
| 949 | } |
| 950 | |
| 951 | // If the user explicitly configured a timezone, treat it as a valid tzdb name |
| 952 | // (the user is responsible for providing a valid value). |
| 953 | if (timezone_user_configured) |
| 954 | timezone_is_tzdb_name = true; |
| 955 | |
| 956 | // Compute abbreviation and UTC offset, then set all three atomically |
| 957 | refresh_system_timezone(configured_tz, timezone_is_tzdb_name); |
| 958 | } |
| 959 | |
| 960 | static inline uint32_t tzif_read_be32(const unsigned char *src) { |
| 961 | return ((uint32_t)src[0] << 24) | |
| 962 | ((uint32_t)src[1] << 16) | |
| 963 | ((uint32_t)src[2] << 8) | |
| 964 | (uint32_t)src[3]; |
| 965 | } |
| 966 | |
| 967 | static inline int64_t tzif_read_be64(const unsigned char *src) { |
| 968 | return (int64_t)( |
| 969 | ((uint64_t)src[0] << 56) | |
| 970 | ((uint64_t)src[1] << 48) | |
| 971 | ((uint64_t)src[2] << 40) | |
| 972 | ((uint64_t)src[3] << 32) | |
| 973 | ((uint64_t)src[4] << 24) | |
| 974 | ((uint64_t)src[5] << 16) | |
| 975 | ((uint64_t)src[6] << 8) | |
| 976 | (uint64_t)src[7] |
| 977 | ); |
| 978 | } |
| 979 | |
| 980 | bool timezone_name_is_safe_tzdb_path(const char *timezone) { |
| 981 | if (!timezone || !*timezone || *timezone == '/') |
| 982 | return false; |
| 983 | |
| 984 | for (const char *p = timezone; *p; p++) { |
| 985 | if (!(isalnum((uint8_t)*p) || *p == '_' || *p == '/' || *p == '-' || *p == '+')) |
| 986 | return false; |
| 987 | } |
| 988 | |
| 989 | return true; |
| 990 | } |
| 991 | |
| 992 | static bool timezone_abbrev_normalize(char *dst, size_t dst_size, const char *src) { |
| 993 | if (!dst || dst_size < 2 || !src || !*src) |
| 994 | return false; |
| 995 | |
| 996 | size_t len = 0; |
| 997 | char *end = dst + dst_size - 1; |
| 998 | |
| 999 | while (*src && dst < end) { |
| 1000 | if (!(isalnum((uint8_t)*src) || *src == '_' || *src == '+' || *src == '-')) |
| 1001 | return false; |
| 1002 | |
| 1003 | *dst++ = *src++; |
| 1004 | len++; |
| 1005 | } |
| 1006 | |
| 1007 | *dst = '\0'; |
| 1008 | return len != 0 && *src == '\0'; |
| 1009 | } |
| 1010 | |
| 1011 | static bool timezone_info_from_tm(struct tm *tmp, char *abbrev, size_t abbrev_size, int32_t *offset) { |
| 1012 | if (!tmp) |
| 1013 | return false; |
| 1014 | |
| 1015 | int32_t new_offset = 0; |
| 1016 | char offset_str[16]; |
| 1017 | |
| 1018 | if (strftime(abbrev, abbrev_size, "%Z", tmp) == 0) |
| 1019 | strncpyz(abbrev, "UTC", abbrev_size - 1); |
| 1020 | |
| 1021 | if (strftime(offset_str, sizeof(offset_str), "%z", tmp) != 0) { |
| 1022 | char sign = offset_str[0] == '-' || offset_str[0] == '+' ? offset_str[0] : '+'; |
| 1023 | int hours = (isdigit((uint8_t)offset_str[1]) ? (offset_str[1] - '0') : 0) * 10 + |
| 1024 | (isdigit((uint8_t)offset_str[2]) ? (offset_str[2] - '0') : 0); |
| 1025 | int minutes = (isdigit((uint8_t)offset_str[3]) ? (offset_str[3] - '0') : 0) * 10 + |
| 1026 | (isdigit((uint8_t)offset_str[4]) ? (offset_str[4] - '0') : 0); |
| 1027 | |
| 1028 | new_offset = (hours * 3600) + (minutes * 60); |
| 1029 | if (sign == '-') |
| 1030 | new_offset = -new_offset; |
| 1031 | } |
| 1032 | |
| 1033 | *offset = new_offset; |
| 1034 | return true; |
| 1035 | } |
| 1036 | |
| 1037 | static bool current_process_timezone_info(time_t t, char *abbrev, size_t abbrev_size, int32_t *offset) { |
| 1038 | struct tm *tmp, tmbuf; |
| 1039 | tmp = localtime_r(&t, &tmbuf); |
| 1040 | return timezone_info_from_tm(tmp, abbrev, abbrev_size, offset); |
| 1041 | } |
| 1042 | |
| 1043 | #ifndef OS_WINDOWS |
| 1044 | struct tzif_header { |
| 1045 | char magic[4]; |
| 1046 | char version; |
| 1047 | char reserved[15]; |
| 1048 | unsigned char ttisgmtcnt[4]; |
| 1049 | unsigned char ttisstdcnt[4]; |
| 1050 | unsigned char leapcnt[4]; |
| 1051 | unsigned char timecnt[4]; |
| 1052 | unsigned char typecnt[4]; |
| 1053 | unsigned char charcnt[4]; |
| 1054 | }; |
| 1055 | |
| 1056 | struct tzif_type { |
| 1057 | int32_t gmtoff; |
| 1058 | uint8_t isdst; |
| 1059 | uint8_t abbrind; |
| 1060 | }; |
| 1061 | |
| 1062 | static bool tzif_skip_bytes(FILE *fp, size_t bytes) { |
| 1063 | char buffer[256]; |
| 1064 | while (bytes) { |
| 1065 | size_t chunk = bytes > sizeof(buffer) ? sizeof(buffer) : bytes; |
| 1066 | if (fread(buffer, 1, chunk, fp) != chunk) |
| 1067 | return false; |
| 1068 | bytes -= chunk; |
| 1069 | } |
| 1070 | return true; |
| 1071 | } |
| 1072 | |
| 1073 | // RFC 8536 sane upper bounds — real tzfiles are far smaller, |
| 1074 | // but we allow headroom for unusual / future data. |
| 1075 | #define TZIF_MAX_TIMECNT 2048 |
| 1076 | #define TZIF_MAX_TYPECNT 256 |
| 1077 | #define TZIF_MAX_CHARCNT 2048 |
| 1078 | #define TZIF_MAX_LEAPCNT 50 |
| 1079 | #define TZIF_MAX_ISSTDCNT 256 |
| 1080 | #define TZIF_MAX_ISGMTCNT 256 |
| 1081 | |
| 1082 | static bool tzif_validate_header_counts(uint32_t timecnt, uint32_t typecnt, uint32_t charcnt, |
| 1083 | uint32_t leapcnt, uint32_t ttisstdcnt, uint32_t ttisgmtcnt) { |
| 1084 | if (timecnt > TZIF_MAX_TIMECNT || typecnt > TZIF_MAX_TYPECNT || |
| 1085 | charcnt > TZIF_MAX_CHARCNT || leapcnt > TZIF_MAX_LEAPCNT || |
| 1086 | ttisstdcnt > TZIF_MAX_ISSTDCNT || ttisgmtcnt > TZIF_MAX_ISGMTCNT) |
| 1087 | return false; |
| 1088 | |
| 1089 | // ttisstdcnt and ttisgmtcnt must be 0 or equal to typecnt per RFC 8536 |
| 1090 | if ((ttisstdcnt != 0 && ttisstdcnt != typecnt) || |
| 1091 | (ttisgmtcnt != 0 && ttisgmtcnt != typecnt)) |
| 1092 | return false; |
| 1093 | |
| 1094 | return true; |
| 1095 | } |
| 1096 | |
| 1097 | static bool tzif_skip_block(FILE *fp, const struct tzif_header *hdr, size_t time_size) { |
| 1098 | uint32_t ttisgmtcnt = tzif_read_be32(hdr->ttisgmtcnt); |
| 1099 | uint32_t ttisstdcnt = tzif_read_be32(hdr->ttisstdcnt); |
| 1100 | uint32_t leapcnt = tzif_read_be32(hdr->leapcnt); |
| 1101 | uint32_t timecnt = tzif_read_be32(hdr->timecnt); |
| 1102 | uint32_t typecnt = tzif_read_be32(hdr->typecnt); |
| 1103 | uint32_t charcnt = tzif_read_be32(hdr->charcnt); |
| 1104 | |
| 1105 | if (!tzif_validate_header_counts(timecnt, typecnt, charcnt, leapcnt, ttisstdcnt, ttisgmtcnt)) |
| 1106 | return false; |
| 1107 | |
| 1108 | size_t bytes = (size_t)timecnt * time_size + |
| 1109 | (size_t)timecnt + |
| 1110 | (size_t)typecnt * 6 + |
| 1111 | (size_t)charcnt + |
| 1112 | (size_t)leapcnt * (time_size + 4) + |
| 1113 | (size_t)ttisstdcnt + |
| 1114 | (size_t)ttisgmtcnt; |
| 1115 | |
| 1116 | return tzif_skip_bytes(fp, bytes); |
| 1117 | } |
| 1118 | |
| 1119 | static int tzif_default_type_index(const struct tzif_type *types, uint32_t typecnt) { |
| 1120 | if (!types || !typecnt) |
| 1121 | return -1; |
| 1122 | |
| 1123 | for (uint32_t i = 0; i < typecnt; i++) { |
| 1124 | if (!types[i].isdst) |
| 1125 | return (int)i; |
| 1126 | } |
| 1127 | |
| 1128 | return 0; |
| 1129 | } |
| 1130 | |
| 1131 | static bool timezone_info_from_tzfile(const char *timezone, time_t t, char *abbrev, size_t abbrev_size, int32_t *offset) { |
| 1132 | if (!timezone_name_is_safe_tzdb_path(timezone)) |
| 1133 | return false; |
| 1134 | |
| 1135 | const char *tzdir = getenv("TZDIR"); |
| 1136 | if (!tzdir || !*tzdir) |
| 1137 | tzdir = "/usr/share/zoneinfo"; |
| 1138 | |
| 1139 | char path[FILENAME_MAX + 1]; |
| 1140 | snprintfz(path, sizeof(path), "%s/%s", tzdir, timezone); |
| 1141 | |
| 1142 | FILE *fp = fopen(path, "rb"); |
| 1143 | if (!fp) |
| 1144 | return false; |
| 1145 | |
| 1146 | bool ok = false; |
| 1147 | struct tzif_header hdr; |
| 1148 | |
| 1149 | if (fread(&hdr, 1, sizeof(hdr), fp) != sizeof(hdr)) |
| 1150 | goto cleanup; |
| 1151 | |
| 1152 | if (memcmp(hdr.magic, "TZif", 4) != 0) |
| 1153 | goto cleanup; |
| 1154 | |
| 1155 | if (hdr.version >= '2') { |
| 1156 | if (!tzif_skip_block(fp, &hdr, 4)) |
| 1157 | goto cleanup; |
| 1158 | |
| 1159 | if (fread(&hdr, 1, sizeof(hdr), fp) != sizeof(hdr)) |
| 1160 | goto cleanup; |
| 1161 | |
| 1162 | if (memcmp(hdr.magic, "TZif", 4) != 0) |
| 1163 | goto cleanup; |
| 1164 | } |
| 1165 | |
| 1166 | uint32_t timecnt = tzif_read_be32(hdr.timecnt); |
| 1167 | uint32_t typecnt = tzif_read_be32(hdr.typecnt); |
| 1168 | uint32_t charcnt = tzif_read_be32(hdr.charcnt); |
| 1169 | uint32_t leapcnt = tzif_read_be32(hdr.leapcnt); |
| 1170 | uint32_t ttisstdcnt = tzif_read_be32(hdr.ttisstdcnt); |
| 1171 | uint32_t ttisgmtcnt = tzif_read_be32(hdr.ttisgmtcnt); |
| 1172 | size_t time_size = (hdr.version >= '2') ? 8 : 4; |
| 1173 | |
| 1174 | if (!typecnt || !charcnt) |
| 1175 | goto cleanup; |
| 1176 | |
| 1177 | if (!tzif_validate_header_counts(timecnt, typecnt, charcnt, leapcnt, ttisstdcnt, ttisgmtcnt)) |
| 1178 | goto cleanup; |
| 1179 | |
| 1180 | int64_t *transition_times = callocz(timecnt ? timecnt : 1, sizeof(*transition_times)); |
| 1181 | uint8_t *transition_types = callocz(timecnt ? timecnt : 1, sizeof(*transition_types)); |
| 1182 | struct tzif_type *types = callocz(typecnt, sizeof(*types)); |
| 1183 | char *abbrs = callocz(charcnt + 1, sizeof(*abbrs)); |
| 1184 | |
| 1185 | unsigned char timebuf[8]; |
| 1186 | unsigned char typebuf[6]; |
| 1187 | |
| 1188 | for (uint32_t i = 0; i < timecnt; i++) { |
| 1189 | if (fread(timebuf, 1, time_size, fp) != time_size) |
| 1190 | goto free_and_cleanup; |
| 1191 | transition_times[i] = (time_size == 8) ? tzif_read_be64(timebuf) : (int32_t)tzif_read_be32(timebuf); |
| 1192 | } |
| 1193 | |
| 1194 | if (timecnt && fread(transition_types, 1, timecnt, fp) != timecnt) |
| 1195 | goto free_and_cleanup; |
| 1196 | |
| 1197 | // validate all transition type indices before using them |
| 1198 | for (uint32_t i = 0; i < timecnt; i++) { |
| 1199 | if (transition_types[i] >= typecnt) |
| 1200 | goto free_and_cleanup; |
| 1201 | } |
| 1202 | |
| 1203 | for (uint32_t i = 0; i < typecnt; i++) { |
| 1204 | if (fread(typebuf, 1, sizeof(typebuf), fp) != sizeof(typebuf)) |
| 1205 | goto free_and_cleanup; |
| 1206 | |
| 1207 | types[i].gmtoff = (int32_t)tzif_read_be32(typebuf); |
| 1208 | types[i].isdst = typebuf[4]; |
| 1209 | types[i].abbrind = typebuf[5]; |
| 1210 | } |
| 1211 | |
| 1212 | if (fread(abbrs, 1, charcnt, fp) != charcnt) |
| 1213 | goto free_and_cleanup; |
| 1214 | abbrs[charcnt] = '\0'; |
| 1215 | |
| 1216 | int type_index = -1; |
| 1217 | if (timecnt == 0) { |
| 1218 | type_index = tzif_default_type_index(types, typecnt); |
| 1219 | } else { |
| 1220 | for (uint32_t i = 0; i < timecnt; i++) { |
| 1221 | if ((int64_t)t < transition_times[i]) |
| 1222 | break; |
| 1223 | type_index = transition_types[i]; |
| 1224 | } |
| 1225 | |
| 1226 | if (type_index < 0) |
| 1227 | type_index = tzif_default_type_index(types, typecnt); |
| 1228 | } |
| 1229 | |
| 1230 | if (type_index < 0 || (uint32_t)type_index >= typecnt) |
| 1231 | goto free_and_cleanup; |
| 1232 | |
| 1233 | if (types[type_index].abbrind >= charcnt) |
| 1234 | goto free_and_cleanup; |
| 1235 | |
| 1236 | const char *tz_abbrev = &abbrs[types[type_index].abbrind]; |
| 1237 | if (!*tz_abbrev) |
| 1238 | tz_abbrev = "UTC"; |
| 1239 | |
| 1240 | strncpyz(abbrev, tz_abbrev, abbrev_size - 1); |
| 1241 | *offset = types[type_index].gmtoff; |
| 1242 | ok = true; |
| 1243 | |
| 1244 | free_and_cleanup: |
| 1245 | freez(abbrs); |
| 1246 | freez(types); |
| 1247 | freez(transition_types); |
| 1248 | freez(transition_times); |
| 1249 | |
| 1250 | cleanup: |
| 1251 | fclose(fp); |
| 1252 | return ok; |
| 1253 | } |
| 1254 | #endif |
| 1255 | |
| 1256 | void refresh_system_timezone(const char *timezone, bool is_tzdb_name) { |
| 1257 | time_t t; |
| 1258 | char abbrev[64]; |
| 1259 | char safe_abbrev[64]; |
| 1260 | const char *new_abbrev = "UTC"; |
| 1261 | int32_t new_offset = 0; |
| 1262 | |
| 1263 | // Update global flag when we have confirmed tzdb knowledge. |
| 1264 | // When is_tzdb_name is false, preserve the existing global flag — a prior |
| 1265 | // successful detection may have already promoted it to true. |
| 1266 | if (is_tzdb_name) |
| 1267 | timezone_is_tzdb_name = true; |
| 1268 | |
| 1269 | t = now_realtime_sec(); |
| 1270 | bool ok = false; |
| 1271 | |
| 1272 | #if defined(HAVE_TZALLOC) && defined(HAVE_LOCALTIME_RZ) && defined(HAVE_TZFREE) |
| 1273 | if (is_tzdb_name) { |
| 1274 | timezone_t tz = tzalloc(timezone); |
| 1275 | if (tz) { |
| 1276 | struct tm *tmp, tmbuf; |
| 1277 | tmp = localtime_rz(tz, &t, &tmbuf); |
| 1278 | ok = timezone_info_from_tm(tmp, abbrev, sizeof(abbrev), &new_offset); |
| 1279 | tzfree(tz); |
| 1280 | } |
| 1281 | } |
| 1282 | #elif !defined(OS_WINDOWS) |
| 1283 | if (is_tzdb_name) |
| 1284 | ok = timezone_info_from_tzfile(timezone, t, abbrev, sizeof(abbrev), &new_offset); |
| 1285 | #endif |
| 1286 | |
| 1287 | if (!ok) |
| 1288 | ok = current_process_timezone_info(t, abbrev, sizeof(abbrev), &new_offset); |
| 1289 | |
| 1290 | if (ok && timezone_abbrev_normalize(safe_abbrev, sizeof(safe_abbrev), abbrev)) |
| 1291 | new_abbrev = safe_abbrev; |
| 1292 | |
| 1293 | // Atomically update the system timezone triplet |
| 1294 | system_tz_set(timezone, new_abbrev, new_offset); |
| 1295 | |
| 1296 | // Update localhost if it exists |
| 1297 | if (localhost) { |
| 1298 | if (rrdhost_update_timezone(localhost, timezone, new_abbrev, new_offset)) { |
| 1299 | // Timezone changed — update the two labels directly, persist, and notify. |
| 1300 | if (localhost->rrdlabels) { |
| 1301 | bool labels_changed = rrdlabels_add_changed(localhost->rrdlabels, "_timezone", timezone, RRDLABEL_SRC_AUTO); |
| 1302 | labels_changed |= rrdlabels_add_changed(localhost->rrdlabels, "_abbrev_timezone", new_abbrev, RRDLABEL_SRC_AUTO); |
| 1303 | rrdhost_flag_set(localhost, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE); |
| 1304 | if(labels_changed) |
| 1305 | rrdhost_flag_set(localhost, RRDHOST_FLAG_PENDING_LABEL_RECHECK); |
| 1306 | stream_send_host_labels(localhost); |
| 1307 | } |
| 1308 | aclk_queue_node_info(localhost, false); |
| 1309 | } |
| 1310 | } |
| 1311 | } |
| 1312 | |
| 1313 | bool analytics_check_enabled(void) { |
| 1314 | if(!netdata_anonymous_statistics_enabled) |
| 1315 | return false; |
| 1316 | |
| 1317 | char filename[FILENAME_MAX + 1]; |
| 1318 | snprintfz(filename, sizeof(filename), "%s/.opt-out-from-anonymous-statistics", netdata_configured_user_config_dir); |
| 1319 | |
| 1320 | if(access(filename, R_OK) != 0) { |
| 1321 | // the file is not there, check the environment variable |
| 1322 | const char *s = getenv("DISABLE_TELEMETRY"); |
| 1323 | netdata_anonymous_statistics_enabled = !s || !*s; |
| 1324 | } |
| 1325 | else |
| 1326 | // the file is there, disable telemetry |
| 1327 | netdata_anonymous_statistics_enabled = false; |
| 1328 | |
| 1329 | return netdata_anonymous_statistics_enabled; |
| 1330 | } |
| 1331 | |
| 1332 | void analytics_reset(void) { |
| 1333 | analytics_data.data_length = 0; |
| 1334 | analytics_set_data(&analytics_data.netdata_config_stream_enabled, "null"); |
| 1335 | analytics_set_data(&analytics_data.netdata_config_memory_mode, "null"); |
| 1336 | analytics_set_data(&analytics_data.netdata_config_exporting_enabled, "null"); |
| 1337 | analytics_set_data(&analytics_data.netdata_exporting_connectors, "null"); |
| 1338 | analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, "null"); |
| 1339 | analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, "null"); |
| 1340 | analytics_set_data(&analytics_data.netdata_allmetrics_json_used, "null"); |
| 1341 | analytics_set_data(&analytics_data.netdata_dashboard_used, "null"); |
| 1342 | analytics_set_data(&analytics_data.netdata_collectors, "null"); |
| 1343 | analytics_set_data(&analytics_data.netdata_collectors_count, "null"); |
| 1344 | analytics_set_data(&analytics_data.netdata_buildinfo, "null"); |
| 1345 | analytics_set_data(&analytics_data.netdata_config_page_cache_size, "null"); |
| 1346 | analytics_set_data(&analytics_data.netdata_config_multidb_disk_quota, "null"); |
| 1347 | analytics_set_data(&analytics_data.netdata_config_https_enabled, "null"); |
| 1348 | analytics_set_data(&analytics_data.netdata_config_web_enabled, "null"); |
| 1349 | analytics_set_data(&analytics_data.netdata_config_release_channel, "null"); |
| 1350 | analytics_set_data(&analytics_data.netdata_mirrored_host_count, "null"); |
| 1351 | analytics_set_data(&analytics_data.netdata_mirrored_hosts_reachable, "null"); |
| 1352 | analytics_set_data(&analytics_data.netdata_mirrored_hosts_unreachable, "null"); |
| 1353 | analytics_set_data(&analytics_data.netdata_notification_methods, "null"); |
| 1354 | analytics_set_data(&analytics_data.netdata_alarms_normal, "null"); |
| 1355 | analytics_set_data(&analytics_data.netdata_alarms_warning, "null"); |
| 1356 | analytics_set_data(&analytics_data.netdata_alarms_critical, "null"); |
| 1357 | analytics_set_data(&analytics_data.netdata_charts_count, "null"); |
| 1358 | analytics_set_data(&analytics_data.netdata_metrics_count, "null"); |
| 1359 | analytics_set_data(&analytics_data.netdata_config_is_parent, "null"); |
| 1360 | analytics_set_data(&analytics_data.netdata_config_hosts_available, "null"); |
| 1361 | analytics_set_data(&analytics_data.netdata_host_cloud_available, "null"); |
| 1362 | analytics_set_data(&analytics_data.netdata_host_aclk_implementation, "null"); |
| 1363 | analytics_set_data(&analytics_data.netdata_host_aclk_available, "null"); |
| 1364 | analytics_set_data(&analytics_data.netdata_host_aclk_protocol, "null"); |
| 1365 | analytics_set_data(&analytics_data.netdata_host_agent_claimed, "null"); |
| 1366 | analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "null"); |
| 1367 | analytics_set_data(&analytics_data.netdata_config_https_available, "null"); |
| 1368 | analytics_set_data(&analytics_data.netdata_install_type, "null"); |
| 1369 | analytics_set_data(&analytics_data.netdata_config_is_private_registry, "null"); |
| 1370 | analytics_set_data(&analytics_data.netdata_config_use_private_registry, "null"); |
| 1371 | analytics_set_data(&analytics_data.netdata_config_oom_score, "null"); |
| 1372 | analytics_set_data(&analytics_data.netdata_prebuilt_distro, "null"); |
| 1373 | analytics_set_data(&analytics_data.netdata_fail_reason, "null"); |
| 1374 | |
| 1375 | analytics_data.prometheus_hits = 0; |
| 1376 | analytics_data.shell_hits = 0; |
| 1377 | analytics_data.json_hits = 0; |
| 1378 | analytics_data.dashboard_hits = 0; |
| 1379 | analytics_data.charts_count = 0; |
| 1380 | analytics_data.metrics_count = 0; |
| 1381 | analytics_data.exporting_enabled = false; |
| 1382 | } |
| 1383 | |
| 1384 | void analytics_init(void) |
| 1385 | { |
| 1386 | spinlock_init(&analytics_data.spinlock); |
| 1387 | } |