Code

Merge branch 'collectd-4.9'
[collectd.git] / contrib / exec-munin.px
1 #!/usr/bin/perl
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 #
24 use strict;
25 use warnings;
27 =head1 NAME
29 exec-munin.px
31 =head1 DESCRIPTION
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.
39 =cut
41 use Sys::Hostname ('hostname');
42 use File::Basename ('basename');
43 use Config::General ('ParseConfig');
44 use Regexp::Common ('number');
46 our $ConfigFile = '/etc/exec-munin.conf';
47 our $TypeMap = {};
48 our $Scripts = [];
49 our $Interval = 300;
51 main ();
52 exit (0);
54 # Configuration {{{
56 =head1 CONFIGURATION
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.
62 Here's a short sample config:
64   AddType voltage-in in
65   AddType voltage-out out
66   Interval 300
67   Script /usr/lib/munin/plugins/nut
69 The options have the following semantic (i.E<nbsp>e. meaning):
71 =over 4
73 =item B<AddType> I<to> I<from> [I<from> ...]
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.
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/>.
85 =item B<Interval> I<Seconds>
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.
92 =item B<Script> I<File>
94 Adds a script to the list of scripts to be executed once per I<Interval>
95 seconds.
97 =back
99 =cut
101 sub handle_config_addtype
103   my $list = shift;
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
115 sub handle_config_script
117   my $scripts = shift;
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
137 sub handle_config
139   my $config = shift;
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   }
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   }
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 }}}
186 sub execute_script
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   }
199   $pinst = basename ($script);
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;
210       print "$host/munin-$pinst/$type interval=$Interval $time:$value\n";
211     }
212   }
214   close ($fh);
215 } # execute_script
217 sub main
219   my $last_run;
220   my $next_run;
222   my %config = ParseConfig (-ConfigFile => $ConfigFile,
223     -AutoTrue => 1,
224     -LowerCaseNames => 1);
225   handle_config (\%config);
227   while (42)
228   {
229     $last_run = time ();
230     $next_run = $last_run + $Interval;
232     for (@$Scripts)
233     {
234       execute_script ($_);
235     }
237     while ((my $timeleft = ($next_run - time ())) > 0)
238     {
239       sleep ($timeleft);
240     }
241   }
242 } # main
244 =head1 REQUIREMENTS
246 This script requires the following Perl modules to be installed:
248 =over 4
250 =item C<Config::General>
252 =item C<Regexp::Common>
254 =back
256 =head1 SEE ALSO
258 L<http://munin.projects.linpro.no/>,
259 L<http://collectd.org/>,
260 L<collectd-exec(5)>
262 =head1 AUTHOR
264 Florian octo Forster E<lt>octo at verplant.orgE<gt>
266 =cut
268 # vim: set sw=2 sts=2 ts=8 fdm=marker :