plugin_tc.c 45 KB

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