Collectd.pm: Improved error handling of failed callbacks.
[collectd.git] / bindings / perl / Collectd.pm
1 # collectd - Collectd.pm
2 # Copyright (C) 2007  Sebastian Harl
3 #
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License as published by the
6 # Free Software Foundation; only version 2 of the License is applicable.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
16 #
17 # Author:
18 #   Sebastian Harl <sh at tokkee.org>
19
20 package Collectd;
21
22 use strict;
23 use warnings;
24
25 use Config;
26
27 use threads;
28 use threads::shared;
29
30 BEGIN {
31         if (! $Config{'useithreads'}) {
32                 die "Perl does not support ithreads!";
33         }
34 }
35
36 require Exporter;
37
38 our @ISA = qw( Exporter );
39
40 our %EXPORT_TAGS = (
41         'plugin' => [ qw(
42                         plugin_register
43                         plugin_unregister
44                         plugin_dispatch_values
45                         plugin_log
46         ) ],
47         'types' => [ qw(
48                         TYPE_INIT
49                         TYPE_READ
50                         TYPE_WRITE
51                         TYPE_SHUTDOWN
52                         TYPE_LOG
53                         TYPE_DATASET
54         ) ],
55         'ds_types' => [ qw(
56                         DS_TYPE_COUNTER
57                         DS_TYPE_GAUGE
58         ) ],
59         'log' => [ qw(
60                         ERROR
61                         WARNING
62                         NOTICE
63                         INFO
64                         DEBUG
65                         LOG_ERR
66                         LOG_WARNING
67                         LOG_NOTICE
68                         LOG_INFO
69                         LOG_DEBUG
70         ) ],
71 );
72
73 {
74         my %seen;
75         push @{$EXPORT_TAGS{'all'}}, grep {! $seen{$_}++ } @{$EXPORT_TAGS{$_}}
76                 foreach keys %EXPORT_TAGS;
77 }
78
79 Exporter::export_ok_tags ('all');
80
81 my @plugins : shared = ();
82
83 my %types = (
84         TYPE_INIT,     "init",
85         TYPE_READ,     "read",
86         TYPE_WRITE,    "write",
87         TYPE_SHUTDOWN, "shutdown",
88         TYPE_LOG,      "log"
89 );
90
91 foreach my $type (keys %types) {
92         $plugins[$type] = &share ({});
93 }
94
95 sub _log {
96         my $caller = shift;
97         my $lvl    = shift;
98         my $msg    = shift;
99
100         if ("Collectd" eq $caller) {
101                 $msg = "perl: $msg";
102         }
103         return plugin_log ($lvl, $msg);
104 }
105
106 sub ERROR   { _log (scalar caller, LOG_ERR,     shift); }
107 sub WARNING { _log (scalar caller, LOG_WARNING, shift); }
108 sub NOTICE  { _log (scalar caller, LOG_NOTICE,  shift); }
109 sub INFO    { _log (scalar caller, LOG_INFO,    shift); }
110 sub DEBUG   { _log (scalar caller, LOG_DEBUG,   shift); }
111
112 sub plugin_call_all {
113         my $type = shift;
114
115         our $cb_name = undef;
116
117         if (! defined $type) {
118                 return;
119         }
120
121         if (TYPE_LOG != $type) {
122                 DEBUG ("Collectd::plugin_call: type = \"$type\", args=\"@_\"");
123         }
124
125         if (! defined $plugins[$type]) {
126                 ERROR ("Collectd::plugin_call: unknown type \"$type\"");
127                 return;
128         }
129
130         lock @plugins;
131         foreach my $plugin (keys %{$plugins[$type]}) {
132                 my $p = $plugins[$type]->{$plugin};
133
134                 my $status = 0;
135
136                 if ($p->{'wait_left'} > 0) {
137                         # TODO: use interval_g
138                         $p->{'wait_left'} -= 10;
139                 }
140
141                 next if ($p->{'wait_left'} > 0);
142
143                 $cb_name = $p->{'cb_name'};
144                 $status = call_by_name (@_);
145
146                 if (! $status) {
147                         my $err = undef;
148
149                         if ($@) {
150                                 $err = $@;
151                         }
152                         else {
153                                 $err = "callback returned false";
154                         }
155
156                         if (TYPE_LOG != $type) {
157                                 ERROR ("Execution of callback \"$cb_name\" failed: $err");
158                         }
159
160                         $status = 0;
161                 }
162
163                 if ($status) {
164                         $p->{'wait_left'} = 0;
165                         $p->{'wait_time'} = 10;
166                 }
167                 elsif (TYPE_READ == $type) {
168                         WARNING ("${plugin}->read() failed with status $status. "
169                                 . "Will suspend it for $p->{'wait_left'} seconds.");
170
171                         $p->{'wait_left'} = $p->{'wait_time'};
172                         $p->{'wait_time'} *= 2;
173
174                         if ($p->{'wait_time'} > 86400) {
175                                 $p->{'wait_time'} = 86400;
176                         }
177                 }
178                 elsif (TYPE_INIT == $type) {
179                         ERROR ("${plugin}->init() failed with status $status. "
180                                 . "Plugin will be disabled.");
181
182                         foreach my $type (keys %types) {
183                                 plugin_unregister ($type, $plugin);
184                         }
185                 }
186                 elsif (TYPE_LOG != $type) {
187                         WARNING ("${plugin}->$types{$type}() failed with status $status.");
188                 }
189         }
190         return 1;
191 }
192
193 # Collectd::plugin_register (type, name, data).
194 #
195 # type:
196 #   init, read, write, shutdown, data set
197 #
198 # name:
199 #   name of the plugin
200 #
201 # data:
202 #   reference to the plugin's subroutine that does the work or the data set
203 #   definition
204 sub plugin_register {
205         my $type = shift;
206         my $name = shift;
207         my $data = shift;
208
209         DEBUG ("Collectd::plugin_register: "
210                 . "type = \"$type\", name = \"$name\", data = \"$data\"");
211
212         if (! ((defined $type) && (defined $name) && (defined $data))) {
213                 ERROR ("Usage: Collectd::plugin_register (type, name, data)");
214                 return;
215         }
216
217         if ((! defined $plugins[$type]) && (TYPE_DATASET != $type)) {
218                 ERROR ("Collectd::plugin_register: Invalid type \"$type\"");
219                 return;
220         }
221
222         if ((TYPE_DATASET == $type) && ("ARRAY" eq ref $data)) {
223                 return plugin_register_data_set ($name, $data);
224         }
225         elsif ((TYPE_DATASET != $type) && (! ref $data)) {
226                 my $pkg = scalar caller;
227
228                 my %p : shared;
229
230                 if ($data !~ m/^$pkg/) {
231                         $data = $pkg . "::" . $data;
232                 }
233
234                 # TODO: make interval_g available at configuration time
235                 %p = (
236                         wait_time => 10,
237                         wait_left => 0,
238                         cb_name   => $data,
239                 );
240
241                 lock @plugins;
242                 $plugins[$type]->{$name} = \%p;
243         }
244         else {
245                 ERROR ("Collectd::plugin_register: Invalid data.");
246                 return;
247         }
248         return 1;
249 }
250
251 sub plugin_unregister {
252         my $type = shift;
253         my $name = shift;
254
255         DEBUG ("Collectd::plugin_unregister: type = \"$type\", name = \"$name\"");
256
257         if (! ((defined $type) && (defined $name))) {
258                 ERROR ("Usage: Collectd::plugin_unregister (type, name)");
259                 return;
260         }
261
262         if (TYPE_DATASET == $type) {
263                 return plugin_unregister_data_set ($name);
264         }
265         elsif (defined $plugins[$type]) {
266                 lock @plugins;
267                 delete $plugins[$type]->{$name};
268         }
269         else {
270                 ERROR ("Collectd::plugin_unregister: Invalid type.");
271                 return;
272         }
273 }
274
275 1;
276
277 # vim: set sw=4 ts=4 tw=78 noexpandtab :
278