]> arthur.barton.de Git - netdata.git/blob - src/plugins_d.c
added libavl for supporting balanced binary trees - this improves search performance...
[netdata.git] / src / plugins_d.c
1 #include <sys/types.h>
2 #include <dirent.h>
3 #include <pthread.h>
4 #include <string.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <signal.h>
8
9 #include "common.h"
10 #include "config.h"
11 #include "log.h"
12 #include "rrd.h"
13 #include "popen.h"
14 #include "plugins_d.h"
15
16 struct plugind *pluginsd_root = NULL;
17
18 void *pluginsd_worker_thread(void *arg)
19 {
20         struct plugind *cd = (struct plugind *)arg;
21         char line[PLUGINSD_LINE_MAX + 1];
22
23 #ifdef DETACH_PLUGINS_FROM_NETDATA
24         unsigned long long usec = 0, susec = 0;
25         struct timeval last = {0, 0} , now = {0, 0};
26 #endif
27
28         while(1) {
29                 FILE *fp = mypopen(cd->cmd, &cd->pid);
30                 if(!fp) {
31                         error("Cannot popen(\"%s\", \"r\").", cd->cmd);
32                         break;
33                 }
34
35                 RRDSET *st = NULL;
36
37                 unsigned long long count = 0;
38                 while(fgets(line, PLUGINSD_LINE_MAX, fp) != NULL) {
39                         char *p = trim(line);
40                         debug(D_PLUGINSD, "PLUGINSD: %s: %s", cd->filename, line);
41
42                         char *s = qstrsep(&p);
43
44                         if(!s || !*s) continue;
45                         else if(!strcmp(s, "SET")) {
46                                 char *t;
47                                 while((t = strchr(p, '='))) *t = ' ';
48                                 
49                                 char *dimension = qstrsep(&p);
50                                 char *value = qstrsep(&p);
51
52                                 if(!dimension || !*dimension || !value) {
53                                         error("PLUGINSD: '%s' is requesting a SET on chart '%s', like this: 'SET %s = %s'. Disabling it.", cd->fullfilename, st->id, dimension?dimension:"", value?value:"");
54                                         cd->enabled = 0;
55                                         kill(cd->pid, SIGTERM);
56                                         break;
57                                 }
58
59                                 if(!st) {
60                                         error("PLUGINSD: '%s' is requesting a SET on dimension %s with value %s, without a BEGIN. Disabling it.", cd->fullfilename, dimension, value);
61                                         cd->enabled = 0;
62                                         kill(cd->pid, SIGTERM);
63                                         break;
64                                 }
65
66                                 if(st->debug) debug(D_PLUGINSD, "PLUGINSD: '%s' is setting dimension %s/%s to %s", cd->fullfilename, st->id, dimension, value);
67                                 rrddim_set(st, dimension, atoll(value));
68
69                                 count++;
70                         }
71                         else if(!strcmp(s, "BEGIN")) {
72                                 char *id = qstrsep(&p);
73                                 char *microseconds_txt = qstrsep(&p);
74
75                                 if(!id) {
76                                         error("PLUGINSD: '%s' is requesting a BEGIN without a chart id. Disabling it.", cd->fullfilename);
77                                         cd->enabled = 0;
78                                         kill(cd->pid, SIGTERM);
79                                         break;
80                                 }
81
82                                 st = rrdset_find(id);
83                                 if(!st) {
84                                         error("PLUGINSD: '%s' is requesting a BEGIN on chart '%s', which does not exist. Disabling it.", cd->fullfilename, id);
85                                         cd->enabled = 0;
86                                         kill(cd->pid, SIGTERM);
87                                         break;
88                                 }
89
90                                 if(st->counter_done) {
91                                         unsigned long long microseconds = 0;
92                                         if(microseconds_txt && *microseconds_txt) microseconds = strtoull(microseconds_txt, NULL, 10);
93                                         if(microseconds) rrdset_next_usec(st, microseconds);
94                                         else rrdset_next_plugins(st);
95                                 }
96                         }
97                         else if(!strcmp(s, "END")) {
98                                 if(!st) {
99                                         error("PLUGINSD: '%s' is requesting an END, without a BEGIN. Disabling it.", cd->fullfilename);
100                                         cd->enabled = 0;
101                                         kill(cd->pid, SIGTERM);
102                                         break;
103                                 }
104
105                                 if(st->debug) debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting a END on chart %s", cd->fullfilename, st->id);
106
107                                 rrdset_done(st);
108                                 st = NULL;
109                         }
110                         else if(!strcmp(s, "FLUSH")) {
111                                 debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting a FLUSH", cd->fullfilename);
112                                 st = NULL;
113                         }
114                         else if(!strcmp(s, "CHART")) {
115                                 st = NULL;
116
117                                 char *type = qstrsep(&p);
118                                 char *id = NULL;
119                                 if(type) {
120                                         id = strchr(type, '.');
121                                         if(id) { *id = '\0'; id++; }
122                                 }
123                                 char *name = qstrsep(&p);
124                                 char *title = qstrsep(&p);
125                                 char *units = qstrsep(&p);
126                                 char *family = qstrsep(&p);
127                                 char *category = qstrsep(&p);
128                                 char *chart = qstrsep(&p);
129                                 char *priority_s = qstrsep(&p);
130                                 char *update_every_s = qstrsep(&p);
131
132                                 if(!type || !*type || !id || !*id) {
133                                         error("PLUGINSD: '%s' is requesting a CHART, without a type.id. Disabling it.", cd->fullfilename);
134                                         cd->enabled = 0;
135                                         kill(cd->pid, SIGTERM);
136                                         break;
137                                 }
138
139                                 int priority = 1000;
140                                 if(priority_s) priority = atoi(priority_s);
141
142                                 int update_every = cd->update_every;
143                                 if(update_every_s) update_every = atoi(update_every_s);
144                                 if(!update_every) update_every = cd->update_every;
145
146                                 int chart_type = RRDSET_TYPE_LINE;
147                                 if(chart) chart_type = rrdset_type_id(chart);
148
149                                 if(!name || !*name) name = NULL;
150                                 if(!family || !*family) family = id;
151                                 if(!category || !*category) category = type;
152
153                                 st = rrdset_find_bytype(type, id);
154                                 if(!st) {
155                                         debug(D_PLUGINSD, "PLUGINSD: Creating chart type='%s', id='%s', name='%s', family='%s', category='%s', chart='%s', priority=%d, update_every=%d"
156                                                 , type, id
157                                                 , name?name:""
158                                                 , family?family:""
159                                                 , category?category:""
160                                                 , rrdset_type_name(chart_type)
161                                                 , priority
162                                                 , update_every
163                                                 );
164
165                                         st = rrdset_create(type, id, name, family, title, units, priority, update_every, chart_type);
166                                         cd->update_every = update_every;
167
168                                         if(strcmp(category, "none") == 0) st->isdetail = 1;
169                                 }
170                                 else debug(D_PLUGINSD, "PLUGINSD: Chart '%s' already exists. Not adding it again.", st->id);
171                         }
172                         else if(!strcmp(s, "DIMENSION")) {
173                                 char *id = qstrsep(&p);
174                                 char *name = qstrsep(&p);
175                                 char *algorithm = qstrsep(&p);
176                                 char *multiplier_s = qstrsep(&p);
177                                 char *divisor_s = qstrsep(&p);
178                                 char *hidden = qstrsep(&p);
179
180                                 if(!id || !*id) {
181                                         error("PLUGINSD: '%s' is requesting a DIMENSION, without an id. Disabling it.", cd->fullfilename);
182                                         cd->enabled = 0;
183                                         kill(cd->pid, SIGTERM);
184                                         break;
185                                 }
186
187                                 if(!st) {
188                                         error("PLUGINSD: '%s' is requesting a DIMENSION, without a CHART. Disabling it.", cd->fullfilename);
189                                         cd->enabled = 0;
190                                         kill(cd->pid, SIGTERM);
191                                         break;
192                                 }
193
194                                 long multiplier = 1;
195                                 if(multiplier_s && *multiplier_s) multiplier = atol(multiplier_s);
196                                 if(!multiplier) multiplier = 1;
197
198                                 long divisor = 1;
199                                 if(divisor_s && *divisor_s) divisor = atol(divisor_s);
200                                 if(!divisor) divisor = 1;
201
202                                 if(!algorithm || !*algorithm) algorithm = "absolute";
203
204                                 if(st->debug) debug(D_PLUGINSD, "PLUGINSD: Creating dimension in chart %s, id='%s', name='%s', algorithm='%s', multiplier=%ld, divisor=%ld, hidden='%s'"
205                                         , st->id
206                                         , id
207                                         , name?name:""
208                                         , rrddim_algorithm_name(rrddim_algorithm_id(algorithm))
209                                         , multiplier
210                                         , divisor
211                                         , hidden?hidden:""
212                                         );
213
214                                 RRDDIM *rd = rrddim_find(st, id);
215                                 if(!rd) {
216                                         rd = rrddim_add(st, id, name, multiplier, divisor, rrddim_algorithm_id(algorithm));
217                                         if(hidden && strcmp(hidden, "hidden") == 0) rd->hidden = 1;
218                                 }
219                                 else if(st->debug) debug(D_PLUGINSD, "PLUGINSD: dimension %s/%s already exists. Not adding it again.", st->id, id);
220                         }
221                         else if(!strcmp(s, "DISABLE")) {
222                                 error("PLUGINSD: '%s' called DISABLE. Disabling it.", cd->fullfilename);
223                                 cd->enabled = 0;
224                                 kill(cd->pid, SIGTERM);
225                                 break;
226                         }
227 #ifdef DETACH_PLUGINS_FROM_NETDATA
228                         else if(!strcmp(s, "MYPID")) {
229                                 char *pid_s = qstrsep(&p);
230                                 pid_t pid = atol(pid_s);
231
232                                 if(pid) cd->pid = pid;
233                                 debug(D_PLUGINSD, "PLUGINSD: %s is on pid %d", cd->id, cd->pid);
234                         }
235                         else if(!strcmp(s, "STOPPING_WAKE_ME_UP_PLEASE")) {
236                                 error("PLUGINSD: '%s' (pid %d) called STOPPING_WAKE_ME_UP_PLEASE.", cd->fullfilename, cd->pid);
237
238                                 gettimeofday(&now, NULL);
239                                 if(!usec && !susec) {
240                                         // our first run
241                                         susec = cd->rrd_update_every * 1000000ULL;
242                                 }
243                                 else {
244                                         // second+ run
245                                         usec = usecdiff(&now, &last) - susec;
246                                         error("PLUGINSD: %s last loop took %llu usec (worked for %llu, sleeped for %llu).\n", cd->fullfilename, usec + susec, usec, susec);
247                                         if(usec < (rrd_update_every * 1000000ULL / 2ULL)) susec = (rrd_update_every * 1000000ULL) - usec;
248                                         else susec = rrd_update_every * 1000000ULL / 2ULL;
249                                 }
250
251                                 error("PLUGINSD: %s sleeping for %llu. Will kill with SIGCONT pid %d to wake it up.\n", cd->fullfilename, susec, cd->pid);
252                                 usleep(susec);
253                                 kill(cd->pid, SIGCONT);
254                                 bcopy(&now, &last, sizeof(struct timeval));
255                                 break;
256                         }
257 #endif
258                         else {
259                                 error("PLUGINSD: '%s' is sending command '%s' which is not known by netdata. Disabling it.", cd->fullfilename, s);
260                                 cd->enabled = 0;
261                                 kill(cd->pid, SIGTERM);
262                                 break;
263                         }
264                 }
265
266                 // fgets() failed or loop broke
267                 mypclose(fp);
268
269                 if(!count && cd->enabled) {
270                         error("PLUGINSD: '%s' does not generate usefull output. Disabling it.", cd->fullfilename);
271                         cd->enabled = 0;
272                         kill(cd->pid, SIGTERM);
273                 }
274
275                 if(cd->enabled) sleep(cd->update_every);
276                 else break;
277         }
278
279         cd->obsolete = 1;
280         return NULL;
281 }
282
283 void *pluginsd_main(void *ptr)
284 {
285         if(ptr) { ; }
286
287         if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
288                 error("Cannot set pthread cancel type to DEFERRED.");
289
290         if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
291                 error("Cannot set pthread cancel state to ENABLE.");
292
293         char *dir_name = config_get("plugins", "plugins directory", PLUGINS_DIR);
294         int automatic_run = config_get_boolean("plugins", "enable running new plugins", 0);
295         int scan_frequency = config_get_number("plugins", "check for new plugins every", 60);
296         DIR *dir = NULL;
297         struct dirent *file = NULL;
298         struct plugind *cd;
299
300         // enable the apps plugin by default
301         config_get_boolean("plugins", "apps", 1);
302
303         if(scan_frequency < 1) scan_frequency = 1;
304
305         while(1) {
306                 dir = opendir(dir_name);
307                 if(!dir) {
308                         error("Cannot open directory '%s'.", dir_name);
309                         return NULL;
310                 }
311
312                 while((file = readdir(dir))) {
313                         debug(D_PLUGINSD, "PLUGINSD: Examining file '%s'", file->d_name);
314
315                         if(strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0) continue;
316
317                         int len = strlen(file->d_name);
318                         if(len <= (int)PLUGINSD_FILE_SUFFIX_LEN) continue;
319                         if(strcmp(PLUGINSD_FILE_SUFFIX, &file->d_name[len - (int)PLUGINSD_FILE_SUFFIX_LEN]) != 0) {
320                                 debug(D_PLUGINSD, "PLUGINSD: File '%s' does not end in '%s'.", file->d_name, PLUGINSD_FILE_SUFFIX);
321                                 continue;
322                         }
323
324                         char pluginname[CONFIG_MAX_NAME + 1];
325                         snprintf(pluginname, CONFIG_MAX_NAME, "%.*s", (int)(len - PLUGINSD_FILE_SUFFIX_LEN), file->d_name);
326                         int enabled = config_get_boolean("plugins", pluginname, automatic_run);
327
328                         if(!enabled) {
329                                 debug(D_PLUGINSD, "PLUGINSD: plugin '%s' is not enabled", file->d_name);
330                                 continue;
331                         }
332
333                         // check if it runs already
334                         for(cd = pluginsd_root ; cd ; cd = cd->next) {
335                                 if(strcmp(cd->filename, file->d_name) == 0) break;
336                         }
337                         if(cd && !cd->obsolete) {
338                                 debug(D_PLUGINSD, "PLUGINSD: plugin '%s' is already running", cd->filename);
339                                 continue;
340                         }
341
342                         // it is not running
343                         // allocate a new one, or use the obsolete one
344                         if(!cd) {
345                                 cd = calloc(sizeof(struct plugind), 1);
346                                 if(!cd) fatal("Cannot allocate memory for plugin.");
347
348                                 snprintf(cd->id, CONFIG_MAX_NAME, "plugin:%s", pluginname);
349                                 
350                                 strncpy(cd->filename, file->d_name, FILENAME_MAX);
351                                 snprintf(cd->fullfilename, FILENAME_MAX, "%s/%s", dir_name, cd->filename);
352
353                                 cd->enabled = enabled;
354                                 cd->update_every = config_get_number(cd->id, "update every", rrd_update_every);
355                                 cd->started_t = time(NULL);
356
357                                 char *def = "";
358                                 snprintf(cd->cmd, PLUGINSD_CMD_MAX, "exec %s %d %s", cd->fullfilename, cd->update_every, config_get(cd->id, "command options", def));
359
360                                 // link it
361                                 if(pluginsd_root) cd->next = pluginsd_root;
362                                 pluginsd_root = cd;
363                         }
364                         cd->obsolete = 0;
365
366                         if(!cd->enabled) continue;
367
368                         // spawn a new thread for it
369                         if(pthread_create(&cd->thread, NULL, pluginsd_worker_thread, cd) != 0) {
370                                 error("CHARTS.D: failed to create new thread for chart.d %s.", cd->filename);
371                                 cd->obsolete = 1;
372                         }
373                         else if(pthread_detach(cd->thread) != 0)
374                                 error("CHARTS.D: Cannot request detach of newly created thread for chart.d %s.", cd->filename);
375                 }
376
377                 closedir(dir);
378                 sleep(scan_frequency);
379         }
380
381         return NULL;
382 }
383
384