Code

collectd-python.conf(5): fix trivial spelling mistakes
[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 $NRPEMap = {};
27 our $Scripts = [];
28 our $Interval = 300;
30 main ();
31 exit (0);
33 # Configuration
34 # {{{
36 =head1 CONFIGURATION
38 This script reads it's configuration from F</etc/exec-nagios.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.
42 Here's a short sample config:
44   NRPEConfig "/etc/nrpe.cfg"
45   Interval 300
46   <Script /usr/lib/nagios/check_tcp>
47     Arguments -H alice -p 22
48     Type delay
49   </Script>
50   <Script /usr/lib/nagios/check_dns>
51     Arguments -H alice
52     Type delay
53   </Script>
55 The options have the following semantic (i.E<nbsp>e. meaning):
57 =over 4
59 =item B<NRPEConfig> I<File>
61 Read the NRPE config and add the command definitions to an alias table. After
62 reading the file you can use the NRPE command name rather than the script's
63 filename within B<Script> blocks (see below). If both, the NRPE config and the
64 B<Script> block, define arguments they will be merged by concatenating the
65 arguments together in the order "NRPE-args Script-args".
67 Please note that this option is rather dumb. It does not support "command
68 argument processing" (i.e. replacing C<$ARG1$> and friends), inclusion of other
69 NRPE config files, include directories etc.
71 =item B<Interval> I<Seconds>
73 Sets the interval in which the plugins are executed. This doesn't need to match
74 the interval setting of the collectd daemon. Usually, you want to execute the
75 Nagios plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
76 seconds.
78 =item E<lt>B<Script> I<File>E<gt>
80 Adds a script to the list of scripts to be executed once per I<Interval>
81 seconds. If the B<NRPEConfig> is given above the B<Script> block, you may use
82 the NRPE command name rather than the script's filename. You can use the
83 following optional arguments to specify the operation further:
85 =over 4
87 =item B<Arguments> I<Arguments>
89 Pass the arguments I<Arguments> to the script. This is often needed with Nagios
90 plugins, because much of the logic is implemented in the plugins, not in the
91 daemon. If you need to specify a warning and/or critical range here, please
92 consider using collectd's own threshold mechanism, which is by far the more
93 elegant solution than this transition layer.
95 =item B<Type> I<Type>
97 If the plugin provides "performance data" the performance data is dispatched to
98 collectd with this type. If no type is configured the data is ignored. Please
99 note that this is limited to types that take exactly one value, such as the
100 type C<delay> in the example above. If you need more complex performance data,
101 rewrite the plugin as a collectd plugin (or at least port it do run directly
102 with the C<exec-plugin>).
104 =back
106 =back
108 =cut
110 sub parse_nrpe_conf
112   my $file = shift;
113   my $fh;
114   my $status;
116   $status = open ($fh, '<', $file);
117   if (!$status)
118   {
119     print STDERR "Reading NRPE config from \"$file\" failed: $!\n";
120     return;
121   }
123   while (<$fh>)
124   {
125     my $line = $_;
126     chomp ($line);
128     if ($line =~ m/^\s*command\[([^\]]+)\]\s*=\s*(.+)$/)
129     {
130       my $alias = $1;
131       my $script;
132       my $arguments;
134       ($script, $arguments) = split (' ', $2, 2);
136       if ($NRPEMap->{$alias})
137       {
138         print STDERR "Warning: NRPE command \"$alias\" redefined.\n";
139       }
141       $NRPEMap->{$alias} = { script => $script };
142       if ($arguments)
143       {
144         $NRPEMap->{$alias}{'arguments'} = $arguments;
145       }
146     }
147   } # while (<$fh>)
149   close ($fh);
150 } # parse_nrpe_conf
152 sub handle_config_addtype
154   my $list = shift;
156   for (my $i = 0; $i < @$list; $i++)
157   {
158     my ($to, @from) = split (' ', $list->[$i]);
159     for (my $j = 0; $j < @from; $j++)
160     {
161       $TypeMap->{$from[$j]} = $to;
162     }
163   }
164 } # handle_config_addtype
166 # Update the script record. This function adds the name of the script /
167 # executable to the hash and merges the configured and NRPE arguments if
168 # required.
169 sub update_script_opts
171   my $opts = shift;
172   my $script = shift;
173   my $nrpe_args = shift;
175   $opts->{'script'} = $script;
177   if ($nrpe_args)
178   {
179     if ($opts->{'arguments'})
180     {
181       $opts->{'arguments'} = $nrpe_args . ' ' . $opts->{'arguments'};
182     }
183     else
184     {
185       $opts->{'arguments'} = $nrpe_args;
186     }
187   }
188 } # update_script_opts
190 sub handle_config_script
192   my $scripts = shift;
194   for (keys %$scripts)
195   {
196     my $script = $_;
197     my $opts = $scripts->{$script};
199     my $nrpe_args = '';
201     # Check if the script exists in the NRPE map. If so, replace the alias name
202     # with the actual script name.
203     if ($NRPEMap->{$script})
204     {
205       if ($NRPEMap->{$script}{'arguments'})
206       {
207         $nrpe_args = $NRPEMap->{$script}{'arguments'};
208       }
209       $script = $NRPEMap->{$script}{'script'};
210     }
212     # Check if the script exists and is executable.
213     if (!-e $script)
214     {
215       print STDERR "Script `$script' doesn't exist.\n";
216     }
217     elsif (!-x $script)
218     {
219       print STDERR "Script `$script' exists but is not executable.\n";
220     }
221     else
222     {
223       # Add the script to the global @$Script array.
224       if (ref ($opts) eq 'ARRAY')
225       {
226         for (@$opts)
227         {
228           my $opt = $_;
229           update_script_opts ($opt, $script, $nrpe_args);
230           push (@$Scripts, $opt);
231         }
232       }
233       else
234       {
235         update_script_opts ($opts, $script, $nrpe_args);
236         push (@$Scripts, $opts);
237       }
238     }
239   } # for (keys %$scripts)
240 } # handle_config_script
242 sub handle_config
244   my $config = shift;
246   if (defined ($config->{'nrpeconfig'}))
247   {
248     if (ref ($config->{'nrpeconfig'}) eq 'ARRAY')
249     {
250       for (@{$config->{'nrpeconfig'}})
251       {
252         parse_nrpe_conf ($_);
253       }
254     }
255     elsif (ref ($config->{'nrpeconfig'}) eq '')
256     {
257       parse_nrpe_conf ($config->{'nrpeconfig'});
258     }
259     else
260     {
261       print STDERR "Cannot handle ref type '"
262       . ref ($config->{'nrpeconfig'}) . "' for option 'NRPEConfig'.\n";
263     }
264   }
266   if (defined ($config->{'addtype'}))
267   {
268     if (ref ($config->{'addtype'}) eq 'ARRAY')
269     {
270       handle_config_addtype ($config->{'addtype'});
271     }
272     elsif (ref ($config->{'addtype'}) eq '')
273     {
274       handle_config_addtype ([$config->{'addtype'}]);
275     }
276     else
277     {
278       print STDERR "Cannot handle ref type '"
279       . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
280     }
281   }
283   if (defined ($config->{'script'}))
284   {
285     if (ref ($config->{'script'}) eq 'HASH')
286     {
287       handle_config_script ($config->{'script'});
288     }
289     else
290     {
291       print STDERR "Cannot handle ref type '"
292       . ref ($config->{'script'}) . "' for option 'Script'.\n";
293     }
294   }
296   if (defined ($config->{'interval'})
297     && (ref ($config->{'interval'}) eq ''))
298   {
299     my $num = int ($config->{'interval'});
300     if ($num > 0)
301     {
302       $Interval = $num;
303     }
304   }
305 } # handle_config }}}
307 sub scale_value
309   my $value = shift;
310   my $unit = shift;
312   if (!$unit)
313   {
314     return ($value);
315   }
317   if (($unit =~ m/^mb(yte)?$/i) || ($unit eq 'M'))
318   {
319     return ($value * 1000000);
320   }
321   elsif ($unit =~ m/^k(b(yte)?)?$/i)
322   {
323     return ($value * 1000);
324   }
326   return ($value);
329 sub sanitize_instance
331   my $inst = shift;
333   if ($inst eq '/')
334   {
335     return ('root');
336   }
338   $inst =~ s/[^A-Za-z_-]/_/g;
339   $inst =~ s/__+/_/g;
340   $inst =~ s/^_//;
341   $inst =~ s/_$//;
343   return ($inst);
346 sub handle_performance_data
348   my $host = shift;
349   my $plugin = shift;
350   my $pinst = shift;
351   my $type = shift;
352   my $time = shift;
353   my $line = shift;
355   my $tinst;
356   my $value;
357   my $unit;
359   if ($line =~ m/^([^=]+)=($RE{num}{real})([^;]*)/)
360   {
361     $tinst = sanitize_instance ($1);
362     $value = scale_value ($2, $3);
363   }
364   else
365   {
366     return;
367   }
369   print "PUTVAL $host/$plugin-$pinst/$type-$tinst interval=$Interval ${time}:$value\n";
372 sub execute_script
374   my $fh;
375   my $pinst;
376   my $time = time ();
377   my $script = shift;
378   my @args = ();
379   my $host = hostname () || 'localhost';
381   my $state = 0;
382   my $serviceoutput;
383   my @serviceperfdata;
384   my @longserviceoutput;
386   my $script_name = $script->{'script'};
387   
388   if ($script->{'arguments'})
389   {
390     @args = split (' ', $script->{'arguments'});
391   }
393   if (!open ($fh, '-|', $script_name, @args))
394   {
395     print STDERR "Cannot execute $script_name: $!";
396     return;
397   }
399   $pinst = sanitize_instance (basename ($script_name));
401   # Parse the output of the plugin. The format is seriously fucked up, because
402   # it got extended way beyond what it could handle.
403   while (my $line = <$fh>)
404   {
405     chomp ($line);
407     if ($state == 0)
408     {
409       my $perfdata;
410       ($serviceoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
411       
412       if ($perfdata)
413       {
414         push (@serviceperfdata, split (' ', $perfdata));
415       }
417       $state = 1;
418     }
419     elsif ($state == 1)
420     {
421       my $longoutput;
422       my $perfdata;
423       ($longoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
425       push (@longserviceoutput, $longoutput);
427       if ($perfdata)
428       {
429         push (@serviceperfdata, split (' ', $perfdata));
430         $state = 2;
431       }
432     }
433     else # ($state == 2)
434     {
435       push (@serviceperfdata, split (' ', $line));
436     }
437   }
439   close ($fh);
440   # Save the exit status of the check in $state
441   $state = $?;
443   if ($state == 0)
444   {
445     $state = 'okay';
446   }
447   elsif ($state == 1)
448   {
449     $state = 'warning';
450   }
451   else
452   {
453     $state = 'failure';
454   }
456   {
457     my $type = $script->{'type'} || 'nagios_check';
459     print "PUTNOTIF time=$time severity=$state host=$host plugin=nagios "
460     . "plugin_instance=$pinst type=$type message=$serviceoutput\n";
461   }
463   if ($script->{'type'})
464   {
465     for (@serviceperfdata)
466     {
467       handle_performance_data ($host, 'nagios', $pinst, $script->{'type'},
468         $time, $_);
469     }
470   }
471 } # execute_script
473 sub main
475   my $last_run;
476   my $next_run;
478   my %config = ParseConfig (-ConfigFile => $ConfigFile,
479     -AutoTrue => 1,
480     -LowerCaseNames => 1);
481   handle_config (\%config);
483   while (42)
484   {
485     $last_run = time ();
486     $next_run = $last_run + $Interval;
488     for (@$Scripts)
489     {
490       execute_script ($_);
491     }
493     while ((my $timeleft = ($next_run - time ())) > 0)
494     {
495       sleep ($timeleft);
496     }
497   }
498 } # main
500 =head1 REQUIREMENTS
502 This script requires the following Perl modules to be installed:
504 =over 4
506 =item C<Config::General>
508 =item C<Regexp::Common>
510 =back
512 =head1 SEE ALSO
514 L<http://www.nagios.org/>,
515 L<http://nagiosplugins.org/>,
516 L<http://collectd.org/>,
517 L<collectd-exec(5)>
519 =head1 AUTHOR
521 Florian octo Forster E<lt>octo at verplant.orgE<gt>
523 =cut
525 # vim: set sw=2 sts=2 ts=8 fdm=marker :