summaryrefslogtreecommitdiffstats
path: root/lib/Monitoring/Plugin
diff options
context:
space:
mode:
authorSven Nierlein <sven@nierlein.de>2014-01-19 23:54:34 (GMT)
committerSven Nierlein <sven@nierlein.de>2014-01-19 23:54:34 (GMT)
commitb418181dfe80dd75169b6e8a619ac1932155dea2 (patch)
treecad9c0ae0eae8e800cfff60555ead06ad33c6856 /lib/Monitoring/Plugin
parent1cd8d1c52cbd47121f344c4074aec84653f412ce (diff)
downloadmonitoring-plugin-perl-b418181dfe80dd75169b6e8a619ac1932155dea2.tar.gz
renamed module into Monitoring::Plugin
since the complete monitoring team has been renamed, we also rename this module. Signed-off-by: Sven Nierlein <sven@nierlein.de>
Diffstat (limited to 'lib/Monitoring/Plugin')
-rw-r--r--lib/Monitoring/Plugin/Config.pm177
-rw-r--r--lib/Monitoring/Plugin/ExitResult.pm71
-rw-r--r--lib/Monitoring/Plugin/Functions.pm445
-rw-r--r--lib/Monitoring/Plugin/Getopt.pm869
-rw-r--r--lib/Monitoring/Plugin/Performance.pm294
-rw-r--r--lib/Monitoring/Plugin/Range.pm169
-rw-r--r--lib/Monitoring/Plugin/Threshold.pm134
7 files changed, 2159 insertions, 0 deletions
diff --git a/lib/Monitoring/Plugin/Config.pm b/lib/Monitoring/Plugin/Config.pm
new file mode 100644
index 0000000..5e941d4
--- /dev/null
+++ b/lib/Monitoring/Plugin/Config.pm
@@ -0,0 +1,177 @@
1package Monitoring::Plugin::Config;
2
3use strict;
4use Carp;
5use File::Spec;
6use base qw(Config::Tiny);
7
8my $FILENAME1 = 'plugins.ini';
9my $FILENAME2 = 'nagios-plugins.ini';
10my $FILENAME3 = 'monitoring-plugins.ini';
11my $CURRENT_FILE = undef;
12
13# Config paths ending in nagios (search for $FILENAME1)
14my @MONITORING_CONFIG_PATH = qw(/etc/nagios /usr/local/nagios/etc /usr/local/etc/nagios /etc/opt/nagios);
15# Config paths not ending in nagios (search for $FILENAME2)
16my @CONFIG_PATH = qw(/etc /usr/local/etc /etc/opt);
17
18# Override Config::Tiny::read to default the filename, if not given
19sub read
20{
21 my $class = shift;
22
23 unless ($_[0]) {
24 SEARCH: {
25 if ($ENV{MONITORING_CONFIG_PATH} || $ENV{NAGIOS_CONFIG_PATH}) {
26 for (split /:/, ($ENV{MONITORING_CONFIG_PATH} || $ENV{NAGIOS_CONFIG_PATH})) {
27 my $file = File::Spec->catfile($_, $FILENAME1);
28 unshift(@_, $file), last SEARCH if -f $file;
29 $file = File::Spec->catfile($_, $FILENAME2);
30 unshift(@_, $file), last SEARCH if -f $file;
31 $file = File::Spec->catfile($_, $FILENAME3);
32 unshift(@_, $file), last SEARCH if -f $file;
33 }
34 }
35 for (@MONITORING_CONFIG_PATH) {
36 my $file = File::Spec->catfile($_, $FILENAME1);
37 unshift(@_, $file), last SEARCH if -f $file;
38 }
39 for (@CONFIG_PATH) {
40 my $file = File::Spec->catfile($_, $FILENAME2);
41 unshift(@_, $file), last SEARCH if -f $file;
42 $file = File::Spec->catfile($_, $FILENAME3);
43 unshift(@_, $file), last SEARCH if -f $file;
44 }
45 }
46
47 # Use die instead of croak, so we can pass a clean message downstream
48 die "Cannot find '$FILENAME1', '$FILENAME2' or '$FILENAME3' in any standard location.\n" unless $_[0];
49 }
50
51 $CURRENT_FILE = $_[0];
52 $class->SUPER::read( @_ );
53}
54
55# Straight from Config::Tiny - only changes are repeated property key support
56# Would be nice if we could just override the per-line handling ...
57sub read_string
58{
59 my $class = ref $_[0] ? ref shift : shift;
60 my $self = bless {}, $class;
61 return undef unless defined $_[0];
62
63 # Parse the file
64 my $ns = '_';
65 my $counter = 0;
66 foreach ( split /(?:\015{1,2}\012|\015|\012)/, shift ) {
67 $counter++;
68
69 # Skip comments and empty lines
70 next if /^\s*(?:\#|\;|$)/;
71
72 # Handle section headers
73 if ( /^\s*\[\s*(.+?)\s*\]\s*$/ ) {
74 # Create the sub-hash if it doesn't exist.
75 # Without this sections without keys will not
76 # appear at all in the completed struct.
77 $self->{$ns = $1} ||= {};
78 next;
79 }
80
81 # Handle properties
82 if ( /^\s*([^=]+?)\s*=\s*(.*?)\s*$/ ) {
83 push @{$self->{$ns}->{$1}}, $2;
84 next;
85 }
86
87 return $self->_error( "Syntax error at line $counter: '$_'" );
88 }
89
90 $self;
91}
92
93sub write { croak "Write access not permitted" }
94
95# Return last file used by read();
96sub np_getfile { return $CURRENT_FILE; }
97
981;
99
100=head1 NAME
101
102Monitoring::Plugin::Config - read nagios plugin .ini style config files
103
104=head1 SYNOPSIS
105
106 # Read given nagios plugin config file
107 $Config = Monitoring::Plugin::Config->read( '/etc/nagios/plugins.ini' );
108
109 # Search for and read default nagios plugin config file
110 $Config = Monitoring::Plugin::Config->read();
111
112 # Access sections and properties (returns scalars or arrayrefs)
113 $rootproperty = $Config->{_}->{rootproperty};
114 $one = $Config->{section}->{one};
115 $Foo = $Config->{section}->{Foo};
116
117=head1 DESCRIPTION
118
119Monitoring::Plugin::Config is a subclass of the excellent Config::Tiny,
120with the following changes:
121
122=over 4
123
124=item
125
126Repeated keys are allowed within sections, returning lists instead of scalars
127
128=item
129
130Write functionality has been removed i.e. access is read only
131
132=item
133
134Monitoring::Plugin::Config searches for a default nagios plugins file if no explicit
135filename is given to C<read()>. The current standard locations checked are:
136
137=over 4
138
139=item /etc/nagios/plugins.ini
140
141=item /usr/local/nagios/etc/plugins.ini
142
143=item /usr/local/etc/nagios /etc/opt/nagios/plugins.ini
144
145=item /etc/nagios-plugins.ini
146
147=item /usr/local/etc/nagios-plugins.ini
148
149=item /etc/opt/nagios-plugins.ini
150
151=back
152
153To use a custom location, set a C<NAGIOS_CONFIG_PATH> environment variable
154to the set of directories that should be checked. The first C<plugins.ini> or
155C<nagios-plugins.ini> file found will be used.
156
157=back
158
159
160=head1 SEE ALSO
161
162L<Config::Tiny>, L<Monitoring::Plugin>
163
164
165=head1 AUTHOR
166
167This code is maintained by the Monitoring Plugin Development Team: see
168https://monitoring-plugins.org
169
170=head1 COPYRIGHT AND LICENSE
171
172Copyright (C) 2006-2014 Monitoring Plugin Development Team
173
174This library is free software; you can redistribute it and/or modify
175it under the same terms as Perl itself.
176
177=cut
diff --git a/lib/Monitoring/Plugin/ExitResult.pm b/lib/Monitoring/Plugin/ExitResult.pm
new file mode 100644
index 0000000..aa9f5da
--- /dev/null
+++ b/lib/Monitoring/Plugin/ExitResult.pm
@@ -0,0 +1,71 @@
1# Tiny helper class to return both output and return_code when testing
2
3package Monitoring::Plugin::ExitResult;
4
5use strict;
6
7# Stringify to message
8use overload '""' => sub { shift->{message} };
9
10# Constructor
11sub new {
12 my $class = shift;
13 return bless { return_code => $_[0], message => $_[1] }, $class;
14}
15
16# Accessors
17sub message { shift->{message} }
18sub return_code { shift->{return_code} }
19sub code { shift->{return_code} }
20
211;
22
23__END__
24
25=head1 NAME
26
27Monitoring::Plugin::ExitResult - Helper class for returning both output and
28return codes when testing.
29
30=head1 SYNOPSIS
31
32 use Test::More;
33 use Monitoring::Plugin::Functions;
34
35 # In a test file somewhere
36 Monitoring::Plugin::Functions::_fake_exit(1);
37
38 # Later ...
39 $e = plugin_exit( CRITICAL, 'aiiii ...' );
40 print $e->message;
41 print $e->return_code;
42
43 # MP::ExitResult also stringifies to the message output
44 like(plugin_exit( WARNING, 'foobar'), qr/^foo/, 'matches!');
45
46
47
48=head1 DESCRIPTION
49
50Monitoring::Plugin::ExitResult is a tiny helper class intended for use
51when testing other Monitoring::Plugin modules. A Monitoring::Plugin::ExitResult
52object is returned by plugin_exit() and friends when
53Monitoring::Plugin::Functions::_fake_exit has been set, instead of doing a
54conventional print + exit.
55
56=head1 AUTHOR
57
58This code is maintained by the Monitoring Plugin Development Team: see
59https://monitoring-plugins.org
60
61Originally:
62 Gavin Carr , E<lt>gavin@openfusion.com.auE<gt>
63
64=head1 COPYRIGHT AND LICENSE
65
66Copyright (C) 2006-2014 Monitoring Plugin Development Team
67
68This library is free software; you can redistribute it and/or modify
69it under the same terms as Perl itself.
70
71=cut
diff --git a/lib/Monitoring/Plugin/Functions.pm b/lib/Monitoring/Plugin/Functions.pm
new file mode 100644
index 0000000..d2856e8
--- /dev/null
+++ b/lib/Monitoring/Plugin/Functions.pm
@@ -0,0 +1,445 @@
1# Functional interface to basic Monitoring::Plugin constants, exports,
2# and functions
3
4package Monitoring::Plugin::Functions;
5
6use 5.006;
7
8use strict;
9use warnings;
10use File::Basename;
11use Params::Validate qw(:types validate);
12use Math::Calc::Units;
13
14# Remember to update Monitoring::Plugins as well
15our $VERSION = "0.37";
16
17our @STATUS_CODES = qw(OK WARNING CRITICAL UNKNOWN DEPENDENT);
18
19require Exporter;
20our @ISA = qw(Exporter);
21our @EXPORT = (@STATUS_CODES, qw(plugin_exit plugin_die check_messages));
22our @EXPORT_OK = qw(%ERRORS %STATUS_TEXT @STATUS_CODES get_shortname max_state max_state_alt convert $value_re);
23our %EXPORT_TAGS = (
24 all => [ @EXPORT, @EXPORT_OK ],
25 codes => [ @STATUS_CODES ],
26 functions => [ qw(plugin_exit plugin_die check_messages max_state max_state_alt convert) ],
27);
28
29use constant OK => 0;
30use constant WARNING => 1;
31use constant CRITICAL => 2;
32use constant UNKNOWN => 3;
33use constant DEPENDENT => 4;
34
35our %ERRORS = (
36 'OK' => OK,
37 'WARNING' => WARNING,
38 'CRITICAL' => CRITICAL,
39 'UNKNOWN' => UNKNOWN,
40 'DEPENDENT' => DEPENDENT,
41);
42
43our %STATUS_TEXT = reverse %ERRORS;
44
45my $value = qr/[-+]?[\d\.]+/;
46our $value_re = qr/$value(?:e$value)?/;
47
48# _fake_exit flag and accessor/mutator, for testing
49my $_fake_exit = 0;
50sub _fake_exit { @_ ? $_fake_exit = shift : $_fake_exit };
51
52# _use_die flag and accessor/mutator, so exceptions can be raised correctly
53my $_use_die = 0;
54sub _use_die { @_ ? $_use_die = shift : $_use_die };
55
56sub get_shortname {
57 my $arg = shift;
58
59 my $shortname = undef;
60
61 return $arg->{shortname} if (defined($arg->{shortname}));
62 $shortname = $arg->{plugin} if (defined( $arg->{plugin}));
63
64 $shortname = uc basename($shortname || $ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0);
65 $shortname =~ s/^CHECK_(?:BY_)?//; # Remove any leading CHECK_[BY_]
66 $shortname =~ s/\..*$//; # Remove any trailing suffix
67 return $shortname;
68}
69
70sub max_state {
71 return CRITICAL if grep { $_ == CRITICAL } @_;
72 return WARNING if grep { $_ == WARNING } @_;
73 return OK if grep { $_ == OK } @_;
74 return UNKNOWN if grep { $_ == UNKNOWN } @_;
75 return DEPENDENT if grep { $_ == DEPENDENT } @_;
76 return UNKNOWN;
77}
78
79sub max_state_alt {
80 return CRITICAL if grep { $_ == CRITICAL } @_;
81 return WARNING if grep { $_ == WARNING } @_;
82 return UNKNOWN if grep { $_ == UNKNOWN } @_;
83 return DEPENDENT if grep { $_ == DEPENDENT } @_;
84 return OK if grep { $_ == OK } @_;
85 return UNKNOWN;
86}
87
88# plugin_exit( $code, $message )
89sub plugin_exit {
90 my ($code, $message, $arg) = @_;
91
92 # Handle named parameters
93 if (defined $code && ($code eq 'return_code' || $code eq 'message')) {
94 # Remove last argument if odd no and last is ref
95 if (int(@_ / 2) != @_ / 2 && ref $_[$#_]) {
96 $arg = pop @_;
97 } else {
98 undef $arg;
99 }
100 my %arg = @_;
101 $code = $arg{return_code};
102 $message = $arg{message};
103 }
104 $arg ||= {};
105
106 # Handle string codes
107 $code = $ERRORS{$code} if defined $code && exists $ERRORS{$code};
108
109 # Set defaults
110 $code = UNKNOWN unless defined $code && exists $STATUS_TEXT{$code};
111 $message = '' unless defined $message;
112 if (ref $message && ref $message eq 'ARRAY') {
113 $message = join(' ', map { chomp; $_ } @$message);
114 }
115 else {
116 chomp $message;
117 }
118
119 # Setup output
120 my $output = "$STATUS_TEXT{$code}";
121 $output .= " - $message" if defined $message && $message ne '';
122 my $shortname = ($arg->{plugin} ? $arg->{plugin}->shortname : undef);
123 $shortname ||= get_shortname(); # Should happen only if funnctions are called directly
124 $output = "$shortname $output" if $shortname;
125 if ($arg->{plugin}) {
126 my $plugin = $arg->{plugin};
127 $output .= " | ". $plugin->all_perfoutput
128 if $plugin->perfdata && $plugin->all_perfoutput;
129 }
130 $output .= "\n";
131
132 # Don't actually exit if _fake_exit set
133 if ($_fake_exit) {
134 require Monitoring::Plugin::ExitResult;
135 return Monitoring::Plugin::ExitResult->new($code, $output);
136 }
137
138 _plugin_exit($code, $output);
139}
140
141sub _plugin_exit {
142 my ($code, $output) = @_;
143 # Print output and exit; die if flag set and called via a die in stack backtrace
144 if ($_use_die) {
145 for (my $i = 0;; $i++) {
146 @_ = caller($i);
147 last unless @_;
148 if ($_[3] =~ m/die/) {
149 $! = $code;
150 die($output);
151 }
152 }
153 }
154 print $output;
155 exit $code;
156}
157
158# plugin_die( $message, [ $code ]) OR plugin_die( $code, $message )
159# Default $code: UNKNOWN
160sub plugin_die {
161 my ($arg1, $arg2, $rest) = @_;
162
163 # Named parameters
164 if (defined $arg1 && ($arg1 eq 'return_code' || $arg1 eq 'message')) {
165 return plugin_exit(@_);
166 }
167
168 # ($code, $message)
169 elsif (defined $arg1 && (exists $ERRORS{$arg1} || exists $STATUS_TEXT{$arg1})) {
170 return plugin_exit(@_);
171 }
172
173 # ($message, $code)
174 elsif (defined $arg2 && (exists $ERRORS{$arg2} || exists $STATUS_TEXT{$arg2})) {
175 return plugin_exit($arg2, $arg1, $rest);
176 }
177
178 # Else just assume $arg1 is the message and hope for the best
179 else {
180 return plugin_exit( UNKNOWN, $arg1, $arg2 );
181 }
182}
183
184# For backwards compatibility
185sub die { plugin_die(@_); }
186
187
188# ------------------------------------------------------------------------
189# Utility functions
190
191# Simple wrapper around Math::Calc::Units::convert
192sub convert
193{
194 my ($value, $from, $to) = @_;
195 my ($newval) = Math::Calc::Units::convert("$value $from", $to, 'exact');
196 return $newval;
197}
198
199# ------------------------------------------------------------------------
200# check_messages - return a status and/or message based on a set of
201# message arrays.
202# Returns a nagios status code in scalar context.
203# Returns a code and a message in list context.
204# The message is join($join, @array) for the relevant array for the code,
205# or join($join_all, $message) for all arrays if $join_all is set.
206sub check_messages {
207 my %arg = validate( @_, {
208 critical => { type => ARRAYREF },
209 warning => { type => ARRAYREF },
210 ok => { type => ARRAYREF | SCALAR, optional => 1 },
211 'join' => { default => ' ' },
212 join_all => 0,
213 });
214 $arg{join} = ' ' unless defined $arg{join};
215
216 # Decide $code
217 my $code = OK;
218 $code ||= CRITICAL if @{$arg{critical}};
219 $code ||= WARNING if @{$arg{warning}};
220 return $code unless wantarray;
221
222 # Compose message
223 my $message = '';
224 if ($arg{join_all}) {
225 $message = join( $arg{join_all},
226 map { @$_ ? join( $arg{'join'}, @$_) : () }
227 $arg{critical},
228 $arg{warning},
229 $arg{ok} ? (ref $arg{ok} ? $arg{ok} : [ $arg{ok} ]) : []
230 );
231 }
232
233 else {
234 $message ||= join( $arg{'join'}, @{$arg{critical}} )
235 if $code == CRITICAL;
236 $message ||= join( $arg{'join'}, @{$arg{warning}} )
237 if $code == WARNING;
238 $message ||= ref $arg{ok} ? join( $arg{'join'}, @{$arg{ok}} ) : $arg{ok}
239 if $arg{ok};
240 }
241
242 return ($code, $message);
243}
244
245# ------------------------------------------------------------------------
246
2471;
248
249# vim:sw=4:sm:et
250
251__END__
252
253=head1 NAME
254
255Monitoring::Plugin::Functions - functions to simplify the creation of
256Nagios plugins
257
258=head1 SYNOPSIS
259
260 # Constants OK, WARNING, CRITICAL, and UNKNOWN exported by default
261 use Monitoring::Plugin::Functions;
262
263 # plugin_exit( CODE, $message ) - exit with error code CODE,
264 # and message "PLUGIN CODE - $message"
265 plugin_exit( CRITICAL, $critical_error ) if $critical_error;
266 plugin_exit( WARNING, $warning_error ) if $warning_error;
267 plugin_exit( OK, $result );
268
269 # plugin_die( $message, [$CODE] ) - just like plugin_exit(),
270 # but CODE is optional, defaulting to UNKNOWN
271 do_something()
272 or plugin_die("do_something() failed horribly");
273 do_something_critical()
274 or plugin_die("do_something_critical() failed", CRITICAL);
275
276 # check_messages - check a set of message arrays, returning a
277 # CODE and/or a result message
278 $code = check_messages(critical => \@crit, warning => \@warn);
279 ($code, $message) = check_messages(
280 critical => \@crit, warning => \@warn,
281 ok => \@ok );
282
283 # get_shortname - return the default short name for this plugin
284 # (as used by plugin_exit/die; not exported by default)
285 $shortname = get_shortname();
286
287
288=head1 DESCRIPTION
289
290This module is part of the Monitoring::Plugin family, a set of modules
291for simplifying the creation of Nagios plugins. This module exports
292convenience functions for the class methods provided by
293Monitoring::Plugin. It is intended for those who prefer a simpler
294functional interface, and who do not need the additional
295functionality of Monitoring::Plugin.
296
297=head2 EXPORTS
298
299Nagios status code constants are exported by default:
300
301 OK
302 WARNING
303 CRITICAL
304 UNKNOWN
305 DEPENDENT
306
307as are the following functions:
308
309 plugin_exit
310 plugin_die
311 check_messages
312
313The following variables and functions are exported only on request:
314
315 %ERRORS
316 %STATUS_TEXT
317 get_shortname
318 max_state
319 max_state_alt
320
321
322=head2 FUNCTIONS
323
324The following functions are supported:
325
326=over 4
327
328=item plugin_exit( <CODE>, $message )
329
330Exit with return code CODE, and a standard nagios message of the
331form "PLUGIN CODE - $message".
332
333=item plugin_die( $message, [CODE] )
334
335Same as plugin_exit(), except that CODE is optional, defaulting
336to UNKNOWN. NOTE: exceptions are not raised by default to calling code.
337Set C<$_use_die> flag if this functionality is required (see test code).
338
339=item check_messages( critical => \@crit, warning => \@warn )
340
341Convenience function to check a set of message arrays and return
342an appropriate nagios return code and/or a result message. Returns
343only a return code in scalar context; returns a return code and an
344error message in list context i.e.
345
346 # Scalar context
347 $code = check_messages(critical => \@crit, warning => \@warn);
348 # List context
349 ($code, $msg) = check_messages(critical => \@crit, warning => \@warn);
350
351check_messages() accepts the following named arguments:
352
353=over 4
354
355=item critical => ARRAYREF
356
357An arrayref of critical error messages - check_messages() returns
358CRITICAL if this arrayref is non-empty. Mandatory.
359
360=item warning => ARRAYREF
361
362An arrayref of warning error messages - check_messages() returns
363WARNING if this arrayref is non-empty ('critical' is checked
364first). Mandatory.
365
366=item ok => ARRAYREF | SCALAR
367
368An arrayref of informational messages (or a single scalar message),
369used in list context if both the 'critical' and 'warning' arrayrefs
370are empty. Optional.
371
372=item join => SCALAR
373
374A string used to join the relevant array to generate the message
375string returned in list context i.e. if the 'critical' array @crit
376is non-empty, check_messages would return:
377
378 join( $join, @crit )
379
380as the result message. Optional; default: ' ' (space).
381
382=item join_all => SCALAR
383
384By default, only one set of messages are joined and returned in the
385result message i.e. if the result is CRITICAL, only the 'critical'
386messages are included in the result; if WARNING, only the 'warning'
387messages are included; if OK, the 'ok' messages are included (if
388supplied) i.e. the default is to return an 'errors-only' type
389message.
390
391If join_all is supplied, however, it will be used as a string to
392join the resultant critical, warning, and ok messages together i.e.
393all messages are joined and returned.
394
395=back
396
397=item get_shortname
398
399Return the default shortname used for this plugin i.e. the first
400token reported by plugin_exit/plugin_die. The default is basically
401
402 uc basename( $ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0 )
403
404with any leading 'CHECK_' and trailing file suffixes removed.
405
406get_shortname is not exported by default, so must be explicitly
407imported.
408
409=item max_state(@a)
410
411Returns the worst state in the array. Order is: CRITICAL, WARNING, OK, UNKNOWN,
412DEPENDENT
413
414The typical usage of max_state is to initialise the state as UNKNOWN and use
415it on the result of various test. If no test were performed successfully the
416state will still be UNKNOWN.
417
418=item max_state_alt(@a)
419
420Returns the worst state in the array. Order is: CRITICAL, WARNING, UNKNOWN,
421DEPENDENT, OK
422
423This is a true definition of a max state (OK last) and should be used if the
424internal tests performed can return UNKNOWN.
425
426=back
427
428=head1 SEE ALSO
429
430Monitoring::Plugin; the nagios plugin developer guidelines at
431https://www.monitoring-plugins.org/doc/guidelines.html.
432
433=head1 AUTHOR
434
435This code is maintained by the Monitoring Plugin Development Team: see
436https://monitoring-plugins.org
437
438=head1 COPYRIGHT AND LICENSE
439
440Copyright (C) 2006-2014 Monitoring Plugin Development Team
441
442This library is free software; you can redistribute it and/or modify
443it under the same terms as Perl itself.
444
445=cut
diff --git a/lib/Monitoring/Plugin/Getopt.pm b/lib/Monitoring/Plugin/Getopt.pm
new file mode 100644
index 0000000..ce1c0f9
--- /dev/null
+++ b/lib/Monitoring/Plugin/Getopt.pm
@@ -0,0 +1,869 @@
1#
2# Monitoring::Plugin::Getopt - OO perl module providing standardised argument
3# processing for nagios plugins
4#
5
6package Monitoring::Plugin::Getopt;
7
8use strict;
9use File::Basename;
10use Getopt::Long qw(:config no_ignore_case bundling);
11use Carp;
12use Params::Validate qw(:all);
13use base qw(Class::Accessor);
14
15use Monitoring::Plugin::Functions;
16use Monitoring::Plugin::Config;
17use vars qw($VERSION);
18$VERSION = $Monitoring::Plugin::Functions::VERSION;
19
20# Standard defaults
21my %DEFAULT = (
22 timeout => 15,
23 verbose => 0,
24 license =>
25"This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
26It may be used, redistributed and/or modified under the terms of the GNU
27General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).",
28);
29# Standard arguments
30my @ARGS = ({
31 spec => 'usage|?',
32 help => "-?, --usage\n Print usage information",
33 }, {
34 spec => 'help|h',
35 help => "-h, --help\n Print detailed help screen",
36 }, {
37 spec => 'version|V',
38 help => "-V, --version\n Print version information",
39 }, {
40 spec => 'extra-opts:s@',
41 help => "--extra-opts=[section][\@file]\n Read options from an ini file. See http://nagiosplugins.org/extra-opts\n for usage and examples.",
42 }, {
43 spec => 'timeout|t=i',
44 help => "-t, --timeout=INTEGER\n Seconds before plugin times out (default: %s)",
45 default => $DEFAULT{timeout},
46 }, {
47 spec => 'verbose|v+',
48 help => "-v, --verbose\n Show details for command-line debugging (can repeat up to 3 times)",
49 default => $DEFAULT{verbose},
50 },
51);
52# Standard arguments we traditionally display last in the help output
53my %DEFER_ARGS = map { $_ => 1 } qw(timeout verbose);
54
55# -------------------------------------------------------------------------
56# Private methods
57
58sub _die
59{
60 my $self = shift;
61 my ($msg) = @_;
62 $msg .= "\n" unless substr($msg, -1) eq "\n";
63 Monitoring::Plugin::Functions::_plugin_exit(3, $msg);
64}
65
66# Return the given attribute, if set, including a final newline
67sub _attr
68{
69 my $self = shift;
70 my ($item, $extra) = @_;
71 $extra = '' unless defined $extra;
72 return '' unless $self->{_attr}->{$item};
73 $self->{_attr}->{$item} . "\n" . $extra;
74}
75
76# Turn argument spec into help-style output
77sub _spec_to_help
78{
79 my ($self, $spec, $label) = @_;
80
81 my ($opts, $type) = split /=/, $spec, 2;
82 my (@short, @long);
83 for (split /\|/, $opts) {
84 if (length $_ == 1) {
85 push @short, "-$_";
86 } else {
87 push @long, "--$_";
88 }
89 }
90
91 my $help = join(', ', @short, @long);
92 if ($type) {
93 if ($label) {
94 $help .= '=' . $label;
95 }
96 else {
97 $help .= $type eq 'i' ? '=INTEGER' : '=STRING';
98 }
99 }
100 elsif ($label) {
101 carp "Label specified, but there's no type in spec '$spec'";
102 }
103 $help .= "\n ";
104 return $help;
105}
106
107# Options output for plugin -h
108sub _options
109{
110 my $self = shift;
111
112 my @args = ();
113 my @defer = ();
114 for (@{$self->{_args}}) {
115 if (exists $DEFER_ARGS{$_->{name}}) {
116 push @defer, $_;
117 } else {
118 push @args, $_;
119 }
120 }
121
122 my @options = ();
123 for my $arg (@args, @defer) {
124 my $help_array = ref $arg->{help} && ref $arg->{help} eq 'ARRAY' ? $arg->{help} : [ $arg->{help} ];
125 my $label_array = $arg->{label} && ref $arg->{label} && ref $arg->{label} eq 'ARRAY' ? $arg->{label} : [ $arg->{label} ];
126 my $help_string = '';
127 for (my $i = 0; $i <= $#$help_array; $i++) {
128 my $help = $help_array->[$i];
129 # Add spec arguments to help if not already there
130 if ($help =~ m/^\s*-/) {
131 $help_string .= $help;
132 }
133 else {
134 $help_string .= $self->_spec_to_help($arg->{spec}, $label_array->[$i]) . $help;
135 $help_string .= "\n " if $i < $#$help_array;
136 }
137 }
138
139 # Add help_string to @options
140 if ($help_string =~ m/%s/) {
141 my $default = defined $arg->{default} ? $arg->{default} : '';
142 # We only handle '%s' formats here, so escape everything else
143 $help_string =~ s/%(?!s)/%%/g;
144 push @options, sprintf($help_string, $default, $default, $default, $default);
145 } else {
146 push @options, $help_string;
147 }
148 }
149
150 return ' ' . join("\n ", @options);
151}
152
153# Output for plugin -? (or missing/invalid args)
154sub _usage
155{
156 my $self = shift;
157 sprintf $self->_attr('usage'), $self->{_attr}->{plugin};
158}
159
160# Output for plugin -V
161sub _revision
162{
163 my $self = shift;
164 my $revision = sprintf "%s %s", $self->{_attr}->{plugin}, $self->{_attr}->{version};
165 $revision .= sprintf " [%s]", $self->{_attr}->{url} if $self->{_attr}->{url};
166 $revision .= "\n";
167 $revision;
168}
169
170# Output for plugin -h
171sub _help
172{
173 my $self = shift;
174 my $help = '';
175 $help .= $self->_revision . "\n";
176 $help .= $self->_attr('license', "\n");
177 $help .= $self->_attr('blurb', "\n");
178 $help .= $self->_usage ? $self->_usage . "\n" : '';
179 $help .= $self->_options ? $self->_options . "\n" : '';
180 $help .= $self->_attr('extra', "\n");
181 return $help;
182}
183
184# Return a Getopt::Long-compatible option array from the current set of specs
185sub _process_specs_getopt_long
186{
187 my $self = shift;
188
189 my @opts = ();
190 for my $arg (@{$self->{_args}}) {
191 push @opts, $arg->{spec};
192 # Setup names and defaults
193 my $spec = $arg->{spec};
194 # Use first arg as name (like Getopt::Long does)
195 $spec =~ s/[=:].*$//;
196 my $name = (split /\s*\|\s*/, $spec)[0];
197 $arg->{name} = $name;
198 if (defined $self->{$name}) {
199 $arg->{default} = $self->{$name};
200 } else {
201 $self->{$name} = $arg->{default};
202 }
203 }
204
205 return @opts;
206}
207
208# Check for existence of required arguments
209sub _check_required_opts
210{
211 my $self = shift;
212
213 my @missing = ();
214 for my $arg (@{$self->{_args}}) {
215 if ($arg->{required} && ! defined $self->{$arg->{name}}) {
216 push @missing, $arg->{name};
217 }
218 }
219 if (@missing) {
220 $self->_die($self->_usage . "\n" .
221 join("\n", map { sprintf "Missing argument: %s", $_ } @missing) . "\n");
222 }
223}
224
225# Process and handle any immediate options
226sub _process_opts
227{
228 my $self = shift;
229
230 # Print message and exit for usage, version, help
231 $self->_die($self->_usage) if $self->{usage};
232 $self->_die($self->_revision) if $self->{version};
233 $self->_die($self->_help) if $self->{help};
234}
235
236# -------------------------------------------------------------------------
237# Default opts methods
238
239sub _load_config_section
240{
241 my $self = shift;
242 my ($section, $file, $flags) = @_;
243 $section ||= $self->{_attr}->{plugin};
244
245 my $Config;
246 eval { $Config = Monitoring::Plugin::Config->read($file); };
247 $self->_die($@) if ($@); #TODO: add test?
248
249 # TODO: is this check sane? Does --extra-opts=foo require a [foo] section?
250 ## Nevertheless, if we die as UNKNOWN here we should do the same on default
251 ## file *added eval/_die above*.
252 $file ||= $Config->np_getfile();
253 $self->_die("Invalid section '$section' in config file '$file'")
254 unless exists $Config->{$section};
255
256 return $Config->{$section};
257}
258
259# Helper method to setup a hash of spec definitions for _cmdline
260sub _setup_spec_index
261{
262 my $self = shift;
263 return if defined $self->{_spec};
264 $self->{_spec} = { map { $_->{name} => $_->{spec} } @{$self->{_args}} };
265}
266
267# Quote values that require it
268sub _cmdline_value
269{
270 my $self = shift;
271 local $_ = shift;
272 if (m/\s/ && (m/^[^"']/ || m/[^"']$/)) {
273 return qq("$_");
274 }
275 elsif ($_ eq '') {
276 return q("");
277 }
278 else {
279 return $_;
280 }
281}
282
283# Helper method to format key/values in $hash in a quasi-commandline format
284sub _cmdline
285{
286 my $self = shift;
287 my ($hash) = @_;
288 $hash ||= $self;
289
290 $self->_setup_spec_index;
291
292 my @args = ();
293 for my $key (sort keys %$hash) {
294 # Skip internal keys
295 next if $key =~ m/^_/;
296
297 # Skip defaults and internals
298 next if exists $DEFAULT{$key} && $hash->{$key} eq $DEFAULT{$key};
299 next if grep { $key eq $_ } qw(help usage version extra-opts);
300 next unless defined $hash->{$key};
301
302 # Render arg
303 my $spec = $self->{_spec}->{$key} || '';
304 if ($spec =~ m/[=:].+$/) {
305 # Arg takes value - may be a scalar or an arrayref
306 for my $value (ref $hash->{$key} eq 'ARRAY' ? @{$hash->{$key}} : ( $hash->{$key} )) {
307 $value = $self->_cmdline_value($value);
308 if (length($key) > 1) {
309 push @args, sprintf "--%s=%s", $key, $value;
310 }
311 else {
312 push @args, "-$key", $value;
313 }
314 }
315 }
316
317 else {
318 # Flag - render long or short based on option length
319 push @args, (length($key) > 1 ? '--' : '-') . $key;
320 }
321 }
322
323 return wantarray ? @args : join(' ', @args);
324}
325
326# Process and load extra-opts sections
327sub _process_extra_opts
328{
329 my $self = shift;
330 my ($args) = @_;
331
332 my $extopts_list = $args->{'extra-opts'};
333
334 my @sargs = ();
335 for my $extopts (@$extopts_list) {
336 $extopts ||= $self->{_attr}->{plugin};
337 my $section = $extopts;
338 my $file = '';
339
340 # Parse section@file
341 if ($extopts =~ m/^([^@]*)@(.*?)\s*$/) {
342 $section = $1;
343 $file = $2;
344 }
345
346 # Load section args
347 my $shash = $self->_load_config_section($section, $file);
348
349 # Turn $shash into a series of commandline-like arguments
350 push @sargs, $self->_cmdline($shash);
351 }
352
353 # Reset ARGV to extra-opts + original
354 @ARGV = ( @sargs, @{$self->{_attr}->{argv}} );
355
356 printf "[extra-opts] %s %s\n", $self->{_attr}->{plugin}, join(' ', @ARGV)
357 if $args->{verbose} && $args->{verbose} >= 3;
358}
359
360# -------------------------------------------------------------------------
361# Public methods
362
363# Define plugin argument
364sub arg
365{
366 my $self = shift;
367 my %args;
368
369 # Named args
370 if ($_[0] =~ m/^(spec|help|required|default)$/ && scalar(@_) % 2 == 0) {
371 %args = validate( @_, {
372 spec => 1,
373 help => 1,
374 default => 0,
375 required => 0,
376 label => 0,
377 });
378 }
379
380 # Positional args
381 else {
382 my @args = validate_pos(@_, 1, 1, 0, 0, 0);
383 %args = (
384 spec => $args[0],
385 help => $args[1],
386 default => $args[2],
387 required => $args[3],
388 label => $args[4],
389 );
390 }
391
392 # Add to private args arrayref
393 push @{$self->{_args}}, \%args;
394}
395
396# Process the @ARGV array using the current _args list (possibly exiting)
397sub getopts
398{
399 my $self = shift;
400
401 # Collate spec arguments for Getopt::Long
402 my @opt_array = $self->_process_specs_getopt_long;
403
404 # Capture original @ARGV (for extra-opts games)
405 $self->{_attr}->{argv} = [ @ARGV ];
406
407 # Call GetOptions using @opt_array
408 my $args1 = {};
409 my $ok = GetOptions($args1, @opt_array);
410 # Invalid options - give usage message and exit
411 $self->_die($self->_usage) unless $ok;
412
413 # Process extra-opts
414 $self->_process_extra_opts($args1);
415
416 # Call GetOptions again, this time including extra-opts
417 $ok = GetOptions($self, @opt_array);
418 # Invalid options - give usage message and exit
419 $self->_die($self->_usage) unless $ok;
420
421 # Process immediate options (possibly exiting)
422 $self->_process_opts;
423
424 # Required options (possibly exiting)
425 $self->_check_required_opts;
426
427 # Setup accessors for options
428 $self->mk_ro_accessors(grep ! /^_/, keys %$self);
429
430 # Setup default alarm handler for alarm($ng->timeout) in plugin
431 $SIG{ALRM} = sub {
432 my $plugin = uc $self->{_attr}->{plugin};
433 $plugin =~ s/^check_//;
434 $self->_die(
435 sprintf("%s UNKNOWN - plugin timed out (timeout %ss)",
436 $plugin, $self->timeout));
437 };
438}
439
440# -------------------------------------------------------------------------
441# Constructor
442
443sub _init
444{
445 my $self = shift;
446
447 # Check params
448 my $plugin = basename($ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0);
449 my %attr = validate( @_, {
450 usage => 1,
451 version => 0,
452 url => 0,
453 plugin => { default => $plugin },
454 blurb => 0,
455 extra => 0,
456 'extra-opts' => 0,
457 license => { default => $DEFAULT{license} },
458 timeout => { default => $DEFAULT{timeout} },
459 });
460
461 # Add attr to private _attr hash (except timeout)
462 $self->{timeout} = delete $attr{timeout};
463 $self->{_attr} = { %attr };
464 # Chomp _attr values
465 chomp foreach values %{$self->{_attr}};
466
467 # Setup initial args list
468 $self->{_args} = [ @ARGS ];
469
470 $self
471}
472
473sub new
474{
475 my $class = shift;
476 my $self = bless {}, $class;
477 $self->_init(@_);
478}
479
480# -------------------------------------------------------------------------
481
4821;
483
484__END__
485
486=head1 NAME
487
488Monitoring::Plugin::Getopt - OO perl module providing standardised argument
489processing for Nagios plugins
490
491
492=head1 SYNOPSIS
493
494 use Monitoring::Plugin::Getopt;
495
496 # Instantiate object (usage is mandatory)
497 $ng = Monitoring::Plugin::Getopt->new(
498 usage => "Usage: %s -H <host> -w <warning> -c <critical>",
499 version => '0.1',
500 url => 'http://www.openfusion.com.au/labs/nagios/',
501 blurb => 'This plugin tests various stuff.',
502 );
503
504 # Add argument - named parameters (spec and help are mandatory)
505 $ng->arg(
506 spec => 'critical|c=i',
507 help => q(Exit with CRITICAL status if fewer than INTEGER foobars are free),
508 required => 1,
509 default => 10,
510 );
511
512 # Add argument - positional parameters - arg spec, help text,
513 # default value, required? (first two mandatory)
514 $ng->arg(
515 'warning|w=i',
516 q(Exit with WARNING status if fewer than INTEGER foobars are free),
517 5,
518 1);
519
520 # Parse arguments and process standard ones (e.g. usage, help, version)
521 $ng->getopts;
522
523 # Access arguments using named accessors or or via the generic get()
524 print $ng->warning;
525 print $ng->get('critical');
526
527
528
529=head1 DESCRIPTION
530
531Monitoring::Plugin::Getopt is an OO perl module providing standardised and
532simplified argument processing for Nagios plugins. It implements
533a number of standard arguments itself (--help, --version,
534--usage, --timeout, --verbose, and their short form counterparts),
535produces standardised nagios plugin help output, and allows
536additional arguments to be easily defined.
537
538
539=head2 CONSTRUCTOR
540
541 # Instantiate object (usage is mandatory)
542 $ng = Monitoring::Plugin::Getopt->new(
543 usage => 'Usage: %s --hello',
544 version => '0.01',
545 );
546
547The Monitoring::Plugin::Getopt constructor accepts the following named
548arguments:
549
550=over 4
551
552=item usage (required)
553
554Short usage message used with --usage/-? and with missing required
555arguments, and included in the longer --help output. Can include
556a '%s' sprintf placeholder which will be replaced with the plugin
557name e.g.
558
559 usage => qq(Usage: %s -H <hostname> -p <ports> [-v]),
560
561might be displayed as:
562
563 $ ./check_tcp_range --usage
564 Usage: check_tcp_range -H <hostname> -p <ports> [-v]
565
566=item version (required)
567
568Plugin version number, included in the --version/-V output, and in
569the longer --help output. e.g.
570
571 $ ./check_tcp_range --version
572 check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/]
573
574=item url
575
576URL for info about this plugin, included in the --version/-V output,
577and in the longer --help output (see preceding 'version' example).
578
579=item blurb
580
581Short plugin description, included in the longer --help output
582(see below for an example).
583
584=item license
585
586License text, included in the longer --help output (see below for an
587example). By default, this is set to the standard nagios plugins
588GPL license text:
589
590 This nagios plugin is free software, and comes with ABSOLUTELY
591 NO WARRANTY. It may be used, redistributed and/or modified under
592 the terms of the GNU General Public Licence (see
593 http://www.fsf.org/licensing/licenses/gpl.txt).
594
595Provide your own to replace this text in the help output.
596
597=item extra
598
599Extra text to be appended at the end of the longer --help output.
600
601=item plugin
602
603Plugin name. This defaults to the basename of your plugin, which is
604usually correct, but you can set it explicitly if not.
605
606=item timeout
607
608Timeout period in seconds, overriding the standard timeout default
609(15 seconds).
610
611=back
612
613The full --help output has the following form:
614
615 version string
616
617 license string
618
619 blurb
620
621 usage string
622
623 options list
624
625 extra text
626
627The 'blurb' and 'extra text' sections are omitted if not supplied. For
628example:
629
630 $ ./check_tcp_range -h
631 check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/]
632
633 This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
634 It may be used, redistributed and/or modified under the terms of the GNU
635 General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).
636
637 This plugin tests arbitrary ranges/sets of tcp ports for a host.
638
639 Usage: check_tcp_range -H <hostname> -p <ports> [-v]
640
641 Options:
642 -h, --help
643 Print detailed help screen
644 -V, --version
645 Print version information
646 -H, --hostname=ADDRESS
647 Host name or IP address
648 -p, --ports=STRING
649 Port numbers to check. Format: comma-separated, colons for ranges,
650 no spaces e.g. 8700:8705,8710:8715,8760
651 -t, --timeout=INTEGER
652 Seconds before plugin times out (default: 15)
653 -v, --verbose
654 Show details for command-line debugging (can repeat up to 3 times)
655
656
657=head2 ARGUMENTS
658
659You can define arguments for your plugin using the arg() method, which
660supports both named and positional arguments. In both cases
661the C<spec> and C<help> arguments are required, while the C<label>,
662C<default>, and C<required> arguments are optional:
663
664 # Define --hello argument (named parameters)
665 $ng->arg(
666 spec => 'hello|h=s',
667 help => "Hello string",
668 required => 1,
669 );
670
671 # Define --hello argument (positional parameters)
672 # Parameter order is 'spec', 'help', 'default', 'required?', 'label'
673 $ng->arg('hello|h=s', "Hello parameter (default %s)", 5, 1);
674
675=over 4
676
677=item spec
678
679The C<spec> argument (the first argument in the positional variant) is a
680L<Getopt::Long> argument specification. See L<Getopt::Long> for the details,
681but basically it is a series of one or more argument names for this argument
682(separated by '|'), suffixed with an '=<type>' indicator if the argument
683takes a value. '=s' indicates a string argument; '=i' indicates an integer
684argument; appending an '@' indicates multiple such arguments are accepted;
685and so on. The following are some examples:
686
687=over 4
688
689=item hello=s
690
691=item hello|h=s
692
693=item ports|port|p=i
694
695=item exclude|X=s@
696
697=item verbose|v+
698
699=back
700
701=item help
702
703The C<help> argument is a string displayed in the --help option list output,
704or it can be a list (an arrayref) of such strings, for multi-line help (see
705below).
706
707The help string is munged in two ways:
708
709=over 4
710
711=item
712
713First, if the help string does NOT begins with a '-' sign, it is prefixed
714by an expanded form of the C<spec> argument. For instance, the following
715hello argument:
716
717 $ng->arg(
718 spec => 'hello|h=s',
719 help => "Hello string",
720 );
721
722would be displayed in the help output as:
723
724 -h, --hello=STRING
725 Hello string
726
727where the '-h, --hello=STRING' part is derived from the spec definition
728(by convention with short args first, then long, then label/type, if any).
729
730=item
731
732Second, if the string contains a '%s' it will be formatted via
733C<sprintf> with the 'default' as the argument i.e.
734
735 sprintf($help, $default)
736
737=back
738
739Multi-line help is useful in cases where an argument can be of different types
740and you want to make this explicit in your help output e.g.
741
742 $ng->arg(
743 spec => 'warning|w=s',
744 help => [
745 'Exit with WARNING status if less than BYTES bytes of disk are free',
746 'Exit with WARNING status if less than PERCENT of disk is free',
747 ],
748 label => [ 'BYTES', 'PERCENT%' ],
749 );
750
751would be displayed in the help output as:
752
753 -w, --warning=BYTES
754 Exit with WARNING status if less than BYTES bytes of disk are free
755 -w, --warning=PERCENT%
756 Exit with WARNING status if less than PERCENT of disk space is free
757
758Note that in this case we've also specified explicit labels in another
759arrayref corresponding to the C<help> one - if this had been omitted
760the types would have defaulted to 'STRING', instead of 'BYTES' and
761'PERCENT%'.
762
763
764=item label
765
766The C<label> argument is a scalar or an arrayref (see 'Multi-line help'
767description above) that overrides the standard type expansion when generating
768help text from the spec definition. By default, C<spec=i> arguments are
769labelled as C<=INTEGER> in the help text, and C<spec=s> arguments are labelled
770as C<=STRING>. By supplying your own C<label> argument you can override these
771standard 'INTEGER' and 'STRING' designations.
772
773For multi-line help, you can supply an ordered list (arrayref) of labels to
774match the list of help strings e.g.
775
776 label => [ 'BYTES', 'PERCENT%' ]
777
778Any labels that are left as undef (or just omitted, if trailing) will just
779use the default 'INTEGER' or 'STRING' designations e.g.
780
781 label => [ undef, 'PERCENT%' ]
782
783
784=item default
785
786The C<default> argument is the default value to be given to this parameter
787if none is explicitly supplied.
788
789
790=item required
791
792The C<required> argument is a boolean used to indicate that this argument
793is mandatory (Monitoring::Plugin::Getopt will exit with your usage message and
794a 'Missing argument' indicator if any required arguments are not supplied).
795
796=back
797
798Note that --help lists your arguments in the order they are defined, so
799you should order your C<arg()> calls accordingly.
800
801
802=head2 GETOPTS
803
804The main parsing and processing functionality is provided by the getopts()
805method, which takes no arguments:
806
807 # Parse and process arguments
808 $ng->getopts;
809
810This parses the command line arguments passed to your plugin using
811Getopt::Long and the builtin and provided argument specifications.
812Flags and argument values are recorded within the object, and can
813be accessed either using the generic get() accessor, or using named
814accessors corresponding to your argument names. For example:
815
816 print $ng->get('hello');
817 print $ng->hello();
818
819 if ($ng->verbose) {
820 # ...
821 }
822
823 if ($ng->get('ports') =~ m/:/) {
824 # ...
825 }
826
827Note that where you have defined alternate argument names, the first is
828considered the citation form. All the builtin arguments are available
829using their long variant names.
830
831
832=head2 BUILTIN PROCESSING
833
834The C<getopts()> method also handles processing of the immediate builtin
835arguments, namely --usage, --version, --help, as well as checking all
836required arguments have been supplied, so you don't have to handle
837those yourself. This means that your plugin will exit from the getopts()
838call in these cases - if you want to catch that you can run getopts()
839within an eval{}.
840
841C<getopts()> also sets up a default ALRM timeout handler so you can use an
842
843 alarm $ng->timeout;
844
845around any blocking operations within your plugin (which you are free
846to override if you want to use a custom timeout message).
847
848
849=head1 SEE ALSO
850
851Monitoring::Plugin, Getopt::Long
852
853
854=head1 AUTHOR
855
856This code is maintained by the Monitoring Plugin Development Team: see
857https://monitoring-plugins.org
858
859Originally:
860 Gavin Carr <gavin@openfusion.com.au>
861
862=head1 COPYRIGHT AND LICENSE
863
864Copyright (C) 2006-2014 Monitoring Plugin Development Team
865
866This library is free software; you can redistribute it and/or modify
867it under the same terms as Perl itself.
868
869=cut
diff --git a/lib/Monitoring/Plugin/Performance.pm b/lib/Monitoring/Plugin/Performance.pm
new file mode 100644
index 0000000..90fc9f4
--- /dev/null
+++ b/lib/Monitoring/Plugin/Performance.pm
@@ -0,0 +1,294 @@
1package Monitoring::Plugin::Performance;
2
3use 5.006;
4
5use strict;
6use warnings;
7
8use Carp;
9use base qw(Class::Accessor::Fast);
10__PACKAGE__->mk_ro_accessors(
11 qw(label value uom warning critical min max)
12);
13
14use Monitoring::Plugin::Functions;
15use Monitoring::Plugin::Threshold;
16use Monitoring::Plugin::Range;
17our ($VERSION) = $Monitoring::Plugin::Functions::VERSION;
18
19sub import {
20 my ($class, %attr) = @_;
21 $_ = $attr{use_die} || 0;
22 Monitoring::Plugin::Functions::_use_die($_);
23}
24
25# This is NOT the same as N::P::Functions::value_re. We leave that to be the strict
26# version. This one allows commas to be part of the numeric value.
27my $value = qr/[-+]?[\d\.,]+/;
28my $value_re = qr/$value(?:e$value)?/;
29my $value_with_negative_infinity = qr/$value_re|~/;
30sub _parse {
31 my $class = shift;
32 my $string = shift;
33 $string =~ /^'?([^'=]+)'?=($value_re)([\w%]*);?($value_with_negative_infinity\:?$value_re?)?;?($value_with_negative_infinity\:?$value_re?)?;?($value_re)?;?($value_re)?/o;
34 return undef unless ((defined $1 && $1 ne "") && (defined $2 && $2 ne ""));
35 my @info = ($1, $2, $3, $4, $5, $6, $7);
36 # We convert any commas to periods, in the value fields
37 map { defined $info[$_] && $info[$_] =~ s/,/./go } (1, 3, 4, 5, 6);
38
39 # Check that $info[1] is an actual value
40 # We do this by returning undef if a warning appears
41 my $performance_value;
42 {
43 my $not_value;
44 local $SIG{__WARN__} = sub { $not_value++ };
45 $performance_value = $info[1]+0;
46 return undef if $not_value;
47 }
48 my $p = $class->new(
49 label => $info[0], value => $performance_value, uom => $info[2], warning => $info[3], critical => $info[4],
50 min => $info[5], max => $info[6]
51 );
52 return $p;
53}
54
55# Map undef to ''
56sub _nvl {
57 my ($self, $value) = @_;
58 defined $value ? $value : ''
59}
60
61sub perfoutput {
62 my $self = shift;
63 # Add quotes if label contains a space character
64 my $label = $self->label;
65 if ($label =~ / /) {
66 $label = "'$label'";
67 }
68 my $out = sprintf "%s=%s%s;%s;%s;%s;%s",
69 $label,
70 $self->value,
71 $self->_nvl($self->uom),
72 $self->_nvl($self->warning),
73 $self->_nvl($self->critical),
74 $self->_nvl($self->min),
75 $self->_nvl($self->max);
76 # Previous implementation omitted trailing ;; - do we need this?
77 $out =~ s/;;$//;
78 return $out;
79}
80
81sub parse_perfstring {
82 my ($class, $perfstring) = @_;
83 my @perfs = ();
84 my $obj;
85 while ($perfstring) {
86 $perfstring =~ s/^\s*//;
87 # If there is more than 1 equals sign, split it out and parse individually
88 if (@{[$perfstring =~ /=/g]} > 1) {
89 $perfstring =~ s/^(.*?=.*?)\s//;
90 if (defined $1) {
91 $obj = $class->_parse($1);
92 } else {
93 # This could occur if perfdata was soemthing=value=
94 # Since this is invalid, we reset the string and continue
95 $perfstring = "";
96 $obj = $class->_parse($perfstring);
97 }
98 } else {
99 $obj = $class->_parse($perfstring);
100 $perfstring = "";
101 }
102 push @perfs, $obj if $obj;
103 }
104 return @perfs;
105}
106
107sub rrdlabel {
108 my $self = shift;
109 my $name = $self->clean_label;
110 # Shorten
111 return substr( $name, 0, 19 );
112}
113
114sub clean_label {
115 my $self = shift;
116 my $name = $self->label;
117 if ($name eq "/") {
118 $name = "root";
119 } elsif ( $name =~ s/^\/// ) {
120 $name =~ s/\//_/g;
121 }
122 # Convert all other characters
123 $name =~ s/\W/_/g;
124 return $name;
125}
126
127# Backward compatibility: create a threshold object on the fly as requested
128sub threshold
129{
130 my $self = shift;
131 return Monitoring::Plugin::Threshold->set_thresholds(
132 warning => $self->warning, critical => $self->critical
133 );
134}
135
136# Constructor - unpack thresholds, map args to hashref
137sub new
138{
139 my $class = shift;
140 my %arg = @_;
141
142 # Convert thresholds
143 if (my $threshold = delete $arg{threshold}) {
144 $arg{warning} ||= $threshold->warning . "";
145 $arg{critical} ||= $threshold->critical . "";
146 }
147
148 $class->SUPER::new(\%arg);
149}
150
1511;
152
153__END__
154
155=head1 NAME
156
157Monitoring::Plugin::Performance - class for handling Monitoring::Plugin
158performance data.
159
160=head1 SYNOPSIS
161
162 use Monitoring::Plugin::Performance use_die => 1;
163
164 # Constructor (also accepts a 'threshold' obj instead of warning/critical)
165 $p = Monitoring::Plugin::Performance->new(
166 label => 'size',
167 value => $value,
168 uom => "kB",
169 warning => $warning,
170 critical => $critical,
171 min => $min,
172 max => $max,
173 );
174
175 # Parser
176 @perf = Monitoring::Plugin::Performance->parse_perfstring(
177 "/=382MB;15264;15269;; /var=218MB;9443;9448"
178 )
179 or warn("Failed to parse perfstring");
180
181 # Accessors
182 for $p (@perf) {
183 printf "label: %s\n", $p->label;
184 printf "value: %s\n", $p->value;
185 printf "uom: %s\n", $p->uom;
186 printf "warning: %s\n", $p->warning;
187 printf "critical: %s\n", $p->critical;
188 printf "min: %s\n", $p->min;
189 printf "max: %s\n", $p->max;
190 # Special accessor returning a threshold obj containing warning/critical
191 $threshold = $p->threshold;
192 }
193
194 # Perfdata output format i.e. label=value[uom];[warn];[crit];[min];[max]
195 print $p->perfoutput;
196
197
198=head1 DESCRIPTION
199
200Monitoring::Plugin class for handling performance data. This is a public
201interface because it could be used by performance graphing routines,
202such as nagiostat (http://nagiostat.sourceforge.net), perfparse
203(http://perfparse.sourceforge.net), nagiosgraph
204(http://nagiosgraph.sourceforge.net) or NagiosGrapher
205(http://www.nagiosexchange.org/NagiosGrapher.84.0.html).
206
207Monitoring::Plugin::Performance offers both a parsing interface (via
208parse_perfstring), for turning nagios performance output strings into
209their components, and a composition interface (via new), for turning
210components into perfdata strings.
211
212=head1 USE'ING THE MODULE
213
214If you are using this module for the purposes of parsing perf data, you
215will probably want to set use_die => 1 at use time. This forces
216&Monitoring::Plugin::Functions::plugin_exit to call die() - rather than exit() -
217when an error occurs. This is then trappable by an eval. If you don't set use_die,
218then an error in these modules will cause your script to exit
219
220=head1 CLASS METHODS
221
222=over 4
223
224=item Monitoring::Plugin::Performance->new(%attributes)
225
226Instantiates a new Monitoring::Plugin::Performance object with the given
227attributes.
228
229=item Monitoring::Plugin::Performance->parse_perfstring($string)
230
231Returns an array of Monitoring::Plugin::Performance objects based on the string
232entered. If there is an error parsing the string - which may consists of several
233sets of data - will return an array with all the successfully parsed sets.
234
235If values are input with commas instead of periods, due to different locale settings,
236then it will still be parsed, but the commas will be converted to periods.
237
238=back
239
240=head1 OBJECT METHODS (ACCESSORS)
241
242=over 4
243
244=item label, value, uom, warning, critical, min, max
245
246These all return scalars. min and max are not well supported yet.
247
248=item threshold
249
250Returns a Monitoring::Plugin::Threshold object holding the warning and critical
251ranges for this performance data (if any).
252
253=item rrdlabel
254
255Returns a string based on 'label' that is suitable for use as dataset name of
256an RRD i.e. munges label to be 1-19 characters long with only characters
257[a-zA-Z0-9_].
258
259This calls $self->clean_label and then truncates to 19 characters.
260
261There is no guarantee that multiple N:P:Performance objects will have unique
262rrdlabels.
263
264=item clean_label
265
266Returns a "clean" label for use as a dataset name in RRD, ie, it converts
267characters that are not [a-zA-Z0-9_] to _.
268
269It also converts "/" to "root" and "/{name}" to "{name}".
270
271=item perfoutput
272
273Outputs the data in Monitoring::Plugin perfdata format i.e.
274label=value[uom];[warn];[crit];[min];[max].
275
276=back
277
278=head1 SEE ALSO
279
280Monitoring::Plugin, Monitoring::Plugin::Threshold, https://www.monitoring-plugins.org/doc/guidelines.html
281
282=head1 AUTHOR
283
284This code is maintained by the Monitoring Plugin Development Team: see
285https://monitoring-plugins.org
286
287=head1 COPYRIGHT AND LICENSE
288
289Copyright (C) 2006-2014 Monitoring Plugin Development Team
290
291This library is free software; you can redistribute it and/or modify
292it under the same terms as Perl itself.
293
294=cut
diff --git a/lib/Monitoring/Plugin/Range.pm b/lib/Monitoring/Plugin/Range.pm
new file mode 100644
index 0000000..af19577
--- /dev/null
+++ b/lib/Monitoring/Plugin/Range.pm
@@ -0,0 +1,169 @@
1package Monitoring::Plugin::Range;
2
3use 5.006;
4
5use strict;
6use warnings;
7
8use Carp;
9use base qw(Class::Accessor::Fast);
10__PACKAGE__->mk_accessors(
11 qw(start end start_infinity end_infinity alert_on)
12);
13
14use Monitoring::Plugin::Functions qw(:DEFAULT $value_re);
15our ($VERSION) = $Monitoring::Plugin::Functions::VERSION;
16
17use overload
18 'eq' => sub { shift->_stringify },
19 '""' => sub { shift->_stringify };
20
21# alert_on constants (undef == range not set)
22use constant OUTSIDE => 0;
23use constant INSIDE => 1;
24
25sub _stringify {
26 my $self = shift;
27 return "" unless $self->is_set;
28 return (($self->alert_on) ? "@" : "") .
29 (($self->start_infinity == 1) ? "~:" : (($self->start == 0)?"":$self->start.":")) .
30 (($self->end_infinity == 1) ? "" : $self->end);
31}
32
33sub is_set {
34 my $self = shift;
35 (! defined $self->alert_on) ? 0 : 1;
36}
37
38sub _set_range_start {
39 my ($self, $value) = @_;
40 $self->start($value+0); # Force scalar into number
41 $self->start_infinity(0);
42}
43
44sub _set_range_end {
45 my ($self, $value) = @_;
46 $self->end($value+0); # Force scalar into number
47 $self->end_infinity(0);
48}
49
50# Returns a N::P::Range object if the string is a conforms to a Monitoring Plugin range string, otherwise null
51sub parse_range_string {
52 my ($class, $string) = @_;
53 my $valid = 0;
54 my $range = $class->new( start => 0, start_infinity => 0, end => 0, end_infinity => 1, alert_on => OUTSIDE);
55
56 $string =~ s/\s//g; # strip out any whitespace
57 # check for valid range definition
58 unless ( $string =~ /[\d~]/ && $string =~ m/^\@?($value_re|~)?(:($value_re)?)?$/ ) {
59 carp "invalid range definition '$string'";
60 return undef;
61 }
62
63 if ($string =~ s/^\@//) {
64 $range->alert_on(INSIDE);
65 }
66
67 if ($string =~ s/^~//) { # '~:x'
68 $range->start_infinity(1);
69 }
70 if ( $string =~ m/^($value_re)?:/ ) { # '10:'
71 my $start = $1;
72 $range->_set_range_start($start) if defined $start;
73 $range->end_infinity(1); # overridden below if there's an end specified
74 $string =~ s/^($value_re)?://;
75 $valid++;
76 }
77 if ($string =~ /^($value_re)$/) { # 'x:10' or '10'
78 $range->_set_range_end($string);
79 $valid++;
80 }
81
82 if ($valid && ($range->start_infinity == 1 || $range->end_infinity == 1 || $range->start <= $range->end)) {
83 return $range;
84 }
85 return undef;
86}
87
88# Returns 1 if an alert should be raised, otherwise 0
89sub check_range {
90 my ($self, $value) = @_;
91 my $false = 0;
92 my $true = 1;
93 if ($self->alert_on == INSIDE) {
94 $false = 1;
95 $true = 0;
96 }
97 if ($self->end_infinity == 0 && $self->start_infinity == 0) {
98 if ($self->start <= $value && $value <= $self->end) {
99 return $false;
100 } else {
101 return $true;
102 }
103 } elsif ($self->start_infinity == 0 && $self->end_infinity == 1) {
104 if ( $value >= $self->start ) {
105 return $false;
106 } else {
107 return $true;
108 }
109 } elsif ($self->start_infinity == 1 && $self->end_infinity == 0) {
110 if ($value <= $self->end) {
111 return $false;
112 } else {
113 return $true;
114 }
115 } else {
116 return $false;
117 }
118}
119
120# Constructor - map args to hashref for SUPER
121sub new
122{
123 shift->SUPER::new({ @_ });
124}
125
1261;
127
128__END__
129
130=head1 NAME
131
132Monitoring::Plugin::Range - class for handling Monitoring::Plugin range data.
133
134=head1 SYNOPSIS
135
136 # NB: This is an internal Monitoring::Plugin class.
137 # See Monitoring::Plugin itself for public interfaces.
138
139 # Instantiate an empty range object
140 $r = Monitoring::Plugin::Range->new;
141
142 # Instantiate by parsing a standard nagios range string
143 $r = Monitoring::Plugin::Range->parse_range_string( $range_str );
144
145 # Returns true if the range is defined/non-empty
146 $r->is_set;
147
148 # Returns true if $value matches range, false otherwise
149 $r->check_range($value);
150
151
152=head1 DESCRIPTION
153
154Internal Monitoring::Plugin class for handling common range data. See
155Monitoring::Plugin for public interfaces.
156
157=head1 AUTHOR
158
159This code is maintained by the Monitoring Plugin Development Team: see
160https://monitoring-plugins.org
161
162=head1 COPYRIGHT AND LICENSE
163
164Copyright (C) 2006-2014 Monitoring Plugin Development Team
165
166This library is free software; you can redistribute it and/or modify
167it under the same terms as Perl itself.
168
169=cut
diff --git a/lib/Monitoring/Plugin/Threshold.pm b/lib/Monitoring/Plugin/Threshold.pm
new file mode 100644
index 0000000..e71e21b
--- /dev/null
+++ b/lib/Monitoring/Plugin/Threshold.pm
@@ -0,0 +1,134 @@
1package Monitoring::Plugin::Threshold;
2
3use 5.006;
4
5use strict;
6use warnings;
7
8use base qw(Class::Accessor::Fast);
9__PACKAGE__->mk_accessors(qw(warning critical));
10
11use Monitoring::Plugin::Range;
12use Monitoring::Plugin::Functions qw(:codes plugin_die);
13our ($VERSION) = $Monitoring::Plugin::Functions::VERSION;
14
15sub get_status
16{
17 my ($self, $value) = @_;
18
19 $value = [ $value ] if (ref $value eq "");
20 foreach my $v (@$value) {
21 if ($self->critical->is_set) {
22 return CRITICAL if $self->critical->check_range($v);
23 }
24 }
25 foreach my $v (@$value) {
26 if ($self->warning->is_set) {
27 return WARNING if $self->warning->check_range($v);
28 }
29 }
30 return OK;
31}
32
33sub _inflate
34{
35 my ($self, $value, $key) = @_;
36
37 # Return an undefined range if $value is undef
38 return Monitoring::Plugin::Range->new if ! defined $value;
39
40 # For refs, check isa N::P::Range
41 if (ref $value) {
42 plugin_die("Invalid $key object: type " . ref $value)
43 unless $value->isa("Monitoring::Plugin::Range");
44 return $value;
45 }
46
47 # Another quick exit if $value is an empty string
48 return Monitoring::Plugin::Range->new if $value eq "";
49
50 # Otherwise parse $value
51 my $range = Monitoring::Plugin::Range->parse_range_string($value);
52 plugin_die("Cannot parse $key range: '$value'") unless(defined($range));
53 return $range;
54}
55
56sub set_thresholds
57{
58 my ($self, %arg) = @_;
59
60 # Equals new() as a class method
61 return $self->new(%arg) unless ref $self;
62
63 # On an object, just acts as special mutator
64 $self->set($_, $arg{$_}) foreach qw(warning critical);
65}
66
67sub set
68{
69 my $self = shift;
70 my ($key, $value) = @_;
71 $self->SUPER::set($key, $self->_inflate($value, $key));
72}
73
74# Constructor - inflate scalars to N::P::Range objects
75sub new
76{
77 my ($self, %arg) = @_;
78 $self->SUPER::new({
79 map { $_ => $self->_inflate($arg{$_}, $_) } qw(warning critical)
80 });
81}
82
831;
84
85__END__
86
87=head1 NAME
88
89Monitoring::Plugin::Threshold - class for handling Monitoring::Plugin thresholds.
90
91=head1 SYNOPSIS
92
93 # NB: This is an internal Monitoring::Plugin class.
94 # See Monitoring::Plugin itself for public interfaces.
95
96 # Constructor
97 $t = Monitoring::Plugin::Threshold->set_thresholds(
98 warning => $warning_range_string,
99 critical => $critical_range_string,
100 );
101
102 # Value checking - returns CRITICAL if in the critical range,
103 # WARNING if in the warning range, and OK otherwise
104 $status = $t->get_status($value);
105
106 # Accessors - return the associated N::P::Range object
107 $warning_range = $t->warning;
108 $critical_range = $t->critical;
109
110
111=head1 DESCRIPTION
112
113Internal Monitoring::Plugin class for handling threshold data. See
114Monitoring::Plugin for public interfaces.
115
116A threshold object contains (typically) a pair of ranges, associated
117with a particular severity e.g.
118
119 warning => range1
120 critical => range2
121
122=head1 AUTHOR
123
124This code is maintained by the Monitoring Plugin Development Team: see
125https://monitoring-plugins.org
126
127=head1 COPYRIGHT AND LICENSE
128
129Copyright (C) 2006-2014 Monitoring Plugin Development Team
130
131This library is free software; you can redistribute it and/or modify
132it under the same terms as Perl itself.
133
134=cut