X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fplugin_tc.c;h=7aef4ab5bc637337068464d66eba4f2cc5cc7473;hb=19c7ffc053db632f4e0fd62e44d323f018895921;hp=408069dba839767908f1f841b3f888d6f135457a;hpb=4573404814de3da6605595bbe0a43837fa48d568;p=netdata.git diff --git a/src/plugin_tc.c b/src/plugin_tc.c index 408069db..7aef4ab5 100644 --- a/src/plugin_tc.c +++ b/src/plugin_tc.c @@ -1,7 +1,6 @@ #include "common.h" -#define RRD_TYPE_TC "tc" -#define RRD_TYPE_TC_LEN strlen(RRD_TYPE_TC) +#define RRD_TYPE_TC "tc" // ---------------------------------------------------------------------------- // /sbin/tc processor @@ -25,6 +24,9 @@ struct tc_class { char hasparent; char isleaf; + char isqdisc; + char render; + unsigned long long bytes; unsigned long long packets; unsigned long long dropped; @@ -39,10 +41,12 @@ struct tc_class { RRDDIM *rd_bytes; RRDDIM *rd_packets; RRDDIM *rd_dropped; + RRDDIM *rd_tokens; + RRDDIM *rd_ctokens; char name_updated; char updated; // updated bytes - int seen; // seen in the tc list (even without bytes) + int unupdated; // the number of times, this has been found un-updated struct tc_class *next; struct tc_class *prev; @@ -64,10 +68,15 @@ struct tc_device { char enabled_bytes; char enabled_packets; char enabled_dropped; + char enabled_tokens; + char enabled_ctokens; + char enabled_all_classes_qdiscs; RRDSET *st_bytes; RRDSET *st_packets; RRDSET *st_dropped; + RRDSET *st_tokens; + RRDSET *st_ctokens; avl_tree classes_index; @@ -94,8 +103,8 @@ avl_tree tc_device_root_index = { tc_device_compare }; -#define tc_device_index_add(st) avl_insert(&tc_device_root_index, (avl *)(st)) -#define tc_device_index_del(st) avl_remove(&tc_device_root_index, (avl *)(st)) +#define tc_device_index_add(st) (struct tc_device *)avl_insert(&tc_device_root_index, (avl *)(st)) +#define tc_device_index_del(st) (struct tc_device *)avl_remove(&tc_device_root_index, (avl *)(st)) static inline struct tc_device *tc_device_index_find(const char *id, uint32_t hash) { struct tc_device tmp; @@ -115,8 +124,8 @@ static int tc_class_compare(void* a, void* b) { else return strcmp(((struct tc_class *)a)->id, ((struct tc_class *)b)->id); } -#define tc_class_index_add(st, rd) avl_insert(&((st)->classes_index), (avl *)(rd)) -#define tc_class_index_del(st, rd) avl_remove(&((st)->classes_index), (avl *)(rd)) +#define tc_class_index_add(st, rd) (struct tc_class *)avl_insert(&((st)->classes_index), (avl *)(rd)) +#define tc_class_index_del(st, rd) (struct tc_class *)avl_remove(&((st)->classes_index), (avl *)(rd)) static inline struct tc_class *tc_class_index_find(struct tc_device *st, const char *id, uint32_t hash) { struct tc_class tmp; @@ -138,9 +147,10 @@ static inline void tc_class_free(struct tc_device *n, struct tc_class *c) { if(c->next) c->next->prev = c->prev; if(c->prev) c->prev->next = c->next; - debug(D_TC_LOOP, "Removing from device '%s' class '%s', parentid '%s', leafid '%s', seen=%d", n->id, c->id, c->parentid?c->parentid:"", c->leafid?c->leafid:"", c->seen); + debug(D_TC_LOOP, "Removing from device '%s' class '%s', parentid '%s', leafid '%s', unused=%d", n->id, c->id, c->parentid?c->parentid:"", c->leafid?c->leafid:"", c->unupdated); - tc_class_index_del(n, c); + if(unlikely(tc_class_index_del(n, c) != c)) + error("plugin_tc: INTERNAL ERROR: attempt remove class '%s' from device '%s': removed a different calls", c->id, n->id); freez(c->id); freez(c->name); @@ -153,7 +163,7 @@ static inline void tc_device_classes_cleanup(struct tc_device *d) { static int cleanup_every = 999; if(unlikely(cleanup_every > 0)) { - cleanup_every = (int) config_get_number("plugin:tc", "cleanup unused classes every", 60); + cleanup_every = (int) config_get_number("plugin:tc", "cleanup unused classes every", 120); if(cleanup_every < 0) cleanup_every = -cleanup_every; } @@ -162,7 +172,7 @@ static inline void tc_device_classes_cleanup(struct tc_device *d) { struct tc_class *c = d->classes; while(c) { - if(unlikely(cleanup_every > 0 && c->seen >= cleanup_every)) { + if(unlikely(cleanup_every && c->unupdated >= cleanup_every)) { struct tc_class *nc = c->next; tc_class_free(d, c); c = nc; @@ -177,261 +187,389 @@ static inline void tc_device_classes_cleanup(struct tc_device *d) { } static inline void tc_device_commit(struct tc_device *d) { - static int enable_new_interfaces = -1, enable_bytes = -1, enable_packets = -1, enable_dropped = -1; + 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; if(unlikely(enable_new_interfaces == -1)) { - enable_new_interfaces = config_get_boolean_ondemand("plugin:tc", "enable new interfaces detected at runtime", CONFIG_ONDEMAND_YES); - enable_bytes = config_get_boolean_ondemand("plugin:tc", "enable traffic charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - enable_packets = config_get_boolean_ondemand("plugin:tc", "enable packets charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - enable_dropped = config_get_boolean_ondemand("plugin:tc", "enable dropped charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + enable_new_interfaces = config_get_boolean_ondemand("plugin:tc", "enable new interfaces detected at runtime", CONFIG_ONDEMAND_YES); + enable_bytes = config_get_boolean_ondemand("plugin:tc", "enable traffic charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + enable_packets = config_get_boolean_ondemand("plugin:tc", "enable packets charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + enable_dropped = config_get_boolean_ondemand("plugin:tc", "enable dropped charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + enable_tokens = config_get_boolean_ondemand("plugin:tc", "enable tokens charts for all interfaces", CONFIG_ONDEMAND_NO); + enable_ctokens = config_get_boolean_ondemand("plugin:tc", "enable ctokens charts for all interfaces", CONFIG_ONDEMAND_NO); + enabled_all_classes_qdiscs = config_get_boolean_ondemand("plugin:tc", "enable show all classes and qdiscs for all interfaces", CONFIG_ONDEMAND_NO); + } + + if(unlikely(d->enabled == (char)-1)) { + char var_name[CONFIG_MAX_NAME + 1]; + snprintfz(var_name, CONFIG_MAX_NAME, "qos for %s", d->id); + + d->enabled = (char)config_get_boolean_ondemand("plugin:tc", var_name, enable_new_interfaces); + + snprintfz(var_name, CONFIG_MAX_NAME, "traffic chart for %s", d->id); + d->enabled_bytes = (char)config_get_boolean_ondemand("plugin:tc", var_name, enable_bytes); + + snprintfz(var_name, CONFIG_MAX_NAME, "packets chart for %s", d->id); + d->enabled_packets = (char)config_get_boolean_ondemand("plugin:tc", var_name, enable_packets); + + snprintfz(var_name, CONFIG_MAX_NAME, "dropped packets chart for %s", d->id); + d->enabled_dropped = (char)config_get_boolean_ondemand("plugin:tc", var_name, enable_dropped); + + snprintfz(var_name, CONFIG_MAX_NAME, "tokens chart for %s", d->id); + d->enabled_tokens = (char)config_get_boolean_ondemand("plugin:tc", var_name, enable_tokens); + + snprintfz(var_name, CONFIG_MAX_NAME, "ctokens chart for %s", d->id); + d->enabled_ctokens = (char)config_get_boolean_ondemand("plugin:tc", var_name, enable_ctokens); + + snprintfz(var_name, CONFIG_MAX_NAME, "show all classes for %s", d->id); + d->enabled_all_classes_qdiscs = (char)config_get_boolean_ondemand("plugin:tc", var_name, enabled_all_classes_qdiscs); } // we only need to add leaf classes - struct tc_class *c, *x; - unsigned long long bytes_sum = 0, packets_sum = 0, dropped_sum = 0; - int active_classes = 0; + struct tc_class *c, *x /*, *root = NULL */; + unsigned long long bytes_sum = 0, packets_sum = 0, dropped_sum = 0, tokens_sum = 0, ctokens_sum = 0; + int active_nodes = 0, updated_classes = 0, updated_qdiscs = 0; + + // prepare all classes + // we set reasonable defaults for the rest of the code below - // set all classes for(c = d->classes ; c ; c = c->next) { - c->isleaf = 1; - c->hasparent = 0; + c->render = 0; // do not render this class + + c->isleaf = 1; // this is a leaf class + c->hasparent = 0; // without a parent + + if(unlikely(!c->updated)) + c->unupdated++; // increase its unupdated counter + else { + c->unupdated = 0; // reset its unupdated counter + + // count how many of each kind + if(c->isqdisc) + updated_qdiscs++; + else + updated_classes++; + } + } + + if(unlikely(!d->enabled || (!updated_classes && !updated_qdiscs))) { + debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. It is not enabled/updated.", d->name?d->name:d->id); + tc_device_classes_cleanup(d); + return; + } + + if(unlikely(updated_classes && updated_qdiscs)) { + error("TC: device '%s' has active both classes (%d) and qdiscs (%d). Will render only qdiscs.", d->id, updated_classes, updated_qdiscs); + + // set all classes to !updated + for(c = d->classes ; c ; c = c->next) + if(unlikely(!c->isqdisc && c->updated)) + c->updated = 0; + + updated_classes = 0; } // mark the classes as leafs and parents - for(c = d->classes ; c ; c = c->next) { + // + // TC is hierarchical: + // - classes can have other classes in them + // - the same is true for qdiscs (i.e. qdiscs have classes, that have other qdiscs) + // + // we need to present a chart with leaf nodes only, so that the sum + // of all dimensions of the chart, will be the total utilization + // of the interface. + // + // here we try to find the ones we need to report + // by default all nodes are marked with: isleaf = 1 (see above) + // + // so, here we remove the isleaf flag from nodes in the middle + // and we add the hasparent flag to leaf nodes we found their parent + for(c = d->classes; c; c = c->next) { if(unlikely(!c->updated)) continue; - for(x = d->classes ; x ; x = x->next) { - if(unlikely(!x->updated)) continue; + //debug(D_TC_LOOP, "TC: In device '%s', %s '%s' has leafid: '%s' and parentid '%s'.", + // d->id, + // c->isqdisc?"qdisc":"class", + // c->id, + // c->leafid?c->leafid:"NULL", + // c->parentid?c->parentid:"NULL"); - if(unlikely(c == x)) continue; + // find if c is leaf or not + for(x = d->classes; x; x = x->next) { + if(unlikely(!x->updated || c == x || !x->parentid)) continue; - if(x->parentid && ( - ( c->hash == x->parent_hash && strcmp(c->id, x->parentid) == 0) || - (c->leafid && c->leaf_hash == x->parent_hash && strcmp(c->leafid, x->parentid) == 0))) { - // debug(D_TC_LOOP, "TC: In device '%s', class '%s' (leafid: '%s') has as leaf class '%s' (parentid: '%s').", d->name?d->name:d->id, c->name?c->name:c->id, c->leafid?c->leafid:c->id, x->name?x->name:x->id, x->parentid?x->parentid:x->id); + // classes have both parentid and leafid + // qdiscs have only parentid + // the following works for both (it is an OR) + + if( (c->hash == x->parent_hash && strcmp(c->id, x->parentid) == 0) || + (c->leafid && c->leaf_hash == x->parent_hash && strcmp(c->leafid, x->parentid) == 0)) { + // 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); c->isleaf = 0; x->hasparent = 1; } } } - // debugging: - /* - for ( c = d->classes ; c ; c = c->next) { - if(c->isleaf && c->hasparent) debug(D_TC_LOOP, "TC: Device %s, class %s, OK", d->name, c->id); - else debug(D_TC_LOOP, "TC: Device %s, class %s, IGNORE (isleaf: %d, hasparent: %d, parent: %s)", d->name, c->id, c->isleaf, c->hasparent, c->parentid); - } - */ - - // we need at least a class for(c = d->classes ; c ; c = c->next) { - // debug(D_TC_LOOP, "TC: Device '%s', class '%s', isLeaf=%d, HasParent=%d, Seen=%d", d->name?d->name:d->id, c->name?c->name:c->id, c->isleaf, c->hasparent, c->seen); - if(!c->updated) continue; - if(c->isleaf && c->hasparent) { - active_classes++; + if(unlikely(!c->updated)) continue; + + // 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); + + if(unlikely((c->isleaf && c->hasparent) || d->enabled_all_classes_qdiscs)) { + c->render = 1; + active_nodes++; bytes_sum += c->bytes; packets_sum += c->packets; dropped_sum += c->dropped; + tokens_sum += c->tokens; + ctokens_sum += c->ctokens; + } + + //if(unlikely(!c->hasparent)) { + // if(root) error("TC: multiple root class/qdisc for device '%s' (old: '%s', new: '%s')", d->id, root->id, c->id); + // root = c; + // debug(D_TC_LOOP, "TC: found root class/qdisc '%s'", root->id); + //} + } + +#ifdef NETDATA_INTERNAL_CHECKS + // dump all the list to see what we know + + if(unlikely(debug_flags & D_TC_LOOP)) { + for(c = d->classes ; c ; c = c->next) { + if(c->render) debug(D_TC_LOOP, "TC: final nodes dump for '%s': class %s, OK", d->name, c->id); + else debug(D_TC_LOOP, "TC: final nodes dump for '%s': class %s, IGNORE (updated: %d, isleaf: %d, hasparent: %d, parent: %s)", d->name?d->name:d->id, c->id, c->updated, c->isleaf, c->hasparent, c->parentid?c->parentid:"(unset)"); } } +#endif - if(unlikely(!active_classes)) { - debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No leaf classes.", d->name?d->name:d->id); + if(unlikely(!active_nodes)) { + debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No useful classes/qdiscs.", d->name?d->name:d->id); tc_device_classes_cleanup(d); return; } - if(unlikely(d->enabled == -1)) { - char var_name[CONFIG_MAX_NAME + 1]; - snprintfz(var_name, CONFIG_MAX_NAME, "qos for %s", d->id); - d->enabled = config_get_boolean_ondemand("plugin:tc", var_name, enable_new_interfaces); + 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).", + d->name?d->name:d->id, + d->enabled, enable_new_interfaces, + d->enabled_bytes, enable_bytes, + d->enabled_packets, enable_packets, + d->enabled_dropped, enable_dropped, + d->enabled_tokens, enable_tokens, + d->enabled_ctokens, enable_ctokens, + d->enabled_all_classes_qdiscs, enabled_all_classes_qdiscs, + bytes_sum, + packets_sum, + dropped_sum, + tokens_sum, + ctokens_sum + ); + + // -------------------------------------------------------------------- + // bytes + + if(d->enabled_bytes == CONFIG_ONDEMAND_YES || (d->enabled_bytes == CONFIG_ONDEMAND_ONDEMAND && bytes_sum)) { + d->enabled_bytes = CONFIG_ONDEMAND_YES; + + if(unlikely(!d->st_bytes)) + d->st_bytes = rrdset_create(RRD_TYPE_TC, d->id, d->name?d->name:d->id, d->family?d->family:d->id, RRD_TYPE_TC ".qos", "Class Usage", "kilobits/s", 7000, rrd_update_every, d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED); - snprintfz(var_name, CONFIG_MAX_NAME, "traffic chart for %s", d->id); - d->enabled_bytes = config_get_boolean_ondemand("plugin:tc", var_name, enable_bytes); + else { + rrdset_next(d->st_bytes); + if(unlikely(d->name_updated)) rrdset_set_name(d->st_bytes, d->name); - snprintfz(var_name, CONFIG_MAX_NAME, "packets chart for %s", d->id); - d->enabled_packets = config_get_boolean_ondemand("plugin:tc", var_name, enable_packets); + // FIXME + // update the family + } - snprintfz(var_name, CONFIG_MAX_NAME, "dropped packets chart for %s", d->id); - d->enabled_dropped = config_get_boolean_ondemand("plugin:tc", var_name, enable_dropped); + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->render)) continue; + + if(unlikely(!c->rd_bytes)) + c->rd_bytes = rrddim_add(d->st_bytes, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL); + else if(unlikely(c->name_updated)) + rrddim_set_name(d->st_bytes, c->rd_bytes, c->name); + + rrddim_set_by_pointer(d->st_bytes, c->rd_bytes, c->bytes); + } + rrdset_done(d->st_bytes); } - if(likely(d->enabled)) { - // -------------------------------------------------------------------- - // bytes + // -------------------------------------------------------------------- + // packets - if(d->enabled_bytes == CONFIG_ONDEMAND_YES || (d->enabled_bytes == CONFIG_ONDEMAND_ONDEMAND && bytes_sum)) { - d->enabled_bytes = CONFIG_ONDEMAND_YES; + if(d->enabled_packets == CONFIG_ONDEMAND_YES || (d->enabled_packets == CONFIG_ONDEMAND_ONDEMAND && packets_sum)) { + d->enabled_packets = CONFIG_ONDEMAND_YES; - if(unlikely(!d->st_bytes)) { - d->st_bytes = rrdset_find_bytype(RRD_TYPE_TC, d->id); - if(unlikely(!d->st_bytes)) { - debug(D_TC_LOOP, "TC: Creating new chart for device '%s'", d->name?d->name:d->id); - d->st_bytes = rrdset_create(RRD_TYPE_TC, d->id, d->name?d->name:d->id, d->family?d->family:d->id, RRD_TYPE_TC ".qos", "Class Usage", "kilobits/s", 7000, rrd_update_every, RRDSET_TYPE_STACKED); - } - } - else { - debug(D_TC_LOOP, "TC: Updating chart for device '%s'", d->name?d->name:d->id); - rrdset_next_plugins(d->st_bytes); + if(unlikely(!d->st_packets)) { + char id[RRD_ID_LENGTH_MAX + 1]; + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s_packets", d->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_packets", d->name?d->name:d->id); - if(unlikely(d->name_updated && d->name && strcmp(d->id, d->name) != 0)) { - rrdset_set_name(d->st_bytes, d->name); - d->name_updated = 0; - } + d->st_packets = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_packets", "Class Packets", "packets/s", 7010, rrd_update_every, d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED); + } + else { + rrdset_next(d->st_packets); - // FIXME - // update the family + if(unlikely(d->name_updated)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_packets", d->name?d->name:d->id); + rrdset_set_name(d->st_packets, name); } - for(c = d->classes ; c ; c = c->next) { - if(unlikely(!c->updated)) continue; + // FIXME + // update the family + } - if(c->isleaf && c->hasparent) { - c->seen++; + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->render)) continue; - if(unlikely(!c->rd_bytes)) { - c->rd_bytes = rrddim_find(d->st_bytes, c->id); - if(unlikely(!c->rd_bytes)) { - debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_bytes->id, c->id, c->name); + if(unlikely(!c->rd_packets)) + c->rd_packets = rrddim_add(d->st_packets, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_INCREMENTAL); + else if(unlikely(c->name_updated)) + rrddim_set_name(d->st_packets, c->rd_packets, c->name); - // new class, we have to add it - c->rd_bytes = rrddim_add(d->st_bytes, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL); - } - else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_bytes->id, c->id); - } + rrddim_set_by_pointer(d->st_packets, c->rd_packets, c->packets); + } + rrdset_done(d->st_packets); + } - rrddim_set_by_pointer(d->st_bytes, c->rd_bytes, c->bytes); + // -------------------------------------------------------------------- + // dropped - // if it has a name, different to the id - if(unlikely(c->name_updated && c->name && strcmp(c->id, c->name) != 0)) { - // update the rrd dimension with the new name - debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", d->st_bytes->id, c->rd_bytes->id, c->name); - rrddim_set_name(d->st_bytes, c->rd_bytes, c->name); - } - } - } - rrdset_done(d->st_bytes); - } + if(d->enabled_dropped == CONFIG_ONDEMAND_YES || (d->enabled_dropped == CONFIG_ONDEMAND_ONDEMAND && dropped_sum)) { + d->enabled_dropped = CONFIG_ONDEMAND_YES; - // -------------------------------------------------------------------- - // packets - - if(d->enabled_packets == CONFIG_ONDEMAND_YES || (d->enabled_packets == CONFIG_ONDEMAND_ONDEMAND && packets_sum)) { - d->enabled_packets = CONFIG_ONDEMAND_YES; + if(unlikely(!d->st_dropped)) { + char id[RRD_ID_LENGTH_MAX + 1]; + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s_dropped", d->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_dropped", d->name?d->name:d->id); - if(unlikely(!d->st_packets)) { - char id[RRD_ID_LENGTH_MAX + 1]; - char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s_packets", d->id); - snprintfz(name, RRD_ID_LENGTH_MAX, "%s_packets", d->name?d->name:d->id); + d->st_dropped = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_dropped", "Class Dropped Packets", "packets/s", 7020, rrd_update_every, d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED); + } + else { + rrdset_next(d->st_dropped); - d->st_packets = rrdset_find_bytype(RRD_TYPE_TC, id); - if(unlikely(!d->st_packets)) { - debug(D_TC_LOOP, "TC: Creating new _packets chart for device '%s'", d->name?d->name:d->id); - d->st_packets = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_packets", "Class Packets", "packets/s", 7010, rrd_update_every, RRDSET_TYPE_STACKED); - } + if(unlikely(d->name_updated)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_dropped", d->name?d->name:d->id); + rrdset_set_name(d->st_dropped, name); } - else { - debug(D_TC_LOOP, "TC: Updating _packets chart for device '%s'", d->name?d->name:d->id); - rrdset_next_plugins(d->st_packets); - // FIXME - // update the family - } + // FIXME + // update the family + } - for(c = d->classes ; c ; c = c->next) { - if(unlikely(!c->updated)) continue; + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->render)) continue; - if(c->isleaf && c->hasparent) { - if(unlikely(!c->rd_packets)) { - c->rd_packets = rrddim_find(d->st_packets, c->id); - if(unlikely(!c->rd_packets)) { - debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_packets->id, c->id, c->name); + if(unlikely(!c->rd_dropped)) + c->rd_dropped = rrddim_add(d->st_dropped, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_INCREMENTAL); + else if(unlikely(c->name_updated)) + rrddim_set_name(d->st_dropped, c->rd_dropped, c->name); - // new class, we have to add it - c->rd_packets = rrddim_add(d->st_packets, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_INCREMENTAL); - } - else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_packets->id, c->id); - } + rrddim_set_by_pointer(d->st_dropped, c->rd_dropped, c->dropped); + } + rrdset_done(d->st_dropped); + } - rrddim_set_by_pointer(d->st_packets, c->rd_packets, c->packets); + // -------------------------------------------------------------------- + // tokens - // if it has a name, different to the id - if(unlikely(c->name_updated && c->name && strcmp(c->id, c->name) != 0)) { - // update the rrd dimension with the new name - debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", d->st_packets->id, c->rd_packets->id, c->name); - rrddim_set_name(d->st_packets, c->rd_packets, c->name); - } - } - } - rrdset_done(d->st_packets); + if(d->enabled_tokens == CONFIG_ONDEMAND_YES || (d->enabled_tokens == CONFIG_ONDEMAND_ONDEMAND && tokens_sum)) { + d->enabled_tokens = CONFIG_ONDEMAND_YES; + + if(unlikely(!d->st_tokens)) { + char id[RRD_ID_LENGTH_MAX + 1]; + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s_tokens", d->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_tokens", d->name?d->name:d->id); + + d->st_tokens = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_tokens", "Class Tokens", "tokens", 7030, rrd_update_every, RRDSET_TYPE_LINE); } + else { + rrdset_next(d->st_tokens); - // -------------------------------------------------------------------- - // dropped - - if(d->enabled_dropped == CONFIG_ONDEMAND_YES || (d->enabled_dropped == CONFIG_ONDEMAND_ONDEMAND && dropped_sum)) { - d->enabled_dropped = CONFIG_ONDEMAND_YES; - - if(unlikely(!d->st_dropped)) { - char id[RRD_ID_LENGTH_MAX + 1]; + if(unlikely(d->name_updated)) { char name[RRD_ID_LENGTH_MAX + 1]; - snprintfz(id, RRD_ID_LENGTH_MAX, "%s_dropped", d->id); - snprintfz(name, RRD_ID_LENGTH_MAX, "%s_dropped", d->name?d->name:d->id); - - d->st_dropped = rrdset_find_bytype(RRD_TYPE_TC, id); - if(unlikely(!d->st_dropped)) { - debug(D_TC_LOOP, "TC: Creating new _dropped chart for device '%s'", d->name?d->name:d->id); - d->st_dropped = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_dropped", "Class Dropped Packets", "packets/s", 7020, rrd_update_every, RRDSET_TYPE_STACKED); - } + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_tokens", d->name?d->name:d->id); + rrdset_set_name(d->st_tokens, name); } - else { - debug(D_TC_LOOP, "TC: Updating _dropped chart for device '%s'", d->name?d->name:d->id); - rrdset_next_plugins(d->st_dropped); - // FIXME - // update the family + // FIXME + // update the family + } + + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->render)) continue; + + if(unlikely(!c->rd_tokens)) { + c->rd_tokens = rrddim_add(d->st_tokens, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_ABSOLUTE); } + else if(unlikely(c->name_updated)) + rrddim_set_name(d->st_tokens, c->rd_tokens, c->name); - for(c = d->classes ; c ; c = c->next) { - if(unlikely(!c->updated)) continue; + rrddim_set_by_pointer(d->st_tokens, c->rd_tokens, c->tokens); + } + rrdset_done(d->st_tokens); + } - if(c->isleaf && c->hasparent) { - if(unlikely(!c->rd_dropped)) { - c->rd_dropped = rrddim_find(d->st_dropped, c->id); - if(unlikely(!c->rd_dropped)) { - debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_dropped->id, c->id, c->name); + // -------------------------------------------------------------------- + // ctokens - // new class, we have to add it - c->rd_dropped = rrddim_add(d->st_dropped, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_INCREMENTAL); - } - else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_dropped->id, c->id); - } + if(d->enabled_ctokens == CONFIG_ONDEMAND_YES || (d->enabled_ctokens == CONFIG_ONDEMAND_ONDEMAND && ctokens_sum)) { + d->enabled_ctokens = CONFIG_ONDEMAND_YES; - rrddim_set_by_pointer(d->st_dropped, c->rd_dropped, c->dropped); + if(unlikely(!d->st_ctokens)) { + char id[RRD_ID_LENGTH_MAX + 1]; + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s_ctokens", d->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_ctokens", d->name?d->name:d->id); - // if it has a name, different to the id - if(unlikely(c->name_updated && c->name && strcmp(c->id, c->name) != 0)) { - // update the rrd dimension with the new name - debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", d->st_dropped->id, c->rd_dropped->id, c->name); - rrddim_set_name(d->st_dropped, c->rd_dropped, c->name); - } - } + d->st_ctokens = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_ctokens", "Class cTokens", "ctokens", 7040, rrd_update_every, RRDSET_TYPE_LINE); + } + else { + debug(D_TC_LOOP, "TC: Updating _ctokens chart for device '%s'", d->name?d->name:d->id); + rrdset_next(d->st_ctokens); + + if(unlikely(d->name_updated)) { + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_ctokens", d->name?d->name:d->id); + rrdset_set_name(d->st_ctokens, name); } - rrdset_done(d->st_dropped); + + // FIXME + // update the family + } + + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->render)) continue; + + if(unlikely(!c->rd_ctokens)) + c->rd_ctokens = rrddim_add(d->st_ctokens, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_ABSOLUTE); + else if(unlikely(c->name_updated)) + rrddim_set_name(d->st_ctokens, c->rd_ctokens, c->name); + + rrddim_set_by_pointer(d->st_ctokens, c->rd_ctokens, c->ctokens); } + rrdset_done(d->st_ctokens); } tc_device_classes_cleanup(d); } -static inline void tc_device_set_class_name(struct tc_device *d, char *id, char *name) -{ +static inline void tc_device_set_class_name(struct tc_device *d, char *id, char *name) { + if(unlikely(!name || !*name)) return; + struct tc_class *c = tc_class_index_find(d, id, 0); if(likely(c)) { - freez(c->name); - c->name = NULL; + if(likely(c->name)) { + if(!strcmp(c->name, name)) return; + freez(c->name); + c->name = NULL; + } if(likely(name && *name && strcmp(c->id, name) != 0)) { debug(D_TC_LOOP, "TC: Setting device '%s', class '%s' name to '%s'", d->id, id, name); @@ -442,10 +580,15 @@ static inline void tc_device_set_class_name(struct tc_device *d, char *id, char } static inline void tc_device_set_device_name(struct tc_device *d, char *name) { - freez(d->name); - d->name = NULL; + if(unlikely(!name || !*name)) return; - if(likely(name && *name && strcmp(d->id, name) != 0)) { + if(d->name) { + if(!strcmp(d->name, name)) return; + freez(d->name); + d->name = NULL; + } + + if(likely(name && *name && strcmp(d->id, name))) { debug(D_TC_LOOP, "TC: Setting device '%s' name to '%s'", d->id, name); d->name = strdupz(name); d->name_updated = 1; @@ -475,10 +618,11 @@ static inline struct tc_device *tc_device_create(char *id) d->id = strdupz(id); d->hash = simple_hash(d->id); - d->enabled = -1; + d->enabled = (char)-1; avl_init(&d->classes_index, tc_class_compare); - tc_device_index_add(d); + if(unlikely(tc_device_index_add(d) != d)) + error("plugin_tc: INTERNAL ERROR: removing device '%s' removed a different device.", d->id); if(!tc_device_root) { tc_device_root = d; @@ -493,7 +637,7 @@ static inline struct tc_device *tc_device_create(char *id) return(d); } -static inline struct tc_class *tc_class_add(struct tc_device *n, char *id, char *parentid, char *leafid) +static inline struct tc_class *tc_class_add(struct tc_device *n, char *id, char qdisc, char *parentid, char *leafid) { struct tc_class *c = tc_class_index_find(n, id, 0); @@ -509,6 +653,7 @@ static inline struct tc_class *tc_class_add(struct tc_device *n, char *id, char c->id = strdupz(id); c->hash = simple_hash(c->id); + c->isqdisc = qdisc; if(parentid && *parentid) { c->parentid = strdupz(parentid); c->parent_hash = simple_hash(c->parentid); @@ -519,11 +664,9 @@ static inline struct tc_class *tc_class_add(struct tc_device *n, char *id, char c->leaf_hash = simple_hash(c->leafid); } - tc_class_index_add(n, c); + if(unlikely(tc_class_index_add(n, c) != c)) + error("plugin_tc: INTERNAL ERROR: attempt index class '%s' on device '%s': already exists", c->id, n->id); } - - c->seen = 1; - return(c); } @@ -536,7 +679,8 @@ static inline void tc_device_free(struct tc_device *n) else tc_device_root = n->prev; } - tc_device_index_del(n); + if(unlikely(tc_device_index_del(n) != n)) + error("plugin_tc: INTERNAL ERROR: removing device '%s' removed a different device.", n->id); while(n->classes) tc_class_free(n, n->classes); @@ -602,9 +746,9 @@ static inline void tc_split_words(char *str, char **words, int max_words) { while(i < max_words) words[i++] = NULL; } -pid_t tc_child_pid = 0; +volatile pid_t tc_child_pid = 0; void *tc_main(void *ptr) { - (void)ptr; + struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; info("TC thread created with task id %d", gettid()); @@ -622,6 +766,7 @@ void *tc_main(void *ptr) { uint32_t BEGIN_HASH = simple_hash("BEGIN"); uint32_t END_HASH = simple_hash("END"); + uint32_t QDISC_HASH = simple_hash("qdisc"); uint32_t CLASS_HASH = simple_hash("class"); uint32_t SENT_HASH = simple_hash("Sent"); uint32_t LENDED_HASH = simple_hash("lended:"); @@ -648,11 +793,10 @@ void *tc_main(void *ptr) { snprintfz(buffer, TC_LINE_MAX, "exec %s %d", tc_script, rrd_update_every); debug(D_TC_LOOP, "executing '%s'", buffer); - fp = mypopen(buffer, &tc_child_pid); + fp = mypopen(buffer, (pid_t *)&tc_child_pid); if(unlikely(!fp)) { error("TC: Cannot popen(\"%s\", \"r\").", buffer); - pthread_exit(NULL); - return NULL; + goto cleanup; } while(fgets(buffer, TC_LINE_MAX, fp) != NULL) { @@ -671,25 +815,51 @@ void *tc_main(void *ptr) { first_hash = simple_hash(words[0]); - if(unlikely(device && first_hash == CLASS_HASH && strcmp(words[0], "class") == 0)) { + if(unlikely(device && ((first_hash == CLASS_HASH && strcmp(words[0], "class") == 0) || (first_hash == QDISC_HASH && strcmp(words[0], "qdisc") == 0)))) { // 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]); - // words[1] : class type - // words[2] : N:XX - // words[3] : parent or root - if(likely(words[1] && words[2] && words[3] && (strcmp(words[3], "parent") == 0 || strcmp(words[3], "root") == 0))) { - //char *type = words[1]; // the class: htb, fq_codel, etc + char *type = words[1]; // the class/qdisc type: htb, fq_codel, etc + char *id = words[2]; // the class/qdisc major:minor + char *parent = words[3]; // the word 'parent' or 'root' + char *parentid = words[4]; // parentid + char *leaf = words[5]; // the word 'leaf' + char *leafid = words[6]; // leafid + + int parent_is_root = 0; + int parent_is_parent = 0; + if(likely(parent)) { + parent_is_parent = !strcmp(parent, "parent"); + + if(!parent_is_parent) + parent_is_root = !strcmp(parent, "root"); + } + + if(likely(type && id && (parent_is_root || parent_is_parent))) { + char qdisc = 0; - // we are only interested for HTB classes - //if(strcmp(type, "htb") != 0) continue; + if(first_hash == QDISC_HASH) { + qdisc = 1; - char *id = words[2]; // the class major:minor - char *parent = words[3]; // 'parent' or 'root' - char *parentid = words[4]; // the parent's id - char *leaf = words[5]; // 'leaf' - char *leafid = words[6]; // leafid + if(!strcmp(type, "ingress")) { + // we don't want to get the ingress qdisc + // there should be an IFB interface for this - if(strcmp(parent, "root") == 0) { + class = NULL; + continue; + } + + if(parent_is_parent && parentid) { + // eliminate the minor number from parentid + // why: parentid is the id of the parent class + // but major: is also the id of the parent qdisc + + char *s = parentid; + while(*s && *s != ':') s++; + if(*s == ':') s[1] = '\0'; + } + } + + if(parent_is_root) { parentid = NULL; leafid = NULL; } @@ -703,7 +873,7 @@ void *tc_main(void *ptr) { leafid = leafbuf; } - class = tc_class_add(device, id, parentid, leafid); + class = tc_class_add(device, id, qdisc, parentid, leafid); } else { // clear the last class @@ -743,7 +913,7 @@ void *tc_main(void *ptr) { else if(unlikely(device && class && first_hash == SENT_HASH && strcmp(words[0], "Sent") == 0)) { // debug(D_TC_LOOP, "SENT line '%s'", words[1]); if(likely(words[1] && *words[1])) { - class->bytes = strtoull(words[1], NULL, 10); + class->bytes = str2ull(words[1]); class->updated = 1; } else { @@ -751,35 +921,35 @@ void *tc_main(void *ptr) { } if(likely(words[3] && *words[3])) - class->packets = strtoull(words[3], NULL, 10); + class->packets = str2ull(words[3]); if(likely(words[6] && *words[6])) - class->dropped = strtoull(words[6], NULL, 10); + class->dropped = str2ull(words[6]); if(likely(words[8] && *words[8])) - class->overlimits = strtoull(words[8], NULL, 10); + class->overlimits = str2ull(words[8]); if(likely(words[10] && *words[10])) - class->requeues = strtoull(words[8], NULL, 10); + class->requeues = str2ull(words[8]); } else if(unlikely(device && class && class->updated && first_hash == LENDED_HASH && strcmp(words[0], "lended:") == 0)) { // debug(D_TC_LOOP, "LENDED line '%s'", words[1]); if(likely(words[1] && *words[1])) - class->lended = strtoull(words[1], NULL, 10); + class->lended = str2ull(words[1]); if(likely(words[3] && *words[3])) - class->borrowed = strtoull(words[3], NULL, 10); + class->borrowed = str2ull(words[3]); if(likely(words[5] && *words[5])) - class->giants = strtoull(words[5], NULL, 10); + class->giants = str2ull(words[5]); } else if(unlikely(device && class && class->updated && first_hash == TOKENS_HASH && strcmp(words[0], "tokens:") == 0)) { // debug(D_TC_LOOP, "TOKENS line '%s'", words[1]); if(likely(words[1] && *words[1])) - class->tokens = strtoull(words[1], NULL, 10); + class->tokens = str2ull(words[1]); if(likely(words[3] && *words[3])) - class->ctokens = strtoull(words[3], NULL, 10); + class->ctokens = str2ull(words[3]); } else if(unlikely(device && first_hash == SETDEVICENAME_HASH && strcmp(words[0], "SETDEVICENAME") == 0)) { // debug(D_TC_LOOP, "SETDEVICENAME line '%s'", words[1]); @@ -842,7 +1012,7 @@ void *tc_main(void *ptr) { } // fgets() failed or loop broke - int code = mypclose(fp, tc_child_pid); + int code = mypclose(fp, (pid_t)tc_child_pid); tc_child_pid = 0; if(unlikely(device)) { @@ -853,8 +1023,7 @@ void *tc_main(void *ptr) { if(unlikely(netdata_exit)) { tc_device_free_all(); - pthread_exit(NULL); - return NULL; + goto cleanup; } if(code == 1 || code == 127) { @@ -863,13 +1032,16 @@ void *tc_main(void *ptr) { error("TC: tc-qos-helper.sh exited with code %d. Disabling it.", code); tc_device_free_all(); - pthread_exit(NULL); - return NULL; + goto cleanup; } sleep((unsigned int) rrd_update_every); } +cleanup: + info("TC thread exiting"); + + static_thread->enabled = 0; pthread_exit(NULL); return NULL; }