src/utils_vl_lookup.[ch]: Add module for efficient lookup of VL matches with wildcards.
[collectd.git] / src / utils_vl_lookup.c
1 /**
2  * collectd - src/utils_vl_lookup.c
3  * Copyright (C) 2012  Florian Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "utils_vl_lookup.h"
30 #include "utils_avltree.h"
31
32 #if BUILD_TEST
33 # define sstrncpy strncpy
34 # define plugin_log(s, ...) do { \
35   printf ("[severity %i] ", s); \
36   printf (__VA_ARGS__); \
37   printf ("\n"); \
38 } while (0)
39 #endif
40
41 /*
42  * Types
43  */
44 struct lookup_s
45 {
46   c_avl_tree_t *by_type_tree;
47
48   lookup_class_callback_t cb_user_class;
49   lookup_obj_callback_t cb_user_obj;
50   lookup_free_class_callback_t cb_free_class;
51   lookup_free_obj_callback_t cb_free_obj;
52 };
53
54 struct user_obj_s;
55 typedef struct user_obj_s user_obj_t;
56 struct user_obj_s
57 {
58   void *user_obj;
59   identifier_t ident;
60
61   user_obj_t *next;
62 };
63
64 struct user_class_s
65 {
66   void *user_class;
67   identifier_t ident;
68   user_obj_t *user_obj_list; /* list of user_obj */
69 };
70 typedef struct user_class_s user_class_t;
71
72 struct user_class_list_s;
73 typedef struct user_class_list_s user_class_list_t;
74 struct user_class_list_s
75 {
76   user_class_t entry;
77   user_class_list_t *next;
78 };
79
80 struct by_type_entry_s
81 {
82   c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */
83   user_class_list_t *wildcard_plugin_list;
84 };
85 typedef struct by_type_entry_s by_type_entry_t;
86
87 #define LU_IS_ANY(str) (strcmp (str, "/any/") == 0)
88 #define LU_IS_ALL(str) (strcmp (str, "/all/") == 0)
89 #define LU_IS_WILDCARD(str) (LU_IS_ANY(str) || LU_IS_ALL(str))
90
91 /*
92  * Private functions
93  */
94 static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
95     data_set_t const *ds, value_list_t const *vl,
96     user_class_t *user_class)
97 {
98   user_obj_t *user_obj;
99
100   user_obj = malloc (sizeof (*user_obj));
101   if (user_obj == NULL)
102   {
103     ERROR ("utils_vl_lookup: malloc failed.");
104     return (NULL);
105   }
106   memset (user_obj, 0, sizeof (*user_obj));
107   user_obj->next = NULL;
108
109   user_obj->user_obj = obj->cb_user_class (ds, vl, user_class->user_class);
110   if (user_obj->user_obj == NULL)
111   {
112     sfree (user_obj);
113     WARNING("utils_vl_lookup: User-provided constructor failed.");
114     return (NULL);
115   }
116
117   sstrncpy (user_obj->ident.host,
118     LU_IS_ALL (user_class->ident.host) ?  "/all/" : vl->host,
119     sizeof (user_obj->ident.host));
120   sstrncpy (user_obj->ident.plugin,
121     LU_IS_ALL (user_class->ident.plugin) ?  "/all/" : vl->plugin,
122     sizeof (user_obj->ident.plugin));
123   sstrncpy (user_obj->ident.plugin_instance,
124     LU_IS_ALL (user_class->ident.plugin_instance) ?  "/all/" : vl->plugin_instance,
125     sizeof (user_obj->ident.plugin_instance));
126   sstrncpy (user_obj->ident.type,
127     LU_IS_ALL (user_class->ident.type) ?  "/all/" : vl->type,
128     sizeof (user_obj->ident.type));
129   sstrncpy (user_obj->ident.type_instance,
130     LU_IS_ALL (user_class->ident.type_instance) ?  "/all/" : vl->type_instance,
131     sizeof (user_obj->ident.type_instance));
132
133   if (user_class->user_obj_list == NULL)
134   {
135     user_class->user_obj_list = user_obj;
136   }
137   else
138   {
139     user_obj_t *last = user_class->user_obj_list;
140     while (last->next != NULL)
141       last = last->next;
142     last->next = user_obj;
143   }
144
145   return (user_obj);
146 } /* }}} void *lu_create_user_obj */
147
148 static user_obj_t *lu_find_user_obj (user_class_t *user_class, /* {{{ */
149     value_list_t const *vl)
150 {
151   user_obj_t *ptr;
152
153   for (ptr = user_class->user_obj_list;
154       ptr != NULL;
155       ptr = ptr->next)
156   {
157     if (!LU_IS_ALL (ptr->ident.host)
158         && (strcmp (ptr->ident.host, vl->host) != 0))
159       continue;
160     if (!LU_IS_ALL (ptr->ident.plugin_instance)
161         && (strcmp (ptr->ident.plugin_instance, vl->plugin_instance) != 0))
162       continue;
163     if (!LU_IS_ALL (ptr->ident.type_instance)
164         && (strcmp (ptr->ident.type_instance, vl->type_instance) != 0))
165       continue;
166
167     return (ptr);
168   }
169
170   return (NULL);
171 } /* }}} user_obj_t *lu_find_user_obj */
172
173 static int lu_handle_user_class (lookup_t *obj, /* {{{ */
174     data_set_t const *ds, value_list_t const *vl,
175     user_class_t *user_class)
176 {
177   user_obj_t *user_obj;
178   int status;
179
180   assert (strcmp (vl->type, user_class->ident.type) == 0);
181   assert (LU_IS_WILDCARD (user_class->ident.plugin)
182       || (strcmp (vl->plugin, user_class->ident.plugin) == 0));
183
184   /* When we get here, type and plugin already match the user class. Now check
185    * the rest of the fields. */
186   if (!LU_IS_WILDCARD (user_class->ident.type_instance)
187       && (strcmp (vl->type_instance, user_class->ident.type_instance) != 0))
188     return (1);
189   if (!LU_IS_WILDCARD (user_class->ident.plugin_instance)
190       && (strcmp (vl->plugin_instance,
191           user_class->ident.plugin_instance) != 0))
192     return (1);
193   if (!LU_IS_WILDCARD (user_class->ident.host)
194       && (strcmp (vl->host, user_class->ident.host) != 0))
195     return (1);
196
197   user_obj = lu_find_user_obj (user_class, vl);
198   if (user_obj == NULL)
199   {
200     /* call lookup_class_callback_t() and insert into the list of user objects. */
201     user_obj = lu_create_user_obj (obj, ds, vl, user_class);
202     if (user_obj == NULL)
203       return (-1);
204   }
205
206   status = obj->cb_user_obj (ds, vl, user_class, user_obj);
207   if (status != 0)
208   {
209     ERROR ("utils_vl_lookup: The user object callback failed with status %i.",
210         status);
211     /* Returning a negative value means: abort! */
212     if (status < 0)
213       return (status);
214     else
215       return (1);
216   }
217
218   return (0);
219 } /* }}} int lu_handle_user_class */
220
221 static int lu_handle_user_class_list (lookup_t *obj, /* {{{ */
222     data_set_t const *ds, value_list_t const *vl,
223     user_class_list_t *user_class_list)
224 {
225   user_class_list_t *ptr;
226   int retval = 0;
227   
228   for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
229   {
230     int status;
231
232     status = lu_handle_user_class (obj, ds, vl, &ptr->entry);
233     if (status < 0)
234       return (status);
235     else if (status == 0)
236       retval++;
237   }
238
239   return (retval);
240 } /* }}} int lu_handle_user_class_list */
241
242 static by_type_entry_t *lu_search_by_type (lookup_t *obj, /* {{{ */
243     char const *type, _Bool allocate_if_missing)
244 {
245   by_type_entry_t *by_type;
246   char *type_copy;
247   int status;
248
249   status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
250   if (status == 0)
251     return (by_type);
252
253   if (!allocate_if_missing)
254     return (NULL);
255
256   type_copy = strdup (type);
257   if (type_copy == NULL)
258   {
259     ERROR ("utils_vl_lookup: strdup failed.");
260     return (NULL);
261   }
262
263   by_type = malloc (sizeof (*by_type));
264   if (by_type == NULL)
265   {
266     ERROR ("utils_vl_lookup: malloc failed.");
267     sfree (type_copy);
268     return (NULL);
269   }
270   memset (by_type, 0, sizeof (*by_type));
271   by_type->wildcard_plugin_list = NULL;
272   
273   by_type->by_plugin_tree = c_avl_create ((void *) strcmp);
274   if (by_type->by_plugin_tree == NULL)
275   {
276     ERROR ("utils_vl_lookup: c_avl_create failed.");
277     sfree (by_type);
278     sfree (type_copy);
279     return (NULL);
280   }
281
282   status = c_avl_insert (obj->by_type_tree,
283       /* key = */ type_copy, /* value = */ by_type);
284   assert (status <= 0); /* >0 => entry exists => race condition. */
285   if (status != 0)
286   {
287     ERROR ("utils_vl_lookup: c_avl_insert failed.");
288     c_avl_destroy (by_type->by_plugin_tree);
289     sfree (by_type);
290     sfree (type_copy);
291     return (NULL);
292   }
293   
294   return (by_type);
295 } /* }}} by_type_entry_t *lu_search_by_type */
296
297 static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
298     identifier_t const *ident, user_class_list_t *user_class_list)
299 {
300   user_class_list_t *ptr = NULL;
301
302   /* Lookup user_class_list from the per-plugin structure. If this is the first
303    * user_class to be added, the blocks return immediately. Otherwise they will
304    * set "ptr" to non-NULL. */
305   if (LU_IS_WILDCARD (ident->plugin))
306   {
307     if (by_type->wildcard_plugin_list == NULL)
308     {
309       by_type->wildcard_plugin_list = user_class_list;
310       return (0);
311     }
312
313     ptr = by_type->wildcard_plugin_list;
314   } /* if (plugin is wildcard) */
315   else /* (plugin is not wildcard) */
316   {
317     int status;
318
319     status = c_avl_get (by_type->by_plugin_tree,
320         ident->plugin, (void *) &ptr);
321
322     if (status != 0) /* plugin not yet in tree */
323     {
324       char *plugin_copy = strdup (ident->plugin);
325
326       if (plugin_copy == NULL)
327       {
328         ERROR ("utils_vl_lookup: strdup failed.");
329         sfree (user_class_list);
330         return (ENOMEM);
331       }
332
333       status = c_avl_insert (by_type->by_plugin_tree,
334           plugin_copy, user_class_list);
335       if (status != 0)
336       {
337         ERROR ("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.",
338             plugin_copy, status);
339         sfree (plugin_copy);
340         sfree (user_class_list);
341         return (status);
342       }
343       else
344       {
345         return (0);
346       }
347     } /* if (plugin not yet in tree) */
348   } /* if (plugin is not wildcard) */
349
350   assert (ptr != NULL);
351
352   while (ptr->next != NULL)
353     ptr = ptr->next;
354   ptr->next = user_class_list;
355
356   return (0);
357 } /* }}} int lu_add_by_plugin */
358
359 static void lu_destroy_user_obj (lookup_t *obj, /* {{{ */
360     user_obj_t *user_obj)
361 {
362   while (user_obj != NULL)
363   {
364     user_obj_t *next = user_obj->next;
365
366     if (obj->cb_free_obj != NULL)
367       obj->cb_free_obj (user_obj->user_obj);
368     user_obj->user_obj = NULL;
369
370     sfree (user_obj);
371     user_obj = next;
372   }
373 } /* }}} void lu_destroy_user_obj */
374
375 static void lu_destroy_user_class_list (lookup_t *obj, /* {{{ */
376     user_class_list_t *user_class_list)
377 {
378   while (user_class_list != NULL)
379   {
380     user_class_list_t *next = user_class_list->next;
381
382     if (obj->cb_free_class != NULL)
383       obj->cb_free_class (user_class_list->entry.user_class);
384     user_class_list->entry.user_class = NULL;
385
386     lu_destroy_user_obj (obj, user_class_list->entry.user_obj_list);
387     user_class_list->entry.user_obj_list = NULL;
388
389     sfree (user_class_list);
390     user_class_list = next;
391   }
392 } /* }}} void lu_destroy_user_class_list */
393
394 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
395     by_type_entry_t *by_type)
396 {
397   
398   while (42)
399   {
400     char *plugin = NULL;
401     user_class_list_t *user_class_list = NULL;
402     int status;
403
404     status = c_avl_pick (by_type->by_plugin_tree,
405         (void *) &plugin, (void *) &user_class_list);
406     if (status != 0)
407       break;
408
409     DEBUG ("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".",
410         plugin);
411     sfree (plugin);
412     lu_destroy_user_class_list (obj, user_class_list);
413   }
414
415   c_avl_destroy (by_type->by_plugin_tree);
416   by_type->by_plugin_tree = NULL;
417
418   lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
419   by_type->wildcard_plugin_list = NULL;
420
421   sfree (by_type);
422 } /* }}} int lu_destroy_by_type */
423
424 /*
425  * Public functions
426  */
427 lookup_t *lookup_create (lookup_class_callback_t cb_user_class, /* {{{ */
428     lookup_obj_callback_t cb_user_obj,
429     lookup_free_class_callback_t cb_free_class,
430     lookup_free_obj_callback_t cb_free_obj)
431 {
432   lookup_t *obj = malloc (sizeof (*obj));
433   if (obj == NULL)
434   {
435     ERROR ("utils_vl_lookup: malloc failed.");
436     return (NULL);
437   }
438   memset (obj, 0, sizeof (*obj));
439
440   obj->by_type_tree = c_avl_create ((void *) strcmp);
441   if (obj->by_type_tree == NULL)
442   {
443     ERROR ("utils_vl_lookup: c_avl_create failed.");
444     sfree (obj);
445     return (NULL);
446   }
447
448   obj->cb_user_class = cb_user_class;
449   obj->cb_user_obj = cb_user_obj;
450   obj->cb_free_class = cb_free_class;
451   obj->cb_free_obj = cb_free_obj;
452
453   return (obj);
454 } /* }}} lookup_t *lookup_create */
455
456 void lookup_destroy (lookup_t *obj) /* {{{ */
457 {
458   int status;
459
460   if (obj == NULL)
461     return;
462
463   while (42)
464   {
465     char *type = NULL;
466     by_type_entry_t *by_type = NULL;
467
468     status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
469     if (status != 0)
470       break;
471
472     DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
473     sfree (type);
474     lu_destroy_by_type (obj, by_type);
475   }
476
477   c_avl_destroy (obj->by_type_tree);
478   obj->by_type_tree = NULL;
479
480   sfree (obj);
481 } /* }}} void lookup_destroy */
482
483 int lookup_add (lookup_t *obj, /* {{{ */
484     identifier_t const *ident, void *user_class)
485 {
486   by_type_entry_t *by_type = NULL;
487   user_class_list_t *user_class_obj;
488
489   by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
490   if (by_type == NULL)
491     return (-1);
492
493   user_class_obj = malloc (sizeof (*user_class_obj));
494   if (user_class_obj == NULL)
495   {
496     ERROR ("utils_vl_lookup: malloc failed.");
497     return (ENOMEM);
498   }
499   memset (user_class_obj, 0, sizeof (*user_class_obj));
500   user_class_obj->entry.user_class = user_class;
501   memmove (&user_class_obj->entry.ident, ident, sizeof (*ident));
502   user_class_obj->entry.user_obj_list = NULL;
503   user_class_obj->next = NULL;
504
505   return (lu_add_by_plugin (by_type, ident, user_class_obj));
506 } /* }}} int lookup_add */
507
508 /* returns the number of successful calls to the callback function */
509 int lookup_search (lookup_t *obj, /* {{{ */
510     data_set_t const *ds, value_list_t const *vl)
511 {
512   by_type_entry_t *by_type = NULL;
513   user_class_list_t *user_class_list = NULL;
514   int retval = 0;
515   int status;
516
517   if ((obj == NULL) || (ds == NULL) || (vl == NULL))
518     return (-EINVAL);
519
520   by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
521   if (by_type == NULL)
522     return (0);
523
524   status = c_avl_get (by_type->by_plugin_tree,
525       vl->plugin, (void *) &user_class_list);
526   if (status == 0)
527   {
528     status = lu_handle_user_class_list (obj, ds, vl, user_class_list);
529     if (status < 0)
530       return (status);
531     retval += status;
532   }
533
534   if (by_type->wildcard_plugin_list != NULL)
535   {
536     status = lu_handle_user_class_list (obj, ds, vl,
537         by_type->wildcard_plugin_list);
538     if (status < 0)
539       return (status);
540     retval += status;
541   }
542     
543   return (retval);
544 } /* }}} lookup_search */