master
h 488 lines 18.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #ifndef NETDATA_PLUGINSD_INTERNALS_H
4 #define NETDATA_PLUGINSD_INTERNALS_H
5
6 #include "pluginsd_parser.h"
7 #include "pluginsd_functions.h"
8 #include "pluginsd_dyncfg.h"
9 #include "pluginsd_replication.h"
10 #include "database/rrdset-pluginsd-array.h"
11
12 #define SERVING_STREAMING(parser) ((parser)->repertoire == PARSER_INIT_STREAMING)
13 #define SERVING_PLUGINSD(parser) ((parser)->repertoire == PARSER_INIT_PLUGINSD)
14
15 PARSER_RC PLUGINSD_DISABLE_PLUGIN(PARSER *parser, const char *keyword, const char *msg);
16
17 ssize_t send_to_plugin(const char *txt, PARSER *parser, STREAM_TRAFFIC_TYPE type);
18
19 static ALWAYS_INLINE RRDHOST *pluginsd_require_scope_host(PARSER *parser, const char *cmd) {
20 RRDHOST *host = parser->user.host;
21
22 if(unlikely(!host))
23 netdata_log_error("PLUGINSD: command %s requires a host, but is not set.", cmd);
24
25 return host;
26 }
27
28 static ALWAYS_INLINE RRDSET *pluginsd_require_scope_chart(PARSER *parser, const char *cmd, const char *parent_cmd) {
29 RRDSET *st = parser->user.st;
30
31 if(unlikely(!st))
32 netdata_log_error("PLUGINSD: command %s requires a chart defined via command %s, but is not set.", cmd, parent_cmd);
33
34 return st;
35 }
36
37 static inline RRDSET *pluginsd_get_scope_chart(PARSER *parser) {
38 return parser->user.st;
39 }
40
41 static inline void rrdset_data_collection_lock_with_trace(PARSER *parser, const char *func) {
42 if(parser->user.st && !parser->user.v2.locked_data_collection) {
43 spinlock_lock_with_trace(&parser->user.st->data_collection_lock, func);
44 parser->user.v2.locked_data_collection = true;
45 }
46 }
47
48 static inline bool rrdset_data_collection_unlock_with_trace(PARSER *parser, const char *func) {
49 if(parser->user.st && parser->user.v2.locked_data_collection) {
50 spinlock_unlock_with_trace(&parser->user.st->data_collection_lock, func);
51 parser->user.v2.locked_data_collection = false;
52 return true;
53 }
54
55 return false;
56 }
57
58 #define rrdset_data_collection_lock(parser) rrdset_data_collection_lock_with_trace(parser, __FUNCTION__)
59 #define rrdset_data_collection_unlock(parser) rrdset_data_collection_unlock_with_trace(parser, __FUNCTION__)
60
61 static ALWAYS_INLINE void rrdset_previous_scope_chart_unlock(PARSER *parser, const char *keyword, bool stale) {
62 if(unlikely(rrdset_data_collection_unlock(parser))) {
63 if(stale)
64 netdata_log_error("PLUGINSD: 'host:%s/chart:%s/' stale data collection lock found during %s; it has been unlocked",
65 rrdhost_hostname(parser->user.st->rrdhost),
66 rrdset_id(parser->user.st),
67 keyword);
68 }
69
70 if(unlikely(parser->user.v2.ml_locked)) {
71 ml_chart_update_end(parser->user.st);
72 parser->user.v2.ml_locked = false;
73
74 if(stale)
75 netdata_log_error("PLUGINSD: 'host:%s/chart:%s/' stale ML lock found during %s, it has been unlocked",
76 rrdhost_hostname(parser->user.st->rrdhost),
77 rrdset_id(parser->user.st),
78 keyword);
79 }
80 }
81
82 static inline void pluginsd_clear_scope_chart(PARSER *parser, const char *keyword, RRDSET *preserve_collector_tid) {
83 rrdset_previous_scope_chart_unlock(parser, keyword, true);
84
85 RRDSET *st = parser->user.st;
86
87 if(parser->user.cleanup_slots && st)
88 rrdset_pluginsd_receive_unslot(st);
89
90 // Clear collector ownership when scope ends, except when explicitly preserving
91 // it for the currently active chart during same-chart re-scope.
92 //
93 // Safety note:
94 // - Full cleanup (rrdset_pluginsd_receive_unslot_and_cleanup) runs on finalized/teardown paths.
95 // - Host teardown stops the receiver thread before slot/index cleanup.
96 // - During active parser execution, unslot paths are collector-aware and skip when another
97 // collector tid is active.
98 // Therefore, eager clear here is an ownership handoff between protocol scopes, not a signal
99 // that teardown cleanup may run concurrently with an active collector loop.
100 // Clear collector ownership only if we are the recorded owner (or no owner exists).
101 // If another thread owns this chart, keep its ownership intact and report it.
102 if(st && st != preserve_collector_tid) {
103 pid_t owner_tid = __atomic_load_n(&st->pluginsd.collector_tid, __ATOMIC_ACQUIRE);
104 pid_t self_tid = gettid_cached();
105
106 if(owner_tid == 0 || owner_tid == self_tid)
107 __atomic_store_n(&st->pluginsd.collector_tid, 0, __ATOMIC_RELEASE);
108 else {
109 netdata_log_error(
110 "PLUGINSD: attempted to clear collector_tid %d for 'host:%s/chart:%s/' "
111 "from non-owner thread %d during %s",
112 (int)owner_tid,
113 rrdhost_hostname(st->rrdhost),
114 rrdset_id(st),
115 (int)self_tid,
116 keyword);
117 }
118 }
119
120 parser->user.st = NULL;
121 parser->user.cleanup_slots = false;
122 parser->user.clabel_count = 0;
123 }
124
125 static ALWAYS_INLINE bool pluginsd_set_scope_chart(PARSER *parser, RRDSET *st, const char *keyword) {
126 RRDSET *old_st = parser->user.st;
127 pid_t old_collector_tid = (old_st) ? __atomic_load_n(&old_st->pluginsd.collector_tid, __ATOMIC_ACQUIRE) : 0;
128 pid_t my_collector_tid = gettid_cached();
129
130 if(unlikely(old_collector_tid)) {
131 if(old_collector_tid != my_collector_tid) {
132 nd_log_limit_static_global_var(erl, 1, 0);
133 nd_log_limit(&erl, NDLS_COLLECTORS, NDLP_WARNING,
134 "PLUGINSD: keyword %s: 'host:%s/chart:%s' is collected twice (my tid %d, other collector tid %d)",
135 keyword ? keyword : "UNKNOWN",
136 rrdhost_hostname(st->rrdhost), rrdset_id(st),
137 my_collector_tid, old_collector_tid);
138
139 return false;
140 }
141 // Don't clear collector_tid here - we still need to access old_st in pluginsd_clear_scope_chart
142 }
143
144 // Set new chart's collector_tid before any access
145 __atomic_store_n(&st->pluginsd.collector_tid, my_collector_tid, __ATOMIC_RELEASE);
146
147 // Access old_st's array in pluginsd_clear_scope_chart while old_st->collector_tid is still set.
148 // Preserve the new chart tid for the old_st == st re-scope case.
149 pluginsd_clear_scope_chart(parser, keyword, st);
150
151 __atomic_store_n(&st->pluginsd.pos, 0, __ATOMIC_RELAXED);
152 parser->user.st = st;
153 parser->user.cleanup_slots = false;
154 parser->user.clabel_count = 0;
155
156 return true;
157 }
158
159 static inline void pluginsd_rrddim_put_to_slot(PARSER *parser, RRDSET *st, RRDDIM *rd, ssize_t slot, bool obsolete) {
160 // Determine the required array size
161 size_t wanted_size;
162
163 if(slot >= 1) {
164 st->pluginsd.dims_with_slots = true;
165 wanted_size = (size_t)slot;
166 }
167 else {
168 st->pluginsd.dims_with_slots = false;
169 wanted_size = dictionary_entries(st->rrddim_root_index);
170 }
171
172 // Get current array (if any) to check size
173 // Note: We're the collector thread with collector_tid set, so the array won't be freed under us
174 PRD_ARRAY *current_arr = prd_array_get_unsafe(&st->pluginsd.prd_array);
175 size_t current_size = current_arr ? current_arr->size : 0;
176
177 // Check if we need to grow the array
178 if(wanted_size > current_size) {
179 // Pre-allocate outside the spinlock to keep critical section short.
180 PRD_ARRAY *new_arr = prd_array_create(wanted_size);
181
182 // Serialize grow transfer with unslot/cleanup detach paths.
183 spinlock_lock(&st->pluginsd.spinlock);
184
185 current_arr = prd_array_get_unsafe(&st->pluginsd.prd_array);
186 current_size = current_arr ? current_arr->size : 0;
187
188 // Re-check under lock in case another path changed the array.
189 if(wanted_size > current_size) {
190 // Copy existing entries from old array (if any) and transfer ownership
191 // to the new array by nulling old pointers.
192 if(current_arr) {
193 memcpy(new_arr->entries, current_arr->entries, current_size * sizeof(struct pluginsd_rrddim));
194 for(size_t i = 0; i < current_size; i++) {
195 current_arr->entries[i].rda = NULL;
196 current_arr->entries[i].rd = NULL;
197 current_arr->entries[i].id = NULL;
198 }
199 }
200
201 // Initialize the new slots (callocz already zeroed them, but be explicit)
202 for(size_t i = current_size; i < wanted_size; i++) {
203 new_arr->entries[i].rda = NULL;
204 new_arr->entries[i].rd = NULL;
205 new_arr->entries[i].id = NULL;
206 }
207
208 // Atomically replace the old array with the new one
209 PRD_ARRAY *old_arr = prd_array_replace(&st->pluginsd.prd_array, new_arr);
210
211 // Release the old array if there was one.
212 if(old_arr) {
213 // Release the old array - it will be freed when refcount reaches 0
214 prd_array_release(old_arr);
215 }
216
217 // Update our local pointer to the new array
218 current_arr = new_arr;
219 new_arr = NULL;
220 }
221 else {
222 current_arr = prd_array_get_unsafe(&st->pluginsd.prd_array);
223 }
224
225 spinlock_unlock(&st->pluginsd.spinlock);
226
227 // Another path already satisfied growth while we were waiting for the lock.
228 if(new_arr)
229 prd_array_release(new_arr);
230 }
231
232 // Now update the slot entry if we're using slots
233 if(st->pluginsd.dims_with_slots && current_arr && slot >= 1 && (size_t)slot <= current_arr->size) {
234 struct pluginsd_rrddim *prd = &current_arr->entries[slot - 1];
235
236 if(prd->rd != rd) {
237 // Release old reference if any
238 if(prd->rda)
239 rrddim_acquired_release(prd->rda);
240
241 prd->rda = rrddim_find_and_acquire(st, string2str(rd->id), true);
242 if(unlikely(!prd->rda)) {
243 prd->rd = NULL;
244 prd->id = NULL;
245 netdata_log_error("PLUGINSD: failed to refresh slot cache for 'host:%s/chart:%s/dim:%s' (slot %zd)",
246 rrdhost_hostname(st->rrdhost), rrdset_id(st), string2str(rd->id), slot);
247 return;
248 }
249 else {
250 prd->rd = rrddim_acquired_to_rrddim(prd->rda);
251 prd->id = string2str(prd->rd->id);
252 }
253 }
254
255 if(obsolete)
256 parser->user.cleanup_slots = true;
257 }
258 }
259
260 static ALWAYS_INLINE RRDDIM *pluginsd_acquire_dimension(RRDHOST *host, RRDSET *st, const char *dimension, ssize_t slot, const char *cmd) {
261 if (unlikely(!dimension || !*dimension)) {
262 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' got a %s, without a dimension.",
263 rrdhost_hostname(host), rrdset_id(st), cmd);
264 return NULL;
265 }
266
267 // Get the array - we're protected by collector_tid being set, so it won't be freed
268 PRD_ARRAY *arr = prd_array_get_unsafe(&st->pluginsd.prd_array);
269
270 if (unlikely(!arr || !arr->size)) {
271 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' got a %s, but the chart has no dimensions.",
272 rrdhost_hostname(host), rrdset_id(st), cmd);
273 return NULL;
274 }
275
276 size_t prd_size = arr->size;
277 struct pluginsd_rrddim *prd;
278 RRDDIM *rd;
279
280 if(likely(st->pluginsd.dims_with_slots)) {
281 // caching with slots
282
283 if(unlikely(slot < 1 || slot > (ssize_t)prd_size)) {
284 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' got a %s with slot %zd, but slots in the range [1 - %zu] are expected.",
285 rrdhost_hostname(host), rrdset_id(st), cmd, slot, prd_size);
286 return NULL;
287 }
288
289 prd = &arr->entries[slot - 1];
290
291 rd = prd->rd;
292 if(likely(rd)) {
293 #ifdef NETDATA_INTERNAL_CHECKS
294 if(!prd->id || strcmp(prd->id, dimension) != 0) {
295 ssize_t t;
296 for(t = 0; t < (ssize_t)prd_size ;t++) {
297 if (arr->entries[t].id && strcmp(arr->entries[t].id, dimension) == 0)
298 break;
299 }
300 if(t >= (ssize_t)prd_size)
301 t = -1;
302
303 internal_fatal(true,
304 "PLUGINSD: expected to find dimension '%s' on slot %zd, but found '%s', "
305 "the right slot is %zd",
306 dimension, slot, prd->id ? prd->id : "(null)", t);
307 }
308 #endif
309 return rd;
310 }
311 }
312 else {
313 // caching without slots
314
315 uint32_t pos = __atomic_load_n(&st->pluginsd.pos, __ATOMIC_RELAXED);
316 if(unlikely(pos >= prd_size))
317 pos = 0;
318
319 __atomic_store_n(&st->pluginsd.pos, pos + 1, __ATOMIC_RELAXED);
320 prd = &arr->entries[pos];
321
322 rd = prd->rd;
323 if(likely(rd)) {
324 const char *id = prd->id;
325
326 if(id && *id && strcmp(id, dimension) == 0) {
327 // we found it cached
328 return rd;
329 }
330 else {
331 // the cached one is not good for us
332 rrddim_acquired_release(prd->rda);
333 prd->rda = NULL;
334 prd->rd = NULL;
335 prd->id = NULL;
336 }
337 }
338 }
339
340 // we need to find the dimension and set it to prd
341
342 RRDDIM_ACQUIRED *rda = rrddim_find_and_acquire(st, dimension, true);
343 if (unlikely(!rda)) {
344 netdata_log_error("PLUGINSD: 'host:%s/chart:%s/dim:%s' got a %s but dimension does not exist.",
345 rrdhost_hostname(host), rrdset_id(st), dimension, cmd);
346
347 return NULL;
348 }
349
350 prd->rda = rda;
351 prd->rd = rd = rrddim_acquired_to_rrddim(rda);
352 prd->id = string2str(rd->id);
353
354 return rd;
355 }
356
357 static inline RRDSET *pluginsd_find_chart(RRDHOST *host, const char *chart, const char *cmd) {
358 if (unlikely(!chart || !*chart)) {
359 netdata_log_error("PLUGINSD: 'host:%s' got a %s without a chart id.",
360 rrdhost_hostname(host), cmd);
361 return NULL;
362 }
363
364 RRDSET *st = rrdset_find(host, chart, true);
365 if (unlikely(!st))
366 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' got a %s but chart does not exist.",
367 rrdhost_hostname(host), chart, cmd);
368
369 return st;
370 }
371
372 static ALWAYS_INLINE ssize_t pluginsd_parse_rrd_slot(char **words, size_t num_words, size_t max_slot) {
373 // Contract: max_slot must fit in ssize_t so the (ssize_t) cast below stays
374 // non-negative. All callers pass small compile-time caps (PLUGINSD_*_SLOT_MAX),
375 // so for the inlined constant this check is folded away by the compiler.
376 internal_fatal(max_slot > (size_t)SSIZE_MAX,
377 "PLUGINSD: max_slot %zu exceeds SSIZE_MAX", max_slot);
378
379 ssize_t slot = -1;
380 char *id = get_word(words, num_words, 1);
381 // Words are NUL-terminated and && short-circuits left-to-right, so each id[k]
382 // is read only after id[0..k-1] matched the non-NUL "SLOT" chars. A shorter word
383 // (e.g. "X" or "SLOT") fails an earlier comparison or hits the terminator at id[4],
384 // so these fixed-index reads never go past the token's NUL. No bounds check needed.
385 if(id && id[0] == PLUGINSD_KEYWORD_SLOT[0] && id[1] == PLUGINSD_KEYWORD_SLOT[1] &&
386 id[2] == PLUGINSD_KEYWORD_SLOT[2] && id[3] == PLUGINSD_KEYWORD_SLOT[3] && id[4] == ':') {
387 unsigned long long parsed_slot = str2ull_encoded(&id[5]);
388 if(unlikely(parsed_slot > max_slot)) {
389 nd_log_limit_static_global_var(erl_slot, 1, 0);
390 nd_log_limit(&erl_slot, NDLS_COLLECTORS, NDLP_WARNING,
391 "PLUGINSD: ignoring invalid SLOT value '%s' above the supported maximum %zu",
392 &id[5], max_slot);
393 // slot 0 means: the SLOT word was present (so the caller still advances its
394 // word index), but the value is unusable as a cache index. Each caller
395 // handles slot < 1 per its own path -- chart lookups fall back to finding
396 // the chart by id, and dimension caching switches to the non-slotted
397 // (by-position) path (pluginsd_rrddim_put_to_slot sets dims_with_slots=false).
398 slot = 0;
399 }
400 else
401 slot = (ssize_t)parsed_slot;
402 }
403
404 return slot;
405 }
406
407 static inline void pluginsd_rrdset_cache_put_to_slot(PARSER *parser, RRDSET *st, ssize_t slot, bool obsolete) {
408 // clean possible old cached data
409 rrdset_pluginsd_receive_unslot(st);
410
411 if(unlikely(slot < 1 || slot >= INT32_MAX))
412 return;
413
414 RRDHOST *host = st->rrdhost;
415
416 if(unlikely((size_t)slot > host->stream.rcv.pluginsd_chart_slots.size)) {
417 spinlock_lock(&host->stream.rcv.pluginsd_chart_slots.spinlock);
418 size_t old_slots = host->stream.rcv.pluginsd_chart_slots.size;
419 size_t new_slots = (old_slots < PLUGINSD_MIN_RRDSET_POINTERS_CACHE) ? PLUGINSD_MIN_RRDSET_POINTERS_CACHE : old_slots * 2;
420
421 if(new_slots < (size_t)slot)
422 new_slots = slot;
423
424 host->stream.rcv.pluginsd_chart_slots.array =
425 reallocz(host->stream.rcv.pluginsd_chart_slots.array, new_slots * sizeof(RRDSET *));
426
427 for(size_t i = old_slots; i < new_slots ;i++)
428 host->stream.rcv.pluginsd_chart_slots.array[i] = NULL;
429
430 host->stream.rcv.pluginsd_chart_slots.size = new_slots;
431 spinlock_unlock(&host->stream.rcv.pluginsd_chart_slots.spinlock);
432
433 rrd_slot_memory_added((new_slots - old_slots) * sizeof(RRDSET *));
434 }
435
436 host->stream.rcv.pluginsd_chart_slots.array[slot - 1] = st;
437 st->pluginsd.last_slot = (int32_t)slot - 1;
438 parser->user.cleanup_slots = obsolete;
439 }
440
441 static ALWAYS_INLINE RRDSET *pluginsd_rrdset_cache_get_from_slot(PARSER *parser, RRDHOST *host, const char *id, ssize_t slot, const char *keyword) {
442 if(unlikely(slot < 1 || (size_t)slot > host->stream.rcv.pluginsd_chart_slots.size))
443 return pluginsd_find_chart(host, id, keyword);
444
445 RRDSET *st = host->stream.rcv.pluginsd_chart_slots.array[slot - 1];
446
447 if(!st) {
448 st = pluginsd_find_chart(host, id, keyword);
449 if(st)
450 pluginsd_rrdset_cache_put_to_slot(parser, st, slot, rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE));
451 }
452 else {
453 internal_fatal(string_strcmp(st->id, id) != 0,
454 "PLUGINSD: wrong chart in slot %zd, expected '%s', found '%s'",
455 slot - 1, id, string2str(st->id));
456 }
457
458 return st;
459 }
460
461 static inline SN_FLAGS pluginsd_parse_storage_number_flags(const char *flags_str) {
462 SN_FLAGS flags = SN_FLAG_NONE;
463
464 char c;
465 while ((c = *flags_str++)) {
466 switch (c) {
467 case 'A':
468 flags |= SN_FLAG_NOT_ANOMALOUS;
469 break;
470
471 case 'R':
472 flags |= SN_FLAG_RESET;
473 break;
474
475 case 'E':
476 flags = SN_EMPTY_SLOT;
477 return flags;
478
479 default:
480 internal_error(true, "Unknown SN_FLAGS flag '%c'", c);
481 break;
482 }
483 }
484
485 return flags;
486 }
487
488 #endif //NETDATA_PLUGINSD_INTERNALS_H