src/utils_logtail.[ch]: Add a module to provide facilities for logfile tailing.
[collectd.git] / src / utils_logtail.c
1 /*
2  * collectd - src/utils_logtail.c
3  * Copyright (C) 2007-2008  C-Ware, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Author:
19  *   Luke Heberling <lukeh at c-ware.com>
20  *
21  * Description:
22  *   Encapsulates useful code to plugins which must parse a log file.
23  */
24
25 #include <time.h>
26 #include "plugin.h"
27 #include "utils_tail.h"
28 #include "utils_llist.h"
29 #include "utils_avltree.h"
30
31 #define DESTROY_INSTANCE(inst)                  \
32   do {                                          \
33     if (inst != NULL)                           \
34     {                                           \
35       if (inst->name != NULL)                   \
36         free (inst->name);                      \
37       if (inst->tail != NULL)                   \
38         cu_tail_destroy (inst->tail);           \
39       if (inst->tree != NULL)                   \
40         c_avl_destroy (inst->tree);             \
41         assert (inst->list == NULL ||           \
42             llist_size (inst->list) == 0);      \
43         if (inst->list != NULL)                 \
44           llist_destroy (inst->list);           \
45         if (inst->counters != NULL)             \
46           free (inst->counters);                \
47         free (inst);                            \
48     }                                           \
49   } while (0)
50
51 struct logtail_instance_s
52 {
53   char *name;
54   cu_tail_t *tail;
55   c_avl_tree_t *tree;
56   llist_t *list;
57   uint cache_size;
58   unsigned long *counters;
59 };
60 typedef struct logtail_instance_s logtail_instance_t;
61
62 static void submit (const char *plugin, const char *instance,
63     const char *name, unsigned long value)
64 {
65   value_list_t vl = VALUE_LIST_INIT;
66   value_t values[1];
67   const data_set_t *ds;
68
69   ds = plugin_get_ds (name);
70   if (ds == NULL)
71     return;
72
73   if (ds->ds->type == DS_TYPE_GAUGE)
74     values[0].gauge = (float)value;
75   else
76     values[0].counter = value;
77
78   vl.values = values;
79   vl.values_len = 1;
80   vl.time = time (NULL);
81   strncpy (vl.host, hostname_g, sizeof (vl.host));
82   strncpy (vl.plugin, plugin, sizeof (vl.plugin));
83   strncpy (vl.type_instance, "", sizeof (vl.type_instance));
84   strncpy (vl.plugin_instance, instance, sizeof (vl.plugin_instance));
85
86   plugin_dispatch_values (name, &vl);
87 } /* static void submit */
88
89 int logtail_term (llist_t **instances)
90 {
91   llentry_t *entry;
92   llentry_t *prev;
93
94   llentry_t *lentry;
95   llentry_t *lprev;
96
97   logtail_instance_t *instance;
98
99   if (*instances != NULL)
100   {
101     entry = llist_head (*instances);
102     while (entry)
103     {
104       prev = entry;
105       entry = entry->next;
106
107       instance = prev->value;
108       if (instance->list != NULL)
109       {
110         lentry = llist_head (instance->list);
111         while (lentry)
112         {
113           lprev = lentry;
114           lentry = lentry->next;
115           if (lprev->key != NULL)
116             free (lprev->key);
117           if (lprev->value != NULL)
118             free (lprev->value);
119           llist_remove (instance->list, lprev);
120           llentry_destroy (lprev);
121         }
122       }
123
124       llist_remove (*instances, prev);
125       llentry_destroy (prev);
126       DESTROY_INSTANCE (instance);
127     }
128
129     llist_destroy (*instances);
130     *instances = NULL;
131   }
132
133   return (0);
134 } /* int logtail_term */
135
136 int logtail_init (llist_t **instances)
137 {
138   if (*instances == NULL)
139     *instances = llist_create();
140
141   return (*instances == NULL);
142 } /* int logtail_init */
143
144 int logtail_read (llist_t **instances, tailfunc *func, char *plugin, char **names)
145 {
146   llentry_t *entry;
147   logtail_instance_t *instance;
148   char buffer[2048];
149   char **name;
150   unsigned long *counter;
151
152   for (entry = llist_head (*instances); entry != NULL; entry = entry->next )
153   {
154     instance = (logtail_instance_t *)entry->value;
155     cu_tail_read (instance->tail, buffer,
156         sizeof (buffer), func, instance);
157
158     for (name = names, counter = instance->counters;
159         *name != NULL; ++name, ++counter)
160       submit (plugin, instance->name, *name, *counter);
161   }
162
163   return (0);
164 } /* int logtail_read */
165
166 int logtail_config (llist_t **instances, oconfig_item_t *ci, char *plugin,
167     char **names, char *default_file, int default_cache_size)
168 {
169   int counterslen = 0;
170   logtail_instance_t *instance;
171
172   llentry_t *entry;
173   char *tail_file;
174
175   oconfig_item_t *gchild;
176   int gchildren;
177
178   oconfig_item_t *child = ci->children;
179   int children = ci->children_num;
180
181   while (*(names++) != NULL)
182     counterslen += sizeof (unsigned long);
183
184   if (*instances == NULL)
185   {
186     *instances = llist_create();
187     if (*instances == NULL)
188       return 1;
189   }
190
191
192   for (; children; --children, ++child)
193   {
194     tail_file = NULL;
195
196     if (strcmp (child->key, "Instance") != 0)
197     {
198       WARNING ("%s plugin: Ignoring unknown"
199           " config option `%s'.", plugin, child->key);
200       continue;
201     }
202
203     if ((child->values_num != 1) ||
204         (child->values[0].type != OCONFIG_TYPE_STRING))
205     {
206       WARNING ("%s plugin: `Instance' needs exactly"
207           " one string argument.", plugin);
208       continue;
209     }
210
211     instance = malloc (sizeof (logtail_instance_t));
212     if (instance == NULL)
213     {
214       ERROR ("%s plugin: `malloc' failed.", plugin);
215       return 1;
216     }
217     memset (instance, '\0', sizeof (logtail_instance_t));
218
219     instance->counters = malloc (counterslen);
220     if (instance->counters == NULL)
221     {
222       ERROR ("%s plugin: `malloc' failed.", plugin);
223       DESTROY_INSTANCE (instance);
224       return 1;
225     }
226     memset (instance->counters, '\0', counterslen);
227
228     instance->name = strdup (child->values[0].value.string);
229     if (instance->name == NULL)
230     {
231       ERROR ("%s plugin: `strdup' failed.", plugin);
232       DESTROY_INSTANCE (instance);
233       return 1;
234     }
235
236     instance->list = llist_create();
237     if (instance->list == NULL)
238     {
239       ERROR ("%s plugin: `llist_create' failed.", plugin);
240       DESTROY_INSTANCE (instance);
241       return 1;
242     }
243
244     instance->tree = c_avl_create ((void *)strcmp);
245     if (instance->tree == NULL)
246     {
247       ERROR ("%s plugin: `c_avl_create' failed.", plugin);
248       DESTROY_INSTANCE (instance);
249       return 1;
250     }
251
252     entry = llentry_create (instance->name, instance);
253     if (entry == NULL)
254     {
255       ERROR ("%s plugin: `llentry_create' failed.", plugin);
256       DESTROY_INSTANCE (instance);
257       return 1;
258     }
259
260     gchild = child->children;
261     gchildren = child->children_num;
262
263     for (; gchildren; --gchildren, ++gchild)
264     {
265       if (strcmp (gchild->key, "LogFile") == 0)
266       {
267         if (gchild->values_num != 1 || 
268             gchild->values[0].type != OCONFIG_TYPE_STRING)
269         {
270           WARNING ("%s plugin: config option `%s'"
271               " should have exactly one string value.",
272               plugin, gchild->key);
273           continue;
274         }
275         if (tail_file != NULL)
276         {
277           WARNING ("%s plugin: ignoring extraneous"
278               " `LogFile' config option.", plugin);
279           continue;
280         }
281         tail_file = gchild->values[0].value.string;
282       }
283       else if (strcmp (gchild->key, "CacheSize") == 0)
284       {
285         if (gchild->values_num != 1 
286             || gchild->values[0].type != OCONFIG_TYPE_NUMBER)
287         {
288           WARNING ("%s plugin: config option `%s'"
289               " should have exactly one numerical value.",
290               plugin, gchild->key);
291           continue;
292         }
293         if (instance->cache_size)
294         {
295           WARNING ("%s plugin: ignoring extraneous"
296               " `CacheSize' config option.", plugin);
297           continue;
298         }
299         instance->cache_size = gchild->values[0].value.number;
300       }
301       else
302       {
303         WARNING ("%s plugin: Ignoring unknown config option"
304             " `%s'.", plugin, gchild->key);
305         continue;
306       }
307
308       if (gchild->children_num)
309       {
310         WARNING ("%s plugin: config option `%s' should not"
311             " have children.", plugin, gchild->key);
312       }
313     }
314
315     if (tail_file == NULL)
316       tail_file = default_file;
317     instance->tail = cu_tail_create (tail_file);
318     if (instance->tail == NULL)
319     {
320       ERROR ("%s plugin: `cu_tail_create' failed.", plugin);
321       DESTROY_INSTANCE (instance);
322
323       llentry_destroy (entry);
324       return 1;
325     }
326
327     if (instance->cache_size == 0)
328       instance->cache_size = default_cache_size;
329
330     llist_append (*instances, entry);
331   }
332
333   return 0;
334 } /* int logtail_config */
335
336 unsigned long *logtail_counters (logtail_instance_t *instance)
337 {
338   return instance->counters;
339 } /* unsigned log *logtail_counters */
340
341 int logtail_cache (logtail_instance_t *instance, char *plugin, char *key, void **data, int len)
342 {
343   llentry_t *entry = NULL;
344
345   if (c_avl_get (instance->tree, key, (void*)&entry) == 0)
346   {
347     *data = entry->value;
348     return (0);
349   }
350
351   if ((key = strdup (key)) == NULL)
352   {
353     ERROR ("%s plugin: `strdup' failed.", plugin);
354     return (0);
355   }
356
357   if (data != NULL && (*data = malloc (len)) == NULL)
358   {
359     ERROR ("%s plugin: `malloc' failed.", plugin);
360     free (key);
361     return (0);
362   }
363
364   if (data != NULL)
365     memset (*data, '\0', len);
366
367   entry = llentry_create (key, data == NULL ? NULL : *data);
368   if (entry == NULL)
369   {
370     ERROR ("%s plugin: `llentry_create' failed.", plugin);
371     free (key);
372     if (data !=NULL)
373       free (*data);
374     return (0);
375   }
376
377   if (c_avl_insert (instance->tree, key, entry) != 0)
378   {
379     ERROR ("%s plugin: `c_avl_insert' failed.", plugin);
380     llentry_destroy (entry);
381     free (key);
382     if (data != NULL)
383       free (*data);
384     return (0);
385   }
386
387   llist_prepend (instance->list, entry);
388
389   while (llist_size (instance->list) > instance->cache_size &&
390       (entry = llist_tail (instance->list)) != NULL )
391   {
392     c_avl_remove (instance->tree, entry->key, NULL, NULL);
393     llist_remove (instance->list, entry);
394     free (entry->key);
395     if (entry->value != NULL)
396       free (entry->value);
397     llentry_destroy (entry);
398   }
399
400   return (1);
401 }
402
403 void logtail_decache (logtail_instance_t *instance, char *key)
404 {
405   llentry_t *entry = NULL;
406   if (c_avl_remove (instance->tree, key, NULL, (void*)&entry))
407     return;
408
409   llist_remove (instance->list, entry);
410   free (entry->key);
411   if (entry->value != NULL)
412     free (entry->value);
413
414   llentry_destroy (entry);
415 }
416
417 /* vim: set sw=2 sts=2 ts=8 : */