Merge branch 'collectd-4.10'
[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 = defined ($ENV{'COLLECTD_INTERVAL'}) ? (0 + $ENV{'COLLECTD_INTERVAL'}) : 300;
50 our $Hostname = defined ($ENV{'COLLECTD_HOSTNAME'}) ? $ENV{'COLLECTD_HOSTNAME'} : '';
51
52 main ();
53 exit (0);
54
55 # Configuration {{{
56
57 =head1 CONFIGURATION
58
59 This script reads it's configuration from F</etc/exec-munin.conf>. The
60 configuration is read using C<Config::General> which understands a Apache-like
61 config syntax, so it's very similar to the F<collectd.conf> syntax, too.
62
63 Here's a short sample config:
64
65   AddType voltage-in in
66   AddType voltage-out out
67   Interval 300
68   Script /usr/lib/munin/plugins/nut
69
70 The options have the following semantic (i.E<nbsp>e. meaning):
71
72 =over 4
73
74 =item B<AddType> I<to> I<from> [I<from> ...]
75
76 collectd uses B<types> to specify how data is structured. In Munin all data is
77 structured the same way, so some way of telling collectd how to handle the data
78 is needed. This option translates the so called "field names" of Munin to the
79 "types" of collectd. If more than one field are of the same type, e.E<nbsp>g.
80 the C<nut> plugin above provides C<in> and C<out> which are both voltages, you
81 can use a hyphen to add a "type instance" to the type.
82
83 For a list of already defined "types" look at the F<types.db> file in
84 collectd's shared data directory, e.E<nbsp>g. F</usr/share/collectd/>.
85
86 =item B<Interval> I<Seconds>
87
88 Sets the interval in which the plugins are executed. This doesn't need to match
89 the interval setting of the collectd daemon. Usually, you want to execute the
90 Munin plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
91 seconds.
92
93 =item B<Script> I<File>
94
95 Adds a script to the list of scripts to be executed once per I<Interval>
96 seconds.
97
98 =back
99
100 =cut
101
102 sub handle_config_addtype
103 {
104   my $list = shift;
105
106   for (my $i = 0; $i < @$list; $i++)
107   {
108     my ($to, @from) = split (' ', $list->[$i]);
109     for (my $j = 0; $j < @from; $j++)
110     {
111       $TypeMap->{$from[$j]} = $to;
112     }
113   }
114 } # handle_config_addtype
115
116 sub handle_config_script
117 {
118   my $scripts = shift;
119
120   for (my $i = 0; $i < @$scripts; $i++)
121   {
122     my $script = $scripts->[$i];
123     if (!-e $script)
124     {
125       print STDERR "Script `$script' doesn't exist.\n";
126     }
127     elsif (!-x $script)
128     {
129       print STDERR "Script `$script' exists but is not executable.\n";
130     }
131     else
132     {
133       push (@$Scripts, $script);
134     }
135   } # for $i
136 } # handle_config_script
137
138 sub handle_config
139 {
140   my $config = shift;
141
142   if (defined ($config->{'addtype'}))
143   {
144     if (ref ($config->{'addtype'}) eq 'ARRAY')
145     {
146       handle_config_addtype ($config->{'addtype'});
147     }
148     elsif (ref ($config->{'addtype'}) eq '')
149     {
150       handle_config_addtype ([$config->{'addtype'}]);
151     }
152     else
153     {
154       print STDERR "Cannot handle ref type '"
155       . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
156     }
157   }
158
159   if (defined ($config->{'script'}))
160   {
161     if (ref ($config->{'script'}) eq 'ARRAY')
162     {
163       handle_config_script ($config->{'script'});
164     }
165     elsif (ref ($config->{'script'}) eq '')
166     {
167       handle_config_script ([$config->{'script'}]);
168     }
169     else
170     {
171       print STDERR "Cannot handle ref type '"
172       . ref ($config->{'script'}) . "' for option 'Script'.\n";
173     }
174   }
175
176   if (defined ($config->{'interval'})
177     && (ref ($config->{'interval'}) eq ''))
178   {
179     my $num = int ($config->{'interval'});
180     if ($num > 0)
181     {
182       $Interval = $num;
183     }
184   }
185 } # handle_config }}}
186
187 sub execute_script
188 {
189   my $fh;
190   my $pinst;
191   my $time = time ();
192   my $script = shift;
193   my $host = $Hostname || hostname () || 'localhost';
194   if (!open ($fh, '-|', $script))
195   {
196     print STDERR "Cannot execute $script: $!";
197     return;
198   }
199
200   $pinst = basename ($script);
201
202   while (my $line = <$fh>)
203   {
204     chomp ($line);
205     if ($line =~ m#^([^\.\-/]+)\.value\s+($RE{num}{real})#)
206     {
207       my $field = $1;
208       my $value = $2;
209       my $type = (defined ($TypeMap->{$field})) ? $TypeMap->{$field} : $field;
210       my $ident = "$host/munin-$pinst/$type";
211
212       $ident =~ s/"/\\"/g;
213
214       print qq(PUTVAL "$ident" interval=$Interval $time:$value\n);
215     }
216   }
217
218   close ($fh);
219 } # execute_script
220
221 sub main
222 {
223   my $last_run;
224   my $next_run;
225
226   my %config = ParseConfig (-ConfigFile => $ConfigFile,
227     -AutoTrue => 1,
228     -LowerCaseNames => 1);
229   handle_config (\%config);
230
231   while (42)
232   {
233     $last_run = time ();
234     $next_run = $last_run + $Interval;
235
236     for (@$Scripts)
237     {
238       execute_script ($_);
239     }
240
241     while ((my $timeleft = ($next_run - time ())) > 0)
242     {
243       sleep ($timeleft);
244     }
245   }
246 } # main
247
248 =head1 REQUIREMENTS
249
250 This script requires the following Perl modules to be installed:
251
252 =over 4
253
254 =item C<Config::General>
255
256 =item C<Regexp::Common>
257
258 =back
259
260 =head1 SEE ALSO
261
262 L<http://munin.projects.linpro.no/>,
263 L<http://collectd.org/>,
264 L<collectd-exec(5)>
265
266 =head1 AUTHOR
267
268 Florian octo Forster E<lt>octo at verplant.orgE<gt>
269
270 =cut
271
272 # vim: set sw=2 sts=2 ts=8 fdm=marker :