]> arthur.barton.de Git - netdata.git/blobdiff - src/plugin_tc.c
fix to find root qdisc
[netdata.git] / src / plugin_tc.c
index 7d521818ed50133be8ab96e766375c9f6375c7c0..bc89de621905bc5fe91ca41a534973e64bfba578 100644 (file)
@@ -25,6 +25,7 @@ struct tc_class {
 
     char hasparent;
     char isleaf;
+    char isqdisc;
     unsigned long long bytes;
     unsigned long long packets;
     unsigned long long dropped;
@@ -39,10 +40,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 +67,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 +102,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 +123,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;
@@ -129,16 +137,19 @@ static inline struct tc_class *tc_class_index_find(struct tc_device *st, const c
 // ----------------------------------------------------------------------------
 
 static inline void tc_class_free(struct tc_device *n, struct tc_class *c) {
-    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);
-
+    if(c == n->classes) {
+        if(c->next)
+            n->classes = c->next;
+        else
+            n->classes = c->prev;
+    }
     if(c->next) c->next->prev = c->prev;
     if(c->prev) c->prev->next = c->next;
-    if(n->classes == c) {
-        if(c->next) n->classes = c->next;
-        else n->classes = c->prev;
-    }
 
-    tc_class_index_del(n, c);
+    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);
+
+    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);
@@ -151,7 +162,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;
     }
 
@@ -160,7 +171,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;
@@ -175,34 +186,43 @@ 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);
     }
 
     // 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;
