contrib/exec-munin.px: Use the "PUTVAL" command explicitly.
[collectd.git] / contrib / exec-munin.px
1 #!/usr/bin/perl
2
3 #
4 # collectd - contrib/exec-munin.px
5 # Copyright (C) 2007,2008  Florian Forster
6 #
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the
9 # Free Software Foundation; only version 2 of the License is applicable.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 #
20 # Authors:
21 #   Florian octo Forster <octo at verplant.org>
22 #
23
24 use strict;
25 use warnings;
26
27 =head1 NAME
28
29 exec-munin.px
30
31 =head1 DESCRIPTION
32
33 This script allows you to use plugins that were written for Munin with
34 collectd's C<exec-plugin>. Since the data models of Munin and collectd are
35 quite different rewriting the plugins should be preferred over using this
36 transition layer. Having more than one "data source" for one "data set" doesn't
37 work with this script, for example.
38
39 =cut
40
41 use Sys::Hostname ('hostname');
42 use File::Basename ('basename');
43 use Config::General ('ParseConfig');
44 use Regexp::Common ('number');
45
46 our $ConfigFile = '/etc/exec-munin.conf';
47 our $TypeMap = {};
48 our $Scripts = [];
49 our $Interval = 300;
50
51 main ();
52 exit (0);
53
54 # Configuration {{{
55
56 =head1 CONFIGURATION
57
58 This script reads it's configuration from F</etc/exec-munin.conf>. The
59 configuration is read using C<Config::General> which understands a Apache-like
60 config syntax, so it's very similar to the F<collectd.conf> syntax, too.
61
62 Here's a short sample config:
63
64   AddType voltage-in in
65   AddType voltage-out out
66   Interval 300
67   Script /usr/lib/munin/plugins/nut
68
69 The options have the following semantic (i.E<nbsp>e. meaning):
70
71 =over 4
72
73 =item B<AddType> I<to> I<from> [I<from> ...]
74
75 collectd uses B<types> to specify how data is structured. In Munin all data is
76 structured the same way, so some way of telling collectd how to handle the data
77 is needed. This option translates the so called "field names" of Munin to the
78 "types" of collectd. If more than one field are of the same type, e.E<nbsp>g.
79 the C<nut> plugin above provides C<in> and C<out> which are both voltages, you
80 can use a hyphen to add a "type instance" to the type.
81
82 For a list of already defined "types" look at the F<types.db> file in
83 collectd's shared data directory, e.E<nbsp>g. F</usr/share/collectd/>.
84
85 =item B<Interval> I<Seconds>
86
87 Sets the interval in which the plugins are executed. This doesn't need to match
88 the interval setting of the collectd daemon. Usually, you want to execute the
89 Munin plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
90 seconds.
91
92 =item B<Script> I<File>
93
94 Adds a script to the list of scripts to be executed once per I<Interval>
95 seconds.
96
97 =back
98
99 =cut
100
101 sub handle_config_addtype
102 {
103   my $list = shift;
104
105   for (my $i = 0; $i < @$list; $i++)
106   {
107     my ($to, @from) = split (' ', $list->[$i]);
108     for (my $j = 0; $j < @from; $j++)
109     {
110       $TypeMap->{$from[$j]} = $to;
111     }
112   }
113 } # handle_config_addtype
114
115 sub handle_config_script
116 {
117   my $scripts = shift;
118
119   for (my $i = 0; $i < @$scripts; $i++)
120   {
121     my $script = $scripts->[$i];
122     if (!-e $script)
123     {
124       print STDERR "Script `$script' doesn't exist.\n";
125     }
126     elsif (!-x $script)
127     {
128       print STDERR "Script `$script' exists but is not executable.\n";
129     }
130     else
131     {
132       push (@$Scripts, $script);
133     }
134   } # for $i
135 } # handle_config_script
136
137 sub handle_config
138 {
139   my $config = shift;
140
141   if (defined ($config->{'addtype'}))
142   {
143     if (ref ($config->{'addtype'}) eq 'ARRAY')
144     {
145       handle_config_addtype ($config->{'addtype'});
146     }
147     elsif (ref ($config->{'addtype'}) eq '')
148     {
149       handle_config_addtype ([$config->{'addtype'}]);
150     }
151     else
152     {
153       print STDERR "Cannot handle ref type '"
154       . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
155     }
156   }
157
158   if (defined ($config->{'script'}))
159   {
160     if (ref ($config->{'script'}) eq 'ARRAY')
161     {
162       handle_config_script ($config->{'script'});
163     }
164     elsif (ref ($config->{'script'}) eq '')
165     {
166       handle_config_script ([$config->{'script'}]);
167     }
168     else
169     {
170       print STDERR "Cannot handle ref type '"
171       . ref ($config->{'script'}) . "' for option 'Script'.\n";
172     }
173   }
174
175   if (defined ($config->{'interval'})
176     && (ref ($config->{'interval'}) eq ''))
177   {
178     my $num = int ($config->{'interval'});
179     if ($num > 0)
180     {
181       $Interval = $num;
182     }
183   }
184 } # handle_config }}}
185
186 sub execute_script
187 {
188   my $fh;
189   my $pinst;
190   my $time = time ();
191   my $script = shift;
192   my $host = hostname () || 'localhost';
193   if (!open ($fh, '-|', $script))
194   {
195     print STDERR "Cannot execute $script: $!";
196     return;
197   }
198
199   $pinst = basename ($script);
200
201   while (my $line = <$fh>)
202   {
203     chomp ($line);
204     if ($line =~ m#^([^\.\-/]+)\.value\s+($RE{num}{real})#)
205     {
206       my $field = $1;
207       my $value = $2;
208       my $type = (defined ($TypeMap->{$field})) ? $TypeMap->{$field} : $field;
209       my $ident = "$host/munin-$pinst/$type";
210
211       $ident =~ s/"/\\"/g;
212
213       print qq(PUTVAL "$ident" interval=$Interval $time:$value\n);
214     }
215   }
216
217   close ($fh);
218 } # execute_script
219
220 sub main
221 {
222   my $last_run;
223   my $next_run;
224
225   my %config = ParseConfig (-ConfigFile => $ConfigFile,
226     -AutoTrue => 1,
227     -LowerCaseNames => 1);
228   handle_config (\%config);
229
230   while (42)
231   {
232     $last_run = time ();
233     $next_run = $last_run + $Interval;
234
235     for (@$Scripts)
236     {
237       execute_script ($_);
238     }
239
240     while ((my $timeleft = ($next_run - time ())) > 0)
241     {
242       sleep ($timeleft);
243     }
244   }
245 } # main
246
247 =head1 REQUIREMENTS
248
249 This script requires the following Perl modules to be installed:
250
251 =over 4
252
253 =item C<Config::General>
254
255 =item C<Regexp::Common>
256
257 =back
258
259 =head1 SEE ALSO
260
261 L<http://munin.projects.linpro.no/>,
262 L<http://collectd.org/>,
263 L<collectd-exec(5)>
264
265 =head1 AUTHOR
266
267 Florian octo Forster E<lt>octo at verplant.orgE<gt>
268
269 =cut
270
271 # vim: set sw=2 sts=2 ts=8 fdm=marker :