collectd-tg: Fix sleep interval.
[collectd.git] / src / filecount.c
1 /**
2  * collectd - src/filecount.c
3  * Copyright (C) 2008  Alessandro Iurlano
4  * Copyright (C) 2008  Florian octo Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Alessandro Iurlano <alessandro.iurlano at gmail.com>
21  *   Florian octo Forster <octo at collectd.org>
22  **/
23
24 #include "collectd.h"
25
26 #include "common.h"
27 #include "plugin.h"
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <dirent.h>
33 #include <fnmatch.h>
34
35 #define FC_RECURSIVE 1
36 #define FC_HIDDEN 2
37
38 struct fc_directory_conf_s
39 {
40   char *path;
41   char *instance;
42
43   int options;
44
45   /* Data counters */
46   uint64_t files_num;
47   uint64_t files_size;
48
49   /* Selectors */
50   char *name;
51   int64_t mtime;
52   int64_t size;
53
54   /* Helper for the recursive functions */
55   time_t now;
56 };
57 typedef struct fc_directory_conf_s fc_directory_conf_t;
58
59 static fc_directory_conf_t **directories = NULL;
60 static size_t directories_num = 0;
61
62 static void fc_submit_dir (const fc_directory_conf_t *dir)
63 {
64   value_t values[1];
65   value_list_t vl = VALUE_LIST_INIT;
66
67   values[0].gauge = (gauge_t) dir->files_num;
68
69   vl.values = values;
70   vl.values_len = STATIC_ARRAY_SIZE (values);
71   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
72   sstrncpy (vl.plugin, "filecount", sizeof (vl.plugin));
73   sstrncpy (vl.plugin_instance, dir->instance, sizeof (vl.plugin_instance));
74   sstrncpy (vl.type, "files", sizeof (vl.type));
75
76   plugin_dispatch_values (&vl);
77
78   values[0].gauge = (gauge_t) dir->files_size;
79   sstrncpy (vl.type, "bytes", sizeof (vl.type));
80
81   plugin_dispatch_values (&vl);
82 } /* void fc_submit_dir */
83
84 /*
85  * Config:
86  * <Plugin filecount>
87  *   <Directory /path/to/dir>
88  *     Instance "foobar"
89  *     Name "*.conf"
90  *     MTime -3600
91  *     Size "+10M"
92  *   </Directory>
93  * </Plugin>
94  *
95  * Collect:
96  * - Number of files
97  * - Total size
98  */
99
100 static int fc_config_set_instance (fc_directory_conf_t *dir, const char *str)
101 {
102   char buffer[1024];
103   char *ptr;
104   char *copy;
105
106   sstrncpy (buffer, str, sizeof (buffer));
107   for (ptr = buffer; *ptr != 0; ptr++)
108     if (*ptr == '/')
109       *ptr = '_';
110
111   for (ptr = buffer; *ptr == '_'; ptr++)
112     /* do nothing */;
113
114   if (*ptr == 0)
115     return (-1);
116
117   copy = strdup (ptr);
118   if (copy == NULL)
119     return (-1);
120
121   sfree (dir->instance);
122   dir->instance = copy;
123
124   return (0);
125 } /* int fc_config_set_instance */
126
127 static int fc_config_add_dir_instance (fc_directory_conf_t *dir,
128     oconfig_item_t *ci)
129 {
130   if ((ci->values_num != 1)
131       || (ci->values[0].type != OCONFIG_TYPE_STRING))
132   {
133     WARNING ("filecount plugin: The `Instance' config option needs exactly "
134         "one string argument.");
135     return (-1);
136   }
137
138   return (fc_config_set_instance (dir, ci->values[0].value.string));
139 } /* int fc_config_add_dir_instance */
140
141 static int fc_config_add_dir_name (fc_directory_conf_t *dir,
142     oconfig_item_t *ci)
143 {
144   char *temp;
145
146   if ((ci->values_num != 1)
147       || (ci->values[0].type != OCONFIG_TYPE_STRING))
148   {
149     WARNING ("filecount plugin: The `Name' config option needs exactly one "
150         "string argument.");
151     return (-1);
152   }
153
154   temp = strdup (ci->values[0].value.string);
155   if (temp == NULL)
156   {
157     ERROR ("filecount plugin: strdup failed.");
158     return (-1);
159   }
160
161   sfree (dir->name);
162   dir->name = temp;
163
164   return (0);
165 } /* int fc_config_add_dir_name */
166
167 static int fc_config_add_dir_mtime (fc_directory_conf_t *dir,
168     oconfig_item_t *ci)
169 {
170   char *endptr;
171   double temp;
172
173   if ((ci->values_num != 1)
174       || ((ci->values[0].type != OCONFIG_TYPE_STRING)
175         && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
176   {
177     WARNING ("filecount plugin: The `MTime' config option needs exactly one "
178         "string or numeric argument.");
179     return (-1);
180   }
181
182   if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
183   {
184     dir->mtime = (int64_t) ci->values[0].value.number;
185     return (0);
186   }
187
188   errno = 0;
189   endptr = NULL;
190   temp = strtod (ci->values[0].value.string, &endptr);
191   if ((errno != 0) || (endptr == NULL)
192       || (endptr == ci->values[0].value.string))
193   {
194     WARNING ("filecount plugin: Converting `%s' to a number failed.",
195         ci->values[0].value.string);
196     return (-1);
197   }
198
199   switch (*endptr)
200   {
201     case 0:
202     case 's':
203     case 'S':
204       break;
205
206     case 'm':
207     case 'M':
208       temp *= 60;
209       break;
210
211     case 'h':
212     case 'H':
213       temp *= 3600;
214       break;
215
216     case 'd':
217     case 'D':
218       temp *= 86400;
219       break;
220
221     case 'w':
222     case 'W':
223       temp *= 7 * 86400;
224       break;
225
226     case 'y':
227     case 'Y':
228       temp *= 31557600; /* == 365.25 * 86400 */
229       break;
230
231     default:
232       WARNING ("filecount plugin: Invalid suffix for `MTime': `%c'", *endptr);
233       return (-1);
234   } /* switch (*endptr) */
235
236   dir->mtime = (int64_t) temp;
237
238   return (0);
239 } /* int fc_config_add_dir_mtime */
240
241 static int fc_config_add_dir_size (fc_directory_conf_t *dir,
242     oconfig_item_t *ci)
243 {
244   char *endptr;
245   double temp;
246
247   if ((ci->values_num != 1)
248       || ((ci->values[0].type != OCONFIG_TYPE_STRING)
249         && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
250   {
251     WARNING ("filecount plugin: The `Size' config option needs exactly one "
252         "string or numeric argument.");
253     return (-1);
254   }
255
256   if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
257   {
258     dir->size = (int64_t) ci->values[0].value.number;
259     return (0);
260   }
261
262   errno = 0;
263   endptr = NULL;
264   temp = strtod (ci->values[0].value.string, &endptr);
265   if ((errno != 0) || (endptr == NULL)
266       || (endptr == ci->values[0].value.string))
267   {
268     WARNING ("filecount plugin: Converting `%s' to a number failed.",
269         ci->values[0].value.string);
270     return (-1);
271   }
272
273   switch (*endptr)
274   {
275     case 0:
276     case 'b':
277     case 'B':
278       break;
279
280     case 'k':
281     case 'K':
282       temp *= 1000.0;
283       break;
284
285     case 'm':
286     case 'M':
287       temp *= 1000.0 * 1000.0;
288       break;
289
290     case 'g':
291     case 'G':
292       temp *= 1000.0 * 1000.0 * 1000.0;
293       break;
294
295     case 't':
296     case 'T':
297       temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0;
298       break;
299
300     case 'p':
301     case 'P':
302       temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0;
303       break;
304
305     default:
306       WARNING ("filecount plugin: Invalid suffix for `Size': `%c'", *endptr);
307       return (-1);
308   } /* switch (*endptr) */
309
310   dir->size = (int64_t) temp;
311
312   return (0);
313 } /* int fc_config_add_dir_size */
314
315 static int fc_config_add_dir_option (fc_directory_conf_t *dir,
316     oconfig_item_t *ci, int bit)
317 {
318   if ((ci->values_num != 1)
319       || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
320   {
321     WARNING ("filecount plugin: The `Recursive' config options needs exactly "
322         "one boolean argument.");
323     return (-1);
324   }
325
326   if (ci->values[0].value.boolean)
327     dir->options |= bit;
328   else
329     dir->options &= ~bit;
330
331   return (0);
332 } /* int fc_config_add_dir_option */
333
334 static int fc_config_add_dir (oconfig_item_t *ci)
335 {
336   fc_directory_conf_t *dir;
337   int status;
338
339   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
340   {
341     WARNING ("filecount plugin: `Directory' needs exactly one string "
342         "argument.");
343     return (-1);
344   }
345
346   /* Initialize `dir' */
347   dir = calloc (1, sizeof (*dir));
348   if (dir == NULL)
349   {
350     ERROR ("filecount plugin: calloc failed.");
351     return (-1);
352   }
353
354   dir->path = strdup (ci->values[0].value.string);
355   if (dir->path == NULL)
356   {
357     ERROR ("filecount plugin: strdup failed.");
358     sfree (dir);
359     return (-1);
360   }
361
362   fc_config_set_instance (dir, dir->path);
363
364   dir->options = FC_RECURSIVE;
365
366   dir->name = NULL;
367   dir->mtime = 0;
368   dir->size = 0;
369
370   status = 0;
371   for (int i = 0; i < ci->children_num; i++)
372   {
373     oconfig_item_t *option = ci->children + i;
374
375     if (strcasecmp ("Instance", option->key) == 0)
376       status = fc_config_add_dir_instance (dir, option);
377     else if (strcasecmp ("Name", option->key) == 0)
378       status = fc_config_add_dir_name (dir, option);
379     else if (strcasecmp ("MTime", option->key) == 0)
380       status = fc_config_add_dir_mtime (dir, option);
381     else if (strcasecmp ("Size", option->key) == 0)
382       status = fc_config_add_dir_size (dir, option);
383     else if (strcasecmp ("Recursive", option->key) == 0)
384       status = fc_config_add_dir_option (dir, option, FC_RECURSIVE);
385     else if (strcasecmp ("IncludeHidden", option->key) == 0)
386       status = fc_config_add_dir_option (dir, option, FC_HIDDEN);
387     else
388     {
389       WARNING ("filecount plugin: fc_config_add_dir: "
390           "Option `%s' not allowed here.", option->key);
391       status = -1;
392     }
393
394     if (status != 0)
395       break;
396   } /* for (ci->children) */
397
398   if (status == 0)
399   {
400     fc_directory_conf_t **temp;
401
402     temp = realloc (directories,
403         sizeof (*directories) * (directories_num + 1));
404     if (temp == NULL)
405     {
406       ERROR ("filecount plugin: realloc failed.");
407       status = -1;
408     }
409     else
410     {
411       directories = temp;
412       directories[directories_num] = dir;
413       directories_num++;
414     }
415   }
416
417   if (status != 0)
418   {
419     sfree (dir->name);
420     sfree (dir->instance);
421     sfree (dir->path);
422     sfree (dir);
423     return (-1);
424   }
425
426   return (0);
427 } /* int fc_config_add_dir */
428
429 static int fc_config (oconfig_item_t *ci)
430 {
431   for (int i = 0; i < ci->children_num; i++)
432   {
433     oconfig_item_t *child = ci->children + i;
434     if (strcasecmp ("Directory", child->key) == 0)
435       fc_config_add_dir (child);
436     else
437     {
438       WARNING ("filecount plugin: Ignoring unknown config option `%s'.",
439           child->key);
440     }
441   } /* for (ci->children) */
442
443   return (0);
444 } /* int fc_config */
445
446 static int fc_init (void)
447 {
448   if (directories_num < 1)
449   {
450     WARNING ("filecount plugin: No directories have been configured.");
451     return (-1);
452   }
453
454   return (0);
455 } /* int fc_init */
456
457 static int fc_read_dir_callback (const char *dirname, const char *filename,
458     void *user_data)
459 {
460   fc_directory_conf_t *dir = user_data;
461   char abs_path[PATH_MAX];
462   struct stat statbuf;
463   int status;
464
465   if (dir == NULL)
466     return (-1);
467
468   ssnprintf (abs_path, sizeof (abs_path), "%s/%s", dirname, filename);
469
470   status = lstat (abs_path, &statbuf);
471   if (status != 0)
472   {
473     ERROR ("filecount plugin: stat (%s) failed.", abs_path);
474     return (-1);
475   }
476
477   if (S_ISDIR (statbuf.st_mode) && (dir->options & FC_RECURSIVE))
478   {
479     status = walk_directory (abs_path, fc_read_dir_callback, dir,
480         /* include hidden = */ (dir->options & FC_HIDDEN) ? 1 : 0);
481     return (status);
482   }
483   else if (!S_ISREG (statbuf.st_mode))
484   {
485     return (0);
486   }
487
488   if (dir->name != NULL)
489   {
490     status = fnmatch (dir->name, filename, /* flags = */ 0);
491     if (status != 0)
492       return (0);
493   }
494
495   if (dir->mtime != 0)
496   {
497     time_t mtime = dir->now;
498
499     if (dir->mtime < 0)
500       mtime += dir->mtime;
501     else
502       mtime -= dir->mtime;
503
504     DEBUG ("filecount plugin: Only collecting files that were touched %s %u.",
505         (dir->mtime < 0) ? "after" : "before",
506         (unsigned int) mtime);
507
508     if (((dir->mtime < 0) && (statbuf.st_mtime < mtime))
509         || ((dir->mtime > 0) && (statbuf.st_mtime > mtime)))
510       return (0);
511   }
512
513   if (dir->size != 0)
514   {
515     off_t size;
516
517     if (dir->size < 0)
518       size = (off_t) ((-1) * dir->size);
519     else
520       size = (off_t) dir->size;
521
522     if (((dir->size < 0) && (statbuf.st_size > size))
523         || ((dir->size > 0) && (statbuf.st_size < size)))
524       return (0);
525   }
526
527   dir->files_num++;
528   dir->files_size += (uint64_t) statbuf.st_size;
529
530   return (0);
531 } /* int fc_read_dir_callback */
532
533 static int fc_read_dir (fc_directory_conf_t *dir)
534 {
535   int status;
536
537   dir->files_num = 0;
538   dir->files_size = 0;
539
540   if (dir->mtime != 0)
541     dir->now = time (NULL);
542
543   status = walk_directory (dir->path, fc_read_dir_callback, dir,
544       /* include hidden */ (dir->options & FC_HIDDEN) ? 1 : 0);
545   if (status != 0)
546   {
547     WARNING ("filecount plugin: walk_directory (%s) failed.", dir->path);
548     return (-1);
549   }
550
551   fc_submit_dir (dir);
552
553   return (0);
554 } /* int fc_read_dir */
555
556 static int fc_read (void)
557 {
558   for (size_t i = 0; i < directories_num; i++)
559     fc_read_dir (directories[i]);
560
561   return (0);
562 } /* int fc_read */
563
564 void module_register (void)
565 {
566   plugin_register_complex_config ("filecount", fc_config);
567   plugin_register_init ("filecount", fc_init);
568   plugin_register_read ("filecount", fc_read);
569 } /* void module_register */
570
571 /*
572  * vim: set sw=2 sts=2 et :
573  */