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