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, 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_BOOLEAN_YES); enable_bytes = config_get_boolean_ondemand("plugin:tc", "enable traffic charts for all interfaces", CONFIG_BOOLEAN_AUTO); enable_packets = config_get_boolean_ondemand("plugin:tc", "enable packets charts for all interfaces", CONFIG_BOOLEAN_AUTO); enable_dropped = config_get_boolean_ondemand("plugin:tc", "enable dropped charts for all interfaces", CONFIG_BOOLEAN_AUTO); enable_tokens = config_get_boolean_ondemand("plugin:tc", "enable tokens charts for all interfaces", CONFIG_BOOLEAN_NO); enable_ctokens = config_get_boolean_ondemand("plugin:tc", "enable ctokens charts for all interfaces", CONFIG_BOOLEAN_NO); enabled_all_classes_qdiscs = config_get_boolean_ondemand("plugin:tc", "enable show all classes and qdiscs for all interfaces", CONFIG_BOOLEAN_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 /*, *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 for(c = d->classes ; c ; c = c->next) { 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 // // 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 if(likely(!d->enabled_all_classes_qdiscs)) { for(c = d->classes; c; c = c->next) { if(unlikely(!c->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"); // find if c is leaf or not for(x = d->classes; x; x = x->next) { if(unlikely(!x->updated || c == x || !x->parentid)) continue; // 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; } } } } for(c = d->classes ; c ; c = c->next) { 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_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; } 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_BOOLEAN_YES || (d->enabled_bytes == CONFIG_BOOLEAN_AUTO && bytes_sum)) { d->enabled_bytes = CONFIG_BOOLEAN_YES; if(unlikely(!d->st_bytes)) d->st_bytes = rrdset_create_localhost( 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" , PLUGIN_TC_NAME , NULL , NETDATA_CHART_PRIO_TC_QOS , localhost->rrd_update_every , d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED ); else { rrdset_next(d->st_bytes); if(unlikely(d->name_updated)) rrdset_set_name(d->st_bytes, d->name); // TODO // update the family } 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, BITS_IN_A_KILOBIT, RRD_ALGORITHM_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); } // -------------------------------------------------------------------- // packets if(d->enabled_packets == CONFIG_BOOLEAN_YES || (d->enabled_packets == CONFIG_BOOLEAN_AUTO && packets_sum)) { d->enabled_packets = CONFIG_BOOLEAN_YES; 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_packets = rrdset_create_localhost( RRD_TYPE_TC , id , name , d->family ? d->family : d->id , RRD_TYPE_TC ".qos_packets" , "Class Packets" , "packets/s" , PLUGIN_TC_NAME , NULL , NETDATA_CHART_PRIO_TC_QOS_PACKETS , localhost->rrd_update_every , d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED ); } else { rrdset_next(d->st_packets); 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); } // TODO // update the family } for(c = d->classes ; c ; c = c->next) { if(unlikely(!c->render)) continue; if(unlikely(!c->rd_packets)) c->rd_packets = rrddim_add(d->st_packets, c->id, c->name?c->name:c->id, 1, 1, RRD_ALGORITHM_INCREMENTAL); else if(unlikely(c->name_updated)) rrddim_set_name(d->st_packets, c->rd_packets, c->name); rrddim_set_by_pointer(d->st_packets, c->rd_packets, c->packets); } rrdset_done(d->st_packets); } // -------------------------------------------------------------------- // dropped if(d->enabled_dropped == CONFIG_BOOLEAN_YES || (d->enabled_dropped == CONFIG_BOOLEAN_AUTO && dropped_sum)) { d->enabled_dropped = CONFIG_BOOLEAN_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); d->st_dropped = rrdset_create_localhost( RRD_TYPE_TC , id , name , d->family ? d->family : d->id , RRD_TYPE_TC ".qos_dropped" , "Class Dropped Packets" , "packets/s" , PLUGIN_TC_NAME , NULL , NETDATA_CHART_PRIO_TC_QOS_DROPPED , localhost->rrd_update_every , d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED ); } else { rrdset_next(d->st_dropped); 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); } // TODO // update the family } for(c = d->classes ; c ; c = c->next) { if(unlikely(!c->render)) continue; if(unlikely(!c->rd_dropped)) c->rd_dropped = rrddim_add(d->st_dropped, c->id, c->name?c->name:c->id, 1, 1, RRD_ALGORITHM_INCREMENTAL); else if(unlikely(c->name_updated)) rrddim_set_name(d->st_dropped, c->rd_dropped, c->name); rrddim_set_by_pointer(d->st_dropped, c->rd_dropped, c->dropped); } rrdset_done(d->st_dropped); } // -------------------------------------------------------------------- // tokens if(d->enabled_tokens == CONFIG_BOOLEAN_YES || (d->enabled_tokens == CONFIG_BOOLEAN_AUTO && tokens_sum)) { d->enabled_tokens = CONFIG_BOOLEAN_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_localhost( RRD_TYPE_TC , id , name , d->family ? d->family : d->id , RRD_TYPE_TC ".qos_tokens" , "Class Tokens" , "tokens" , PLUGIN_TC_NAME , NULL , NETDATA_CHART_PRIO_TC_QOS_TOCKENS , localhost->rrd_update_every , RRDSET_TYPE_LINE ); } else { rrdset_next(d->st_tokens); if(unlikely(d->name_updated)) { char name[RRD_ID_LENGTH_MAX + 1]; snprintfz(name, RRD_ID_LENGTH_MAX, "%s_tokens", d->name?d->name:d->id); rrdset_set_name(d->st_tokens, name); } // TODO // 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, RRD_ALGORITHM_ABSOLUTE); } else if(unlikely(c->name_updated)) rrddim_set_name(d->st_tokens, c->rd_tokens, c->name); rrddim_set_by_pointer(d->st_tokens, c->rd_tokens, c->tokens); } rrdset_done(d->st_tokens); } // -------------------------------------------------------------------- // ctokens if(d->enabled_ctokens == CONFIG_BOOLEAN_YES || (d->enabled_ctokens == CONFIG_BOOLEAN_AUTO && ctokens_sum)) { d->enabled_ctokens = CONFIG_BOOLEAN_YES; 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); d->st_ctokens = rrdset_create_localhost( RRD_TYPE_TC , id , name , d->family ? d->family : d->id , RRD_TYPE_TC ".qos_ctokens" , "Class cTokens" , "ctokens" , PLUGIN_TC_NAME , NULL , NETDATA_CHART_PRIO_TC_QOS_CTOCKENS , localhost->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); } // TODO // 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, RRD_ALGORITHM_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_commit(struct tc_device *d) { static int enable_new_interfaces = -1; if(enable_new_interfaces == -1) enable_new_interfaces = config_get_boolean("plugin:tc", "enable new interfaces detected at runtime", 1); // we only need to add leaf classes struct tc_class *c, *x; // set all classes for(c = d->classes ; c ; c = c->next) { c->isleaf = 1; c->hasparent = 0; } // mark the classes as leafs and parents for(c = d->classes ; c ; c = c->next) { if(!c->updated) continue; for(x = d->classes ; x ; x = x->next) { if(!x->updated) continue; if(c == x) 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); 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) break; } if(!c) { debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No leaf classes.", d->name?d->name:d->id); tc_device_classes_cleanup(d); return; } char var_name[CONFIG_MAX_NAME + 1]; snprintfz(var_name, CONFIG_MAX_NAME, "qos for %s", d->id); if(config_get_boolean("plugin:tc", var_name, enable_new_interfaces)) { RRDSET *st = rrdset_find_bytype(RRD_TYPE_TC, d->id); if(!st) { debug(D_TC_LOOP, "TC: Creating new chart for device '%s'", d->name?d->name:d->id); st = 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); for(c = d->classes ; c ; c = c->next) { if(!c->updated) continue; if(c->isleaf && c->hasparent) rrddim_add(st, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL); } } else { debug(D_TC_LOOP, "TC: Updating chart for device '%s'", d->name?d->name:d->id); rrdset_next_plugins(st); if(d->name && strcmp(d->id, d->name) != 0) rrdset_set_name(st, d->name); } for(c = d->classes ; c ; c = c->next) { if(!c->updated) continue; if(c->isleaf && c->hasparent) { RRDDIM *rd = rrddim_find(st, c->id); if(!rd) { debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s'", st->id, c->id, c->name); // new class, we have to add it rd = rrddim_add(st, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL); } else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", st->id, c->id); rrddim_set_by_pointer(st, rd, c->bytes); // if it has a name, different to the id if(c->name) { // update the rrd dimension with the new name debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", st->id, rd->id, c->name); rrddim_set_name(st, rd, c->name); free(c->name); c->name = NULL; } } } rrdset_done(st); } tc_device_classes_cleanup(d); }