| 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 = ¤t_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 |