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