Code

Solaris 8 fixes: Check for <stdint.h> before inclusion.
[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 =back
93 =cut
95 sub handle_config_addtype
96 {
97   my $list = shift;
99   for (my $i = 0; $i < @$list; $i++)
100   {
101     my ($to, @from) = split (' ', $list->[$i]);
102     for (my $j = 0; $j < @from; $j++)
103     {
104       $TypeMap->{$from[$j]} = $to;
105     }
106   }
107 } # handle_config_addtype
109 sub handle_config_script
111   my $scripts = shift;
113   for (keys %$scripts)
114   {
115     my $script = $_;
116     my $opts = $scripts->{$script};
118     if (!-e $script)
119     {
120       print STDERR "Script `$script' doesn't exist.\n";
121     }
122     elsif (!-x $script)
123     {
124       print STDERR "Script `$script' exists but is not executable.\n";
125     }
126     else
127     {
128       if (ref ($opts) eq 'ARRAY')
129         {
130           for (@$opts)
131             {
132               my $opt = $_;
133               $opt->{'script'} = $script;
134               push (@$Scripts, $opt);
135             }
136         }
137           else
138         {
139           $opts->{'script'} = $script;
140           push (@$Scripts, $opts);
141         }
142     }
143   } # for (keys %$scripts)
144 } # handle_config_script
146 sub handle_config
148   my $config = shift;
150   if (defined ($config->{'addtype'}))
151   {
152     if (ref ($config->{'addtype'}) eq 'ARRAY')
153     {
154       handle_config_addtype ($config->{'addtype'});
155     }
156     elsif (ref ($config->{'addtype'}) eq '')
157     {
158       handle_config_addtype ([$config->{'addtype'}]);
159     }
160     else
161     {
162       print STDERR "Cannot handle ref type '"
163       . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
164     }
165   }
167   if (defined ($config->{'script'}))
168   {
169     if (ref ($config->{'script'}) eq 'HASH')
170     {
171       handle_config_script ($config->{'script'});
172     }
173     else
174     {
175       print STDERR "Cannot handle ref type '"
176       . ref ($config->{'script'}) . "' for option 'Script'.\n";
177     }
178   }
180   if (defined ($config->{'interval'})
181     && (ref ($config->{'interval'}) eq ''))
182   {
183     my $num = int ($config->{'interval'});
184     if ($num > 0)
185     {
186       $Interval = $num;
187     }
188   }
189 } # handle_config }}}
191 sub scale_value
193   my $value = shift;
194   my $unit = shift;
196   if (!$unit)
197   {
198     return ($value);
199   }
201   if (($unit =~ m/^mb(yte)?$/i) || ($unit eq 'M'))
202   {
203     return ($value * 1000000);
204   }
205   elsif ($unit =~ m/^k(b(yte)?)?$/i)
206   {
207     return ($value * 1000);
208   }
210   return ($value);
213 sub sanitize_instance
215   my $inst = shift;
217   if ($inst eq '/')
218   {
219     return ('root');
220   }
222   $inst =~ s/[^A-Za-z_-]/_/g;
223   $inst =~ s/__+/_/g;
224   $inst =~ s/^_//;
225   $inst =~ s/_$//;
227   return ($inst);
230 sub handle_performance_data
232   my $host = shift;
233   my $plugin = shift;
234   my $pinst = shift;
235   my $type = shift;
236   my $time = shift;
237   my $line = shift;
239   my $tinst;
240   my $value;
241   my $unit;
243   if ($line =~ m/^([^=]+)=($RE{num}{real})([^;]*)/)
244   {
245     $tinst = sanitize_instance ($1);
246     $value = scale_value ($2, $3);
247   }
248   else
249   {
250     return;
251   }
253   print "PUTVAL $host/$plugin-$pinst/$type-$tinst interval=$Interval ${time}:$value\n";
256 sub execute_script
258   my $fh;
259   my $pinst;
260   my $time = time ();
261   my $script = shift;
262   my @args = ();
263   my $host = hostname () || 'localhost';
265   my $state = 0;
266   my $serviceoutput;
267   my @serviceperfdata;
268   my @longserviceoutput;
270   my $script_name = $script->{'script'};
271   
272   if ($script->{'arguments'})
273   {
274     @args = split (' ', $script->{'arguments'});
275   }
277   if (!open ($fh, '-|', $script_name, @args))
278   {
279     print STDERR "Cannot execute $script_name: $!";
280     return;
281   }
283   $pinst = sanitize_instance (basename ($script_name));
285   # Parse the output of the plugin. The format is seriously fucked up, because
286   # it got extended way beyond what it could handle.
287   while (my $line = <$fh>)
288   {
289     chomp ($line);
291     if ($state == 0)
292     {
293       my $perfdata;
294       ($serviceoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
295       
296       if ($perfdata)
297       {
298         push (@serviceperfdata, split (' ', $perfdata));
299       }
301       $state = 1;
302     }
303     elsif ($state == 1)
304     {
305       my $longoutput;
306       my $perfdata;
307       ($longoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
309       push (@longserviceoutput, $longoutput);
311       if ($perfdata)
312       {
313         push (@serviceperfdata, split (' ', $perfdata));
314         $state = 2;
315       }
316     }
317     else # ($state == 2)
318     {
319       push (@serviceperfdata, split (' ', $line));
320     }
321   }
323   close ($fh);
324   # Save the exit status of the check in $state
325   $state = $?;
327   if ($state == 0)
328   {
329     $state = 'okay';
330   }
331   elsif ($state == 1)
332   {
333     $state = 'warning';
334   }
335   else
336   {
337     $state = 'failure';
338   }
340   {
341     my $type = $script->{'type'} || 'nagios_check';
343     print "PUTNOTIF time=$time severity=$state host=$host plugin=nagios "
344     . "plugin_instance=$pinst type=$type message=$serviceoutput\n";
345   }
347   if ($script->{'type'})
348   {
349     for (@serviceperfdata)
350     {
351       handle_performance_data ($host, 'nagios', $pinst, $script->{'type'},
352         $time, $_);
353     }
354   }
355 } # execute_script
357 sub main
359   my $last_run;
360   my $next_run;
362   my %config = ParseConfig (-ConfigFile => $ConfigFile,
363     -AutoTrue => 1,
364     -LowerCaseNames => 1);
365   handle_config (\%config);
367   while (42)
368   {
369     $last_run = time ();
370     $next_run = $last_run + $Interval;
372     for (@$Scripts)
373     {
374       execute_script ($_);
375     }
377     while ((my $timeleft = ($next_run - time ())) > 0)
378     {
379       sleep ($timeleft);
380     }
381   }
382 } # main
384 =head1 REQUIREMENTS
386 This script requires the following Perl modules to be installed:
388 =over 4
390 =item C<Config::General>
392 =item C<Regexp::Common>
394 =back
396 =head1 SEE ALSO
398 L<http://www.nagios.org/>,
399 L<http://nagiosplugins.org/>,
400 L<http://collectd.org/>,
401 L<collectd-exec(5)>
403 =head1 AUTHOR
405 Florian octo Forster E<lt>octo at verplant.orgE<gt>
407 =cut
409 # vim: set sw=2 sts=2 ts=8 fdm=marker :