1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "common.h"
4
+#include "daemon-status-file.h"
5
+
6
+#include <curl/curl.h>
7
+#include <openssl/evp.h>
8
+#include <openssl/pem.h>
9
+#include <openssl/err.h>
10
+
11
+#define STATUS_FILENAME "status-netdata.json"
12
+#define STATUS_FILENAME_TMP "status-netdata.json.tmp"
13
+
14
+ENUM_STR_MAP_DEFINE(DAEMON_STATUS) = {
15
+ { DAEMON_STATUS_NONE, "none"},
16
+ { DAEMON_STATUS_INITIALIZING, "initializing"},
17
+ { DAEMON_STATUS_RUNNING, "running"},
18
+ { DAEMON_STATUS_EXITING, "exiting"},
19
+ { DAEMON_STATUS_EXITED, "exited"},
20
+
21
+ // terminator
22
+ { 0, NULL },
23
+};
24
+ENUM_STR_DEFINE_FUNCTIONS(DAEMON_STATUS, DAEMON_STATUS_NONE, "none");
25
+
26
+ENUM_STR_MAP_DEFINE(DAEMON_OS_TYPE) = {
27
+ {DAEMON_OS_TYPE_UNKNOWN, "unknown"},
28
+ {DAEMON_OS_TYPE_LINUX, "linux"},
29
+ {DAEMON_OS_TYPE_FREEBSD, "freebsd"},
30
+ {DAEMON_OS_TYPE_MACOS, "macos"},
31
+ {DAEMON_OS_TYPE_WINDOWS, "windows"},
32
+
33
+ // terminator
34
+ { 0, NULL },
35
+};
36
+ENUM_STR_DEFINE_FUNCTIONS(DAEMON_OS_TYPE, DAEMON_OS_TYPE_UNKNOWN, "unknown");
37
+
38
+static DAEMON_STATUS_FILE last_session_status = { 0 };
39
+static DAEMON_STATUS_FILE session_status = { 0 };
40
+
41
+// --------------------------------------------------------------------------------------------------------------------
42
+// json generation
43
+
44
+static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
45
+ buffer_json_member_add_datetime_rfc3339(wb, "@timestamp", ds->timestamp_ut, true); // ECS
46
+ buffer_json_member_add_uint64(wb, "version", 1); // custom
47
+
48
+ buffer_json_member_add_object(wb, "agent"); // ECS
49
+ {
50
+ buffer_json_member_add_uuid(wb, "id", ds->host_id.uuid); // ECS
51
+ buffer_json_member_add_uuid_compact(wb, "ephemeral_id", ds->invocation.uuid); // ECS
52
+ buffer_json_member_add_string(wb, "version", ds->version); // ECS
53
+
54
+ buffer_json_member_add_time_t(wb, "uptime", ds->uptime); // custom
55
+
56
+ buffer_json_member_add_uuid(wb, "ND_node_id", ds->node_id.uuid); // custom
57
+ buffer_json_member_add_uuid(wb, "ND_claim_id", ds->claim_id.uuid); // custom
58
+
59
+ ND_PROFILE_2json(wb, "ND_profile", ds->profile); // custom
60
+ buffer_json_member_add_string(wb, "ND_status", DAEMON_STATUS_2str(ds->status)); // custom
61
+ EXIT_REASON_2json(wb, "ND_exit_reason", ds->exit_reason); // custom
62
+
63
+ buffer_json_member_add_object(wb, "ND_timings"); // custom
64
+ {
65
+ buffer_json_member_add_time_t(wb, "init", ds->timings.init);
66
+ buffer_json_member_add_time_t(wb, "exit", ds->timings.exit);
67
+ }
68
+ buffer_json_object_close(wb);
69
+ }
70
+ buffer_json_object_close(wb);
71
+
72
+ buffer_json_member_add_object(wb, "host"); // ECS
73
+ {
74
+ buffer_json_member_add_object(wb, "boot"); // ECS
75
+ {
76
+ buffer_json_member_add_uuid(wb, "id", ds->boot_id.uuid); // ECS
77
+ }
78
+ buffer_json_object_close(wb);
79
+ buffer_json_member_add_time_t(wb, "uptime", ds->boottime); // ECS
80
+
81
+ buffer_json_member_add_object(wb, "memory"); // custom
82
+ if(OS_SYSTEM_MEMORY_OK(ds->memory)) {
83
+ buffer_json_member_add_uint64(wb, "total", ds->memory.ram_total_bytes);
84
+ buffer_json_member_add_uint64(wb, "free", ds->memory.ram_available_bytes);
85
+ }
86
+ buffer_json_object_close(wb);
87
+
88
+ buffer_json_member_add_object(wb, "disk"); // ECS
89
+ {
90
+ buffer_json_member_add_object(wb, "db");
91
+ if (OS_SYSTEM_DISK_SPACE_OK(ds->var_cache)) {
92
+ buffer_json_member_add_uint64(wb, "total", ds->var_cache.total_bytes);
93
+ buffer_json_member_add_uint64(wb, "free", ds->var_cache.free_bytes);
94
+ buffer_json_member_add_uint64(wb, "inodes_total", ds->var_cache.total_inodes);
95
+ buffer_json_member_add_uint64(wb, "inodes_free", ds->var_cache.free_inodes);
96
+ buffer_json_member_add_boolean(wb, "read_only", ds->var_cache.is_read_only);
97
+ }
98
+ buffer_json_object_close(wb);
99
+ }
100
+ buffer_json_object_close(wb);
101
+ }
102
+ buffer_json_object_close(wb);
103
+
104
+ buffer_json_member_add_object(wb, "os"); // ECS
105
+ {
106
+ buffer_json_member_add_string(wb, "type", DAEMON_OS_TYPE_2str(ds->os_type)); // ECS
107
+ }
108
+ buffer_json_object_close(wb);
109
+
110
+ buffer_json_member_add_object(wb, "fatal");
111
+ {
112
+ buffer_json_member_add_uint64(wb, "line", ds->fatal.line);
113
+ buffer_json_member_add_string_or_empty(wb, "filename", ds->fatal.filename);
114
+ buffer_json_member_add_string_or_empty(wb, "function", ds->fatal.function);
115
+ buffer_json_member_add_string_or_empty(wb, "message", ds->fatal.message);
116
+ buffer_json_member_add_string_or_empty(wb, "stack_trace", ds->fatal.stack_trace);
117
+ }
118
+ buffer_json_object_close(wb);
119
+}
120
+
121
+// --------------------------------------------------------------------------------------------------------------------
122
+// json parsing
123
+
124
+static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *error) {
125
+ char path[1024]; path[0] = '\0';
126
+
127
+ DAEMON_STATUS_FILE *ds = data;
128
+ char datetime[RFC3339_MAX_LENGTH]; datetime[0] = '\0';
129
+
130
+ // change management, version to know which fields to expect
131
+ uint64_t version = 0;
132
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "version", version, error, true);
133
+
134
+ bool required = false; // allow missing fields and values
135
+
136
+ // Parse timestamp
137
+ JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", datetime, error, required);
138
+ if(datetime[0])
139
+ ds->timestamp_ut = rfc3339_parse_ut(datetime, NULL);
140
+
141
+ // Parse agent object
142
+ JSONC_PARSE_SUBOBJECT(jobj, path, "agent", error, required, {
143
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "id", ds->host_id.uuid, error, required);
144
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "ephemeral_id", ds->invocation.uuid, error, required);
145
+ JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "version", ds->version, error, required);
146
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "uptime", ds->uptime, error, required);
147
+ JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "ND_profile", ND_PROFILE_2id_one, ds->profile, error, required);
148
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "ND_status", DAEMON_STATUS_2id, ds->status, error, required);
149
+ JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "ND_exit_reason", EXIT_REASON_2id_one, ds->exit_reason, error, required);
150
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "ND_node_id", ds->node_id.uuid, error, required);
151
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "ND_claim_id", ds->claim_id.uuid, error, required);
152
+
153
+ JSONC_PARSE_SUBOBJECT(jobj, path, "ND_timings", error, required, {
154
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "init", ds->timings.init, error, required);
155
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "exit", ds->timings.exit, error, required);
156
+ });
157
+ });
158
+
159
+ // Parse host object
160
+ JSONC_PARSE_SUBOBJECT(jobj, path, "host", error, required, {
161
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "uptime", ds->boottime, error, required);
162
+
163
+ JSONC_PARSE_SUBOBJECT(jobj, path, "boot", error, required, {
164
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "id", ds->boot_id.uuid, error, required);
165
+ });
166
+
167
+ JSONC_PARSE_SUBOBJECT(jobj, path, "memory", error, required, {
168
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "total", ds->memory.ram_total_bytes, error, required);
169
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "free", ds->memory.ram_available_bytes, error, required);
170
+ if(!OS_SYSTEM_MEMORY_OK(ds->memory))
171
+ ds->memory = OS_SYSTEM_MEMORY_EMPTY;
172
+ });
173
+
174
+ JSONC_PARSE_SUBOBJECT(jobj, path, "disk", error, required, {
175
+ JSONC_PARSE_SUBOBJECT(jobj, path, "db", error, required, {
176
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "total", ds->var_cache.total_bytes, error, false);
177
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "free", ds->var_cache.free_bytes, error, false);
178
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "inodes_total", ds->var_cache.total_inodes, error, false);
179
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "inodes_free", ds->var_cache.free_inodes, error, false);
180
+ JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, "read_only", ds->var_cache.is_read_only, error, false);
181
+ if(!OS_SYSTEM_DISK_SPACE_OK(ds->var_cache))
182
+ ds->var_cache = OS_SYSTEM_DISK_SPACE_EMPTY;
183
+ });
184
+ });
185
+ });
186
+
187
+ // Parse os object
188
+ JSONC_PARSE_SUBOBJECT(jobj, path, "os", error, required, {
189
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "type", DAEMON_OS_TYPE_2id, ds->os_type, error, required);
190
+ });
191
+
192
+ // Parse fatal object
193
+ JSONC_PARSE_SUBOBJECT(jobj, path, "fatal", error, required, {
194
+ JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "filename", ds->fatal.filename, error, required);
195
+ JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "function", ds->fatal.function, error, required);
196
+ JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "message", ds->fatal.message, error, required);
197
+ JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "stack_trace", ds->fatal.stack_trace, error, required);
198
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "line", ds->fatal.line, error, required);
199
+ });
200
+
201
+ return true;
202
+}
203
+
204
+// --------------------------------------------------------------------------------------------------------------------
205
+// get the current status
206
+
207
+static DAEMON_STATUS_FILE daemon_status_file_get(DAEMON_STATUS status) {
208
+ usec_t now_ut = now_realtime_usec();
209
+
210
+#if defined(OS_LINUX)
211
+ session_status.os_type = DAEMON_OS_TYPE_LINUX;
212
+#elif defined(OS_FREEBSD)
213
+ session_status.os_type = DAEMON_OS_TYPE_FREEBSD;
214
+#elif defined(OS_MACOS)
215
+ session_status.os_type = DAEMON_OS_TYPE_MACOS;
216
+#elif defined(OS_WINDOWS)
217
+ session_status.os_type = DAEMON_OS_TYPE_WINDOWS;
218
+#endif
219
+
220
+ if(session_status.status == DAEMON_STATUS_INITIALIZING && status == DAEMON_STATUS_RUNNING)
221
+ session_status.timings.init = (time_t)((now_ut - session_status.timestamp_ut + USEC_PER_SEC/2) / USEC_PER_SEC);
222
+
223
+ if(session_status.status == DAEMON_STATUS_EXITING && status == DAEMON_STATUS_EXITED)
224
+ session_status.timings.exit = (time_t)((now_ut - session_status.timestamp_ut + USEC_PER_SEC/2) / USEC_PER_SEC);
225
+
226
+ strncpyz(session_status.version, NETDATA_VERSION, sizeof(session_status.version) - 1);
227
+
228
+ session_status.boot_id = os_boot_id();
229
+ if(!UUIDeq(session_status.boot_id, last_session_status.boot_id) && os_boot_ids_match(session_status.boot_id, last_session_status.boot_id)) {
230
+ // there is a slight difference in boot_id, but it is still the same boot
231
+ // copy the last boot_id
232
+ session_status.boot_id = last_session_status.boot_id;
233
+ }
234
+
235
+ session_status.boottime = now_boottime_sec();
236
+ session_status.uptime = now_realtime_sec() - netdata_start_time;
237
+ session_status.timestamp_ut = now_ut;
238
+ session_status.invocation = nd_log_get_invocation_id();
239
+
240
+ session_status.claim_id = claim_id_get_uuid();
241
+
242
+ if(localhost) {
243
+ session_status.host_id = localhost->host_id;
244
+ session_status.node_id = localhost->node_id;
245
+ }
246
+ else if(!UUIDiszero(last_session_status.host_id))
247
+ session_status.host_id = last_session_status.host_id;
248
+ else {
249
+ const char *machine_guid = registry_get_this_machine_guid();
250
+ if(machine_guid && *machine_guid) {
251
+ if (uuid_parse_flexi(machine_guid, session_status.host_id.uuid) != 0)
252
+ session_status.host_id = UUID_ZERO;
253
+ }
254
+ else
255
+ session_status.host_id = UUID_ZERO;
256
+ }
257
+
258
+ if(UUIDiszero(session_status.claim_id))
259
+ session_status.claim_id = last_session_status.claim_id;
260
+ if(UUIDiszero(session_status.node_id))
261
+ session_status.node_id = last_session_status.node_id;
262
+ if(UUIDiszero(session_status.host_id))
263
+ session_status.host_id = last_session_status.host_id;
264
+
265
+ session_status.exit_reason = exit_initiated;
266
+ session_status.profile = nd_profile_detect_and_configure(false);
267
+
268
+ if(status != DAEMON_STATUS_NONE)
269
+ session_status.status = status;
270
+
271
+ session_status.memory = os_system_memory(true);
272
+ session_status.var_cache = os_disk_space(netdata_configured_cache_dir);
273
+
274
+ return session_status;
275
+}
276
+
277
+// --------------------------------------------------------------------------------------------------------------------
278
+// file helpers
279
+
280
+// List of fallback directories to try
281
+static const char *status_file_fallbacks[] = {
282
+ "/tmp",
283
+ "/run",
284
+ "/var/run",
285
+};
286
+
287
+static bool check_status_file(const char *directory, char *filename, size_t filename_size, time_t *mtime) {
288
+ if(!directory || !*directory)
289
+ return false;
290
+
291
+ snprintfz(filename, filename_size, "%s/%s", directory, STATUS_FILENAME);
292
+
293
+ // Get file metadata
294
+ OS_FILE_METADATA metadata = os_get_file_metadata(filename);
295
+ if (!OS_FILE_METADATA_OK(metadata))
296
+ return false;
297
+
298
+ *mtime = metadata.modified_time;
299
+ return true;
300
+}
301
+
302
+// --------------------------------------------------------------------------------------------------------------------
303
+// load a saved status
304
+
305
+static bool load_status_file(const char *filename, DAEMON_STATUS_FILE *status) {
306
+ FILE *fp = fopen(filename, "r");
307
+ if (!fp)
308
+ return false;
309
+
310
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
311
+ CLEAN_BUFFER *error = buffer_create(0, NULL);
312
+
313
+ // Get file size
314
+ fseek(fp, 0, SEEK_END);
315
+ long file_size = ftell(fp);
316
+ fseek(fp, 0, SEEK_SET);
317
+
318
+ // Read the file
319
+ buffer_need_bytes(wb, file_size + 1);
320
+ size_t read_bytes = fread(wb->buffer, 1, file_size, fp);
321
+ fclose(fp);
322
+
323
+ if (read_bytes == 0)
324
+ return false;
325
+
326
+ wb->buffer[read_bytes] = '\0';
327
+ wb->len = read_bytes;
328
+
329
+ // Parse the JSON
330
+ return json_parse_payload_or_error(wb, error, daemon_status_file_from_json, status) == HTTP_RESP_OK;
331
+}
332
+
333
+DAEMON_STATUS_FILE daemon_status_file_load(void) {
334
+ DAEMON_STATUS_FILE status = {0};
335
+ char newest_filename[FILENAME_MAX] = "";
336
+ char current_filename[FILENAME_MAX];
337
+ time_t newest_mtime = 0, current_mtime;
338
+
339
+ // Check primary directory first
340
+ if(check_status_file(netdata_configured_cache_dir, current_filename, sizeof(current_filename), ¤t_mtime)) {
341
+ strncpyz(newest_filename, current_filename, sizeof(newest_filename) - 1);
342
+ newest_mtime = current_mtime;
343
+ }
344
+
345
+ // Check each fallback location
346
+ for(size_t i = 0; i < _countof(status_file_fallbacks); i++) {
347
+ if(check_status_file(status_file_fallbacks[i], current_filename, sizeof(current_filename), ¤t_mtime) &&
348
+ (!*newest_filename || current_mtime > newest_mtime)) {
349
+ strncpyz(newest_filename, current_filename, sizeof(newest_filename) - 1);
350
+ newest_mtime = current_mtime;
351
+ }
352
+ }
353
+
354
+ // Load the newest file found
355
+ if(*newest_filename) {
356
+ if(!load_status_file(newest_filename, &status))
357
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to load newest status file: %s", newest_filename);
358
+ }
359
+ else
360
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot find a status file in any location");
361
+
362
+ return status;
363
+}
364
+
365
+// --------------------------------------------------------------------------------------------------------------------
366
+// save the current status
367
+
368
+static bool save_status_file(const char *directory, const char *content, size_t content_size) {
369
+ if(!directory || !*directory)
370
+ return false;
371
+
372
+ char filename[FILENAME_MAX];
373
+ char temp_filename[FILENAME_MAX];
374
+
375
+ snprintfz(filename, sizeof(filename), "%s/%s", directory, STATUS_FILENAME);
376
+ snprintfz(temp_filename, sizeof(temp_filename), "%s/%s", directory, STATUS_FILENAME_TMP);
377
+
378
+ FILE *fp = fopen(temp_filename, "w");
379
+ if (!fp)
380
+ return false;
381
+
382
+ bool ok = fwrite(content, 1, content_size, fp) == content_size;
383
+ fclose(fp);
384
+
385
+ if (!ok) {
386
+ unlink(filename);
387
+ unlink(temp_filename);
388
+ return false;
389
+ }
390
+
391
+ if (chmod(temp_filename, 0664) != 0) {
392
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot set permissions on status file '%s'", temp_filename);
393
+ unlink(temp_filename);
394
+ return false;
395
+ }
396
+
397
+ if (rename(temp_filename, filename) != 0) {
398
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot rename status file '%s' to '%s'", temp_filename, filename);
399
+ unlink(temp_filename);
400
+ return false;
401
+ }
402
+
403
+ return true;
404
+}
405
+
406
+void daemon_status_file_save(DAEMON_STATUS status) {
407
+ static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
408
+ spinlock_lock(&spinlock);
409
+
410
+ // Get current status
411
+ DAEMON_STATUS_FILE ds = daemon_status_file_get(status);
412
+
413
+ // Prepare JSON content
414
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
415
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
416
+ daemon_status_file_to_json(wb, &ds);
417
+ buffer_json_finalize(wb);
418
+
419
+ const char *content = buffer_tostring(wb);
420
+ size_t content_size = buffer_strlen(wb);
421
+
422
+ // Try primary directory first
423
+ bool saved = false;
424
+ if (save_status_file(netdata_configured_cache_dir, content, content_size))
425
+ saved = true;
426
+ else {
427
+ nd_log(NDLS_DAEMON, NDLP_DEBUG, "Failed to save status file in primary directory %s",
428
+ netdata_configured_cache_dir);
429
+
430
+ // Try each fallback directory until successful
431
+ for(size_t i = 0; i < _countof(status_file_fallbacks); i++) {
432
+ if(save_status_file(status_file_fallbacks[i], content, content_size)) {
433
+ nd_log(NDLS_DAEMON, NDLP_DEBUG, "Saved status file in fallback %s", status_file_fallbacks[i]);
434
+ saved = true;
435
+ break;
436
+ }
437
+ }
438
+ }
439
+
440
+ if (!saved)
441
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to save status file in any location");
442
+
443
+ spinlock_unlock(&spinlock);
444
+}
445
+
446
+// --------------------------------------------------------------------------------------------------------------------
447
+// POST the last status to agent-events
448
+
449
+struct post_status_file_thread_data {
450
+ const char *cause;
451
+ const char *msg;
452
+ ND_LOG_FIELD_PRIORITY priority;
453
+ DAEMON_STATUS_FILE status;
454
+};
455
+
456
+void post_status_file(struct post_status_file_thread_data *d) {
457
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
458
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
459
+ buffer_json_member_add_string(wb, "exit_cause", d->cause); // custom
460
+ buffer_json_member_add_string(wb, "message", d->msg); // ECS
461
+ buffer_json_member_add_uint64(wb, "priority", d->priority); // custom
462
+ daemon_status_file_to_json(wb, &d->status);
463
+ buffer_json_finalize(wb);
464
+
465
+ const char *json_data = buffer_tostring(wb);
466
+
467
+ CURL *curl = curl_easy_init();
468
+ if(!curl)
469
+ return;
470
+
471
+ curl_easy_setopt(curl, CURLOPT_URL, "https://agent-events.netdata.cloud/agent-events");
472
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
473
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);
474
+ struct curl_slist *headers = NULL;
475
+ headers = curl_slist_append(headers, "Content-Type: application/json");
476
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
477
+
478
+ CURLcode rc = curl_easy_perform(curl);
479
+ (void)rc;
480
+
481
+ curl_easy_cleanup(curl);
482
+ curl_slist_free_all(headers);
483
+}
484
+
485
+void *post_status_file_thread(void *ptr) {
486
+ struct post_status_file_thread_data *d = (struct post_status_file_thread_data *)ptr;
487
+ post_status_file(d);
488
+ freez((void *)d->cause);
489
+ freez((void *)d->msg);
490
+ freez(d);
491
+ return NULL;
492
+}
493
+
494
+// --------------------------------------------------------------------------------------------------------------------
495
+// check last status on startup and post crash report
496
+
497
+void daemon_status_file_check_crash(void) {
498
+ last_session_status = daemon_status_file_load();
499
+ daemon_status_file_save(DAEMON_STATUS_INITIALIZING);
500
+ ND_LOG_FIELD_PRIORITY pri = NDLP_NOTICE;
501
+
502
+ bool new_version = strcmp(last_session_status.version, session_status.version) != 0;
503
+ bool post_crash_report = false;
504
+ bool disable_crash_report = false;
505
+ bool dump_json = true;
506
+ const char *msg, *cause;
507
+ switch(last_session_status.status) {
508
+ default:
509
+ case DAEMON_STATUS_NONE:
510
+ // probably a previous version of netdata was running
511
+ cause = "no last status";
512
+ msg = "No status found for the previous Netdata session";
513
+ disable_crash_report = true;
514
+ break;
515
+
516
+ case DAEMON_STATUS_EXITED:
517
+ if(last_session_status.exit_reason == EXIT_REASON_NONE) {
518
+ cause = "exit no reason";
519
+ msg = "Netdata was last stopped gracefully (no exit reason set)";
520
+ if(!last_session_status.timestamp_ut)
521
+ dump_json = false;
522
+ }
523
+ else if(!is_exit_reason_normal(last_session_status.exit_reason)) {
524
+ cause = "exit on fatal";
525
+ msg = "Netdata was last stopped gracefully (encountered an error)";
526
+ pri = NDLP_ERR;
527
+ post_crash_report = true;
528
+ }
529
+ else if(last_session_status.exit_reason & EXIT_REASON_SYSTEM_SHUTDOWN) {
530
+ cause = "exit on system shutdown";
531
+ msg = "Netdata has gracefully stopped due to system shutdown";
532
+ }
533
+ else if(last_session_status.exit_reason & EXIT_REASON_UPDATE) {
534
+ cause = "exit to update";
535
+ msg = "Netdata has gracefully restarted to update to a new version";
536
+ }
537
+ else if(new_version) {
538
+ cause = "exit and updated";
539
+ msg = "Netdata has gracefully restarted and updated to a new version";
540
+ last_session_status.exit_reason |= EXIT_REASON_UPDATE;
541
+ }
542
+ else {
543
+ cause = "exit instructed";
544
+ msg = "Netdata was last stopped gracefully (instructed to do so)";
545
+ }
546
+ break;
547
+
548
+ case DAEMON_STATUS_INITIALIZING:
549
+ cause = "crashed on start";
550
+ msg = "Netdata was last killed/crashed while starting";
551
+ pri = NDLP_ERR;
552
+ post_crash_report = true;
553
+ break;
554
+
555
+ case DAEMON_STATUS_EXITING:
556
+ if(!is_exit_reason_normal(last_session_status.exit_reason)) {
557
+ cause = "crashed on fatal";
558
+ msg = "Netdata was last killed/crashed while exiting after encountering an error";
559
+ }
560
+ else if(last_session_status.exit_reason & EXIT_REASON_SYSTEM_SHUTDOWN) {
561
+ cause = "crashed on system shutdown";
562
+ msg = "Netdata was last killed/crashed while exiting due to system shutdown";
563
+ }
564
+ else if(new_version || (last_session_status.exit_reason & EXIT_REASON_UPDATE)) {
565
+ cause = "crashed on update";
566
+ msg = "Netdata was last killed/crashed while exiting to update to a new version";
567
+ }
568
+ else {
569
+ cause = "crashed on exit";
570
+ msg = "Netdata was last killed/crashed while exiting (instructed to do so)";
571
+ }
572
+ pri = NDLP_ERR;
573
+ post_crash_report = true;
574
+ break;
575
+
576
+ case DAEMON_STATUS_RUNNING: {
577
+ if (!UUIDeq(session_status.boot_id, last_session_status.boot_id)) {
578
+ cause = "abnormal power off";
579
+ msg = "The system was abnormally powered off while Netdata was running";
580
+ pri = NDLP_CRIT;
581
+ }
582
+ else {
583
+ cause = "killed hard";
584
+ msg = "Netdata was last killed/crashed while operating normally";
585
+ pri = NDLP_CRIT;
586
+ post_crash_report = true;
587
+ }
588
+ break;
589
+ }
590
+ }
591
+
592
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
593
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
594
+ if(dump_json)
595
+ daemon_status_file_to_json(wb, &last_session_status);
596
+ buffer_json_finalize(wb);
597
+
598
+ ND_LOG_STACK lgs[] = {
599
+ ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &netdata_startup_msgid),
600
+ ND_LOG_FIELD_END(),
601
+ };
602
+ ND_LOG_STACK_PUSH(lgs);
603
+
604
+ nd_log(NDLS_DAEMON, pri,
605
+ "Netdata Agent version '%s' is starting...\n"
606
+ "Last exit status: %s (%s):\n\n%s",
607
+ NETDATA_VERSION, msg, cause, buffer_tostring(wb));
608
+
609
+ if(!disable_crash_report && (analytics_check_enabled() || post_crash_report)) {
610
+ netdata_conf_ssl();
611
+
612
+ struct post_status_file_thread_data *d = calloc(1, sizeof(*d));
613
+ d->cause = strdupz(cause);
614
+ d->msg = strdupz(msg);
615
+ d->status = last_session_status;
616
+ d->priority = pri;
617
+ nd_thread_create("post_status_file", NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_DEFAULT, post_status_file_thread, d);
618
+ }
619
+}
620
+
621
+bool daemon_status_file_has_last_crashed(void) {
622
+ return last_session_status.status != DAEMON_STATUS_EXITED || !is_exit_reason_normal(last_session_status.exit_reason);
623
+}
624
+
625
+bool daemon_status_file_was_incomplete_shutdown(void) {
626
+ return last_session_status.status == DAEMON_STATUS_EXITING;
627
+}
628
+
629
+// --------------------------------------------------------------------------------------------------------------------
630
+// ng_log() hook for receiving fatal message information
631
+
632
+void daemon_status_file_register_fatal(const char *filename, const char *function, const char *message, const char *stack_trace, long line) {
633
+ static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
634
+ spinlock_lock(&spinlock);
635
+
636
+ if(session_status.fatal.filename || session_status.fatal.function || session_status.fatal.message || session_status.fatal.stack_trace) {
637
+ spinlock_unlock(&spinlock);
638
+ freez((void *)filename);
639
+ freez((void *)function);
640
+ freez((void *)message);
641
+ freez((void *)stack_trace);
642
+ return;
643
+ }
644
+
645
+ session_status.fatal.filename = filename;
646
+ session_status.fatal.function = function;
647
+ session_status.fatal.message = message;
648
+ session_status.fatal.stack_trace = stack_trace;
649
+ session_status.fatal.line = line;
650
+
651
+ spinlock_unlock(&spinlock);
652
+}