]> arthur.barton.de Git - netdata.git/blob - src/plugin_tc.c
cleanup locking; ability to compile AVL with MUTEX instead of RWLOCK; disks and inter...
[netdata.git] / src / plugin_tc.c
1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <sys/resource.h>
9
10 #include "avl.h"
11 #include "log.h"
12 #include "common.h"
13 #include "appconfig.h"
14 #include "rrd.h"
15 #include "popen.h"
16 #include "plugin_tc.h"
17 #include "main.h"
18
19 #define RRD_TYPE_TC                                     "tc"
20 #define RRD_TYPE_TC_LEN                         strlen(RRD_TYPE_TC)
21
22 // ----------------------------------------------------------------------------
23 // /sbin/tc processor
24 // this requires the script plugins.d/tc-qos-helper.sh
25
26 #define TC_LINE_MAX 1024
27
28 struct tc_class {
29         avl avl;
30
31         char *id;
32         uint32_t hash;
33
34         char *name;
35
36         char *leafid;
37         uint32_t leaf_hash;
38
39         char *parentid;
40         uint32_t parent_hash;
41
42         char hasparent;
43         char isleaf;
44         unsigned long long bytes;
45
46         char updated;   // updated bytes
47         char seen;              // seen in the tc list (even without bytes)
48
49         struct tc_class *next;
50         struct tc_class *prev;
51 };
52
53 struct tc_device {
54         avl avl;
55
56         char *id;
57         uint32_t hash;
58
59         char *name;
60         char *family;
61
62         avl_tree classes_index;
63
64         struct tc_class *classes;
65
66         struct tc_device *next;
67         struct tc_device *prev;
68 };
69
70
71 struct tc_device *tc_device_root = NULL;
72
73 // ----------------------------------------------------------------------------
74 // tc_device index
75
76 static int tc_device_iterator(avl *a) { if(a) {}; return 0; }
77
78 static int tc_device_compare(void* a, void* b) {
79         if(((struct tc_device *)a)->hash < ((struct tc_device *)b)->hash) return -1;
80         else if(((struct tc_device *)a)->hash > ((struct tc_device *)b)->hash) return 1;
81         else return strcmp(((struct tc_device *)a)->id, ((struct tc_device *)b)->id);
82 }
83
84 avl_tree tc_device_root_index = {
85                 NULL,
86                 tc_device_compare,
87 #ifdef AVL_LOCK_WITH_MUTEX
88                 PTHREAD_MUTEX_INITIALIZER
89 #else
90                 PTHREAD_RWLOCK_INITIALIZER
91 #endif
92 };
93
94 #define tc_device_index_add(st) avl_insert(&tc_device_root_index, (avl *)(st))
95 #define tc_device_index_del(st) avl_remove(&tc_device_root_index, (avl *)(st))
96
97 static struct tc_device *tc_device_index_find(const char *id, uint32_t hash) {
98         struct tc_device *result = NULL, tmp;
99         tmp.id = (char *)id;
100         tmp.hash = (hash)?hash:simple_hash(tmp.id);
101
102         avl_search(&(tc_device_root_index), (avl *)&tmp, tc_device_iterator, (avl **)&result);
103         return result;
104 }
105
106
107 // ----------------------------------------------------------------------------
108 // tc_class index
109
110 static int tc_class_iterator(avl *a) { if(a) {}; return 0; }
111
112 static int tc_class_compare(void* a, void* b) {
113         if(((struct tc_class *)a)->hash < ((struct tc_class *)b)->hash) return -1;
114         else if(((struct tc_class *)a)->hash > ((struct tc_class *)b)->hash) return 1;
115         else return strcmp(((struct tc_class *)a)->id, ((struct tc_class *)b)->id);
116 }
117
118 #define tc_class_index_add(st, rd) avl_insert(&((st)->classes_index), (avl *)(rd))
119 #define tc_class_index_del(st, rd) avl_remove(&((st)->classes_index), (avl *)(rd))
120
121 static struct tc_class *tc_class_index_find(struct tc_device *st, const char *id, uint32_t hash) {
122         struct tc_class *result = NULL, tmp;
123         tmp.id = (char *)id;
124         tmp.hash = (hash)?hash:simple_hash(tmp.id);
125
126         avl_search(&(st->classes_index), (avl *)&tmp, tc_class_iterator, (avl **)&result);
127         return result;
128 }
129
130 // ----------------------------------------------------------------------------
131
132 static void tc_class_free(struct tc_device *n, struct tc_class *c) {
133         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);
134
135         if(c->next) c->next->prev = c->prev;
136         if(c->prev) c->prev->next = c->next;
137         if(n->classes == c) {
138                 if(c->next) n->classes = c->next;
139                 else n->classes = c->prev;
140         }
141
142         tc_class_index_del(n, c);
143
144         if(c->id) free(c->id);
145         if(c->name) free(c->name);
146         if(c->leafid) free(c->leafid);
147         if(c->parentid) free(c->parentid);
148
149         free(c);
150 }
151
152 static void tc_device_classes_cleanup(struct tc_device *d) {
153         static int cleanup_every = 999;
154
155         if(cleanup_every > 0) {
156                 cleanup_every = -config_get_number("plugin:tc", "cleanup unused classes every", 60);
157                 if(cleanup_every > 0) cleanup_every = -cleanup_every;
158                 if(cleanup_every == 0) cleanup_every = -1;
159         }
160
161         struct tc_class *c = d->classes;
162         while(c) {
163                 if(c->seen < cleanup_every) {
164                         struct tc_class *nc = c->next;
165                         tc_class_free(d, c);
166                         c = nc;
167                 }
168                 else c = c->next;
169
170                 if(c) {
171                         c->updated = 0;
172                         c->seen--;
173                 }
174         }
175 }
176
177 static void tc_device_commit(struct tc_device *d)
178 {
179         static int enable_new_interfaces = -1;
180
181         if(enable_new_interfaces == -1) enable_new_interfaces = config_get_boolean("plugin:tc", "enable new interfaces detected at runtime", 1);
182
183         // we only need to add leaf classes
184         struct tc_class *c, *x;
185
186         // set all classes
187         for(c = d->classes ; c ; c = c->next) {
188                 c->isleaf = 1;
189                 c->hasparent = 0;
190         }
191
192         // mark the classes as leafs and parents
193         for(c = d->classes ; c ; c = c->next) {
194                 if(!c->updated) continue;
195
196                 for(x = d->classes ; x ; x = x->next) {
197                         if(!x->updated) continue;
198
199                         if(c == x) continue;
200
201                         if(x->parentid && (
202                                 (               c->hash      == x->parent_hash && strcmp(c->id,     x->parentid) == 0) ||
203                                 (c->leafid   && c->leaf_hash == x->parent_hash && strcmp(c->leafid, x->parentid) == 0))) {
204                                 // 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);
205                                 c->isleaf = 0;
206                                 x->hasparent = 1;
207                         }
208                 }
209         }
210
211         // debugging:
212         /*
213         for ( c = d->classes ; c ; c = c->next) {
214                 if(c->isleaf && c->hasparent) debug(D_TC_LOOP, "TC: Device %s, class %s, OK", d->name, c->id);
215                 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);
216         }
217         */
218
219         // we need at least a class
220         for(c = d->classes ; c ; c = c->next) {
221                 // 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);
222                 if(!c->updated) continue;
223                 if(c->isleaf && c->hasparent) break;
224         }
225         if(!c) {
226                 debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No leaf classes.", d->name?d->name:d->id);
227                 tc_device_classes_cleanup(d);
228                 return;
229         }
230
231         char var_name[CONFIG_MAX_NAME + 1];
232         snprintf(var_name, CONFIG_MAX_NAME, "qos for %s", d->id);
233         if(config_get_boolean("plugin:tc", var_name, enable_new_interfaces)) {
234                 RRDSET *st = rrdset_find_bytype(RRD_TYPE_TC, d->id);
235                 if(!st) {
236                         debug(D_TC_LOOP, "TC: Creating new chart for device '%s'", d->name?d->name:d->id);
237
238                         st = rrdset_create(RRD_TYPE_TC, d->id, d->name?d->name:d->id, d->family?d->family:d->id, "Class Usage", "kilobits/s", 1000, rrd_update_every, RRDSET_TYPE_STACKED);
239
240                         for(c = d->classes ; c ; c = c->next) {
241                                 if(!c->updated) continue;
242
243                                 if(c->isleaf && c->hasparent)
244                                         rrddim_add(st, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL);
245                         }
246                 }
247                 else {
248                         debug(D_TC_LOOP, "TC: Updating chart for device '%s'", d->name?d->name:d->id);
249                         rrdset_next_plugins(st);
250
251                         if(d->name && strcmp(d->id, d->name) != 0) rrdset_set_name(st, d->name);
252                 }
253
254                 for(c = d->classes ; c ; c = c->next) {
255                         if(!c->updated) continue;
256
257                         if(c->isleaf && c->hasparent) {
258                                 RRDDIM *rd = rrddim_find(st, c->id);
259
260                                 if(!rd) {
261                                         debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s'", st->id, c->id, c->name);
262
263                                         // new class, we have to add it
264                                         rd = rrddim_add(st, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL);
265                                 }
266                                 else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", st->id, c->id);
267
268                                 rrddim_set_by_pointer(st, rd, c->bytes);
269
270                                 // if it has a name, different to the id
271                                 if(c->name) {
272                                         // update the rrd dimension with the new name
273                                         debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", st->id, rd->id, c->name);
274                                         rrddim_set_name(st, rd, c->name);
275
276                                         free(c->name);
277                                         c->name = NULL;
278                                 }
279                         }
280                 }
281                 rrdset_done(st);
282         }
283
284         tc_device_classes_cleanup(d);
285 }
286
287 static void tc_device_set_class_name(struct tc_device *d, char *id, char *name)
288 {
289         struct tc_class *c = tc_class_index_find(d, id, 0);
290         if(c) {
291                 if(c->name) free(c->name);
292                 c->name = NULL;
293
294                 if(name && *name && strcmp(c->id, name) != 0) {
295                         debug(D_TC_LOOP, "TC: Setting device '%s', class '%s' name to '%s'", d->id, id, name);
296                         c->name = strdup(name);
297                 }
298         }
299 }
300
301 static void tc_device_set_device_name(struct tc_device *d, char *name) {
302         if(d->name) free(d->name);
303         d->name = NULL;
304
305         if(name && *name && strcmp(d->id, name) != 0) {
306                 debug(D_TC_LOOP, "TC: Setting device '%s' name to '%s'", d->id, name);
307                 d->name = strdup(name);
308         }
309 }
310
311 static void tc_device_set_device_family(struct tc_device *d, char *family) {
312         if(d->family) free(d->family);
313         d->family = NULL;
314
315         if(family && *family && strcmp(d->id, family) != 0) {
316                 debug(D_TC_LOOP, "TC: Setting device '%s' family to '%s'", d->id, family);
317                 d->family = strdup(family);
318         }
319         // no need for null termination - it is already null
320 }
321
322 static struct tc_device *tc_device_create(char *id)
323 {
324         struct tc_device *d = tc_device_index_find(id, 0);
325
326         if(!d) {
327                 debug(D_TC_LOOP, "TC: Creating device '%s'", id);
328
329                 d = calloc(1, sizeof(struct tc_device));
330                 if(!d) {
331                         fatal("Cannot allocate memory for tc_device %s", id);
332                         return NULL;
333                 }
334
335                 d->id = strdup(id);
336                 d->hash = simple_hash(d->id);
337
338                 d->classes_index.root = NULL;
339                 d->classes_index.compar = tc_class_compare;
340 #ifdef AVL_LOCK_WITH_MUTEX
341                 pthread_mutex_init(&d->classes_index.mutex, NULL);
342 #else
343                 pthread_rwlock_init(&d->classes_index.rwlock, NULL);
344 #endif
345
346                 tc_device_index_add(d);
347
348                 if(!tc_device_root) {
349                         tc_device_root = d;
350                 }
351                 else {
352                         d->next = tc_device_root;
353                         tc_device_root->prev = d;
354                         tc_device_root = d;
355                 }
356         }
357
358         return(d);
359 }
360
361 static struct tc_class *tc_class_add(struct tc_device *n, char *id, char *parentid, char *leafid)
362 {
363         struct tc_class *c = tc_class_index_find(n, id, 0);
364
365         if(!c) {
366                 debug(D_TC_LOOP, "TC: Creating in device '%s', class id '%s', parentid '%s', leafid '%s'", n->id, id, parentid?parentid:"", leafid?leafid:"");
367
368                 c = calloc(1, sizeof(struct tc_class));
369                 if(!c) {
370                         fatal("Cannot allocate memory for tc class");
371                         return NULL;
372                 }
373
374                 if(n->classes) n->classes->prev = c;
375                 c->next = n->classes;
376                 n->classes = c;
377
378                 c->id = strdup(id);
379                 if(!c->id) {
380                         free(c);
381                         return NULL;
382                 }
383                 c->hash = simple_hash(c->id);
384
385                 if(parentid && *parentid) {
386                         c->parentid = strdup(parentid);
387                         c->parent_hash = simple_hash(c->parentid);
388                 }
389
390                 if(leafid && *leafid) {
391                         c->leafid = strdup(leafid);
392                         c->leaf_hash = simple_hash(c->leafid);
393                 }
394
395                 tc_class_index_add(n, c);
396         }
397
398         c->seen = 1;
399
400         return(c);
401 }
402
403 static void tc_device_free(struct tc_device *n)
404 {
405         if(n->next) n->next->prev = n->prev;
406         if(n->prev) n->prev->next = n->next;
407         if(tc_device_root == n) {
408                 if(n->next) tc_device_root = n->next;
409                 else tc_device_root = n->prev;
410         }
411
412         tc_device_index_del(n);
413
414         while(n->classes) tc_class_free(n, n->classes);
415
416         if(n->id) free(n->id);
417         if(n->name) free(n->name);
418         if(n->family) free(n->family);
419
420         free(n);
421 }
422
423 static void tc_device_free_all()
424 {
425         while(tc_device_root)
426                 tc_device_free(tc_device_root);
427 }
428
429 #define MAX_WORDS 20
430
431 static inline int tc_space(char c) {
432         switch(c) {
433         case ' ':
434         case '\t':
435         case '\r':
436         case '\n':
437                 return 1;
438
439         default:
440                 return 0;
441         }
442 }
443
444 static void tc_split_words(char *str, char **words, int max_words) {
445         char *s = str;
446         int i = 0;
447
448         // skip all white space
449         while(tc_space(*s)) s++;
450
451         // store the first word
452         words[i++] = s;
453
454         // while we have something
455         while(*s) {
456                 // if it is a space
457                 if(tc_space(*s)) {
458
459                         // terminate the word
460                         *s++ = '\0';
461
462                         // skip all white space
463                         while(tc_space(*s)) s++;
464
465                         // if we reached the end, stop
466                         if(!*s) break;
467
468                         // store the next word
469                         if(i < max_words) words[i++] = s;
470                         else break;
471                 }
472                 else s++;
473         }
474
475         // terminate the words
476         while(i < max_words) words[i++] = NULL;
477 }
478
479 pid_t tc_child_pid = 0;
480 void *tc_main(void *ptr)
481 {
482         if(ptr) { ; }
483
484         info("TC thread created with task id %d", gettid());
485
486         if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
487                 error("Cannot set pthread cancel type to DEFERRED.");
488
489         if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
490                 error("Cannot set pthread cancel state to ENABLE.");
491
492         struct rusage thread;
493         RRDSET *stcpu = NULL, *sttime = NULL;
494
495         char buffer[TC_LINE_MAX+1] = "";
496         char *words[MAX_WORDS] = { NULL };
497
498         uint32_t BEGIN_HASH = simple_hash("BEGIN");
499         uint32_t END_HASH = simple_hash("END");
500         uint32_t CLASS_HASH = simple_hash("class");
501         uint32_t SENT_HASH = simple_hash("Sent");
502         uint32_t SETDEVICENAME_HASH = simple_hash("SETDEVICENAME");
503         uint32_t SETDEVICEGROUP_HASH = simple_hash("SETDEVICEGROUP");
504         uint32_t SETCLASSNAME_HASH = simple_hash("SETCLASSNAME");
505         uint32_t WORKTIME_HASH = simple_hash("WORKTIME");
506 #ifdef DETACH_PLUGINS_FROM_NETDATA
507         uint32_t MYPID_HASH = simple_hash("MYPID");
508 #endif
509         uint32_t first_hash;
510
511         for(;1;) {
512                 FILE *fp;
513                 struct tc_device *device = NULL;
514                 struct tc_class *class = NULL;
515
516                 snprintf(buffer, TC_LINE_MAX, "exec %s %d", config_get("plugin:tc", "script to run to get tc values", PLUGINS_DIR "/tc-qos-helper.sh"), rrd_update_every);
517                 debug(D_TC_LOOP, "executing '%s'", buffer);
518                 // fp = popen(buffer, "r");
519                 fp = mypopen(buffer, &tc_child_pid);
520                 if(!fp) {
521                         error("TC: Cannot popen(\"%s\", \"r\").", buffer);
522                         return NULL;
523                 }
524
525                 while(fgets(buffer, TC_LINE_MAX, fp) != NULL) {
526                         if(netdata_exit) break;
527
528                         buffer[TC_LINE_MAX] = '\0';
529                         // debug(D_TC_LOOP, "TC: read '%s'", buffer);
530
531                         tc_split_words(buffer, words, MAX_WORDS);
532                         if(!words[0] || !*words[0]) {
533                                 // debug(D_TC_LOOP, "empty line");
534                                 continue;
535                         }
536                         // else debug(D_TC_LOOP, "First word is '%s'", words[0]);
537
538                         first_hash = simple_hash(words[0]);
539
540                         if(first_hash == CLASS_HASH && strcmp(words[0], "class") == 0 && device) {
541                                 // 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]);
542
543                                 if(words[1] && words[2] && words[3] && words[4] && (strcmp(words[3], "parent") == 0 || strcmp(words[3], "root") == 0)) {
544                                         // char *type     = words[1];  // the class: htb, fq_codel, etc
545                                         char *id       = words[2];      // the class major:minor
546                                         char *parent   = words[3];      // 'parent' or 'root'
547                                         char *parentid = words[4];      // the parent's id
548                                         char *leaf     = words[5];      // 'leaf'
549                                         char *leafid   = words[6];      // leafid
550
551                                         if(strcmp(parent, "root") == 0) {
552                                                 parentid = NULL;
553                                                 leafid = NULL;
554                                         }
555                                         else if(!leaf || strcmp(leaf, "leaf") != 0)
556                                                 leafid = NULL;
557
558                                         char leafbuf[20 + 1] = "";
559                                         if(leafid && leafid[strlen(leafid) - 1] == ':') {
560                                                 strncpy(leafbuf, leafid, 20 - 1);
561                                                 strcat(leafbuf, "1");
562                                                 leafid = leafbuf;
563                                         }
564
565                                         class = tc_class_add(device, id, parentid, leafid);
566                                 }
567                         }
568                         else if(first_hash == END_HASH && strcmp(words[0], "END") == 0) {
569                                 // debug(D_TC_LOOP, "END line");
570
571                                 if(device) {
572                                         if(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL) != 0)
573                                                 error("Cannot set pthread cancel state to DISABLE.");
574
575                                         tc_device_commit(device);
576                                         // tc_device_free(device);
577                                         device = NULL;
578                                         class = NULL;
579
580                                         if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
581                                                 error("Cannot set pthread cancel state to ENABLE.");
582                                 }
583                         }
584                         else if(first_hash == BEGIN_HASH && strcmp(words[0], "BEGIN") == 0) {
585                                 // debug(D_TC_LOOP, "BEGIN line on device '%s'", words[1]);
586
587                                 if(device) {
588                                         // tc_device_free(device);
589                                         device = NULL;
590                                         class = NULL;
591                                 }
592
593                                 if(words[1] && *words[1]) {
594                                         device = tc_device_create(words[1]);
595                                         class = NULL;
596                                 }
597                         }
598                         else if(first_hash == SENT_HASH && strcmp(words[0], "Sent") == 0 && device && class) {
599                                 // debug(D_TC_LOOP, "SENT line '%s'", words[1]);
600                                 if(words[1] && *words[1]) {
601                                         class->bytes = atoll(words[1]);
602                                         class->updated = 1;
603                                 }
604                                 else class->bytes = 0;
605                         }
606                         else if(first_hash == SETDEVICENAME_HASH && strcmp(words[0], "SETDEVICENAME") == 0 && device) {
607                                 // debug(D_TC_LOOP, "SETDEVICENAME line '%s'", words[1]);
608                                 if(words[1] && *words[1]) tc_device_set_device_name(device, words[1]);
609                         }
610                         else if(first_hash == SETDEVICEGROUP_HASH && strcmp(words[0], "SETDEVICEGROUP") == 0 && device) {
611                                 // debug(D_TC_LOOP, "SETDEVICEGROUP line '%s'", words[1]);
612                                 if(words[1] && *words[1]) tc_device_set_device_family(device, words[1]);
613                         }
614                         else if(first_hash == SETCLASSNAME_HASH && strcmp(words[0], "SETCLASSNAME") == 0 && device) {
615                                 // debug(D_TC_LOOP, "SETCLASSNAME line '%s' '%s'", words[1], words[2]);
616                                 char *id    = words[1];
617                                 char *path  = words[2];
618                                 if(id && *id && path && *path) tc_device_set_class_name(device, id, path);
619                         }
620                         else if(first_hash == WORKTIME_HASH && strcmp(words[0], "WORKTIME") == 0) {
621                                 // debug(D_TC_LOOP, "WORKTIME line '%s' '%s'", words[1], words[2]);
622                                 getrusage(RUSAGE_THREAD, &thread);
623
624                                 if(!stcpu) stcpu = rrdset_find("netdata.plugin_tc_cpu");
625                                 if(!stcpu) {
626                                         stcpu = rrdset_create("netdata", "plugin_tc_cpu", NULL, "netdata", "NetData TC CPU usage", "milliseconds/s", 10000, rrd_update_every, RRDSET_TYPE_STACKED);
627                                         rrddim_add(stcpu, "user",  NULL,  1, 1000, RRDDIM_INCREMENTAL);
628                                         rrddim_add(stcpu, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL);
629                                 }
630                                 else rrdset_next(stcpu);
631
632                                 rrddim_set(stcpu, "user"  , thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec);
633                                 rrddim_set(stcpu, "system", thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec);
634                                 rrdset_done(stcpu);
635
636                                 if(!sttime) stcpu = rrdset_find("netdata.plugin_tc_time");
637                                 if(!sttime) {
638                                         sttime = rrdset_create("netdata", "plugin_tc_time", NULL, "netdata", "NetData TC script execution", "milliseconds/run", 10001, rrd_update_every, RRDSET_TYPE_AREA);
639                                         rrddim_add(sttime, "run_time",  "run time",  1, 1, RRDDIM_ABSOLUTE);
640                                 }
641                                 else rrdset_next(sttime);
642
643                                 rrddim_set(sttime, "run_time", atoll(words[1]));
644                                 rrdset_done(sttime);
645
646                         }
647 #ifdef DETACH_PLUGINS_FROM_NETDATA
648                         else if(first_hash == MYPID_HASH && (strcmp(words[0], "MYPID") == 0)) {
649                                 // debug(D_TC_LOOP, "MYPID line '%s'", words[1]);
650                                 char *id = words[1];
651                                 pid_t pid = atol(id);
652
653                                 if(pid) tc_child_pid = pid;
654
655                                 debug(D_TC_LOOP, "TC: Child PID is %d.", tc_child_pid);
656                         }
657 #endif
658                         //else {
659                         //      debug(D_TC_LOOP, "IGNORED line");
660                         //}
661                 }
662                 mypclose(fp, tc_child_pid);
663                 tc_child_pid = 0;
664
665                 if(device) {
666                         // tc_device_free(device);
667                         device = NULL;
668                         class = NULL;
669                 }
670
671                 if(netdata_exit) {
672                         tc_device_free_all();
673                         return NULL;
674                 }
675
676                 sleep(rrd_update_every);
677         }
678
679         return NULL;
680 }
681