Code

Merge branch 'collectd-4.10' into collectd-5.0
[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 = defined ($ENV{'COLLECTD_INTERVAL'}) ? (0 + $ENV{'COLLECTD_INTERVAL'}) : 300;
28 our $Hostname = defined ($ENV{'COLLECTD_HOSTNAME'}) ? $ENV{'COLLECTD_HOSTNAME'} : '';
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   Interval 300
45   <Script /usr/lib/nagios/check_tcp>
46     Arguments -H alice -p 22
47     Type delay
48   </Script>
49   <Script /usr/lib/nagios/check_dns>
50     Arguments -H alice
51     Type delay
52   </Script>
54 The options have the following semantic (i.E<nbsp>e. meaning):
56 =over 4
58 =item B<Interval> I<Seconds>
60 Sets the interval in which the plugins are executed. This doesn't need to match
61 the interval setting of the collectd daemon. Usually, you want to execute the
62 Nagios plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
63 seconds.
65 =item E<lt>B<Script> I<File>E<gt>
67 Adds a script to the list of scripts to be executed once per I<Interval>
68 seconds. You can use the following optional arguments to specify the operation
69 further:
71 =over 4
73 =item B<Arguments> I<Arguments>
75 Pass the arguments I<Arguments> to the script. This is often needed with Nagios
76 plugins, because much of the logic is implemented in the plugins, not in the
77 daemon. If you need to specify a warning and/or critical range here, please
78 consider using collectd's own threshold mechanism, which is by far the more
79 elegant solution than this transition layer.
81 =item B<Type> I<Type>
83 If the plugin provides "performance data" the performance data is dispatched to
84 collectd with this type. If no type is configured the data is ignored. Please
85 note that this is limited to types that take exactly one value, such as the
86 type C<delay> in the example above. If you need more complex performance data,
87 rewrite the plugin as a collectd plugin (or at least port it do run directly
88 with the C<exec-plugin>).
90 =back
92 =back
94 =cut
96 sub handle_config_addtype
97 {
98   my $list = shift;
100   for (my $i = 0; $i < @$list; $i++)
101   {
102     my ($to, @from) = split (' ', $list->[$i]);
103     for (my $j = 0; $j < @from; $j++)
104     {
105       $TypeMap->{$from[$j]} = $to;
106     }
107   }
108 } # handle_config_addtype
110 sub handle_config_script
112   my $scripts = shift;
114   for (keys %$scripts)
115   {
116     my $script = $_;
117     my $opts = $scripts->{$script};
119     if (!-e $script)
120     {
121       print STDERR "Script `$script' doesn't exist.\n";
122     }
123     elsif (!-x $script)
124     {
125       print STDERR "Script `$script' exists but is not executable.\n";
126     }
127     else
128     {
129       if (ref ($opts) eq 'ARRAY')
130         {
131           for (@$opts)
132             {
133               my $opt = $_;
134               $opt->{'script'} = $script;
135               push (@$Scripts, $opt);
136             }
137         }
138           else
139         {
140           $opts->{'script'} = $script;
141           push (@$Scripts, $opts);
142         }
143     }
144   } # for (keys %$scripts)
145 } # handle_config_script
147 sub handle_config
149   my $config = shift;
151   if (defined ($config->{'addtype'}))
152   {
153     if (ref ($config->{'addtype'}) eq 'ARRAY')
154     {
155       handle_config_addtype ($config->{'addtype'});
156     }
157     elsif (ref ($config->{'addtype'}) eq '')
158     {
159       handle_config_addtype ([$config->{'addtype'}]);
160     }
161     else
162     {
163       print STDERR "Cannot handle ref type '"
164       . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
165     }
166   }
168   if (defined ($config->{'script'}))
169   {
170     if (ref ($config->{'script'}) eq 'HASH')
171     {
172       handle_config_script ($config->{'script'});
173     }
174     else
175     {
176       print STDERR "Cannot handle ref type '"
177       . ref ($config->{'script'}) . "' for option 'Script'.\n";
178     }
179   }
181   if (defined ($config->{'interval'})
182     && (ref ($config->{'interval'}) eq ''))
183   {
184     my $num = int ($config->{'interval'});
185     if ($num > 0)
186     {
187       $Interval = $num;
188     }
189   }
190 } # handle_config }}}
192 sub scale_value
194   my $value = shift;
195   my $unit = shift;
197   if (!$unit)
198   {
199     return ($value);
200   }
202   if (($unit =~ m/^mb(yte)?$/i) || ($unit eq 'M'))
203   {
204     return ($value * 1000000);
205   }
206   elsif ($unit =~ m/^k(b(yte)?)?$/i)
207   {
208     return ($value * 1000);
209   }
211   return ($value);
214 sub sanitize_instance
216   my $inst = shift;
218   if ($inst eq '/')
219   {
220     return ('root');
221   }
223   $inst =~ s/[^A-Za-z_-]/_/g;
224   $inst =~ s/__+/_/g;
225   $inst =~ s/^_//;
226   $inst =~ s/_$//;
228   return ($inst);
231 sub handle_performance_data
233   my $host = shift;
234   my $plugin = shift;
235   my $pinst = shift;
236   my $type = shift;
237   my $time = shift;
238   my $line = shift;
239   my $ident = "$host/$plugin-$pinst/$type-$tinst";
241   my $tinst;
242   my $value;
243   my $unit;
245   if ($line =~ m/^([^=]+)=($RE{num}{real})([^;]*)/)
246   {
247     $tinst = sanitize_instance ($1);
248     $value = scale_value ($2, $3);
249   }
250   else
251   {
252     return;
253   }
255   $ident =~ s/"/\\"/g;
257   print qq(PUTVAL "$ident" interval=$Interval ${time}:$value\n);
260 sub execute_script
262   my $fh;
263   my $pinst;
264   my $time = time ();
265   my $script = shift;
266   my @args = ();
267   my $host = $Hostname || hostname () || 'localhost';
269   my $state = 0;
270   my $serviceoutput;
271   my @serviceperfdata;
272   my @longserviceoutput;
274   my $script_name = $script->{'script'};
275   
276   if ($script->{'arguments'})
277   {
278     @args = split (' ', $script->{'arguments'});
279   }
281   if (!open ($fh, '-|', $script_name, @args))
282   {
283     print STDERR "Cannot execute $script_name: $!";
284     return;
285   }
287   $pinst = sanitize_instance (basename ($script_name));
289   # Parse the output of the plugin. The format is seriously fucked up, because
290   # it got extended way beyond what it could handle.
291   while (my $line = <$fh>)
292   {
293     chomp ($line);
295     if ($state == 0)
296     {
297       my $perfdata;
298       ($serviceoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
299       
300       if ($perfdata)
301       {
302         push (@serviceperfdata, split (' ', $perfdata));
303       }
305       $state = 1;
306     }
307     elsif ($state == 1)
308     {
309       my $longoutput;
310       my $perfdata;
311       ($longoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
313       push (@longserviceoutput, $longoutput);
315       if ($perfdata)
316       {
317         push (@serviceperfdata, split (' ', $perfdata));
318         $state = 2;
319       }
320     }
321     else # ($state == 2)
322     {
323       push (@serviceperfdata, split (' ', $line));
324     }
325   }
327   close ($fh);
328   # Save the exit status of the check in $state
329   $state = $?;
331   if ($state == 0)
332   {
333     $state = 'okay';
334   }
335   elsif ($state == 1)
336   {
337     $state = 'warning';
338   }
339   else
340   {
341     $state = 'failure';
342   }
344   {
345     my $type = $script->{'type'} || 'nagios_check';
347     print "PUTNOTIF time=$time severity=$state host=$host plugin=nagios "
348     . "plugin_instance=$pinst type=$type message=$serviceoutput\n";
349   }
351   if ($script->{'type'})
352   {
353     for (@serviceperfdata)
354     {
355       handle_performance_data ($host, 'nagios', $pinst, $script->{'type'},
356         $time, $_);
357     }
358   }
359 } # execute_script
361 sub main
363   my $last_run;
364   my $next_run;
366   my %config = ParseConfig (-ConfigFile => $ConfigFile,
367     -AutoTrue => 1,
368     -LowerCaseNames => 1);
369   handle_config (\%config);
371   while (42)
372   {
373     $last_run = time ();
374     $next_run = $last_run + $Interval;
376     for (@$Scripts)
377     {
378       execute_script ($_);
379     }
381     while ((my $timeleft = ($next_run - time ())) > 0)
382     {
383       sleep ($timeleft);
384     }
385   }
386 } # main
388 =head1 REQUIREMENTS
390 This script requires the following Perl modules to be installed:
392 =over 4
394 =item C<Config::General>
396 =item C<Regexp::Common>
398 =back
400 =head1 SEE ALSO
402 L<http://www.nagios.org/>,
403 L<http://nagiosplugins.org/>,
404 L<http://collectd.org/>,
405 L<collectd-exec(5)>
407 =head1 AUTHOR
409 Florian octo Forster E<lt>octo at verplant.orgE<gt>
411 =cut
413 # vim: set sw=2 sts=2 ts=8 fdm=marker :