a0237841eb43a7aebea172bed7246cd71f0e0bea
[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
29 #include <pthread.h>
30 #include <regex.h>
31
32 #include "common.h"
33 #include "utils_vl_lookup.h"
34 #include "utils_avltree.h"
35
36 #if HAVE_LIBKSTAT
37 kstat_ctl_t *kc;
38 #endif /* HAVE_LIBKSTAT */
39
40 #if BUILD_TEST
41 # define sstrncpy strncpy
42 # define plugin_log(s, ...) do { \
43   printf ("[severity %i] ", s); \
44   printf (__VA_ARGS__); \
45   printf ("\n"); \
46 } while (0)
47 #endif
48
49 /*
50  * Types
51  */
52 struct part_match_s
53 {
54   char str[DATA_MAX_NAME_LEN];
55   regex_t regex;
56   _Bool is_regex;
57 };
58 typedef struct part_match_s part_match_t;
59
60 struct identifier_match_s
61 {
62   part_match_t host;
63   part_match_t plugin;
64   part_match_t plugin_instance;
65   part_match_t type;
66   part_match_t type_instance;
67
68   unsigned int group_by;
69 };
70 typedef struct identifier_match_s identifier_match_t;
71
72 struct lookup_s
73 {
74   c_avl_tree_t *by_type_tree;
75
76   lookup_class_callback_t cb_user_class;
77   lookup_obj_callback_t cb_user_obj;
78   lookup_free_class_callback_t cb_free_class;
79   lookup_free_obj_callback_t cb_free_obj;
80 };
81
82 struct user_obj_s;
83 typedef struct user_obj_s user_obj_t;
84 struct user_obj_s
85 {
86   void *user_obj;
87   identifier_t ident;
88
89   user_obj_t *next;
90 };
91
92 struct user_class_s
93 {
94   pthread_mutex_t lock;
95   void *user_class;
96   identifier_match_t match;
97   user_obj_t *user_obj_list; /* list of user_obj */
98 };
99 typedef struct user_class_s user_class_t;
100
101 struct user_class_list_s;
102 typedef struct user_class_list_s user_class_list_t;
103 struct user_class_list_s
104 {
105   user_class_t entry;
106   user_class_list_t *next;
107 };
108
109 struct by_type_entry_s
110 {
111   c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */
112   user_class_list_t *wildcard_plugin_list;
113 };
114 typedef struct by_type_entry_s by_type_entry_t;
115
116 /*
117  * Private functions
118  */
119 static _Bool lu_part_matches (part_match_t const *match, /* {{{ */
120     char const *str)
121 {
122   if (match->is_regex)
123   {
124     /* Short cut popular catch-all regex. */
125     if (strcmp (".*", match->str) == 0)
126       return (1);
127
128     int status = regexec (&match->regex, str,
129         /* nmatch = */ 0, /* pmatch = */ NULL,
130         /* flags = */ 0);
131     if (status == 0)
132       return (1);
133     else
134       return (0);
135   }
136   else if (strcmp (match->str, str) == 0)
137     return (1);
138   else
139     return (0);
140 } /* }}} _Bool lu_part_matches */
141
142 static int lu_copy_ident_to_match_part (part_match_t *match_part, /* {{{ */
143     char const *ident_part)
144 {
145   size_t len = strlen (ident_part);
146   int status;
147
148   if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/'))
149   {
150     sstrncpy (match_part->str, ident_part, sizeof (match_part->str));
151     match_part->is_regex = 0;
152     return (0);
153   }
154
155   /* Copy string without the leading slash. */
156   sstrncpy (match_part->str, ident_part + 1, sizeof (match_part->str));
157   assert (sizeof (match_part->str) > len);
158   /* strip trailing slash */
159   match_part->str[len - 2] = 0;
160
161   status = regcomp (&match_part->regex, match_part->str,
162       /* flags = */ REG_EXTENDED);
163   if (status != 0)
164   {
165     char errbuf[1024];
166     regerror (status, &match_part->regex, errbuf, sizeof (errbuf));
167     ERROR ("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s",
168         match_part->str, errbuf);
169     return (EINVAL);
170   }
171   match_part->is_regex = 1;
172
173   return (0);
174 } /* }}} int lu_copy_ident_to_match_part */
175
176 static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
177     identifier_t const *ident, unsigned int group_by)
178 {
179   memset (match, 0, sizeof (*match));
180
181   match->group_by = group_by;
182
183 #define COPY_FIELD(field) do { \
184   int status = lu_copy_ident_to_match_part (&match->field, ident->field); \
185   if (status != 0) \
186     return (status); \
187 } while (0)
188
189   COPY_FIELD (host);
190   COPY_FIELD (plugin);
191   COPY_FIELD (plugin_instance);
192   COPY_FIELD (type);
193   COPY_FIELD (type_instance);
194
195 #undef COPY_FIELD
196
197   return (0);
198 } /* }}} int lu_copy_ident_to_match */
199
200 /* user_class->lock must be held when calling this function */
201 static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
202     data_set_t const *ds, value_list_t const *vl,
203     user_class_t *user_class)
204 {
205   user_obj_t *user_obj;
206
207   user_obj = calloc (1, sizeof (*user_obj));
208   if (user_obj == NULL)
209   {
210     ERROR ("utils_vl_lookup: calloc failed.");
211     return (NULL);
212   }
213   user_obj->next = NULL;
214
215   user_obj->user_obj = obj->cb_user_class (ds, vl, user_class->user_class);
216   if (user_obj->user_obj == NULL)
217   {
218     sfree (user_obj);
219     WARNING("utils_vl_lookup: User-provided constructor failed.");
220     return (NULL);
221   }
222
223 #define COPY_FIELD(field, group_mask) do { \
224   if (user_class->match.field.is_regex \
225       && ((user_class->match.group_by & group_mask) == 0)) \
226     sstrncpy (user_obj->ident.field, "/.*/", sizeof (user_obj->ident.field)); \
227   else \
228     sstrncpy (user_obj->ident.field, vl->field, sizeof (user_obj->ident.field)); \
229 } while (0)
230
231   COPY_FIELD (host, LU_GROUP_BY_HOST);
232   COPY_FIELD (plugin, LU_GROUP_BY_PLUGIN);
233   COPY_FIELD (plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE);
234   COPY_FIELD (type, 0);
235   COPY_FIELD (type_instance, LU_GROUP_BY_TYPE_INSTANCE);
236
237 #undef COPY_FIELD
238
239   if (user_class->user_obj_list == NULL)
240   {
241     user_class->user_obj_list = user_obj;
242   }
243   else
244   {
245     user_obj_t *last = user_class->user_obj_list;
246     while (last->next != NULL)
247       last = last->next;
248     last->next = user_obj;
249   }
250
251   return (user_obj);
252 } /* }}} void *lu_create_user_obj */
253
254 /* user_class->lock must be held when calling this function */
255 static user_obj_t *lu_find_user_obj (user_class_t *user_class, /* {{{ */
256     value_list_t const *vl)
257 {
258   user_obj_t *ptr;
259
260   for (ptr = user_class->user_obj_list;
261       ptr != NULL;
262       ptr = ptr->next)
263   {
264     if (user_class->match.host.is_regex
265         && (user_class->match.group_by & LU_GROUP_BY_HOST)
266         && (strcmp (vl->host, ptr->ident.host) != 0))
267       continue;
268     if (user_class->match.plugin.is_regex
269         && (user_class->match.group_by & LU_GROUP_BY_PLUGIN)
270         && (strcmp (vl->plugin, ptr->ident.plugin) != 0))
271       continue;
272     if (user_class->match.plugin_instance.is_regex
273         && (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE)
274         && (strcmp (vl->plugin_instance, ptr->ident.plugin_instance) != 0))
275       continue;
276     if (user_class->match.type_instance.is_regex
277         && (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE)
278         && (strcmp (vl->type_instance, ptr->ident.type_instance) != 0))
279       continue;
280
281     return (ptr);
282   }
283
284   return (NULL);
285 } /* }}} user_obj_t *lu_find_user_obj */
286
287 static int lu_handle_user_class (lookup_t *obj, /* {{{ */
288     data_set_t const *ds, value_list_t const *vl,
289     user_class_t *user_class)
290 {
291   user_obj_t *user_obj;
292   int status;
293
294   assert (strcmp (vl->type, user_class->match.type.str) == 0);
295   assert (user_class->match.plugin.is_regex
296       || (strcmp (vl->plugin, user_class->match.plugin.str)) == 0);
297
298   if (!lu_part_matches (&user_class->match.type_instance, vl->type_instance)
299       || !lu_part_matches (&user_class->match.plugin_instance, vl->plugin_instance)
300       || !lu_part_matches (&user_class->match.plugin, vl->plugin)
301       || !lu_part_matches (&user_class->match.host, vl->host))
302     return (1);
303
304   pthread_mutex_lock (&user_class->lock);
305   user_obj = lu_find_user_obj (user_class, vl);
306   if (user_obj == NULL)
307   {
308     /* call lookup_class_callback_t() and insert into the list of user objects. */
309     user_obj = lu_create_user_obj (obj, ds, vl, user_class);
310     if (user_obj == NULL) {
311       pthread_mutex_unlock (&user_class->lock);
312       return (-1);
313     }
314   }
315   pthread_mutex_unlock (&user_class->lock);
316
317   status = obj->cb_user_obj (ds, vl,
318       user_class->user_class, user_obj->user_obj);
319   if (status != 0)
320   {
321     ERROR ("utils_vl_lookup: The user object callback failed with status %i.",
322         status);
323     /* Returning a negative value means: abort! */
324     if (status < 0)
325       return (status);
326     else
327       return (1);
328   }
329
330   return (0);
331 } /* }}} int lu_handle_user_class */
332
333 static int lu_handle_user_class_list (lookup_t *obj, /* {{{ */
334     data_set_t const *ds, value_list_t const *vl,
335     user_class_list_t *user_class_list)
336 {
337   user_class_list_t *ptr;
338   int retval = 0;
339
340   for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
341   {
342     int status;
343
344     status = lu_handle_user_class (obj, ds, vl, &ptr->entry);
345     if (status < 0)
346       return (status);
347     else if (status == 0)
348       retval++;
349   }
350
351   return (retval);
352 } /* }}} int lu_handle_user_class_list */
353
354 static by_type_entry_t *lu_search_by_type (lookup_t *obj, /* {{{ */
355     char const *type, _Bool allocate_if_missing)
356 {
357   by_type_entry_t *by_type;
358   char *type_copy;
359   int status;
360
361   status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
362   if (status == 0)
363     return (by_type);
364
365   if (!allocate_if_missing)
366     return (NULL);
367
368   type_copy = strdup (type);
369   if (type_copy == NULL)
370   {
371     ERROR ("utils_vl_lookup: strdup failed.");
372     return (NULL);
373   }
374
375   by_type = calloc (1, sizeof (*by_type));
376   if (by_type == NULL)
377   {
378     ERROR ("utils_vl_lookup: calloc failed.");
379     sfree (type_copy);
380     return (NULL);
381   }
382   by_type->wildcard_plugin_list = NULL;
383
384   by_type->by_plugin_tree = c_avl_create ((void *) strcmp);
385   if (by_type->by_plugin_tree == NULL)
386   {
387     ERROR ("utils_vl_lookup: c_avl_create failed.");
388     sfree (by_type);
389     sfree (type_copy);
390     return (NULL);
391   }
392
393   status = c_avl_insert (obj->by_type_tree,
394       /* key = */ type_copy, /* value = */ by_type);
395   assert (status <= 0); /* >0 => entry exists => race condition. */
396   if (status != 0)
397   {
398     ERROR ("utils_vl_lookup: c_avl_insert failed.");
399     c_avl_destroy (by_type->by_plugin_tree);
400     sfree (by_type);
401     sfree (type_copy);
402     return (NULL);
403   }
404
405   return (by_type);
406 } /* }}} by_type_entry_t *lu_search_by_type */
407
408 static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
409     user_class_list_t *user_class_list)
410 {
411   user_class_list_t *ptr = NULL;
412   identifier_match_t const *match = &user_class_list->entry.match;
413
414   /* Lookup user_class_list from the per-plugin structure. If this is the first
415    * user_class to be added, the block returns immediately. Otherwise they will
416    * set "ptr" to non-NULL. */
417   if (match->plugin.is_regex)
418   {
419     if (by_type->wildcard_plugin_list == NULL)
420     {
421       by_type->wildcard_plugin_list = user_class_list;
422       return (0);
423     }
424
425     ptr = by_type->wildcard_plugin_list;
426   } /* if (plugin is wildcard) */
427   else /* (plugin is not wildcard) */
428   {
429     int status;
430
431     status = c_avl_get (by_type->by_plugin_tree,
432         match->plugin.str, (void *) &ptr);
433
434     if (status != 0) /* plugin not yet in tree */
435     {
436       char *plugin_copy = strdup (match->plugin.str);
437
438       if (plugin_copy == NULL)
439       {
440         ERROR ("utils_vl_lookup: strdup failed.");
441         sfree (user_class_list);
442         return (ENOMEM);
443       }
444
445       status = c_avl_insert (by_type->by_plugin_tree,
446           plugin_copy, user_class_list);
447       if (status != 0)
448       {
449         ERROR ("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.",
450             plugin_copy, status);
451         sfree (plugin_copy);
452         sfree (user_class_list);
453         return (status);
454       }
455       else
456       {
457         return (0);
458       }
459     } /* if (plugin not yet in tree) */
460   } /* if (plugin is not wildcard) */
461
462   assert (ptr != NULL);
463
464   while (ptr->next != NULL)
465     ptr = ptr->next;
466   ptr->next = user_class_list;
467
468   return (0);
469 } /* }}} int lu_add_by_plugin */
470
471 static void lu_destroy_user_obj (lookup_t *obj, /* {{{ */
472     user_obj_t *user_obj)
473 {
474   while (user_obj != NULL)
475   {
476     user_obj_t *next = user_obj->next;
477
478     if (obj->cb_free_obj != NULL)
479       obj->cb_free_obj (user_obj->user_obj);
480     user_obj->user_obj = NULL;
481
482     sfree (user_obj);
483     user_obj = next;
484   }
485 } /* }}} void lu_destroy_user_obj */
486
487 static void lu_destroy_user_class_list (lookup_t *obj, /* {{{ */
488     user_class_list_t *user_class_list)
489 {
490   while (user_class_list != NULL)
491   {
492     user_class_list_t *next = user_class_list->next;
493
494     if (obj->cb_free_class != NULL)
495       obj->cb_free_class (user_class_list->entry.user_class);
496     user_class_list->entry.user_class = NULL;
497
498     lu_destroy_user_obj (obj, user_class_list->entry.user_obj_list);
499     user_class_list->entry.user_obj_list = NULL;
500     pthread_mutex_destroy (&user_class_list->entry.lock);
501
502     sfree (user_class_list);
503     user_class_list = next;
504   }
505 } /* }}} void lu_destroy_user_class_list */
506
507 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
508     by_type_entry_t *by_type)
509 {
510
511   while (42)
512   {
513     char *plugin = NULL;
514     user_class_list_t *user_class_list = NULL;
515     int status;
516
517     status = c_avl_pick (by_type->by_plugin_tree,
518         (void *) &plugin, (void *) &user_class_list);
519     if (status != 0)
520       break;
521
522     DEBUG ("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".",
523         plugin);
524     sfree (plugin);
525     lu_destroy_user_class_list (obj, user_class_list);
526   }
527
528   c_avl_destroy (by_type->by_plugin_tree);
529   by_type->by_plugin_tree = NULL;
530
531   lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
532   by_type->wildcard_plugin_list = NULL;
533
534   sfree (by_type);
535 } /* }}} int lu_destroy_by_type */
536
537 /*
538  * Public functions
539  */
540 lookup_t *lookup_create (lookup_class_callback_t cb_user_class, /* {{{ */
541     lookup_obj_callback_t cb_user_obj,
542     lookup_free_class_callback_t cb_free_class,
543     lookup_free_obj_callback_t cb_free_obj)
544 {
545   lookup_t *obj = calloc (1, sizeof (*obj));
546   if (obj == NULL)
547   {
548     ERROR ("utils_vl_lookup: calloc failed.");
549     return (NULL);
550   }
551
552   obj->by_type_tree = c_avl_create ((void *) strcmp);
553   if (obj->by_type_tree == NULL)
554   {
555     ERROR ("utils_vl_lookup: c_avl_create failed.");
556     sfree (obj);
557     return (NULL);
558   }
559
560   obj->cb_user_class = cb_user_class;
561   obj->cb_user_obj = cb_user_obj;
562   obj->cb_free_class = cb_free_class;
563   obj->cb_free_obj = cb_free_obj;
564
565   return (obj);
566 } /* }}} lookup_t *lookup_create */
567
568 void lookup_destroy (lookup_t *obj) /* {{{ */
569 {
570   int status;
571
572   if (obj == NULL)
573     return;
574
575   while (42)
576   {
577     char *type = NULL;
578     by_type_entry_t *by_type = NULL;
579
580     status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
581     if (status != 0)
582       break;
583
584     DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
585     sfree (type);
586     lu_destroy_by_type (obj, by_type);
587   }
588
589   c_avl_destroy (obj->by_type_tree);
590   obj->by_type_tree = NULL;
591
592   sfree (obj);
593 } /* }}} void lookup_destroy */
594
595 int lookup_add (lookup_t *obj, /* {{{ */
596     identifier_t const *ident, unsigned int group_by, void *user_class)
597 {
598   by_type_entry_t *by_type = NULL;
599   user_class_list_t *user_class_obj;
600
601   by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
602   if (by_type == NULL)
603     return (-1);
604
605   user_class_obj = calloc (1, sizeof (*user_class_obj));
606   if (user_class_obj == NULL)
607   {
608     ERROR ("utils_vl_lookup: calloc failed.");
609     return (ENOMEM);
610   }
611   pthread_mutex_init (&user_class_obj->entry.lock, /* attr = */ NULL);
612   user_class_obj->entry.user_class = user_class;
613   lu_copy_ident_to_match (&user_class_obj->entry.match, ident, group_by);
614   user_class_obj->entry.user_obj_list = NULL;
615   user_class_obj->next = NULL;
616
617   return (lu_add_by_plugin (by_type, user_class_obj));
618 } /* }}} int lookup_add */
619
620 /* returns the number of successful calls to the callback function */
621 int lookup_search (lookup_t *obj, /* {{{ */
622     data_set_t const *ds, value_list_t const *vl)
623 {
624   by_type_entry_t *by_type = NULL;
625   user_class_list_t *user_class_list = NULL;
626   int retval = 0;
627   int status;
628
629   if ((obj == NULL) || (ds == NULL) || (vl == NULL))
630     return (-EINVAL);
631
632   by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
633   if (by_type == NULL)
634     return (0);
635
636   status = c_avl_get (by_type->by_plugin_tree,
637       vl->plugin, (void *) &user_class_list);
638   if (status == 0)
639   {
640     status = lu_handle_user_class_list (obj, ds, vl, user_class_list);
641     if (status < 0)
642       return (status);
643     retval += status;
644   }
645
646   if (by_type->wildcard_plugin_list != NULL)
647   {
648     status = lu_handle_user_class_list (obj, ds, vl,
649         by_type->wildcard_plugin_list);
650     if (status < 0)
651       return (status);
652     retval += status;
653   }
654
655   return (retval);
656 } /* }}} lookup_search */