+    unsigned long long bytes_sum = 0, packets_sum = 0, dropped_sum = 0, tokens_sum = 0, ctokens_sum = 0;
+    int active_qos = 0;
 
     // set all classes
     for(c = d->classes ; c ; c = c->next) {
         c->isleaf = 1;
         c->hasparent = 0;
+
+        if(unlikely(!c->updated))
+            c->unupdated++;
+        else
+            c->unupdated = 0;
     }
 
     // mark the classes as leafs and parents
     for(c = d->classes ; c ; c = c->next) {
         if(unlikely(!c->updated)) continue;
+        if(unlikely(c->isqdisc)) continue;
 
         for(x = d->classes ; x ; x = x->next) {
             if(unlikely(!x->updated)) continue;
-
             if(unlikely(c == x)) continue;
+            if(unlikely(x->isqdisc)) continue;
 
             if(x->parentid && (
                 (               c->hash      == x->parent_hash && strcmp(c->id,     x->parentid) == 0) ||
@@ -214,47 +234,78 @@ static inline void tc_device_commit(struct tc_device *d) {
         }
     }
 
-    // debugging:
+    // debugging only
     /*
-    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);
+    if(unlikely(debug_flags & D_TC_LOOP)) {
+        for(c = d->classes ; c ; c = c->next) {
+            if((c->isleaf && c->hasparent) || d->enabled_all_classes_qdiscs) 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?d->name:d->id, c->id, c->isleaf, c->hasparent, c->parentid?c->parentid:"(unset)");
+        }
     }
     */
 
-    // we need at least a class
+    // we need the root qdisc
     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++;
-            bytes_sum += c->bytes;
-            packets_sum += c->packets;
-            dropped_sum += c->dropped;
+        debug(D_TC_LOOP, "TC: Device '%s', class '%s', isleaf=%s, isqdisc=%s, hasparent=%s bytes=%d, packtes=%d, dropped=%d, tokens=%d, ctokens=%d", d->name?d->name:d->id, c->name?c->name:c->id, c->isleaf?"true":"false", c->isqdisc?"true":"false", c->hasparent?"true":"false", c->bytes, c->packets, c->dropped, c->tokens, c->ctokens);
+        if(unlikely(c->updated && c->isqdisc && !c->parentid)) {
+            active_qos = 1;
+            bytes_sum = c->bytes;
+            packets_sum = c->packets;
+            dropped_sum = c->dropped;
+            tokens_sum = c->tokens;
+            ctokens_sum = c->ctokens;
+            debug(D_TC_LOOP, "TC: found root qdisc. active_qos=%d, bytes_sum=%llu", active_qos, bytes_sum);
+            break;
         }
     }
 
-    if(unlikely(!active_classes)) {
+    if(unlikely(!active_qos)) {
         debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No leaf classes.", d->name?d->name:d->id);
         tc_device_classes_cleanup(d);
         return;
     }
 
-    if(unlikely(d->enabled == -1)) {
+    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         config_get_boolean_ondemand("plugin:tc", var_name, enable_new_interfaces);
+        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   config_get_boolean_ondemand("plugin:tc", var_name, enable_bytes);
+        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 config_get_boolean_ondemand("plugin:tc", var_name, enable_packets);
+        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 = config_get_boolean_ondemand("plugin:tc", var_name, enable_dropped);
+        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);
     }
 
+    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
+        );
+
     if(likely(d->enabled)) {
         // --------------------------------------------------------------------
         // bytes
@@ -271,7 +322,7 @@ static inline void tc_device_commit(struct tc_device *d) {
             }
             else {
                 debug(D_TC_LOOP, "TC: Updating chart for device '%s'", d->name?d->name:d->id);
-                rrdset_next_plugins(d->st_bytes);
+                rrdset_next(d->st_bytes);
 
                 if(unlikely(d->name_updated && d->name && strcmp(d->id, d->name) != 0)) {
                     rrdset_set_name(d->st_bytes, d->name);
@@ -285,9 +336,7 @@ static inline void tc_device_commit(struct tc_device *d) {
             for(c = d->classes ; c ; c = c->next) {
                 if(unlikely(!c->updated)) continue;
 
-                if(c->isleaf && c->hasparent) {
-                    c->seen++;
-
+                if((c->isleaf && c->hasparent && !c->isqdisc) || d->enabled_all_classes_qdiscs) {
                     if(unlikely(!c->rd_bytes)) {
                         c->rd_bytes = rrddim_find(d->st_bytes, c->id);
                         if(unlikely(!c->rd_bytes)) {
@@ -332,7 +381,7 @@ static inline void tc_device_commit(struct tc_device *d) {
             }
             else {
                 debug(D_TC_LOOP, "TC: Updating _packets chart for device '%s'", d->name?d->name:d->id);
-                rrdset_next_plugins(d->st_packets);
+                rrdset_next(d->st_packets);
 
                 // FIXME
                 // update the family
@@ -341,7 +390,7 @@ static inline void tc_device_commit(struct tc_device *d) {
             for(c = d->classes ; c ; c = c->next) {
                 if(unlikely(!c->updated)) continue;
 
-                if(c->isleaf && c->hasparent) {
+                if((c->isleaf && c->hasparent && !c->isqdisc) || d->enabled_all_classes_qdiscs) {
                     if(unlikely(!c->rd_packets)) {
                         c->rd_packets = rrddim_find(d->st_packets, c->id);
                         if(unlikely(!c->rd_packets)) {
@@ -386,7 +435,7 @@ static inline void tc_device_commit(struct tc_device *d) {
             }
             else {
                 debug(D_TC_LOOP, "TC: Updating _dropped chart for device '%s'", d->name?d->name:d->id);
-                rrdset_next_plugins(d->st_dropped);
+                rrdset_next(d->st_dropped);
 
                 // FIXME
                 // update the family
@@ -395,7 +444,7 @@ static inline void tc_device_commit(struct tc_device *d) {
             for(c = d->classes ; c ; c = c->next) {
                 if(unlikely(!c->updated)) continue;
 
-                if(c->isleaf && c->hasparent) {
+                if((c->isleaf && c->hasparent && !c->isqdisc) || d->enabled_all_classes_qdiscs) {
                     if(unlikely(!c->rd_dropped)) {
                         c->rd_dropped = rrddim_find(d->st_dropped, c->id);
                         if(unlikely(!c->rd_dropped)) {
@@ -419,6 +468,114 @@ static inline void tc_device_commit(struct tc_device *d) {
             }
             rrdset_done(d->st_dropped);
         }
+
+        // --------------------------------------------------------------------
+        // tokens
+        
+        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_find_bytype(RRD_TYPE_TC, id);
+                if(unlikely(!d->st_tokens)) {
+                    debug(D_TC_LOOP, "TC: Creating new _tokens chart for device '%s'", 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 {
+                debug(D_TC_LOOP, "TC: Updating _tokens chart for device '%s'", d->name?d->name:d->id);
+                rrdset_next(d->st_tokens);
+
+                // FIXME
+                // update the family
+            }
+
+            for(c = d->classes ; c ; c = c->next) {
+                if(unlikely(!c->updated)) continue;
+
+                if((c->isleaf && c->hasparent && !c->isqdisc) || d->enabled_all_classes_qdiscs) {
+                    if(unlikely(!c->rd_tokens)) {
+                        c->rd_tokens = rrddim_find(d->st_tokens, c->id);
+                        if(unlikely(!c->rd_tokens)) {
+                            debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_tokens->id, c->id, c->name);
+
+                            // new class, we have to add it
+                            c->rd_tokens = rrddim_add(d->st_tokens, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_ABSOLUTE);
+                        }
+                        else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_tokens->id, c->id);
+                    }
+
+                    rrddim_set_by_pointer(d->st_tokens, c->rd_tokens, c->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_tokens->id, c->rd_tokens->id, c->name);
+                        rrddim_set_name(d->st_tokens, c->rd_tokens, c->name);
+                    }
+                }
+            }
+            rrdset_done(d->st_tokens);
+        }
+
+        // --------------------------------------------------------------------
+        // ctokens
+        
+        if(d->enabled_ctokens == CONFIG_ONDEMAND_YES || (d->enabled_ctokens == CONFIG_ONDEMAND_ONDEMAND && ctokens_sum)) {
+            d->enabled_ctokens = CONFIG_ONDEMAND_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_find_bytype(RRD_TYPE_TC, id);
+                if(unlikely(!d->st_ctokens)) {
+                    debug(D_TC_LOOP, "TC: Creating new _ctokens chart for device '%s'", d->name?d->name:d->id);
+                    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);
+
+                // FIXME
+                // update the family
+            }
+
+            for(c = d->classes ; c ; c = c->next) {
+                if(unlikely(!c->updated)) continue;
+
+                if((c->isleaf && c->hasparent && !c->isqdisc) || d->enabled_all_classes_qdiscs) {
+                    if(unlikely(!c->rd_ctokens)) {
+                        c->rd_ctokens = rrddim_find(d->st_ctokens, c->id);
+                        if(unlikely(!c->rd_ctokens)) {
+                            debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_ctokens->id, c->id, c->name);
+
+                            // new class, we have to add it
+                            c->rd_ctokens = rrddim_add(d->st_ctokens, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_ABSOLUTE);
+                        }
+                        else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_ctokens->id, c->id);
+                    }
+
+                    rrddim_set_by_pointer(d->st_ctokens, c->rd_ctokens, c->ctokens);
+
+                    // 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_ctokens->id, c->rd_ctokens->id, c->name);
+                        rrddim_set_name(d->st_ctokens, c->rd_ctokens, c->name);
+                    }
+                }
+            }
+            rrdset_done(d->st_ctokens);
+        }
     }
 
     tc_device_classes_cleanup(d);
@@ -473,10 +630,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;
@@ -491,7 +649,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);
 
@@ -507,6 +665,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);
@@ -517,11 +676,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);
 }
 
@@ -534,7 +691,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);
 
@@ -600,9 +758,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());
 
@@ -620,6 +778,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:");
@@ -646,11 +805,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) {
@@ -669,7 +827,7 @@ 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
@@ -686,7 +844,11 @@ void *tc_main(void *ptr) {
                     char *parentid = words[4];  // the parent's id
                     char *leaf     = words[5];  // 'leaf'
                     char *leafid   = words[6];  // leafid
+                    char qdisc = 0;
 
+                    if(strcmp(words[0], "qdisc") == 0) {
+                        qdisc = 1;
+                    }
                     if(strcmp(parent, "root") == 0) {
                         parentid = NULL;
                         leafid = NULL;
@@ -701,7 +863,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
@@ -741,7 +903,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 {
@@ -749,35 +911,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]);
@@ -840,7 +1002,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)) {
@@ -851,8 +1013,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) {
@@ -861,13 +1022,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;
 }