Code

Merge branch 'collectd-4.2' into collectd-4.3
[collectd.git] / contrib / exec-nagios.px
1 #!/usr/bin/perl
3 use strict;
4 use warnings;
6 =head1 NAME
8 exec-nagios.px
10 =head1 DESCRIPTION
12 This script allows you to use plugins that were written for Nagios with
13 collectd's C<exec-plugin>. If the plugin checks some kind of threshold, please
14 consider configuring the threshold using collectd's own facilities instead of
15 using this transition layer.
17 =cut
19 use Sys::Hostname ('hostname');
20 use File::Basename ('basename');
21 use Config::General ('ParseConfig');
22 use Regexp::Common ('number');
24 our $ConfigFile = '/etc/exec-nagios.conf';
25 our $TypeMap = {};
26 our $Scripts = [];
27 our $Interval = 300;
29 main ();
30 exit (0);
32 # Configuration
33 # {{{
35 =head1 CONFIGURATION
37 This script reads it's configuration from F</etc/exec-nagios.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.
41 Here's a short sample config:
43   Interval 300
44   <Script /usr/lib/nagios/check_tcp>
45     Arguments -H alice -p 22
46     Type delay
47   </Script>
48   <Script /usr/lib/nagios/check_dns>
49     Arguments -H alice
50     Type delay
51   </Script>
53 The options have the following semantic (i.E<nbsp>e. meaning):
55 =over 4
57 =item B<Interval> I<Seconds>
59 Sets the interval in which the plugins are executed. This doesn't need to match
60 the interval setting of the collectd daemon. Usually, you want to execute the
61 Nagios plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
62 seconds.
64 =item E<lt>B<Script> I<File>E<gt>
66 Adds a script to the list of scripts to be executed once per I<Interval>
67 seconds. You can use the following optional arguments to specify the operation
68 further:
70 =over 4
72 =item B<Arguments> I<Arguments>
74 Pass the arguments I<Arguments> to the script. This is often needed with Nagios
75 plugins, because much of the logic is implemented in the plugins, not in the
76 daemon. If you need to specify a warning and/or critical range here, please
77 consider using collectd's own threshold mechanism, which is by far the more
78 elegant solution than this transition layer.
80 =item B<Type> I<Type>
82 If the plugin provides "performance data" the performance data is dispatched to
83 collectd with this type. If no type is configured the data is ignored. Please
84 note that this is limited to types that take exactly one value, such as the
85 type C<delay> in the example above. If you need more complex performance data,
86 rewrite the plugin as a collectd plugin (or at least port it do run directly
87 with the C<exec-plugin>).
89 =back
91 =cut
93 sub handle_config_addtype
94 {
95   my $list = shift;
97   for (my $i = 0; $i < @$list; $i++)
98   {
99     my ($to, @from) = split (' ', $list->[$i]);
100     for (my $j = 0; $j < @from; $j++)
101     {
102       $TypeMap->{$from[$j]} = $to;
103     }
104   }
105 } # handle_config_addtype
107 sub handle_config_script
109   my $scripts = shift;
111   for (keys %$scripts)
112   {
113     my $script = $_;
114     my $opts = $scripts->{$script};
116     if (!-e $script)
117     {
118       print STDERR "Script `$script' doesn't exist.\n";
119     }
120     elsif (!-x $script)
121     {
122       print STDERR "Script `$script' exists but is not executable.\n";
123     }
124     else
125     {
126       $opts->{'script'} = $script;
127       push (@$Scripts, $opts);
128     }
129   } # for (keys %$scripts)
130 } # handle_config_script
132 sub handle_config
134   my $config = shift;
136   if (defined ($config->{'addtype'}))
137   {
138     if (ref ($config->{'addtype'}) eq 'ARRAY')
139     {
140       handle_config_addtype ($config->{'addtype'});
141     }
142     elsif (ref ($config->{'addtype'}) eq '')
143     {
144       handle_config_addtype ([$config->{'addtype'}]);
145     }
146     else
147     {
148       print STDERR "Cannot handle ref type '"
149       . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
150     }
151   }
153   if (defined ($config->{'script'}))
154   {
155     if (ref ($config->{'script'}) eq 'HASH')
156     {
157       handle_config_script ($config->{'script'});
158     }
159     else
160     {
161       print STDERR "Cannot handle ref type '"
162       . ref ($config->{'script'}) . "' for option 'Script'.\n";
163     }
164   }
166   if (defined ($config->{'interval'})
167     && (ref ($config->{'interval'}) eq ''))
168   {
169     my $num = int ($config->{'interval'});
170     if ($num > 0)
171     {
172       $Interval = $num;
173     }
174   }
175 } # handle_config }}}
177 sub scale_value
179   my $value = shift;
180   my $unit = shift;
182   if (!$unit)
183   {
184     return ($value);
185   }
187   if (($unit =~ m/^mb(yte)?$/i) || ($unit eq 'M'))
188   {
189     return ($value * 1000000);
190   }
191   elsif ($unit =~ m/^k(b(yte)?)?$/i)
192   {
193     return ($value * 1000);
194   }
196   return ($value);
199 sub sanitize_instance
201   my $inst = shift;
203   if ($inst eq '/')
204   {
205     return ('root');
206   }
208   $inst =~ s/[^A-Za-z_-]/_/g;
209   $inst =~ s/__+/_/g;
210   $inst =~ s/^_//;
211   $inst =~ s/_$//;
213   return ($inst);
216 sub handle_performance_data
218   my $host = shift;
219   my $plugin = shift;
220   my $pinst = shift;
221   my $type = shift;
222   my $time = shift;
223   my $line = shift;
225   my $tinst;
226   my $value;
227   my $unit;
229   if ($line =~ m/^([^=]+)=($RE{num}{real})([^;]*)/)
230   {
231     $tinst = sanitize_instance ($1);
232     $value = scale_value ($2, $3);
233   }
234   else
235   {
236     return;
237   }
239   print "PUTVAL $host/$plugin-$pinst/$type-$tinst interval=$Interval ${time}:$value\n";
242 sub execute_script
244   my $fh;
245   my $pinst;
246   my $time = time ();
247   my $script = shift;
248   my @args = ();
249   my $host = hostname () || 'localhost';
251   my $state = 0;
252   my $serviceoutput;
253   my @serviceperfdata;
254   my @longserviceoutput;
256   my $script_name = $script->{'script'};
257   
258   if ($script->{'arguments'})
259   {
260     @args = split (' ', $script->{'arguments'});
261   }
263   if (!open ($fh, '-|', $script_name, @args))
264   {
265     print STDERR "Cannot execute $script_name: $!";
266     return;
267   }
269   $pinst = sanitize_instance (basename ($script_name));
271   # Parse the output of the plugin. The format is seriously fucked up, because
272   # it got extended way beyond what it could handle.
273   while (my $line = <$fh>)
274   {
275     chomp ($line);
277     if ($state == 0)
278     {
279       my $perfdata;
280       ($serviceoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
281       
282       if ($perfdata)
283       {
284         push (@serviceperfdata, split (' ', $perfdata));
285       }
287       $state = 1;
288     }
289     elsif ($state == 1)
290     {
291       my $longoutput;
292       my $perfdata;
293       ($longoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
295       push (@longserviceoutput, $longoutput);
297       if ($perfdata)
298       {
299         push (@serviceperfdata, split (' ', $perfdata));
300         $state = 2;
301       }
302     }
303     else # ($state == 2)
304     {
305       push (@serviceperfdata, split (' ', $line));
306     }
307   }
309   close ($fh);
310   # Save the exit status of the check in $state
311   $state = $?;
313   if ($state == 0)
314   {
315     $state = 'okay';
316   }
317   elsif ($state == 1)
318   {
319     $state = 'warning';
320   }
321   else
322   {
323     $state = 'failure';
324   }
326   {
327     my $type = $script->{'type'} || 'nagios_check';
329     print "PUTNOTIF time=$time severity=$state host=$host plugin=nagios "
330     . "plugin_instance=$pinst type=$type message=$serviceoutput\n";
331   }
333   if ($script->{'type'})
334   {
335     for (@serviceperfdata)
336     {
337       handle_performance_data ($host, 'nagios', $pinst, $script->{'type'},
338         $time, $_);
339     }
340   }
341 } # execute_script
343 sub main
345   my $last_run;
346   my $next_run;
348   my %config = ParseConfig (-ConfigFile => $ConfigFile,
349     -AutoTrue => 1,
350     -LowerCaseNames => 1);
351   handle_config (\%config);
353   while (42)
354   {
355     $last_run = time ();
356     $next_run = $last_run + $Interval;
358     for (@$Scripts)
359     {
360       execute_script ($_);
361     }
363     while ((my $timeleft = ($next_run - time ())) > 0)
364     {
365       sleep ($timeleft);
366     }
367   }
368 } # main
370 =head1 REQUIREMENTS
372 This script requires the following Perl modules to be installed:
374 =over 4
376 =item C<Config::General>
378 =item C<Regexp::Common>
380 =back
382 =head1 SEE ALSO
384 L<http://www.nagios.org/>,
385 L<http://nagiosplugins.org/>,
386 L<http://collectd.org/>,
387 L<collectd-exec(5)>
389 =head1 AUTHOR
391 Florian octo Forster E<lt>octo at verplant.orgE<gt>
393 =cut
395 # vim: set sw=2 sts=2 ts=8 fdm=marker :