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