c0e99f5f747fae4ef02de765e02c0ac67bff553a
[collectd.git] / src / perl.c
1 /**
2  * collectd - src/perl.c
3  * Copyright (C) 2007  Sebastian Harl
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  *   Sebastian Harl <sh at tokkee.org>
20  **/
21
22 /*
23  * This plugin embeds a Perl interpreter into collectd and provides an
24  * interface for collectd plugins written in perl.
25  */
26
27 /* do not automatically get the thread specific perl interpreter */
28 #define PERL_NO_GET_CONTEXT
29
30 #include "collectd.h"
31
32 #include "configfile.h"
33
34 #include <EXTERN.h>
35 #include <perl.h>
36
37 #include <XSUB.h>
38
39 /* Some versions of Perl define their own version of DEBUG... :-/ */
40 #ifdef DEBUG
41 # undef DEBUG
42 #endif /* DEBUG */
43
44 /* ... while we want the definition found in plugin.h. */
45 #include "plugin.h"
46 #include "common.h"
47
48 #include <pthread.h>
49
50 #if !defined(USE_ITHREADS)
51 # error "Perl does not support ithreads!"
52 #endif /* !defined(USE_ITHREADS) */
53
54 /* clear the Perl sub's stack frame
55  * (this should only be used inside an XSUB) */
56 #define CLEAR_STACK_FRAME PL_stack_sp = PL_stack_base + *PL_markstack_ptr
57
58 #define PLUGIN_INIT     0
59 #define PLUGIN_READ     1
60 #define PLUGIN_WRITE    2
61 #define PLUGIN_SHUTDOWN 3
62 #define PLUGIN_LOG      4
63
64 #define PLUGIN_TYPES    5
65
66 #define PLUGIN_DATASET  255
67
68 #define log_debug(...) DEBUG ("perl: " __VA_ARGS__)
69 #define log_info(...) INFO ("perl: " __VA_ARGS__)
70 #define log_warn(...) WARNING ("perl: " __VA_ARGS__)
71 #define log_err(...) ERROR ("perl: " __VA_ARGS__)
72
73 /* this is defined in DynaLoader.a */
74 void boot_DynaLoader (PerlInterpreter *, CV *);
75
76 static XS (Collectd_plugin_register_ds);
77 static XS (Collectd_plugin_unregister_ds);
78 static XS (Collectd_plugin_dispatch_values);
79 static XS (Collectd_plugin_log);
80 static XS (Collectd_call_by_name);
81
82 /*
83  * private data types
84  */
85
86 typedef struct c_ithread_s {
87         /* the thread's Perl interpreter */
88         PerlInterpreter *interp;
89
90         /* double linked list of threads */
91         struct c_ithread_s *prev;
92         struct c_ithread_s *next;
93 } c_ithread_t;
94
95 typedef struct {
96         c_ithread_t *head;
97         c_ithread_t *tail;
98
99 #if COLLECT_DEBUG
100         /* some usage stats */
101         int number_of_threads;
102 #endif /* COLLECT_DEBUG */
103
104         pthread_mutex_t mutex;
105 } c_ithread_list_t;
106
107 /*
108  * private variables
109  */
110
111 /* if perl_threads != NULL perl_threads->head must
112  * point to the "base" thread */
113 static c_ithread_list_t *perl_threads = NULL;
114
115 /* the key used to store each pthread's ithread */
116 static pthread_key_t perl_thr_key;
117
118 static int    perl_argc = 0;
119 static char **perl_argv = NULL;
120
121 static char base_name[DATA_MAX_NAME_LEN] = "";
122
123 static struct {
124         char name[64];
125         XS ((*f));
126 } api[] =
127 {
128         { "Collectd::plugin_register_data_set",   Collectd_plugin_register_ds },
129         { "Collectd::plugin_unregister_data_set", Collectd_plugin_unregister_ds },
130         { "Collectd::plugin_dispatch_values",     Collectd_plugin_dispatch_values },
131         { "Collectd::plugin_log",                 Collectd_plugin_log },
132         { "Collectd::call_by_name",               Collectd_call_by_name },
133         { "", NULL }
134 };
135
136 struct {
137         char name[64];
138         int  value;
139 } constants[] =
140 {
141         { "Collectd::TYPE_INIT",       PLUGIN_INIT },
142         { "Collectd::TYPE_READ",       PLUGIN_READ },
143         { "Collectd::TYPE_WRITE",      PLUGIN_WRITE },
144         { "Collectd::TYPE_SHUTDOWN",   PLUGIN_SHUTDOWN },
145         { "Collectd::TYPE_LOG",        PLUGIN_LOG },
146         { "Collectd::TYPE_DATASET",    PLUGIN_DATASET },
147         { "Collectd::DS_TYPE_COUNTER", DS_TYPE_COUNTER },
148         { "Collectd::DS_TYPE_GAUGE",   DS_TYPE_GAUGE },
149         { "Collectd::LOG_ERR",         LOG_ERR },
150         { "Collectd::LOG_WARNING",     LOG_WARNING },
151         { "Collectd::LOG_NOTICE",      LOG_NOTICE },
152         { "Collectd::LOG_INFO",        LOG_INFO },
153         { "Collectd::LOG_DEBUG",       LOG_DEBUG },
154         { "", 0 }
155 };
156
157 struct {
158         char  name[64];
159         char *var;
160 } g_strings[] =
161 {
162         { "Collectd::hostname_g", hostname_g },
163         { "", NULL }
164 };
165
166 struct {
167         char  name[64];
168         int  *var;
169 } g_integers[] =
170 {
171         { "Collectd::interval_g", &interval_g },
172         { "", NULL }
173 };
174
175 /*
176  * Helper functions for data type conversion.
177  */
178
179 /*
180  * data source:
181  * [
182  *   {
183  *     name => $ds_name,
184  *     type => $ds_type,
185  *     min  => $ds_min,
186  *     max  => $ds_max
187  *   },
188  *   ...
189  * ]
190  */
191 static int hv2data_source (pTHX_ HV *hash, data_source_t *ds)
192 {
193         SV **tmp = NULL;
194
195         if ((NULL == hash) || (NULL == ds))
196                 return -1;
197
198         if (NULL != (tmp = hv_fetch (hash, "name", 4, 0))) {
199                 strncpy (ds->name, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
200                 ds->name[DATA_MAX_NAME_LEN - 1] = '\0';
201         }
202         else {
203                 log_err ("hv2data_source: No DS name given.");
204                 return -1;
205         }
206
207         if (NULL != (tmp = hv_fetch (hash, "type", 4, 0))) {
208                 ds->type = SvIV (*tmp);
209
210                 if ((DS_TYPE_COUNTER != ds->type) && (DS_TYPE_GAUGE != ds->type)) {
211                         log_err ("hv2data_source: Invalid DS type.");
212                         return -1;
213                 }
214         }
215         else {
216                 ds->type = DS_TYPE_COUNTER;
217         }
218
219         if (NULL != (tmp = hv_fetch (hash, "min", 3, 0)))
220                 ds->min = SvNV (*tmp);
221         else
222                 ds->min = NAN;
223
224         if (NULL != (tmp = hv_fetch (hash, "max", 3, 0)))
225                 ds->max = SvNV (*tmp);
226         else
227                 ds->max = NAN;
228         return 0;
229 } /* static data_source_t *hv2data_source (HV *) */
230
231 static int av2value (pTHX_ char *name, AV *array, value_t *value, int len)
232 {
233         const data_set_t *ds;
234
235         int i = 0;
236
237         if ((NULL == name) || (NULL == array) || (NULL == value))
238                 return -1;
239
240         if (av_len (array) < len - 1)
241                 len = av_len (array) + 1;
242
243         if (0 >= len)
244                 return -1;
245
246         ds = plugin_get_ds (name);
247         if (NULL == ds) {
248                 log_err ("av2value: Unknown dataset \"%s\"", name);
249                 return -1;
250         }
251
252         if (ds->ds_num < len) {
253                 log_warn ("av2value: Value length exceeds data set length.");
254                 len = ds->ds_num;
255         }
256
257         for (i = 0; i < len; ++i) {
258                 SV **tmp = av_fetch (array, i, 0);
259
260                 if (NULL != tmp) {
261                         if (DS_TYPE_COUNTER == ds->ds[i].type)
262                                 value[i].counter = SvIV (*tmp);
263                         else
264                                 value[i].gauge = SvNV (*tmp);
265                 }
266                 else {
267                         return -1;
268                 }
269         }
270         return len;
271 } /* static int av2value (char *, AV *, value_t *, int) */
272
273 static int data_set2av (pTHX_ data_set_t *ds, AV *array)
274 {
275         int i = 0;
276
277         if ((NULL == ds) || (NULL == array))
278                 return -1;
279
280         av_extend (array, ds->ds_num);
281
282         for (i = 0; i < ds->ds_num; ++i) {
283                 HV *source = newHV ();
284
285                 if (NULL == hv_store (source, "name", 4,
286                                 newSVpv (ds->ds[i].name, 0), 0))
287                         return -1;
288
289                 if (NULL == hv_store (source, "type", 4, newSViv (ds->ds[i].type), 0))
290                         return -1;
291
292                 if (! isnan (ds->ds[i].min))
293                         if (NULL == hv_store (source, "min", 3,
294                                         newSVnv (ds->ds[i].min), 0))
295                                 return -1;
296
297                 if (! isnan (ds->ds[i].max))
298                         if (NULL == hv_store (source, "max", 3,
299                                         newSVnv (ds->ds[i].max), 0))
300                                 return -1;
301
302                 if (NULL == av_store (array, i, newRV_noinc ((SV *)source)))
303                         return -1;
304         }
305         return 0;
306 } /* static int data_set2av (data_set_t *, AV *) */
307
308 static int value_list2hv (pTHX_ value_list_t *vl, data_set_t *ds, HV *hash)
309 {
310         AV *values = NULL;
311
312         int i   = 0;
313         int len = 0;
314
315         if ((NULL == vl) || (NULL == ds) || (NULL == hash))
316                 return -1;
317
318         len = vl->values_len;
319
320         if (ds->ds_num < len) {
321                 log_warn ("value2av: Value length exceeds data set length.");
322                 len = ds->ds_num;
323         }
324
325         values = newAV ();
326         av_extend (values, len - 1);
327
328         for (i = 0; i < len; ++i) {
329                 SV *val = NULL;
330
331                 if (DS_TYPE_COUNTER == ds->ds[i].type)
332                         val = newSViv (vl->values[i].counter);
333                 else
334                         val = newSVnv (vl->values[i].gauge);
335
336                 if (NULL == av_store (values, i, val)) {
337                         av_undef (values);
338                         return -1;
339                 }
340         }
341
342         if (NULL == hv_store (hash, "values", 6, newRV_noinc ((SV *)values), 0))
343                 return -1;
344
345         if (0 != vl->time)
346                 if (NULL == hv_store (hash, "time", 4, newSViv (vl->time), 0))
347                         return -1;
348
349         if ('\0' != vl->host[0])
350                 if (NULL == hv_store (hash, "host", 4, newSVpv (vl->host, 0), 0))
351                         return -1;
352
353         if ('\0' != vl->plugin[0])
354                 if (NULL == hv_store (hash, "plugin", 6, newSVpv (vl->plugin, 0), 0))
355                         return -1;
356
357         if ('\0' != vl->plugin_instance[0])
358                 if (NULL == hv_store (hash, "plugin_instance", 15,
359                                 newSVpv (vl->plugin_instance, 0), 0))
360                         return -1;
361
362         if ('\0' != vl->type_instance[0])
363                 if (NULL == hv_store (hash, "type_instance", 13,
364                                 newSVpv (vl->type_instance, 0), 0))
365                         return -1;
366         return 0;
367 } /* static int value2av (value_list_t *, data_set_t *, HV *) */
368
369 /*
370  * Internal functions.
371  */
372
373 static char *get_module_name (char *buf, size_t buf_len, const char *module) {
374         int status = 0;
375         if (base_name[0] == '\0')
376                 status = snprintf (buf, buf_len, "%s", module);
377         else
378                 status = snprintf (buf, buf_len, "%s::%s", base_name, module);
379         if ((status < 0) || (status >= buf_len))
380                 return (NULL);
381         buf[buf_len - 1] = '\0';
382         return (buf);
383 } /* char *get_module_name */
384
385 /*
386  * Add a plugin's data set definition.
387  */
388 static int pplugin_register_data_set (pTHX_ char *name, AV *dataset)
389 {
390         int len = -1;
391         int i   = 0;
392
393         data_source_t *ds  = NULL;
394         data_set_t    *set = NULL;
395
396         if ((NULL == name) || (NULL == dataset))
397                 return -1;
398
399         len = av_len (dataset);
400
401         if (-1 == len)
402                 return -1;
403
404         ds  = (data_source_t *)smalloc ((len + 1) * sizeof (data_source_t));
405         set = (data_set_t *)smalloc (sizeof (data_set_t));
406
407         for (i = 0; i <= len; ++i) {
408                 SV **elem = av_fetch (dataset, i, 0);
409
410                 if (NULL == elem)
411                         return -1;
412
413                 if (! (SvROK (*elem) && (SVt_PVHV == SvTYPE (SvRV (*elem))))) {
414                         log_err ("pplugin_register_data_set: Invalid data source.");
415                         return -1;
416                 }
417
418                 if (-1 == hv2data_source (aTHX_ (HV *)SvRV (*elem), &ds[i]))
419                         return -1;
420
421                 log_debug ("pplugin_register_data_set: "
422                                 "DS.name = \"%s\", DS.type = %i, DS.min = %f, DS.max = %f",
423                                 ds[i].name, ds[i].type, ds[i].min, ds[i].max);
424         }
425
426         strncpy (set->type, name, DATA_MAX_NAME_LEN);
427         set->type[DATA_MAX_NAME_LEN - 1] = '\0';
428
429         set->ds_num = len + 1;
430         set->ds = ds;
431         return plugin_register_data_set (set);
432 } /* static int pplugin_register_data_set (char *, SV *) */
433
434 /*
435  * Remove a plugin's data set definition.
436  */
437 static int pplugin_unregister_data_set (char *name)
438 {
439         if (NULL == name)
440                 return 0;
441         return plugin_unregister_data_set (name);
442 } /* static int pplugin_unregister_data_set (char *) */
443
444 /*
445  * Submit the values to the write functions.
446  *
447  * value list:
448  * {
449  *   values => [ @values ],
450  *   time   => $time,
451  *   host   => $host,
452  *   plugin => $plugin,
453  *   plugin_instance => $pinstance,
454  *   type_instance   => $tinstance,
455  * }
456  */
457 static int pplugin_dispatch_values (pTHX_ char *name, HV *values)
458 {
459         value_list_t list = VALUE_LIST_INIT;
460         value_t      *val = NULL;
461
462         SV **tmp = NULL;
463
464         int ret = 0;
465
466         if ((NULL == name) || (NULL == values))
467                 return -1;
468
469         if ((NULL == (tmp = hv_fetch (values, "values", 6, 0)))
470                         || (! (SvROK (*tmp) && (SVt_PVAV == SvTYPE (SvRV (*tmp)))))) {
471                 log_err ("pplugin_dispatch_values: No valid values given.");
472                 return -1;
473         }
474
475         {
476                 AV  *array = (AV *)SvRV (*tmp);
477                 int len    = av_len (array) + 1;
478
479                 if (len <= 0)
480                         return -1;
481
482                 val = (value_t *)smalloc (len * sizeof (value_t));
483
484                 list.values_len = av2value (aTHX_ name, (AV *)SvRV (*tmp), val, len);
485                 list.values = val;
486
487                 if (-1 == list.values_len) {
488                         sfree (val);
489                         return -1;
490                 }
491         }
492
493         if (NULL != (tmp = hv_fetch (values, "time", 4, 0))) {
494                 list.time = (time_t)SvIV (*tmp);
495         }
496         else {
497                 list.time = time (NULL);
498         }
499
500         if (NULL != (tmp = hv_fetch (values, "host", 4, 0))) {
501                 strncpy (list.host, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
502                 list.host[DATA_MAX_NAME_LEN - 1] = '\0';
503         }
504         else {
505                 strcpy (list.host, hostname_g);
506         }
507
508         if (NULL != (tmp = hv_fetch (values, "plugin", 6, 0))) {
509                 strncpy (list.plugin, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
510                 list.plugin[DATA_MAX_NAME_LEN - 1] = '\0';
511         }
512
513         if (NULL != (tmp = hv_fetch (values,
514                         "plugin_instance", 15, 0))) {
515                 strncpy (list.plugin_instance, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
516                 list.plugin_instance[DATA_MAX_NAME_LEN - 1] = '\0';
517         }
518
519         if (NULL != (tmp = hv_fetch (values, "type_instance", 13, 0))) {
520                 strncpy (list.type_instance, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
521                 list.type_instance[DATA_MAX_NAME_LEN - 1] = '\0';
522         }
523
524         ret = plugin_dispatch_values (name, &list);
525
526         sfree (val);
527         return ret;
528 } /* static int pplugin_dispatch_values (char *, HV *) */
529
530 /*
531  * Call all working functions of the given type.
532  */
533 static int pplugin_call_all (pTHX_ int type, ...)
534 {
535         int retvals = 0;
536
537         va_list ap;
538         int ret = 0;
539
540         dSP;
541
542         if ((type < 0) || (type >= PLUGIN_TYPES))
543                 return -1;
544
545         va_start (ap, type);
546
547         ENTER;
548         SAVETMPS;
549
550         PUSHMARK (SP);
551
552         XPUSHs (sv_2mortal (newSViv ((IV)type)));
553
554         if (PLUGIN_WRITE == type) {
555                 /*
556                  * $_[0] = $plugin_type;
557                  *
558                  * $_[1] =
559                  * [
560                  *   {
561                  *     name => $ds_name,
562                  *     type => $ds_type,
563                  *     min  => $ds_min,
564                  *     max  => $ds_max
565                  *   },
566                  *   ...
567                  * ];
568                  *
569                  * $_[2] =
570                  * {
571                  *   values => [ $v1, ... ],
572                  *   time   => $time,
573                  *   host   => $hostname,
574                  *   plugin => $plugin,
575                  *   plugin_instance => $instance,
576                  *   type_instance   => $type_instance
577                  * };
578                  */
579                 data_set_t   *ds;
580                 value_list_t *vl;
581
582                 AV *pds = newAV ();
583                 HV *pvl = newHV ();
584
585                 ds = va_arg (ap, data_set_t *);
586                 vl = va_arg (ap, value_list_t *);
587
588                 if (-1 == data_set2av (aTHX_ ds, pds))
589                         return -1;
590
591                 if (-1 == value_list2hv (aTHX_ vl, ds, pvl))
592                         return -1;
593
594                 XPUSHs (sv_2mortal (newSVpv (ds->type, 0)));
595                 XPUSHs (sv_2mortal (newRV_noinc ((SV *)pds)));
596                 XPUSHs (sv_2mortal (newRV_noinc ((SV *)pvl)));
597         }
598         else if (PLUGIN_LOG == type) {
599                 /*
600                  * $_[0] = $level;
601                  *
602                  * $_[1] = $message;
603                  */
604                 XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
605                 XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
606         }
607
608         PUTBACK;
609
610         retvals = call_pv ("Collectd::plugin_call_all", G_SCALAR);
611
612         SPAGAIN;
613         if (0 < retvals) {
614                 SV *tmp = POPs;
615                 if (! SvTRUE (tmp))
616                         ret = -1;
617         }
618
619         PUTBACK;
620         FREETMPS;
621         LEAVE;
622
623         va_end (ap);
624         return ret;
625 } /* static int pplugin_call_all (int, ...) */
626
627 /*
628  * Exported Perl API.
629  */
630
631 /*
632  * Collectd::plugin_register_data_set (type, dataset).
633  *
634  * type:
635  *   type of the dataset
636  *
637  * dataset:
638  *   dataset to be registered
639  */
640 static XS (Collectd_plugin_register_ds)
641 {
642         SV  *data = NULL;
643         int ret   = 0;
644
645         dXSARGS;
646
647         if (2 != items) {
648                 log_err ("Usage: Collectd::plugin_register_data_set(type, dataset)");
649                 XSRETURN_EMPTY;
650         }
651
652         log_debug ("Collectd::plugin_register_data_set: "
653                         "type = \"%s\", dataset = \"%s\"",
654                         SvPV_nolen (ST (0)), SvPV_nolen (ST (1)));
655
656         data = ST (1);
657
658         if (SvROK (data) && (SVt_PVAV == SvTYPE (SvRV (data)))) {
659                 ret = pplugin_register_data_set (aTHX_ SvPV_nolen (ST (0)),
660                                 (AV *)SvRV (data));
661         }
662         else {
663                 log_err ("Collectd::plugin_register_data_set: Invalid data.");
664                 XSRETURN_EMPTY;
665         }
666
667         if (0 == ret)
668                 XSRETURN_YES;
669         else
670                 XSRETURN_EMPTY;
671 } /* static XS (Collectd_plugin_register_ds) */
672
673 /*
674  * Collectd::plugin_unregister_data_set (type).
675  *
676  * type:
677  *   type of the dataset
678  */
679 static XS (Collectd_plugin_unregister_ds)
680 {
681         dXSARGS;
682
683         if (1 != items) {
684                 log_err ("Usage: Collectd::plugin_unregister_data_set(type)");
685                 XSRETURN_EMPTY;
686         }
687
688         log_debug ("Collectd::plugin_unregister_data_set: type = \"%s\"",
689                         SvPV_nolen (ST (0)));
690
691         if (0 == pplugin_unregister_data_set (SvPV_nolen (ST (1))))
692                 XSRETURN_YES;
693         else
694                 XSRETURN_EMPTY;
695 } /* static XS (Collectd_plugin_register_ds) */
696
697 /*
698  * Collectd::plugin_dispatch_values (name, values).
699  *
700  * name:
701  *   name of the plugin
702  *
703  * values:
704  *   value list to submit
705  */
706 static XS (Collectd_plugin_dispatch_values)
707 {
708         SV *values = NULL;
709
710         int ret = 0;
711
712         dXSARGS;
713
714         if (2 != items) {
715                 log_err ("Usage: Collectd::plugin_dispatch_values(name, values)");
716                 XSRETURN_EMPTY;
717         }
718
719         log_debug ("Collectd::plugin_dispatch_values: "
720                         "name = \"%s\", values=\"%s\"",
721                         SvPV_nolen (ST (0)), SvPV_nolen (ST (1)));
722
723         values = ST (1);
724
725         if (! (SvROK (values) && (SVt_PVHV == SvTYPE (SvRV (values))))) {
726                 log_err ("Collectd::plugin_dispatch_values: Invalid values.");
727                 XSRETURN_EMPTY;
728         }
729
730         if ((NULL == ST (0)) || (NULL == values))
731                 XSRETURN_EMPTY;
732
733         ret = pplugin_dispatch_values (aTHX_ SvPV_nolen (ST (0)),
734                         (HV *)SvRV (values));
735
736         if (0 == ret)
737                 XSRETURN_YES;
738         else
739                 XSRETURN_EMPTY;
740 } /* static XS (Collectd_plugin_dispatch_values) */
741
742 /*
743  * Collectd::plugin_log (level, message).
744  *
745  * level:
746  *   log level (LOG_DEBUG, ... LOG_ERR)
747  *
748  * message:
749  *   log message
750  */
751 static XS (Collectd_plugin_log)
752 {
753         dXSARGS;
754
755         if (2 != items) {
756                 log_err ("Usage: Collectd::plugin_log(level, message)");
757                 XSRETURN_EMPTY;
758         }
759
760         plugin_log (SvIV (ST (0)), SvPV_nolen (ST (1)));
761         XSRETURN_YES;
762 } /* static XS (Collectd_plugin_log) */
763
764 /*
765  * Collectd::call_by_name (...).
766  *
767  * Call a Perl sub identified by its name passed through $Collectd::cb_name.
768  */
769 static XS (Collectd_call_by_name)
770 {
771         SV   *tmp  = NULL;
772         char *name = NULL;
773
774         if (NULL == (tmp = get_sv ("Collectd::cb_name", 0))) {
775                 sv_setpv (get_sv ("@", 1), "cb_name has not been set");
776                 CLEAR_STACK_FRAME;
777                 return;
778         }
779
780         name = SvPV_nolen (tmp);
781
782         if (NULL == get_cv (name, 0)) {
783                 sv_setpvf (get_sv ("@", 1), "unknown callback \"%s\"", name);
784                 CLEAR_STACK_FRAME;
785                 return;
786         }
787
788         /* simply pass on the subroutine call without touching the stack,
789          * thus leaving any arguments and return values in place */
790         call_pv (name, 0);
791 } /* static XS (Collectd_call_by_name) */
792
793 /*
794  * collectd's perl interpreter based thread implementation.
795  *
796  * This has been inspired by Perl's ithreads introduced in version 5.6.0.
797  */
798
799 /* must be called with perl_threads->mutex locked */
800 static void c_ithread_destroy (c_ithread_t *ithread)
801 {
802         dTHXa (ithread->interp);
803
804         assert (NULL != perl_threads);
805
806         PERL_SET_CONTEXT (aTHX);
807         log_debug ("Shutting down Perl interpreter %p...", aTHX);
808
809 #if COLLECT_DEBUG
810         sv_report_used ();
811
812         --perl_threads->number_of_threads;
813 #endif /* COLLECT_DEBUG */
814
815         perl_destruct (aTHX);
816         perl_free (aTHX);
817
818         if (NULL == ithread->prev)
819                 perl_threads->head = ithread->next;
820         else
821                 ithread->prev->next = ithread->next;
822
823         if (NULL == ithread->next)
824                 perl_threads->tail = ithread->prev;
825         else
826                 ithread->next->prev = ithread->prev;
827
828         sfree (ithread);
829         return;
830 } /* static void c_ithread_destroy (c_ithread_t *) */
831
832 static void c_ithread_destructor (void *arg)
833 {
834         c_ithread_t *ithread = (c_ithread_t *)arg;
835         c_ithread_t *t = NULL;
836
837         if (NULL == perl_threads)
838                 return;
839
840         pthread_mutex_lock (&perl_threads->mutex);
841
842         for (t = perl_threads->head; NULL != t; t = t->next)
843                 if (t == ithread)
844                         break;
845
846         /* the ithread no longer exists */
847         if (NULL == t)
848                 return;
849
850         c_ithread_destroy (ithread);
851
852         pthread_mutex_unlock (&perl_threads->mutex);
853         return;
854 } /* static void c_ithread_destructor (void *) */
855
856 /* must be called with perl_threads->mutex locked */
857 static c_ithread_t *c_ithread_create (PerlInterpreter *base)
858 {
859         c_ithread_t *t = NULL;
860         dTHXa (NULL);
861
862         assert (NULL != perl_threads);
863
864         t = (c_ithread_t *)smalloc (sizeof (c_ithread_t));
865         memset (t, 0, sizeof (c_ithread_t));
866
867         t->interp = (NULL == base)
868                 ? NULL
869                 : perl_clone (base, CLONEf_KEEP_PTR_TABLE);
870
871         aTHX = t->interp;
872
873         if (NULL != base) {
874                 av_clear (PL_endav);
875                 av_undef (PL_endav);
876                 PL_endav = Nullav;
877         }
878
879 #if COLLECT_DEBUG
880         ++perl_threads->number_of_threads;
881 #endif /* COLLECT_DEBUG */
882
883         t->next = NULL;
884
885         if (NULL == perl_threads->tail) {
886                 perl_threads->head = t;
887                 t->prev = NULL;
888         }
889         else {
890                 perl_threads->tail->next = t;
891                 t->prev = perl_threads->tail;
892         }
893
894         perl_threads->tail = t;
895
896         pthread_setspecific (perl_thr_key, (const void *)t);
897         return t;
898 } /* static c_ithread_t *c_ithread_create (PerlInterpreter *) */
899
900 /*
901  * Interface to collectd.
902  */
903
904 static int perl_init (void)
905 {
906         dTHX;
907
908         if (NULL == perl_threads)
909                 return 0;
910
911         if (NULL == aTHX) {
912                 c_ithread_t *t = NULL;
913
914                 pthread_mutex_lock (&perl_threads->mutex);
915                 t = c_ithread_create (perl_threads->head->interp);
916                 pthread_mutex_unlock (&perl_threads->mutex);
917
918                 aTHX = t->interp;
919         }
920
921         log_debug ("perl_init: c_ithread: interp = %p (active threads: %i)",
922                         aTHX, perl_threads->number_of_threads);
923         return pplugin_call_all (aTHX_ PLUGIN_INIT);
924 } /* static int perl_init (void) */
925
926 static int perl_read (void)
927 {
928         dTHX;
929
930         if (NULL == perl_threads)
931                 return 0;
932
933         if (NULL == aTHX) {
934                 c_ithread_t *t = NULL;
935
936                 pthread_mutex_lock (&perl_threads->mutex);
937                 t = c_ithread_create (perl_threads->head->interp);
938                 pthread_mutex_unlock (&perl_threads->mutex);
939
940                 aTHX = t->interp;
941         }
942
943         log_debug ("perl_read: c_ithread: interp = %p (active threads: %i)",
944                         aTHX, perl_threads->number_of_threads);
945         return pplugin_call_all (aTHX_ PLUGIN_READ);
946 } /* static int perl_read (void) */
947
948 static int perl_write (const data_set_t *ds, const value_list_t *vl)
949 {
950         dTHX;
951
952         if (NULL == perl_threads)
953                 return 0;
954
955         if (NULL == aTHX) {
956                 c_ithread_t *t = NULL;
957
958                 pthread_mutex_lock (&perl_threads->mutex);
959                 t = c_ithread_create (perl_threads->head->interp);
960                 pthread_mutex_unlock (&perl_threads->mutex);
961
962                 aTHX = t->interp;
963         }
964
965         log_debug ("perl_write: c_ithread: interp = %p (active threads: %i)",
966                         aTHX, perl_threads->number_of_threads);
967         return pplugin_call_all (aTHX_ PLUGIN_WRITE, ds, vl);
968 } /* static int perl_write (const data_set_t *, const value_list_t *) */
969
970 static void perl_log (int level, const char *msg)
971 {
972         dTHX;
973
974         if (NULL == perl_threads)
975                 return;
976
977         if (NULL == aTHX) {
978                 c_ithread_t *t = NULL;
979
980                 pthread_mutex_lock (&perl_threads->mutex);
981                 t = c_ithread_create (perl_threads->head->interp);
982                 pthread_mutex_unlock (&perl_threads->mutex);
983
984                 aTHX = t->interp;
985         }
986
987         pplugin_call_all (aTHX_ PLUGIN_LOG, level, msg);
988         return;
989 } /* static void perl_log (int, const char *) */
990
991 static int perl_shutdown (void)
992 {
993         c_ithread_t *t = NULL;
994
995         int ret = 0;
996
997         dTHX;
998
999         plugin_unregister_complex_config ("perl");
1000
1001         if (NULL == perl_threads)
1002                 return 0;
1003
1004         if (NULL == aTHX) {
1005                 c_ithread_t *t = NULL;
1006
1007                 pthread_mutex_lock (&perl_threads->mutex);
1008                 t = c_ithread_create (perl_threads->head->interp);
1009                 pthread_mutex_unlock (&perl_threads->mutex);
1010
1011                 aTHX = t->interp;
1012         }
1013
1014         log_debug ("perl_shutdown: c_ithread: interp = %p (active threads: %i)",
1015                         aTHX, perl_threads->number_of_threads);
1016
1017         plugin_unregister_log ("perl");
1018         plugin_unregister_init ("perl");
1019         plugin_unregister_read ("perl");
1020         plugin_unregister_write ("perl");
1021
1022         ret = pplugin_call_all (aTHX_ PLUGIN_SHUTDOWN);
1023
1024         pthread_mutex_lock (&perl_threads->mutex);
1025         t = perl_threads->tail;
1026
1027         while (NULL != t) {
1028                 c_ithread_t *thr = t;
1029
1030                 /* the pointer has to be advanced before destroying
1031                  * the thread as this will free the memory */
1032                 t = t->prev;
1033
1034                 c_ithread_destroy (thr);
1035         }
1036
1037         pthread_mutex_unlock (&perl_threads->mutex);
1038         pthread_mutex_destroy (&perl_threads->mutex);
1039
1040         sfree (perl_threads);
1041
1042         pthread_key_delete (perl_thr_key);
1043
1044         PERL_SYS_TERM ();
1045
1046         plugin_unregister_shutdown ("perl");
1047         return ret;
1048 } /* static void perl_shutdown (void) */
1049
1050 /*
1051  * Access functions for global variables.
1052  *
1053  * These functions implement the "magic" used to access
1054  * the global variables from Perl.
1055  */
1056
1057 static int g_pv_get (pTHX_ SV *var, MAGIC *mg)
1058 {
1059         char *pv = mg->mg_ptr;
1060         sv_setpv (var, pv);
1061         return 0;
1062 } /* static int g_pv_get (pTHX_ SV *, MAGIC *) */
1063
1064 static int g_pv_set (pTHX_ SV *var, MAGIC *mg)
1065 {
1066         char *pv = mg->mg_ptr;
1067         strncpy (pv, SvPV_nolen (var), DATA_MAX_NAME_LEN);
1068         pv[DATA_MAX_NAME_LEN - 1] = '\0';
1069         return 0;
1070 } /* static int g_pv_set (pTHX_ SV *, MAGIC *) */
1071
1072 static int g_iv_get (pTHX_ SV *var, MAGIC *mg)
1073 {
1074         int *iv = (int *)mg->mg_ptr;
1075         sv_setiv (var, *iv);
1076         return 0;
1077 } /* static int g_iv_get (pTHX_ SV *, MAGIC *) */
1078
1079 static int g_iv_set (pTHX_ SV *var, MAGIC *mg)
1080 {
1081         int *iv = (int *)mg->mg_ptr;
1082         *iv = (int)SvIV (var);
1083         return 0;
1084 } /* static int g_iv_set (pTHX_ SV *, MAGIC *) */
1085
1086 static MGVTBL g_pv_vtbl = { g_pv_get, g_pv_set, NULL, NULL, NULL };
1087 static MGVTBL g_iv_vtbl = { g_iv_get, g_iv_set, NULL, NULL, NULL };
1088
1089 /* bootstrap the Collectd module */
1090 static void xs_init (pTHX)
1091 {
1092         HV   *stash = NULL;
1093         SV   *tmp   = NULL;
1094         char *file  = __FILE__;
1095
1096         int i = 0;
1097
1098         dXSUB_SYS;
1099
1100         /* enable usage of Perl modules using shared libraries */
1101         newXS ("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
1102
1103         /* register API */
1104         for (i = 0; NULL != api[i].f; ++i)
1105                 newXS (api[i].name, api[i].f, file);
1106
1107         stash = gv_stashpv ("Collectd", 1);
1108
1109         /* export "constants" */
1110         for (i = 0; '\0' != constants[i].name[0]; ++i)
1111                 newCONSTSUB (stash, constants[i].name, newSViv (constants[i].value));
1112
1113         /* export global variables
1114          * by adding "magic" to the SV's representing the globale variables
1115          * perl is able to automagically call the get/set function when
1116          * accessing any such variable (this is basically the same as using
1117          * tie() in Perl) */
1118         /* global strings */
1119         for (i = 0; '\0' != g_strings[i].name[0]; ++i) {
1120                 tmp = get_sv (g_strings[i].name, 1);
1121                 sv_magicext (tmp, NULL, PERL_MAGIC_ext, &g_pv_vtbl,
1122                                 g_strings[i].var, 0);
1123         }
1124
1125         /* global integers */
1126         for (i = 0; '\0' != g_integers[i].name[0]; ++i) {
1127                 tmp = get_sv (g_integers[i].name, 1);
1128                 sv_magicext (tmp, NULL, PERL_MAGIC_ext, &g_iv_vtbl,
1129                                 (char *)g_integers[i].var, 0);
1130         }
1131         return;
1132 } /* static void xs_init (pTHX) */
1133
1134 /* Initialize the global Perl interpreter. */
1135 static int init_pi (int argc, char **argv)
1136 {
1137         dTHXa (NULL);
1138
1139         if (NULL != perl_threads)
1140                 return 0;
1141
1142         log_info ("Initializing Perl interpreter...");
1143 #if COLLECT_DEBUG
1144         {
1145                 int i = 0;
1146
1147                 for (i = 0; i < argc; ++i)
1148                         log_debug ("argv[%i] = \"%s\"", i, argv[i]);
1149         }
1150 #endif /* COLLECT_DEBUG */
1151
1152         if (0 != pthread_key_create (&perl_thr_key, c_ithread_destructor)) {
1153                 log_err ("init_pi: pthread_key_create failed");
1154
1155                 /* this must not happen - cowardly giving up if it does */
1156                 exit (1);
1157         }
1158
1159         PERL_SYS_INIT3 (&argc, &argv, &environ);
1160
1161         perl_threads = (c_ithread_list_t *)smalloc (sizeof (c_ithread_list_t));
1162         memset (perl_threads, 0, sizeof (c_ithread_list_t));
1163
1164         pthread_mutex_init (&perl_threads->mutex, NULL);
1165         /* locking the mutex should not be necessary at this point
1166          * but let's just do it for the sake of completeness */
1167         pthread_mutex_lock (&perl_threads->mutex);
1168
1169         perl_threads->head = c_ithread_create (NULL);
1170         perl_threads->tail = perl_threads->head;
1171
1172         if (NULL == (perl_threads->head->interp = perl_alloc ())) {
1173                 log_err ("init_pi: Not enough memory.");
1174                 exit (3);
1175         }
1176
1177         aTHX = perl_threads->head->interp;
1178         pthread_mutex_unlock (&perl_threads->mutex);
1179
1180         perl_construct (aTHX);
1181
1182         PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
1183
1184         if (0 != perl_parse (aTHX_ xs_init, argc, argv, NULL)) {
1185                 log_err ("init_pi: Unable to bootstrap Collectd.");
1186                 exit (1);
1187         }
1188
1189         /* Set $0 to "collectd" because perl_parse() has to set it to "-e". */
1190         sv_setpv (get_sv ("0", 0), "collectd");
1191
1192         perl_run (aTHX);
1193
1194         plugin_register_log ("perl", perl_log);
1195         plugin_register_init ("perl", perl_init);
1196
1197         plugin_register_read ("perl", perl_read);
1198
1199         plugin_register_write ("perl", perl_write);
1200         plugin_register_shutdown ("perl", perl_shutdown);
1201         return 0;
1202 } /* static int init_pi (const char **, const int) */
1203
1204 /*
1205  * LoadPlugin "<Plugin>"
1206  */
1207 static int perl_config_loadplugin (pTHX_ oconfig_item_t *ci)
1208 {
1209         char module_name[DATA_MAX_NAME_LEN];
1210
1211         char *value = NULL;
1212
1213         if ((0 != ci->children_num) || (1 != ci->values_num)
1214                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
1215                 log_err ("LoadPlugin expects a single string argument.");
1216                 return 1;
1217         }
1218
1219         value = ci->values[0].value.string;
1220
1221         if (NULL == get_module_name (module_name, sizeof (module_name), value)) {
1222                 log_err ("Invalid module name %s", value);
1223                 return (1);
1224         }
1225
1226         init_pi (perl_argc, perl_argv);
1227         assert (NULL != perl_threads);
1228         assert (NULL != perl_threads->head);
1229
1230         aTHX = perl_threads->head->interp;
1231
1232         log_debug ("perl_config: loading perl plugin \"%s\"", value);
1233         load_module (PERL_LOADMOD_NOIMPORT,
1234                         newSVpv (module_name, strlen (module_name)), Nullsv);
1235         return 0;
1236 } /* static int perl_config_loadplugin (oconfig_item_it *) */
1237
1238 /*
1239  * BaseName "<Name>"
1240  */
1241 static int perl_config_basename (pTHX_ oconfig_item_t *ci)
1242 {
1243         char *value = NULL;
1244
1245         if ((0 != ci->children_num) || (1 != ci->values_num)
1246                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
1247                 log_err ("BaseName expects a single string argument.");
1248                 return 1;
1249         }
1250
1251         value = ci->values[0].value.string;
1252
1253         log_debug ("perl_config: Setting plugin basename to \"%s\"", value);
1254         strncpy (base_name, value, sizeof (base_name));
1255         base_name[sizeof (base_name) - 1] = '\0';
1256         return 0;
1257 } /* static int perl_config_basename (oconfig_item_it *) */
1258
1259 /*
1260  * EnableDebugger "<Package>"|""
1261  */
1262 static int perl_config_enabledebugger (pTHX_ oconfig_item_t *ci)
1263 {
1264         char *value = NULL;
1265
1266         if ((0 != ci->children_num) || (1 != ci->values_num)
1267                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
1268                 log_err ("EnableDebugger expects a single string argument.");
1269                 return 1;
1270         }
1271
1272         value = ci->values[0].value.string;
1273
1274         perl_argv = (char **)realloc (perl_argv,
1275                         (++perl_argc + 1) * sizeof (char *));
1276
1277         if (NULL == perl_argv) {
1278                 log_err ("perl_config: Not enough memory.");
1279                 exit (3);
1280         }
1281
1282         if ('\0' == value[0]) {
1283                 perl_argv[perl_argc - 1] = "-d";
1284         }
1285         else {
1286                 perl_argv[perl_argc - 1] = (char *)smalloc (strlen (value) + 4);
1287                 sstrncpy (perl_argv[perl_argc - 1], "-d:", 4);
1288                 sstrncpy (perl_argv[perl_argc - 1] + 3, value, strlen (value) + 1);
1289         }
1290
1291         perl_argv[perl_argc] = NULL;
1292         return 0;
1293 } /* static int perl_config_enabledebugger (oconfig_item_it *) */
1294
1295 /*
1296  * IncludeDir "<Dir>"
1297  */
1298 static int perl_config_includedir (pTHX_ oconfig_item_t *ci)
1299 {
1300         char *value = NULL;
1301
1302         if ((0 != ci->children_num) || (1 != ci->values_num)
1303                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
1304                 log_err ("IncludeDir expects a single string argument.");
1305                 return 1;
1306         }
1307
1308         value = ci->values[0].value.string;
1309
1310         if (NULL == aTHX) {
1311                 perl_argv = (char **)realloc (perl_argv,
1312                                 (++perl_argc + 1) * sizeof (char *));
1313
1314                 if (NULL == perl_argv) {
1315                         log_err ("perl_config: Not enough memory.");
1316                         exit (3);
1317                 }
1318
1319                 perl_argv[perl_argc - 1] = (char *)smalloc (strlen (value) + 3);
1320                 sstrncpy(perl_argv[perl_argc - 1], "-I", 3);
1321                 sstrncpy(perl_argv[perl_argc - 1] + 2, value, strlen (value) + 1);
1322
1323                 perl_argv[perl_argc] = NULL;
1324         }
1325         else {
1326                 /* prepend the directory to @INC */
1327                 av_unshift (GvAVn (PL_incgv), 1);
1328                 av_store (GvAVn (PL_incgv), 0, newSVpv (value, strlen (value)));
1329         }
1330         return 0;
1331 } /* static int perl_config_includedir (oconfig_item_it *) */
1332
1333 static int perl_config (oconfig_item_t *ci)
1334 {
1335         int i = 0;
1336
1337         dTHX;
1338
1339         /* dTHX does not get any valid values in case Perl
1340          * has not been initialized */
1341         if (NULL == perl_threads)
1342                 aTHX = NULL;
1343
1344         for (i = 0; i < ci->children_num; ++i) {
1345                 oconfig_item_t *c = ci->children + i;
1346
1347                 if (0 == strcasecmp (c->key, "LoadPlugin"))
1348                         perl_config_loadplugin (aTHX_ c);
1349                 else if (0 == strcasecmp (c->key, "BaseName"))
1350                         perl_config_basename (aTHX_ c);
1351                 else if (0 == strcasecmp (c->key, "EnableDebugger"))
1352                         perl_config_enabledebugger (aTHX_ c);
1353                 else if (0 == strcasecmp (c->key, "IncludeDir"))
1354                         perl_config_includedir (aTHX_ c);
1355                 else
1356                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
1357         }
1358         return 0;
1359 } /* static int perl_config (oconfig_item_t *) */
1360
1361 void module_register (void)
1362 {
1363         perl_argc = 4;
1364         perl_argv = (char **)smalloc ((perl_argc + 1) * sizeof (char *));
1365
1366         /* default options for the Perl interpreter */
1367         perl_argv[0] = "";
1368         perl_argv[1] = "-MCollectd";
1369         perl_argv[2] = "-e";
1370         perl_argv[3] = "1";
1371         perl_argv[4] = NULL;
1372
1373         plugin_register_complex_config ("perl", perl_config);
1374         return;
1375 } /* void module_register (void) */
1376
1377 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
1378