master
c 1,157 lines 43.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "database/rrd.h"
4
5 #define RRD_TYPE_TC "tc"
6 #define PLUGIN_TC_NAME "tc.plugin"
7
8 // ----------------------------------------------------------------------------
9 // /sbin/tc processor
10 // this requires the script plugins.d/tc-qos-helper.sh
11
12 #define TC_LINE_MAX 1024
13
14 struct tc_class {
15 STRING *id;
16 STRING *name;
17 STRING *leafid;
18 STRING *parentid;
19
20 bool hasparent;
21 bool isleaf;
22 bool isqdisc;
23 bool render;
24 bool name_updated;
25 bool updated;
26
27 int unupdated; // the number of times, this has been found un-updated
28
29 unsigned long long bytes;
30 unsigned long long packets;
31 unsigned long long dropped;
32 unsigned long long tokens;
33 unsigned long long ctokens;
34
35 //unsigned long long overlimits;
36 //unsigned long long requeues;
37 //unsigned long long lended;
38 //unsigned long long borrowed;
39 //unsigned long long giants;
40
41 RRDDIM *rd_bytes;
42 RRDDIM *rd_packets;
43 RRDDIM *rd_dropped;
44 RRDDIM *rd_tokens;
45 RRDDIM *rd_ctokens;
46 };
47
48 struct tc_device {
49 STRING *id;
50 STRING *name;
51 STRING *family;
52
53 bool name_updated;
54 bool family_updated;
55
56 char enabled;
57 char enabled_bytes;
58 char enabled_packets;
59 char enabled_dropped;
60 char enabled_tokens;
61 char enabled_ctokens;
62 char enabled_all_classes_qdiscs;
63
64 RRDSET *st_bytes;
65 RRDSET *st_packets;
66 RRDSET *st_dropped;
67 RRDSET *st_tokens;
68 RRDSET *st_ctokens;
69
70 DICTIONARY *classes;
71 };
72
73
74 // ----------------------------------------------------------------------------
75 // tc_class index
76
77 static void tc_class_free_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
78 // struct tc_device *d = data;
79 struct tc_class *c = value;
80
81 string_freez(c->id);
82 string_freez(c->name);
83 string_freez(c->leafid);
84 string_freez(c->parentid);
85 }
86
87 static bool tc_class_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
88 struct tc_device *d = data; (void)d;
89 struct tc_class *c = old_value; (void)c;
90 struct tc_class *new_c = new_value; (void)new_c;
91
92 collector_error("TC: class '%s' is already in device '%s'. Ignoring duplicate.", dictionary_acquired_item_name(item), string2str(d->id));
93
94 tc_class_free_callback(item, new_value, data);
95
96 return true;
97 }
98
99 static void tc_class_index_init(struct tc_device *d) {
100 if(!d->classes) {
101 d->classes = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(struct tc_class));
102
103 dictionary_register_delete_callback(d->classes, tc_class_free_callback, d);
104 dictionary_register_conflict_callback(d->classes, tc_class_conflict_callback, d);
105 }
106 }
107
108 static void tc_class_index_destroy(struct tc_device *d) {
109 dictionary_destroy(d->classes);
110 d->classes = NULL;
111 }
112
113 static struct tc_class *tc_class_index_add(struct tc_device *d, struct tc_class *c) {
114 return dictionary_set(d->classes, string2str(c->id), c, sizeof(struct tc_class));
115 }
116
117 static void tc_class_index_del(struct tc_device *d, struct tc_class *c) {
118 dictionary_del(d->classes, string2str(c->id));
119 }
120
121 static inline struct tc_class *tc_class_index_find(struct tc_device *d, const char *id) {
122 return dictionary_get(d->classes, id);
123 }
124
125 // ----------------------------------------------------------------------------
126 // tc_device index
127
128 static DICTIONARY *tc_device_root_index = NULL;
129
130 static void tc_device_add_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
131 struct tc_device *d = value;
132 tc_class_index_init(d);
133 }
134
135 static void tc_device_free_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
136 struct tc_device *d = value;
137
138 tc_class_index_destroy(d);
139
140 string_freez(d->id);
141 string_freez(d->name);
142 string_freez(d->family);
143 }
144
145 static void tc_device_index_init() {
146 if(!tc_device_root_index) {
147 tc_device_root_index = dictionary_create_advanced(
148 DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_SINGLE_THREADED | DICT_OPTION_ADD_IN_FRONT,
149 &dictionary_stats_category_collectors, 0);
150
151 dictionary_register_insert_callback(tc_device_root_index, tc_device_add_callback, NULL);
152 dictionary_register_delete_callback(tc_device_root_index, tc_device_free_callback, NULL);
153 }
154 }
155
156 static void tc_device_index_destroy() {
157 dictionary_destroy(tc_device_root_index);
158 tc_device_root_index = NULL;
159 }
160
161 static struct tc_device *tc_device_index_add(struct tc_device *d) {
162 return dictionary_set(tc_device_root_index, string2str(d->id), d, sizeof(*d));
163 }
164
165 //static struct tc_device *tc_device_index_del(struct tc_device *d) {
166 // dictionary_del(tc_device_root_index, string2str(d->id));
167 // return d;
168 //}
169
170 static inline struct tc_device *tc_device_index_find(const char *id) {
171 return dictionary_get(tc_device_root_index, id);
172 }
173
174 // ----------------------------------------------------------------------------
175
176 static inline void tc_class_free(struct tc_device *n, struct tc_class *c) {
177 netdata_log_debug(D_TC_LOOP, "Removing from device '%s' class '%s', parentid '%s', leafid '%s', unused=%d",
178 string2str(n->id), string2str(c->id), string2str(c->parentid), string2str(c->leafid),
179 c->unupdated);
180
181 tc_class_index_del(n, c);
182 }
183
184 static inline void tc_device_classes_cleanup(struct tc_device *d) {
185 static int cleanup_every = 999;
186
187 if(unlikely(cleanup_every > 0)) {
188 cleanup_every = (int) inicfg_get_number(&netdata_config, "plugin:tc", "cleanup unused classes every", 120);
189 if(cleanup_every < 0) cleanup_every = -cleanup_every;
190 }
191
192 d->name_updated = false;
193 d->family_updated = false;
194
195 struct tc_class *c;
196 dfe_start_write(d->classes, c) {
197 if(unlikely(cleanup_every && c->unupdated >= cleanup_every))
198 tc_class_free(d, c);
199
200 else {
201 c->updated = false;
202 c->name_updated = false;
203 }
204 }
205 dfe_done(c);
206 }
207
208 static inline void tc_device_commit(struct tc_device *d) {
209 static int enable_tokens = -1, enable_ctokens = -1, enabled_all_classes_qdiscs = -1;
210
211 if(unlikely(enabled_all_classes_qdiscs == -1)) {
212 enable_tokens = inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", "enable tokens charts for all interfaces", CONFIG_BOOLEAN_NO);
213 enable_ctokens = inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", "enable ctokens charts for all interfaces", CONFIG_BOOLEAN_NO);
214 enabled_all_classes_qdiscs = inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", "enable show all classes and qdiscs for all interfaces", CONFIG_BOOLEAN_NO);
215 }
216
217 if(unlikely(d->enabled == (char)-1)) {
218 d->enabled = CONFIG_BOOLEAN_YES;
219 d->enabled_bytes = CONFIG_BOOLEAN_YES;
220 d->enabled_packets = CONFIG_BOOLEAN_YES;
221 d->enabled_dropped = CONFIG_BOOLEAN_YES;
222 d->enabled_tokens = enable_tokens;
223 d->enabled_ctokens = enable_ctokens;
224 d->enabled_all_classes_qdiscs = enabled_all_classes_qdiscs;
225
226
227 char var_name[CONFIG_MAX_NAME + 1];
228
229 snprintfz(var_name, CONFIG_MAX_NAME, "qos for %s", string2str(d->id));
230 if (inicfg_exists(&netdata_config, "plugin:tc", var_name))
231 d->enabled = (char)inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", var_name, d->enabled);
232
233 snprintfz(var_name, CONFIG_MAX_NAME, "traffic chart for %s", string2str(d->id));
234 if (inicfg_exists(&netdata_config, "plugin:tc", var_name))
235 d->enabled_bytes = (char)inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", var_name, d->enabled_bytes);
236
237 snprintfz(var_name, CONFIG_MAX_NAME, "packets chart for %s", string2str(d->id));
238 if (inicfg_exists(&netdata_config, "plugin:tc", var_name))
239 d->enabled_packets = (char)inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", var_name, d->enabled_packets);
240
241 snprintfz(var_name, CONFIG_MAX_NAME, "dropped packets chart for %s", string2str(d->id));
242 if (inicfg_exists(&netdata_config, "plugin:tc", var_name))
243 d->enabled_dropped = (char)inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", var_name, d->enabled_dropped);
244
245 snprintfz(var_name, CONFIG_MAX_NAME, "tokens chart for %s", string2str(d->id));
246 if (inicfg_exists(&netdata_config, "plugin:tc", var_name))
247 d->enabled_tokens = (char)inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", var_name, d->enabled_tokens);
248
249 snprintfz(var_name, CONFIG_MAX_NAME, "ctokens chart for %s", string2str(d->id));
250 if (inicfg_exists(&netdata_config, "plugin:tc", var_name))
251 d->enabled_ctokens = (char)inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", var_name, d->enabled_ctokens);
252
253 snprintfz(var_name, CONFIG_MAX_NAME, "show all classes for %s", string2str(d->id));
254 if (inicfg_exists(&netdata_config, "plugin:tc", var_name))
255 d->enabled_all_classes_qdiscs =
256 (char)inicfg_get_boolean_ondemand(&netdata_config, "plugin:tc", var_name, d->enabled_all_classes_qdiscs);
257 }
258
259 // we only need to add leaf classes
260 struct tc_class *c, *x /*, *root = NULL */;
261 unsigned long long bytes_sum = 0, packets_sum = 0, dropped_sum = 0, tokens_sum = 0, ctokens_sum = 0;
262 int active_nodes = 0, updated_classes = 0, updated_qdiscs = 0;
263
264 // prepare all classes
265 // we set reasonable defaults for the rest of the code below
266
267 dfe_start_read(d->classes, c) {
268 c->render = false; // do not render this class
269 c->isleaf = true; // this is a leaf class
270 c->hasparent = false; // without a parent
271
272 if(unlikely(!c->updated))
273 c->unupdated++; // increase its unupdated counter
274 else {
275 c->unupdated = 0; // reset its unupdated counter
276
277 // count how many of each kind
278 if(c->isqdisc)
279 updated_qdiscs++;
280 else
281 updated_classes++;
282 }
283 }
284 dfe_done(c);
285
286 if(unlikely(!d->enabled || (!updated_classes && !updated_qdiscs))) {
287 netdata_log_debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. It is not enabled/updated.", string2str(d->name?d->name:d->id));
288 tc_device_classes_cleanup(d);
289 return;
290 }
291
292 if(unlikely(updated_classes && updated_qdiscs)) {
293 collector_error("TC: device '%s' has active both classes (%d) and qdiscs (%d). Will render only qdiscs.", string2str(d->id), updated_classes, updated_qdiscs);
294
295 // set all classes to !updated
296 dfe_start_read(d->classes, c) {
297 if (unlikely(!c->isqdisc && c->updated))
298 c->updated = false;
299 }
300 dfe_done(c);
301 updated_classes = 0;
302 }
303
304 // mark the classes as leafs and parents
305 //
306 // TC is hierarchical:
307 // - classes can have other classes in them
308 // - the same is true for qdiscs (i.e. qdiscs have classes, that have other qdiscs)
309 //
310 // we need to present a chart with leaf nodes only, so that the sum
311 // of all dimensions of the chart, will be the total utilization
312 // of the interface.
313 //
314 // here we try to find the ones we need to report
315 // by default all nodes are marked with: isleaf = 1 (see above)
316 //
317 // so, here we remove the isleaf flag from nodes in the middle
318 // and we add the hasparent flag to leaf nodes we found their parent
319 if(likely(!d->enabled_all_classes_qdiscs)) {
320 dfe_start_read(d->classes, c) {
321 if(unlikely(!c->updated))
322 continue;
323
324 //netdata_log_debug(D_TC_LOOP, "TC: In device '%s', %s '%s' has leafid: '%s' and parentid '%s'.",
325 // d->id,
326 // c->isqdisc?"qdisc":"class",
327 // c->id,
328 // c->leafid?c->leafid:"NULL",
329 // c->parentid?c->parentid:"NULL");
330
331 // find if c is leaf or not
332 dfe_start_read(d->classes, x) {
333 if(unlikely(!x->updated || c == x || !x->parentid))
334 continue;
335
336 // classes have both parentid and leafid
337 // qdiscs have only parentid
338 // the following works for both (it is an OR)
339
340 if((x->parentid && c->id == x->parentid) ||
341 (c->leafid && x->parentid && c->leafid == x->parentid)) {
342 // netdata_log_debug(D_TC_LOOP, "TC: In device '%s', %s '%s' (leafid: '%s') has as leaf %s '%s' (parentid: '%s').", d->name?d->name:d->id, c->isqdisc?"qdisc":"class", c->name?c->name:c->id, c->leafid?c->leafid:c->id, x->isqdisc?"qdisc":"class", x->name?x->name:x->id, x->parentid?x->parentid:x->id);
343 c->isleaf = false;
344 x->hasparent = true;
345 }
346 }
347 dfe_done(x);
348 }
349 dfe_done(c);
350 }
351
352 dfe_start_read(d->classes, c) {
353 if(unlikely(!c->updated))
354 continue;
355
356 // netdata_log_debug(D_TC_LOOP, "TC: device '%s', %s '%s' isleaf=%d, hasparent=%d", d->id, (c->isqdisc)?"qdisc":"class", c->id, c->isleaf, c->hasparent);
357
358 if(unlikely((c->isleaf && c->hasparent) || d->enabled_all_classes_qdiscs)) {
359 c->render = true;
360 active_nodes++;
361 bytes_sum += c->bytes;
362 packets_sum += c->packets;
363 dropped_sum += c->dropped;
364 tokens_sum += c->tokens;
365 ctokens_sum += c->ctokens;
366 }
367
368 //if(unlikely(!c->hasparent)) {
369 // if(root) collector_error("TC: multiple root class/qdisc for device '%s' (old: '%s', new: '%s')", d->id, root->id, c->id);
370 // root = c;
371 // netdata_log_debug(D_TC_LOOP, "TC: found root class/qdisc '%s'", root->id);
372 //}
373 }
374 dfe_done(c);
375
376 #ifdef NETDATA_INTERNAL_CHECKS
377 // dump all the list to see what we know
378
379 if(unlikely(debug_flags & D_TC_LOOP)) {
380 dfe_start_read(d->classes, c) {
381 if(c->render) netdata_log_debug(D_TC_LOOP, "TC: final nodes dump for '%s': class %s, OK", string2str(d->name), string2str(c->id));
382 else netdata_log_debug(D_TC_LOOP, "TC: final nodes dump for '%s': class '%s', IGNORE (updated: %d, isleaf: %d, hasparent: %d, parent: '%s')",
383 string2str(d->name?d->name:d->id), string2str(c->id), c->updated, c->isleaf, c->hasparent, string2str(c->parentid));
384 }
385 dfe_done(c);
386 }
387 #endif
388
389 if(unlikely(!active_nodes)) {
390 netdata_log_debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No useful classes/qdiscs.", string2str(d->name?d->name:d->id));
391 tc_device_classes_cleanup(d);
392 return;
393 }
394
395 // --------------------------------------------------------------------
396 // bytes
397
398 if (d->enabled_bytes == CONFIG_BOOLEAN_YES || d->enabled_bytes == CONFIG_BOOLEAN_AUTO) {
399 d->enabled_bytes = CONFIG_BOOLEAN_YES;
400
401 if(unlikely(!d->st_bytes)) {
402 d->st_bytes = rrdset_create_localhost(
403 RRD_TYPE_TC,
404 string2str(d->id),
405 string2str(d->name ? d->name : d->id),
406 string2str(d->family ? d->family : d->id),
407 RRD_TYPE_TC ".qos",
408 "Class Usage",
409 "kilobits/s",
410 PLUGIN_TC_NAME,
411 NULL,
412 NETDATA_CHART_PRIO_TC_QOS,
413 localhost->rrd_update_every,
414 d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED);
415
416 rrdlabels_add(d->st_bytes->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO);
417 rrdlabels_add(d->st_bytes->rrdlabels, "device_name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO);
418 rrdlabels_add(d->st_bytes->rrdlabels, "device_group", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO);
419 }
420 else {
421 if(unlikely(d->name_updated))
422 rrdset_reset_name(d->st_bytes, string2str(d->name));
423
424 if(d->name && d->name_updated)
425 rrdlabels_add(d->st_bytes->rrdlabels, "device_name", string2str(d->name), RRDLABEL_SRC_AUTO);
426
427 if(d->family && d->family_updated)
428 rrdlabels_add(d->st_bytes->rrdlabels, "device_group", string2str(d->family), RRDLABEL_SRC_AUTO);
429
430 // TODO
431 // update the family
432 }
433
434 dfe_start_read(d->classes, c) {
435 if(unlikely(!c->render)) continue;
436
437 if(unlikely(!c->rd_bytes))
438 c->rd_bytes = rrddim_add(d->st_bytes, string2str(c->id), string2str(c->name?c->name:c->id), 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
439 else if(unlikely(c->name_updated))
440 rrddim_reset_name(d->st_bytes, c->rd_bytes, string2str(c->name));
441
442 rrddim_set_by_pointer(d->st_bytes, c->rd_bytes, c->bytes);
443 }
444 dfe_done(c);
445
446 rrdset_done(d->st_bytes);
447 }
448
449 // --------------------------------------------------------------------
450 // packets
451
452 if (d->enabled_packets == CONFIG_BOOLEAN_YES || d->enabled_packets == CONFIG_BOOLEAN_AUTO) {
453 d->enabled_packets = CONFIG_BOOLEAN_YES;
454
455 if(unlikely(!d->st_packets)) {
456 char id[RRD_ID_LENGTH_MAX + 1];
457 char name[RRD_ID_LENGTH_MAX + 1];
458 snprintfz(id, RRD_ID_LENGTH_MAX, "%s_packets", string2str(d->id));
459 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_packets", string2str(d->name ? d->name : d->id));
460
461 d->st_packets = rrdset_create_localhost(
462 RRD_TYPE_TC,
463 id,
464 name,
465 string2str(d->family ? d->family : d->id),
466 RRD_TYPE_TC ".qos_packets",
467 "Class Packets",
468 "packets/s",
469 PLUGIN_TC_NAME,
470 NULL,
471 NETDATA_CHART_PRIO_TC_QOS_PACKETS,
472 localhost->rrd_update_every,
473 d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED);
474
475 rrdlabels_add(d->st_packets->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO);
476 rrdlabels_add(d->st_packets->rrdlabels, "device_name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO);
477 rrdlabels_add(d->st_packets->rrdlabels, "device_group", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO);
478 }
479 else {
480 if(unlikely(d->name_updated)) {
481 char name[RRD_ID_LENGTH_MAX + 1];
482 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_packets", string2str(d->name?d->name:d->id));
483 rrdset_reset_name(d->st_packets, name);
484 }
485
486 if(d->name && d->name_updated)
487 rrdlabels_add(d->st_packets->rrdlabels, "device_name", string2str(d->name), RRDLABEL_SRC_AUTO);
488
489 if(d->family && d->family_updated)
490 rrdlabels_add(d->st_packets->rrdlabels, "device_group", string2str(d->family), RRDLABEL_SRC_AUTO);
491
492 // TODO
493 // update the family
494 }
495
496 dfe_start_read(d->classes, c) {
497 if(unlikely(!c->render)) continue;
498
499 if(unlikely(!c->rd_packets))
500 c->rd_packets = rrddim_add(d->st_packets, string2str(c->id), string2str(c->name?c->name:c->id), 1, 1, RRD_ALGORITHM_INCREMENTAL);
501 else if(unlikely(c->name_updated))
502 rrddim_reset_name(d->st_packets, c->rd_packets, string2str(c->name));
503
504 rrddim_set_by_pointer(d->st_packets, c->rd_packets, c->packets);
505 }
506 dfe_done(c);
507
508 rrdset_done(d->st_packets);
509 }
510
511 // --------------------------------------------------------------------
512 // dropped
513
514 if (d->enabled_dropped == CONFIG_BOOLEAN_YES || d->enabled_dropped == CONFIG_BOOLEAN_AUTO) {
515 d->enabled_dropped = CONFIG_BOOLEAN_YES;
516
517 if(unlikely(!d->st_dropped)) {
518 char id[RRD_ID_LENGTH_MAX + 1];
519 char name[RRD_ID_LENGTH_MAX + 1];
520 snprintfz(id, RRD_ID_LENGTH_MAX, "%s_dropped", string2str(d->id));
521 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_dropped", string2str(d->name ? d->name : d->id));
522
523 d->st_dropped = rrdset_create_localhost(
524 RRD_TYPE_TC,
525 id,
526 name,
527 string2str(d->family ? d->family : d->id),
528 RRD_TYPE_TC ".qos_dropped",
529 "Class Dropped Packets",
530 "packets/s",
531 PLUGIN_TC_NAME,
532 NULL,
533 NETDATA_CHART_PRIO_TC_QOS_DROPPED,
534 localhost->rrd_update_every,
535 d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED);
536
537 rrdlabels_add(d->st_dropped->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO);
538 rrdlabels_add(d->st_dropped->rrdlabels, "device_name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO);
539 rrdlabels_add(d->st_dropped->rrdlabels, "device_group", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO);
540 }
541 else {
542 if(unlikely(d->name_updated)) {
543 char name[RRD_ID_LENGTH_MAX + 1];
544 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_dropped", string2str(d->name?d->name:d->id));
545 rrdset_reset_name(d->st_dropped, name);
546 }
547
548 if(d->name && d->name_updated)
549 rrdlabels_add(d->st_dropped->rrdlabels, "device_name", string2str(d->name), RRDLABEL_SRC_AUTO);
550
551 if(d->family && d->family_updated)
552 rrdlabels_add(d->st_dropped->rrdlabels, "device_group", string2str(d->family), RRDLABEL_SRC_AUTO);
553
554 // TODO
555 // update the family
556 }
557
558 dfe_start_read(d->classes, c) {
559 if(unlikely(!c->render)) continue;
560
561 if(unlikely(!c->rd_dropped))
562 c->rd_dropped = rrddim_add(d->st_dropped, string2str(c->id), string2str(c->name?c->name:c->id), 1, 1, RRD_ALGORITHM_INCREMENTAL);
563 else if(unlikely(c->name_updated))
564 rrddim_reset_name(d->st_dropped, c->rd_dropped, string2str(c->name));
565
566 rrddim_set_by_pointer(d->st_dropped, c->rd_dropped, c->dropped);
567 }
568 dfe_done(c);
569
570 rrdset_done(d->st_dropped);
571 }
572
573 // --------------------------------------------------------------------
574 // tokens
575
576 if (d->enabled_tokens == CONFIG_BOOLEAN_YES || d->enabled_tokens == CONFIG_BOOLEAN_AUTO) {
577 d->enabled_tokens = CONFIG_BOOLEAN_YES;
578
579 if(unlikely(!d->st_tokens)) {
580 char id[RRD_ID_LENGTH_MAX + 1];
581 char name[RRD_ID_LENGTH_MAX + 1];
582 snprintfz(id, RRD_ID_LENGTH_MAX, "%s_tokens", string2str(d->id));
583 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_tokens", string2str(d->name ? d->name : d->id));
584
585 d->st_tokens = rrdset_create_localhost(
586 RRD_TYPE_TC,
587 id,
588 name,
589 string2str(d->family ? d->family : d->id),
590 RRD_TYPE_TC ".qos_tokens",
591 "Class Tokens",
592 "tokens",
593 PLUGIN_TC_NAME,
594 NULL,
595 NETDATA_CHART_PRIO_TC_QOS_TOKENS,
596 localhost->rrd_update_every,
597 RRDSET_TYPE_LINE);
598
599 rrdlabels_add(d->st_tokens->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO);
600 rrdlabels_add(d->st_tokens->rrdlabels, "device_name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO);
601 rrdlabels_add(d->st_tokens->rrdlabels, "device_group", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO);
602 }
603 else {
604 if(unlikely(d->name_updated)) {
605 char name[RRD_ID_LENGTH_MAX + 1];
606 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_tokens", string2str(d->name?d->name:d->id));
607 rrdset_reset_name(d->st_tokens, name);
608 }
609
610 if(d->name && d->name_updated)
611 rrdlabels_add(d->st_tokens->rrdlabels, "device_name", string2str(d->name), RRDLABEL_SRC_AUTO);
612
613 if(d->family && d->family_updated)
614 rrdlabels_add(d->st_tokens->rrdlabels, "device_group", string2str(d->family), RRDLABEL_SRC_AUTO);
615
616 // TODO
617 // update the family
618 }
619
620 dfe_start_read(d->classes, c) {
621 if(unlikely(!c->render)) continue;
622
623 if(unlikely(!c->rd_tokens)) {
624 c->rd_tokens = rrddim_add(d->st_tokens, string2str(c->id), string2str(c->name?c->name:c->id), 1, 1, RRD_ALGORITHM_ABSOLUTE);
625 }
626 else if(unlikely(c->name_updated))
627 rrddim_reset_name(d->st_tokens, c->rd_tokens, string2str(c->name));
628
629 rrddim_set_by_pointer(d->st_tokens, c->rd_tokens, c->tokens);
630 }
631 dfe_done(c);
632
633 rrdset_done(d->st_tokens);
634 }
635
636 // --------------------------------------------------------------------
637 // ctokens
638
639 if (d->enabled_ctokens == CONFIG_BOOLEAN_YES || d->enabled_ctokens == CONFIG_BOOLEAN_AUTO) {
640 d->enabled_ctokens = CONFIG_BOOLEAN_YES;
641
642 if(unlikely(!d->st_ctokens)) {
643 char id[RRD_ID_LENGTH_MAX + 1];
644 char name[RRD_ID_LENGTH_MAX + 1];
645 snprintfz(id, RRD_ID_LENGTH_MAX, "%s_ctokens", string2str(d->id));
646 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_ctokens", string2str(d->name ? d->name : d->id));
647
648 d->st_ctokens = rrdset_create_localhost(
649 RRD_TYPE_TC,
650 id,
651 name,
652 string2str(d->family ? d->family : d->id),
653 RRD_TYPE_TC ".qos_ctokens",
654 "Class cTokens",
655 "ctokens",
656 PLUGIN_TC_NAME,
657 NULL,
658 NETDATA_CHART_PRIO_TC_QOS_CTOKENS,
659 localhost->rrd_update_every,
660 RRDSET_TYPE_LINE);
661
662 rrdlabels_add(d->st_ctokens->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO);
663 rrdlabels_add(d->st_ctokens->rrdlabels, "device_name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO);
664 rrdlabels_add(d->st_ctokens->rrdlabels, "device_group", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO);
665 }
666 else {
667 netdata_log_debug(D_TC_LOOP, "TC: Updating _ctokens chart for device '%s'", string2str(d->name?d->name:d->id));
668
669 if(unlikely(d->name_updated)) {
670 char name[RRD_ID_LENGTH_MAX + 1];
671 snprintfz(name, RRD_ID_LENGTH_MAX, "%s_ctokens", string2str(d->name?d->name:d->id));
672 rrdset_reset_name(d->st_ctokens, name);
673 }
674
675 if(d->name && d->name_updated)
676 rrdlabels_add(d->st_ctokens->rrdlabels, "device_name", string2str(d->name), RRDLABEL_SRC_AUTO);
677
678 if(d->family && d->family_updated)
679 rrdlabels_add(d->st_ctokens->rrdlabels, "device_group", string2str(d->family), RRDLABEL_SRC_AUTO);
680
681 // TODO
682 // update the family
683 }
684
685 dfe_start_read(d->classes, c) {
686 if(unlikely(!c->render)) continue;
687
688 if(unlikely(!c->rd_ctokens))
689 c->rd_ctokens = rrddim_add(d->st_ctokens, string2str(c->id), string2str(c->name?c->name:c->id), 1, 1, RRD_ALGORITHM_ABSOLUTE);
690 else if(unlikely(c->name_updated))
691 rrddim_reset_name(d->st_ctokens, c->rd_ctokens, string2str(c->name));
692
693 rrddim_set_by_pointer(d->st_ctokens, c->rd_ctokens, c->ctokens);
694 }
695 dfe_done(c);
696
697 rrdset_done(d->st_ctokens);
698 }
699
700 tc_device_classes_cleanup(d);
701 }
702
703 static inline void tc_device_set_class_name(struct tc_device *d, char *id, char *name) {
704 if(unlikely(!name || !*name)) return;
705
706 struct tc_class *c = tc_class_index_find(d, id);
707 if(likely(c)) {
708 if(likely(c->name)) {
709 if(!strcmp(string2str(c->name), name)) return;
710 string_freez(c->name);
711 c->name = NULL;
712 }
713
714 if(likely(name && *name && strcmp(string2str(c->id), name) != 0)) {
715 netdata_log_debug(D_TC_LOOP, "TC: Setting device '%s', class '%s' name to '%s'", string2str(d->id), id, name);
716 c->name = string_strdupz(name);
717 c->name_updated = true;
718 }
719 }
720 }
721
722 static inline void tc_device_set_device_name(struct tc_device *d, char *name) {
723 if(unlikely(!name || !*name)) return;
724
725 if(d->name) {
726 if(!strcmp(string2str(d->name), name)) return;
727 string_freez(d->name);
728 d->name = NULL;
729 }
730
731 if(likely(name && *name && strcmp(string2str(d->id), name) != 0)) {
732 netdata_log_debug(D_TC_LOOP, "TC: Setting device '%s' name to '%s'", string2str(d->id), name);
733 d->name = string_strdupz(name);
734 d->name_updated = true;
735 }
736 }
737
738 static inline void tc_device_set_device_family(struct tc_device *d, char *family) {
739 string_freez(d->family);
740 d->family = NULL;
741
742 if(likely(family && *family && strcmp(string2str(d->id), family) != 0)) {
743 netdata_log_debug(D_TC_LOOP, "TC: Setting device '%s' family to '%s'", string2str(d->id), family);
744 d->family = string_strdupz(family);
745 d->family_updated = true;
746 }
747 // no need for null termination - it is already null
748 }
749
750 static inline struct tc_device *tc_device_create(char *id) {
751 struct tc_device *d = tc_device_index_find(id);
752
753 if(!d) {
754 netdata_log_debug(D_TC_LOOP, "TC: Creating device '%s'", id);
755
756 struct tc_device tmp = {
757 .id = string_strdupz(id),
758 .enabled = (char)-1,
759 };
760 d = tc_device_index_add(&tmp);
761 }
762
763 return(d);
764 }
765
766 static inline struct tc_class *tc_class_add(struct tc_device *n, char *id, bool qdisc, char *parentid, char *leafid) {
767 struct tc_class *c = tc_class_index_find(n, id);
768
769 if(!c) {
770 netdata_log_debug(D_TC_LOOP, "TC: Creating in device '%s', class id '%s', parentid '%s', leafid '%s'",
771 string2str(n->id), id, parentid?parentid:"", leafid?leafid:"");
772
773 struct tc_class tmp = {
774 .id = string_strdupz(id),
775 .isqdisc = qdisc,
776 .parentid = string_strdupz(parentid),
777 .leafid = string_strdupz(leafid),
778 };
779
780 tc_class_index_add(n, &tmp);
781 }
782 return(c);
783 }
784
785 //static inline void tc_device_free(struct tc_device *d) {
786 // tc_device_index_del(d);
787 //}
788
789 static inline int tc_space(char c) {
790 switch(c) {
791 case ' ':
792 case '\t':
793 case '\r':
794 case '\n':
795 return 1;
796
797 default:
798 return 0;
799 }
800 }
801
802 static inline void tc_split_words(char *str, char **words, int max_words) {
803 char *s = str;
804 int i = 0;
805
806 // skip all white space
807 while(tc_space(*s)) s++;
808
809 // store the first word
810 words[i++] = s;
811
812 // while we have something
813 while(*s) {
814 // if it is a space
815 if(unlikely(tc_space(*s))) {
816
817 // terminate the word
818 *s++ = '\0';
819
820 // skip all white space
821 while(tc_space(*s)) s++;
822
823 // if we reached the end, stop
824 if(!*s) break;
825
826 // store the next word
827 if(i < max_words) words[i++] = s;
828 else break;
829 }
830 else s++;
831 }
832
833 // terminate the words
834 while(i < max_words) words[i++] = NULL;
835 }
836
837 static POPEN_INSTANCE *tc_child_instance = NULL;
838
839 static void tc_main_cleanup(void *pptr) {
840 struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr);
841 if(!static_thread) return;
842
843 worker_unregister();
844 tc_device_index_destroy();
845
846 static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
847
848 if(tc_child_instance) {
849 collector_info("TC: stopping the running tc-qos-helper script");
850 int code = spawn_popen_wait(tc_child_instance); (void)code;
851 tc_child_instance = NULL;
852 }
853
854 static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
855 }
856
857 #define WORKER_TC_CLASS 0
858 #define WORKER_TC_BEGIN 1
859 #define WORKER_TC_END 2
860 #define WORKER_TC_SENT 3
861 #define WORKER_TC_LENDED 4
862 #define WORKER_TC_TOKENS 5
863 #define WORKER_TC_SETDEVICENAME 6
864 #define WORKER_TC_SETDEVICEGROUP 7
865 #define WORKER_TC_SETCLASSNAME 8
866 #define WORKER_TC_WORKTIME 9
867 #define WORKER_TC_PLUGIN_TIME 10
868 #define WORKER_TC_DEVICES 11
869 #define WORKER_TC_CLASSES 12
870
871 #if WORKER_UTILIZATION_MAX_JOB_TYPES < 13
872 #error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 10
873 #endif
874
875 void tc_main(void *ptr) {
876 CLEANUP_FUNCTION_REGISTER(tc_main_cleanup) cleanup_ptr = ptr;
877
878 worker_register("TC");
879 worker_register_job_name(WORKER_TC_CLASS, "class");
880 worker_register_job_name(WORKER_TC_BEGIN, "begin");
881 worker_register_job_name(WORKER_TC_END, "end");
882 worker_register_job_name(WORKER_TC_SENT, "sent");
883 worker_register_job_name(WORKER_TC_LENDED, "lended");
884 worker_register_job_name(WORKER_TC_TOKENS, "tokens");
885 worker_register_job_name(WORKER_TC_SETDEVICENAME, "devicename");
886 worker_register_job_name(WORKER_TC_SETDEVICEGROUP, "devicegroup");
887 worker_register_job_name(WORKER_TC_SETCLASSNAME, "classname");
888 worker_register_job_name(WORKER_TC_WORKTIME, "worktime");
889
890 worker_register_job_custom_metric(WORKER_TC_PLUGIN_TIME, "tc script execution time", "milliseconds/run", WORKER_METRIC_ABSOLUTE);
891 worker_register_job_custom_metric(WORKER_TC_DEVICES, "number of devices", "devices", WORKER_METRIC_ABSOLUTE);
892 worker_register_job_custom_metric(WORKER_TC_CLASSES, "number of classes", "classes", WORKER_METRIC_ABSOLUTE);
893
894 tc_device_index_init();
895
896 char command[FILENAME_MAX + 1];
897 char *words[PLUGINSD_MAX_WORDS] = { NULL };
898
899 uint32_t BEGIN_HASH = simple_hash("BEGIN");
900 uint32_t END_HASH = simple_hash("END");
901 uint32_t QDISC_HASH = simple_hash("qdisc");
902 uint32_t CLASS_HASH = simple_hash("class");
903 uint32_t SENT_HASH = simple_hash("Sent");
904 uint32_t LENDED_HASH = simple_hash("lended:");
905 uint32_t TOKENS_HASH = simple_hash("tokens:");
906 uint32_t SETDEVICENAME_HASH = simple_hash("SETDEVICENAME");
907 uint32_t SETDEVICEGROUP_HASH = simple_hash("SETDEVICEGROUP");
908 uint32_t SETCLASSNAME_HASH = simple_hash("SETCLASSNAME");
909 uint32_t WORKTIME_HASH = simple_hash("WORKTIME");
910 uint32_t first_hash;
911
912 snprintfz(command, TC_LINE_MAX, "%s/tc-qos-helper.sh", netdata_configured_primary_plugins_dir);
913 const char *tc_script = inicfg_get(&netdata_config, "plugin:tc", "script to run to get tc values", command);
914
915 while(service_running(SERVICE_COLLECTORS)) {
916 struct tc_device *device = NULL;
917 struct tc_class *class = NULL;
918
919 snprintfz(command, TC_LINE_MAX, "exec %s %d", tc_script, localhost->rrd_update_every);
920 netdata_log_debug(D_TC_LOOP, "executing '%s'", command);
921
922 tc_child_instance = spawn_popen_run(command);
923 if(!tc_child_instance) {
924 collector_error("TC: Cannot popen(\"%s\", \"r\").", command);
925 return;
926 }
927
928 char buffer[TC_LINE_MAX+1] = "";
929 while(fgets(buffer, TC_LINE_MAX, spawn_popen_stdout(tc_child_instance)) != NULL) {
930 if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
931
932 buffer[TC_LINE_MAX] = '\0';
933 // netdata_log_debug(D_TC_LOOP, "TC: read '%s'", buffer);
934
935 tc_split_words(buffer, words, PLUGINSD_MAX_WORDS);
936
937 if(unlikely(!words[0] || !*words[0])) {
938 // netdata_log_debug(D_TC_LOOP, "empty line");
939 worker_is_idle();
940 continue;
941 }
942 // else netdata_log_debug(D_TC_LOOP, "First word is '%s'", words[0]);
943
944 first_hash = simple_hash(words[0]);
945
946 if(unlikely(device && ((first_hash == CLASS_HASH && strcmp(words[0], "class") == 0) || (first_hash == QDISC_HASH && strcmp(words[0], "qdisc") == 0)))) {
947 worker_is_busy(WORKER_TC_CLASS);
948
949 // netdata_log_debug(D_TC_LOOP, "CLASS line on class id='%s', parent='%s', parentid='%s', leaf='%s', leafid='%s'", words[2], words[3], words[4], words[5], words[6]);
950
951 char *type = words[1]; // the class/qdisc type: htb, fq_codel, etc
952 char *id = words[2]; // the class/qdisc major:minor
953 char *parent = words[3]; // the word 'parent' or 'root'
954 char *parentid = words[4]; // parentid
955 char *leaf = words[5]; // the word 'leaf'
956 char *leafid = words[6]; // leafid
957
958 int parent_is_root = 0;
959 int parent_is_parent = 0;
960 if(likely(parent)) {
961 parent_is_parent = !strcmp(parent, "parent");
962
963 if(!parent_is_parent)
964 parent_is_root = !strcmp(parent, "root");
965 }
966
967 if(likely(type && id && (parent_is_root || parent_is_parent))) {
968 bool qdisc = false;
969
970 if(first_hash == QDISC_HASH) {
971 qdisc = true;
972
973 if(!strcmp(type, "ingress")) {
974 // we don't want to get the ingress qdisc
975 // there should be an IFB interface for this
976
977 class = NULL;
978 worker_is_idle();
979 continue;
980 }
981
982 if(parent_is_parent && parentid) {
983 // eliminate the minor number from parentid
984 // why: parentid is the id of the parent class
985 // but major: is also the id of the parent qdisc
986
987 char *s = parentid;
988 while(*s && *s != ':') s++;
989 if(*s == ':') s[1] = '\0';
990 }
991 }
992
993 if(parent_is_root) {
994 parentid = NULL;
995 leafid = NULL;
996 }
997 else if(!leaf || strcmp(leaf, "leaf") != 0)
998 leafid = NULL;
999
1000 char leafbuf[20 + 1] = "";
1001 if(leafid && leafid[strlen(leafid) - 1] == ':') {
1002 strncpyz(leafbuf, leafid, 20 - 1);
1003 strcat(leafbuf, "1");
1004 leafid = leafbuf;
1005 }
1006
1007 class = tc_class_add(device, id, qdisc, parentid, leafid);
1008 }
1009 else {
1010 // clear the last class
1011 class = NULL;
1012 }
1013 }
1014 else if(unlikely(first_hash == END_HASH && strcmp(words[0], "END") == 0)) {
1015 worker_is_busy(WORKER_TC_END);
1016
1017 // netdata_log_debug(D_TC_LOOP, "END line");
1018
1019 if(likely(device)) {
1020 tc_device_commit(device);
1021 // tc_device_free(device);
1022 }
1023
1024 device = NULL;
1025 class = NULL;
1026 }
1027 else if(unlikely(first_hash == BEGIN_HASH && strcmp(words[0], "BEGIN") == 0)) {
1028 worker_is_busy(WORKER_TC_BEGIN);
1029
1030 // netdata_log_debug(D_TC_LOOP, "BEGIN line on device '%s'", words[1]);
1031
1032 if(likely(words[1] && *words[1])) {
1033 device = tc_device_create(words[1]);
1034 }
1035 else {
1036 // tc_device_free(device);
1037 device = NULL;
1038 }
1039
1040 class = NULL;
1041 }
1042 else if(unlikely(device && class && first_hash == SENT_HASH && strcmp(words[0], "Sent") == 0)) {
1043 worker_is_busy(WORKER_TC_SENT);
1044
1045 // netdata_log_debug(D_TC_LOOP, "SENT line '%s'", words[1]);
1046 if(likely(words[1] && *words[1])) {
1047 class->bytes = str2ull(words[1], NULL);
1048 class->updated = true;
1049 }
1050 else {
1051 class->updated = false;
1052 }
1053
1054 if(likely(words[3] && *words[3]))
1055 class->packets = str2ull(words[3], NULL);
1056
1057 if(likely(words[6] && *words[6]))
1058 class->dropped = str2ull(words[6], NULL);
1059
1060 //if(likely(words[8] && *words[8]))
1061 // class->overlimits = str2ull(words[8]);
1062
1063 //if(likely(words[10] && *words[10]))
1064 // class->requeues = str2ull(words[8]);
1065 }
1066 else if(unlikely(device && class && class->updated && first_hash == LENDED_HASH && strcmp(words[0], "lended:") == 0)) {
1067 worker_is_busy(WORKER_TC_LENDED);
1068
1069 // netdata_log_debug(D_TC_LOOP, "LENDED line '%s'", words[1]);
1070 //if(likely(words[1] && *words[1]))
1071 // class->lended = str2ull(words[1]);
1072
1073 //if(likely(words[3] && *words[3]))
1074 // class->borrowed = str2ull(words[3]);
1075
1076 //if(likely(words[5] && *words[5]))
1077 // class->giants = str2ull(words[5]);
1078 }
1079 else if(unlikely(device && class && class->updated && first_hash == TOKENS_HASH && strcmp(words[0], "tokens:") == 0)) {
1080 worker_is_busy(WORKER_TC_TOKENS);
1081
1082 // netdata_log_debug(D_TC_LOOP, "TOKENS line '%s'", words[1]);
1083 if(likely(words[1] && *words[1]))
1084 class->tokens = str2ull(words[1], NULL);
1085
1086 if(likely(words[3] && *words[3]))
1087 class->ctokens = str2ull(words[3], NULL);
1088 }
1089 else if(unlikely(device && first_hash == SETDEVICENAME_HASH && strcmp(words[0], "SETDEVICENAME") == 0)) {
1090 worker_is_busy(WORKER_TC_SETDEVICENAME);
1091
1092 // netdata_log_debug(D_TC_LOOP, "SETDEVICENAME line '%s'", words[1]);
1093 if(likely(words[1] && *words[1]))
1094 tc_device_set_device_name(device, words[1]);
1095 }
1096 else if(unlikely(device && first_hash == SETDEVICEGROUP_HASH && strcmp(words[0], "SETDEVICEGROUP") == 0)) {
1097 worker_is_busy(WORKER_TC_SETDEVICEGROUP);
1098
1099 // netdata_log_debug(D_TC_LOOP, "SETDEVICEGROUP line '%s'", words[1]);
1100 if(likely(words[1] && *words[1]))
1101 tc_device_set_device_family(device, words[1]);
1102 }
1103 else if(unlikely(device && first_hash == SETCLASSNAME_HASH && strcmp(words[0], "SETCLASSNAME") == 0)) {
1104 worker_is_busy(WORKER_TC_SETCLASSNAME);
1105
1106 // netdata_log_debug(D_TC_LOOP, "SETCLASSNAME line '%s' '%s'", words[1], words[2]);
1107 char *id = words[1];
1108 char *path = words[2];
1109 if(likely(id && *id && path && *path))
1110 tc_device_set_class_name(device, id, path);
1111 }
1112 else if(unlikely(first_hash == WORKTIME_HASH && strcmp(words[0], "WORKTIME") == 0)) {
1113 worker_is_busy(WORKER_TC_WORKTIME);
1114 worker_set_metric(WORKER_TC_PLUGIN_TIME, str2ll(words[1], NULL));
1115
1116 size_t number_of_devices = dictionary_entries(tc_device_root_index);
1117 size_t number_of_classes = 0;
1118
1119 struct tc_device *d;
1120 dfe_start_read(tc_device_root_index, d) {
1121 number_of_classes += dictionary_entries(d->classes);
1122 }
1123 dfe_done(d);
1124
1125 worker_set_metric(WORKER_TC_DEVICES, number_of_devices);
1126 worker_set_metric(WORKER_TC_CLASSES, number_of_classes);
1127 }
1128 //else {
1129 // netdata_log_debug(D_TC_LOOP, "IGNORED line");
1130 //}
1131
1132 worker_is_idle();
1133 }
1134
1135 // fgets() failed or loop broke
1136 int code = spawn_popen_kill(tc_child_instance, 0);
1137 tc_child_instance = NULL;
1138
1139 if(unlikely(device)) {
1140 // tc_device_free(device);
1141 device = NULL;
1142 class = NULL;
1143 }
1144
1145 if(unlikely(!service_running(SERVICE_COLLECTORS)))
1146 return;
1147
1148 if(code == 1 || code == 127) {
1149 // 1 = DISABLE
1150 // 127 = cannot even run it
1151 collector_error("TC: tc-qos-helper.sh exited with code %d. Disabling it.", code);
1152 return;
1153 }
1154
1155 sleep((unsigned int) localhost->rrd_update_every);
1156 };
1157 }