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