master
c 222 lines 6.83 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "status-file-dedup.h"
4 #include "status-file-io.h"
5 #include "status-file-dmi.h"
6
7 #define DEDUP_FILENAME "dedup-netdata.dat"
8 #define DEDUP_VERSION 1
9 #define DEDUP_MAGIC 0x1DEDA9F17EDA7150 // 1(x) DEDUPFILEDAT (v)1 50(entries)
10
11 #define REPORT_EVENTS_EVERY (86400 - 3600) // -1 hour to tolerate cron randomness
12
13 typedef struct {
14 uint64_t magic;
15 size_t v;
16 uint64_t hash;
17 struct {
18 bool sentry;
19 uint64_t hash;
20 usec_t timestamp_ut;
21 } slot[50];
22 } DAEMON_STATUS_DEDUP;
23
24 static DAEMON_STATUS_DEDUP dedup = { 0 };
25
26 static void stack_trace_anonymize(char *s) {
27 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
28 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
29
30 char *p = s;
31 while (*p && (p = strstr(p, "0x"))) {
32 p[1] = '0';
33 p += 2;
34 while(isxdigit((uint8_t)*p)) *p++ = '0';
35 }
36 }
37
38 uint64_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg, const char *cause) {
39 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
40 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
41
42 struct {
43 uint32_t v;
44 DAEMON_STATUS status;
45 SIGNAL_CODE signal_code;
46 ND_PROFILE profile;
47 EXIT_REASON exit_reason;
48 RRD_DB_MODE db_mode;
49 uint32_t worker_job_id;
50 uint8_t db_tiers;
51 bool kubernetes;
52 bool sentry_available;
53 bool sentry_fatal;
54 ND_MACHINE_GUID host_id;
55 ND_UUID machine_id;
56 long line;
57 char version[sizeof(ds->version)];
58 char filename[sizeof(ds->fatal.filename)];
59 char function[sizeof(ds->fatal.function)];
60 char stack_trace[sizeof(ds->fatal.stack_trace)];
61 char thread[sizeof(ds->fatal.thread)];
62 char msg[128];
63 char cause[32];
64 } to_hash;
65
66 // this is important to remove any random bytes from the structure
67 memset(&to_hash, 0, sizeof(to_hash));
68
69 dsf_acquire(*ds);
70
71 to_hash.v = ds->v;
72 to_hash.status = ds->status;
73 to_hash.signal_code = ds->fatal.signal_code;
74 to_hash.profile = ds->profile;
75 to_hash.exit_reason = ds->exit_reason;
76 to_hash.db_mode = ds->db_mode;
77 to_hash.db_tiers = ds->db_tiers;
78 to_hash.kubernetes = ds->kubernetes;
79 to_hash.sentry_available = ds->sentry_available;
80 to_hash.sentry_fatal = ds->fatal.sentry;
81 to_hash.host_id = ds->host_id;
82 to_hash.machine_id = ds->machine_id;
83 to_hash.worker_job_id = ds->fatal.worker_job_id;
84
85 safecpy(to_hash.version, ds->version);
86 safecpy(to_hash.filename, ds->fatal.filename);
87 safecpy(to_hash.filename, ds->fatal.function);
88 safecpy(to_hash.stack_trace, ds->fatal.stack_trace);
89 safecpy(to_hash.thread, ds->fatal.thread);
90
91 if(msg)
92 safecpy(to_hash.msg, msg);
93
94 if(cause)
95 safecpy(to_hash.cause, cause);
96
97 stack_trace_anonymize(to_hash.stack_trace);
98
99 uint64_t hash = fnv1a_hash_bin64(&to_hash, sizeof(to_hash));
100
101 dsf_release(*ds);
102 return hash;
103 }
104
105 // --------------------------------------------------------------------------------------------------------------------
106 // read and write the dedup hashes
107
108 static bool status_file_dedup_load_and_parse(const char *filename, void *data __maybe_unused) {
109 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
110 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
111
112 int fp = open(filename, O_RDONLY);
113 if(fp == -1)
114 goto failed;
115
116 memset(&dedup, 0, sizeof(dedup));
117 ssize_t r = read(fp, &dedup, sizeof(dedup));
118 close(fp);
119
120 if(r != sizeof(dedup))
121 goto failed;
122
123 if(dedup.magic != DEDUP_MAGIC)
124 goto failed;
125
126 if(dedup.v != DEDUP_VERSION)
127 goto failed;
128
129 uint64_t hash = fnv1a_hash_bin64(&dedup.slot, sizeof(dedup.slot));
130 if(dedup.hash != hash)
131 goto failed;
132
133 return true;
134
135 failed:
136 memset(&dedup, 0, sizeof(dedup));
137 return false;
138 }
139
140 bool daemon_status_dedup_load(void) {
141 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
142 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
143
144 return status_file_io_load(DEDUP_FILENAME, status_file_dedup_load_and_parse, NULL);
145 }
146
147 static bool daemon_status_dedup_save(void) {
148 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
149 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
150
151 dedup.magic = DEDUP_MAGIC;
152 dedup.v = DEDUP_VERSION;
153 dedup.hash = fnv1a_hash_bin64(&dedup.slot, sizeof(dedup.slot));
154 return status_file_io_save(DEDUP_FILENAME, &dedup, sizeof(dedup), false);
155 }
156
157 // --------------------------------------------------------------------------------------------------------------------
158 // deduplication hashes management
159
160 bool dedup_already_posted(DAEMON_STATUS_FILE *ds __maybe_unused, uint64_t hash, bool sentry) {
161 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
162 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
163
164 daemon_status_dedup_load();
165
166 usec_t now_ut = now_realtime_usec();
167
168 for(size_t i = 0; i < _countof(dedup.slot); i++) {
169 if(dedup.slot[i].timestamp_ut == 0)
170 continue;
171
172 if(hash == dedup.slot[i].hash &&
173 sentry == dedup.slot[i].sentry &&
174 now_ut - dedup.slot[i].timestamp_ut < REPORT_EVENTS_EVERY * USEC_PER_SEC) {
175 // we have already posted this crash
176 return true;
177 }
178 }
179
180 return false;
181 }
182
183 void dedup_keep_hash(DAEMON_STATUS_FILE *ds __maybe_unused, uint64_t hash, bool sentry) {
184 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
185 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
186
187 daemon_status_dedup_load();
188
189 // find the same hash
190 for(size_t i = 0; i < _countof(dedup.slot); i++) {
191 if(dedup.slot[i].hash == hash && dedup.slot[i].sentry == sentry) {
192 dedup.slot[i].hash = hash;
193 dedup.slot[i].sentry = sentry;
194 dedup.slot[i].timestamp_ut = now_realtime_usec();
195 goto save;
196 }
197 }
198
199 // find an empty slot
200 for(size_t i = 0; i < _countof(dedup.slot); i++) {
201 if(!dedup.slot[i].hash) {
202 dedup.slot[i].hash = hash;
203 dedup.slot[i].sentry = sentry;
204 dedup.slot[i].timestamp_ut = now_realtime_usec();
205 goto save;
206 }
207 }
208
209 // find the oldest slot
210 size_t store_at_slot = 0;
211 for(size_t i = 1; i < _countof(dedup.slot); i++) {
212 if(dedup.slot[i].timestamp_ut < dedup.slot[store_at_slot].timestamp_ut)
213 store_at_slot = i;
214 }
215
216 dedup.slot[store_at_slot].hash = hash;
217 dedup.slot[store_at_slot].sentry = sentry;
218 dedup.slot[store_at_slot].timestamp_ut = now_realtime_usec();
219
220 save:
221 daemon_status_dedup_save();
222 }