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