contrib/exec-munin.px: Add GPLv2 header.
[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
210       print "$host/munin-$pinst/$type interval=$Interval $time:$value\n";
211     }
212   }
213
214   close ($fh);
215 } # execute_script
216
217 sub main
218 {
219   my $last_run;
220   my $next_run;
221
222   my %config = ParseConfig (-ConfigFile => $ConfigFile,
223     -AutoTrue => 1,
224     -LowerCaseNames => 1);
225   handle_config (\%config);
226
227   while (42)
228   {
229     $last_run = time ();
230     $next_run = $last_run + $Interval;
231
232     for (@$Scripts)
233     {
234       execute_script ($_);
235     }
236
237     while ((my $timeleft = ($next_run - time ())) > 0)
238     {
239       sleep ($timeleft);
240     }
241   }
242 } # main
243
244 =head1 REQUIREMENTS
245
246 This script requires the following Perl modules to be installed:
247
248 =over 4
249
250 =item C<Config::General>
251
252 =item C<Regexp::Common>
253
254 =back
255
256 =head1 SEE ALSO
257
258 L<http://munin.projects.linpro.no/>,
259 L<http://collectd.org/>,
260 L<collectd-exec(5)>
261
262 =head1 AUTHOR
263
264 Florian octo Forster E<lt>octo at verplant.orgE<gt>
265
266 =cut
267
268 # vim: set sw=2 sts=2 ts=8 fdm=marker :