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
110 {
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
147 {
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
192 {
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);
211 }
213 sub sanitize_instance
214 {
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);
228 }
230 sub handle_performance_data
231 {
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";
254 }
256 sub execute_script
257 {
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'};
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);
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
358 {
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 :