master
c 1,619 lines 63.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "pluginsd_internals.h"
4 #include "streaming/stream-replication-receiver.h"
5 #include "database/rrddim-collection.h"
6
7 static inline PARSER_RC pluginsd_set(char **words, size_t num_words, PARSER *parser) {
8 int idx = 1;
9 ssize_t slot = pluginsd_parse_rrd_slot(words, num_words, PLUGINSD_DIMENSION_SLOT_MAX);
10 if(slot >= 0) idx++;
11
12 char *dimension = get_word(words, num_words, idx++);
13 char *value = get_word(words, num_words, idx++);
14
15 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_SET);
16 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
17
18 RRDSET *st = pluginsd_require_scope_chart(parser, PLUGINSD_KEYWORD_SET, PLUGINSD_KEYWORD_CHART);
19 if(!st) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
20
21 RRDDIM *rd = pluginsd_acquire_dimension(host, st, dimension, slot, PLUGINSD_KEYWORD_SET);
22 if(!rd) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
23
24 st->pluginsd.set = true;
25
26 if (unlikely(rrdset_flag_check(st, RRDSET_FLAG_DEBUG)))
27 netdata_log_debug(D_PLUGINSD, "PLUGINSD: 'host:%s/chart:%s/dim:%s' SET is setting value to '%s'",
28 rrdhost_hostname(host), rrdset_id(st), dimension, value && *value ? value : "UNSET");
29
30 if (value && *value) {
31 if(rrddim_is_float(rd))
32 rrddim_set_by_pointer_double(st, rd, str2ndd_encoded(value, NULL));
33 else
34 rrddim_set_by_pointer(st, rd, str2ll_encoded(value));
35 }
36
37 return PARSER_RC_OK;
38 }
39
40 static inline PARSER_RC pluginsd_begin(char **words, size_t num_words, PARSER *parser) {
41 int idx = 1;
42 ssize_t slot = pluginsd_parse_rrd_slot(words, num_words, PLUGINSD_CHART_SLOT_MAX);
43 if(slot >= 0) idx++;
44
45 char *id = get_word(words, num_words, idx++);
46 char *microseconds_txt = get_word(words, num_words, idx++);
47
48 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_BEGIN);
49 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
50
51 RRDSET *st = pluginsd_rrdset_cache_get_from_slot(parser, host, id, slot, PLUGINSD_KEYWORD_BEGIN);
52 if(!st) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
53
54 if(!pluginsd_set_scope_chart(parser, st, PLUGINSD_KEYWORD_BEGIN))
55 return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
56
57 usec_t microseconds = 0;
58 if (microseconds_txt && *microseconds_txt) {
59 long long t = str2ll(microseconds_txt, NULL);
60 if(t >= 0)
61 microseconds = t;
62 }
63
64 #ifdef NETDATA_LOG_REPLICATION_REQUESTS
65 if(st->replay.log_next_data_collection) {
66 st->replay.log_next_data_collection = false;
67
68 internal_error(true,
69 "REPLAY: 'host:%s/chart:%s' first BEGIN after replication, last collected %llu, last updated %llu, microseconds %llu",
70 rrdhost_hostname(host), rrdset_id(st),
71 st->last_collected_time.tv_sec * USEC_PER_SEC + st->last_collected_time.tv_usec,
72 st->last_updated.tv_sec * USEC_PER_SEC + st->last_updated.tv_usec,
73 (long long unsigned)microseconds
74 );
75 }
76 #endif
77
78 if (likely(st->counter_done)) {
79 if (likely(microseconds)) {
80 if (parser->user.trust_durations)
81 rrdset_next_usec_unfiltered(st, microseconds);
82 else
83 rrdset_next_usec(st, microseconds);
84 }
85 else
86 rrdset_next(st);
87 }
88 return PARSER_RC_OK;
89 }
90
91 static inline PARSER_RC pluginsd_end(char **words, size_t num_words, PARSER *parser) {
92 char *tv_sec = get_word(words, num_words, 1);
93 char *tv_usec = get_word(words, num_words, 2);
94 char *pending_rrdset_next = get_word(words, num_words, 3);
95
96 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_END);
97 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
98
99 RRDSET *st = pluginsd_require_scope_chart(parser, PLUGINSD_KEYWORD_END, PLUGINSD_KEYWORD_BEGIN);
100 if(!st) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
101
102 if (unlikely(rrdset_flag_check(st, RRDSET_FLAG_DEBUG)))
103 netdata_log_debug(D_PLUGINSD, "requested an END on chart '%s'", rrdset_id(st));
104
105 pluginsd_clear_scope_chart(parser, PLUGINSD_KEYWORD_END, NULL);
106 parser->user.data_collections_count++;
107
108 struct timeval tv = {
109 .tv_sec = (tv_sec && *tv_sec) ? str2ll(tv_sec, NULL) : 0,
110 .tv_usec = (tv_usec && *tv_usec) ? str2ll(tv_usec, NULL) : 0
111 };
112
113 if(!tv.tv_sec)
114 now_realtime_timeval(&tv);
115
116 rrdset_timed_done(st, tv, pending_rrdset_next && *pending_rrdset_next ? true : false);
117
118 return PARSER_RC_OK;
119 }
120
121 static void pluginsd_host_define_cleanup(PARSER *parser) {
122 string_freez(parser->user.host_define.hostname);
123 rrdlabels_destroy(parser->user.host_define.rrdlabels);
124
125 parser->user.host_define.hostname = NULL;
126 parser->user.host_define.rrdlabels = NULL;
127 parser->user.host_define.parsing_host = false;
128 parser->user.host_define.node_stale_after_seconds = 0;
129 }
130
131 static inline bool pluginsd_validate_machine_guid(const char *guid, nd_uuid_t *uuid, char *output) {
132 if(uuid_parse(guid, *uuid))
133 return false;
134
135 uuid_unparse_lower(*uuid, output);
136
137 return true;
138 }
139
140 static inline PARSER_RC pluginsd_host_define(char **words, size_t num_words, PARSER *parser) {
141 char *guid = get_word(words, num_words, 1);
142 char *hostname = get_word(words, num_words, 2);
143
144 if(unlikely(!guid || !*guid || !hostname || !*hostname))
145 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_HOST_DEFINE, "missing parameters");
146
147 if(unlikely(parser->user.host_define.parsing_host))
148 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_HOST_DEFINE,
149 "another host definition is already open - did you send " PLUGINSD_KEYWORD_HOST_DEFINE_END "?");
150
151 if(!pluginsd_validate_machine_guid(guid, &parser->user.host_define.machine_guid, parser->user.host_define.machine_guid_str))
152 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_HOST_DEFINE, "cannot parse MACHINE_GUID - is it a valid UUID?");
153
154 parser->user.host_define.hostname = string_strdupz(hostname);
155 parser->user.host_define.rrdlabels = rrdlabels_create();
156 parser->user.host_define.parsing_host = true;
157 parser->user.host_define.node_stale_after_seconds = 0;
158
159 return PARSER_RC_OK;
160 }
161
162 static inline PARSER_RC pluginsd_host_dictionary(char **words, size_t num_words, PARSER *parser, RRDLABELS *labels, const char *keyword) {
163 char *name = get_word(words, num_words, 1);
164 char *value = get_word(words, num_words, 2);
165
166 if(!name || !*name || !value)
167 return PLUGINSD_DISABLE_PLUGIN(parser, keyword, "missing parameters");
168
169 if(!parser->user.host_define.parsing_host || !labels)
170 return PLUGINSD_DISABLE_PLUGIN(parser, keyword, "host is not defined, send " PLUGINSD_KEYWORD_HOST_DEFINE " before this");
171
172 rrdlabels_add(labels, name, value, RRDLABEL_SRC_CONFIG);
173 if (strcmp(name, "_node_stale_after_seconds") == 0) {
174 uint32_t seconds = str2u(value);
175 parser->user.host_define.node_stale_after_seconds = seconds;
176 }
177 return PARSER_RC_OK;
178 }
179
180 static inline PARSER_RC pluginsd_host_labels(char **words, size_t num_words, PARSER *parser) {
181 return pluginsd_host_dictionary(words, num_words, parser,
182 parser->user.host_define.rrdlabels,
183 PLUGINSD_KEYWORD_HOST_LABEL);
184 }
185
186 // returns true when the _is_ephemeral label was added or its value changed.
187 static inline bool pluginsd_update_host_ephemerality(RRDHOST *host) {
188 char value[64];
189 rrdlabels_get_value_strcpyz(host->rrdlabels, value, sizeof(value), HOST_LABEL_IS_EPHEMERAL);
190 if(value[0] && inicfg_test_boolean_value(value)) {
191 rrdhost_option_set(host, RRDHOST_OPTION_EPHEMERAL_HOST);
192 strncpyz(value, "true", sizeof(value) - 1);
193 }
194 else {
195 rrdhost_option_clear(host, RRDHOST_OPTION_EPHEMERAL_HOST);
196 strncpyz(value, "false", sizeof(value) - 1);
197 }
198
199 // Set or replace current label as needed
200 return rrdlabels_add_changed(host->rrdlabels, HOST_LABEL_IS_EPHEMERAL, value, RRDLABEL_SRC_CONFIG);
201 }
202
203 #define VNODE_BASE_EPOCH (1704067200L) // Jan 1, 2024 00:00:00 UTC
204
205 static inline PARSER_RC pluginsd_host_define_end(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser) {
206 if(!parser->user.host_define.parsing_host)
207 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_HOST_DEFINE_END, "missing initialization, send " PLUGINSD_KEYWORD_HOST_DEFINE " before this");
208
209 struct rrdhost_system_info *system_info = rrdhost_system_info_from_host_labels(parser->user.host_define.rrdlabels);
210
211 SYSTEM_TZ tz = system_tz_get();
212 RRDHOST *host = rrdhost_find_or_create(
213 string2str(parser->user.host_define.hostname),
214 string2str(parser->user.host_define.hostname),
215 parser->user.host_define.machine_guid_str,
216 NETDATA_VIRTUAL_HOST,
217 tz.timezone,
218 tz.abbrev_timezone,
219 tz.utc_offset,
220 program_name,
221 NETDATA_VERSION,
222 nd_profile.update_every,
223 default_rrd_history_entries,
224 default_rrd_memory_mode,
225 health_plugin_enabled(),
226 stream_send.enabled,
227 stream_send.parents.destination,
228 stream_send.api_key,
229 stream_send.send_charts_matching,
230 stream_receive.replication.enabled,
231 stream_receive.replication.period,
232 stream_receive.replication.step,
233 system_info,
234 false);
235 system_tz_free(&tz);
236
237 rrdhost_system_info_free(system_info);
238
239 rrdhost_option_set(host, RRDHOST_OPTION_VIRTUAL_HOST);
240 rrdhost_flag_set(host, RRDHOST_FLAG_COLLECTOR_ONLINE);
241 object_state_activate_if_not_activated(&host->state_id);
242 ml_host_start(host);
243 pulse_host_status(host, 0, 0); // this will detect the receiver status
244
245 bool labels_changed;
246 if(host->rrdlabels) {
247 labels_changed = rrdlabels_migrate_to_these(host->rrdlabels, parser->user.host_define.rrdlabels);
248 }
249 else {
250 host->rrdlabels = parser->user.host_define.rrdlabels;
251 parser->user.host_define.rrdlabels = NULL;
252 labels_changed = true;
253 }
254
255 if(SERVING_PLUGINSD(parser)) {
256 labels_changed |= rrdlabels_add_changed(host->rrdlabels, "_collector_machine_guid",
257 localhost->machine_guid, RRDLABEL_SRC_AUTO);
258 }
259
260 labels_changed |= pluginsd_update_host_ephemerality(host);
261 pluginsd_host_define_cleanup(parser);
262
263 if(labels_changed)
264 rrdhost_flag_set(host, RRDHOST_FLAG_PENDING_LABEL_RECHECK);
265
266 parser->user.host = host;
267 pluginsd_clear_scope_chart(parser, PLUGINSD_KEYWORD_HOST_DEFINE_END, NULL);
268
269 rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN);
270 rrdcontext_host_child_connected(host);
271 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
272 if (aclk_host_config)
273 aclk_queue_node_info(host, true);
274 else
275 schedule_node_state_update(host, 100);
276
277 rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE);
278 uint32_t *Pvalue = (uint32_t *) JudyLIns(&parser->user.vnodes.JudyL, (Word_t) host, PJE0);
279 if (Pvalue != PJERR)
280 *Pvalue = (uint32_t) (now_realtime_sec() - VNODE_BASE_EPOCH);
281 else
282 nd_log_daemon(NDLP_ERR, "VNODE: Cannot track virtual host \"%s\" for staleness - JudyLIns error", rrdhost_hostname(host));
283 host->node_stale_after_seconds = parser->user.host_define.node_stale_after_seconds;
284 nd_log_daemon(NDLP_INFO, "VNODE: Configuring node stale after %u seconds for host \"%s\"", host->node_stale_after_seconds, rrdhost_hostname(host));
285 return PARSER_RC_OK;
286 }
287
288 static inline PARSER_RC pluginsd_host(char **words, size_t num_words, PARSER *parser)
289 {
290 static time_t last_host_stale_check = 0;
291 char *guid = get_word(words, num_words, 1);
292
293 if(!guid || !*guid || strcmp(guid, "localhost") == 0) {
294 parser->user.host = localhost;
295 // Check if we need to switch any nodes to stale
296 uint32_t min_check_interval = UINT_MAX;
297 time_t now = now_realtime_sec();
298 if (last_host_stale_check < now) {
299 Word_t Index = 0;
300 bool first_then_next = true;
301 uint32_t *Pvalue;
302 while ((Pvalue = (uint32_t *) JudyLFirstThenNext(parser->user.vnodes.JudyL, &Index, &first_then_next))) {
303 RRDHOST *virtual_host = (RRDHOST *) Index;
304 uint32_t stale_after_seconds = virtual_host->node_stale_after_seconds;
305 if (!stale_after_seconds)
306 continue;
307
308 min_check_interval = MIN(min_check_interval, stale_after_seconds);
309 if (rrdhost_option_check(virtual_host, RRDHOST_OPTION_VIRTUAL_HOST)) {
310 time_t last_seen = (*Pvalue + VNODE_BASE_EPOCH);
311 uint32_t seen_seconds_ago = (uint32_t) (now - last_seen);
312
313 if (seen_seconds_ago >= stale_after_seconds) {
314 rrdhost_option_clear(virtual_host, RRDHOST_OPTION_VIRTUAL_HOST);
315 rrdhost_flag_clear(virtual_host, RRDHOST_FLAG_COLLECTOR_ONLINE);
316 nd_log_daemon(NDLP_INFO, "VNODE: Marking node \"%s\" as STALE, last seen %u seconds ago", rrdhost_hostname(virtual_host), seen_seconds_ago);
317 schedule_node_state_update(virtual_host, 1000);
318 stream_sender_signal_to_stop_and_wait(virtual_host, STREAM_HANDSHAKE_SND_VNODE_IS_STALE, false);
319 }
320 }
321 }
322 if (min_check_interval == UINT_MAX)
323 min_check_interval = 60;
324
325 last_host_stale_check = now_realtime_sec() + min_check_interval;
326 }
327 return PARSER_RC_OK;
328 }
329
330 nd_uuid_t uuid;
331 char uuid_str[UUID_STR_LEN];
332 if(!pluginsd_validate_machine_guid(guid, &uuid, uuid_str))
333 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_HOST, "cannot parse MACHINE_GUID - is it a valid UUID?");
334
335 RRDHOST *host = rrdhost_find_by_guid(uuid_str);
336 if(unlikely(!host))
337 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_HOST, "cannot find a host with this machine guid - have you created it?");
338
339 parser->user.host = host;
340 uint32_t *Pvalue = (uint32_t *) JudyLGet(parser->user.vnodes.JudyL, (Word_t) host, PJE0);
341 if (Pvalue) {
342 *Pvalue = (uint32_t) (now_realtime_sec() - VNODE_BASE_EPOCH);
343 // Check if we need to enable
344 if (!rrdhost_option_check(host, RRDHOST_OPTION_VIRTUAL_HOST)) {
345 rrdhost_option_set(host, RRDHOST_OPTION_VIRTUAL_HOST);
346 rrdhost_flag_set(host, RRDHOST_FLAG_COLLECTOR_ONLINE);
347 nd_log_daemon(NDLP_INFO, "VNODE: Re-enabling virtual host \"%s\"", rrdhost_hostname(host));
348 schedule_node_state_update(host, 1000);
349 }
350 }
351 return PARSER_RC_OK;
352 }
353
354 static inline PARSER_RC pluginsd_chart(char **words, size_t num_words, PARSER *parser) {
355 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_CHART);
356 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
357
358 int idx = 1;
359 ssize_t slot = pluginsd_parse_rrd_slot(words, num_words, PLUGINSD_CHART_SLOT_MAX);
360 if(slot >= 0) idx++;
361
362 char *type = get_word(words, num_words, idx++);
363 char *name = get_word(words, num_words, idx++);
364 char *title = get_word(words, num_words, idx++);
365 char *units = get_word(words, num_words, idx++);
366 char *family = get_word(words, num_words, idx++);
367 char *context = get_word(words, num_words, idx++);
368 char *chart = get_word(words, num_words, idx++);
369 char *priority_s = get_word(words, num_words, idx++);
370 char *update_every_s = get_word(words, num_words, idx++);
371 char *options = get_word(words, num_words, idx++);
372 char *plugin = get_word(words, num_words, idx++);
373 char *module = get_word(words, num_words, idx++);
374
375 // parse the id from type
376 char *id = NULL;
377 if (likely(type && (id = strchr(type, '.')))) {
378 *id = '\0';
379 id++;
380 }
381
382 // make sure we have the required variables
383 if (unlikely((!type || !*type || !id || !*id)))
384 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_CHART, "missing parameters");
385
386 // parse the name, and make sure it does not include 'type.'
387 if (unlikely(name && *name)) {
388 // when data are streamed from child nodes
389 // name will be type.name
390 // so, we have to remove 'type.' from name too
391 size_t len = strlen(type);
392 if (strncmp(type, name, len) == 0 && name[len] == '.')
393 name = &name[len + 1];
394
395 // if the name is the same with the id,
396 // or is just 'NULL', clear it.
397 if (unlikely(strcmp(name, id) == 0 || strcasecmp(name, "NULL") == 0 || strcasecmp(name, "(NULL)") == 0))
398 name = NULL;
399 }
400
401 int priority = 1000;
402 if (likely(priority_s && *priority_s))
403 priority = str2i(priority_s);
404
405 int update_every = parser->user.cd->update_every;
406 if (likely(update_every_s && *update_every_s))
407 update_every = str2i(update_every_s);
408 if (unlikely(!update_every))
409 update_every = parser->user.cd->update_every;
410
411 RRDSET_TYPE chart_type = RRDSET_TYPE_LINE;
412 if (unlikely(chart))
413 chart_type = rrdset_type_id(chart);
414
415 if (unlikely(name && !*name))
416 name = NULL;
417 if (unlikely(family && !*family))
418 family = NULL;
419 if (unlikely(context && !*context))
420 context = NULL;
421 if (unlikely(!title))
422 title = "";
423 if (unlikely(!units))
424 units = "unknown";
425
426 netdata_log_debug(
427 D_PLUGINSD,
428 "creating chart type='%s', id='%s', name='%s', family='%s', context='%s', chart='%s', priority=%d, update_every=%d",
429 type, id, name ? name : "", family ? family : "", context ? context : "", rrdset_type_name(chart_type),
430 priority, update_every);
431
432 RRDSET *st = NULL;
433
434 st = rrdset_create(
435 host, type, id, name, family, context, title, units,
436 (plugin && *plugin) ? plugin : string2str(parser->user.cd->filename),
437 module, priority, update_every,
438 chart_type);
439
440 bool obsolete = false;
441 if (likely(st)) {
442 if (options && *options) {
443 if (strstr(options, "obsolete")) {
444 rrdset_is_obsolete___safe_from_collector_thread(st);
445 obsolete = true;
446 }
447 else
448 rrdset_isnot_obsolete___safe_from_collector_thread(st);
449
450 if (strstr(options, "hidden"))
451 rrdset_flag_set(st, RRDSET_FLAG_HIDDEN);
452 else
453 rrdset_flag_clear(st, RRDSET_FLAG_HIDDEN);
454
455 if (strstr(options, "store_first"))
456 rrdset_flag_set(st, RRDSET_FLAG_STORE_FIRST);
457 else
458 rrdset_flag_clear(st, RRDSET_FLAG_STORE_FIRST);
459 }
460 else
461 rrdset_flag_clear(st, RRDSET_FLAG_STORE_FIRST);
462
463 if(!pluginsd_set_scope_chart(parser, st, PLUGINSD_KEYWORD_CHART))
464 return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
465
466 pluginsd_rrdset_cache_put_to_slot(parser, st, slot, obsolete);
467 }
468 else
469 pluginsd_clear_scope_chart(parser, PLUGINSD_KEYWORD_CHART, NULL);
470
471 return PARSER_RC_OK;
472 }
473
474 static inline PARSER_RC pluginsd_dimension(char **words, size_t num_words, PARSER *parser) {
475 int idx = 1;
476 ssize_t slot = pluginsd_parse_rrd_slot(words, num_words, PLUGINSD_DIMENSION_SLOT_MAX);
477 if(slot >= 0) idx++;
478
479 char *id = get_word(words, num_words, idx++);
480 char *name = get_word(words, num_words, idx++);
481 char *algorithm = get_word(words, num_words, idx++);
482 char *multiplier_s = get_word(words, num_words, idx++);
483 char *divisor_s = get_word(words, num_words, idx++);
484 char *options = get_word(words, num_words, idx++);
485
486 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_DIMENSION);
487 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
488
489 RRDSET *st = pluginsd_require_scope_chart(parser, PLUGINSD_KEYWORD_DIMENSION, PLUGINSD_KEYWORD_CHART);
490 if(!st) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
491
492 if (unlikely(!id || !*id))
493 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DIMENSION, "missing dimension id");
494
495 long multiplier = 1;
496 if (multiplier_s && *multiplier_s) {
497 multiplier = str2ll_encoded(multiplier_s);
498 if (unlikely(!multiplier))
499 multiplier = 1;
500 }
501
502 long divisor = 1;
503 if (likely(divisor_s && *divisor_s)) {
504 divisor = str2ll_encoded(divisor_s);
505 if (unlikely(!divisor))
506 divisor = 1;
507 }
508
509 if (unlikely(!algorithm || !*algorithm))
510 algorithm = "absolute";
511
512 if (unlikely(st && rrdset_flag_check(st, RRDSET_FLAG_DEBUG)))
513 netdata_log_debug(
514 D_PLUGINSD,
515 "creating dimension in chart %s, id='%s', name='%s', algorithm='%s', multiplier=%ld, divisor=%ld, hidden='%s'",
516 rrdset_id(st), id, name ? name : "", rrd_algorithm_name(rrd_algorithm_id(algorithm)), multiplier, divisor,
517 options ? options : "");
518
519 RRDDIM *rd = rrddim_add(st, id, name, multiplier, divisor, rrd_algorithm_id(algorithm));
520 if (unlikely(!rd))
521 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DIMENSION, "failed to create dimension");
522
523 int unhide_dimension = 1;
524
525 rrddim_option_clear(rd, RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS);
526 bool obsolete = false;
527 if (options && *options) {
528 if (strstr(options, "obsolete") != NULL) {
529 obsolete = true;
530 rrddim_is_obsolete___safe_from_collector_thread(st, rd);
531 }
532 else
533 rrddim_isnot_obsolete___safe_from_collector_thread(st, rd);
534
535 unhide_dimension = !strstr(options, "hidden");
536
537 if (strstr(options, "noreset") != NULL)
538 rrddim_option_set(rd, RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS);
539 if (strstr(options, "nooverflow") != NULL)
540 rrddim_option_set(rd, RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS);
541
542 if (strstr(options, "type=float") != NULL) {
543 if(!rrddim_is_float(rd))
544 memset(&rd->collector.collected, 0, sizeof(rd->collector.collected));
545 rrddim_option_set(rd, RRDDIM_OPTION_VALUE_FLOAT);
546 }
547 else if (strstr(options, "type=int") != NULL) {
548 if(rrddim_is_float(rd))
549 memset(&rd->collector.collected, 0, sizeof(rd->collector.collected));
550 rrddim_option_clear(rd, RRDDIM_OPTION_VALUE_FLOAT);
551 }
552 }
553 else
554 rrddim_isnot_obsolete___safe_from_collector_thread(st, rd);
555
556 bool should_update_dimension = false;
557
558 if (likely(unhide_dimension)) {
559 rrddim_option_clear(rd, RRDDIM_OPTION_HIDDEN);
560 should_update_dimension = rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN);
561 }
562 else {
563 rrddim_option_set(rd, RRDDIM_OPTION_HIDDEN);
564 should_update_dimension = !rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN);
565 }
566
567 if (should_update_dimension) {
568 rrddim_flag_set(rd, RRDDIM_FLAG_METADATA_UPDATE);
569 rrdhost_flag_set(rd->rrdset->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
570 }
571
572 pluginsd_rrddim_put_to_slot(parser, st, rd, slot, obsolete);
573
574 return PARSER_RC_OK;
575 }
576
577 // ----------------------------------------------------------------------------
578
579 static inline PARSER_RC pluginsd_variable(char **words, size_t num_words, PARSER *parser) {
580 char *name = get_word(words, num_words, 1);
581 char *value = get_word(words, num_words, 2);
582 NETDATA_DOUBLE v;
583
584 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_VARIABLE);
585 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
586
587 RRDSET *st = pluginsd_get_scope_chart(parser);
588
589 int global = (st) ? 0 : 1;
590
591 if (name && *name) {
592 if ((strcmp(name, "GLOBAL") == 0 || strcmp(name, "HOST") == 0)) {
593 global = 1;
594 name = get_word(words, num_words, 2);
595 value = get_word(words, num_words, 3);
596 } else if ((strcmp(name, "LOCAL") == 0 || strcmp(name, "CHART") == 0)) {
597 global = 0;
598 name = get_word(words, num_words, 2);
599 value = get_word(words, num_words, 3);
600 }
601 }
602
603 if (unlikely(!name || !*name))
604 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_VARIABLE, "missing variable name");
605
606 if (unlikely(!value || !*value))
607 value = NULL;
608
609 if (unlikely(!value)) {
610 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' cannot set %s VARIABLE '%s' to an empty value",
611 rrdhost_hostname(host),
612 st ? rrdset_id(st):"UNSET",
613 (global) ? "HOST" : "CHART",
614 name);
615 return PARSER_RC_OK;
616 }
617
618 if (!global && !st)
619 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_VARIABLE, "no chart is defined and no GLOBAL is given");
620
621 char *endptr = NULL;
622 v = (NETDATA_DOUBLE) str2ndd_encoded(value, &endptr);
623 if (unlikely(endptr && *endptr)) {
624 if (endptr == value)
625 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' the value '%s' of VARIABLE '%s' cannot be parsed as a number",
626 rrdhost_hostname(host),
627 st ? rrdset_id(st):"UNSET",
628 value,
629 name);
630 else
631 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' the value '%s' of VARIABLE '%s' has leftovers: '%s'",
632 rrdhost_hostname(host),
633 st ? rrdset_id(st):"UNSET",
634 value,
635 name,
636 endptr);
637 }
638
639 if (global) {
640 const RRDVAR_ACQUIRED *rva = rrdvar_host_variable_add_and_acquire(host, name);
641 if (rva) {
642 rrdvar_host_variable_set(host, rva, v);
643 rrdvar_host_variable_release(host, rva);
644 }
645 else
646 netdata_log_error("PLUGINSD: 'host:%s' cannot find/create HOST VARIABLE '%s'",
647 rrdhost_hostname(host),
648 name);
649 } else {
650 const RRDVAR_ACQUIRED *rsa = rrdvar_chart_variable_add_and_acquire(st, name);
651 if (rsa) {
652 rrdvar_chart_variable_set(st, rsa, v);
653 rrdvar_chart_variable_release(st, rsa);
654 }
655 else
656 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' cannot find/create CHART VARIABLE '%s'",
657 rrdhost_hostname(host), rrdset_id(st), name);
658 }
659
660 return PARSER_RC_OK;
661 }
662
663 static inline PARSER_RC pluginsd_flush(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser) {
664 netdata_log_debug(D_PLUGINSD, "requested a " PLUGINSD_KEYWORD_FLUSH);
665 pluginsd_clear_scope_chart(parser, PLUGINSD_KEYWORD_FLUSH, NULL);
666 parser->user.replay.start_time = 0;
667 parser->user.replay.end_time = 0;
668 parser->user.replay.start_time_ut = 0;
669 parser->user.replay.end_time_ut = 0;
670 return PARSER_RC_OK;
671 }
672
673 static inline PARSER_RC pluginsd_disable(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser) {
674 netdata_log_info("PLUGINSD: plugin called DISABLE. Disabling it.");
675 parser->user.enabled = 0;
676 return PARSER_RC_STOP;
677 }
678
679 static inline PARSER_RC pluginsd_label(char **words, size_t num_words, PARSER *parser) {
680 const char *name = get_word(words, num_words, 1);
681 const char *label_source = get_word(words, num_words, 2);
682 const char *value = get_word(words, num_words, 3);
683
684 if (!name || !label_source || !value)
685 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_LABEL, "missing parameters");
686
687 char *store = (char *)value;
688 bool allocated_store = false;
689
690 if(unlikely(num_words > 4)) {
691 allocated_store = true;
692 store = mallocz(PLUGINSD_LINE_MAX + 1);
693 size_t remaining = PLUGINSD_LINE_MAX;
694 char *move = store;
695 char *word;
696 for(size_t i = 3; i < num_words && remaining > 2 && (word = get_word(words, num_words, i)) ;i++) {
697 if(i > 3) {
698 *move++ = ' ';
699 *move = '\0';
700 remaining--;
701 }
702
703 size_t length = strlen(word);
704 if (length > remaining)
705 length = remaining;
706
707 remaining -= length;
708 memcpy(move, word, length);
709 move += length;
710 *move = '\0';
711 }
712 }
713
714 if(unlikely(!(parser->user.new_host_labels)))
715 parser->user.new_host_labels = rrdlabels_create();
716
717 rrdlabels_add(parser->user.new_host_labels, name, store, str2l(label_source));
718
719 if (allocated_store)
720 freez(store);
721
722 return PARSER_RC_OK;
723 }
724
725 static inline PARSER_RC pluginsd_overwrite(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser) {
726 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_OVERWRITE);
727 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
728
729 netdata_log_debug(D_PLUGINSD, "requested to OVERWRITE host labels");
730
731 if(unlikely(!host->rrdlabels))
732 host->rrdlabels = rrdlabels_create();
733
734 bool labels_changed = rrdlabels_migrate_to_these(host->rrdlabels, parser->user.new_host_labels);
735 labels_changed |= pluginsd_update_host_ephemerality(host);
736
737 if(!rrdlabels_exist(host->rrdlabels, "_os"))
738 labels_changed |= rrdlabels_add_changed(host->rrdlabels, "_os", string2str(host->os), RRDLABEL_SRC_AUTO);
739
740 if(!rrdlabels_exist(host->rrdlabels, "_hostname"))
741 labels_changed |= rrdlabels_add_changed(host->rrdlabels, "_hostname", string2str(host->hostname), RRDLABEL_SRC_AUTO);
742
743 rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE);
744 if(labels_changed)
745 rrdhost_flag_set(host, RRDHOST_FLAG_PENDING_LABEL_RECHECK);
746
747 rrdlabels_destroy(parser->user.new_host_labels);
748 parser->user.new_host_labels = NULL;
749 return PARSER_RC_OK;
750 }
751
752 static inline PARSER_RC pluginsd_clabel(char **words, size_t num_words, PARSER *parser) {
753 const char *name = get_word(words, num_words, 1);
754 const char *value = get_word(words, num_words, 2);
755 const char *label_source = get_word(words, num_words, 3);
756
757 if (!name || !value || !label_source) {
758 netdata_log_error("Ignoring malformed or empty CHART LABEL command.");
759 return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
760 }
761
762 RRDSET *st = pluginsd_get_scope_chart(parser);
763 if(!st) return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_CLABEL, "Got CHART LABEL without a chart");
764
765 if(unlikely(parser->user.clabel_count++ == 0))
766 rrdlabels_unmark_all(st->rrdlabels);
767
768 rrdlabels_add(st->rrdlabels, name, value, str2l(label_source));
769
770 return PARSER_RC_OK;
771 }
772
773 static inline PARSER_RC pluginsd_clabel_commit(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser) {
774 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_CLABEL_COMMIT);
775 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
776
777 RRDSET *st = pluginsd_require_scope_chart(parser, PLUGINSD_KEYWORD_CLABEL_COMMIT, PLUGINSD_KEYWORD_BEGIN);
778 if(!st) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
779
780 netdata_log_debug(D_PLUGINSD, "requested to commit chart labels");
781
782 if(!parser->user.clabel_count) {
783 netdata_log_error("PLUGINSD: 'host:%s' got CLABEL_COMMIT, without a CHART or BEGIN. Ignoring it.", rrdhost_hostname(host));
784 return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
785 }
786
787 bool labels_changed = rrdlabels_remove_all_unmarked_and_changed(st->rrdlabels);
788
789 rrdset_flag_set(st, RRDSET_FLAG_METADATA_UPDATE);
790 rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
791 rrdset_metadata_updated(st);
792
793 if(labels_changed) {
794 rrdset_flag_set(st, RRDSET_FLAG_PENDING_LABEL_RECHECK);
795 rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION);
796 }
797
798 parser->user.clabel_count = 0;
799
800 return PARSER_RC_OK;
801 }
802
803 static ALWAYS_INLINE PARSER_RC pluginsd_begin_v2(char **words, size_t num_words, PARSER *parser) {
804 timing_init();
805
806 int idx = 1;
807 ssize_t slot = pluginsd_parse_rrd_slot(words, num_words, PLUGINSD_CHART_SLOT_MAX);
808 if(slot >= 0) idx++;
809
810 char *id = get_word(words, num_words, idx++);
811 char *update_every_str = get_word(words, num_words, idx++);
812 char *end_time_str = get_word(words, num_words, idx++);
813 char *wall_clock_time_str = get_word(words, num_words, idx++);
814
815 if(unlikely(!id || !update_every_str || !end_time_str || !wall_clock_time_str))
816 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_BEGIN_V2, "missing parameters");
817
818 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_BEGIN_V2);
819 if(unlikely(!host)) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
820
821 timing_step(TIMING_STEP_BEGIN2_PREPARE);
822
823 RRDSET *st = pluginsd_rrdset_cache_get_from_slot(parser, host, id, slot, PLUGINSD_KEYWORD_BEGIN_V2);
824
825 if(unlikely(!st)) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
826
827 if(!pluginsd_set_scope_chart(parser, st, PLUGINSD_KEYWORD_BEGIN_V2))
828 return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
829
830 if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE))) {
831 if(!spinlock_trylock(&st->destroy_lock))
832 fatal("PLUGINSD: chart '%s' of host '%s' is being collected while is being destroyed.", rrdset_id(st), rrdhost_hostname(st->rrdhost));
833
834 rrdset_isnot_obsolete___safe_from_collector_thread(st);
835 spinlock_unlock(&st->destroy_lock);
836 }
837
838 timing_step(TIMING_STEP_BEGIN2_FIND_CHART);
839
840 // ------------------------------------------------------------------------
841 // parse the parameters
842
843 time_t update_every = (time_t) str2ull_encoded(update_every_str);
844 time_t end_time = (time_t) str2ull_encoded(end_time_str);
845
846 time_t wall_clock_time;
847 if(likely(*wall_clock_time_str == '#'))
848 wall_clock_time = end_time;
849 else
850 wall_clock_time = (time_t) str2ull_encoded(wall_clock_time_str);
851
852 if (unlikely(update_every != st->update_every))
853 rrdset_set_update_every_s(st, update_every);
854
855 timing_step(TIMING_STEP_BEGIN2_PARSE);
856
857 // ------------------------------------------------------------------------
858 // prepare our state
859
860 rrdset_data_collection_lock(parser);
861
862 parser->user.v2.update_every = update_every;
863 parser->user.v2.end_time = end_time;
864 parser->user.v2.wall_clock_time = wall_clock_time;
865 parser->user.v2.ml_locked = ml_chart_update_begin(st);
866
867 timing_step(TIMING_STEP_BEGIN2_ML);
868
869 // ------------------------------------------------------------------------
870 // propagate it forward in v2
871
872 if(!parser->user.v2.stream_buffer.wb && rrdhost_has_stream_sender_enabled(st->rrdhost))
873 parser->user.v2.stream_buffer = stream_send_metrics_init(parser->user.st, wall_clock_time);
874
875 if(parser->user.v2.stream_buffer.v2 && parser->user.v2.stream_buffer.wb) {
876 // check receiver capabilities
877 bool can_copy = stream_has_capability(&parser->user, STREAM_CAP_IEEE754) == stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_IEEE754);
878
879 // check sender capabilities
880 bool with_slots = stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_SLOTS) ? true : false;
881 NUMBER_ENCODING integer_encoding = stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_HEX;
882
883 BUFFER *wb = parser->user.v2.stream_buffer.wb;
884
885 buffer_need_bytes(wb, 1024);
886
887 if(unlikely(parser->user.v2.stream_buffer.begin_v2_added))
888 buffer_fast_strcat(wb, PLUGINSD_KEYWORD_END_V2 "\n", sizeof(PLUGINSD_KEYWORD_END_V2) - 1 + 1);
889
890 buffer_fast_strcat(wb, PLUGINSD_KEYWORD_BEGIN_V2, sizeof(PLUGINSD_KEYWORD_BEGIN_V2) - 1);
891
892 if(with_slots) {
893 buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2);
894 buffer_print_uint64_encoded(wb, integer_encoding, st->stream.snd.chart_slot);
895 }
896
897 buffer_fast_strcat(wb, " '", 2);
898 buffer_fast_strcat(wb, rrdset_id(st), string_strlen(st->id));
899 buffer_fast_strcat(wb, "' ", 2);
900
901 if(can_copy)
902 buffer_strcat(wb, update_every_str);
903 else
904 buffer_print_uint64_encoded(wb, integer_encoding, update_every);
905
906 buffer_fast_strcat(wb, " ", 1);
907
908 if(can_copy)
909 buffer_strcat(wb, end_time_str);
910 else
911 buffer_print_uint64_encoded(wb, integer_encoding, end_time);
912
913 buffer_fast_strcat(wb, " ", 1);
914
915 if(can_copy)
916 buffer_strcat(wb, wall_clock_time_str);
917 else
918 buffer_print_uint64_encoded(wb, integer_encoding, wall_clock_time);
919
920 buffer_fast_strcat(wb, "\n", 1);
921
922 parser->user.v2.stream_buffer.last_point_end_time_s = end_time;
923 parser->user.v2.stream_buffer.begin_v2_added = true;
924 }
925
926 timing_step(TIMING_STEP_BEGIN2_PROPAGATE);
927
928 // ------------------------------------------------------------------------
929 // store it
930
931 st->last_collected_time.tv_sec = end_time;
932 st->last_collected_time.tv_usec = 0;
933 st->last_updated.tv_sec = end_time;
934 st->last_updated.tv_usec = 0;
935 st->counter++;
936 st->counter_done++;
937
938 // these are only needed for db mode RAM, ALLOC
939 st->db.current_entry++;
940 if(st->db.current_entry >= st->db.entries)
941 st->db.current_entry -= st->db.entries;
942
943 timing_step(TIMING_STEP_BEGIN2_STORE);
944
945 return PARSER_RC_OK;
946 }
947
948 static ALWAYS_INLINE PARSER_RC pluginsd_set_v2(char **words, size_t num_words, PARSER *parser) {
949 timing_init();
950
951 int idx = 1;
952 ssize_t slot = pluginsd_parse_rrd_slot(words, num_words, PLUGINSD_DIMENSION_SLOT_MAX);
953 if(slot >= 0) idx++;
954
955 char *dimension = get_word(words, num_words, idx++);
956 char *collected_str = get_word(words, num_words, idx++);
957 char *value_str = get_word(words, num_words, idx++);
958 char *flags_str = get_word(words, num_words, idx++);
959
960 if(unlikely(!dimension || !collected_str || !value_str || !flags_str))
961 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_SET_V2, "missing parameters");
962
963 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_SET_V2);
964 if(unlikely(!host)) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
965
966 RRDSET *st = pluginsd_require_scope_chart(parser, PLUGINSD_KEYWORD_SET_V2, PLUGINSD_KEYWORD_BEGIN_V2);
967 if(unlikely(!st)) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
968
969 timing_step(TIMING_STEP_SET2_PREPARE);
970
971 RRDDIM *rd = pluginsd_acquire_dimension(host, st, dimension, slot, PLUGINSD_KEYWORD_SET_V2);
972 if(unlikely(!rd)) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
973
974 st->pluginsd.set = true;
975
976 if(unlikely(rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE))) {
977 if(!spinlock_trylock(&rd->destroy_lock))
978 fatal("PLUGINSD: dimension '%s' of chart '%s' is being collected while is being destroyed.", rrddim_id(rd), rrdset_id(st));
979
980 rrddim_isnot_obsolete___safe_from_collector_thread(st, rd);
981 spinlock_unlock(&rd->destroy_lock);
982 }
983
984 timing_step(TIMING_STEP_SET2_LOOKUP_DIMENSION);
985
986 // ------------------------------------------------------------------------
987 // parse the parameters
988
989 // The sender only sends float baselines when it has STREAM_CAP_FLOAT_BASELINE;
990 // older senders always send int64, even for float dimensions.
991 bool sender_sent_float = rrddim_is_float(rd) && stream_has_capability(&parser->user, STREAM_CAP_FLOAT_BASELINE);
992
993 collected_number collected_value = 0;
994 NETDATA_DOUBLE collected_value_d = 0.0;
995 if(sender_sent_float)
996 collected_value_d = str2ndd_encoded(collected_str, NULL);
997 else
998 collected_value = (collected_number) str2ll_encoded(collected_str);
999
1000 NETDATA_DOUBLE value;
1001 if(*value_str == '#')
1002 value = sender_sent_float ? collected_value_d : (NETDATA_DOUBLE)collected_value;
1003 else
1004 value = str2ndd_encoded(value_str, NULL);
1005
1006 SN_FLAGS flags = pluginsd_parse_storage_number_flags(flags_str);
1007
1008 timing_step(TIMING_STEP_SET2_PARSE);
1009
1010 // ------------------------------------------------------------------------
1011 // check value and ML
1012
1013 if(stream_has_capability(&parser->user, STREAM_CAP_ML_MODELS)) {
1014 // we receive anomaly information, no need for prediction on this node
1015 if (unlikely(!netdata_double_isnumber(value) || (flags == SN_EMPTY_SLOT))) {
1016 value = NAN;
1017 flags = SN_EMPTY_SLOT;
1018 }
1019
1020 if(parser->user.v2.ml_locked)
1021 ml_dimension_received_anomaly(rd, !(flags & SN_FLAG_NOT_ANOMALOUS));
1022 }
1023 else {
1024 // we don't receive anomaly information, we need to run prediction on this node
1025 if (unlikely(!netdata_double_isnumber(value) || (flags == SN_EMPTY_SLOT))) {
1026 value = NAN;
1027 flags = SN_EMPTY_SLOT;
1028
1029 if(parser->user.v2.ml_locked)
1030 ml_dimension_is_anomalous(rd, parser->user.v2.end_time, 0, false);
1031 }
1032 else if(parser->user.v2.ml_locked) {
1033 if (ml_dimension_is_anomalous(rd, parser->user.v2.end_time, value, true)) {
1034 // clear anomaly bit: 0 -> is anomalous, 1 -> not anomalous
1035 flags &= ~((storage_number) SN_FLAG_NOT_ANOMALOUS);
1036 }
1037 else
1038 flags |= SN_FLAG_NOT_ANOMALOUS;
1039 }
1040 }
1041
1042 timing_step(TIMING_STEP_SET2_ML);
1043
1044 // ------------------------------------------------------------------------
1045 // propagate it forward in v2
1046
1047 if(parser->user.v2.stream_buffer.v2 && parser->user.v2.stream_buffer.begin_v2_added && parser->user.v2.stream_buffer.wb) {
1048 // check if receiver and sender have the same number parsing capabilities
1049 bool can_copy = stream_has_capability(&parser->user, STREAM_CAP_IEEE754) == stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_IEEE754);
1050
1051 // check if the float baseline capability matches between incoming and outgoing
1052 bool downstream_float = rrddim_is_float(rd) && stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_FLOAT_BASELINE);
1053 if(sender_sent_float != downstream_float)
1054 can_copy = false;
1055
1056 // check the downstream parent capabilities
1057 bool with_slots = stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_SLOTS) ? true : false;
1058 NUMBER_ENCODING integer_encoding = stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_HEX;
1059 NUMBER_ENCODING doubles_encoding = stream_has_capability(&parser->user.v2.stream_buffer, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_DECIMAL;
1060
1061 BUFFER *wb = parser->user.v2.stream_buffer.wb;
1062 buffer_need_bytes(wb, 1024);
1063 buffer_fast_strcat(wb, PLUGINSD_KEYWORD_SET_V2, sizeof(PLUGINSD_KEYWORD_SET_V2) - 1);
1064
1065 if(with_slots) {
1066 buffer_fast_strcat(wb, " "PLUGINSD_KEYWORD_SLOT":", sizeof(PLUGINSD_KEYWORD_SLOT) - 1 + 2);
1067 buffer_print_uint64_encoded(wb, integer_encoding, rd->stream.snd.dim_slot);
1068 }
1069
1070 buffer_fast_strcat(wb, " '", 2);
1071 buffer_fast_strcat(wb, rrddim_id(rd), string_strlen(rd->id));
1072 buffer_fast_strcat(wb, "' ", 2);
1073 if(can_copy)
1074 buffer_strcat(wb, collected_str);
1075 else if(downstream_float)
1076 buffer_print_netdata_double_encoded(wb, doubles_encoding, sender_sent_float ? collected_value_d : (NETDATA_DOUBLE)collected_value);
1077 else
1078 buffer_print_int64_encoded(wb, integer_encoding, sender_sent_float ? (int64_t)collected_value_d : collected_value);
1079 buffer_fast_strcat(wb, " ", 1);
1080 if(can_copy)
1081 buffer_strcat(wb, value_str);
1082 else
1083 buffer_print_netdata_double_encoded(wb, doubles_encoding, value); // original v2 had decimal
1084 buffer_fast_strcat(wb, " ", 1);
1085 buffer_print_sn_flags(wb, flags, true);
1086 buffer_fast_strcat(wb, "\n", 1);
1087 }
1088
1089 timing_step(TIMING_STEP_SET2_PROPAGATE);
1090
1091 // ------------------------------------------------------------------------
1092 // store it
1093
1094 rrddim_store_metric(rd, parser->user.v2.end_time * USEC_PER_SEC, value, flags);
1095 rd->collector.last_collected_time.tv_sec = parser->user.v2.end_time;
1096 rd->collector.last_collected_time.tv_usec = 0;
1097 if(sender_sent_float)
1098 rrddim_set_last_collected_float(rd, collected_value_d);
1099 else if(rrddim_is_float(rd))
1100 rrddim_set_last_collected_float(rd, (NETDATA_DOUBLE)collected_value);
1101 else
1102 rrddim_set_last_collected_int(rd, collected_value);
1103 rd->collector.last_stored_value = value;
1104 rd->collector.last_calculated_value = value;
1105 rd->collector.counter++;
1106 rrddim_set_updated(rd);
1107
1108 timing_step(TIMING_STEP_SET2_STORE);
1109
1110 return PARSER_RC_OK;
1111 }
1112
1113 static ALWAYS_INLINE PARSER_RC pluginsd_end_v2(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser) {
1114 timing_init();
1115
1116 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_END_V2);
1117 if(unlikely(!host)) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
1118
1119 RRDSET *st = pluginsd_require_scope_chart(parser, PLUGINSD_KEYWORD_END_V2, PLUGINSD_KEYWORD_BEGIN_V2);
1120 if(unlikely(!st)) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
1121
1122 parser->user.data_collections_count++;
1123
1124 timing_step(TIMING_STEP_END2_PREPARE);
1125
1126 // ------------------------------------------------------------------------
1127 // propagate the whole chart update in v1
1128
1129 if(unlikely(!parser->user.v2.stream_buffer.v2 && !parser->user.v2.stream_buffer.begin_v2_added && parser->user.v2.stream_buffer.wb))
1130 stream_send_rrdset_metrics_v1(&parser->user.v2.stream_buffer, st);
1131
1132 timing_step(TIMING_STEP_END2_PUSH_V1);
1133
1134 // ------------------------------------------------------------------------
1135 // unblock data collection
1136
1137 rrdset_previous_scope_chart_unlock(parser, PLUGINSD_KEYWORD_END_V2, false);
1138 rrdcontext_collected_rrdset(st);
1139 store_metric_collection_completed();
1140
1141 timing_step(TIMING_STEP_END2_RRDSET);
1142
1143 // ------------------------------------------------------------------------
1144 // propagate it forward
1145
1146 stream_send_rrdset_metrics_finished(&parser->user.v2.stream_buffer, st);
1147
1148 timing_step(TIMING_STEP_END2_PROPAGATE);
1149
1150 // ------------------------------------------------------------------------
1151 // cleanup RRDSET / RRDDIM
1152
1153 // Get the array - we're protected by collector_tid being set, so it won't be freed
1154 PRD_ARRAY *prd_arr = prd_array_get_unsafe(&st->pluginsd.prd_array);
1155
1156 if(likely(st->pluginsd.dims_with_slots && prd_arr && prd_arr->size)) {
1157 for(size_t i = 0; i < prd_arr->size ;i++) {
1158 RRDDIM *rd = prd_arr->entries[i].rd;
1159
1160 if(!rd)
1161 continue;
1162
1163 rd->collector.calculated_value = 0;
1164 if(rrddim_is_float(rd)) {
1165 rrddim_set_collected_float(rd, 0.0);
1166 }
1167 else
1168 rrddim_set_collected_int(rd, 0);
1169 rrddim_clear_updated(rd);
1170 }
1171 }
1172 else {
1173 RRDDIM *rd;
1174 rrddim_foreach_read(rd, st){
1175 rd->collector.calculated_value = 0;
1176 if(rrddim_is_float(rd)) {
1177 rrddim_set_collected_float(rd, 0.0);
1178 }
1179 else
1180 rrddim_set_collected_int(rd, 0);
1181 rrddim_clear_updated(rd);
1182 }
1183 rrddim_foreach_done(rd);
1184 }
1185
1186 // ------------------------------------------------------------------------
1187 // reset state
1188
1189 parser->user.v2 = (struct parser_user_object_v2){ 0 };
1190
1191 timing_step(TIMING_STEP_END2_STORE);
1192 timing_report();
1193
1194 return PARSER_RC_OK;
1195 }
1196
1197 static inline PARSER_RC pluginsd_trust_durations(char **words, size_t num_words, PARSER *parser) {
1198 char *value = get_word(words, num_words, 1);
1199
1200 if (!value || !*value)
1201 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_TRUST_DURATIONS, "missing parameter");
1202
1203 int trusted = str2i(value);
1204 if (trusted != 0 && trusted != 1)
1205 return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_TRUST_DURATIONS, "parameter must be 0 or 1");
1206
1207 parser->user.trust_durations = trusted;
1208
1209 netdata_log_debug(D_PLUGINSD, "PLUGINSD: trust durations set to %d", trusted);
1210
1211 return PARSER_RC_OK;
1212 }
1213
1214 static inline PARSER_RC pluginsd_exit(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser __maybe_unused) {
1215 netdata_log_info("PLUGINSD: plugin called EXIT.");
1216 return PARSER_RC_STOP;
1217 }
1218
1219 static inline PARSER_RC pluginsd_plugin_keepalive(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser __maybe_unused) {
1220 return PARSER_RC_OK;
1221 }
1222
1223 static void pluginsd_json_stream_paths(PARSER *parser, void *action_data __maybe_unused) {
1224 stream_path_set_from_json(parser->user.host, buffer_tostring(parser->defer.response), false);
1225 buffer_free(parser->defer.response);
1226 }
1227
1228 static void pluginsd_json_ml_model(PARSER *parser, void *action_data __maybe_unused) {
1229 ml_model_received_from_child(parser->user.host, buffer_tostring(parser->defer.response));
1230 buffer_free(parser->defer.response);
1231 }
1232
1233 static void pluginsd_json_dev_null(PARSER *parser, void *action_data __maybe_unused) {
1234 buffer_free(parser->defer.response);
1235 }
1236
1237 static PARSER_RC pluginsd_json(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser) {
1238 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_JSON);
1239 if(!host) return PLUGINSD_DISABLE_PLUGIN(parser, NULL, NULL);
1240
1241 char *keyword = get_word(words, num_words, 1);
1242
1243 parser->defer.response = buffer_create(0, NULL);
1244 parser->defer.end_keyword = PLUGINSD_KEYWORD_JSON_END;
1245 parser->defer.action = pluginsd_json_dev_null;
1246 parser->defer.action_data = NULL;
1247 parser->flags |= PARSER_DEFER_UNTIL_KEYWORD;
1248
1249 if(strcmp(keyword, PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH) == 0)
1250 parser->defer.action = pluginsd_json_stream_paths;
1251 else if(strcmp(keyword, PLUGINSD_KEYWORD_JSON_CMD_ML_MODEL) == 0)
1252 parser->defer.action = pluginsd_json_ml_model;
1253 else
1254 netdata_log_error("PLUGINSD: invalid JSON payload keyword '%s'", keyword);
1255
1256 return PARSER_RC_OK;
1257 }
1258
1259 PARSER_RC stream_receiver_pluginsd_claimed_id(char **words, size_t num_words, PARSER *parser);
1260
1261 // ----------------------------------------------------------------------------
1262
1263 void pluginsd_cleanup_v2(PARSER *parser) {
1264 // this is called when the thread is stopped while processing
1265 pluginsd_clear_scope_chart(parser, "THREAD CLEANUP", NULL);
1266 }
1267
1268 void pluginsd_process_cleanup(PARSER *parser) {
1269 if(!parser) return;
1270
1271 pluginsd_cleanup_v2(parser);
1272 pluginsd_host_define_cleanup(parser);
1273
1274 rrdlabels_destroy(parser->user.new_host_labels);
1275 parser->user.clabel_count = 0;
1276
1277 parser_destroy(parser);
1278 }
1279
1280 bool parser_reconstruct_node(BUFFER *wb, void *ptr) {
1281 PARSER *parser = ptr;
1282 if(!parser || !parser->user.host)
1283 return false;
1284
1285 buffer_strcat(wb, rrdhost_hostname(parser->user.host));
1286 return true;
1287 }
1288
1289 bool parser_reconstruct_instance(BUFFER *wb, void *ptr) {
1290 PARSER *parser = ptr;
1291 if(!parser || !parser->user.st)
1292 return false;
1293
1294 buffer_strcat(wb, rrdset_name(parser->user.st));
1295 return true;
1296 }
1297
1298 bool parser_reconstruct_context(BUFFER *wb, void *ptr) {
1299 PARSER *parser = ptr;
1300 if(!parser || !parser->user.st)
1301 return false;
1302
1303 buffer_strcat(wb, string2str(parser->user.st->context));
1304 return true;
1305 }
1306
1307 inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, int fd_input, int fd_output, int trust_durations)
1308 {
1309 int enabled = cd->unsafe.enabled;
1310
1311 if (fd_input == -1 || fd_output == -1 || !enabled) {
1312 cd->unsafe.enabled = 0;
1313 return 0;
1314 }
1315
1316 PARSER *parser;
1317 {
1318 PARSER_USER_OBJECT user = {
1319 .enabled = cd->unsafe.enabled,
1320 .host = host,
1321 .cd = cd,
1322 .trust_durations = trust_durations
1323 };
1324
1325 parser = parser_init(&user, fd_input, fd_output, PARSER_INPUT_SPLIT, NULL);
1326 }
1327
1328 pluginsd_keywords_init(parser, PARSER_INIT_PLUGINSD);
1329
1330 rrd_collector_started();
1331
1332 size_t count = 0;
1333
1334 ND_LOG_STACK lgs[] = {
1335 ND_LOG_FIELD_CB(NDF_REQUEST, line_splitter_reconstruct_line, &parser->line),
1336 ND_LOG_FIELD_CB(NDF_NIDL_NODE, parser_reconstruct_node, parser),
1337 ND_LOG_FIELD_CB(NDF_NIDL_INSTANCE, parser_reconstruct_instance, parser),
1338 ND_LOG_FIELD_CB(NDF_NIDL_CONTEXT, parser_reconstruct_context, parser),
1339 ND_LOG_FIELD_END(),
1340 };
1341 ND_LOG_STACK_PUSH(lgs);
1342
1343 buffered_reader_init(&parser->reader);
1344 CLEAN_BUFFER *buffer = buffer_create(sizeof(parser->reader.read_buffer) + 2, NULL);
1345 bool send_quit = true;
1346 while(likely(service_running(SERVICE_COLLECTORS))) {
1347
1348 if(unlikely(!buffered_reader_next_line(&parser->reader, buffer))) {
1349 buffered_reader_ret_t ret = buffered_reader_read_timeout(
1350 &parser->reader, parser->fd_input,
1351 2 * 60 * MSEC_PER_SEC, true);
1352
1353 if(unlikely(ret != BUFFERED_READER_READ_OK)) {
1354 nd_log(NDLS_COLLECTORS, NDLP_INFO, "PLUGINSD: buffered reader not OK (%d)", ret);
1355 if(ret == BUFFERED_READER_READ_POLLERR || ret == BUFFERED_READER_READ_POLLHUP)
1356 send_quit = false;
1357 break;
1358 }
1359
1360 continue;
1361 }
1362
1363 if(unlikely(parser_action(parser, buffer->buffer)))
1364 break;
1365
1366 buffer->len = 0;
1367 buffer->buffer[0] = '\0';
1368 }
1369
1370 if(send_quit) {
1371 nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
1372 "PLUGINSD: sending '"PLUGINSD_CALL_QUIT"' to plugin: %s",
1373 string2str(cd->filename));
1374
1375 send_to_plugin(PLUGINSD_CALL_QUIT, parser, STREAM_TRAFFIC_TYPE_METADATA);
1376 }
1377
1378 cd->unsafe.enabled = parser->user.enabled;
1379 count = parser->user.data_collections_count;
1380
1381 if(likely(count)) {
1382 cd->successful_collections += count;
1383 cd->serial_failures = 0;
1384 }
1385 else
1386 cd->serial_failures++;
1387
1388 {
1389 Word_t Index = 0;
1390 bool first_then_next = true;
1391 while (JudyLFirstThenNext(parser->user.vnodes.JudyL, &Index, &first_then_next)) {
1392 RRDHOST *virtual_host = (RRDHOST *) Index;
1393 nd_log_daemon(NDLP_INFO, "PLUGINSD: Checking virtual status for %s", rrdhost_hostname(virtual_host));
1394 if (rrdhost_option_check(virtual_host, RRDHOST_OPTION_VIRTUAL_HOST)) {
1395 nd_log_daemon(NDLP_INFO, "PLUGINSD: Reseting virtual host status for %s", rrdhost_hostname(virtual_host));
1396 rrdhost_option_clear(virtual_host, RRDHOST_OPTION_VIRTUAL_HOST);
1397 rrdhost_flag_clear(virtual_host, RRDHOST_FLAG_COLLECTOR_ONLINE);
1398 schedule_node_state_update(virtual_host, 1000);
1399 }
1400 }
1401 (void) JudyLFreeArray(&parser->user.vnodes.JudyL, PJE0);
1402 }
1403
1404 // mark all charts of this plugin as obsolete
1405 RRDSET *st;
1406 rrdset_foreach_read(st, localhost) {
1407 if(st->collector_tid == gettid_cached())
1408 rrdset_is_obsolete___safe_from_collector_thread(st);
1409 }
1410 rrdset_foreach_done(st);
1411
1412 pluginsd_process_cleanup(parser);
1413 rrd_collector_finished();
1414
1415 return count;
1416 }
1417
1418 #include "gperf-hashtable.h"
1419
1420 ALWAYS_INLINE PARSER_RC parser_execute(PARSER *parser, const PARSER_KEYWORD *keyword, char **words, size_t num_words) {
1421 // put all the keywords ordered by the frequency they are used
1422
1423 switch(keyword->id) {
1424 case PLUGINSD_KEYWORD_ID_SET2:
1425 return pluginsd_set_v2(words, num_words, parser);
1426 case PLUGINSD_KEYWORD_ID_BEGIN2:
1427 return pluginsd_begin_v2(words, num_words, parser);
1428 case PLUGINSD_KEYWORD_ID_END2:
1429 return pluginsd_end_v2(words, num_words, parser);
1430 case PLUGINSD_KEYWORD_ID_SET:
1431 return pluginsd_set(words, num_words, parser);
1432 case PLUGINSD_KEYWORD_ID_BEGIN:
1433 return pluginsd_begin(words, num_words, parser);
1434 case PLUGINSD_KEYWORD_ID_END:
1435 return pluginsd_end(words, num_words, parser);
1436 case PLUGINSD_KEYWORD_ID_RSET:
1437 return pluginsd_replay_set(words, num_words, parser);
1438 case PLUGINSD_KEYWORD_ID_RBEGIN:
1439 return pluginsd_replay_begin(words, num_words, parser);
1440 case PLUGINSD_KEYWORD_ID_RDSTATE:
1441 return pluginsd_replay_rrddim_collection_state(words, num_words, parser);
1442 case PLUGINSD_KEYWORD_ID_RSSTATE:
1443 return pluginsd_replay_rrdset_collection_state(words, num_words, parser);
1444 case PLUGINSD_KEYWORD_ID_REND:
1445 return pluginsd_replay_end(words, num_words, parser);
1446 case PLUGINSD_KEYWORD_ID_DIMENSION:
1447 return pluginsd_dimension(words, num_words, parser);
1448 case PLUGINSD_KEYWORD_ID_CHART:
1449 return pluginsd_chart(words, num_words, parser);
1450 case PLUGINSD_KEYWORD_ID_CHART_DEFINITION_END:
1451 return pluginsd_chart_definition_end(words, num_words, parser);
1452 case PLUGINSD_KEYWORD_ID_CLABEL:
1453 return pluginsd_clabel(words, num_words, parser);
1454 case PLUGINSD_KEYWORD_ID_CLABEL_COMMIT:
1455 return pluginsd_clabel_commit(words, num_words, parser);
1456 case PLUGINSD_KEYWORD_ID_FUNCTION:
1457 return pluginsd_function(words, num_words, parser);
1458 case PLUGINSD_KEYWORD_ID_FUNCTION_RESULT_BEGIN:
1459 return pluginsd_function_result_begin(words, num_words, parser);
1460 case PLUGINSD_KEYWORD_ID_FUNCTION_PROGRESS:
1461 return pluginsd_function_progress(words, num_words, parser);
1462 case PLUGINSD_KEYWORD_ID_JSON:
1463 return pluginsd_json(words, num_words, parser);
1464 case PLUGINSD_KEYWORD_ID_LABEL:
1465 return pluginsd_label(words, num_words, parser);
1466 case PLUGINSD_KEYWORD_ID_OVERWRITE:
1467 return pluginsd_overwrite(words, num_words, parser);
1468 case PLUGINSD_KEYWORD_ID_VARIABLE:
1469 return pluginsd_variable(words, num_words, parser);
1470 case PLUGINSD_KEYWORD_ID_CLAIMED_ID:
1471 return stream_receiver_pluginsd_claimed_id(words, num_words, parser);
1472 case PLUGINSD_KEYWORD_ID_HOST:
1473 return pluginsd_host(words, num_words, parser);
1474 case PLUGINSD_KEYWORD_ID_HOST_DEFINE:
1475 return pluginsd_host_define(words, num_words, parser);
1476 case PLUGINSD_KEYWORD_ID_HOST_DEFINE_END:
1477 return pluginsd_host_define_end(words, num_words, parser);
1478 case PLUGINSD_KEYWORD_ID_HOST_LABEL:
1479 return pluginsd_host_labels(words, num_words, parser);
1480 case PLUGINSD_KEYWORD_ID_FLUSH:
1481 return pluginsd_flush(words, num_words, parser);
1482 case PLUGINSD_KEYWORD_ID_DISABLE:
1483 return pluginsd_disable(words, num_words, parser);
1484 case PLUGINSD_KEYWORD_ID_EXIT:
1485 return pluginsd_exit(words, num_words, parser);
1486 case PLUGINSD_KEYWORD_ID_CONFIG:
1487 return pluginsd_config(words, num_words, parser);
1488 case PLUGINSD_KEYWORD_ID_TRUST_DURATIONS:
1489 return pluginsd_trust_durations(words, num_words, parser);
1490 case PLUGINSD_KEYWORD_ID_PLUGIN_KEEPALIVE:
1491 return pluginsd_plugin_keepalive(words, num_words, parser);
1492
1493 case PLUGINSD_KEYWORD_ID_DYNCFG_ENABLE:
1494 case PLUGINSD_KEYWORD_ID_DYNCFG_REGISTER_MODULE:
1495 case PLUGINSD_KEYWORD_ID_DYNCFG_REGISTER_JOB:
1496 case PLUGINSD_KEYWORD_ID_DYNCFG_RESET:
1497 case PLUGINSD_KEYWORD_ID_REPORT_JOB_STATUS:
1498 case PLUGINSD_KEYWORD_ID_DELETE_JOB:
1499 return pluginsd_dyncfg_noop(words, num_words, parser);
1500
1501 default:
1502 netdata_log_error("Unknown keyword '%s' with id %zu", keyword->keyword, keyword->id);
1503 return PARSER_RC_ERROR;;
1504 }
1505 }
1506
1507 void parser_init_repertoire(PARSER *parser, PARSER_REPERTOIRE repertoire) {
1508 parser->repertoire = repertoire;
1509
1510 for(size_t i = GPERF_PARSER_MIN_HASH_VALUE ; i <= GPERF_PARSER_MAX_HASH_VALUE ;i++) {
1511 if(gperf_keywords[i].keyword && *gperf_keywords[i].keyword && (parser->repertoire & gperf_keywords[i].repertoire))
1512 worker_register_job_name(gperf_keywords[i].worker_job_id, gperf_keywords[i].keyword);
1513 }
1514 }
1515
1516 static int pluginsd_parser_unittest_slot_bounds(size_t max_slot) {
1517 // The boundary cases below build "max_slot - 1", so a zero cap would underflow.
1518 // All real callers pass nonzero compile-time caps; guard against misuse anyway.
1519 if(max_slot < 1) {
1520 netdata_log_error("PLUGINSD: slot bounds unittest requires max_slot >= 1, got %zu", max_slot);
1521 return 1;
1522 }
1523
1524 // Note on initialization: every element below is given an explicit
1525 // initializer, so C zero-fills the remainder of each slot_word array. The
1526 // trailing three entries start empty and are filled from max_slot at runtime.
1527 struct slot_test_case {
1528 char slot_word[64];
1529 ssize_t expected;
1530 } cases[] = {
1531 { "", -1 }, // no SLOT word -> -1 (caller must not advance idx)
1532 { PLUGINSD_KEYWORD_SLOT ":0", 0 }, // explicit zero -> uncached
1533 { PLUGINSD_KEYWORD_SLOT ":1", 1 }, // smallest cached slot
1534 { PLUGINSD_KEYWORD_SLOT ":-1", 0 }, // negative parses as unsigned 0 -> uncached
1535 { PLUGINSD_KEYWORD_SLOT ":abc", 0 }, // malformed decimal -> 0 -> uncached
1536 { PLUGINSD_KEYWORD_SLOT ":0xZZ", 0 }, // malformed hex -> 0 -> uncached
1537 { PLUGINSD_KEYWORD_SLOT ":0x0AAAAAAAAAAAAAAB", 0 }, // over cap; cast stays positive, would wrap allocation
1538 { PLUGINSD_KEYWORD_SLOT ":0xFFFFFFFFFFFFFFFF", 0 }, // u64 max -> over cap -> uncached
1539 { PLUGINSD_KEYWORD_SLOT ":0x40000000", 0 }, // over both caps -> uncached (the reported OOM value)
1540 { "", 0 }, // filled below: max_slot - 1 (accepted)
1541 { "", 0 }, // filled below: max_slot (accepted, boundary)
1542 { "", 0 }, // filled below: max_slot + 1 (rejected, boundary)
1543 };
1544
1545 const size_t n = _countof(cases);
1546
1547 snprintfz(cases[n - 3].slot_word, sizeof(cases[n - 3].slot_word),
1548 PLUGINSD_KEYWORD_SLOT ":%zu", max_slot - 1);
1549 cases[n - 3].expected = (ssize_t)(max_slot - 1);
1550
1551 snprintfz(cases[n - 2].slot_word, sizeof(cases[n - 2].slot_word),
1552 PLUGINSD_KEYWORD_SLOT ":%zu", max_slot);
1553 cases[n - 2].expected = (ssize_t)max_slot;
1554
1555 snprintfz(cases[n - 1].slot_word, sizeof(cases[n - 1].slot_word),
1556 PLUGINSD_KEYWORD_SLOT ":%zu", max_slot + 1);
1557 cases[n - 1].expected = 0;
1558
1559 for(size_t i = 0; i < _countof(cases); i++) {
1560 char command[] = "DIMENSION";
1561 char *words[] = { command, cases[i].slot_word[0] ? cases[i].slot_word : NULL };
1562 size_t num_words = words[1] ? 2 : 1;
1563
1564 ssize_t slot = pluginsd_parse_rrd_slot(words, num_words, max_slot);
1565 if(slot != cases[i].expected) {
1566 netdata_log_error("PLUGINSD: slot parser unittest failed for '%s': expected %zd, got %zd",
1567 words[1] ? words[1] : "(unset)", cases[i].expected, slot);
1568 return 1;
1569 }
1570 }
1571
1572 return 0;
1573 }
1574
1575 int pluginsd_parser_unittest(void) {
1576 if(pluginsd_parser_unittest_slot_bounds(PLUGINSD_DIMENSION_SLOT_MAX))
1577 return 1;
1578
1579 if(pluginsd_parser_unittest_slot_bounds(PLUGINSD_CHART_SLOT_MAX))
1580 return 1;
1581
1582 PARSER *p = parser_init(NULL, -1, -1, PARSER_INPUT_SPLIT, NULL);
1583 pluginsd_keywords_init(p, PARSER_INIT_PLUGINSD | PARSER_INIT_STREAMING);
1584
1585 char *lines[] = {
1586 "BEGIN2 abcdefghijklmnopqr 123",
1587 "SET2 abcdefg 0x12345678 0 0",
1588 "SET2 hijklmnoqr 0x12345678 0 0",
1589 "SET2 stuvwxyz 0x12345678 0 0",
1590 "END2",
1591 NULL,
1592 };
1593
1594 char *words[PLUGINSD_MAX_WORDS];
1595 size_t iterations = 1000000;
1596 size_t count = 0;
1597 char input[PLUGINSD_LINE_MAX + 1];
1598
1599 usec_t started = now_realtime_usec();
1600 while(--iterations) {
1601 for(size_t line = 0; lines[line] ;line++) {
1602 strncpyz(input, lines[line], PLUGINSD_LINE_MAX);
1603 size_t num_words = quoted_strings_splitter_pluginsd(input, words, PLUGINSD_MAX_WORDS);
1604 const char *command = get_word(words, num_words, 0);
1605 const PARSER_KEYWORD *keyword = parser_find_keyword(p, command);
1606 if(unlikely(!keyword))
1607 fatal("Cannot parse the line '%s'", lines[line]);
1608 count++;
1609 }
1610 }
1611 usec_t ended = now_realtime_usec();
1612
1613 netdata_log_info("Parsed %zu lines in %0.2f secs, %0.2f klines/sec", count,
1614 (double)(ended - started) / (double)USEC_PER_SEC,
1615 (double)count / ((double)(ended - started) / (double)USEC_PER_SEC) / 1000.0);
1616
1617 parser_destroy(p);
1618 return 0;
1619 }