status file improvements (dedup and signal handler use) (#19751)
* remove the address from the stack trace to make it unique * log backtrace failure to get symbols * improve status file handling via the signal handler * add already-running exit reason
Costa Tsaousis committed
Mar 3, 2025 at 11:59 UTC
6da3a165090ab00f5966cd3ce431b3f315c52b25
8 files changed
+243
-152
src/aclk/aclk_proxy.c
+2
-1
@@ -112,7 +112,8 @@ static inline int check_http_environment(const char **proxy)
112
113
char buf[1024];
114
snprintfz(buf, sizeof(buf),
115
- "Environment var '%s' defined but of unknown format. Supported syntax: 'http://[user:pass@]host:port'.",
115
+ "Environment var '%s' defined but of unknown format '%s'. "
116
+ "Supported syntax: 'http://[user:pass@]host:port'.",
117
var, tmp);
118
safe_log_proxy_error(buf, tmp);
119
src/daemon/daemon-status-file.c
+196
-132
@@ -37,14 +37,37 @@ ENUM_STR_MAP_DEFINE(DAEMON_OS_TYPE) = {
37
};
38
ENUM_STR_DEFINE_FUNCTIONS(DAEMON_OS_TYPE, DAEMON_OS_TYPE_UNKNOWN, "unknown");
39
40
-static DAEMON_STATUS_FILE last_session_status = { 0 };
41
-static DAEMON_STATUS_FILE session_status = { 0 };
42
-static SPINLOCK dsf_spinlock = SPINLOCK_INITIALIZER;
40
+static DAEMON_STATUS_FILE last_session_status = {
41
+ .spinlock = SPINLOCK_INITIALIZER,
42
+ .fatal = {
43
+ .spinlock = SPINLOCK_INITIALIZER,
44
+ },
45
+ .dedup = {
46
+ .spinlock = SPINLOCK_INITIALIZER,
47
+ },
48
+};
49
+
50
+static DAEMON_STATUS_FILE session_status = {
51
+ .spinlock = SPINLOCK_INITIALIZER,
52
+ .fatal = {
53
+ .spinlock = SPINLOCK_INITIALIZER,
54
+ },
55
+ .dedup = {
56
+ .spinlock = SPINLOCK_INITIALIZER,
57
+ },
58
+};
59
+
60
+static void daemon_status_file_out_of_memory(void);
61
+
62
+// these are used instead of locks when locks cannot be used (signal handler, out of memory, etc)
63
+#define dsf_acquire(ds) __atomic_load_n(&(ds).v, __ATOMIC_ACQUIRE)
64
+#define dsf_release(ds) __atomic_store_n(&(ds).v, STATUS_FILE_VERSION, __ATOMIC_RELEASE)
65
66
// --------------------------------------------------------------------------------------------------------------------
67
// json generation
68
69
static XXH64_hash_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg, const char *cause) {
70
+ dsf_acquire(*ds);
71
CLEAN_BUFFER *wb = buffer_create(0, NULL);
72
buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
73
buffer_json_member_add_uint64(wb, "version", STATUS_FILE_VERSION);
@@ -62,12 +85,15 @@ static XXH64_hash_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *
85
buffer_json_member_add_string(wb, "status", DAEMON_STATUS_2str(ds->status));
86
EXIT_REASON_2json(wb, "exit_reason", ds->exit_reason);
87
ND_PROFILE_2json(wb, "profile", ds->profile);
88
+ dsf_release(*ds);
89
buffer_json_finalize(wb);
90
XXH64_hash_t hash = XXH3_64bits((const void *)buffer_tostring(wb), buffer_strlen(wb));
91
return hash;
92
}
93
94
static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
95
+ dsf_acquire(*ds);
96
+
97
buffer_json_member_add_datetime_rfc3339(wb, "@timestamp", ds->timestamp_ut, true); // ECS
98
buffer_json_member_add_uint64(wb, "version", STATUS_FILE_VERSION); // custom
99
@@ -159,19 +185,21 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
185
186
buffer_json_member_add_array(wb, "dedup"); // custom
187
{
162
- for(size_t i = 0; i < _countof(ds->dedup); i++) {
163
- if (ds->dedup[i].timestamp_ut == 0)
188
+ for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
189
+ if (ds->dedup.slot[i].timestamp_ut == 0)
190
continue;
191
192
buffer_json_add_array_item_object(wb); // custom
193
{
168
- buffer_json_member_add_datetime_rfc3339(wb, "@timestamp", ds->dedup[i].timestamp_ut, true); // custom
169
- buffer_json_member_add_uint64(wb, "hash", ds->dedup[i].hash); // custom
194
+ buffer_json_member_add_datetime_rfc3339(wb, "@timestamp", ds->dedup.slot[i].timestamp_ut, true); // custom
195
+ buffer_json_member_add_uint64(wb, "hash", ds->dedup.slot[i].hash); // custom
196
}
197
buffer_json_object_close(wb);
198
}
199
}
200
buffer_json_array_close(wb);
201
+
202
+ dsf_release(*ds);
203
}
204
205
// --------------------------------------------------------------------------------------------------------------------
@@ -278,9 +306,9 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
306
datetime[0] = '\0';
307
JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", datetime, error, required_v3);
308
if (datetime[0])
281
- ds->dedup[0].timestamp_ut = rfc3339_parse_ut(datetime, NULL);
309
+ ds->dedup.slot[0].timestamp_ut = rfc3339_parse_ut(datetime, NULL);
310
283
- JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "hash", ds->dedup[0].hash, error, required_v3);
311
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "hash", ds->dedup.slot[0].hash, error, required_v3);
312
JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "restarts", ds->restarts, error, required_v3);
313
});
314
}
@@ -288,14 +316,14 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
316
JSONC_PARSE_ARRAY(jobj, path, "dedup", error, required_v4, {
317
size_t i = 0;
318
JSONC_PARSE_ARRAY_ITEM_OBJECT(jobj, path, i, required_v4, {
291
- if(i >= _countof(ds->dedup))
319
+ if(i >= _countof(ds->dedup.slot))
320
break;
321
322
JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", datetime, error, required_v4);
323
if (datetime[0])
296
- ds->dedup[i].timestamp_ut = rfc3339_parse_ut(datetime, NULL);
324
+ ds->dedup.slot[i].timestamp_ut = rfc3339_parse_ut(datetime, NULL);
325
298
- JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "hash", ds->dedup[i].hash, error, required_v4);
326
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "hash", ds->dedup.slot[i].hash, error, required_v4);
327
});
328
});
329
}
@@ -307,10 +335,11 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
335
// get the current status
336
337
static void daemon_status_file_refresh(DAEMON_STATUS status) {
310
- spinlock_lock(&dsf_spinlock);
311
-
338
usec_t now_ut = now_realtime_usec();
339
340
+ dsf_acquire(session_status);
341
+ spinlock_lock(&session_status.spinlock);
342
+
343
#if defined(OS_LINUX)
344
session_status.os_type = DAEMON_OS_TYPE_LINUX;
345
#elif defined(OS_FREEBSD)
@@ -321,11 +350,17 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
350
session_status.os_type = DAEMON_OS_TYPE_WINDOWS;
351
#endif
352
353
+ if(session_status.status != DAEMON_STATUS_INITIALIZING && status == DAEMON_STATUS_INITIALIZING)
354
+ session_status.timings.init_started_ut = now_ut;
355
+
356
+ if(session_status.status != DAEMON_STATUS_EXITING && status == DAEMON_STATUS_EXITING)
357
+ session_status.timings.exit_started_ut = now_ut;
358
+
359
if(session_status.status == DAEMON_STATUS_INITIALIZING && status == DAEMON_STATUS_RUNNING)
325
- session_status.timings.init = (time_t)((now_ut - session_status.timestamp_ut + USEC_PER_SEC/2) / USEC_PER_SEC);
360
+ session_status.timings.init = (time_t)((now_ut - session_status.timings.init_started_ut + USEC_PER_SEC/2) / USEC_PER_SEC);
361
362
if(session_status.status == DAEMON_STATUS_EXITING && status == DAEMON_STATUS_EXITED)
328
- session_status.timings.exit = (time_t)((now_ut - session_status.timestamp_ut + USEC_PER_SEC/2) / USEC_PER_SEC);
363
+ session_status.timings.exit = (time_t)((now_ut - session_status.timings.exit_started_ut + USEC_PER_SEC/2) / USEC_PER_SEC);
364
365
strncpyz(session_status.version, NETDATA_VERSION, sizeof(session_status.version) - 1);
366
@@ -384,9 +419,9 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
419
session_status.os_id_like = strdupz(last_session_status.os_id_like);
420
if(!session_status.restarts)
421
session_status.restarts = last_session_status.restarts + 1;
387
- if(!session_status.dedup[0].timestamp_ut || !session_status.dedup[0].hash) {
388
- for (size_t i = 0; i < _countof(session_status.dedup); i++)
389
- session_status.dedup[i] = last_session_status.dedup[i];
422
+ if(!session_status.dedup.slot[0].timestamp_ut || !session_status.dedup.slot[0].hash) {
423
+ for (size_t i = 0; i < _countof(session_status.dedup.slot); i++)
424
+ session_status.dedup.slot[i] = last_session_status.dedup.slot[i];
425
}
426
427
if(!session_status.install_type) {
@@ -408,7 +443,8 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
443
session_status.memory = os_system_memory(true);
444
session_status.var_cache = os_disk_space(netdata_configured_cache_dir);
445
411
- spinlock_unlock(&dsf_spinlock);
446
+ spinlock_unlock(&session_status.spinlock);
447
+ dsf_release(session_status);
448
}
449
450
// --------------------------------------------------------------------------------------------------------------------
@@ -608,136 +644,97 @@ static void static_save_buffer_init(void) {
644
buffer_flush(static_save_buffer);
645
}
646
611
-static void daemon_status_file_save(DAEMON_STATUS_FILE *ds, bool have_lock) {
612
- if(!have_lock)
613
- spinlock_lock(&dsf_spinlock);
614
-
615
- static_save_buffer_init();
616
-
617
- buffer_json_initialize(static_save_buffer, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
618
- daemon_status_file_to_json(static_save_buffer, ds);
619
- buffer_json_finalize(static_save_buffer);
647
+static void daemon_status_file_save(BUFFER *wb, DAEMON_STATUS_FILE *ds, bool log) {
648
+ buffer_flush(wb);
649
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
650
+ daemon_status_file_to_json(wb, ds);
651
+ buffer_json_finalize(wb);
652
621
- const char *content = buffer_tostring(static_save_buffer);
622
- size_t content_size = buffer_strlen(static_save_buffer);
653
+ const char *content = buffer_tostring(wb);
654
+ size_t content_size = buffer_strlen(wb);
655
656
// Try primary directory first
657
bool saved = false;
658
if (save_status_file(netdata_configured_cache_dir, content, content_size))
659
saved = true;
660
else {
629
- nd_log(NDLS_DAEMON, NDLP_DEBUG, "Failed to save status file in primary directory %s",
630
- netdata_configured_cache_dir);
661
+ if(log)
662
+ nd_log(NDLS_DAEMON, NDLP_DEBUG, "Failed to save status file in primary directory %s",
663
+ netdata_configured_cache_dir);
664
665
// Try each fallback directory until successful
666
for(size_t i = 0; i < _countof(status_file_fallbacks); i++) {
667
if (save_status_file(status_file_fallbacks[i], content, content_size)) {
635
- nd_log(NDLS_DAEMON, NDLP_DEBUG, "Saved status file in fallback %s", status_file_fallbacks[i]);
668
+ if(log)
669
+ nd_log(NDLS_DAEMON, NDLP_DEBUG, "Saved status file in fallback %s", status_file_fallbacks[i]);
670
saved = true;
671
break;
672
}
673
}
674
}
675
642
- if (!saved)
676
+ if (!saved && log)
677
nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to save status file in any location");
644
-
645
- if(!have_lock)
646
- spinlock_unlock(&dsf_spinlock);
647
-}
648
-
649
-void daemon_status_file_update_status(DAEMON_STATUS status) {
650
- daemon_status_file_refresh(status);
651
- daemon_status_file_save(&session_status, false);
652
-}
653
-
654
-void daemon_status_file_exit_reason_save(EXIT_REASON reason) {
655
- exit_initiated_add(reason);
656
- spinlock_lock(&dsf_spinlock);
657
- session_status.exit_reason = exit_initiated;
658
- spinlock_unlock(&dsf_spinlock);
659
- daemon_status_file_save(&session_status, false);
660
-}
661
-
662
-static void daemon_status_file_out_of_memory(void) {
663
- daemon_status_file_exit_reason_save(EXIT_REASON_OUT_OF_MEMORY);
664
-}
665
-
666
-void daemon_status_file_deadly_signal_received(EXIT_REASON reason) {
667
- // DO NOT LOCK IN THIS FUNCTION - WE CRASHED ALREADY AND WE ARE INSIDE THE SIGNAL HANDLER!
668
-
669
- session_status.exit_reason |= reason;
670
-
671
- if(!session_status.fatal.thread[0])
672
- strncpyz(session_status.fatal.thread, nd_thread_tag(), sizeof(session_status.fatal.thread) - 1);
673
-
674
- if(!session_status.fatal.stack_trace[0]) {
675
- static_save_buffer_init();
676
- capture_stack_trace(static_save_buffer);
677
- strncpyz(session_status.fatal.stack_trace, buffer_tostring(static_save_buffer), sizeof(session_status.fatal.stack_trace) - 1);
678
- }
679
-
680
- daemon_status_file_save(&session_status, true);
678
}
679
680
// --------------------------------------------------------------------------------------------------------------------
681
// deduplication hashes management
682
686
-static bool dedup_already_posted(XXH64_hash_t hash) {
687
- spinlock_lock(&dsf_spinlock);
683
+static bool dedup_already_posted(DAEMON_STATUS_FILE *ds, XXH64_hash_t hash) {
684
+ spinlock_lock(&ds->dedup.spinlock);
685
686
usec_t now_ut = now_realtime_usec();
687
691
- for(size_t i = 0; i < _countof(session_status.dedup); i++) {
692
- if(session_status.dedup[i].timestamp_ut == 0)
688
+ for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
689
+ if(ds->dedup.slot[i].timestamp_ut == 0)
690
continue;
691
695
- if(hash == session_status.dedup[i].hash &&
696
- now_ut - session_status.dedup[i].timestamp_ut < 86400 * USEC_PER_SEC) {
692
+ if(hash == ds->dedup.slot[i].hash &&
693
+ now_ut - ds->dedup.slot[i].timestamp_ut < 86400 * USEC_PER_SEC) {
694
// we have already posted this crash
698
- spinlock_unlock(&dsf_spinlock);
695
+ spinlock_unlock(&ds->dedup.spinlock);
696
return true;
697
}
698
}
699
703
- spinlock_unlock(&dsf_spinlock);
700
+ spinlock_unlock(&ds->dedup.spinlock);
701
return false;
702
}
703
704
static void dedup_keep_hash(DAEMON_STATUS_FILE *ds, XXH64_hash_t hash) {
708
- spinlock_lock(&dsf_spinlock);
705
+ spinlock_lock(&ds->dedup.spinlock);
706
707
// find the same hash
711
- for(size_t i = 0; i < _countof(ds->dedup); i++) {
712
- if(ds->dedup[i].hash == hash) {
713
- ds->dedup[i].hash = hash;
714
- ds->dedup[i].timestamp_ut = now_realtime_usec();
715
- spinlock_unlock(&dsf_spinlock);
708
+ for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
709
+ if(ds->dedup.slot[i].hash == hash) {
710
+ ds->dedup.slot[i].hash = hash;
711
+ ds->dedup.slot[i].timestamp_ut = now_realtime_usec();
712
+ spinlock_unlock(&ds->dedup.spinlock);
713
return;
714
}
715
}
716
717
// find an empty slot
721
- for(size_t i = 0; i < _countof(ds->dedup); i++) {
722
- if(!ds->dedup[i].hash) {
723
- ds->dedup[i].hash = hash;
724
- ds->dedup[i].timestamp_ut = now_realtime_usec();
725
- spinlock_unlock(&dsf_spinlock);
718
+ for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
719
+ if(!ds->dedup.slot[i].hash) {
720
+ ds->dedup.slot[i].hash = hash;
721
+ ds->dedup.slot[i].timestamp_ut = now_realtime_usec();
722
+ spinlock_unlock(&ds->dedup.spinlock);
723
return;
724
}
725
}
726
727
// find the oldest slot
728
size_t store_at_slot = 0;
732
- for(size_t i = 1; i < _countof(ds->dedup); i++) {
733
- if(ds->dedup[i].timestamp_ut < ds->dedup[store_at_slot].timestamp_ut)
729
+ for(size_t i = 1; i < _countof(ds->dedup.slot); i++) {
730
+ if(ds->dedup.slot[i].timestamp_ut < ds->dedup.slot[store_at_slot].timestamp_ut)
731
store_at_slot = i;
732
}
733
737
- ds->dedup[store_at_slot].hash = hash;
738
- ds->dedup[store_at_slot].timestamp_ut = now_realtime_usec();
734
+ ds->dedup.slot[store_at_slot].hash = hash;
735
+ ds->dedup.slot[store_at_slot].timestamp_ut = now_realtime_usec();
736
740
- spinlock_unlock(&dsf_spinlock);
737
+ spinlock_unlock(&ds->dedup.spinlock);
738
}
739
740
// --------------------------------------------------------------------------------------------------------------------
@@ -776,7 +773,7 @@ void post_status_file(struct post_status_file_thread_data *d) {
773
if(rc == CURLE_OK) {
774
XXH64_hash_t hash = daemon_status_file_hash(d->status, d->msg, d->cause);
775
dedup_keep_hash(&session_status, hash);
779
- daemon_status_file_save(&session_status, false);
776
+ daemon_status_file_save(wb, &session_status, true);
777
}
778
779
curl_easy_cleanup(curl);
@@ -808,6 +805,8 @@ struct log_priority PRI_BAD_BUT_NO_REASON = { NDLP_ERR, NDLP_WARNING };
805
void daemon_status_file_check_crash(void) {
806
FUNCTION_RUN_ONCE();
807
808
+ static_save_buffer_init();
809
+
810
mallocz_register_out_of_memory_cb(daemon_status_file_out_of_memory);
811
812
last_session_status = daemon_status_file_load();
@@ -875,6 +874,11 @@ void daemon_status_file_check_crash(void) {
874
msg = "Netdata was last crashed while starting, because it couldn't allocate memory";
875
pri = PRI_USER_SHOULD_FIX;
876
}
877
+ else if (last_session_status.exit_reason & EXIT_REASON_ALREADY_RUNNING) {
878
+ cause = "out of memory";
879
+ msg = "Netdata couldn't start, because it was already running";
880
+ pri = PRI_USER_SHOULD_FIX;
881
+ }
882
else if (OS_SYSTEM_DISK_SPACE_OK(last_session_status.var_cache) &&
883
last_session_status.var_cache.is_read_only) {
884
cause = "disk read-only";
@@ -979,7 +983,7 @@ void daemon_status_file_check_crash(void) {
983
984
// check if we have already posted this crash in the last 24 hours
985
XXH64_hash_t hash = daemon_status_file_hash(&last_session_status, msg, cause);
982
- if(dedup_already_posted(hash))
986
+ if(dedup_already_posted(&session_status, hash))
987
disable_crash_report = true;
988
989
if(!disable_crash_report && (analytics_check_enabled() || post_crash_report)) {
@@ -994,6 +998,97 @@ void daemon_status_file_check_crash(void) {
998
}
999
}
1000
1001
+// --------------------------------------------------------------------------------------------------------------------
1002
+// ng_log() hook for receiving fatal message information
1003
+
1004
+void daemon_status_file_register_fatal(const char *filename, const char *function, const char *message, const char *errno_str, const char *stack_trace, long line) {
1005
+ FUNCTION_RUN_ONCE();
1006
+
1007
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
1008
+
1009
+ dsf_acquire(session_status);
1010
+ spinlock_lock(&session_status.fatal.spinlock);
1011
+
1012
+ // do not check the function, because it may have a startup step in it
1013
+ if(session_status.fatal.filename || session_status.fatal.message || session_status.fatal.errno_str || session_status.fatal.thread[0]) {
1014
+ spinlock_unlock(&session_status.fatal.spinlock);
1015
+ freez((void *)filename);
1016
+ freez((void *)function);
1017
+ freez((void *)message);
1018
+ freez((void *)errno_str);
1019
+ freez((void *)stack_trace);
1020
+ return;
1021
+ }
1022
+
1023
+ exit_initiated_add(EXIT_REASON_FATAL);
1024
+ strncpyz(session_status.fatal.thread, nd_thread_tag(), sizeof(session_status.fatal.thread) - 1);
1025
+
1026
+ session_status.fatal.filename = filename;
1027
+ freez((char *)session_status.fatal.function); // it may have a startup step
1028
+ session_status.fatal.function = function;
1029
+ session_status.fatal.message = message;
1030
+ session_status.fatal.errno_str = errno_str;
1031
+ strncpyz(session_status.fatal.stack_trace, stack_trace, sizeof(session_status.fatal.stack_trace) - 1);
1032
+ freez((char *)stack_trace);
1033
+ session_status.fatal.line = line;
1034
+
1035
+ spinlock_unlock(&session_status.fatal.spinlock);
1036
+ dsf_release(session_status);
1037
+
1038
+ daemon_status_file_save(wb, &session_status, false);
1039
+}
1040
+
1041
+// --------------------------------------------------------------------------------------------------------------------
1042
+
1043
+void daemon_status_file_update_status(DAEMON_STATUS status) {
1044
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
1045
+ daemon_status_file_refresh(status);
1046
+ daemon_status_file_save(wb, &session_status, true);
1047
+}
1048
+
1049
+static void daemon_status_file_out_of_memory(void) {
1050
+ FUNCTION_RUN_ONCE();
1051
+
1052
+ // DO NOT LOCK OR ALLOCATE IN THIS FUNCTION - WE DON'T HAVE ANY MEMORY AVAILABLE - IT HAPPENED ALREADY!
1053
+
1054
+ exit_initiated_add(EXIT_REASON_OUT_OF_MEMORY);
1055
+
1056
+ // the buffer should already be allocated, so this should normally do nothing
1057
+ static_save_buffer_init();
1058
+
1059
+ dsf_acquire(session_status);
1060
+ session_status.exit_reason = exit_initiated;
1061
+ dsf_release(session_status);
1062
+
1063
+ daemon_status_file_save(static_save_buffer, &session_status, false);
1064
+}
1065
+
1066
+void daemon_status_file_deadly_signal_received(EXIT_REASON reason) {
1067
+ FUNCTION_RUN_ONCE();
1068
+
1069
+ // DO NOT LOCK OR ALLOCATE IN THIS FUNCTION - WE CRASHED ALREADY AND WE ARE INSIDE THE SIGNAL HANDLER!
1070
+
1071
+ dsf_acquire(session_status);
1072
+
1073
+ session_status.exit_reason |= reason;
1074
+ if(!session_status.fatal.thread[0])
1075
+ strncpyz(session_status.fatal.thread, nd_thread_tag(), sizeof(session_status.fatal.thread) - 1);
1076
+
1077
+ dsf_release(session_status);
1078
+
1079
+ // the buffer should already be allocated, so this should normally do nothing
1080
+ static_save_buffer_init();
1081
+
1082
+ // save what we know already
1083
+ daemon_status_file_save(static_save_buffer, &session_status, false);
1084
+
1085
+ if(!session_status.fatal.stack_trace[0]) {
1086
+ capture_stack_trace(static_save_buffer);
1087
+ strncpyz(session_status.fatal.stack_trace, buffer_tostring(static_save_buffer), sizeof(session_status.fatal.stack_trace) - 1);
1088
+ daemon_status_file_save(static_save_buffer, &session_status, false);
1089
+ }
1090
+}
1091
+
1092
bool daemon_status_file_has_last_crashed(void) {
1093
return last_session_status.status != DAEMON_STATUS_EXITED || !is_exit_reason_normal(last_session_status.exit_reason);
1094
}
@@ -1002,6 +1097,9 @@ bool daemon_status_file_was_incomplete_shutdown(void) {
1097
return last_session_status.status == DAEMON_STATUS_EXITING;
1098
}
1099
1100
+// --------------------------------------------------------------------------------------------------------------------
1101
+// startup and shutdown steps
1102
+
1103
void daemon_status_file_startup_step(const char *step) {
1104
if(session_status.fatal.filename)
1105
// we have a fatal logged
@@ -1030,37 +1128,3 @@ void daemon_status_file_shutdown_step(const char *step) {
1128
1129
daemon_status_file_update_status(DAEMON_STATUS_NONE);
1130
}
1033
-
1034
-// --------------------------------------------------------------------------------------------------------------------
1035
-// ng_log() hook for receiving fatal message information
1036
-
1037
-void daemon_status_file_register_fatal(const char *filename, const char *function, const char *message, const char *errno_str, const char *stack_trace, long line) {
1038
- spinlock_lock(&dsf_spinlock);
1039
-
1040
- // do not check the function, because it may have a startup step in it
1041
- if(session_status.fatal.filename || session_status.fatal.message || session_status.fatal.errno_str || session_status.fatal.thread[0]) {
1042
- spinlock_unlock(&dsf_spinlock);
1043
- freez((void *)filename);
1044
- freez((void *)function);
1045
- freez((void *)message);
1046
- freez((void *)errno_str);
1047
- freez((void *)stack_trace);
1048
- return;
1049
- }
1050
-
1051
- exit_initiated_add(EXIT_REASON_FATAL);
1052
- strncpyz(session_status.fatal.thread, nd_thread_tag(), sizeof(session_status.fatal.thread) - 1);
1053
-
1054
- session_status.fatal.filename = filename;
1055
- freez((char *)session_status.fatal.function); // it may have a startup step
1056
- session_status.fatal.function = function;
1057
- session_status.fatal.message = message;
1058
- session_status.fatal.errno_str = errno_str;
1059
- strncpyz(session_status.fatal.stack_trace, stack_trace, sizeof(session_status.fatal.stack_trace) - 1);
1060
- freez((char *)stack_trace);
1061
- session_status.fatal.line = line;
1062
-
1063
- spinlock_unlock(&dsf_spinlock);
1064
-
1065
- daemon_status_file_save(&session_status, false);
1066
-}
src/daemon/daemon-status-file.h
+12
-4
@@ -25,6 +25,9 @@ typedef enum {
25
ENUM_STR_DEFINE_FUNCTIONS_EXTERN(DAEMON_OS_TYPE);
26
27
typedef struct daemon_status_file {
28
+ SPINLOCK spinlock;
29
+ uint32_t v; // the version of the status file
30
+
31
char version[32]; // the netdata version
32
DAEMON_STATUS status; // the daemon status
33
EXIT_REASON exit_reason; // the exit reason (maybe empty)
@@ -43,7 +46,9 @@ typedef struct daemon_status_file {
46
ND_UUID claim_id; // the Netdata Cloud claim id of the agent
47
48
struct {
49
+ usec_t init_started_ut;
50
time_t init;
51
+ usec_t exit_started_ut;
52
time_t exit;
53
} timings;
54
@@ -62,6 +67,7 @@ typedef struct daemon_status_file {
67
bool read_system_info;
68
69
struct {
70
+ SPINLOCK spinlock;
71
long line;
72
const char *filename;
73
const char *function;
@@ -72,9 +78,12 @@ typedef struct daemon_status_file {
78
} fatal;
79
80
struct {
75
- XXH64_hash_t hash;
76
- usec_t timestamp_ut;
77
- } dedup[20];
81
+ SPINLOCK spinlock;
82
+ struct {
83
+ XXH64_hash_t hash;
84
+ usec_t timestamp_ut;
85
+ } slot[20];
86
+ } dedup;
87
} DAEMON_STATUS_FILE;
88
89
// loads the last status saved
@@ -82,7 +91,6 @@ DAEMON_STATUS_FILE daemon_status_file_load(void);
91
92
// saves the current status
93
void daemon_status_file_update_status(DAEMON_STATUS status);
85
-void daemon_status_file_exit_reason_save(EXIT_REASON reason);
94
void daemon_status_file_deadly_signal_received(EXIT_REASON reason);
95
96
// check for a crash
src/daemon/main.c
+2
@@ -902,6 +902,8 @@ int netdata_main(int argc, char **argv) {
902
delta_startup_time("web server sockets");
903
if(web_server_mode != WEB_SERVER_MODE_NONE) {
904
if (!api_listen_sockets_setup()) {
905
+ exit_initiated_add(EXIT_REASON_ALREADY_RUNNING);
906
+ daemon_status_file_update_status(DAEMON_STATUS_NONE);
907
fatal("Cannot setup listen port(s). Is Netdata already running?");
908
exit(1);
909
}
src/libnetdata/common.h
+13
-2
@@ -426,8 +426,19 @@ typedef uint32_t uid_t;
426
427
// --------------------------------------------------------------------------------------------------------------------
428
429
-#define FUNCTION_RUN_ONCE() { static bool __run_once = false; if(__run_once) return; __run_once = true; }
430
-#define FUNCTION_RUN_ONCE_RET(ret) { static bool __run_once = false; if(__run_once) return (ret); __run_once = true; }
429
+#define FUNCTION_RUN_ONCE() { \
430
+ static bool __run_once = false; \
431
+ if (!__sync_bool_compare_and_swap(&__run_once, false, true)) { \
432
+ return; \
433
+ } \
434
+}
435
+
436
+#define FUNCTION_RUN_ONCE_RET(ret) { \
437
+ static bool __run_once = false; \
438
+ if (!__sync_bool_compare_and_swap(&__run_once, false, true)) { \
439
+ return (ret); \
440
+ } \
441
+}
442
443
// --------------------------------------------------------------------------------------------------------------------
444
src/libnetdata/exit/exit_initiated.c
+1
@@ -10,6 +10,7 @@ ENUM_STR_MAP_DEFINE(EXIT_REASON) = {
10
{ EXIT_REASON_SIGFPE, "signal-floating-point-exception"},
11
{ EXIT_REASON_SIGILL, "signal-illegal-instruction"},
12
{ EXIT_REASON_OUT_OF_MEMORY, "out-of-memory"},
13
+ { EXIT_REASON_ALREADY_RUNNING, "already-running"},
14
15
{ EXIT_REASON_FATAL, "fatal"},
16
src/libnetdata/exit/exit_initiated.h
+10
-9
@@ -15,28 +15,29 @@ typedef enum {
15
EXIT_REASON_SIGFPE = (1 << 2),
16
EXIT_REASON_SIGILL = (1 << 3),
17
EXIT_REASON_OUT_OF_MEMORY = (1 << 4),
18
+ EXIT_REASON_ALREADY_RUNNING = (1 << 5),
19
20
// abnormal termination via a fatal message
20
- EXIT_REASON_FATAL = (1 << 5), // a fatal message
21
+ EXIT_REASON_FATAL = (1 << 6), // a fatal message
22
23
// normal termination via APIs
23
- EXIT_REASON_API_QUIT = (1 << 6), // developer only
24
- EXIT_REASON_CMD_EXIT = (1 << 7), // netdatacli
24
+ EXIT_REASON_API_QUIT = (1 << 7), // developer only
25
+ EXIT_REASON_CMD_EXIT = (1 << 8), // netdatacli
26
27
// signals - normal termination
27
- EXIT_REASON_SIGQUIT = (1 << 8), // rare, but graceful
28
- EXIT_REASON_SIGTERM = (1 << 9), // received on Linux, FreeBSD, MacOS
29
- EXIT_REASON_SIGINT = (1 << 10), // received on Windows on normal termination
28
+ EXIT_REASON_SIGQUIT = (1 << 9), // rare, but graceful
29
+ EXIT_REASON_SIGTERM = (1 << 10), // received on Linux, FreeBSD, MacOS
30
+ EXIT_REASON_SIGINT = (1 << 11), // received on Windows on normal termination
31
32
// windows specific, service stop
32
- EXIT_REASON_SERVICE_STOP = (1 << 11),
33
+ EXIT_REASON_SERVICE_STOP = (1 << 12),
34
35
// automatically detect when exit_initiated_set() is called
36
// supports Linux, FreeBSD, MacOS, Windows
36
- EXIT_REASON_SYSTEM_SHUTDOWN = (1 << 12),
37
+ EXIT_REASON_SYSTEM_SHUTDOWN = (1 << 13),
38
39
// netdata update
39
- EXIT_REASON_UPDATE = (1 << 13),
40
+ EXIT_REASON_UPDATE = (1 << 14),
41
} EXIT_REASON;
42
43
#define EXIT_REASON_NORMAL (EXIT_REASON_SIGINT|EXIT_REASON_SIGTERM|EXIT_REASON_SIGQUIT|EXIT_REASON_API_QUIT|EXIT_REASON_CMD_EXIT|EXIT_REASON_SERVICE_STOP|EXIT_REASON_SYSTEM_SHUTDOWN|EXIT_REASON_UPDATE)
src/libnetdata/log/nd_log-stacktrace.c
+7
-4
@@ -54,13 +54,16 @@ void capture_stack_trace(BUFFER *wb) {
54
messages = backtrace_symbols(array, size);
55
56
if (messages == NULL) {
57
- // Failed to get backtrace symbols
57
+ buffer_strcat(wb, "backtrace failed to get symbols");
58
return;
59
}
60
61
- // Format the stack trace
62
- for (i = 0; i < size ; i++)
63
- buffer_sprintf(wb, "#%d %s\n", i, messages[i]);
61
+ // Format the stack trace (removing the address part)
62
+ for (i = 0; i < size; i++) {
63
+ char *p = strstr(messages[i], " [");
64
+ size_t len = p ? (size_t)(p - messages[i]) : strlen(messages[i]);
65
+ buffer_sprintf(wb, "#%d %.*s\n", i, (int)len, messages[i]);
66
+ }
67
68
free(messages);
69
}