master
c 568 lines 23.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "rrdcontext-internal.h"
4 #include "../sqlite/sqlite_aclk.h"
5
6 // ----------------------------------------------------------------------------
7 // visualizing flags
8
9 struct rrdcontext_reason rrdcontext_reasons[] = {
10 // context related
11 {RRD_FLAG_UPDATE_REASON_TRIGGERED, "triggered transition", 65 * USEC_PER_SEC },
12 {RRD_FLAG_UPDATE_REASON_NEW_OBJECT, "object created", 65 * USEC_PER_SEC },
13 {RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT, "object updated", 65 * USEC_PER_SEC },
14 {RRD_FLAG_UPDATE_REASON_LOAD_SQL, "loaded from sql", 65 * USEC_PER_SEC },
15 {RRD_FLAG_UPDATE_REASON_CHANGED_METADATA, "changed metadata", 65 * USEC_PER_SEC },
16 {RRD_FLAG_UPDATE_REASON_ZERO_RETENTION, "has no retention", 65 * USEC_PER_SEC },
17 {RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T, "updated first_time_t", 65 * USEC_PER_SEC },
18 {RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T, "updated last_time_t", 65 * USEC_PER_SEC },
19 {RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED, "stopped collected", 65 * USEC_PER_SEC },
20 {RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED, "started collected", 5 * USEC_PER_SEC },
21 {RRD_FLAG_UPDATE_REASON_UNUSED, "unused", 5 * USEC_PER_SEC },
22
23 // not context related
24 {RRD_FLAG_UPDATE_REASON_CHANGED_LINKING, "changed rrd link", 65 * USEC_PER_SEC },
25 {RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD, "child disconnected", 65 * USEC_PER_SEC },
26 {RRD_FLAG_UPDATE_REASON_DB_ROTATION, "db rotation", 65 * USEC_PER_SEC },
27 {RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION, "updated retention", 65 * USEC_PER_SEC },
28
29 // terminator
30 {0, NULL, 0 },
31 };
32
33 void rrd_reasons_to_buffer_json_array_items(RRD_FLAGS flags, BUFFER *wb) {
34 for(int i = 0, added = 0; rrdcontext_reasons[i].name ; i++) {
35 if (flags & rrdcontext_reasons[i].flag) {
36 buffer_json_add_array_item_string(wb, rrdcontext_reasons[i].name);
37 added++;
38 }
39 }
40 }
41 // ----------------------------------------------------------------------------
42 // public API
43
44 ALWAYS_INLINE void rrdcontext_updated_rrddim(RRDDIM *rd) {
45 rrdmetric_from_rrddim(rd);
46 }
47
48 ALWAYS_INLINE void rrdcontext_removed_rrddim(RRDDIM *rd) {
49 rrdmetric_rrddim_is_freed(rd);
50 }
51
52 ALWAYS_INLINE void rrdcontext_updated_rrddim_algorithm(RRDDIM *rd) {
53 rrdmetric_updated_rrddim_algorithm(rd);
54 }
55
56 ALWAYS_INLINE void rrdcontext_updated_rrddim_multiplier(RRDDIM *rd) {
57 rrdmetric_updated_rrddim_flags(rd);
58 }
59
60 ALWAYS_INLINE void rrdcontext_updated_rrddim_divisor(RRDDIM *rd) {
61 rrdmetric_updated_rrddim_flags(rd);
62 }
63
64 ALWAYS_INLINE void rrdcontext_updated_rrddim_flags(RRDDIM *rd) {
65 rrdmetric_updated_rrddim_flags(rd);
66 }
67
68 ALWAYS_INLINE void rrdcontext_collected_rrddim(RRDDIM *rd) {
69 rrdmetric_collected_rrddim(rd);
70 }
71
72 ALWAYS_INLINE void rrdcontext_updated_rrdset(RRDSET *st) {
73 rrdinstance_from_rrdset(st);
74 }
75
76 ALWAYS_INLINE void rrdcontext_removed_rrdset(RRDSET *st) {
77 rrdinstance_rrdset_is_freed(st);
78 }
79
80 ALWAYS_INLINE void rrdcontext_updated_retention_rrdset(RRDSET *st) {
81 rrdinstance_rrdset_has_updated_retention(st);
82 }
83
84 ALWAYS_INLINE void rrdcontext_updated_rrdset_name(RRDSET *st) {
85 rrdinstance_updated_rrdset_name(st);
86 }
87
88 ALWAYS_INLINE void rrdcontext_updated_rrdset_flags(RRDSET *st) {
89 rrdinstance_updated_rrdset_flags(st);
90 }
91
92 ALWAYS_INLINE void rrdcontext_collected_rrdset(RRDSET *st) {
93 rrdinstance_collected_rrdset(st);
94 }
95
96 ALWAYS_INLINE void rrdcontext_host_child_disconnected(RRDHOST *host) {
97 rrdhost_flag_set(host, RRDHOST_FLAG_RRDCONTEXT_GET_RETENTION);
98 }
99
100 ALWAYS_INLINE void rrdcontext_host_child_connected(RRDHOST *host) {
101 // clear the rrdcontexts status cache inside RRDSET and RRDDIM
102 RRDSET *st;
103 rrdset_foreach_read(st, host) {
104 rrdinstance_rrdset_not_collected(st);
105 }
106 rrdset_foreach_done(st);
107 }
108
109 // Cross-thread schedule slot for the deep rrdcontext GC pass. Written by
110 // rrdcontext_db_rotation() (dbengine rotation), rrdcontext_request_full_gc()
111 // (chart-cleanup), and the rrdcontext worker (reset to 0 after a pass).
112 // All accesses go through __atomic_* so 32-bit platforms cannot tear the
113 // 64-bit value, and the request_full_gc() check-then-set is a real CAS
114 // rather than a TOCTOU race.
115 usec_t rrdcontext_next_db_rotation_ut = 0;
116
117 // Companion flag for rrdcontext_request_full_gc(): set when its CAS
118 // failed because the worker was already mid-pass with the deadline in
119 // the past. The worker reads-and-clears this after each pass and arms
120 // a follow-up if set, so an archive that landed mid-pass (after the
121 // worker had already walked its host) doesn't get stranded.
122 size_t rrdcontext_full_gc_rerun_requested = 0;
123
124 ALWAYS_INLINE void rrdcontext_db_rotation(void) {
125 // called when the db rotates its database
126 __atomic_store_n(&rrdcontext_next_db_rotation_ut,
127 now_realtime_usec() + FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS * USEC_PER_SEC,
128 __ATOMIC_RELAXED);
129 // Count only real dbengine rotations (not chart-cleanup-driven scans),
130 // so the extreme-cardinality guard in the rrdcontext worker preserves
131 // its original "wait for first rotation" semantics.
132 rrdcontext_count_db_rotation();
133 }
134
135 ALWAYS_INLINE void rrdcontext_request_full_gc(void) {
136 // Schedule a deep rrdcontext GC pass. Called from chart-cleanup paths
137 // (e.g. svc_rrd_cleanup_obsolete_charts_from_all_hosts) so non-dbengine
138 // hosts also drop archived rrdinstance / rrdmetric entries -- otherwise
139 // those grow unbounded with chart churn (k8s cgroups, etc.) because the
140 // dbengine rotation trigger never fires on them.
141 //
142 // The maintenance loop runs every 10 s; under continuous churn it would
143 // free charts on every pass. Unconditionally rewriting the deadline
144 // would push it out by another 120 s on every call, so under sustained
145 // churn the deep GC would never actually fire. Only arm the deadline if
146 // no pass is already scheduled; the worker resets the slot to 0 after
147 // it runs, at which point the next chart-free arms a fresh window.
148 // Multiple requests within that window coalesce into a single GC pass.
149 usec_t now = now_realtime_usec();
150 usec_t expected = 0;
151 usec_t deadline = now + FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS * USEC_PER_SEC;
152 if(!__atomic_compare_exchange_n(&rrdcontext_next_db_rotation_ut,
153 &expected, deadline,
154 false,
155 __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
156 // CAS failed -- expected now holds the slot's actual value.
157 // If that deadline is in the past, the worker is mid-pass and
158 // may already have walked the host whose archive triggered this
159 // request. Mark for a follow-up so the worker schedules another
160 // pass after the current one. Future-armed deadlines need no
161 // follow-up: their upcoming pass will see the archive.
162 if(expected && expected <= now)
163 __atomic_store_n(&rrdcontext_full_gc_rerun_requested, 1, __ATOMIC_RELAXED);
164 }
165 }
166
167 int rrdcontext_find_dimension_uuid(RRDSET *st, const char *id, nd_uuid_t *store_uuid) {
168 if(!st->rrdhost) return 1;
169 if(!st->context) return 2;
170
171 RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(st->rrdhost->rrdctx.contexts, string2str(st->context));
172 if(!rca) return 3;
173
174 RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
175
176 RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_get_and_acquire_item(rc->rrdinstances, string2str(st->id));
177 if(!ria) {
178 rrdcontext_release(rca);
179 return 4;
180 }
181
182 RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
183
184 RRDMETRIC_ACQUIRED *rma = (RRDMETRIC_ACQUIRED *)dictionary_get_and_acquire_item(ri->rrdmetrics, id);
185 if(!rma) {
186 rrdinstance_release(ria);
187 rrdcontext_release(rca);
188 return 5;
189 }
190
191 RRDMETRIC *rm = rrdmetric_acquired_value(rma);
192
193 uuidmap_uuid(rm->uuid, *store_uuid);
194
195 rrdmetric_release(rma);
196 rrdinstance_release(ria);
197 rrdcontext_release(rca);
198 return 0;
199 }
200
201 int rrdcontext_find_chart_uuid(RRDSET *st, nd_uuid_t *store_uuid) {
202 if(!st->rrdhost) return 1;
203 if(!st->context) return 2;
204
205 RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(st->rrdhost->rrdctx.contexts, string2str(st->context));
206 if(!rca) return 3;
207
208 RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
209
210 RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_get_and_acquire_item(rc->rrdinstances, string2str(st->id));
211 if(!ria) {
212 rrdcontext_release(rca);
213 return 4;
214 }
215
216 RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
217 uuidmap_uuid(ri->uuid, *store_uuid);
218
219 rrdinstance_release(ria);
220 rrdcontext_release(rca);
221 return 0;
222 }
223
224 int rrdcontext_foreach_instance_with_rrdset_in_context(RRDHOST *host, const char *context, int (*callback)(RRDSET *st, void *data), void *data) {
225 if(unlikely(!host || !context || !*context || !callback))
226 return -1;
227
228 RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(host->rrdctx.contexts, context);
229 if(unlikely(!rca)) return -1;
230
231 RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
232 if(unlikely(!rc)) return -1;
233
234 int ret = 0;
235 RRDINSTANCE *ri;
236 dfe_start_read(rc->rrdinstances, ri) {
237 if(ri->rrdset) {
238 int r = callback(ri->rrdset, data);
239 if(r >= 0) ret += r;
240 else {
241 ret = r;
242 break;
243 }
244 }
245 }
246 dfe_done(ri);
247
248 rrdcontext_release(rca);
249
250 return ret;
251 }
252
253 // ----------------------------------------------------------------------------
254 // ACLK interface
255
256 static void rrdcontext_checkpoint_clear_pending_unsafe(struct aclk_sync_cfg_t *aclk_host_config) {
257 freez(aclk_host_config->pending_ctx_claim_id);
258 freez(aclk_host_config->pending_ctx_node_id);
259 aclk_host_config->pending_ctx_claim_id = NULL;
260 aclk_host_config->pending_ctx_node_id = NULL;
261 aclk_host_config->pending_ctx_version_hash = 0;
262 aclk_host_config->pending_ctx_saved_monotonic_s = 0;
263 __atomic_store_n(&aclk_host_config->pending_ctx_checkpoint, false, __ATOMIC_RELEASE);
264 }
265
266 static uint64_t rrdcontext_checkpoint_invalidate_pending(RRDHOST *host) {
267 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
268 if(!aclk_host_config)
269 return 0;
270
271 spinlock_lock(&aclk_host_config->pending_ctx_spinlock);
272 uint64_t generation = __atomic_add_fetch(&aclk_host_config->pending_ctx_generation, 1, __ATOMIC_RELEASE);
273 rrdcontext_checkpoint_clear_pending_unsafe(aclk_host_config);
274 spinlock_unlock(&aclk_host_config->pending_ctx_spinlock);
275
276 return generation;
277 }
278
279 static bool rrdcontext_checkpoint_generation_is_current(RRDHOST *host, const char *claim_id, const char *node_id, uint64_t generation) {
280 if(!generation)
281 return true;
282
283 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
284 uint64_t current_generation = aclk_host_config ?
285 __atomic_load_n(&aclk_host_config->pending_ctx_generation, __ATOMIC_ACQUIRE) :
286 0;
287
288 if(likely(aclk_host_config && current_generation == generation))
289 return true;
290
291 nd_log(NDLS_DAEMON, NDLP_DEBUG,
292 "RRDCONTEXT: skipping stale checkpoint for host '%s', claim id '%s', node id '%s' "
293 "(generation %"PRIu64", current %"PRIu64").",
294 rrdhost_hostname(host), claim_id, node_id, generation, current_generation);
295 return false;
296 }
297
298 // Save a pending checkpoint to be replayed when context processing completes.
299 // Returns true if saved successfully, false if save failed (caller should execute immediately).
300 static bool rrdcontext_checkpoint_save_pending(RRDHOST *host, struct ctxs_checkpoint *cmd) {
301 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
302 if(!aclk_host_config)
303 return false;
304
305 spinlock_lock(&aclk_host_config->pending_ctx_spinlock);
306
307 // Pause incremental context streaming until the deferred checkpoint is replayed.
308 rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS);
309
310 __atomic_add_fetch(&aclk_host_config->pending_ctx_generation, 1, __ATOMIC_RELEASE);
311 rrdcontext_checkpoint_clear_pending_unsafe(aclk_host_config);
312 aclk_host_config->pending_ctx_claim_id = strdupz(cmd->claim_id);
313 aclk_host_config->pending_ctx_node_id = strdupz(cmd->node_id);
314 aclk_host_config->pending_ctx_version_hash = cmd->version_hash;
315 aclk_host_config->pending_ctx_saved_monotonic_s = now_monotonic_sec();
316 __atomic_store_n(&aclk_host_config->pending_ctx_checkpoint, true, __ATOMIC_RELEASE);
317 spinlock_unlock(&aclk_host_config->pending_ctx_spinlock);
318 return true;
319 }
320
321 // Execute the checkpoint: compare version hash, send snapshot if needed, enable streaming.
322 static void rrdcontext_checkpoint_execute(RRDHOST *host, const char *claim_id, const char *node_id, uint64_t version_hash, uint64_t generation) {
323 if(!rrdcontext_checkpoint_generation_is_current(host, claim_id, node_id, generation))
324 return;
325
326 if(rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS)) {
327 nd_log(NDLS_DAEMON, NDLP_NOTICE,
328 "RRDCONTEXT: checkpoint for claim id '%s', node id '%s', "
329 "while node '%s' has an active context streaming.",
330 claim_id, node_id, rrdhost_hostname(host));
331
332 // disable it temporarily, so that our worker will not attempt to send messages in parallel
333 rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS);
334 }
335
336 uint64_t our_version_hash = rrdcontext_version_hash(host);
337
338 if(version_hash != our_version_hash) {
339 nd_log(NDLS_DAEMON, NDLP_NOTICE,
340 "RRDCONTEXT: received version hash %"PRIu64" for host '%s', does not match our version hash %"PRIu64". "
341 "Sending snapshot of all contexts.",
342 version_hash, rrdhost_hostname(host), our_version_hash);
343
344 // prepare the snapshot
345 char uuid_str[UUID_STR_LEN];
346 uuid_unparse_lower(host->node_id.uuid, uuid_str);
347 contexts_snapshot_t bundle = contexts_snapshot_new(claim_id, uuid_str, our_version_hash);
348
349 // do a deep scan on every metric of the host to make sure all our data are updated
350 rrdcontext_recalculate_host_retention(host, RRD_FLAG_NONE, false);
351
352 // calculate version hash and pack all the messages together in one go
353 our_version_hash = rrdcontext_version_hash_with_callback(host, rrdcontext_message_send_unsafe, true, bundle);
354
355 if(!rrdcontext_checkpoint_generation_is_current(host, claim_id, node_id, generation)) {
356 contexts_snapshot_delete(bundle);
357 return;
358 }
359
360 // update the version
361 contexts_snapshot_set_version(bundle, our_version_hash);
362
363 // send it
364 aclk_send_contexts_snapshot(bundle);
365 }
366
367 if(!rrdcontext_checkpoint_generation_is_current(host, claim_id, node_id, generation))
368 return;
369
370 nd_log(NDLS_DAEMON, NDLP_DEBUG,
371 "RRDCONTEXT: host '%s' enabling streaming of contexts",
372 rrdhost_hostname(host));
373
374 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
375 if(aclk_host_config) {
376 spinlock_lock(&aclk_host_config->pending_ctx_spinlock);
377
378 bool can_enable =
379 __atomic_load_n(&aclk_host_config->pending_ctx_generation, __ATOMIC_ACQUIRE) == generation &&
380 !__atomic_load_n(&aclk_host_config->pending_ctx_checkpoint, __ATOMIC_ACQUIRE);
381
382 if(can_enable)
383 rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS);
384
385 spinlock_unlock(&aclk_host_config->pending_ctx_spinlock);
386
387 if(!can_enable)
388 return;
389 }
390 else
391 rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS);
392
393 char node_str[UUID_STR_LEN];
394 uuid_unparse_lower(host->node_id.uuid, node_str);
395 nd_log(NDLS_ACCESS, NDLP_DEBUG,
396 "ACLK REQ [%s (%s)]: STREAM CONTEXTS ENABLED",
397 node_str, rrdhost_hostname(host));
398 }
399
400 void rrdcontext_hub_checkpoint_command(void *ptr) {
401 struct ctxs_checkpoint *cmd = ptr;
402
403 if(!claim_id_matches(cmd->claim_id)) {
404 CLAIM_ID claim_id = claim_id_get();
405 nd_log(NDLS_DAEMON, NDLP_WARNING,
406 "RRDCONTEXT: received checkpoint command for claim_id '%s', node id '%s', "
407 "but this is not our claim id. Ours '%s', received '%s'. Ignoring command.",
408 cmd->claim_id, cmd->node_id,
409 claim_id.str, cmd->claim_id);
410
411 return;
412 }
413
414 RRDHOST *host = rrdhost_find_by_node_id(cmd->node_id);
415 if(!host) {
416 nd_log(NDLS_DAEMON, NDLP_WARNING,
417 "RRDCONTEXT: received checkpoint command for claim id '%s', node id '%s', "
418 "but there is no node with such node id here. Ignoring command.",
419 cmd->claim_id, cmd->node_id);
420
421 return;
422 }
423
424 if(rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD) ||
425 rrdcontext_queue_entries(&host->rrdctx.pp_queue) > 0) {
426 nd_log(NDLS_DAEMON, NDLP_NOTICE,
427 "RRDCONTEXT: received checkpoint command for claim id '%s', node id '%s', "
428 "but host '%s' has pending context work. Saving checkpoint for replay after processing completes.",
429 cmd->claim_id, cmd->node_id, rrdhost_hostname(host));
430
431 if(rrdcontext_checkpoint_save_pending(host, cmd))
432 return;
433
434 nd_log(NDLS_DAEMON, NDLP_WARNING,
435 "RRDCONTEXT: failed to save pending checkpoint for host '%s' (no aclk config). Executing immediately.",
436 rrdhost_hostname(host));
437 }
438
439 uint64_t generation = rrdcontext_checkpoint_invalidate_pending(host);
440 rrdcontext_checkpoint_execute(host, cmd->claim_id, cmd->node_id, cmd->version_hash, generation);
441 }
442
443 void rrdcontext_hub_stop_streaming_command(void *ptr) {
444 struct stop_streaming_ctxs *cmd = ptr;
445
446 if(!claim_id_matches(cmd->claim_id)) {
447 CLAIM_ID claim_id = claim_id_get();
448 nd_log(NDLS_DAEMON, NDLP_WARNING,
449 "RRDCONTEXT: received stop streaming command for claim_id '%s', node id '%s', "
450 "but this is not our claim id. Ours '%s', received '%s'. Ignoring command.",
451 cmd->claim_id, cmd->node_id,
452 claim_id.str, cmd->claim_id);
453
454 return;
455 }
456
457 RRDHOST *host = rrdhost_find_by_node_id(cmd->node_id);
458 if(!host) {
459 nd_log(NDLS_DAEMON, NDLP_WARNING,
460 "RRDCONTEXT: received stop streaming command for claim id '%s', node id '%s', "
461 "but there is no node with such node id here. Ignoring command.",
462 cmd->claim_id, cmd->node_id);
463
464 return;
465 }
466
467 bool had_pending_checkpoint = false;
468 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
469 if(aclk_host_config)
470 had_pending_checkpoint = __atomic_load_n(&aclk_host_config->pending_ctx_checkpoint, __ATOMIC_ACQUIRE);
471
472 rrdcontext_checkpoint_invalidate_pending(host);
473
474 if(!rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS)) {
475 nd_log(NDLS_DAEMON, NDLP_NOTICE,
476 "RRDCONTEXT: received stop streaming command for claim id '%s', node id '%s', "
477 "but node '%s' does not have active context streaming%s.",
478 cmd->claim_id, cmd->node_id, rrdhost_hostname(host),
479 had_pending_checkpoint ? "; invalidated deferred checkpoint" : "");
480
481 return;
482 }
483
484 nd_log(NDLS_DAEMON, NDLP_DEBUG,
485 "RRDCONTEXT: host '%s' disabling streaming of contexts",
486 rrdhost_hostname(host));
487
488 rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS);
489 }
490
491 #define PENDING_CTX_CHECKPOINT_MAX_AGE_S 300
492
493 void rrdcontext_hub_pending_checkpoint_replay(RRDHOST *host) {
494 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
495 if(!aclk_host_config || !__atomic_load_n(&aclk_host_config->pending_ctx_checkpoint, __ATOMIC_ACQUIRE))
496 return;
497
498 bool pending_context_load = rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD);
499 bool pp_queue_empty = rrdcontext_queue_entries(&host->rrdctx.pp_queue) <= 0;
500
501 spinlock_lock(&aclk_host_config->pending_ctx_spinlock);
502 if(!__atomic_load_n(&aclk_host_config->pending_ctx_checkpoint, __ATOMIC_RELAXED)) {
503 spinlock_unlock(&aclk_host_config->pending_ctx_spinlock);
504 return;
505 }
506
507 // replay when pp_queue is empty, or force replay on timeout
508 time_t now_s = now_monotonic_sec();
509 bool timed_out = (now_s - aclk_host_config->pending_ctx_saved_monotonic_s >= PENDING_CTX_CHECKPOINT_MAX_AGE_S);
510 if((pending_context_load || !pp_queue_empty) && !timed_out) {
511 spinlock_unlock(&aclk_host_config->pending_ctx_spinlock);
512 return;
513 }
514
515 char *claim_id = aclk_host_config->pending_ctx_claim_id;
516 char *node_id = aclk_host_config->pending_ctx_node_id;
517 uint64_t version_hash = aclk_host_config->pending_ctx_version_hash;
518 uint64_t generation = __atomic_load_n(&aclk_host_config->pending_ctx_generation, __ATOMIC_RELAXED);
519
520 aclk_host_config->pending_ctx_claim_id = NULL;
521 aclk_host_config->pending_ctx_node_id = NULL;
522 aclk_host_config->pending_ctx_version_hash = 0;
523 aclk_host_config->pending_ctx_saved_monotonic_s = 0;
524 __atomic_store_n(&aclk_host_config->pending_ctx_checkpoint, false, __ATOMIC_RELEASE);
525 spinlock_unlock(&aclk_host_config->pending_ctx_spinlock);
526
527 if(timed_out && (pending_context_load || !pp_queue_empty))
528 nd_log(NDLS_DAEMON, NDLP_WARNING,
529 "RRDCONTEXT: pending checkpoint for host '%s' timed out after %d sec with context work still active. Forcing replay.",
530 rrdhost_hostname(host), PENDING_CTX_CHECKPOINT_MAX_AGE_S);
531
532 if(!claim_id || !node_id) {
533 freez(claim_id);
534 freez(node_id);
535 return;
536 }
537
538 // verify claim id is still valid
539 if(!claim_id_matches(claim_id)) {
540 nd_log(NDLS_DAEMON, NDLP_WARNING,
541 "RRDCONTEXT: pending checkpoint for host '%s' has stale claim id '%s'. Discarding.",
542 rrdhost_hostname(host), claim_id);
543 freez(claim_id);
544 freez(node_id);
545 return;
546 }
547
548 nd_log(NDLS_DAEMON, NDLP_NOTICE,
549 "RRDCONTEXT: replaying deferred checkpoint for host '%s', claim id '%s', node id '%s'.",
550 rrdhost_hostname(host), claim_id, node_id);
551
552 rrdcontext_checkpoint_execute(host, claim_id, node_id, version_hash, generation);
553
554 freez(claim_id);
555 freez(node_id);
556 }
557
558 ALWAYS_INLINE
559 bool rrdcontext_retention_match(RRDCONTEXT_ACQUIRED *rca, time_t after, time_t before) {
560 if(unlikely(!rca)) return false;
561
562 RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
563
564 if(rrd_flag_is_collected(rc))
565 return query_matches_retention(after, before, rc->first_time_s, before > rc->last_time_s ? before : rc->last_time_s, 1);
566 else
567 return query_matches_retention(after, before, rc->first_time_s, rc->last_time_s, 1);
568 }