├── t ├── npg03 │ ├── expected │ │ ├── 05_disk1 │ │ ├── 05_disk3 │ │ ├── 00_noextra │ │ ├── 05_disk2 │ │ ├── 05_disk4 │ │ ├── 05_disk5 │ │ ├── 12_nosection_implicit │ │ ├── 05_disk7 │ │ ├── 02_override2 │ │ ├── 01_override1 │ │ ├── 09_funnystuff │ │ ├── 15_badsection_catch │ │ ├── 05_disk6 │ │ └── 00_basic │ ├── input │ │ ├── 00_noextra │ │ ├── 05_disk1 │ │ ├── 02_override2 │ │ ├── 05_disk3 │ │ ├── 05_disk7 │ │ ├── 09_funnystuff │ │ ├── 05_disk2 │ │ ├── 05_disk4 │ │ ├── 12_nosection_implicit │ │ ├── 14_badsection_dies │ │ ├── 05_disk5 │ │ ├── 13_nosection_explicit_dies │ │ ├── 05_disk6 │ │ ├── 15_badsection_catch │ │ ├── 00_basic │ │ └── 01_override1 │ ├── plugins.ini │ └── README ├── Monitoring-Plugin-05.t ├── Monitoring-Plugin-Performance-02.t ├── Monitoring-Plugin-Functions-03.t ├── Monitoring-Plugin-Functions-04.t ├── Monitoring-Plugin-Getopt-02.t ├── check_stuff.t ├── Monitoring-Plugin-01.t ├── Monitoring-Plugin-Getopt-03.t ├── Monitoring-Plugin-04.t ├── Monitoring-Plugin-Getopt-04.t ├── check_stuff.pl ├── Monitoring-Plugin-Getopt-01.t ├── Monitoring-Plugin-02.t ├── Monitoring-Plugin-Functions-01.t ├── Monitoring-Plugin-Functions-02.t ├── Monitoring-Plugin-Range.t ├── Monitoring-Plugin-Threshold.t ├── Monitoring-Plugin-03.t └── Monitoring-Plugin-Performance.t ├── .gitignore ├── MANIFEST.SKIP ├── .travis.yml ├── notes ├── Makefile.PL ├── README ├── lib └── Monitoring │ ├── Plugin │ ├── ExitResult.pm │ ├── Threshold.pm │ ├── Range.pm │ ├── Config.pm │ ├── Performance.pm │ ├── Functions.pm │ └── Getopt.pm │ └── Plugin.pm ├── MANIFEST └── Changes /t/npg03/expected/05_disk1: -------------------------------------------------------------------------------- 1 | check_disk -p /tmp -p /home 2 | -------------------------------------------------------------------------------- /t/npg03/expected/05_disk3: -------------------------------------------------------------------------------- 1 | check_disk -p /tmp -p /var 2 | -------------------------------------------------------------------------------- /t/npg03/input/00_noextra: -------------------------------------------------------------------------------- 1 | check_mysql -S -H localhost 2 | -------------------------------------------------------------------------------- /t/npg03/expected/00_noextra: -------------------------------------------------------------------------------- 1 | check_mysql -H localhost -S 2 | -------------------------------------------------------------------------------- /t/npg03/input/05_disk1: -------------------------------------------------------------------------------- 1 | check_disk --extra-opts= -p /home 2 | -------------------------------------------------------------------------------- /t/npg03/expected/05_disk2: -------------------------------------------------------------------------------- 1 | check_disk -p /tmp -p /home -p /users 2 | -------------------------------------------------------------------------------- /t/npg03/expected/05_disk4: -------------------------------------------------------------------------------- 1 | check_disk -p /tmp -p /var -p /home 2 | -------------------------------------------------------------------------------- /t/npg03/expected/05_disk5: -------------------------------------------------------------------------------- 1 | check_disk -p /var -p /tmp -p /home 2 | -------------------------------------------------------------------------------- /t/npg03/input/02_override2: -------------------------------------------------------------------------------- 1 | check_mysql --extra-opts= -u admin 2 | -------------------------------------------------------------------------------- /t/npg03/input/05_disk3: -------------------------------------------------------------------------------- 1 | check_disk --extra-opts=check_2_disks 2 | -------------------------------------------------------------------------------- /t/npg03/input/05_disk7: -------------------------------------------------------------------------------- 1 | check_disk3 --extra-opts --path=/home 2 | -------------------------------------------------------------------------------- /t/npg03/input/09_funnystuff: -------------------------------------------------------------------------------- 1 | check_disk --extra-opts=funny_stuff 2 | -------------------------------------------------------------------------------- /t/npg03/expected/12_nosection_implicit: -------------------------------------------------------------------------------- 1 | check_no_section -H localhost 2 | -------------------------------------------------------------------------------- /t/npg03/input/05_disk2: -------------------------------------------------------------------------------- 1 | check_disk --extra-opts= -p /home -p /users 2 | -------------------------------------------------------------------------------- /t/npg03/input/05_disk4: -------------------------------------------------------------------------------- 1 | check_disk -p /home --extra-opts=check_2_disks 2 | -------------------------------------------------------------------------------- /t/npg03/input/12_nosection_implicit: -------------------------------------------------------------------------------- 1 | check_no_section -H localhost 2 | -------------------------------------------------------------------------------- /t/npg03/expected/05_disk7: -------------------------------------------------------------------------------- 1 | check_disk3 -p / -p /var -p /tmp --path=/home 2 | -------------------------------------------------------------------------------- /t/npg03/input/14_badsection_dies: -------------------------------------------------------------------------------- 1 | check_no_section --extra-opts=bad_section 2 | -------------------------------------------------------------------------------- /t/npg03/expected/02_override2: -------------------------------------------------------------------------------- 1 | check_mysql --password=secret --username=admin 2 | -------------------------------------------------------------------------------- /t/npg03/input/05_disk5: -------------------------------------------------------------------------------- 1 | check_disk -p /home --extra-opts=check_2_disks_reprise 2 | -------------------------------------------------------------------------------- /t/npg03/input/13_nosection_explicit_dies: -------------------------------------------------------------------------------- 1 | check_no_section --extra-opts= -H localhost 2 | -------------------------------------------------------------------------------- /t/npg03/expected/01_override1: -------------------------------------------------------------------------------- 1 | check_mysql --critical=15 --username=admin --warning=5 2 | -------------------------------------------------------------------------------- /t/npg03/input/05_disk6: -------------------------------------------------------------------------------- 1 | check_disk2 --warning=10% --critical=5% --extra-opts= --path=/usr 2 | -------------------------------------------------------------------------------- /t/npg03/input/15_badsection_catch: -------------------------------------------------------------------------------- 1 | check_no_section_default_file --extra-opts=bad_section 2 | -------------------------------------------------------------------------------- /t/npg03/input/00_basic: -------------------------------------------------------------------------------- 1 | check_mysql -S --extra-opts= --extra-opts=more_options -H localhost 2 | -------------------------------------------------------------------------------- /t/npg03/input/01_override1: -------------------------------------------------------------------------------- 1 | check_mysql --username=admin --extra-opts=more_options --warning=5 2 | -------------------------------------------------------------------------------- /t/npg03/expected/09_funnystuff: -------------------------------------------------------------------------------- 1 | check_disk --expect=" space in front" -p "" --username="Ton Voon" 2 | -------------------------------------------------------------------------------- /t/npg03/expected/15_badsection_catch: -------------------------------------------------------------------------------- 1 | Invalid section 'bad_section' in config file 't/npg03/plugins.ini' 2 | -------------------------------------------------------------------------------- /t/npg03/expected/05_disk6: -------------------------------------------------------------------------------- 1 | check_disk2 --critical=5% --path=/var --path=/home --path=/usr --units=GB --warning=10% 2 | -------------------------------------------------------------------------------- /t/npg03/expected/00_basic: -------------------------------------------------------------------------------- 1 | check_mysql -H localhost -S --critical=15 --password=secret --username=altinity --warning=10 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # from https://github.com/github/gitignore/blob/master/Perl.gitignore 2 | /blib/ 3 | /.build/ 4 | _build/ 5 | cover_db/ 6 | inc/ 7 | Build 8 | !Build/ 9 | Build.bat 10 | .last_cover_stats 11 | /Makefile 12 | /Makefile.old 13 | /MANIFEST.bak 14 | /META.yml 15 | /META.json 16 | /MYMETA.* 17 | nytprof.out 18 | /pm_to_blib 19 | *.o 20 | *.bs 21 | /_eumm/ 22 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | \.swp 3 | blib 4 | tmp 5 | \.git 6 | ^Makefile$ 7 | Makefile.old 8 | MANIFEST.bak 9 | MANIFEST.SKIP 10 | MYMETA.yml 11 | MYMETA.json 12 | pm_to_blib 13 | .*\.gz 14 | # does not work for centos without F::C::R installed 15 | inc/File/Copy/Recursive.pm 16 | TODO 17 | build-stamp 18 | configure-stamp 19 | nytprof/ 20 | nytprof.out 21 | .~ko-6.1.3-perllint~ 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | perl: 3 | - "5.30" 4 | - "5.28" 5 | - "5.26" 6 | - "5.24" 7 | - "5.22" 8 | - "5.20" 9 | - "5.18" 10 | - "5.16" 11 | - "5.14" 12 | 13 | notifications: 14 | irc: 15 | channels: 16 | - "chat.freenode.net#Monitoring-Plugins" 17 | on_success: change 18 | on_failure: always 19 | skip_join: true 20 | email: 21 | - team@monitoring-plugins.org 22 | 23 | 24 | -------------------------------------------------------------------------------- /notes: -------------------------------------------------------------------------------- 1 | RELEASING 2 | 3 | Change version number in lib/Monitoring/Plugin.pm and lib/Monitoring/Plugin/Functions.pm 4 | Add date to Changes file 5 | git commit 6 | 7 | perl Makefile.PL 8 | make 9 | make test 10 | make dist 11 | 12 | Upload file to CPAN 13 | 14 | Send announcement to announce@monitoring-plugins.org, help@monitoring-plugins.org and 15 | devel@monitoring-plugins.org 16 | Add news item to https://monitoring-plugins.org 17 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-05.t: -------------------------------------------------------------------------------- 1 | # Check for exported vars 2 | # Can't include Monitoring::Plugin::Functions because it also exports %STATUS_TEXT 3 | 4 | use strict; 5 | use Test::More tests=>4; 6 | 7 | BEGIN { use_ok('Monitoring::Plugin') }; 8 | 9 | eval ' $_ = $STATUS_TEXT{0} '; 10 | like( $@, '/Global symbol "%STATUS_TEXT" requires explicit package name/' ); 11 | 12 | use_ok("Monitoring::Plugin", qw(%STATUS_TEXT)); 13 | 14 | eval ' $_ = $STATUS_TEXT{0} '; 15 | is( $@, '' ); 16 | -------------------------------------------------------------------------------- /t/npg03/plugins.ini: -------------------------------------------------------------------------------- 1 | [check_mysql] 2 | username=tonvoon 3 | password=secret 4 | 5 | [more_options] 6 | username=altinity 7 | warning=10 8 | critical=15 9 | 10 | [check_disk] 11 | p=/tmp 12 | 13 | [check_2_disks] 14 | p=/tmp 15 | p=/var 16 | 17 | [check_2_disks_reprise] 18 | p=/var 19 | p=/tmp 20 | 21 | [check_disk2] 22 | path=/var 23 | path=/home 24 | units=GB 25 | 26 | [funny_stuff] 27 | username="Ton Voon" 28 | p= 29 | expect=" space in front" 30 | 31 | # Test 3 parameters 32 | [check_disk3] 33 | p=/ 34 | p=/var 35 | p=/tmp 36 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Performance-02.t: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | use Test::More tests => 3; 4 | use_ok("Monitoring::Plugin::Performance", use_die => 1); 5 | 6 | eval { Monitoring::Plugin::Functions::plugin_die("Testing") }; 7 | is( $@, "MONITORING-PLUGIN-PERFORMANCE-02 UNKNOWN - Testing\n", "use_die correctly set on import"); 8 | 9 | 10 | use_ok("Monitoring::Plugin::Performance"); 11 | eval { Monitoring::Plugin::Functions::plugin_die("Test OK exit", 0) }; 12 | 13 | fail("Should not get here if code works correctly because prior plugin_die should have exited"); 14 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use inc::Module::Install; 2 | 3 | name 'Monitoring-Plugin'; 4 | all_from 'lib/Monitoring/Plugin.pm'; 5 | author 'Monitoring Plugin Team '; 6 | license 'perl'; 7 | repository 'https://github.com/monitoring-plugins/monitoring-plugin-perl'; 8 | 9 | requires 'Params::Validate' => 0; 10 | requires 'Class::Accessor' => 0; 11 | requires 'Carp' => 0; 12 | requires 'Config::Tiny' => 0; 13 | requires 'File::Spec' => 0; 14 | requires 'File::Basename' => 0; 15 | requires 'IO::File' => 0; 16 | requires 'Math::Calc::Units' => 0; # used in M::P::Performance 17 | 18 | 19 | build_requires 'Test::More' => 0.62; 20 | 21 | WriteAll; 22 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Functions-03.t: -------------------------------------------------------------------------------- 1 | # max_state tests 2 | 3 | use strict; 4 | use Test::More tests => 8; 5 | 6 | BEGIN { use_ok("Monitoring::Plugin::Functions", ":all") } 7 | 8 | my $new_state = max_state( OK, WARNING ); 9 | 10 | is( $new_state, WARNING, "Moved up to WARNING" ); 11 | is( max_state( $new_state, UNKNOWN ), WARNING, "Still at WARNING" ); 12 | 13 | $new_state = max_state( $new_state, CRITICAL ); 14 | is( $new_state, CRITICAL, "Now at CRITICAL" ); 15 | is( max_state( OK, OK ), OK, "This is OK" ); 16 | 17 | is( max_state( OK, UNKNOWN ), OK, "This is still OK, not UNKNOWN" ); 18 | 19 | is( max_state( OK, OK, OK, OK, OK, WARNING ), WARNING, "Use WARNING in this list" ); 20 | 21 | is( max_state(), UNKNOWN, "Return UNKNOWN if no parameters" ); 22 | -------------------------------------------------------------------------------- /t/npg03/README: -------------------------------------------------------------------------------- 1 | Monitoring-Plugin-Getopt-03.t automatically tests all cases defined in 2 | the 'input' directory and expects the output to match the 3 | corresponding file in the 'expected' directory. To define a new test 4 | case, just create a new file in the 'input' directory containing the 5 | input command line, and a corresponding file in the 'expected' 6 | directory containing what you think the expanded command line should 7 | be. Note that this expansion is normalised as follows: 8 | 9 | - command line arguments are reported in alphabetical order 10 | - extraneous white space is removed 11 | 12 | Also, if you use a completely new argument than those currently 13 | defined in Monitoring-Plugin-Getopt-03.t you will need to define it 14 | there as well. 15 | 16 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Functions-04.t: -------------------------------------------------------------------------------- 1 | # max_state_alt tests 2 | 3 | use strict; 4 | use Test::More tests => 8; 5 | 6 | BEGIN { use_ok("Monitoring::Plugin::Functions", ":all") } 7 | 8 | my $new_state = max_state_alt( OK, WARNING ); 9 | 10 | is( $new_state, WARNING, "Moved up to WARNING" ); 11 | is( max_state_alt( $new_state, UNKNOWN ), WARNING, "Still at WARNING" ); 12 | 13 | $new_state = max_state_alt( $new_state, CRITICAL ); 14 | is( $new_state, CRITICAL, "Now at CRITICAL" ); 15 | is( max_state_alt( OK, OK ), OK, "This is OK" ); 16 | 17 | is( max_state_alt( OK, UNKNOWN ), UNKNOWN, "This is UNKNOWN" ); 18 | 19 | is( max_state_alt( OK, OK, OK, OK, OK, WARNING ), WARNING, "Use WARNING in this list" ); 20 | 21 | is( max_state_alt(), UNKNOWN, "Return UNKNOWN if no parameters" ); 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Monitoring::Plugin 2 | ================== 3 | 4 | These modules are meant for perl developers of plugins for Naemon, Nagios, 5 | Icinga, Shinken and other compatible products. It is meant to 6 | simplify a lot of the common functions required to do checking of a 7 | particular service. 8 | This module is maintained by the Monitoring-Plugins team 9 | (https://monitoring-plugins.org) 10 | 11 | INSTALLATION 12 | 13 | To install this module type the following: 14 | 15 | perl Makefile.PL 16 | make 17 | make test 18 | make install 19 | 20 | 21 | EXAMPLE SCRIPT 22 | 23 | "Enough talk! Show me where to start!" 24 | 25 | See the file 'check_stuff.pl' in the 't' directory for a complete 26 | working example of a plugin script. 27 | 28 | 29 | COPYRIGHT AND LICENCE 30 | 31 | Copyright (C) 2014 by Monitoring Plugin Team 32 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 33 | 34 | This library is free software; you can redistribute it and/or modify 35 | it under the same terms as Perl itself, either Perl version 5.8.4 or, 36 | at your option, any later version of Perl 5 you may have available. 37 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Getopt-02.t: -------------------------------------------------------------------------------- 1 | # Monitoring::Plugin::Getopt timeout tests 2 | 3 | use strict; 4 | 5 | use Test::More tests => 14; 6 | BEGIN { use_ok('Monitoring::Plugin::Getopt') }; 7 | 8 | # Needed to get evals to work in testing 9 | Monitoring::Plugin::Functions::_use_die(1); 10 | 11 | my %PARAM = ( 12 | version => '0.01', 13 | url => 'http://www.openfusion.com.au/labs/nagios/', 14 | blurb => 'This plugin tests various stuff.', 15 | usage => "Usage: %s -H -w 16 | -c ", 17 | plugin => 'test_plugin', 18 | timeout => 18, 19 | ); 20 | 21 | sub setup 22 | { 23 | # Instantiate object 24 | my $ng = Monitoring::Plugin::Getopt->new(%PARAM); 25 | ok($ng, 'constructor ok'); 26 | return $ng; 27 | } 28 | 29 | my $ng; 30 | 31 | # No args 32 | @ARGV = qw(); 33 | $ng = setup(); 34 | $ng->getopts; 35 | is($ng->timeout, 18, 'default timeout set to 18'); 36 | 37 | # Check help message 38 | @ARGV = ( '-h' ); 39 | $ng = setup; 40 | ok(! defined eval { $ng->getopts }, 'getopts died on help'); 41 | like($@, qr/times out.*default: 18\b/i, 'help timeout changed to 18'); 42 | 43 | # Explicit timeout 44 | @ARGV = qw(--timeout=25 --verbose); 45 | $ng = setup(); 46 | $ng->getopts; 47 | is($ng->timeout, 25, 'timeout changed to 25'); 48 | 49 | # Explicit timeout 50 | @ARGV = qw(-t10 --verbose); 51 | $ng = setup(); 52 | $ng->getopts; 53 | is($ng->timeout, 10, 'timeout changed to 10'); 54 | 55 | # Short timeout, test default timeout handler 56 | @ARGV = qw(-t2 --verbose); 57 | $ng = setup(); 58 | $ng->getopts; 59 | is($ng->timeout, 2, 'timeout changed to 2'); 60 | alarm($ng->timeout); 61 | # Loop 62 | ok(! defined eval { 1 while 1 }, 'loop timed out'); 63 | like($@, qr/UNKNOWN\b.*\btimed out/, 'default timeout handler ok'); 64 | -------------------------------------------------------------------------------- /t/check_stuff.t: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl 2 | # 3 | use strict; use warnings; 4 | #use Test::More qw(no_plan); 5 | use Test::More tests => 14; 6 | 7 | my ($r,$args); 8 | my $s = 't/check_stuff.pl'; 9 | $s = "$^X -Ilib $s"; 10 | 11 | my $n = 'STUFF'; 12 | 13 | # Monitoring status strings and exit codes 14 | my %e = qw( 15 | OK 0 16 | WARNING 1 17 | CRITICAL 2 18 | UNKNOWN 3 19 | ); 20 | 21 | $r = `$s`; 22 | is $?>>8 , $e{UNKNOWN}, "exits($e{UNKNOWN}) with no args"; 23 | like $r, qr/^$n UNKNOWN/, "UNKNOWN with no args"; 24 | 25 | $r = `$s -V`; 26 | is $?>>8 , $e{UNKNOWN}, "exits($e{UNKNOWN}) with -V arg"; 27 | like $r, qr/^[\w\.]+ \d+/i, "looks like there's a version"; 28 | 29 | $r = `$s -h`; 30 | is $?>>8 , $e{UNKNOWN}, "exits($e{UNKNOWN}) with -h arg"; 31 | like $r, qr/usage/i, "looks like there's something helpful"; # broken 32 | 33 | $args = " -r 99 "; 34 | diag "running `$s $args`" if $ENV{TEST_VERBOSE}; 35 | $r = `$s $args`; 36 | diag "output: '$r'" if $ENV{TEST_VERBOSE}; 37 | is $?>>8 , $e{UNKNOWN}, "exits($e{UNKNOWN}) with $args"; 38 | like $r, qr/UNKNOWN.+invalid/i, "UNKNOWN (warning: invalid -r) with $args"; 39 | 40 | 41 | my $expected = { 42 | " -w 10:15 -c~:15 -r 0" => 'WARNING', 43 | " -w 10:15 -c~:15 -r 11" => 'OK', 44 | " -w 10:15 -c~:15 -r 15.8" => 'CRITICAL', 45 | }; 46 | 47 | test_expected( $s, $expected ); 48 | 49 | 50 | sub test_expected { 51 | my $s = shift; 52 | my $expected = shift; 53 | foreach ( keys %$expected ) { 54 | diag "running `$s $_`" if $ENV{TEST_VERBOSE}; 55 | $r = `$s $_`; 56 | diag "output: '$r'" if $ENV{TEST_VERBOSE}; 57 | is $?>>8 , $e{$expected->{$_}}, "exits($e{$expected->{$_}}) with $_"; 58 | like $r, qr/^$n $expected->{$_}/i, "looks $expected->{$_} with $_"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin/ExitResult.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin::ExitResult; 2 | 3 | # Tiny helper class to return both output and return_code when testing 4 | 5 | use 5.006; 6 | use strict; 7 | use warnings; 8 | 9 | # Stringify to message 10 | use overload '""' => sub { shift->{message} }; 11 | 12 | # Constructor 13 | sub new { 14 | my $class = shift; 15 | return bless { return_code => $_[0], message => $_[1] }, $class; 16 | } 17 | 18 | # Accessors 19 | sub message { shift->{message} } 20 | sub return_code { shift->{return_code} } 21 | sub code { shift->{return_code} } 22 | 23 | 1; 24 | 25 | __END__ 26 | 27 | =head1 NAME 28 | 29 | Monitoring::Plugin::ExitResult - Helper class for returning both output and 30 | return codes when testing. 31 | 32 | =head1 SYNOPSIS 33 | 34 | use Test::More; 35 | use Monitoring::Plugin::Functions; 36 | 37 | # In a test file somewhere 38 | Monitoring::Plugin::Functions::_fake_exit(1); 39 | 40 | # Later ... 41 | $e = plugin_exit( CRITICAL, 'aiiii ...' ); 42 | print $e->message; 43 | print $e->return_code; 44 | 45 | # MP::ExitResult also stringifies to the message output 46 | like(plugin_exit( WARNING, 'foobar'), qr/^foo/, 'matches!'); 47 | 48 | 49 | 50 | =head1 DESCRIPTION 51 | 52 | Monitoring::Plugin::ExitResult is a tiny helper class intended for use 53 | when testing other Monitoring::Plugin modules. A Monitoring::Plugin::ExitResult 54 | object is returned by plugin_exit() and friends when 55 | Monitoring::Plugin::Functions::_fake_exit has been set, instead of doing a 56 | conventional print + exit. 57 | 58 | =head1 AUTHOR 59 | 60 | This code is maintained by the Monitoring Plugin Development Team: see 61 | https://monitoring-plugins.org 62 | 63 | Originally: 64 | Gavin Carr , Egavin@openfusion.com.auE 65 | 66 | =head1 COPYRIGHT AND LICENSE 67 | 68 | Copyright (C) 2014 by Monitoring Plugin Team 69 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 70 | 71 | This library is free software; you can redistribute it and/or modify 72 | it under the same terms as Perl itself. 73 | 74 | =cut 75 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | Changes 2 | inc/Module/Install.pm 3 | inc/Module/Install/Base.pm 4 | inc/Module/Install/Can.pm 5 | inc/Module/Install/Fetch.pm 6 | inc/Module/Install/Makefile.pm 7 | inc/Module/Install/Metadata.pm 8 | inc/Module/Install/Win32.pm 9 | inc/Module/Install/WriteAll.pm 10 | lib/Monitoring/Plugin.pm 11 | lib/Monitoring/Plugin/Config.pm 12 | lib/Monitoring/Plugin/ExitResult.pm 13 | lib/Monitoring/Plugin/Functions.pm 14 | lib/Monitoring/Plugin/Getopt.pm 15 | lib/Monitoring/Plugin/Performance.pm 16 | lib/Monitoring/Plugin/Range.pm 17 | lib/Monitoring/Plugin/Threshold.pm 18 | Makefile.PL 19 | MANIFEST This list of files 20 | META.yml 21 | notes 22 | README 23 | t/check_stuff.pl 24 | t/check_stuff.t 25 | t/Monitoring-Plugin-01.t 26 | t/Monitoring-Plugin-02.t 27 | t/Monitoring-Plugin-03.t 28 | t/Monitoring-Plugin-04.t 29 | t/Monitoring-Plugin-05.t 30 | t/Monitoring-Plugin-Functions-01.t 31 | t/Monitoring-Plugin-Functions-02.t 32 | t/Monitoring-Plugin-Functions-03.t 33 | t/Monitoring-Plugin-Functions-04.t 34 | t/Monitoring-Plugin-Getopt-01.t 35 | t/Monitoring-Plugin-Getopt-02.t 36 | t/Monitoring-Plugin-Getopt-03.t 37 | t/Monitoring-Plugin-Getopt-04.t 38 | t/Monitoring-Plugin-Performance-02.t 39 | t/Monitoring-Plugin-Performance.t 40 | t/Monitoring-Plugin-Range.t 41 | t/Monitoring-Plugin-Threshold.t 42 | t/npg03/expected/00_basic 43 | t/npg03/expected/00_noextra 44 | t/npg03/expected/01_override1 45 | t/npg03/expected/02_override2 46 | t/npg03/expected/05_disk1 47 | t/npg03/expected/05_disk2 48 | t/npg03/expected/05_disk3 49 | t/npg03/expected/05_disk4 50 | t/npg03/expected/05_disk5 51 | t/npg03/expected/05_disk6 52 | t/npg03/expected/05_disk7 53 | t/npg03/expected/09_funnystuff 54 | t/npg03/expected/12_nosection_implicit 55 | t/npg03/expected/15_badsection_catch 56 | t/npg03/input/00_basic 57 | t/npg03/input/00_noextra 58 | t/npg03/input/01_override1 59 | t/npg03/input/02_override2 60 | t/npg03/input/05_disk1 61 | t/npg03/input/05_disk2 62 | t/npg03/input/05_disk3 63 | t/npg03/input/05_disk4 64 | t/npg03/input/05_disk5 65 | t/npg03/input/05_disk6 66 | t/npg03/input/05_disk7 67 | t/npg03/input/09_funnystuff 68 | t/npg03/input/12_nosection_implicit 69 | t/npg03/input/13_nosection_explicit_dies 70 | t/npg03/input/14_badsection_dies 71 | t/npg03/input/15_badsection_catch 72 | t/npg03/plugins.ini 73 | t/npg03/README 74 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-01.t: -------------------------------------------------------------------------------- 1 | # Monitoring::Plugin original test cases 2 | 3 | use strict; 4 | use Test::More tests => 15; 5 | 6 | BEGIN { use_ok('Monitoring::Plugin') }; 7 | 8 | use Monitoring::Plugin::Functions; 9 | Monitoring::Plugin::Functions::_fake_exit(1); 10 | 11 | diag "\nusing Monitoring::Plugin revision ". $Monitoring::Plugin::VERSION . "\n" 12 | if $ENV{TEST_VERBOSE}; 13 | 14 | my $p = Monitoring::Plugin->new(); 15 | isa_ok( $p, "Monitoring::Plugin"); 16 | 17 | $p->shortname("PAGESIZE"); 18 | is($p->shortname, "PAGESIZE", "shortname explicitly set correctly"); 19 | 20 | $p = Monitoring::Plugin->new(); 21 | is($p->shortname, "MONITORING-PLUGIN-01", "shortname should default on new"); 22 | 23 | $p = Monitoring::Plugin->new( shortname => "SIZE", () ); 24 | is($p->shortname, "SIZE", "shortname set correctly on new"); 25 | 26 | $p = Monitoring::Plugin->new( plugin => "check_stuff", () ); 27 | is($p->shortname, "STUFF", "shortname uses plugin name as default"); 28 | 29 | $p = Monitoring::Plugin->new( shortname => "SIZE", plugin => "check_stuff", () ); 30 | is($p->shortname, "SIZE", "shortname is not overriden by default"); 31 | 32 | diag "warn if < 10, critical if > 25 " if $ENV{TEST_VERBOSE}; 33 | my $t = $p->set_thresholds( warning => "10:25", critical => "~:25" ); 34 | 35 | use Data::Dumper; 36 | #diag "dumping p: ". Dumper $p; 37 | #diag "dumping perfdata: ". Dumper $p->perfdata; 38 | 39 | 40 | $p->add_perfdata( 41 | label => "size", 42 | value => 1, 43 | uom => "kB", 44 | threshold => $t, 45 | ); 46 | 47 | cmp_ok( $p->all_perfoutput, 'eq', "size=1kB;10:25;~:25", "Perfdata correct"); 48 | #diag "dumping perfdata: ". Dumper ($p->perfdata); 49 | 50 | $p->add_perfdata( 51 | label => "time", 52 | value => "3.52", 53 | threshold => $t, 54 | ); 55 | 56 | is( $p->all_perfoutput, "size=1kB;10:25;~:25 time=3.52;10:25;~:25", "Perfdata correct when no uom specified"); 57 | 58 | my $expected = {qw( 59 | -1 WARNING 60 | 1 WARNING 61 | 20 OK 62 | 25 OK 63 | 26 CRITICAL 64 | 30 CRITICAL 65 | )}; 66 | 67 | foreach (sort {$a<=>$b} keys %$expected) { 68 | like $p->die( return_code => $t->get_status($_), message => "page size at http://... was ${_}kB" ), 69 | qr/$expected->{$_}/, 70 | "Output okay. $_ = $expected->{$_}" ; 71 | } 72 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Getopt-03.t: -------------------------------------------------------------------------------- 1 | # Monitoring::Plugin::Getopt --extra-opts tests 2 | 3 | use strict; 4 | use File::Spec; 5 | use File::Basename; 6 | use IO::File; 7 | 8 | use Test::More qw(no_plan); 9 | BEGIN { use_ok('Monitoring::Plugin::Getopt') }; 10 | 11 | # Needed to get evals to work in testing 12 | Monitoring::Plugin::Functions::_use_die(1); 13 | 14 | my $tdir = 'npg03'; 15 | if (! -d $tdir) { 16 | my $ttdir = File::Spec->catdir('t', $tdir); 17 | die "missing '$tdir' directory\n" unless -d $ttdir; 18 | $tdir = $ttdir; 19 | } 20 | 21 | # Load expected files 22 | my %EXPECTED = (); 23 | for my $efile (glob File::Spec->catfile($tdir, 'expected', '*')) { 24 | my $fh = IO::File->new($efile, 'r') or die "Cannot open input file '$efile': $!"; 25 | if (my $cmd = $fh->getline()) { # First line only! 26 | chomp $cmd; 27 | $cmd =~ s/^\s+//; 28 | $cmd =~ s/\s+$//; 29 | $EXPECTED{ basename($efile) } = $cmd; 30 | } 31 | } 32 | 33 | # Override MONITORING_CONFIG_PATH to use our test plugins.ini file 34 | $ENV{MONITORING_CONFIG_PATH} = "/random/bogus/path:$tdir"; 35 | 36 | my %PARAM = ( 37 | version => '0.01', 38 | blurb => 'This plugin tests various stuff.', 39 | usage => "Usage: %s -H -w 40 | -c ", 41 | ); 42 | 43 | sub ng_setup 44 | { 45 | my $arg = shift; 46 | 47 | # Instantiate object 48 | my $ng = Monitoring::Plugin::Getopt->new(%PARAM); 49 | 50 | if (ref $arg eq 'ARRAY' && @$arg) { 51 | $ng->arg(%$_) foreach @$arg; 52 | } 53 | 54 | return $ng; 55 | } 56 | 57 | # Setup our Monitoring::Plugin::Getopt object 58 | my $ng; 59 | my $arg = [ 60 | { spec => 'S', help => '-S' }, 61 | { spec => 'H=s', help => '-H' }, 62 | { spec => 'p=s@', help => '-p' }, 63 | { spec => 'path=s@', help => '--path' }, 64 | { spec => 'username|u=s', help => '--username' }, 65 | { spec => 'password=s', help => '--password' }, 66 | { spec => 'critical=s', help => '--critical' }, 67 | { spec => 'warning=s', help => '--warning' }, 68 | { spec => 'expect=s', help => '--expect' }, 69 | { spec => 'units=s', help => '--units' }, 70 | ]; 71 | 72 | #my %SKIP = map { $_ => 1 } qw(05_singlechar1 07_singlechar3); 73 | #my %SKIP = map { $_ => 1 } qw(06_singlechar2); 74 | my %SKIP = (); 75 | 76 | # Process all test cases in $tdir/input 77 | my $glob = $ARGV[0] || '*'; 78 | for my $infile (glob File::Spec->catfile($tdir, 'input', $glob)) { 79 | $ng = ng_setup($arg); 80 | 81 | my $fh = IO::File->new($infile, 'r') or die "Cannot open input file '$infile': $!"; 82 | $infile = basename($infile); 83 | 84 | if (my $cmd = $fh->getline()) { # First line only! 85 | $cmd =~ s/^\s+//; 86 | my ($plugin, @args) = split /\s+/, $cmd; 87 | 88 | # Fake out the plugin name 89 | $ng->{_attr}->{plugin} = $plugin; 90 | 91 | # Parse the options 92 | SKIP: { 93 | skip "Skipping ..." if $SKIP{$infile}; 94 | 95 | @ARGV = @args; 96 | eval { $ng->getopts }; 97 | if ($@) { 98 | chomp $@; 99 | ok($infile =~ m/_(dies?|catch)$/, "$infile ($@)"); 100 | my $expect = $EXPECTED{$infile}; 101 | # windows expects backslashes fixes rt.cpan #100708 102 | $expect =~ s#/#\\#gmx if $^O =~ m/^MSWin/; 103 | is($@, $expect, $infile) if ($infile =~ m/_catch$/); 104 | } 105 | else { 106 | is($plugin . ' ' . $ng->_cmdline, $EXPECTED{$infile}, $infile); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-04.t: -------------------------------------------------------------------------------- 1 | 2 | # tests for toplevel access to Threshold and GetOpts stuff 3 | 4 | use strict; 5 | #use Test::More 'no_plan'; 6 | use Test::More tests=>30; 7 | 8 | BEGIN { use_ok('Monitoring::Plugin') }; 9 | use Monitoring::Plugin::Functions; 10 | Monitoring::Plugin::Functions::_fake_exit(1); 11 | 12 | 13 | eval { Monitoring::Plugin->new(); }; 14 | ok(! $@, "constructor DOESN'T die without usage"); 15 | 16 | my $p = Monitoring::Plugin->new(); 17 | eval { $p->add_arg('warning', 'warning') }; 18 | ok($@, "add_arg() dies if you haven't instantiated with usage"); 19 | eval { $p->getopts }; 20 | ok($@, "getopts() dies if you haven't instantiated with usage"); 21 | 22 | $p = Monitoring::Plugin->new( usage => "dummy usage statement" ); 23 | 24 | # option accessors work 25 | can_ok $p, 'opts'; 26 | isa_ok $p->opts, 'Monitoring::Plugin::Getopt', "Getopt object is defined"; 27 | 28 | $p->add_arg('warning|w=s', "warning"); 29 | $p->add_arg('critical|c=s', "critical"); 30 | 31 | @ARGV = qw(-w 5 -c 10); 32 | $p->getopts; 33 | is $p->opts->warning, "5", "warning opt is accessible"; 34 | is $p->opts->critical, "10", "critical opt is accessible"; 35 | 36 | 37 | can_ok $p, 'perfdata'; 38 | #isa_ok $p->perfdata, 'Monitoring::Plugin::Performance', "perfdata object is defined"; 39 | 40 | 41 | can_ok $p, 'threshold'; 42 | #isa_ok $p->threshold, 'Monitoring::Plugin::Threshold', "threshold object is defined"; 43 | 44 | 45 | eval { $p->check_threshold() }; 46 | ok($@, "check_threshold dies if called with no args"); 47 | 48 | 49 | # thresholds set implicitly 50 | is $p->check_threshold(2), OK, "check_threshold OK when called implicitly"; 51 | is $p->check_threshold(6), WARNING, "check_threshold WARNING"; 52 | is $p->check_threshold(11), CRITICAL, "check_threshold CRITICAL"; 53 | is $p->check_threshold(check=>11), CRITICAL, "check_threshold CRITICAL with hash param"; 54 | 55 | # Check that arrays allowed 56 | is $p->check_threshold([2,1]), OK, "check_threshold OK when called implicitly"; 57 | is $p->check_threshold([6,2]), WARNING, "check_threshold WARNING"; 58 | is $p->check_threshold([1,2,6,11]), CRITICAL, "check_threshold CRITICAL"; 59 | is $p->check_threshold(check=>[1,2,6,11]), CRITICAL, "check_threshold CRITICAL with hash param"; 60 | 61 | # thresholds set explicitly 62 | is $p->check_threshold( 63 | check => 2, 64 | warning => 50, 65 | critical => 100 66 | ), OK, "check_threshold explicit OK"; 67 | 68 | is $p->check_threshold( 69 | check => 66, 70 | warning => 50, 71 | critical => 100 72 | ), WARNING, "check_threshold explicit WARNING"; 73 | 74 | 75 | is $p->check_threshold( 76 | check => -1, 77 | warning => 5, 78 | critical => '0:5', 79 | ), CRITICAL, "check_threshold explicit CRITICAL"; 80 | 81 | 82 | 83 | # what happens if you forget to define warning or critical thresholds? 84 | $p = undef; 85 | $p = Monitoring::Plugin->new(); 86 | 87 | is $p->check_threshold(2), UNKNOWN, "everything is now UNKNOWN"; 88 | is $p->check_threshold(-200), UNKNOWN, "everything is now UNKNOWN"; 89 | is $p->check_threshold(134098.3124), UNKNOWN, "everything is now UNKNOWN"; 90 | is $p->check_threshold("foo bar baz"), UNKNOWN, "everything is now UNKNOWN"; 91 | 92 | 93 | # how about when you define just one? 94 | 95 | $p->set_thresholds(warning => "10:25"); 96 | is $p->check_threshold(2), WARNING, "check_threshold works (WARNING) after explicit set_thresholds"; 97 | is $p->check_threshold(-200), WARNING, "and again"; 98 | is $p->check_threshold(25.5), WARNING, "and again"; 99 | is $p->check_threshold(11), OK, "now OK"; 100 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin/Threshold.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin::Threshold; 2 | 3 | use 5.006; 4 | use strict; 5 | use warnings; 6 | 7 | use base qw(Class::Accessor::Fast); 8 | __PACKAGE__->mk_accessors(qw(warning critical)); 9 | 10 | use Monitoring::Plugin::Range; 11 | use Monitoring::Plugin::Functions qw(:codes plugin_die); 12 | our ($VERSION) = $Monitoring::Plugin::Functions::VERSION; 13 | 14 | sub get_status 15 | { 16 | my ($self, $value) = @_; 17 | 18 | $value = [ $value ] if (ref $value eq ""); 19 | foreach my $v (@$value) { 20 | if ($self->critical->is_set) { 21 | return CRITICAL if $self->critical->check_range($v); 22 | } 23 | } 24 | foreach my $v (@$value) { 25 | if ($self->warning->is_set) { 26 | return WARNING if $self->warning->check_range($v); 27 | } 28 | } 29 | return OK; 30 | } 31 | 32 | sub _inflate 33 | { 34 | my ($self, $value, $key) = @_; 35 | 36 | # Return an undefined range if $value is undef 37 | return Monitoring::Plugin::Range->new if ! defined $value; 38 | 39 | # For refs, check isa N::P::Range 40 | if (ref $value) { 41 | plugin_die("Invalid $key object: type " . ref $value) 42 | unless $value->isa("Monitoring::Plugin::Range"); 43 | return $value; 44 | } 45 | 46 | # Another quick exit if $value is an empty string 47 | return Monitoring::Plugin::Range->new if $value eq ""; 48 | 49 | # Otherwise parse $value 50 | my $range = Monitoring::Plugin::Range->parse_range_string($value); 51 | plugin_die("Cannot parse $key range: '$value'") unless(defined($range)); 52 | return $range; 53 | } 54 | 55 | sub set_thresholds 56 | { 57 | my ($self, %arg) = @_; 58 | 59 | # Equals new() as a class method 60 | return $self->new(%arg) unless ref $self; 61 | 62 | # On an object, just acts as special mutator 63 | $self->set($_, $arg{$_}) foreach qw(warning critical); 64 | } 65 | 66 | sub set 67 | { 68 | my $self = shift; 69 | my ($key, $value) = @_; 70 | $self->SUPER::set($key, $self->_inflate($value, $key)); 71 | } 72 | 73 | # Constructor - inflate scalars to N::P::Range objects 74 | sub new 75 | { 76 | my ($self, %arg) = @_; 77 | $self->SUPER::new({ 78 | map { $_ => $self->_inflate($arg{$_}, $_) } qw(warning critical) 79 | }); 80 | } 81 | 82 | 1; 83 | 84 | __END__ 85 | 86 | =head1 NAME 87 | 88 | Monitoring::Plugin::Threshold - class for handling Monitoring::Plugin thresholds. 89 | 90 | =head1 SYNOPSIS 91 | 92 | # NB: This is an internal Monitoring::Plugin class. 93 | # See Monitoring::Plugin itself for public interfaces. 94 | 95 | # Constructor 96 | $t = Monitoring::Plugin::Threshold->set_thresholds( 97 | warning => $warning_range_string, 98 | critical => $critical_range_string, 99 | ); 100 | 101 | # Value checking - returns CRITICAL if in the critical range, 102 | # WARNING if in the warning range, and OK otherwise 103 | $status = $t->get_status($value); 104 | 105 | # Accessors - return the associated N::P::Range object 106 | $warning_range = $t->warning; 107 | $critical_range = $t->critical; 108 | 109 | 110 | =head1 DESCRIPTION 111 | 112 | Internal Monitoring::Plugin class for handling threshold data. See 113 | Monitoring::Plugin for public interfaces. 114 | 115 | A threshold object contains (typically) a pair of ranges, associated 116 | with a particular severity e.g. 117 | 118 | warning => range1 119 | critical => range2 120 | 121 | =head1 AUTHOR 122 | 123 | This code is maintained by the Monitoring Plugin Development Team: see 124 | https://monitoring-plugins.org 125 | 126 | =head1 COPYRIGHT AND LICENSE 127 | 128 | Copyright (C) 2014 by Monitoring Plugin Team 129 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 130 | 131 | This library is free software; you can redistribute it and/or modify 132 | it under the same terms as Perl itself. 133 | 134 | =cut 135 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Getopt-04.t: -------------------------------------------------------------------------------- 1 | # Monitoring::Plugin::Getopt spec-to-help generation tests 2 | 3 | use strict; 4 | 5 | use Test::More tests => 15; 6 | BEGIN { use_ok('Monitoring::Plugin::Getopt') }; 7 | 8 | # Needed to get evals to work in testing 9 | Monitoring::Plugin::Functions::_use_die(1); 10 | 11 | my %PARAM = ( 12 | version => '0.01', 13 | usage => "Don't use this plugin!", 14 | ); 15 | 16 | sub setup 17 | { 18 | # Instantiate object 19 | my $ng = Monitoring::Plugin::Getopt->new(%PARAM); 20 | ok($ng, 'constructor ok'); 21 | 22 | # Positional args, no short arguments, INTEGER 23 | $ng->arg('warning=i' => 24 | qq(Exit with WARNING status if less than INTEGER foobars are free), 25 | 5); 26 | 27 | # Named args, long + short arguments, INTEGER 28 | $ng->arg( 29 | spec => 'critical|c=i', 30 | help => qq(Exit with CRITICAL status if less than INTEGER foobars are free), 31 | required => 1, 32 | ); 33 | 34 | # Named args, multiple short arguments, STRING, default expansion 35 | $ng->arg( 36 | spec => 'x|y|z=s', 37 | help => qq(Foobar. Default: %s), 38 | default => "XYZ", 39 | ); 40 | 41 | # Named args, multiple mixed, no label 42 | $ng->arg( 43 | spec => 'long|longer|longest|l', 44 | help => qq(Long format), 45 | ); 46 | 47 | # Named args, long + short, explicit label 48 | $ng->arg( 49 | spec => 'hostname|H=s', 50 | label => 'ADDRESS', 51 | help => qq(Hostname), 52 | ); 53 | 54 | # Positional args, long only, explicit label 55 | $ng->arg('avatar=s', 'Avatar', undef, undef, 'AVATAR'); 56 | 57 | # Multiline help test, named args 58 | $ng->arg( 59 | spec => 'disk=s', 60 | label => [ qw(BYTES PERCENT%), undef ], 61 | help => [ 62 | qq(Disk limit in BYTES), 63 | qq(Disk limit in PERCENT), 64 | qq(Disk limit in FOOBARS (Default: %s)), 65 | ], 66 | default => 1024, 67 | ); 68 | 69 | # Multiline help test, positional args 70 | $ng->arg( 71 | 'limit=s', 72 | [ 73 | qq(Limit in BYTES), 74 | qq(Limit in PERCENT), 75 | ], 76 | undef, 77 | undef, 78 | [ undef, 'PERCENT%' ], 79 | ); 80 | 81 | # Named args with *optional* but pre-set value 82 | $ng->arg( 83 | spec => 'dirport|d:9030', 84 | help => 'dirport', 85 | ); 86 | 87 | # Named args with *optional* string value 88 | $ng->arg( 89 | spec => 'enablesomething|s:s', 90 | help => 'something', 91 | ); 92 | 93 | # Named args with *optional* integer value (same as ":0") 94 | $ng->arg( 95 | spec => 'testtimeout|T:i', 96 | help => 'testtimeout', 97 | ); 98 | 99 | # Named args with *optional* but increasing integer value 100 | $ng->arg( 101 | spec => 'verbosity|v:+', 102 | help => 'verbosity', 103 | ); 104 | 105 | return $ng; 106 | } 107 | 108 | my $ng; 109 | 110 | @ARGV = ( '--help' ); 111 | $ng = setup; 112 | ok(! defined eval { $ng->getopts }, 'getopts died on help'); 113 | like($@, qr/\n --warning=INTEGER/, 'warning ok'); 114 | like($@, qr/\n -c, --critical=INTEGER/, 'critical ok'); 115 | like($@, qr/\n -x, -y, -z=STRING\n Foobar. Default: XYZ\n/, 'x|y|z ok'); 116 | like($@, qr/\n -l, --long, --longer, --longest\n Long format\n/, 'long ok'); 117 | like($@, qr/\n -H, --hostname=ADDRESS\n Hostname\n/, 'hostname ok'); 118 | like($@, qr/\n --avatar=AVATAR\n Avatar\n/, 'avatar ok'); 119 | like($@, qr/\n --disk=BYTES\n Disk limit in BYTES\n --disk=PERCENT%\n Disk limit in PERCENT\n --disk=STRING\n Disk limit in FOOBARS \(Default: 1024\)\n/, 'disk multiline ok'); 120 | like($@, qr/\n --limit=STRING\n Limit in BYTES\n --limit=PERCENT%\n Limit in PERCENT\n/, 'limit multiline ok'); 121 | like($@, qr/\n -d, --dirport\[=INTEGER\]/, 'dirport ok'); 122 | like($@, qr/\n -s, --enablesomething\[=STRING\]/, 'enablesomething ok'); 123 | like($@, qr/\n -T, --testtimeout\[=INTEGER\]/, 'testtimeout ok'); 124 | like($@, qr/\n -v, --verbosity\[=INTEGER\]/, 'verbosity ok'); 125 | #print $@; 126 | -------------------------------------------------------------------------------- /t/check_stuff.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl 2 | 3 | ### check_stuff.pl 4 | 5 | # an example plugin using the Monitoring::Plugin module. 6 | 7 | # Originally by Nathan Vonnahme, n8v at users dot sourceforge 8 | # dot net, July 19 2006 9 | 10 | # Please modify to your heart's content and use as the basis for all 11 | # the really cool monitoring scripts you're going to create. 12 | # You rock. 13 | 14 | ############################################################################## 15 | # prologue 16 | use strict; 17 | use warnings; 18 | 19 | use Monitoring::Plugin; 20 | 21 | use vars qw($VERSION $PROGNAME $verbose $warn $critical $timeout $result); 22 | $VERSION = '1.0'; 23 | 24 | # get the base name of this script for use in the examples 25 | use File::Basename; 26 | $PROGNAME = basename($0); 27 | 28 | 29 | ############################################################################## 30 | # define and get the command line options. 31 | # see the command line option guidelines at 32 | # https://www.monitoring-plugins.org/doc/guidelines.html#PLUGOPTIONS 33 | 34 | 35 | # Instantiate Monitoring::Plugin object (the 'usage' parameter is mandatory) 36 | my $p = Monitoring::Plugin->new( 37 | usage => "Usage: %s [ -v|--verbose ] [-H ] [-t ] 38 | [ -c|--critical= ] 39 | [ -w|--warning= ] 40 | [ -r|--result = ]", 41 | version => $VERSION, 42 | blurb => 'This plugin is an example of a monitoring plugin written in Perl using the Monitoring::Plugin modules. It will generate a random integer between 1 and 20 (though you can specify the number with the -n option for testing), and will output OK, WARNING or CRITICAL if the resulting number is outside the specified thresholds.', 43 | 44 | extra => " 45 | 46 | THRESHOLDs for -w and -c are specified 'min:max' or 'min:' or ':max' 47 | (or 'max'). If specified '\@min:max', a warning status will be generated 48 | if the count *is* inside the specified range. 49 | 50 | See more threshold examples at https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT 51 | 52 | Examples: 53 | 54 | $PROGNAME -w 10 -c 18 Returns a warning 55 | if the resulting number is greater than 10, 56 | or a critical error 57 | if it is greater than 18. 58 | 59 | $PROGNAME -w 10 : -c 4 : Returns a warning 60 | if the resulting number is less than 10, 61 | or a critical error 62 | if it is less than 4. 63 | 64 | " 65 | ); 66 | 67 | 68 | # Define and document the valid command line options 69 | # usage, help, version, timeout and verbose are defined by default. 70 | 71 | $p->add_arg( 72 | spec => 'warning|w=s', 73 | 74 | help => 75 | qq{-w, --warning=INTEGER:INTEGER 76 | Minimum and maximum number of allowable result, outside of which a 77 | warning will be generated. If omitted, no warning is generated.}, 78 | 79 | # required => 1, 80 | # default => 10, 81 | ); 82 | 83 | $p->add_arg( 84 | spec => 'critical|c=s', 85 | help => 86 | qq{-c, --critical=INTEGER:INTEGER 87 | Minimum and maximum number of the generated result, outside of 88 | which a critical will be generated. }, 89 | ); 90 | 91 | $p->add_arg( 92 | spec => 'result|r=f', 93 | help => 94 | qq{-r, --result=INTEGER 95 | Specify the result on the command line rather than generating a 96 | random number. For testing.}, 97 | ); 98 | 99 | # Parse arguments and process standard ones (e.g. usage, help, version) 100 | $p->getopts; 101 | 102 | 103 | # perform sanity checking on command line options 104 | if ( (defined $p->opts->result) && ($p->opts->result < 0 || $p->opts->result > 20) ) { 105 | $p->plugin_die( " invalid number supplied for the -r option " ); 106 | } 107 | 108 | unless ( defined $p->opts->warning || defined $p->opts->critical ) { 109 | $p->plugin_die( " you didn't supply a threshold argument " ); 110 | } 111 | 112 | 113 | 114 | ############################################################################## 115 | # check stuff. 116 | 117 | # THIS is where you'd do your actual checking to get a real value for $result 118 | # don't forget to timeout after $p->opts->timeout seconds, if applicable. 119 | my $result; 120 | if (defined $p->opts->result) { # you got a 'result' option from the command line options 121 | $result = $p->opts->result; 122 | print " using supplied result $result from command line \n 123 | " if $p->opts->verbose; 124 | } 125 | else { 126 | $result = int rand(20)+1; 127 | print " generated random result $result\n " if $p->opts->verbose; 128 | } 129 | 130 | 131 | ############################################################################## 132 | # check the result against the defined warning and critical thresholds, 133 | # output the result and exit 134 | $p->plugin_exit( 135 | return_code => $p->check_threshold($result), 136 | message => " sample result was $result" 137 | ); 138 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin/Range.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin::Range; 2 | 3 | use 5.006; 4 | use strict; 5 | use warnings; 6 | 7 | use Carp; 8 | use base qw(Class::Accessor::Fast); 9 | __PACKAGE__->mk_accessors( 10 | qw(start end start_infinity end_infinity alert_on) 11 | ); 12 | 13 | use Monitoring::Plugin::Functions qw(:DEFAULT $value_re); 14 | our ($VERSION) = $Monitoring::Plugin::Functions::VERSION; 15 | 16 | use overload 17 | 'eq' => sub { shift->_stringify }, 18 | '""' => sub { shift->_stringify }; 19 | 20 | # alert_on constants (undef == range not set) 21 | use constant OUTSIDE => 0; 22 | use constant INSIDE => 1; 23 | 24 | sub _stringify { 25 | my $self = shift; 26 | return "" unless $self->is_set; 27 | return (($self->alert_on) ? "@" : "") . 28 | (($self->start_infinity == 1) ? "~:" : (($self->start == 0)?"":$self->start.":")) . 29 | (($self->end_infinity == 1) ? "" : $self->end); 30 | } 31 | 32 | sub is_set { 33 | my $self = shift; 34 | (! defined $self->alert_on) ? 0 : 1; 35 | } 36 | 37 | sub _set_range_start { 38 | my ($self, $value) = @_; 39 | $self->start($value+0); # Force scalar into number 40 | $self->start_infinity(0); 41 | } 42 | 43 | sub _set_range_end { 44 | my ($self, $value) = @_; 45 | $self->end($value+0); # Force scalar into number 46 | $self->end_infinity(0); 47 | } 48 | 49 | # Returns a N::P::Range object if the string is a conforms to a Monitoring Plugin range string, otherwise null 50 | sub parse_range_string { 51 | my ($class, $string) = @_; 52 | my $valid = 0; 53 | my $range = $class->new( start => 0, start_infinity => 0, end => 0, end_infinity => 1, alert_on => OUTSIDE); 54 | 55 | $string =~ s/\s//g; # strip out any whitespace 56 | # check for valid range definition 57 | unless ( $string =~ /[\d~]/ && $string =~ m/^\@?($value_re|~)?(:($value_re)?)?$/ ) { 58 | carp "invalid range definition '$string'"; 59 | return undef; 60 | } 61 | 62 | if ($string =~ s/^\@//) { 63 | $range->alert_on(INSIDE); 64 | } 65 | 66 | if ($string =~ s/^~//) { # '~:x' 67 | $range->start_infinity(1); 68 | } 69 | if ( $string =~ m/^($value_re)?:/ ) { # '10:' 70 | my $start = $1; 71 | $range->_set_range_start($start) if defined $start; 72 | $range->end_infinity(1); # overridden below if there's an end specified 73 | $string =~ s/^($value_re)?://; 74 | $valid++; 75 | } 76 | if ($string =~ /^($value_re)$/) { # 'x:10' or '10' 77 | $range->_set_range_end($string); 78 | $valid++; 79 | } 80 | 81 | if ($valid && ($range->start_infinity == 1 || $range->end_infinity == 1 || $range->start <= $range->end)) { 82 | return $range; 83 | } 84 | return undef; 85 | } 86 | 87 | # Returns 1 if an alert should be raised, otherwise 0 88 | sub check_range { 89 | my ($self, $value) = @_; 90 | my $false = 0; 91 | my $true = 1; 92 | if ($self->alert_on == INSIDE) { 93 | $false = 1; 94 | $true = 0; 95 | } 96 | if ($self->end_infinity == 0 && $self->start_infinity == 0) { 97 | if ($self->start <= $value && $value <= $self->end) { 98 | return $false; 99 | } else { 100 | return $true; 101 | } 102 | } elsif ($self->start_infinity == 0 && $self->end_infinity == 1) { 103 | if ( $value >= $self->start ) { 104 | return $false; 105 | } else { 106 | return $true; 107 | } 108 | } elsif ($self->start_infinity == 1 && $self->end_infinity == 0) { 109 | if ($value <= $self->end) { 110 | return $false; 111 | } else { 112 | return $true; 113 | } 114 | } else { 115 | return $false; 116 | } 117 | } 118 | 119 | # Constructor - map args to hashref for SUPER 120 | sub new 121 | { 122 | shift->SUPER::new({ @_ }); 123 | } 124 | 125 | 1; 126 | 127 | __END__ 128 | 129 | =head1 NAME 130 | 131 | Monitoring::Plugin::Range - class for handling Monitoring::Plugin range data. 132 | 133 | =head1 SYNOPSIS 134 | 135 | # NB: This is an internal Monitoring::Plugin class. 136 | # See Monitoring::Plugin itself for public interfaces. 137 | 138 | # Instantiate an empty range object 139 | $r = Monitoring::Plugin::Range->new; 140 | 141 | # Instantiate by parsing a standard nagios range string 142 | $r = Monitoring::Plugin::Range->parse_range_string( $range_str ); 143 | 144 | # Returns true if the range is defined/non-empty 145 | $r->is_set; 146 | 147 | # Returns true if $value matches range, false otherwise 148 | $r->check_range($value); 149 | 150 | 151 | =head1 DESCRIPTION 152 | 153 | Internal Monitoring::Plugin class for handling common range data. See 154 | Monitoring::Plugin for public interfaces. 155 | 156 | =head1 AUTHOR 157 | 158 | This code is maintained by the Monitoring Plugin Development Team: see 159 | https://monitoring-plugins.org 160 | 161 | =head1 COPYRIGHT AND LICENSE 162 | 163 | Copyright (C) 2014 by Monitoring Plugin Team 164 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 165 | 166 | This library is free software; you can redistribute it and/or modify 167 | it under the same terms as Perl itself. 168 | 169 | =cut 170 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl module Monitoring::Plugin. 2 | 3 | next: 4 | 0.40 25th July 2018 5 | - improve --extra-opts error handling (#18) 6 | - fix handling named arguments (#17) 7 | - Allow negation of command line arguments using '--no'-prefix (#13) 8 | - Fix plugin-name processing in ALRM handler. (#12) 9 | - Fixed regex in plugin_exit() that handles hyphen for LONGOUTPUT (#11) 10 | - Support LONGTEXT output in plugin_exit (#10) 11 | - convert empty perfdata values to 'U' (daku3649 #5) 12 | - fix output when there is only long plugin output (pdugas) 13 | 14 | 0.39 11th April 2015 15 | - fix help formating when using colons (Evgeni Golov) 16 | - fix "Redundant argument in sprintf" in perl 5.21 (RT #103214) 17 | 18 | 0.38 28th December 2014 19 | - fix getopt test on windows system 20 | 21 | 0.37 20nd January 2014 22 | - renamed module due to trademark issues 23 | 24 | 0.36 22nd December 2011 25 | - Updated check_threshold to allow multiple check values to be checked at once 26 | 27 | 0.35 3rd December 2010 28 | - Fixed test failures with Test::More 0.96 (Slaven Rezic and Peter John Edwards - RT57709) 29 | 30 | 0.34 15th April 2010 31 | - Amended standard --extra-opts help 32 | - pod fix (Frank Wiegand - RT51872) 33 | - Added %STATUS_TEXT to valid possible exports (Andrew Ford - RT46048) 34 | 35 | 0.33 5th June 2009 36 | - Fixed infinite loop when invalid performance data with multiple = were present 37 | 38 | 0.32 3rd March 2009 39 | - Handle performance data with quotes in the label (thanks to Kang) 40 | - Die if default config file is not available and --extra-opts is set 41 | 42 | 0.31 5th January 2009 43 | - Check for valid numerical value before returning perfdata object 44 | 45 | 0.30 13th December 2008 46 | - Fixed performance parsing when numeric fields had commas instead of periods due to locale settings 47 | - If a performance set is not parseable, instead of returning an empty array, will return all the successfully 48 | parsed sets 49 | - Fixed test plan for Nagios-Plugin-Performance.t 50 | 51 | 0.29 2nd December 2008 52 | - clean_label, for cleaning up a label for RRD, but without truncation 53 | 54 | 0.28 21st November 2008 55 | - Fixed test problems when run against Test::More 0.86 56 | - Added max_state_* wrappers 57 | 58 | 0.27 14th May 2008 59 | - Fixed parsing of performance data with scientific notation 60 | 61 | 0.26 28th March 2008 62 | - Fixed test failure in t/Nagios-Plugin-Getopt-03.t (Thomas Guyot-Sionnest) 63 | 64 | 0.25 17th March 2008 65 | - Fixed parsing of performance data with negative values and full range definitions 66 | 67 | 0.24 1st February 2008 68 | - Fixed a test failure which highlighted a precision rounding within hashes 69 | 70 | 0.23 18th December 2007 71 | - Use $^X for perl in check_stuff.t test, due to lots of failing in CPAN Testers 72 | 73 | 0.22 13th December 2007 74 | - Fixed handling of repeated ini arguments 75 | 76 | 0.21 24th September 2007 77 | - Help, usage and version output now goes to stdout, not stderr 78 | 79 | 0.20 5th September 2007 80 | - Version bump because of CPAN permission problems 81 | 82 | 0.19 4th September 2007 83 | - Fix test failures due to bad MANIFEST file 84 | - Fixed performance parsing where uom = % 85 | - Fixed version numbering 86 | 87 | 0.18 31st August 2007 88 | - Fix error when parsing performance data where warn or crit are 0 89 | - Optional _use_die flag to force nagios_die to call die instead of exit, so 90 | exceptions can be caught with an eval 91 | - Convenience function to set use_die so you can run 'use Nagios::Plugin::Performance use_die => 1' 92 | 93 | 0.17 23rd March 2007 94 | - bump version number again due to cpan indexing stupidity (Gavin) 95 | 96 | 0.16 23rd March 2007 97 | - added support for multi-entry help output (e.g. two separate help entries for --warning) (Gavin) 98 | - added automatic spec-to-help-text support to N::P::Getopt (Gavin) 99 | - added initial --extra-opts support to N::P::Getopt (Gavin) 100 | - removed default use of Threshold from N::P::Performance (Gavin) 101 | - removed remaining Class::Struct usages from Performance, Threshold, and Range (Gavin) 102 | - fixed warnings when no uom specified for add_perfdata (Ton) 103 | - added max_state function in N::P::Functions (Ton) 104 | 105 | 0.15 19th December 2006 106 | - exposed Getopt and Threshold functionality from top level Nagios::Plugin 107 | - exchanged Class::Struct for Class::Accessor 108 | 109 | 0.14 18th October 2006 110 | - Fixed version number due to CPAN upload 111 | 112 | 0.13 18th October 2006 113 | - Lots of extra tests and fixes from Nathan Vonnahme 114 | - Nagios::Plugin::Getopt, Functions and ExitResult added by Gavin Carr 115 | 116 | 0.12 15th June 2006 117 | - rrdlabel method available to get a performance label, 118 | converted to something rrd can use 119 | - fixes to parse_perfstring routine if values are 0 120 | - is_set method for range object to see if warning/critical range is set 121 | 122 | 0.11 14th June 2006 123 | - Interface changed for parse_perfstring, returning empty 124 | array if not parseable 125 | - Fixed problem when parsing nagiosgraph data (linefeed at end 126 | of perfdata) 127 | 128 | 0.10 8th June 2006 129 | First release to CPAN 130 | 131 | 0.01 Fri Jun 2 14:10:58 2006 132 | - original version; created by h2xs 1.23 with options 133 | -X -n Nagios::Plugin 134 | 135 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Getopt-01.t: -------------------------------------------------------------------------------- 1 | # Monitoring::Plugin::Getopt basic tests 2 | 3 | use strict; 4 | 5 | use Test::More tests => 81; 6 | BEGIN { use_ok('Monitoring::Plugin::Getopt') }; 7 | 8 | # Needed to get evals to work in testing 9 | Monitoring::Plugin::Functions::_use_die(1); 10 | 11 | my %PARAM = ( 12 | version => '0.01', 13 | url => 'http://www.openfusion.com.au/labs/nagios/', 14 | blurb => 'This plugin tests various stuff.', 15 | usage => "Usage: %s -H -w 16 | -c ", 17 | plugin => 'test_plugin', 18 | ); 19 | 20 | sub setup 21 | { 22 | # Instantiate object 23 | my $ng = Monitoring::Plugin::Getopt->new(%PARAM); 24 | ok($ng, 'constructor ok'); 25 | 26 | # Add argument - short form - arg spec, help text, default, required? 27 | $ng->arg('warning|w=s' => 28 | qq(-w, --warning=INTEGER\n Exit with WARNING status if less than INTEGER foobars are free), 29 | 5); 30 | 31 | # Add argument - named version 32 | $ng->arg( 33 | spec => 'critical|c=i', 34 | help => qq(Exit with CRITICAL status if less than INTEGER foobars are free), 35 | required => 1, 36 | ); 37 | 38 | # Add argument - boolean, supporting --no-prefix 39 | $ng->arg( 40 | spec => 'perfdata!', 41 | help => qq(Provide performance data), 42 | default => 1, 43 | ); 44 | 45 | return $ng; 46 | } 47 | 48 | my $ng; 49 | 50 | # Simple usage (short and long args) 51 | @ARGV = qw(-w 3 --critical 10 --timeout=12 --verbose); 52 | $ng = setup; 53 | $ng->getopts; 54 | is($ng->warning, 3, 'warning set to 3'); 55 | is($ng->critical, 10, 'critical set to 10'); 56 | is($ng->timeout, 12, 'timeout set to 12'); 57 | is($ng->perfdata, 1, 'perfdata set to default of 1'); 58 | 59 | # Disable perfdata 60 | @ARGV = qw(--critical 10 --no-perfdata); 61 | $ng = setup; 62 | $ng->getopts; 63 | is($ng->perfdata, 0, 'perfdata set to 0'); 64 | 65 | # Check multiple verbose flags 66 | @ARGV = qw(-w 3 --critical 10 -v -v -v); 67 | $ng = setup; 68 | $ng->getopts; 69 | is ($ng->verbose, 3, "Verbose set to level 3"); 70 | 71 | @ARGV = qw(-w 3 --critical 10 --verbose --verbose --verbose); 72 | $ng = setup; 73 | $ng->getopts; 74 | is ($ng->verbose, 3, "Verbose set to level 3 (longhand)"); 75 | 76 | # Missing args 77 | @ARGV = qw(); 78 | $ng = setup; 79 | ok(! defined eval { $ng->getopts }, 'getopts died on missing args'); 80 | like($@, qr/Usage:/, 'usage message'); 81 | like($@, qr/Missing arg/, 'missing arguments'); 82 | is($ng->verbose, 0, 'verbose set to 0'); 83 | # Missing critical 84 | @ARGV = qw(-w0 -v); 85 | $ng = setup; 86 | ok(! defined eval { $ng->getopts }, 'getopts died on missing args'); 87 | like($@, qr/Usage:/, 'usage message'); 88 | like($@, qr/Missing argument: critical/, 'missing argument: critical'); 89 | unlike($@, qr/Missing argument: warning/, 'no missing argument: warning'); 90 | is($ng->warning, 0, 'warning set to 0'); 91 | is($ng->critical, undef, 'critical undef'); 92 | is($ng->timeout, 15, 'timeout set to default'); 93 | is($ng->verbose, 1, 'verbose set to true'); 94 | # Missing warning 95 | @ARGV = qw(--critical=27 --timeout 17 --verbose); 96 | $ng = setup; 97 | $ng->getopts; 98 | is($ng->warning, 5, 'warning 5 (default)'); 99 | is($ng->critical, 27, 'critical set to 27'); 100 | is($ng->timeout, 17, 'timeout set to 17'); 101 | is($ng->verbose, 1, 'verbose set to true'); 102 | 103 | # -? --usage 104 | @ARGV = ( '-?' ); 105 | $ng = setup; 106 | ok(! defined eval { $ng->getopts }, 'getopts died on usage'); 107 | like($@, qr/Usage:/, 'usage message'); 108 | unlike($@, qr/Missing arg/, 'no missing arguments'); 109 | @ARGV = ( '--usage' ); 110 | $ng = setup; 111 | ok(! defined eval { $ng->getopts }, 'getopts died on usage'); 112 | like($@, qr/Usage:/, 'usage message'); 113 | unlike($@, qr/Missing arg/, 'no missing arguments'); 114 | 115 | # -V --version 116 | @ARGV = ( '-V' ); 117 | $ng = setup; 118 | ok(! defined eval { $ng->getopts }, 'getopts died on version'); 119 | like($@, qr/^$PARAM{plugin}/, 'version info includes plugin name'); 120 | like($@, qr/$PARAM{version}/, 'version info includes version'); 121 | like($@, qr/$PARAM{url}/, 'version info includes url'); 122 | unlike($@, qr/Usage:/, 'no usage message'); 123 | unlike($@, qr/Missing arg/, 'no missing arguments'); 124 | 125 | @ARGV = ( '--version' ); 126 | $ng = setup; 127 | ok(! defined eval { $ng->getopts }, 'getopts died on version'); 128 | like($@, qr/^$PARAM{plugin}/, 'version info includes plugin name'); 129 | like($@, qr/$PARAM{version}/, 'version info includes version'); 130 | like($@, qr/$PARAM{url}/, 'version info includes url'); 131 | unlike($@, qr/Usage:/, 'no usage message'); 132 | unlike($@, qr/Missing arg/, 'no missing arguments'); 133 | 134 | # -h --help 135 | @ARGV = ( '-h' ); 136 | $ng = setup; 137 | ok(! defined eval { $ng->getopts }, 'getopts died on help'); 138 | like($@, qr/^$PARAM{plugin}/, 'help includes plugin name'); 139 | like($@, qr/$PARAM{version}/, 'help includes version'); 140 | like($@, qr/$PARAM{url}/, 'help includes url'); 141 | like($@, qr/General Public Licence/, 'help includes licence'); 142 | like($@, qr/$PARAM{blurb}/, 'help includes blurb'); 143 | like($@, qr/Usage:/, 'help includes usage message'); 144 | like($@, qr/--version/, 'help includes default options 1'); 145 | like($@, qr/--verbose/, 'help includes default options 2'); 146 | like($@, qr/--warning/, 'help includes custom option 1'); 147 | like($@, qr/--critical/, 'help includes custom option 2'); 148 | like($@, qr/--\[no-\]perfdata\n/, 'help includes custom option 3'); 149 | unlike($@, qr/Missing arg/, 'no missing arguments'); 150 | 151 | @ARGV = ( '--help' ); 152 | $ng = setup; 153 | ok(! defined eval { $ng->getopts }, 'getopts died on help'); 154 | like($@, qr/^$PARAM{plugin}/, 'help includes plugin name'); 155 | like($@, qr/$PARAM{version}/, 'help includes version'); 156 | like($@, qr/$PARAM{url}/, 'help includes url'); 157 | like($@, qr/General Public Licence/, 'help includes licence'); 158 | like($@, qr/$PARAM{blurb}/, 'help includes blurb'); 159 | like($@, qr/Usage:/, 'help includes usage message'); 160 | like($@, qr/--version/, 'help includes default options 1'); 161 | like($@, qr/--verbose/, 'help includes default options 2'); 162 | like($@, qr/--warning/, 'help includes custom option 1'); 163 | like($@, qr/-c, --critical=INTEGER/, 'help includes custom option 2, with expanded args'); 164 | like($@, qr/--\[no-\]perfdata\n/, 'help includes custom option 3'); 165 | unlike($@, qr/Missing arg/, 'no missing arguments'); 166 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin/Config.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin::Config; 2 | 3 | use 5.006; 4 | use strict; 5 | use warnings; 6 | 7 | use Carp; 8 | use File::Spec; 9 | use base qw(Config::Tiny); 10 | 11 | my $FILENAME1 = 'plugins.ini'; 12 | my $FILENAME2 = 'nagios-plugins.ini'; 13 | my $FILENAME3 = 'monitoring-plugins.ini'; 14 | my $CURRENT_FILE = undef; 15 | 16 | # Config paths ending in nagios (search for $FILENAME1) 17 | my @MONITORING_CONFIG_PATH = qw(/etc/nagios /usr/local/nagios/etc /usr/local/etc/nagios /etc/opt/nagios); 18 | # Config paths not ending in nagios (search for $FILENAME2) 19 | my @CONFIG_PATH = qw(/etc /usr/local/etc /etc/opt); 20 | 21 | # Override Config::Tiny::read to default the filename, if not given 22 | sub read 23 | { 24 | my $class = shift; 25 | 26 | unless ($_[0]) { 27 | SEARCH: { 28 | if ($ENV{MONITORING_CONFIG_PATH} || $ENV{NAGIOS_CONFIG_PATH}) { 29 | for (split /:/, ($ENV{MONITORING_CONFIG_PATH} || $ENV{NAGIOS_CONFIG_PATH})) { 30 | my $file = File::Spec->catfile($_, $FILENAME1); 31 | unshift(@_, $file), last SEARCH if -f $file; 32 | $file = File::Spec->catfile($_, $FILENAME2); 33 | unshift(@_, $file), last SEARCH if -f $file; 34 | $file = File::Spec->catfile($_, $FILENAME3); 35 | unshift(@_, $file), last SEARCH if -f $file; 36 | } 37 | } 38 | for (@MONITORING_CONFIG_PATH) { 39 | my $file = File::Spec->catfile($_, $FILENAME1); 40 | unshift(@_, $file), last SEARCH if -f $file; 41 | } 42 | for (@CONFIG_PATH) { 43 | my $file = File::Spec->catfile($_, $FILENAME2); 44 | unshift(@_, $file), last SEARCH if -f $file; 45 | $file = File::Spec->catfile($_, $FILENAME3); 46 | unshift(@_, $file), last SEARCH if -f $file; 47 | } 48 | } 49 | 50 | # Use die instead of croak, so we can pass a clean message downstream 51 | die "Cannot find '$FILENAME1', '$FILENAME2' or '$FILENAME3' in any standard location.\n" unless $_[0]; 52 | } 53 | 54 | $CURRENT_FILE = $_[0]; 55 | $class->SUPER::read( @_ ); 56 | } 57 | 58 | # Straight from Config::Tiny - only changes are repeated property key support 59 | # Would be nice if we could just override the per-line handling ... 60 | sub read_string 61 | { 62 | my $class = ref $_[0] ? ref shift : shift; 63 | my $self = bless {}, $class; 64 | return undef unless defined $_[0]; 65 | 66 | # Parse the file 67 | my $ns = '_'; 68 | my $counter = 0; 69 | foreach ( split /(?:\015{1,2}\012|\015|\012)/, shift ) { 70 | $counter++; 71 | 72 | # Skip comments and empty lines 73 | next if /^\s*(?:\#|\;|$)/; 74 | 75 | # Handle section headers 76 | if ( /^\s*\[\s*(.+?)\s*\]\s*$/ ) { 77 | # Create the sub-hash if it doesn't exist. 78 | # Without this sections without keys will not 79 | # appear at all in the completed struct. 80 | $self->{$ns = $1} ||= {}; 81 | next; 82 | } 83 | 84 | # Handle properties 85 | if ( /^\s*([^=]+?)\s*=\s*(.*?)\s*$/ ) { 86 | push @{$self->{$ns}->{$1}}, $2; 87 | next; 88 | } 89 | 90 | return $self->_error( "Syntax error at line $counter: '$_'" ); 91 | } 92 | 93 | $self; 94 | } 95 | 96 | sub write { croak "Write access not permitted" } 97 | 98 | # Return last file used by read(); 99 | sub mp_getfile { return $CURRENT_FILE; } 100 | 101 | 1; 102 | 103 | __END__ 104 | 105 | =head1 NAME 106 | 107 | Monitoring::Plugin::Config - read nagios plugin .ini style config files 108 | 109 | =head1 SYNOPSIS 110 | 111 | # Read given nagios plugin config file 112 | $Config = Monitoring::Plugin::Config->read( '/etc/nagios/plugins.ini' ); 113 | 114 | # Search for and read default nagios plugin config file 115 | $Config = Monitoring::Plugin::Config->read(); 116 | 117 | # Access sections and properties (returns scalars or arrayrefs) 118 | $rootproperty = $Config->{_}->{rootproperty}; 119 | $one = $Config->{section}->{one}; 120 | $Foo = $Config->{section}->{Foo}; 121 | 122 | =head1 DESCRIPTION 123 | 124 | Monitoring::Plugin::Config is a subclass of the excellent Config::Tiny, 125 | with the following changes: 126 | 127 | =over 4 128 | 129 | =item 130 | 131 | Repeated keys are allowed within sections, returning lists instead of scalars 132 | 133 | =item 134 | 135 | Write functionality has been removed i.e. access is read only 136 | 137 | =item 138 | 139 | Monitoring::Plugin::Config searches for a default nagios plugins file if no explicit 140 | filename is given to C. The current standard locations checked are: 141 | 142 | =over 4 143 | 144 | =item /etc/nagios/plugins.ini 145 | 146 | =item /usr/local/nagios/etc/plugins.ini 147 | 148 | =item /usr/local/etc/nagios /etc/opt/nagios/plugins.ini 149 | 150 | =item /etc/nagios-plugins.ini 151 | 152 | =item /usr/local/etc/nagios-plugins.ini 153 | 154 | =item /etc/opt/nagios-plugins.ini 155 | 156 | =back 157 | 158 | To use a custom location, set a C environment variable 159 | to the set of directories that should be checked. The first C or 160 | C file found will be used. 161 | 162 | =back 163 | 164 | 165 | =head1 SEE ALSO 166 | 167 | L, L 168 | 169 | 170 | =head1 AUTHOR 171 | 172 | This code is maintained by the Monitoring Plugin Development Team: see 173 | https://monitoring-plugins.org 174 | 175 | =head1 COPYRIGHT AND LICENSE 176 | 177 | Copyright (C) 2014 by Monitoring Plugin Team 178 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 179 | 180 | This library is free software; you can redistribute it and/or modify 181 | it under the same terms as Perl itself. 182 | 183 | =cut 184 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-02.t: -------------------------------------------------------------------------------- 1 | # Monitoring::Plugin test set 2, testing MP::Functions wrapping 2 | 3 | use strict; 4 | use Test::More tests => 103; 5 | 6 | BEGIN { use_ok("Monitoring::Plugin") } 7 | require Monitoring::Plugin::Functions; 8 | Monitoring::Plugin::Functions::_fake_exit(1); 9 | 10 | # Hardcoded checks of constants 11 | my %ERRORS = %Monitoring::Plugin::Functions::ERRORS; 12 | is(OK, $ERRORS{OK}, "OK => $ERRORS{OK}"); 13 | is(WARNING, $ERRORS{WARNING}, "WARNING => $ERRORS{WARNING}"); 14 | is(CRITICAL, $ERRORS{CRITICAL}, "CRITICAL => $ERRORS{CRITICAL}"); 15 | is(UNKNOWN, $ERRORS{UNKNOWN}, "UNKNOWN => $ERRORS{UNKNOWN}"); 16 | is(DEPENDENT, $ERRORS{DEPENDENT}, "DEPENDENT => $ERRORS{DEPENDENT}"); 17 | 18 | my $plugin = 'TEST_PLUGIN'; 19 | my $np = Monitoring::Plugin->new( shortname => $plugin ); 20 | is($np->shortname, $plugin, "shortname() is $plugin"); 21 | 22 | # Test plugin_exit( CONSTANT, $msg ), plugin_exit( $string, $msg ) 23 | my $r; 24 | my @ok = ( 25 | [ OK, "OK", 'test the first', ], 26 | [ WARNING, "WARNING", 'test the second', ], 27 | [ CRITICAL, "CRITICAL", 'test the third', ], 28 | [ UNKNOWN, "UNKNOWN", 'test the fourth', ], 29 | [ DEPENDENT, "DEPENDENT", 'test the fifth', ], 30 | ); 31 | for (@ok) { 32 | # CONSTANT 33 | $r = $np->plugin_exit($_->[0], $_->[2]); 34 | is($r->return_code, $_->[0], 35 | sprintf('plugin_exit(%s, $msg) returned %s', $_->[1], $_->[0])); 36 | like($r->message, qr/$plugin\b.*$_->[1]\b.*\b$_->[2]$/, 37 | sprintf('plugin_exit(%s, $msg) output matched "%s"', $_->[1], 38 | $plugin . ' ' . $_->[1] . '.*' . $_->[2])); 39 | 40 | # $string 41 | $r = $np->plugin_exit($_->[1], $_->[2]); 42 | is($r->return_code, $_->[0], 43 | sprintf('plugin_exit("%s", $msg) returned %s', $_->[1], $_->[0])); 44 | like($r->message, qr/$plugin\b.*$_->[1]\b.*\b$_->[2]$/, 45 | sprintf('plugin_exit("%s", $msg) output matched "%s"', $_->[1], 46 | $plugin . ' ' . $_->[1] . '.*' . $_->[2])); 47 | like($r, qr/$plugin\b.*$_->[1]\b.*\b$_->[2]$/, 48 | sprintf('plugin_exit("%s", $msg) stringified matched "%s"', $_->[1], 49 | $plugin . ' ' . $_->[1] . '.*' . $_->[2])); 50 | } 51 | 52 | # plugin_exit code corner cases 53 | my @ugly1 = ( 54 | [ -1, 'testing code -1' ], 55 | [ 7, 'testing code 7' ], 56 | [ undef, 'testing code undef' ], 57 | [ '', qq(testing code '') ], 58 | [ 'string', qq(testing code 'string') ], 59 | ); 60 | for (@ugly1) { 61 | $r = $np->plugin_exit($_->[0], $_->[1]); 62 | my $display = defined $_->[0] ? "'$_->[0]'" : 'undef'; 63 | is($r->return_code, UNKNOWN, "plugin_exit($display, \$msg) returned ". UNKNOWN); 64 | like($r->message, qr/UNKNOWN\b.*\b$_->[1]$/, 65 | sprintf('plugin_exit(%s, $msg) output matched "%s"', 66 | $display, 'UNKNOWN.*' . $_->[1])); 67 | } 68 | 69 | # plugin_exit message corner cases 70 | my @ugly2 = ( 71 | [ '' ], 72 | [ undef ], 73 | [ UNKNOWN ], 74 | ); 75 | for (@ugly2) { 76 | $r = $np->plugin_exit(CRITICAL, $_->[0]); 77 | my $display1 = defined $_->[0] ? "'$_->[0]'" : "undef"; 78 | my $display2 = defined $_->[0] ? $_->[0] : ''; 79 | like($r->message, qr/CRITICAL\b.*\b$display2$/, 80 | sprintf('plugin_exit(%s, $msg) output matched "%s"', 81 | $display1, "CRITICAL.*$display2")); 82 | } 83 | 84 | # Test plugin_die( $msg ) 85 | my @msg = ( 86 | [ 'die you dog' ], 87 | [ '' ], 88 | [ undef ], 89 | ); 90 | for (@msg) { 91 | $r = $np->plugin_die($_->[0]); 92 | my $display1 = defined $_->[0] ? "'$_->[0]'" : "undef"; 93 | my $display2 = defined $_->[0] ? $_->[0] : ''; 94 | is($r->return_code, UNKNOWN, 95 | sprintf('plugin_die(%s) returned UNKNOWN', $display1)); 96 | like($r->message, qr/UNKNOWN\b.*\b$display2$/, 97 | sprintf('plugin_die(%s) output matched "%s"', $display1, 98 | "UNKNOWN.*$display2")); 99 | } 100 | 101 | # Test plugin_die( CONSTANT, $msg ), plugin_die( $msg, CONSTANT ), 102 | # plugin_die( $string, $msg ), and plugin_die( $msg, $string ) 103 | @ok = ( 104 | [ OK, "OK", 'test the first', ], 105 | [ WARNING, "WARNING", 'test the second', ], 106 | [ CRITICAL, "CRITICAL", 'test the third', ], 107 | [ UNKNOWN, "UNKNOWN", 'test the fourth', ], 108 | [ DEPENDENT, "DEPENDENT", 'test the fifth', ], 109 | ); 110 | for (@ok) { 111 | # CONSTANT, $msg 112 | $r = $np->plugin_die($_->[0], $_->[2]); 113 | is($r->return_code, $_->[0], 114 | sprintf('plugin_die(%s, $msg) returned %s', $_->[1], $_->[0])); 115 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 116 | sprintf('plugin_die(%s, $msg) output matched "%s"', 117 | $_->[1], $_->[1] . '.*' . $_->[2])); 118 | 119 | # $msg, CONSTANT 120 | $r = $np->plugin_die($_->[2], $_->[0]); 121 | is($r->return_code, $_->[0], 122 | sprintf('plugin_die($msg, %s) returned %s', $_->[1], $_->[0])); 123 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 124 | sprintf('plugin_die($msg, %s) output matched "%s"', 125 | $_->[1], $_->[1] . '.*' . $_->[2])); 126 | 127 | # $string, $msg 128 | $r = $np->plugin_die($_->[1], $_->[2]); 129 | is($r->return_code, $_->[0], 130 | sprintf('plugin_die("%s", $msg) returned %s', $_->[1], $_->[0])); 131 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 132 | sprintf('plugin_die("%s", $msg) output matched "%s"', $_->[1], 133 | $_->[1] . '.*' . $_->[2])); 134 | like($r, qr/$_->[1]\b.*\b$_->[2]$/, 135 | sprintf('plugin_die("%s", $msg) stringified matched "%s"', $_->[1], 136 | $_->[1] . '.*' . $_->[2])); 137 | 138 | # $string, $msg 139 | $r = $np->plugin_die($_->[2], $_->[1]); 140 | is($r->return_code, $_->[0], 141 | sprintf('plugin_die($msg, "%s") returned %s', $_->[1], $_->[0])); 142 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 143 | sprintf('plugin_die($msg, "%s") output matched "%s"', $_->[1], 144 | $_->[1] . '.*' . $_->[2])); 145 | like($r, qr/$_->[1]\b.*\b$_->[2]$/, 146 | sprintf('plugin_die($msg, "%s") stringified matched "%s"', $_->[1], 147 | $_->[1] . '.*' . $_->[2])); 148 | } 149 | 150 | 151 | # shortname testing 152 | SKIP: { 153 | skip "requires File::Basename", 2 unless eval { require File::Basename }; 154 | $np = Monitoring::Plugin->new( version => "1"); 155 | $plugin = uc File::Basename::basename($0); 156 | $plugin =~ s/\..*$//; 157 | is($np->shortname, $plugin, "shortname() is '$plugin'"); 158 | $r = $np->plugin_exit(OK, "foobar"); 159 | like($r->message, qr/^$plugin OK/, "message begins with '$plugin OK'"); 160 | } 161 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Functions-01.t: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | use Test::More tests => 116; 4 | 5 | BEGIN { use_ok("Monitoring::Plugin::Functions", ":all"); } 6 | Monitoring::Plugin::Functions::_fake_exit(1); 7 | 8 | my $this_version=$Monitoring::Plugin::Functions::VERSION; 9 | foreach my $m ("", qw(::Threshold ::Getopt ::Performance ::Range)) { 10 | my $mod = "Monitoring::Plugin$m"; 11 | use_ok($mod); 12 | # Lots of hackery below. Easier to say $mod->VERSION, but this is probably a recent perl thing 13 | my $v = "$mod"."::VERSION"; 14 | my $a = eval "\$$v"; 15 | is($a, $this_version, "Version number for $mod the same as Functions: $this_version"); 16 | } 17 | 18 | # check get_shortname 19 | is(get_shortname, "MONITORING-PLUGIN-FUNCTIONS-01", "get_shortname ok"); 20 | 21 | # Hardcoded checks of constants 22 | ok(%ERRORS, '%ERRORS defined'); 23 | is(OK, $ERRORS{OK}, "OK => $ERRORS{OK}"); 24 | is(WARNING, $ERRORS{WARNING}, "WARNING => $ERRORS{WARNING}"); 25 | is(CRITICAL, $ERRORS{CRITICAL}, "CRITICAL => $ERRORS{CRITICAL}"); 26 | is(UNKNOWN, $ERRORS{UNKNOWN}, "UNKNOWN => $ERRORS{UNKNOWN}"); 27 | is(DEPENDENT, $ERRORS{DEPENDENT}, "DEPENDENT => $ERRORS{DEPENDENT}"); 28 | 29 | # Test plugin_exit( CONSTANT, $msg ), plugin_exit( $string, $msg ) 30 | my $r; 31 | my @ok = ( 32 | [ OK, "OK", 'test the first', ], 33 | [ WARNING, "WARNING", 'test the second', ], 34 | [ CRITICAL, "CRITICAL", 'test the third', ], 35 | [ UNKNOWN, "UNKNOWN", 'test the fourth', ], 36 | [ DEPENDENT, "DEPENDENT", 'test the fifth', ], 37 | ); 38 | for (@ok) { 39 | # CONSTANT 40 | $r = plugin_exit($_->[0], $_->[2]); 41 | is($r->return_code, $_->[0], 42 | sprintf('plugin_exit(%s, $msg) returned %s', $_->[1], $_->[0])); 43 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 44 | sprintf('plugin_exit(%s, $msg) output matched "%s"', 45 | $_->[1], $_->[1] . '.*' . $_->[2])); 46 | 47 | # $string 48 | $r = plugin_exit($_->[1], $_->[2]); 49 | is($r->return_code, $_->[0], 50 | sprintf('plugin_exit("%s", $msg) returned %s', $_->[1], $_->[0])); 51 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 52 | sprintf('plugin_exit("%s", $msg) output matched "%s"', $_->[1], 53 | $_->[1] . '.*' . $_->[2])); 54 | like($r, qr/$_->[1]\b.*\b$_->[2]$/, 55 | sprintf('plugin_exit("%s", $msg) stringified matched "%s"', $_->[1], 56 | $_->[1] . '.*' . $_->[2])); 57 | } 58 | 59 | # plugin_exit code corner cases 60 | my @ugly1 = ( 61 | [ -1, 'testing code -1' ], 62 | [ 7, 'testing code 7' ], 63 | [ undef, 'testing code undef' ], 64 | [ '', qq(testing code '') ], 65 | [ 'string', qq(testing code 'string') ], 66 | ); 67 | for (@ugly1) { 68 | $r = plugin_exit($_->[0], $_->[1]); 69 | my $display = defined $_->[0] ? "'$_->[0]'" : 'undef'; 70 | is($r->return_code, UNKNOWN, "plugin_exit($display, \$msg) returned ". UNKNOWN); 71 | like($r->message, qr/UNKNOWN\b.*\b$_->[1]$/, 72 | sprintf('plugin_exit(%s, $msg) output matched "%s"', 73 | $display, 'UNKNOWN.*' . $_->[1])); 74 | } 75 | 76 | # plugin_exit message corner cases 77 | my @ugly2 = ( 78 | [ '' ], 79 | [ undef ], 80 | [ UNKNOWN ], 81 | ); 82 | for (@ugly2) { 83 | $r = plugin_exit(CRITICAL, $_->[0]); 84 | my $display1 = defined $_->[0] ? "'$_->[0]'" : "undef"; 85 | my $display2 = defined $_->[0] ? $_->[0] : ''; 86 | like($r->message, qr/CRITICAL\b.*\b$display2$/, 87 | sprintf('plugin_exit(%s, $msg) output matched "%s"', 88 | $display1, "CRITICAL.*$display2")); 89 | } 90 | 91 | # plugin_exit message with longoutput 92 | my @ugly3 = ( 93 | [ "MSG\nLONGOUTPUT", " - MSG\nLONGOUTPUT" ], 94 | [ "\nLONGOUTPUT", "\nLONGOUTPUT" ], 95 | [ " \nLONGOUTPUT", " \nLONGOUTPUT" ], 96 | ); 97 | for (@ugly3) { 98 | $r = plugin_exit(CRITICAL, $_->[0]); 99 | like($r->message, qr/CRITICAL$_->[1]$/, 100 | sprintf('plugin_exit(CRITICAL, $msg) output matched "%s"', 101 | "CRITICAL$_->[1]")); 102 | } 103 | 104 | # Test plugin_die( $msg ) 105 | my @msg = ( 106 | [ 'die you dog' ], 107 | [ '' ], 108 | [ undef ], 109 | ); 110 | for (@msg) { 111 | $r = plugin_die($_->[0]); 112 | my $display1 = defined $_->[0] ? "'$_->[0]'" : "undef"; 113 | my $display2 = defined $_->[0] ? $_->[0] : ''; 114 | is($r->return_code, UNKNOWN, 115 | sprintf('plugin_die(%s) returned UNKNOWN', $display1)); 116 | like($r->message, qr/UNKNOWN\b.*\b$display2$/, 117 | sprintf('plugin_die(%s) output matched "%s"', $display1, 118 | "UNKNOWN.*$display2")); 119 | } 120 | 121 | # Test plugin_die( CONSTANT, $msg ), plugin_die( $msg, CONSTANT ), 122 | # plugin_die( $string, $msg ), and plugin_die( $msg, $string ) 123 | @ok = ( 124 | [ OK, "OK", 'test the first', ], 125 | [ WARNING, "WARNING", 'test the second', ], 126 | [ CRITICAL, "CRITICAL", 'test the third', ], 127 | [ UNKNOWN, "UNKNOWN", 'test the fourth', ], 128 | [ DEPENDENT, "DEPENDENT", 'test the fifth', ], 129 | ); 130 | for (@ok) { 131 | # CONSTANT, $msg 132 | $r = plugin_die($_->[0], $_->[2]); 133 | is($r->return_code, $_->[0], 134 | sprintf('plugin_die(%s, $msg) returned %s', $_->[1], $_->[0])); 135 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 136 | sprintf('plugin_die(%s, $msg) output matched "%s"', 137 | $_->[1], $_->[1] . '.*' . $_->[2])); 138 | 139 | # $msg, CONSTANT 140 | $r = plugin_die($_->[2], $_->[0]); 141 | is($r->return_code, $_->[0], 142 | sprintf('plugin_die($msg, %s) returned %s', $_->[1], $_->[0])); 143 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 144 | sprintf('plugin_die($msg, %s) output matched "%s"', 145 | $_->[1], $_->[1] . '.*' . $_->[2])); 146 | 147 | # $string, $msg 148 | $r = plugin_die($_->[1], $_->[2]); 149 | is($r->return_code, $_->[0], 150 | sprintf('plugin_die("%s", $msg) returned %s', $_->[1], $_->[0])); 151 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 152 | sprintf('plugin_die("%s", $msg) output matched "%s"', $_->[1], 153 | $_->[1] . '.*' . $_->[2])); 154 | like($r, qr/$_->[1]\b.*\b$_->[2]$/, 155 | sprintf('plugin_die("%s", $msg) stringified matched "%s"', $_->[1], 156 | $_->[1] . '.*' . $_->[2])); 157 | 158 | # $string, $msg 159 | $r = plugin_die($_->[2], $_->[1]); 160 | is($r->return_code, $_->[0], 161 | sprintf('plugin_die($msg, "%s") returned %s', $_->[1], $_->[0])); 162 | like($r->message, qr/$_->[1]\b.*\b$_->[2]$/, 163 | sprintf('plugin_die($msg, "%s") output matched "%s"', $_->[1], 164 | $_->[1] . '.*' . $_->[2])); 165 | like($r, qr/$_->[1]\b.*\b$_->[2]$/, 166 | sprintf('plugin_die($msg, "%s") stringified matched "%s"', $_->[1], 167 | $_->[1] . '.*' . $_->[2])); 168 | } 169 | 170 | # Check that _use_die set to 1 will catch exceptions correctly 171 | Monitoring::Plugin::Functions::_fake_exit(0); 172 | Monitoring::Plugin::Functions::_use_die(1); 173 | eval { plugin_die("Using die") }; 174 | is( $@, "MONITORING-PLUGIN-FUNCTIONS-01 UNKNOWN - Using die\n", "Caught exception"); 175 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Functions-02.t: -------------------------------------------------------------------------------- 1 | # check_messages tests 2 | 3 | use strict; 4 | use Test::More tests => 37; 5 | 6 | BEGIN { use_ok("Monitoring::Plugin::Functions", ":all") } 7 | 8 | my ($code, $message); 9 | 10 | # ------------------------------------------------------------------------- 11 | # Check codes 12 | my @codes = ( 13 | [ [ qw(Critical) ], [ qw(Warning) ], CRITICAL ], 14 | [ [], [ qw(Warning) ], WARNING ], 15 | [ [], [], OK ], 16 | ); 17 | my $i = 0; 18 | for (@codes) { 19 | $i++; 20 | $code = check_messages( critical => $_->[0], warning => $_->[1] ); 21 | is($code, $_->[2], "Code test $i returned $STATUS_TEXT{$_->[2]}"); 22 | } 23 | 24 | # ------------------------------------------------------------------------- 25 | # Check messages 26 | my %arrays = ( 27 | critical => [ qw(A B C) ], 28 | warning => [ qw(D E F) ], 29 | ok => [ qw(G H I) ], 30 | ); 31 | my %messages = map { $_ => join(' ', @{$arrays{$_}}) } keys %arrays; 32 | 33 | # critical, warning 34 | ($code, $message) = check_messages( 35 | critical => $arrays{critical}, warning => $arrays{warning}, 36 | ); 37 | is($code, CRITICAL, "(critical, warning) code is $STATUS_TEXT{$code}"); 38 | is($message, $messages{critical}, "(critical, warning) message is $message"); 39 | 40 | # critical, warning, ok 41 | ($code, $message) = check_messages( 42 | critical => $arrays{critical}, warning => $arrays{warning}, 43 | ok => $arrays{ok}, 44 | ); 45 | is($code, CRITICAL, "(critical, warning, ok) code is $STATUS_TEXT{$code}"); 46 | is($message, $messages{critical}, "(critical, warning, ok) message is $message"); 47 | 48 | # critical, warning, $ok 49 | ($code, $message) = check_messages( 50 | critical => $arrays{critical}, warning => $arrays{warning}, 51 | ok => 'G H I', 52 | ); 53 | is($code, CRITICAL, "(critical, warning, \$ok) code is $STATUS_TEXT{$code}"); 54 | is($message, $messages{critical}, "(critical, warning, \$ok) message is $message"); 55 | 56 | # warning 57 | ($code, $message) = check_messages( 58 | critical => [], warning => $arrays{warning}, 59 | ); 60 | is($code, WARNING, "(warning) code is $STATUS_TEXT{$code}"); 61 | is($message, $messages{warning}, "(warning) message is $message"); 62 | 63 | # warning, ok 64 | ($code, $message) = check_messages( 65 | critical => [], warning => $arrays{warning}, ok => $arrays{ok}, 66 | ); 67 | is($code, WARNING, "(warning, ok) code is $STATUS_TEXT{$code}"); 68 | is($message, $messages{warning}, "(warning, ok) message is $message"); 69 | 70 | # ok 71 | ($code, $message) = check_messages( 72 | critical => [], warning => [], ok => $arrays{ok}, 73 | ); 74 | is($code, OK, "(ok) code is $STATUS_TEXT{$code}"); 75 | is($message, $messages{ok}, "(ok) message is $message"); 76 | 77 | # $ok 78 | ($code, $message) = check_messages( 79 | critical => [], warning => [], ok => 'G H I', 80 | ); 81 | is($code, OK, "(\$ok) code is $STATUS_TEXT{$code}"); 82 | is($message, $messages{ok}, "(\$ok) message is $message"); 83 | 84 | # ------------------------------------------------------------------------- 85 | # explicit join 86 | my $join = '+'; 87 | ($code, $message) = check_messages( 88 | critical => $arrays{critical}, warning => $arrays{warning}, 89 | join => $join, 90 | ); 91 | is($message, join($join, @{$arrays{critical}}), "joined '$join' (critical, warning) message is $message"); 92 | $join = ''; 93 | ($code, $message) = check_messages( 94 | critical => [], warning => $arrays{warning}, 95 | join => $join, 96 | ); 97 | is($message, join($join, @{$arrays{warning}}), "joined '$join' (warning) message is $message"); 98 | $join = undef; 99 | ($code, $message) = check_messages( 100 | critical => [], warning => [], ok => $arrays{ok}, 101 | join => $join, 102 | ); 103 | is($message, join(' ', @{$arrays{ok}}), "joined undef (ok) message is $message"); 104 | 105 | # ------------------------------------------------------------------------- 106 | # join_all messages 107 | my $join_all = ' :: '; 108 | my $msg_all_cwo = join($join_all, map { join(' ', @{$arrays{$_}}) } 109 | qw(critical warning ok)); 110 | my $msg_all_cw = join($join_all, map { join(' ', @{$arrays{$_}}) } 111 | qw(critical warning)); 112 | my $msg_all_wo = join($join_all, map { join(' ', @{$arrays{$_}}) } 113 | qw(warning ok)); 114 | 115 | # critical, warning, ok 116 | ($code, $message) = check_messages( 117 | critical => $arrays{critical}, warning => $arrays{warning}, ok => $arrays{ok}, 118 | join_all => $join_all, 119 | ); 120 | is($code, CRITICAL, "(critical, warning, ok) code is $STATUS_TEXT{$code}"); 121 | is($message, $msg_all_cwo, "join_all '$join_all' (critical, warning, ok) message is $message"); 122 | 123 | # critical, warning, $ok 124 | ($code, $message) = check_messages( 125 | critical => $arrays{critical}, warning => $arrays{warning}, ok => 'G H I', 126 | join_all => $join_all, 127 | ); 128 | is($code, CRITICAL, "(critical, warning, \$ok) code is $STATUS_TEXT{$code}"); 129 | is($message, $msg_all_cwo, "join_all '$join_all' (critical, warning, \$ok) message is $message"); 130 | 131 | # critical, warning 132 | ($code, $message) = check_messages( 133 | critical => $arrays{critical}, warning => $arrays{warning}, 134 | join_all => $join_all, 135 | ); 136 | is($code, CRITICAL, "(critical, warning) code is $STATUS_TEXT{$code}"); 137 | is($message, $msg_all_cw, "join_all '$join_all' (critical, warning) message is $message"); 138 | 139 | # warning, ok 140 | ($code, $message) = check_messages( 141 | critical => [], warning => $arrays{warning}, ok => $arrays{ok}, 142 | join_all => $join_all, 143 | ); 144 | is($code, WARNING, "(warning, ok) code is $STATUS_TEXT{$code}"); 145 | is($message, $msg_all_wo, "join_all '$join_all' (critical, warning, ok) message is $message"); 146 | 147 | # warning, $ok 148 | ($code, $message) = check_messages( 149 | critical => [], warning => $arrays{warning}, ok => 'G H I', 150 | join_all => $join_all, 151 | ); 152 | is($code, WARNING, "(warning, \$ok) code is $STATUS_TEXT{$code}"); 153 | is($message, $msg_all_wo, "join_all '$join_all' (critical, warning, \$ok) message is $message"); 154 | 155 | # warning 156 | ($code, $message) = check_messages( 157 | critical => [], warning => $arrays{warning}, 158 | join_all => $join_all, 159 | ); 160 | is($code, WARNING, "(warning) code is $STATUS_TEXT{$code}"); 161 | is($message, 'D E F', "join_all '$join_all' (critical, warning) message is $message"); 162 | 163 | # ------------------------------------------------------------------------- 164 | # Error cases 165 | 166 | # Test failures without required fields 167 | ok(! defined eval { ($code, $message) = check_messages() }, 168 | "check_messages dies without message args"); 169 | 170 | ok(! defined eval { ($code, $message) = check_messages(warning => $arrays{warning}) }, 171 | "check_messages dies without 'critical' message"); 172 | 173 | ok(! defined eval { ($code, $message) = check_messages(critical => $arrays{critical}) }, 174 | "check_messages dies without 'warning' message"); 175 | 176 | ok(defined eval { ($code, $message) = check_messages(critical => $arrays{critical}, warning => $arrays{warning}) }, 177 | "check_messages ok with 'critical' and 'warning' messages"); 178 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Range.t: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | #use Test::More qw(no_plan); 4 | use Test::More tests => 151; 5 | 6 | BEGIN { 7 | use_ok('Monitoring::Plugin::Range'); 8 | # Silence warnings unless TEST_VERBOSE is set 9 | $SIG{__WARN__} = sub { warn $_[0] if $ENV{TEST_VERBOSE} }; 10 | }; 11 | 12 | diag "\nusing Monitoring::Plugin::Range revision ". $Monitoring::Plugin::Range::VERSION . "\n" if $ENV{TEST_VERBOSE}; 13 | 14 | my $r; 15 | 16 | diag "'garbage in' checks -- you should see 7 invalid range definition warnings here:" if $ENV{TEST_VERBOSE}; 17 | 18 | foreach (qw( 19 | : 20 | 1:~ 21 | foo 22 | 1-10 23 | 10:~ 24 | 1-10:2.4 25 | 26 | ), '1,10' # avoid warning about using , inside qw() 27 | ) { 28 | $r =Monitoring::Plugin::Range->parse_range_string($_); 29 | is $r, undef, "'$_' should not be a valid range" ; 30 | } 31 | 32 | 33 | diag "range: 0..6 inclusive" if $ENV{TEST_VERBOSE}; 34 | $r = Monitoring::Plugin::Range->parse_range_string("6"); 35 | isa_ok( $r, "Monitoring::Plugin::Range"); 36 | ok( defined $r, "'6' is valid range"); 37 | cmp_ok( $r->start, '==', 0, "Start correct"); 38 | cmp_ok( $r->start_infinity, '==', 0, "Not using negative infinity"); 39 | cmp_ok( $r->end, '==', 6, "End correct"); 40 | cmp_ok( $r->end_infinity, '==', 0, "Not using positive infinity"); 41 | cmp_ok( $r, 'eq', "6", "Stringification back to original"); 42 | 43 | my $expected = { 44 | -1 => 1, # 1 means it raises an alert because it's OUTSIDE the range 45 | 0 => 0, # 0 means it's inside the range (no alert) 46 | 4 => 0, 47 | 6 => 0, 48 | 6.1 => 1, 49 | 79.999999 => 1, 50 | }; 51 | 52 | sub test_expected { 53 | my $r = shift; 54 | my $expected = shift; 55 | foreach (sort {$a<=>$b} keys %$expected) { 56 | is $r->check_range($_), $expected->{$_}, 57 | " $_ should " . ($expected->{$_} ? 'not ' : '') . "be in the range (line ".(caller)[2].")"; 58 | } 59 | } 60 | 61 | test_expected( $r, $expected ); 62 | 63 | diag "range : -7..23, inclusive" if $ENV{TEST_VERBOSE}; 64 | $r = Monitoring::Plugin::Range->parse_range_string("-7:23"); 65 | ok( defined $r, "'-7:23' is valid range"); 66 | cmp_ok( $r->start, '==', -7, "Start correct"); 67 | cmp_ok( $r->start_infinity, '==', 0, "Not using negative infinity"); 68 | cmp_ok( $r->end, '==', 23, "End correct"); 69 | cmp_ok( $r->end_infinity, '==', 0, "Not using positive infinity"); 70 | cmp_ok( $r, 'eq', "-7:23", "Stringification back to original"); 71 | 72 | $expected = { 73 | -23 => 1, 74 | -7 => 0, 75 | -1 => 0, 76 | 0 => 0, 77 | 4 => 0, 78 | 23 => 0, 79 | 23.1 => 1, 80 | 79.999999 => 1, 81 | }; 82 | test_expected( $r, $expected ); 83 | 84 | 85 | diag "range : 0..5.75, inclusive" if $ENV{TEST_VERBOSE}; 86 | $r = Monitoring::Plugin::Range->parse_range_string(":5.75"); 87 | ok( defined $r, "':5.75' is valid range"); 88 | cmp_ok( $r->start, '==', 0, "Start correct"); 89 | cmp_ok( $r->start_infinity, '==', 0, "Not using negative infinity"); 90 | cmp_ok( $r->end, '==', 5.75, "End correct"); 91 | cmp_ok( $r->end_infinity, '==', 0, "Not using positive infinity"); 92 | cmp_ok( $r, 'eq', "5.75", "Stringification to simplification"); 93 | $expected = { 94 | -1 => 1, 95 | 0 => 0, 96 | 4 => 0, 97 | 5.75 => 0, 98 | 5.7501 => 1, 99 | 6 => 1, 100 | 6.1 => 1, 101 | 79.999999 => 1, 102 | }; 103 | test_expected( $r, $expected ); 104 | 105 | 106 | 107 | diag "range : negative infinity .. -95.99, inclusive" if $ENV{TEST_VERBOSE}; 108 | $r = Monitoring::Plugin::Range->parse_range_string("~:-95.99"); 109 | ok( defined $r, "'~:-95.99' is valid range"); 110 | cmp_ok( $r->start_infinity, '==', 1, "Using negative infinity"); 111 | cmp_ok( $r->end, '==', -95.99, "End correct"); 112 | cmp_ok( $r->end_infinity, '==', 0, "Not using positive infinity"); 113 | cmp_ok( $r, 'eq', "~:-95.99", "Stringification back to original"); 114 | $expected = { 115 | -1001341 => 0, 116 | -96 => 0, 117 | -95.999 => 0, 118 | -95.99 => 0, 119 | -95.989 => 1, 120 | -95 => 1, 121 | 0 => 1, 122 | 5.7501 => 1, 123 | 79.999999 => 1, 124 | }; 125 | test_expected( $r, $expected ); 126 | 127 | diag "range 10..infinity , inclusive" if $ENV{TEST_VERBOSE}; 128 | test_expected( $r, $expected ); 129 | $r = Monitoring::Plugin::Range->parse_range_string("10:"); 130 | ok( defined $r, "'10:' is valid range"); 131 | cmp_ok( $r->start, '==', 10, "Start correct"); 132 | cmp_ok( $r->start_infinity, '==', 0, "Not using negative infinity"); 133 | cmp_ok( $r->end_infinity, '==', 1, "Using positive infinity"); 134 | cmp_ok( $r, 'eq', "10:", "Stringification back to original"); 135 | $expected = { 136 | -95.999 => 1, 137 | -1 => 1, 138 | 0 => 1, 139 | 9.91 => 1, 140 | 10 => 0, 141 | 11.1 => 0, 142 | 123456789012346 => 0, 143 | }; 144 | test_expected( $r, $expected ); 145 | 146 | 147 | 148 | diag "range 123456789012345..infinity , inclusive" if $ENV{TEST_VERBOSE}; 149 | test_expected( $r, $expected ); 150 | $r = Monitoring::Plugin::Range->parse_range_string("123456789012345:"); 151 | ok( defined $r, "'123456789012345:' is valid range"); 152 | cmp_ok( $r->start, '==', 123456789012345, "Start correct"); 153 | cmp_ok( $r->start_infinity, '==', 0, "Not using negative infinity"); 154 | cmp_ok( $r->end_infinity, '==', 1, "Using positive infinity"); 155 | cmp_ok( $r, 'eq', "123456789012345:", "Stringification back to original"); 156 | $expected = { 157 | -95.999 => 1, 158 | -1 => 1, 159 | 0 => 1, 160 | # The fractional values needs to be quoted, otherwise the hash rounds it up to ..345 161 | # and there is one less test run. 162 | # I think some newer versions of perl use a higher precision value for the hash key. 163 | # This doesn't appear to affect the actual plugin though 164 | "123456789012344.91" => 1, 165 | 123456789012345 => 0, 166 | "123456789012345.61" => 0, 167 | 123456789012346 => 0, 168 | }; 169 | test_expected( $r, $expected ); 170 | 171 | 172 | diag "range: <= zero " if $ENV{TEST_VERBOSE}; 173 | $r = Monitoring::Plugin::Range->parse_range_string("~:0"); 174 | ok( defined $r, "'~:0' is valid range"); 175 | cmp_ok( $r->start_infinity, '==', 1, "Using negative infinity"); 176 | cmp_ok( $r->end, '==', 0, "End correct"); 177 | cmp_ok( $r->end_infinity, '==', 0, "Not using positive infinity"); 178 | cmp_ok( $r->alert_on, '==', 0, "Will alert on outside of range"); 179 | cmp_ok( $r, 'eq', "~:0", "Stringification back to original"); 180 | ok( $r->check_range(0.5) == 1, "0.5 - alert"); 181 | ok( $r->check_range(-10) == 0, "-10 - no alert"); 182 | ok( $r->check_range(0) == 0, "0 - no alert"); 183 | $expected = { 184 | -123456789012344.91 => 0, 185 | -1 => 0, 186 | 0 => 0, 187 | .001 => 1, 188 | 123456789012345 => 1, 189 | }; 190 | test_expected( $r, $expected ); 191 | 192 | 193 | diag "range: OUTSIDE 0..657.8210567" if $ENV{TEST_VERBOSE}; 194 | $r = Monitoring::Plugin::Range->parse_range_string('@0:657.8210567'); 195 | ok( defined $r, '"@0:657.8210567" is a valid range'); 196 | cmp_ok( $r->start, '==', 0, "Start correct"); 197 | cmp_ok( $r->start_infinity, '==', 0, "Not using negative infinity"); 198 | cmp_ok( $r->end, '==', 657.8210567, "End correct"); 199 | cmp_ok( $r->end_infinity, '==', 0, "Not using positive infinity"); 200 | cmp_ok( $r->alert_on, '==', 1, "Will alert on inside of range"); 201 | cmp_ok( $r, 'eq', '@657.8210567', "Stringification to simplified version"); 202 | ok( $r->check_range(32.88) == 1, "32.88 - alert"); 203 | ok( $r->check_range(-2) == 0, "-2 - no alert"); 204 | ok( $r->check_range(657.8210567) == 1, "657.8210567 - alert"); 205 | ok( $r->check_range(0) == 1, "0 - alert"); 206 | $expected = { 207 | -134151 => 0, 208 | -1 => 0, 209 | 0 => 1, 210 | .001 => 1, 211 | 657.8210567 => 1, 212 | 657.9 => 0, 213 | 123456789012345 => 0, 214 | }; 215 | test_expected( $r, $expected ); 216 | 217 | 218 | diag "range: 1..1 inclusive (equals one)" if $ENV{TEST_VERBOSE}; 219 | $r = Monitoring::Plugin::Range->parse_range_string('1:1'); 220 | ok( defined $r, '"1:1" is a valid range'); 221 | cmp_ok( $r->start, '==', 1, "Start correct"); 222 | cmp_ok( $r->start_infinity, '==', 0, "Not using negative infinity"); 223 | cmp_ok( $r->end, '==', 1, "End correct"); 224 | cmp_ok( $r->end_infinity, '==', 0, "Not using positive infinity"); 225 | cmp_ok( $r, 'eq', "1:1", "Stringification to simplified version"); 226 | ok( $r->check_range(0.5) == 1, "0.5 - alert"); 227 | ok( $r->check_range(1) == 0, "1 - no alert"); 228 | ok( $r->check_range(5.2) == 1, "5.2 - alert"); 229 | $expected = { 230 | -1 => 1, 231 | 0 => 1, 232 | .5 => 1, 233 | 1 => 0, 234 | 1.001 => 1, 235 | 5.2 => 1, 236 | }; 237 | test_expected( $r, $expected ); 238 | 239 | 240 | $r = Monitoring::Plugin::Range->parse_range_string('2:1'); 241 | ok( ! defined $r, '"2:1" is rejected'); 242 | 243 | # TODO: Need more tests for invalid data 244 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Threshold.t: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | use Test::More tests => 93; 4 | BEGIN { 5 | use_ok('Monitoring::Plugin::Threshold'); 6 | use_ok('Monitoring::Plugin::Functions', ':all' ); 7 | # Silence warnings unless TEST_VERBOSE is set 8 | $SIG{__WARN__} = sub { warn $_[0] if $ENV{TEST_VERBOSE} }; 9 | } 10 | 11 | diag "\nusing Monitoring::Plugin::Threshold revision ". $Monitoring::Plugin::Threshold::VERSION . "\n" 12 | if $ENV{TEST_VERBOSE}; 13 | 14 | Monitoring::Plugin::Functions::_fake_exit(1); 15 | 16 | my $t; 17 | 18 | $t = Monitoring::Plugin::Threshold->set_thresholds(warning => undef, critical => undef); 19 | ok( defined $t, "two undefs" ); 20 | ok( ! $t->warning->is_set, "warning not set" ); 21 | ok( ! $t->critical->is_set, "critical not set" ); 22 | 23 | $t = Monitoring::Plugin::Threshold->set_thresholds(warning => "", critical => ""); 24 | ok( defined $t, "two empty strings" ); 25 | ok( ! $t->warning->is_set, "warning not set" ); 26 | ok( ! $t->critical->is_set, "critical not set" ); 27 | 28 | diag "threshold: critical if > 80" if $ENV{TEST_VERBOSE}; 29 | my $t = Monitoring::Plugin::Threshold->set_thresholds(critical => "80"); 30 | ok( defined $t, "Threshold ('', '80') set"); 31 | ok( ! $t->warning->is_set, "Warning not set"); 32 | cmp_ok( $t->critical->start, '==', 0, "Critical strat set correctly"); 33 | cmp_ok( $t->critical->end, '==', 80, "Critical end set correctly"); 34 | ok ! $t->critical->end_infinity, "not forever"; 35 | 36 | my $expected = { qw( 37 | -1 CRITICAL 38 | 4 OK 39 | 79.999999 OK 40 | 80 OK 41 | 80.1 CRITICAL 42 | 102321 CRITICAL 43 | ) }; 44 | 45 | sub test_expected_statuses { 46 | my $t = shift; 47 | my $expected = shift; 48 | my $debug = shift; 49 | 50 | foreach (sort {$a<=>$b} keys %$expected) { 51 | is $STATUS_TEXT{$t->get_status($_)}, $expected->{$_}, " $_ - $expected->{$_}"; 52 | if ($debug) { 53 | diag "val = $_; critical check = ".$t->critical->check_range($_). 54 | "; warning check = ".$t->warning->check_range($_); 55 | } 56 | } 57 | use Data::Dumper; 58 | diag "thresh dump: ". Dumper $t if $debug; 59 | } 60 | test_expected_statuses( $t, $expected ); 61 | 62 | # GMC: this test seems bogus to me - either we've died, in which case internal 63 | # state is undefined (and untestable!), or we should be returning a non-fatal error 64 | if (0) { 65 | diag "threshold: warn if less than 5 or more than 33." if $ENV{TEST_VERBOSE}; 66 | eval { $t = Monitoring::Plugin::Threshold->set_thresholds(warning => "5:33", critical => "") }; 67 | ok( defined $t, "Threshold ('5:33', '') set"); 68 | cmp_ok( $t->warning->start, '==', 5, "Warning start set"); 69 | cmp_ok( $t->warning->end, '==', 33, "Warning end set"); 70 | ok( ! $t->critical->is_set, "Critical not set"); 71 | } 72 | 73 | # GC: same as previous test, except critical is undef instead of '' 74 | diag "threshold: warn if less than 5 or more than 33." if $ENV{TEST_VERBOSE}; 75 | $t = Monitoring::Plugin::Threshold->set_thresholds(warning => "5:33", critical => undef); 76 | ok( defined $t, "Threshold ('5:33', '') set"); 77 | cmp_ok( $t->warning->start, '==', 5, "Warning start set"); 78 | cmp_ok( $t->warning->end, '==', 33, "Warning end set"); 79 | ok( ! $t->critical->is_set, "Critical not set"); 80 | 81 | $expected = { qw( 82 | -1 WARNING 83 | 4 WARNING 84 | 4.999999 WARNING 85 | 5 OK 86 | 14.21 OK 87 | 33 OK 88 | 33.01 WARNING 89 | 10231 WARNING 90 | ) }; 91 | test_expected_statuses( $t, $expected ); 92 | 93 | diag "threshold: warn if more than 30; critical if > 60" if $ENV{TEST_VERBOSE}; 94 | $t = Monitoring::Plugin::Threshold->set_thresholds(warning => "~:30", critical => "~:60"); 95 | ok( defined $t, "Threshold ('~:30', '~:60') set"); 96 | cmp_ok( $t->warning->end, '==', 30, "Warning end set"); 97 | cmp_ok( $t->critical->end, '==',60, "Critical end set"); 98 | ok $t->critical->start_infinity, "Critical starts at negative infinity"; 99 | 100 | $expected = { qw( 101 | -1 OK 102 | 4 OK 103 | 29.999999 OK 104 | 30 OK 105 | 30.1 WARNING 106 | 50.90 WARNING 107 | 59.9 WARNING 108 | 60 WARNING 109 | 60.00001 CRITICAL 110 | 10231 CRITICAL 111 | ) }; 112 | test_expected_statuses( $t, $expected ); 113 | 114 | # "I'm going to die homeless, penniless, and 30 pounds overweight." 115 | # "...and that's...okay." 116 | 117 | # TODO: figure out why this doesn't work and fix the test. 118 | goto SKIP_DEATH; 119 | diag "threshold: test pure crap for arguments - default to OK." if $ENV{TEST_VERBOSE}; 120 | diag "you should see one invalid range definition warning and an UNKNOWN line here:\n"; 121 | Monitoring::Plugin::Functions->print_on_die(1); 122 | Monitoring::Plugin::Functions->exit_on_die(1); 123 | 124 | dies_ok( sub { 125 | $t = Monitoring::Plugin::Threshold->set_thresholds( 126 | warning => "total", 127 | critical => "rubbish" 128 | ) 129 | }, "bad thresholds cause death" 130 | ); 131 | Monitoring::Plugin::Functions->print_on_die(0); 132 | Monitoring::Plugin::Functions->exit_on_die(0); 133 | SKIP_DEATH: 134 | 135 | 136 | diag "threshold: critical if > 25 " if $ENV{TEST_VERBOSE}; 137 | $t = Monitoring::Plugin::Threshold->set_thresholds( critical => "~:25" ); 138 | ok( defined $t, "Threshold ('', '~:25') set (".$t->critical.")" ); 139 | ok( ! $t->warning->is_set, "Warning not set"); 140 | cmp_ok( $t->critical->end, '==',25, "Critical end set"); 141 | ok $t->critical->start_infinity, "Critical starts at negative infinity"; 142 | 143 | $expected = { qw( 144 | -1 OK 145 | 4 OK 146 | 10 OK 147 | 14.21 OK 148 | 25 OK 149 | 25.01 CRITICAL 150 | 31001 CRITICAL 151 | ) }; 152 | test_expected_statuses( $t, $expected); 153 | 154 | diag "threshold: warn if OUTSIDE {10..25} , critical if > 25 " if $ENV{TEST_VERBOSE}; 155 | $t = Monitoring::Plugin::Threshold->set_thresholds(warning => "10:25", critical => "~:25"); 156 | ok( defined $t, "Threshold ('10:25', '~:25') set"); 157 | cmp_ok( $t->warning->start, '==', 10, "Warning start set"); 158 | cmp_ok( $t->warning->end, '==', 25, "Warning end set"); 159 | cmp_ok( $t->critical->end, '==', 25, "Critical end set"); 160 | 161 | $expected = { qw( 162 | -1 WARNING 163 | 4 WARNING 164 | 9.999999 WARNING 165 | 10 OK 166 | 14.21 OK 167 | 25 OK 168 | 25.01 CRITICAL 169 | 31001 CRITICAL 170 | ) }; 171 | test_expected_statuses( $t, $expected ); 172 | 173 | 174 | diag "warn if INSIDE {10..25} , critical if < 10 " if $ENV{TEST_VERBOSE}; 175 | $t = Monitoring::Plugin::Threshold->set_thresholds(warning => "\@10:25", critical => "10:"); 176 | $expected = { qw( 177 | -1 CRITICAL 178 | 4 CRITICAL 179 | 9.999999 CRITICAL 180 | 10 WARNING 181 | 14.21 WARNING 182 | 25 WARNING 183 | 25.01 OK 184 | 31001 OK 185 | ) }; 186 | test_expected_statuses( $t, $expected ); 187 | 188 | 189 | # GMC: as of 0.16, set_thresholds can also be called as a mutator 190 | diag "threshold mutator: warn if more than 30; critical if > 60" 191 | if $ENV{TEST_VERBOSE}; 192 | my $t1 = $t; 193 | $t->set_thresholds(warning => "0:45", critical => "0:90"); 194 | is($t1, $t, "same threshold object after \$t->set_thresholds"); 195 | ok( defined $t, "Threshold ('0:45', '0:90') set"); 196 | is( $t->warning->start, 0, "Warning start ok"); 197 | is( $t->warning->end, 45, "Warning end ok"); 198 | is( $t->critical->start, 0, "Critical start ok"); 199 | is( $t->critical->end, 90, "Critical end ok"); 200 | 201 | 202 | # Also as of 0.16, accepts N::P::Range objects as arguments 203 | my $warning = Monitoring::Plugin::Range->parse_range_string("50"); 204 | my $critical = Monitoring::Plugin::Range->parse_range_string("70:90"); 205 | $t = Monitoring::Plugin::Threshold->set_thresholds(warning => $warning, critical => $critical); 206 | ok( defined $t, "Threshold from ranges ('50', '70:90') set"); 207 | is( $t->warning->start, 0, "Warning start ok"); 208 | is( $t->warning->end, 50, "Warning end ok"); 209 | is( $t->critical->start, 70, "Critical start ok"); 210 | is( $t->critical->end, 90, "Critical end ok"); 211 | 212 | $critical = Monitoring::Plugin::Range->parse_range_string("90:"); 213 | $t->set_thresholds(warning => "~:20", critical => $critical); 214 | ok( defined $t, "Threshold from string + range ('~:20', '90:') set"); 215 | ok( $t->warning->start_infinity, "Warning start ok (infinity)"); 216 | is( $t->warning->end, 20, "Warning end ok"); 217 | is( $t->critical->start, 90, "Critical start ok"); 218 | ok( $t->critical->end_infinity, "Critical end ok (infinity)"); 219 | 220 | 221 | ok 1, "sweet, made it to the end."; 222 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin/Performance.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin::Performance; 2 | 3 | use 5.006; 4 | use strict; 5 | use warnings; 6 | 7 | use Carp; 8 | use base qw(Class::Accessor::Fast); 9 | __PACKAGE__->mk_ro_accessors( 10 | qw(label value uom warning critical min max) 11 | ); 12 | 13 | use Monitoring::Plugin::Functions; 14 | use Monitoring::Plugin::Threshold; 15 | use Monitoring::Plugin::Range; 16 | our ($VERSION) = $Monitoring::Plugin::Functions::VERSION; 17 | 18 | sub import { 19 | my ($class, %attr) = @_; 20 | $_ = $attr{use_die} || 0; 21 | Monitoring::Plugin::Functions::_use_die($_); 22 | } 23 | 24 | # This is NOT the same as N::P::Functions::value_re. We leave that to be the strict 25 | # version. This one allows commas to be part of the numeric value. 26 | my $value = qr/[-+]?[\d\.,]+/; 27 | my $value_re = qr/$value(?:e$value)?/; 28 | my $value_with_negative_infinity = qr/$value_re|~/; 29 | sub _parse { 30 | my $class = shift; 31 | my $string = shift; 32 | $string =~ /^'?([^'=]+)'?=($value_re)([\w%]*);?($value_with_negative_infinity\:?$value_re?)?;?($value_with_negative_infinity\:?$value_re?)?;?($value_re)?;?($value_re)?/o; 33 | return undef unless ((defined $1 && $1 ne "") && (defined $2 && $2 ne "")); 34 | my @info = ($1, $2, $3, $4, $5, $6, $7); 35 | # We convert any commas to periods, in the value fields 36 | map { defined $info[$_] && $info[$_] =~ s/,/./go } (1, 3, 4, 5, 6); 37 | 38 | # Check that $info[1] is an actual value 39 | # We do this by returning undef if a warning appears 40 | my $performance_value; 41 | { 42 | my $not_value; 43 | local $SIG{__WARN__} = sub { $not_value++ }; 44 | $performance_value = $info[1]+0; 45 | return undef if $not_value; 46 | } 47 | my $p = $class->new( 48 | label => $info[0], value => $performance_value, uom => $info[2], warning => $info[3], critical => $info[4], 49 | min => $info[5], max => $info[6] 50 | ); 51 | return $p; 52 | } 53 | 54 | # Map undef to '' 55 | sub _nvl { 56 | my ($self, $value) = @_; 57 | defined $value ? $value : '' 58 | } 59 | 60 | sub perfoutput { 61 | my $self = shift; 62 | # Add quotes if label contains a space character 63 | my $label = $self->label; 64 | if ($label =~ / /) { 65 | $label = "'$label'"; 66 | } 67 | 68 | my $value = $self->value; 69 | # To prevent invalid output, we change empty value to value "U" 70 | if ($value eq '') { 71 | $value = 'U'; 72 | } 73 | 74 | my $out = sprintf "%s=%s%s;%s;%s;%s;%s", 75 | $label, 76 | $value, 77 | $self->_nvl($self->uom), 78 | $self->_nvl($self->warning), 79 | $self->_nvl($self->critical), 80 | $self->_nvl($self->min), 81 | $self->_nvl($self->max); 82 | # Previous implementation omitted trailing ;; - do we need this? 83 | $out =~ s/;;$//; 84 | return $out; 85 | } 86 | 87 | sub parse_perfstring { 88 | my ($class, $perfstring) = @_; 89 | my @perfs = (); 90 | my $obj; 91 | while ($perfstring) { 92 | $perfstring =~ s/^\s*//; 93 | # If there is more than 1 equals sign, split it out and parse individually 94 | if (@{[$perfstring =~ /=/g]} > 1) { 95 | $perfstring =~ s/^(.*?=.*?)\s//; 96 | if (defined $1) { 97 | $obj = $class->_parse($1); 98 | } else { 99 | # This could occur if perfdata was soemthing=value= 100 | # Since this is invalid, we reset the string and continue 101 | $perfstring = ""; 102 | $obj = $class->_parse($perfstring); 103 | } 104 | } else { 105 | $obj = $class->_parse($perfstring); 106 | $perfstring = ""; 107 | } 108 | push @perfs, $obj if $obj; 109 | } 110 | return @perfs; 111 | } 112 | 113 | sub rrdlabel { 114 | my $self = shift; 115 | my $name = $self->clean_label; 116 | # Shorten 117 | return substr( $name, 0, 19 ); 118 | } 119 | 120 | sub clean_label { 121 | my $self = shift; 122 | my $name = $self->label; 123 | if ($name eq "/") { 124 | $name = "root"; 125 | } elsif ( $name =~ s/^\/// ) { 126 | $name =~ s/\//_/g; 127 | } 128 | # Convert all other characters 129 | $name =~ s/\W/_/g; 130 | return $name; 131 | } 132 | 133 | # Backward compatibility: create a threshold object on the fly as requested 134 | sub threshold 135 | { 136 | my $self = shift; 137 | return Monitoring::Plugin::Threshold->set_thresholds( 138 | warning => $self->warning, critical => $self->critical 139 | ); 140 | } 141 | 142 | # Constructor - unpack thresholds, map args to hashref 143 | sub new 144 | { 145 | my $class = shift; 146 | my %arg = @_; 147 | 148 | # Convert thresholds 149 | if (my $threshold = delete $arg{threshold}) { 150 | $arg{warning} ||= $threshold->warning . ""; 151 | $arg{critical} ||= $threshold->critical . ""; 152 | } 153 | 154 | $class->SUPER::new(\%arg); 155 | } 156 | 157 | 1; 158 | 159 | __END__ 160 | 161 | =head1 NAME 162 | 163 | Monitoring::Plugin::Performance - class for handling Monitoring::Plugin 164 | performance data. 165 | 166 | =head1 SYNOPSIS 167 | 168 | use Monitoring::Plugin::Performance use_die => 1; 169 | 170 | # Constructor (also accepts a 'threshold' obj instead of warning/critical) 171 | $p = Monitoring::Plugin::Performance->new( 172 | label => 'size', 173 | value => $value, 174 | uom => "kB", 175 | warning => $warning, 176 | critical => $critical, 177 | min => $min, 178 | max => $max, 179 | ); 180 | 181 | # Parser 182 | @perf = Monitoring::Plugin::Performance->parse_perfstring( 183 | "/=382MB;15264;15269;; /var=218MB;9443;9448" 184 | ) 185 | or warn("Failed to parse perfstring"); 186 | 187 | # Accessors 188 | for $p (@perf) { 189 | printf "label: %s\n", $p->label; 190 | printf "value: %s\n", $p->value; 191 | printf "uom: %s\n", $p->uom; 192 | printf "warning: %s\n", $p->warning; 193 | printf "critical: %s\n", $p->critical; 194 | printf "min: %s\n", $p->min; 195 | printf "max: %s\n", $p->max; 196 | # Special accessor returning a threshold obj containing warning/critical 197 | $threshold = $p->threshold; 198 | } 199 | 200 | # Perfdata output format i.e. label=value[uom];[warn];[crit];[min];[max] 201 | print $p->perfoutput; 202 | 203 | 204 | =head1 DESCRIPTION 205 | 206 | Monitoring::Plugin class for handling performance data. This is a public 207 | interface because it could be used by performance graphing routines, 208 | such as nagiostat (http://nagiostat.sourceforge.net), perfparse 209 | (http://perfparse.sourceforge.net), nagiosgraph 210 | (http://nagiosgraph.sourceforge.net) or NagiosGrapher 211 | (http://www.nagiosexchange.org/NagiosGrapher.84.0.html). 212 | 213 | Monitoring::Plugin::Performance offers both a parsing interface (via 214 | parse_perfstring), for turning nagios performance output strings into 215 | their components, and a composition interface (via new), for turning 216 | components into perfdata strings. 217 | 218 | =head1 USE'ING THE MODULE 219 | 220 | If you are using this module for the purposes of parsing perf data, you 221 | will probably want to set use_die => 1 at use time. This forces 222 | &Monitoring::Plugin::Functions::plugin_exit to call die() - rather than exit() - 223 | when an error occurs. This is then trappable by an eval. If you don't set use_die, 224 | then an error in these modules will cause your script to exit 225 | 226 | =head1 CLASS METHODS 227 | 228 | =over 4 229 | 230 | =item Monitoring::Plugin::Performance->new(%attributes) 231 | 232 | Instantiates a new Monitoring::Plugin::Performance object with the given 233 | attributes. 234 | 235 | =item Monitoring::Plugin::Performance->parse_perfstring($string) 236 | 237 | Returns an array of Monitoring::Plugin::Performance objects based on the string 238 | entered. If there is an error parsing the string - which may consists of several 239 | sets of data - will return an array with all the successfully parsed sets. 240 | 241 | If values are input with commas instead of periods, due to different locale settings, 242 | then it will still be parsed, but the commas will be converted to periods. 243 | 244 | =back 245 | 246 | =head1 OBJECT METHODS (ACCESSORS) 247 | 248 | =over 4 249 | 250 | =item label, value, uom, warning, critical, min, max 251 | 252 | These all return scalars. min and max are not well supported yet. 253 | 254 | =item threshold 255 | 256 | Returns a Monitoring::Plugin::Threshold object holding the warning and critical 257 | ranges for this performance data (if any). 258 | 259 | =item rrdlabel 260 | 261 | Returns a string based on 'label' that is suitable for use as dataset name of 262 | an RRD i.e. munges label to be 1-19 characters long with only characters 263 | [a-zA-Z0-9_]. 264 | 265 | This calls $self->clean_label and then truncates to 19 characters. 266 | 267 | There is no guarantee that multiple N:P:Performance objects will have unique 268 | rrdlabels. 269 | 270 | =item clean_label 271 | 272 | Returns a "clean" label for use as a dataset name in RRD, ie, it converts 273 | characters that are not [a-zA-Z0-9_] to _. 274 | 275 | It also converts "/" to "root" and "/{name}" to "{name}". 276 | 277 | =item perfoutput 278 | 279 | Outputs the data in Monitoring::Plugin perfdata format i.e. 280 | label=value[uom];[warn];[crit];[min];[max]. 281 | 282 | =back 283 | 284 | =head1 SEE ALSO 285 | 286 | Monitoring::Plugin, Monitoring::Plugin::Threshold, https://www.monitoring-plugins.org/doc/guidelines.html 287 | 288 | =head1 AUTHOR 289 | 290 | This code is maintained by the Monitoring Plugin Development Team: see 291 | https://monitoring-plugins.org 292 | 293 | =head1 COPYRIGHT AND LICENSE 294 | 295 | Copyright (C) 2014 by Monitoring Plugin Team 296 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 297 | 298 | This library is free software; you can redistribute it and/or modify 299 | it under the same terms as Perl itself. 300 | 301 | =cut 302 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-03.t: -------------------------------------------------------------------------------- 1 | # $np->check_messages tests 2 | 3 | use strict; 4 | use Test::More tests => 61; 5 | 6 | BEGIN { 7 | use_ok("Monitoring::Plugin"); 8 | use_ok("Monitoring::Plugin::Functions", ":all"); 9 | } 10 | Monitoring::Plugin::Functions::_fake_exit(1); 11 | 12 | my $plugin = 'MP_CHECK_MESSAGES_03'; 13 | my $np = Monitoring::Plugin->new( shortname => $plugin, () ); 14 | is($np->shortname, $plugin, "shortname() is $plugin"); 15 | 16 | my ($code, $message); 17 | 18 | # ------------------------------------------------------------------------- 19 | # Check codes 20 | my @codes = ( 21 | [ [ qw(Critical) ], [ qw(Warning) ], CRITICAL ], 22 | [ [], [ qw(Warning) ], WARNING ], 23 | [ [], [], OK ], 24 | ); 25 | my $i = 0; 26 | for (@codes) { 27 | $i++; 28 | $code = $np->check_messages( critical => $_->[0], warning => $_->[1] ); 29 | is($code, $_->[2], "Code test $i returned $STATUS_TEXT{$_->[2]}"); 30 | } 31 | 32 | # ------------------------------------------------------------------------- 33 | # Check messages 34 | my %arrays = ( 35 | critical => [ qw(A B C) ], 36 | warning => [ qw(D E F) ], 37 | ok => [ qw(G H I) ], 38 | ); 39 | my %messages = map { $_ => join(' ', @{$arrays{$_}}) } keys %arrays; 40 | 41 | # critical, warning 42 | ($code, $message) = $np->check_messages( 43 | critical => $arrays{critical}, warning => $arrays{warning}, 44 | ); 45 | is($code, CRITICAL, "(critical, warning) code is $STATUS_TEXT{$code}"); 46 | is($message, $messages{critical}, "(critical, warning) message is $message"); 47 | 48 | # critical, warning, ok 49 | ($code, $message) = $np->check_messages( 50 | critical => $arrays{critical}, warning => $arrays{warning}, 51 | ok => $arrays{ok}, 52 | ); 53 | is($code, CRITICAL, "(critical, warning, ok) code is $STATUS_TEXT{$code}"); 54 | is($message, $messages{critical}, "(critical, warning, ok) message is $message"); 55 | 56 | # critical, warning, $ok 57 | ($code, $message) = $np->check_messages( 58 | critical => $arrays{critical}, warning => $arrays{warning}, 59 | ok => 'G H I', 60 | ); 61 | is($code, CRITICAL, "(critical, warning, \$ok) code is $STATUS_TEXT{$code}"); 62 | is($message, $messages{critical}, "(critical, warning, \$ok) message is $message"); 63 | 64 | # warning 65 | ($code, $message) = $np->check_messages( 66 | critical => [], warning => $arrays{warning}, 67 | ); 68 | is($code, WARNING, "(warning) code is $STATUS_TEXT{$code}"); 69 | is($message, $messages{warning}, "(warning) message is $message"); 70 | 71 | # warning, ok 72 | ($code, $message) = $np->check_messages( 73 | critical => [], warning => $arrays{warning}, ok => $arrays{ok}, 74 | ); 75 | is($code, WARNING, "(warning, ok) code is $STATUS_TEXT{$code}"); 76 | is($message, $messages{warning}, "(warning, ok) message is $message"); 77 | 78 | # ok 79 | ($code, $message) = $np->check_messages( 80 | critical => [], warning => [], ok => $arrays{ok}, 81 | ); 82 | is($code, OK, "(ok) code is $STATUS_TEXT{$code}"); 83 | is($message, $messages{ok}, "(ok) message is $message"); 84 | 85 | # $ok 86 | ($code, $message) = $np->check_messages( 87 | critical => [], warning => [], ok => 'G H I', 88 | ); 89 | is($code, OK, "(\$ok) code is $STATUS_TEXT{$code}"); 90 | is($message, $messages{ok}, "(\$ok) message is $message"); 91 | 92 | # ------------------------------------------------------------------------- 93 | # explicit join 94 | my $join = '+'; 95 | ($code, $message) = $np->check_messages( 96 | critical => $arrays{critical}, warning => $arrays{warning}, 97 | join => $join, 98 | ); 99 | is($message, join($join, @{$arrays{critical}}), "joined '$join' (critical, warning) message is $message"); 100 | $join = ''; 101 | ($code, $message) = $np->check_messages( 102 | critical => [], warning => $arrays{warning}, 103 | join => $join, 104 | ); 105 | is($message, join($join, @{$arrays{warning}}), "joined '$join' (warning) message is $message"); 106 | $join = undef; 107 | ($code, $message) = $np->check_messages( 108 | critical => [], warning => [], ok => $arrays{ok}, 109 | join => $join, 110 | ); 111 | is($message, join(' ', @{$arrays{ok}}), "joined undef (ok) message is $message"); 112 | 113 | # ------------------------------------------------------------------------- 114 | # join_all messages 115 | my $join_all = ' :: '; 116 | my $msg_all_cwo = join($join_all, map { join(' ', @{$arrays{$_}}) } 117 | qw(critical warning ok)); 118 | my $msg_all_cw = join($join_all, map { join(' ', @{$arrays{$_}}) } 119 | qw(critical warning)); 120 | my $msg_all_wo = join($join_all, map { join(' ', @{$arrays{$_}}) } 121 | qw(warning ok)); 122 | 123 | # critical, warning, ok 124 | ($code, $message) = $np->check_messages( 125 | critical => $arrays{critical}, warning => $arrays{warning}, ok => $arrays{ok}, 126 | join_all => $join_all, 127 | ); 128 | is($code, CRITICAL, "(critical, warning, ok) code is $STATUS_TEXT{$code}"); 129 | is($message, $msg_all_cwo, "join_all '$join_all' (critical, warning, ok) message is $message"); 130 | 131 | # critical, warning, $ok 132 | ($code, $message) = $np->check_messages( 133 | critical => $arrays{critical}, warning => $arrays{warning}, ok => 'G H I', 134 | join_all => $join_all, 135 | ); 136 | is($code, CRITICAL, "(critical, warning, \$ok) code is $STATUS_TEXT{$code}"); 137 | is($message, $msg_all_cwo, "join_all '$join_all' (critical, warning, \$ok) message is $message"); 138 | 139 | # critical, warning 140 | ($code, $message) = $np->check_messages( 141 | critical => $arrays{critical}, warning => $arrays{warning}, 142 | join_all => $join_all, 143 | ); 144 | is($code, CRITICAL, "(critical, warning) code is $STATUS_TEXT{$code}"); 145 | is($message, $msg_all_cw, "join_all '$join_all' (critical, warning) message is $message"); 146 | 147 | # warning, ok 148 | ($code, $message) = $np->check_messages( 149 | critical => [], warning => $arrays{warning}, ok => $arrays{ok}, 150 | join_all => $join_all, 151 | ); 152 | is($code, WARNING, "(warning, ok) code is $STATUS_TEXT{$code}"); 153 | is($message, $msg_all_wo, "join_all '$join_all' (critical, warning, ok) message is $message"); 154 | 155 | # warning, $ok 156 | ($code, $message) = $np->check_messages( 157 | critical => [], warning => $arrays{warning}, ok => 'G H I', 158 | join_all => $join_all, 159 | ); 160 | is($code, WARNING, "(warning, \$ok) code is $STATUS_TEXT{$code}"); 161 | is($message, $msg_all_wo, "join_all '$join_all' (critical, warning, \$ok) message is $message"); 162 | 163 | # warning 164 | ($code, $message) = $np->check_messages( 165 | critical => [], warning => $arrays{warning}, 166 | join_all => $join_all, 167 | ); 168 | is($code, WARNING, "(warning) code is $STATUS_TEXT{$code}"); 169 | is($message, 'D E F', "join_all '$join_all' (critical, warning) message is $message"); 170 | 171 | # ------------------------------------------------------------------------- 172 | # add_messages 173 | 174 | # Constant codes 175 | $np = Monitoring::Plugin->new(); 176 | $np->add_message( CRITICAL, "A B C" ); 177 | $np->add_message( WARNING, "D E F" ); 178 | ($code, $message) = $np->check_messages(); 179 | is($code, CRITICAL, "(CRITICAL, WARNING) code is $STATUS_TEXT{$code}"); 180 | is($message, $messages{critical}, "(CRITICAL, WARNING) message is $message"); 181 | 182 | $np = Monitoring::Plugin->new(); 183 | $np->add_message( CRITICAL, "A B C" ); 184 | ($code, $message) = $np->check_messages(); 185 | is($code, CRITICAL, "(CRITICAL) code is $STATUS_TEXT{$code}"); 186 | is($message, $messages{critical}, "(CRITICAL) message is $message"); 187 | 188 | $np = Monitoring::Plugin->new(); 189 | $np->add_message( WARNING, "D E F" ); 190 | ($code, $message) = $np->check_messages(); 191 | is($code, WARNING, "(WARNING) code is $STATUS_TEXT{$code}"); 192 | is($message, $messages{warning}, "(WARNING) message is $message"); 193 | 194 | $np = Monitoring::Plugin->new(); 195 | $np->add_message( WARNING, "D E F" ); 196 | $np->add_message( OK, "G H I" ); 197 | ($code, $message) = $np->check_messages(); 198 | is($code, WARNING, "(WARNING, OK) code is $STATUS_TEXT{$code}"); 199 | is($message, $messages{warning}, "(WARNING, OK) message is $message"); 200 | 201 | $np = Monitoring::Plugin->new(); 202 | $np->add_message( OK, "G H I" ); 203 | ($code, $message) = $np->check_messages(); 204 | is($code, OK, "(OK) code is $STATUS_TEXT{$code}"); 205 | is($message, $messages{ok}, "(OK) message is $message"); 206 | 207 | 208 | # String codes 209 | $np = Monitoring::Plugin->new(); 210 | $np->add_message( critical => "A B C" ); 211 | $np->add_message( warning => "D E F" ); 212 | ($code, $message) = $np->check_messages(); 213 | is($code, CRITICAL, "(critical, warning) code is $STATUS_TEXT{$code}"); 214 | is($message, $messages{critical}, "(critical, warning) message is $message"); 215 | 216 | $np = Monitoring::Plugin->new(); 217 | $np->add_message( critical => "A B C" ); 218 | ($code, $message) = $np->check_messages(); 219 | is($code, CRITICAL, "(critical) code is $STATUS_TEXT{$code}"); 220 | is($message, $messages{critical}, "(critical) message is $message"); 221 | 222 | $np = Monitoring::Plugin->new(); 223 | $np->add_message( warning => "D E F" ); 224 | ($code, $message) = $np->check_messages(); 225 | is($code, WARNING, "(warning) code is $STATUS_TEXT{$code}"); 226 | is($message, $messages{warning}, "(warning) message is $message"); 227 | 228 | $np = Monitoring::Plugin->new(); 229 | $np->add_message( warning => "D E F" ); 230 | $np->add_message( ok => "G H I" ); 231 | ($code, $message) = $np->check_messages(); 232 | is($code, WARNING, "(warning, ok) code is $STATUS_TEXT{$code}"); 233 | is($message, $messages{warning}, "(warning, ok) message is $message"); 234 | 235 | $np = Monitoring::Plugin->new(); 236 | $np->add_message( ok => "G H I" ); 237 | ($code, $message) = $np->check_messages(); 238 | is($code, OK, "(ok) code is $STATUS_TEXT{$code}"); 239 | is($message, $messages{ok}, "(ok) message is $message"); 240 | 241 | 242 | # No add_message 243 | $np = Monitoring::Plugin->new(); 244 | ($code, $message) = $np->check_messages(); 245 | is($code, OK, "() code is $STATUS_TEXT{$code}"); 246 | is($message, '', "() message is ''"); 247 | 248 | 249 | # ------------------------------------------------------------------------- 250 | # Error conditions 251 | 252 | # add_message errors 253 | $np = Monitoring::Plugin->new(); 254 | ok(! defined eval { $np->add_message( foobar => 'hi mum' ) }, 255 | 'add_message dies on invalid code'); 256 | ok(! defined eval { $np->add_message( OKAY => 'hi mum' ) }, 257 | 'add_message dies on invalid code'); 258 | # UNKNOWN and DEPENDENT error codes 259 | ok(! defined eval { $np->add_message( unknown => 'hi mum' ) }, 260 | 'add_message dies on UNKNOWN code'); 261 | ok(! defined eval { $np->add_message( dependent => 'hi mum' ) }, 262 | 'add_message dies on DEPENDENT code'); 263 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin/Functions.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin::Functions; 2 | 3 | # Functional interface to basic Monitoring::Plugin constants, exports, 4 | # and functions 5 | 6 | use 5.006; 7 | use strict; 8 | use warnings; 9 | 10 | use File::Basename; 11 | use Params::Validate qw(:types validate); 12 | use Math::Calc::Units; 13 | 14 | # Remember to update Monitoring::Plugins as well 15 | our $VERSION = "0.40"; 16 | 17 | our @STATUS_CODES = qw(OK WARNING CRITICAL UNKNOWN DEPENDENT); 18 | 19 | require Exporter; 20 | our @ISA = qw(Exporter); 21 | our @EXPORT = (@STATUS_CODES, qw(plugin_exit plugin_die check_messages)); 22 | our @EXPORT_OK = qw(%ERRORS %STATUS_TEXT @STATUS_CODES get_shortname max_state max_state_alt convert $value_re); 23 | our %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 | 29 | use constant OK => 0; 30 | use constant WARNING => 1; 31 | use constant CRITICAL => 2; 32 | use constant UNKNOWN => 3; 33 | use constant DEPENDENT => 4; 34 | 35 | our %ERRORS = ( 36 | 'OK' => OK, 37 | 'WARNING' => WARNING, 38 | 'CRITICAL' => CRITICAL, 39 | 'UNKNOWN' => UNKNOWN, 40 | 'DEPENDENT' => DEPENDENT, 41 | ); 42 | 43 | our %STATUS_TEXT = reverse %ERRORS; 44 | 45 | my $value = qr/[-+]?[\d\.]+/; 46 | our $value_re = qr/$value(?:e$value)?/; 47 | 48 | # _fake_exit flag and accessor/mutator, for testing 49 | my $_fake_exit = 0; 50 | sub _fake_exit { @_ ? $_fake_exit = shift : $_fake_exit }; 51 | 52 | # _use_die flag and accessor/mutator, so exceptions can be raised correctly 53 | my $_use_die = 0; 54 | sub _use_die { @_ ? $_use_die = shift : $_use_die }; 55 | 56 | sub 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 | 70 | sub 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 | 79 | sub 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 ) 89 | sub 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 | if (defined $message && $message ne '') { 122 | $output .= " - " unless $message =~ /^\s*\n/mxs; 123 | $output .= $message; 124 | } 125 | my $shortname = ($arg->{plugin} ? $arg->{plugin}->shortname : undef); 126 | $shortname ||= get_shortname(); # Should happen only if funnctions are called directly 127 | $output = "$shortname $output" if $shortname; 128 | if ($arg->{plugin}) { 129 | my $plugin = $arg->{plugin}; 130 | $output .= " | ". $plugin->all_perfoutput 131 | if $plugin->perfdata && $plugin->all_perfoutput; 132 | } 133 | $output .= "\n"; 134 | 135 | # Don't actually exit if _fake_exit set 136 | if ($_fake_exit) { 137 | require Monitoring::Plugin::ExitResult; 138 | return Monitoring::Plugin::ExitResult->new($code, $output); 139 | } 140 | 141 | _plugin_exit($code, $output); 142 | } 143 | 144 | sub _plugin_exit { 145 | my ($code, $output) = @_; 146 | # Print output and exit; die if flag set and called via a die in stack backtrace 147 | if ($_use_die) { 148 | for (my $i = 0;; $i++) { 149 | @_ = caller($i); 150 | last unless @_; 151 | if ($_[3] =~ m/die/) { 152 | $! = $code; 153 | die($output); 154 | } 155 | } 156 | } 157 | print $output; 158 | exit $code; 159 | } 160 | 161 | # plugin_die( $message, [ $code ]) OR plugin_die( $code, $message ) 162 | # Default $code: UNKNOWN 163 | sub plugin_die { 164 | my ($arg1, $arg2, $rest) = @_; 165 | 166 | # Named parameters 167 | if (defined $arg1 && ($arg1 eq 'return_code' || $arg1 eq 'message')) { 168 | return plugin_exit(@_); 169 | } 170 | 171 | # ($code, $message) 172 | elsif (defined $arg1 && (exists $ERRORS{$arg1} || exists $STATUS_TEXT{$arg1})) { 173 | return plugin_exit(@_); 174 | } 175 | 176 | # ($message, $code) 177 | elsif (defined $arg2 && (exists $ERRORS{$arg2} || exists $STATUS_TEXT{$arg2})) { 178 | return plugin_exit($arg2, $arg1, $rest); 179 | } 180 | 181 | # Else just assume $arg1 is the message and hope for the best 182 | else { 183 | return plugin_exit( UNKNOWN, $arg1, $arg2 ); 184 | } 185 | } 186 | 187 | # For backwards compatibility 188 | sub die { plugin_die(@_); } 189 | 190 | 191 | # ------------------------------------------------------------------------ 192 | # Utility functions 193 | 194 | # Simple wrapper around Math::Calc::Units::convert 195 | sub convert 196 | { 197 | my ($value, $from, $to) = @_; 198 | my ($newval) = Math::Calc::Units::convert("$value $from", $to, 'exact'); 199 | return $newval; 200 | } 201 | 202 | # ------------------------------------------------------------------------ 203 | # check_messages - return a status and/or message based on a set of 204 | # message arrays. 205 | # Returns a nagios status code in scalar context. 206 | # Returns a code and a message in list context. 207 | # The message is join($join, @array) for the relevant array for the code, 208 | # or join($join_all, $message) for all arrays if $join_all is set. 209 | sub check_messages { 210 | my %arg = validate( @_, { 211 | critical => { type => ARRAYREF }, 212 | warning => { type => ARRAYREF }, 213 | ok => { type => ARRAYREF | SCALAR, optional => 1 }, 214 | 'join' => { default => ' ' }, 215 | join_all => 0, 216 | }); 217 | $arg{join} = ' ' unless defined $arg{join}; 218 | 219 | # Decide $code 220 | my $code = OK; 221 | $code ||= CRITICAL if @{$arg{critical}}; 222 | $code ||= WARNING if @{$arg{warning}}; 223 | return $code unless wantarray; 224 | 225 | # Compose message 226 | my $message = ''; 227 | if ($arg{join_all}) { 228 | $message = join( $arg{join_all}, 229 | map { @$_ ? join( $arg{'join'}, @$_) : () } 230 | $arg{critical}, 231 | $arg{warning}, 232 | $arg{ok} ? (ref $arg{ok} ? $arg{ok} : [ $arg{ok} ]) : [] 233 | ); 234 | } 235 | 236 | else { 237 | $message ||= join( $arg{'join'}, @{$arg{critical}} ) 238 | if $code == CRITICAL; 239 | $message ||= join( $arg{'join'}, @{$arg{warning}} ) 240 | if $code == WARNING; 241 | $message ||= ref $arg{ok} ? join( $arg{'join'}, @{$arg{ok}} ) : $arg{ok} 242 | if $arg{ok}; 243 | } 244 | 245 | return ($code, $message); 246 | } 247 | 248 | # ------------------------------------------------------------------------ 249 | 250 | 1; 251 | 252 | # vim:sw=4:sm:et 253 | 254 | __END__ 255 | 256 | =head1 NAME 257 | 258 | Monitoring::Plugin::Functions - functions to simplify the creation of 259 | Nagios plugins 260 | 261 | =head1 SYNOPSIS 262 | 263 | # Constants OK, WARNING, CRITICAL, and UNKNOWN exported by default 264 | use Monitoring::Plugin::Functions; 265 | 266 | # plugin_exit( CODE, $message ) - exit with error code CODE, 267 | # and message "PLUGIN CODE - $message" 268 | plugin_exit( CRITICAL, $critical_error ) if $critical_error; 269 | plugin_exit( WARNING, $warning_error ) if $warning_error; 270 | plugin_exit( OK, $result ); 271 | 272 | # plugin_die( $message, [$CODE] ) - just like plugin_exit(), 273 | # but CODE is optional, defaulting to UNKNOWN 274 | do_something() 275 | or plugin_die("do_something() failed horribly"); 276 | do_something_critical() 277 | or plugin_die("do_something_critical() failed", CRITICAL); 278 | 279 | # check_messages - check a set of message arrays, returning a 280 | # CODE and/or a result message 281 | $code = check_messages(critical => \@crit, warning => \@warn); 282 | ($code, $message) = check_messages( 283 | critical => \@crit, warning => \@warn, 284 | ok => \@ok ); 285 | 286 | # get_shortname - return the default short name for this plugin 287 | # (as used by plugin_exit/die; not exported by default) 288 | $shortname = get_shortname(); 289 | 290 | 291 | =head1 DESCRIPTION 292 | 293 | This module is part of the Monitoring::Plugin family, a set of modules 294 | for simplifying the creation of Nagios plugins. This module exports 295 | convenience functions for the class methods provided by 296 | Monitoring::Plugin. It is intended for those who prefer a simpler 297 | functional interface, and who do not need the additional 298 | functionality of Monitoring::Plugin. 299 | 300 | =head2 EXPORTS 301 | 302 | Nagios status code constants are exported by default: 303 | 304 | OK 305 | WARNING 306 | CRITICAL 307 | UNKNOWN 308 | DEPENDENT 309 | 310 | as are the following functions: 311 | 312 | plugin_exit 313 | plugin_die 314 | check_messages 315 | 316 | The following variables and functions are exported only on request: 317 | 318 | %ERRORS 319 | %STATUS_TEXT 320 | get_shortname 321 | max_state 322 | max_state_alt 323 | 324 | 325 | =head2 FUNCTIONS 326 | 327 | The following functions are supported: 328 | 329 | =over 4 330 | 331 | =item plugin_exit( , $message ) 332 | 333 | Exit with return code CODE, and a standard nagios message of the 334 | form "PLUGIN CODE - $message". 335 | 336 | =item plugin_die( $message, [CODE] ) 337 | 338 | Same as plugin_exit(), except that CODE is optional, defaulting 339 | to UNKNOWN. NOTE: exceptions are not raised by default to calling code. 340 | Set C<$_use_die> flag if this functionality is required (see test code). 341 | 342 | =item check_messages( critical => \@crit, warning => \@warn ) 343 | 344 | Convenience function to check a set of message arrays and return 345 | an appropriate nagios return code and/or a result message. Returns 346 | only a return code in scalar context; returns a return code and an 347 | error message in list context i.e. 348 | 349 | # Scalar context 350 | $code = check_messages(critical => \@crit, warning => \@warn); 351 | # List context 352 | ($code, $msg) = check_messages(critical => \@crit, warning => \@warn); 353 | 354 | check_messages() accepts the following named arguments: 355 | 356 | =over 4 357 | 358 | =item critical => ARRAYREF 359 | 360 | An arrayref of critical error messages - check_messages() returns 361 | CRITICAL if this arrayref is non-empty. Mandatory. 362 | 363 | =item warning => ARRAYREF 364 | 365 | An arrayref of warning error messages - check_messages() returns 366 | WARNING if this arrayref is non-empty ('critical' is checked 367 | first). Mandatory. 368 | 369 | =item ok => ARRAYREF | SCALAR 370 | 371 | An arrayref of informational messages (or a single scalar message), 372 | used in list context if both the 'critical' and 'warning' arrayrefs 373 | are empty. Optional. 374 | 375 | =item join => SCALAR 376 | 377 | A string used to join the relevant array to generate the message 378 | string returned in list context i.e. if the 'critical' array @crit 379 | is non-empty, check_messages would return: 380 | 381 | join( $join, @crit ) 382 | 383 | as the result message. Optional; default: ' ' (space). 384 | 385 | =item join_all => SCALAR 386 | 387 | By default, only one set of messages are joined and returned in the 388 | result message i.e. if the result is CRITICAL, only the 'critical' 389 | messages are included in the result; if WARNING, only the 'warning' 390 | messages are included; if OK, the 'ok' messages are included (if 391 | supplied) i.e. the default is to return an 'errors-only' type 392 | message. 393 | 394 | If join_all is supplied, however, it will be used as a string to 395 | join the resultant critical, warning, and ok messages together i.e. 396 | all messages are joined and returned. 397 | 398 | =back 399 | 400 | =item get_shortname 401 | 402 | Return the default shortname used for this plugin i.e. the first 403 | token reported by plugin_exit/plugin_die. The default is basically 404 | 405 | uc basename( $ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0 ) 406 | 407 | with any leading 'CHECK_' and trailing file suffixes removed. 408 | 409 | get_shortname is not exported by default, so must be explicitly 410 | imported. 411 | 412 | =item max_state(@a) 413 | 414 | Returns the worst state in the array. Order is: CRITICAL, WARNING, OK, UNKNOWN, 415 | DEPENDENT 416 | 417 | The typical usage of max_state is to initialise the state as UNKNOWN and use 418 | it on the result of various test. If no test were performed successfully the 419 | state will still be UNKNOWN. 420 | 421 | =item max_state_alt(@a) 422 | 423 | Returns the worst state in the array. Order is: CRITICAL, WARNING, UNKNOWN, 424 | DEPENDENT, OK 425 | 426 | This is a true definition of a max state (OK last) and should be used if the 427 | internal tests performed can return UNKNOWN. 428 | 429 | =back 430 | 431 | =head1 SEE ALSO 432 | 433 | Monitoring::Plugin; the nagios plugin developer guidelines at 434 | https://www.monitoring-plugins.org/doc/guidelines.html. 435 | 436 | =head1 AUTHOR 437 | 438 | This code is maintained by the Monitoring Plugin Development Team: see 439 | https://monitoring-plugins.org 440 | 441 | =head1 COPYRIGHT AND LICENSE 442 | 443 | Copyright (C) 2014 by Monitoring Plugin Team 444 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 445 | 446 | This library is free software; you can redistribute it and/or modify 447 | it under the same terms as Perl itself. 448 | 449 | =cut 450 | -------------------------------------------------------------------------------- /t/Monitoring-Plugin-Performance.t: -------------------------------------------------------------------------------- 1 | 2 | use warnings; 3 | use strict; 4 | use Test::More; 5 | use Monitoring::Plugin::Functions; 6 | Monitoring::Plugin::Functions::_fake_exit(1); 7 | 8 | 9 | my (@p, $p); 10 | my @test = ( 11 | { 12 | perfoutput => "/=382MB;15264;15269;0;32768", label => '/', rrdlabel => 'root', value => 382, uom => 'MB', warning => 15264, critical => 15269, min => 0, max => 32768, clean_label => "root", 13 | }, { 14 | perfoutput => "/var=218MB;9443;9448", label => '/var', rrdlabel => 'var', value => '218', uom => 'MB', warning => 9443, critical => 9448, min => undef, max => undef, clean_label => "var", 15 | }, { 16 | perfoutput => '/var/long@:-/filesystem/name/and/bad/chars=218MB;9443;9448', label => '/var/long@:-/filesystem/name/and/bad/chars', rrdlabel => 'var_long____filesys', value => '218', uom => 'MB', warning => 9443, critical => 9448, min => undef, max => undef, clean_label => 'var_long____filesystem_name_and_bad_chars', 17 | }, { 18 | perfoutput => "'page file'=36%;80;90;", 19 | expected_perfoutput => "'page file'=36%;80;90", 20 | label => 'page file', 21 | rrdlabel => 'page_file', 22 | value => '36', 23 | uom => '%', 24 | warning => 80, 25 | critical => 90, 26 | min => undef, 27 | max => undef, 28 | clean_label => 'page_file', 29 | }, { 30 | perfoutput => "'data'=5;;;;", 31 | expected_perfoutput => "data=5;;", 32 | label => 'data', 33 | rrdlabel => 'data', 34 | value => 5, 35 | uom => "", 36 | warning => undef, 37 | critical => undef, 38 | min => undef, 39 | max => undef, 40 | clean_label => 'data', 41 | }, 42 | ); 43 | 44 | plan tests => (11 * scalar @test) + 176; 45 | 46 | use_ok('Monitoring::Plugin::Performance'); 47 | diag "\nusing Monitoring::Plugin::Performance revision ". $Monitoring::Plugin::Performance::VERSION . "\n" if $ENV{TEST_VERBOSE}; 48 | 49 | # Round-trip tests 50 | for my $t (@test) { 51 | # Parse to components 52 | ($p) = Monitoring::Plugin::Performance->parse_perfstring($t->{perfoutput}); 53 | is ($p->value, $t->{value}, "value okay $t->{value}"); 54 | is ($p->label, $t->{label}, "label okay $t->{label}"); 55 | is ($p->uom, $t->{uom}, "uom okay $t->{uom}"); 56 | 57 | # Construct from components 58 | my @construct = qw(label value uom warning critical min max); 59 | $p = Monitoring::Plugin::Performance->new(map { $_ => $t->{$_} } @construct); 60 | my $expected_perfoutput = $t->{perfoutput}; 61 | if (exists $t->{expected_perfoutput}) { 62 | $expected_perfoutput = $t->{expected_perfoutput}; 63 | }; 64 | is($p->perfoutput, $expected_perfoutput, "perfoutput okay ($expected_perfoutput)"); 65 | # Check threshold accessor 66 | foreach my $type (qw(warning critical)) { 67 | if (! defined $t->{$type}) { 68 | isnt( $p->threshold->$type->is_set, "threshold $type not set"); 69 | } else { 70 | is($p->threshold->$type->end, $t->{$type}, "threshold $type okay ($t->{$type})"); 71 | } 72 | } 73 | is($p->rrdlabel, $t->{rrdlabel}, "rrdlabel okay"); 74 | is($p->clean_label, $t->{clean_label}, "clean_label okay" ); 75 | 76 | # Construct using threshold 77 | @construct = qw(label value uom min max); 78 | $p = Monitoring::Plugin::Performance->new( 79 | map({ $_ => $t->{$_} } @construct), 80 | threshold => Monitoring::Plugin::Threshold->set_thresholds(warning => $t->{warning}, critical => $t->{critical}), 81 | ); 82 | is($p->perfoutput, $expected_perfoutput, "perfoutput okay ($expected_perfoutput)"); 83 | # Check warning/critical accessors 84 | foreach my $type (qw(warning critical)) { 85 | if (! defined $t->{$type}) { 86 | isnt( $p->threshold->$type->is_set, "threshold $type not set"); 87 | } else { 88 | is($p->threshold->$type->end, $t->{$type}, "threshold $type okay ($t->{$type})"); 89 | } 90 | } 91 | } 92 | 93 | 94 | # Test multiple parse_perfstrings 95 | @p = Monitoring::Plugin::Performance->parse_perfstring("/=382MB;15264;15269;; /var=218MB;9443;9448"); 96 | cmp_ok( $p[0]->label, 'eq', "/", "label okay"); 97 | cmp_ok( $p[0]->rrdlabel, 'eq', "root", "rrd label okay"); 98 | cmp_ok( $p[0]->value, '==', 382, "value okay"); 99 | cmp_ok( $p[0]->uom, 'eq', "MB", "uom okay"); 100 | cmp_ok( $p[0]->threshold->warning->end, "==", 15264, "warn okay"); 101 | cmp_ok( $p[0]->threshold->critical->end, "==", 15269, "crit okay"); 102 | ok(! defined $p[0]->min, "min undef"); 103 | ok(! defined $p[0]->max, "max undef"); 104 | 105 | cmp_ok( $p[1]->label, 'eq', "/var", "label okay"); 106 | cmp_ok( $p[1]->rrdlabel, 'eq', "var", "rrd label okay"); 107 | cmp_ok( $p[1]->value, '==', 218, "value okay"); 108 | cmp_ok( $p[1]->uom, 'eq', "MB", "uom okay"); 109 | cmp_ok( $p[1]->threshold->warning->end, "==", 9443, "warn okay"); 110 | cmp_ok( $p[1]->threshold->critical->end, "==", 9448, "crit okay"); 111 | 112 | @p = Monitoring::Plugin::Performance->parse_perfstring("rubbish"); 113 | ok( ! @p, "Errors correctly"); 114 | ok( ! Monitoring::Plugin::Performance->parse_perfstring(""), "Errors on empty string"); 115 | 116 | 117 | 118 | # Check 1 bad with 1 good format output 119 | @p = Monitoring::Plugin::Performance->parse_perfstring("rta=&391ms;100,200;500,034;0; pl=0%;20;60 "); 120 | is( scalar @p, 1, "One bad piece of data - only one returned" ); 121 | is( $p[0]->label, "pl", "label okay for different numeric"); 122 | is( $p[0]->value, 0, "value okay"); 123 | is( $p[0]->uom, "%", "uom okay"); 124 | ok( $p[0]->threshold->warning->is_set, "Warning range has been set"); 125 | is( $p[0]->threshold->warning, "20", "warn okay"); 126 | is( $p[0]->threshold->critical->is_set, 1, "Critical range has been set"); 127 | is( $p[0]->threshold->critical, "60", "warn okay"); 128 | 129 | # Same as above, but order swapped 130 | @p = Monitoring::Plugin::Performance->parse_perfstring(" pl=0%;20;60 rta=&391ms;100,200;500,034;0; "); 131 | is( scalar @p, 1, "One bad piece of data - only one returned" ); 132 | is( $p[0]->label, "pl", "label okay for different numeric"); 133 | is( $p[0]->value, 0, "value okay"); 134 | is( $p[0]->uom, "%", "uom okay"); 135 | ok( $p[0]->threshold->warning->is_set, "Warning range has been set"); 136 | is( $p[0]->threshold->warning, "20", "warn okay"); 137 | is( $p[0]->threshold->critical->is_set, 1, "Critical range has been set"); 138 | is( $p[0]->threshold->critical, "60", "warn okay"); 139 | 140 | 141 | 142 | 143 | @p = Monitoring::Plugin::Performance->parse_perfstring( 144 | "time=0.001229s;0.000000;0.000000;0.000000;10.000000"); 145 | cmp_ok( $p[0]->label, "eq", "time", "label okay"); 146 | cmp_ok( $p[0]->value, "==", 0.001229, "value okay"); 147 | cmp_ok( $p[0]->uom, "eq", "s", "uom okay"); 148 | ok( $p[0]->threshold->warning->is_set, "warn okay"); 149 | ok( $p[0]->threshold->critical->is_set, "crit okay"); 150 | 151 | 152 | 153 | @p = Monitoring::Plugin::Performance->parse_perfstring( 154 | "load1=0.000;5.000;9.000;0; load5=0.000;5.000;9.000;0; load15=0.000;5.000;9.000;0;"); 155 | cmp_ok( $p[0]->label, "eq", "load1", "label okay"); 156 | cmp_ok( $p[0]->value, "eq", "0", "value okay with 0 as string"); 157 | cmp_ok( $p[0]->uom, "eq", "", "uom empty"); 158 | cmp_ok( $p[0]->threshold->warning, "eq", "5", "warn okay"); 159 | cmp_ok( $p[0]->threshold->critical, "eq", "9", "crit okay"); 160 | cmp_ok( $p[1]->label, "eq", "load5", "label okay"); 161 | cmp_ok( $p[2]->label, "eq", "load15", "label okay"); 162 | 163 | @p = Monitoring::Plugin::Performance->parse_perfstring( "users=4;20;50;0" ); 164 | cmp_ok( $p[0]->label, "eq", "users", "label okay"); 165 | cmp_ok( $p[0]->value, "==", 4, "value okay"); 166 | cmp_ok( $p[0]->uom, "eq", "", "uom empty"); 167 | cmp_ok( $p[0]->threshold->warning, 'eq', "20", "warn okay"); 168 | cmp_ok( $p[0]->threshold->critical, 'eq', "50", "crit okay"); 169 | 170 | @p = Monitoring::Plugin::Performance->parse_perfstring( "users=4;20;50;0\n" ); 171 | ok( @p, "parse correctly with linefeed at end (nagiosgraph)"); 172 | 173 | @p = Monitoring::Plugin::Performance->parse_perfstring( 174 | "time=0.215300s;5.000000;10.000000;0.000000 size=426B;;;0" ); 175 | cmp_ok( $p[0]->label, "eq", "time", "label okay"); 176 | cmp_ok( $p[0]->value, "eq", "0.2153", "value okay"); 177 | cmp_ok( $p[0]->uom, "eq", "s", "uom okay"); 178 | cmp_ok( $p[0]->threshold->warning, 'eq', "5", "warn okay"); 179 | cmp_ok( $p[0]->threshold->critical, 'eq', "10", "crit okay"); 180 | cmp_ok( $p[1]->label, "eq", "size", "label okay"); 181 | cmp_ok( $p[1]->value, "==", 426, "value okay"); 182 | cmp_ok( $p[1]->uom, "eq", "B", "uom okay"); 183 | ok( ! $p[1]->threshold->warning->is_set, "warn okay"); 184 | ok( ! $p[1]->threshold->critical->is_set, "crit okay"); 185 | 186 | # Edge cases 187 | @p = Monitoring::Plugin::Performance->parse_perfstring("/home/a-m=0;0;0 shared-folder:big=20 12345678901234567890=20"); 188 | cmp_ok( $p[0]->rrdlabel, "eq", "home_a_m", "changing / to _"); 189 | ok( $p[0]->threshold->warning->is_set, "Warning range has been set"); 190 | cmp_ok( $p[1]->rrdlabel, "eq", "shared_folder_big", "replacing bad characters"); 191 | cmp_ok( $p[2]->rrdlabel, "eq", "1234567890123456789", "shortening rrd label"); 192 | 193 | # turn off fake_exit and enable use_die so we pick up on errors via plugin_die 194 | Monitoring::Plugin::Functions::_use_die(1); 195 | Monitoring::Plugin::Functions::_fake_exit(0); 196 | 197 | @p = Monitoring::Plugin::Performance->parse_perfstring("time=0.002722s;0.000000;0.000000;0.000000;10.000000"); 198 | cmp_ok( $p[0]->label, "eq", "time", "label okay"); 199 | cmp_ok( $p[0]->value, "eq", "0.002722", "value okay"); 200 | cmp_ok( $p[0]->uom, "eq", "s", "uom okay"); 201 | ok( defined $p[0]->threshold->warning->is_set, "Warning range has been set"); 202 | ok( defined $p[0]->threshold->critical->is_set, "Critical range has been set"); 203 | # The two below used to be cmp_ok, but Test::More 0.86 appears to have a problem with a stringification 204 | # of 0. See http://rt.cpan.org/Ticket/Display.html?id=41109 205 | # We need to force stringification for test. See RT 57709 206 | is( $p[0]->threshold->warning."", "0", "warn okay"); 207 | is( $p[0]->threshold->critical."", "0", "crit okay"); 208 | 209 | @p = Monitoring::Plugin::Performance->parse_perfstring("pct_used=73.7%;90;95"); 210 | cmp_ok( $p[0]->label, "eq", "pct_used", "label okay"); 211 | cmp_ok( $p[0]->value, "eq", "73.7", "value okay"); 212 | cmp_ok( $p[0]->uom, "eq", "%", "uom okay"); 213 | ok( defined eval { $p[0]->threshold->warning->is_set }, "Warning range has been set"); 214 | ok( defined eval { $p[0]->threshold->critical->is_set }, "Critical range has been set"); 215 | cmp_ok( $p[0]->threshold->warning, 'eq', "90", "warn okay"); 216 | cmp_ok( $p[0]->threshold->critical, 'eq', "95", "crit okay"); 217 | 218 | # Check ranges are parsed correctly 219 | @p = Monitoring::Plugin::Performance->parse_perfstring("availability=93.8%;90:99;"); 220 | is( $p[0]->label, "availability", "label okay"); 221 | is( $p[0]->value, "93.8", "value okay"); 222 | is( $p[0]->uom, "%", "uom okay"); 223 | ok( defined eval { $p[0]->threshold->warning->is_set }, "Warning range has been set"); 224 | is( $p[0]->threshold->critical->is_set, 0, "Critical range has not been set"); 225 | is( $p[0]->threshold->warning, "90:99", "warn okay"); 226 | 227 | # Check that negative values are parsed correctly in value and ranges 228 | @p = Monitoring::Plugin::Performance->parse_perfstring("offset=-0.004476s;-60.000000:-5;-120.000000:-3;"); 229 | is( $p[0]->label, "offset", "label okay"); 230 | is( $p[0]->value, "-0.004476", "value okay"); 231 | is( $p[0]->uom, "s", "uom okay"); 232 | ok( defined eval { $p[0]->threshold->warning->is_set }, "Warning range has been set"); 233 | ok( defined eval { $p[0]->threshold->critical->is_set }, "Critical range has been set"); 234 | is( $p[0]->threshold->warning, "-60:-5", "warn okay"); 235 | is( $p[0]->threshold->critical, "-120:-3", "crit okay"); 236 | 237 | # Check infinity values are okay 238 | @p = Monitoring::Plugin::Performance->parse_perfstring("salary=52GBP;~:23.5;45.2:"); 239 | is( $p[0]->label, "salary", "label okay"); 240 | is( $p[0]->value, "52", "value okay"); 241 | is( $p[0]->uom, "GBP", "uom okay"); 242 | ok( defined eval { $p[0]->threshold->warning->is_set }, "Warning range has been set"); 243 | is( $p[0]->threshold->critical->is_set, 1, "Critical range has been set"); 244 | is( $p[0]->threshold->warning, "~:23.5", "warn okay"); 245 | is( $p[0]->threshold->critical, "45.2:", "warn okay"); 246 | 247 | # Check scientific notation 248 | @p = Monitoring::Plugin::Performance->parse_perfstring("offset=1.120567322e-05"); 249 | is( $p[0]->label, "offset", "label okay for scientific notation"); 250 | is( $p[0]->value, 1.120567322e-05, "value okay"); 251 | is( $p[0]->uom, "", "uom okay"); 252 | ok( ! $p[0]->threshold->warning->is_set, "Warning range has not been set"); 253 | ok( ! $p[0]->threshold->critical->is_set, "Critical range has not been set"); 254 | 255 | 256 | # Check scientific notation with warnings and criticals 257 | @p = Monitoring::Plugin::Performance->parse_perfstring("offset=-1.120567322e-05unit;-1.1e-05:1.0e-03;4.3e+02:4.3e+25"); 258 | is( $p[0]->label, "offset", "label okay for scientific notation in warnings and criticals"); 259 | is( $p[0]->value, -1.120567322e-05, "value okay"); 260 | is( $p[0]->uom, "unit", "uom okay"); 261 | ok( $p[0]->threshold->warning->is_set, "Warning range has been set"); 262 | is( $p[0]->threshold->warning, "-1.1e-05:0.001", "warn okay"); 263 | is( $p[0]->threshold->critical->is_set, 1, "Critical range has been set"); 264 | is( $p[0]->threshold->critical, "430:4.3e+25", "warn okay"); 265 | 266 | 267 | 268 | # Check different collation with commas instead of periods 269 | @p = Monitoring::Plugin::Performance->parse_perfstring("rta=1,391ms;100,200;500,034;0; pl=0%;20;60;;"); 270 | is( $p[0]->label, "rta", "label okay for numeric with commas instead of periods"); 271 | is( $p[0]->value, 1.391, "value okay"); 272 | is( $p[0]->uom, "ms", "uom okay"); 273 | ok( $p[0]->threshold->warning->is_set, "Warning range has been set"); 274 | is( $p[0]->threshold->warning, "100.2", "warn okay"); 275 | is( $p[0]->threshold->critical->is_set, 1, "Critical range has been set"); 276 | is( $p[0]->threshold->critical, "500.034", "warn okay"); 277 | is( $p[1]->label, "pl", "label okay for different numeric"); 278 | is( $p[1]->value, 0, "value okay"); 279 | is( $p[1]->uom, "%", "uom okay"); 280 | ok( $p[1]->threshold->warning->is_set, "Warning range has been set"); 281 | is( $p[1]->threshold->warning, "20", "warn okay"); 282 | is( $p[1]->threshold->critical->is_set, 1, "Critical range has been set"); 283 | is( $p[1]->threshold->critical, "60", "warn okay"); 284 | 285 | 286 | # Another set of comma separated stuff 287 | @p = Monitoring::Plugin::Performance->parse_perfstring("offset=-0,023545s;60,000000;120,000000;"); 288 | is( $p[0]->label, "offset", "label okay for numeric with commas instead of periods"); 289 | is( $p[0]->value, -0.023545, "value okay"); 290 | is( $p[0]->uom, "s", "uom okay"); 291 | is( $p[0]->threshold->warning->is_set, 1, "Warning range has been set"); 292 | is( $p[0]->threshold->warning, 60, "warn okay"); 293 | is( $p[0]->threshold->critical->is_set, 1, "Critical range has been set"); 294 | is( $p[0]->threshold->critical, 120, "warn okay"); 295 | 296 | # Some values with funny commas 297 | @p = Monitoring::Plugin::Performance->parse_perfstring("time=1800,600,300,0,3600 other=45.6"); 298 | is( $p[0]->label, "other", "Ignored time=1800,600,300,0,3600, but allowed other=45.6"); 299 | is( $p[0]->value, 45.6, "value okay"); 300 | is( $p[0]->uom, "", "uom okay"); 301 | 302 | 303 | # Test labels with spaces (returned by nsclient++) 304 | @p = Monitoring::Plugin::Performance->parse_perfstring("'C:\ Label: Serial Number bc22aa2e'=8015MB;16387;18435;0;20484 'D:\ Label: Serial Number XA22aa2e'=8015MB;16388;18436;1;2048"); 305 | is( $p[0]->label, "C:\ Label: Serial Number bc22aa2e"); 306 | is( $p[0]->rrdlabel, "C__Label___Serial_N"); 307 | is( $p[0]->value, 8015, "value okay"); 308 | is( $p[0]->uom, "MB", "uom okay"); 309 | is( $p[0]->threshold->warning->end, 16387, "warn okay"); 310 | is( $p[0]->threshold->critical->end, 18435, "crit okay"); 311 | is( $p[0]->min, 0, "min ok"); 312 | is( $p[0]->max, 20484, "max ok"); 313 | 314 | is( $p[1]->label, "D:\ Label: Serial Number XA22aa2e", "label okay"); 315 | is( $p[1]->rrdlabel, "D__Label__Serial_Nu", "rrd label okay"); 316 | is( $p[1]->value, 8015, "value okay"); 317 | is( $p[1]->uom, "MB", "uom okay"); 318 | is( $p[1]->threshold->warning->end, 16388, "warn okay"); 319 | is( $p[1]->threshold->critical->end, 18436, "crit okay"); 320 | is( $p[1]->min, 1, "min ok"); 321 | is( $p[1]->max, 2048, "max ok"); 322 | 323 | 324 | # Mix labels with and without quotes 325 | @p = Monitoring::Plugin::Performance->parse_perfstring(" short=4 'C:\ Label: Serial Number bc22aa2e'=8015MB;16387;18435;0;20484 end=5 "); 326 | is( $p[0]->label, "short" ); 327 | is( $p[0]->rrdlabel, "short"); 328 | is( $p[0]->value, 4, "value okay"); 329 | is( $p[0]->uom, "", "uom okay"); 330 | isnt( $p[0]->threshold->warning->is_set, "warn okay"); 331 | isnt( $p[0]->threshold->critical->is_set, "crit okay"); 332 | is( $p[0]->min, undef, "min ok"); 333 | is( $p[0]->max, undef, "max ok"); 334 | 335 | is( $p[1]->label, "C:\ Label: Serial Number bc22aa2e", "label okay"); 336 | is( $p[1]->rrdlabel, "C__Label___Serial_N", "rrd label okay"); 337 | is( $p[1]->value, 8015, "value okay"); 338 | is( $p[1]->uom, "MB", "uom okay"); 339 | is( $p[1]->threshold->warning->end, 16387, "warn okay"); 340 | is( $p[1]->threshold->critical->end, 18435, "crit okay"); 341 | is( $p[1]->min, 0, "min ok"); 342 | is( $p[1]->max, 20484, "max ok"); 343 | 344 | is( $p[2]->label, "end" ); 345 | is( $p[2]->rrdlabel, "end" ); 346 | is( $p[2]->value, 5, "value okay"); 347 | is( $p[2]->uom, "", "uom okay"); 348 | isnt( $p[2]->threshold->warning->is_set, "warn okay"); 349 | isnt( $p[2]->threshold->critical->is_set, 18436, "crit okay"); 350 | is( $p[2]->min, undef, "min ok"); 351 | is( $p[2]->max, undef, "max ok"); 352 | 353 | 354 | @p = Monitoring::Plugin::Performance->parse_perfstring("processes=9;WKFLSV32.exe;9="); 355 | is_deeply( \@p, [], "Fails parsing correctly"); 356 | 357 | # add_perfdata tests in t/Monitoring-Plugin-01.t 358 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin; 2 | 3 | use Monitoring::Plugin::Functions qw(:codes %ERRORS %STATUS_TEXT @STATUS_CODES); 4 | use Params::Validate qw(:all); 5 | 6 | use 5.006; 7 | use strict; 8 | use warnings; 9 | 10 | use Carp; 11 | use base qw(Class::Accessor::Fast); 12 | 13 | Monitoring::Plugin->mk_accessors(qw( 14 | shortname 15 | perfdata 16 | messages 17 | opts 18 | threshold 19 | )); 20 | 21 | use Exporter; 22 | our @ISA = qw(Exporter); 23 | our @EXPORT = (@STATUS_CODES); 24 | our @EXPORT_OK = qw(%ERRORS %STATUS_TEXT); 25 | 26 | # CPAN stupidly won't index this module without a literal $VERSION here, 27 | # so we're forced to duplicate it explicitly 28 | # Make sure you update $Monitoring::Plugin::Functions::VERSION too 29 | our $VERSION = "0.40"; 30 | 31 | sub new { 32 | my $class = shift; 33 | # my %args = @_; 34 | 35 | my %args = validate( @_, 36 | { 37 | shortname => 0, 38 | usage => 0, 39 | version => 0, 40 | url => 0, 41 | plugin => 0, 42 | blurb => 0, 43 | extra => 0, 44 | license => 0, 45 | timeout => 0 46 | }, 47 | ); 48 | 49 | my $shortname = Monitoring::Plugin::Functions::get_shortname(\%args); 50 | delete $args{shortname} if (exists $args{shortname}); 51 | my $self = { 52 | shortname => $shortname, 53 | perfdata => [], # to be added later 54 | messages => { 55 | warning => [], 56 | critical => [], 57 | ok => [] 58 | }, 59 | opts => undef, # see below 60 | threshold => undef, # defined later 61 | }; 62 | bless $self, $class; 63 | if (exists $args{usage}) { 64 | require Monitoring::Plugin::Getopt; 65 | $self->opts( new Monitoring::Plugin::Getopt(%args) ); 66 | } 67 | return $self; 68 | } 69 | 70 | sub add_perfdata { 71 | my ($self, %args) = @_; 72 | require Monitoring::Plugin::Performance; 73 | my $perf = Monitoring::Plugin::Performance->new(%args); 74 | push @{$self->perfdata}, $perf; 75 | } 76 | sub all_perfoutput { 77 | my $self = shift; 78 | return join(" ", map {$_->perfoutput} (@{$self->perfdata})); 79 | } 80 | 81 | sub set_thresholds { 82 | my $self = shift; 83 | require Monitoring::Plugin::Threshold; 84 | return $self->threshold( Monitoring::Plugin::Threshold->set_thresholds(@_)); 85 | } 86 | 87 | # MP::Functions wrappers 88 | sub plugin_exit { 89 | my $self = shift; 90 | Monitoring::Plugin::Functions::plugin_exit(@_, { plugin => $self }); 91 | } 92 | sub plugin_die { 93 | my $self = shift; 94 | Monitoring::Plugin::Functions::plugin_die(@_, { plugin => $self }); 95 | } 96 | sub nagios_exit { 97 | my $self = shift; 98 | Monitoring::Plugin::Functions::plugin_exit(@_, { plugin => $self }); 99 | } 100 | sub nagios_die { 101 | my $self = shift; 102 | Monitoring::Plugin::Functions::plugin_die(@_, { plugin => $self }); 103 | } 104 | sub die { 105 | my $self = shift; 106 | Monitoring::Plugin::Functions::plugin_die(@_, { plugin => $self }); 107 | } 108 | sub max_state { 109 | Monitoring::Plugin::Functions::max_state(@_); 110 | } 111 | sub max_state_alt { 112 | Monitoring::Plugin::Functions::max_state_alt(@_); 113 | } 114 | 115 | # top level interface to Monitoring::Plugin::Threshold 116 | sub check_threshold { 117 | my $self = shift; 118 | 119 | my %args; 120 | 121 | if ( $#_ == 0 && (! ref $_[0] || ref $_[0] eq "ARRAY" )) { # one positional param 122 | %args = (check => shift); 123 | } 124 | else { 125 | %args = validate ( @_, { # named params 126 | check => 1, 127 | warning => 0, 128 | critical => 0, 129 | } ); 130 | } 131 | 132 | # in order of preference, get warning and critical from 133 | # 1. explicit arguments to check_threshold 134 | # 2. previously explicitly set threshold object or implicit theshold object created by warning and critical 135 | if ( exists $args{warning} || exists $args{critical} ) { 136 | $self->set_thresholds( 137 | warning => $args{warning}, 138 | critical => $args{critical}, 139 | ); 140 | } 141 | elsif ( defined $self->threshold ) { 142 | # noop 143 | } 144 | else { 145 | return UNKNOWN; 146 | } 147 | 148 | return $self->threshold->get_status($args{check}); 149 | } 150 | 151 | # top level interface to my Monitoring::Plugin::Getopt object 152 | sub add_arg { 153 | my $self = shift; 154 | $self->opts->arg(@_) if $self->_check_for_opts; 155 | } 156 | sub getopts { 157 | my $self = shift; 158 | $self->opts->getopts(@_) if $self->_check_for_opts; 159 | $self->set_thresholds( 160 | warning => $self->opts->warning, 161 | critical => $self->opts->critical, 162 | ) if ( defined $self->opts->warning || defined $self->opts->critical ); 163 | } 164 | 165 | sub _check_for_opts { 166 | my $self = shift; 167 | croak 168 | "You have to supply a 'usage' param to Monitoring::Plugin::new() if you want to use Getopts from your Monitoring::Plugin object." 169 | unless ref $self->opts() eq 'Monitoring::Plugin::Getopt'; 170 | return $self; 171 | } 172 | 173 | 174 | 175 | # ------------------------------------------------------------------------- 176 | # MP::Functions::check_messages helpers and wrappers 177 | 178 | sub add_message { 179 | my $self = shift; 180 | my ($code, @messages) = @_; 181 | 182 | croak "Invalid error code '$code'" 183 | unless defined($ERRORS{uc $code}) || defined($STATUS_TEXT{$code}); 184 | 185 | # Store messages using strings rather than numeric codes 186 | $code = $STATUS_TEXT{$code} if $STATUS_TEXT{$code}; 187 | $code = lc $code; 188 | croak "Error code '$code' not supported by add_message" 189 | if $code eq 'unknown' || $code eq 'dependent'; 190 | 191 | $self->messages($code, []) unless $self->messages->{$code}; 192 | push @{$self->messages->{$code}}, @messages; 193 | } 194 | 195 | sub check_messages { 196 | my $self = shift; 197 | my %args = @_; 198 | 199 | # Add object messages to any passed in as args 200 | for my $code (qw(critical warning ok)) { 201 | my $messages = $self->messages->{$code} || []; 202 | if ($args{$code}) { 203 | unless (ref $args{$code} eq 'ARRAY') { 204 | if ($code eq 'ok') { 205 | $args{$code} = [ $args{$code} ]; 206 | } else { 207 | croak "Invalid argument '$code'" 208 | } 209 | } 210 | push @{$args{$code}}, @$messages; 211 | } 212 | else { 213 | $args{$code} = $messages; 214 | } 215 | } 216 | 217 | Monitoring::Plugin::Functions::check_messages(%args); 218 | } 219 | 220 | # ------------------------------------------------------------------------- 221 | 222 | 1; 223 | 224 | #vim:et:sw=4 225 | 226 | __END__ 227 | 228 | =head1 NAME 229 | 230 | Monitoring::Plugin - A family of perl modules to streamline writing Naemon, Nagios, 231 | Icinga or Shinken (and compatible) plugins. 232 | 233 | =head1 SYNOPSIS 234 | 235 | # Constants OK, WARNING, CRITICAL, and UNKNOWN are exported by default 236 | # See also Monitoring::Plugin::Functions for a functional interface 237 | use Monitoring::Plugin; 238 | 239 | # Constructor 240 | $np = Monitoring::Plugin->new; # OR 241 | $np = Monitoring::Plugin->new( shortname => "PAGESIZE" ); # OR 242 | 243 | 244 | # use Monitoring::Plugin::Getopt to process the @ARGV command line options: 245 | # --verbose, --help, --usage, --timeout and --host are defined automatically. 246 | $np = Monitoring::Plugin->new( 247 | usage => "Usage: %s [ -v|--verbose ] [-H ] [-t ] " 248 | . "[ -c|--critical= ] [ -w|--warning= ]", 249 | ); 250 | 251 | # add valid command line options and build them into your usage/help documentation. 252 | $np->add_arg( 253 | spec => 'warning|w=s', 254 | help => '-w, --warning=INTEGER:INTEGER . See ' 255 | . 'https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT ' 256 | . 'for the threshold format. ', 257 | ); 258 | 259 | # Parse @ARGV and process standard arguments (e.g. usage, help, version) 260 | $np->getopts; 261 | 262 | 263 | # Exit/return value methods - plugin_exit( CODE, MESSAGE ), 264 | # plugin_die( MESSAGE, [CODE]) 265 | $page = retrieve_page($page1) 266 | or $np->plugin_exit( UNKNOWN, "Could not retrieve page" ); 267 | # Return code: 3; 268 | # output: PAGESIZE UNKNOWN - Could not retrieve page 269 | test_page($page) 270 | or $np->plugin_exit( CRITICAL, "Bad page found" ); 271 | 272 | # plugin_die() is just like plugin_exit(), but return code defaults 273 | # to UNKNOWN 274 | $page = retrieve_page($page2) 275 | or $np->plugin_die( "Could not retrieve page" ); 276 | # Return code: 3; 277 | # output: PAGESIZE UNKNOWN - Could not retrieve page 278 | 279 | # Threshold methods 280 | $code = $np->check_threshold( 281 | check => $value, 282 | warning => $warning_threshold, 283 | critical => $critical_threshold, 284 | ); 285 | $np->plugin_exit( $code, "Threshold check failed" ) if $code != OK; 286 | 287 | # Message methods 288 | # add_message( CODE, $message ); check_messages() 289 | for (@collection) { 290 | if (m/Error/) { 291 | $np->add_message( CRITICAL, $_ ); 292 | } else { 293 | $np->add_message( OK, $_ ); 294 | } 295 | } 296 | ($code, $message) = $np->check_messages(); 297 | plugin_exit( $code, $message ); 298 | # If any items in collection matched m/Error/, returns CRITICAL and 299 | # the joined set of Error messages; otherwise returns OK and the 300 | # joined set of ok messages 301 | 302 | 303 | # Perfdata methods 304 | $np->add_perfdata( 305 | label => "size", 306 | value => $value, 307 | uom => "kB", 308 | threshold => $threshold, 309 | ); 310 | $np->add_perfdata( label => "time", ... ); 311 | $np->plugin_exit( OK, "page size at http://... was ${value}kB" ); 312 | # Return code: 0; 313 | # output: PAGESIZE OK - page size at http://... was 36kB \ 314 | # | size=36kB;10:25;25: time=... 315 | 316 | 317 | =head1 DESCRIPTION 318 | 319 | Monitoring::Plugin and its associated Monitoring::Plugin::* modules are a 320 | family of perl modules to streamline writing Monitoring plugins. The main 321 | end user modules are Monitoring::Plugin, providing an object-oriented 322 | interface to the entire Monitoring::Plugin::* collection, and 323 | Monitoring::Plugin::Functions, providing a simpler functional interface to 324 | a useful subset of the available functionality. 325 | 326 | The purpose of the collection is to make it as simple as possible for 327 | developers to create plugins that conform the Monitoring Plugin guidelines 328 | (https://www.monitoring-plugins.org/doc/guidelines.html). 329 | 330 | 331 | =head2 EXPORTS 332 | 333 | Nagios status code constants are exported by default: 334 | 335 | OK 336 | WARNING 337 | CRITICAL 338 | UNKNOWN 339 | DEPENDENT 340 | 341 | The following variables are also exported on request: 342 | 343 | =over 4 344 | 345 | =item %ERRORS 346 | 347 | A hash mapping error strings ("CRITICAL", "UNKNOWN", etc.) to the 348 | corresponding status code. 349 | 350 | =item %STATUS_TEXT 351 | 352 | A hash mapping status code constants (OK, WARNING, CRITICAL, etc.) to the 353 | corresponding error string ("OK", "WARNING, "CRITICAL", etc.) i.e. the 354 | reverse of %ERRORS. 355 | 356 | =back 357 | 358 | 359 | =head2 CONSTRUCTOR 360 | 361 | Monitoring::Plugin->new; 362 | 363 | Monitoring::Plugin->new( shortname => 'PAGESIZE' ); 364 | 365 | Monitoring::Plugin->new( 366 | usage => "Usage: %s [ -v|--verbose ] [-H ] [-t ] 367 | [ -c|--critical= ] [ -w|--warning= ] ", 368 | version => $VERSION, 369 | blurb => $blurb, 370 | extra => $extra, 371 | url => $url, 372 | license => $license, 373 | plugin => basename $0, 374 | timeout => 15, 375 | ); 376 | 377 | Instantiates a new Monitoring::Plugin object. Accepts the following named 378 | arguments: 379 | 380 | =over 4 381 | 382 | =item shortname 383 | 384 | The 'shortname' for this plugin, used as the first token in the plugin 385 | output by the various exit methods. Default: uc basename $0. 386 | 387 | =item usage ("Usage: %s --foo --bar") 388 | 389 | Passing a value for the usage() argument makes Monitoring::Plugin 390 | instantiate its own C object so you can start 391 | doing command line argument processing. See 392 | L for more about "usage" and the 393 | following options: 394 | 395 | =item version 396 | 397 | =item url 398 | 399 | =item blurb 400 | 401 | =item license 402 | 403 | =item extra 404 | 405 | =item plugin 406 | 407 | =item timeout 408 | 409 | =back 410 | 411 | =head2 GETTER/SETTER 412 | 413 | The following internal variables can be retrieved or set by calling a 414 | method with the respective name. Expect for C, don't change 415 | values unless you know what you're doing. 416 | 417 | Examples: 418 | 419 | use Data::Dumper; 420 | print Dumper($plugin->perfdata); 421 | $plugin->shortname('DifferentName'); 422 | 423 | =over 424 | 425 | =item shortname 426 | 427 | =item perfdata 428 | 429 | =item messages 430 | 431 | =item opts 432 | 433 | =item threshold 434 | 435 | =back 436 | 437 | =head2 OPTION HANDLING METHODS 438 | 439 | C provides these methods for accessing the functionality in C. 440 | 441 | =over 4 442 | 443 | =item add_arg 444 | 445 | Examples: 446 | 447 | # Define --hello argument (named parameters) 448 | $plugin->add_arg( 449 | spec => 'hello=s', 450 | help => "--hello\n Hello string", 451 | required => 1, 452 | ); 453 | 454 | # Define --hello argument (positional parameters) 455 | # Parameter order is 'spec', 'help', 'default', 'required?' 456 | $plugin->add_arg('hello=s', "--hello\n Hello string", undef, 1); 457 | 458 | See L for more details. 459 | 460 | =item getopts() 461 | 462 | Parses and processes the command line options you've defined, 463 | automatically doing the right thing with help/usage/version arguments. 464 | 465 | See L for more details. 466 | 467 | =item opts() 468 | 469 | Assuming you've instantiated it by passing 'usage' to new(), opts() 470 | returns the Monitoring::Plugin object's C object, 471 | with which you can do lots of great things. 472 | 473 | E.g. 474 | 475 | if ( $plugin->opts->verbose ) { 476 | print "yah yah YAH YAH YAH!!!"; 477 | } 478 | 479 | # start counting down to timeout 480 | alarm $plugin->opts->timeout; 481 | your_long_check_step_that_might_time_out(); 482 | 483 | # access any of your custom command line options, 484 | # assuming you've done these steps above: 485 | # $plugin->add_arg('my_argument=s', '--my_argument [STRING]'); 486 | # $plugin->getopts; 487 | print $plugin->opts->my_argument; 488 | 489 | Again, see L. 490 | 491 | =back 492 | 493 | =head2 EXIT METHODS 494 | 495 | =over 4 496 | 497 | =item plugin_exit( , $message ) 498 | 499 | Exit with return code CODE, and a standard nagios message of the 500 | form "SHORTNAME CODE - $message". 501 | 502 | =item nagios_exit( , $message ) 503 | 504 | Alias for plugin_exit(). Deprecated. 505 | 506 | =item plugin_die( $message, [] ) 507 | 508 | Same as plugin_exit(), except that CODE is optional, defaulting 509 | to UNKNOWN. NOTE: exceptions are not raised by default to calling code. 510 | Set C<$_use_die> flag if this functionality is required (see test code). 511 | 512 | =item nagios_die( $message, [] ) 513 | 514 | Alias for plugin_die(). Deprecated. 515 | 516 | =item die( $message, [] ) 517 | 518 | Alias for plugin_die(). Deprecated. 519 | 520 | =item max_state, max_state_alt 521 | 522 | These are wrapper function for Monitoring::Plugin::Functions::max_state and 523 | Monitoring::Plugin::Functions::max_state_alt. 524 | 525 | =back 526 | 527 | =head2 THRESHOLD METHODS 528 | 529 | These provide a top level interface to the 530 | C module; for more details, see 531 | L and L. 532 | 533 | =over 4 534 | 535 | =item check_threshold( $value ) 536 | 537 | =item check_threshold( check => $value, warning => $warn, critical => $crit ) 538 | 539 | Evaluates $value against the thresholds and returns OK, CRITICAL, or 540 | WARNING constant. The thresholds may be: 541 | 542 | 1. explicitly set by passing 'warning' and/or 'critical' parameters to 543 | C, or, 544 | 545 | 2. explicitly set by calling C before C, or, 546 | 547 | 3. implicitly set by command-line parameters -w, -c, --critical or 548 | --warning, if you have run C<< $plugin->getopts() >>. 549 | 550 | You can specify $value as an array of values and each will be checked against 551 | the thresholds. 552 | 553 | The return value is ready to pass to C , e . g ., 554 | 555 | $p->plugin_exit( 556 | return_code => $p->check_threshold($result), 557 | message => " sample result was $result" 558 | ); 559 | 560 | 561 | =item set_thresholds(warning => "10:25", critical => "~:25") 562 | 563 | Sets the acceptable ranges and creates the plugin's 564 | Monitoring::Plugins::Threshold object. See 565 | https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT 566 | for details and examples of the threshold format. 567 | 568 | =item threshold() 569 | 570 | Returns the object's C object, if it has 571 | been defined by calling set_thresholds(). You can pass a new 572 | Threshold object to it to replace the old one too, but you shouldn't 573 | need to do that from a plugin script. 574 | 575 | =back 576 | 577 | =head2 MESSAGE METHODS 578 | 579 | add_messages and check_messages are higher-level convenience methods to add 580 | and then check a set of messages, returning an appropriate return code 581 | and/or result message. They are equivalent to maintaining a set of @critical, 582 | @warning, and and @ok message arrays (add_message), and then doing a final 583 | if test (check_messages) like this: 584 | 585 | if (@critical) { 586 | plugin_exit( CRITICAL, join(' ', @critical) ); 587 | } 588 | elsif (@warning) { 589 | plugin_exit( WARNING, join(' ', @warning) ); 590 | } 591 | else { 592 | plugin_exit( OK, join(' ', @ok) ); 593 | } 594 | 595 | =over 4 596 | 597 | =item add_message( , $message ) 598 | 599 | Add a message with CODE status to the object. May be called multiple times. 600 | The messages added are checked by check_messages, following. 601 | 602 | Only CRITICAL, WARNING, and OK are accepted as valid codes. 603 | 604 | 605 | =item check_messages() 606 | 607 | Check the current set of messages and return an appropriate nagios return 608 | code and/or a result message. In scalar context, returns only a return 609 | code; in list context returns both a return code and an output message, 610 | suitable for passing directly to plugin_exit() e.g. 611 | 612 | $code = $np->check_messages; 613 | ($code, $message) = $np->check_messages; 614 | 615 | check_messages returns CRITICAL if any critical messages are found, WARNING 616 | if any warning messages are found, and OK otherwise. The message returned 617 | in list context defaults to the joined set of error messages; this may be 618 | customised using the arguments below. 619 | 620 | check_messages accepts the following named arguments (none are required): 621 | 622 | =over 4 623 | 624 | =item join => SCALAR 625 | 626 | A string used to join the relevant array to generate the message 627 | string returned in list context i.e. if the 'critical' array @crit 628 | is non-empty, check_messages would return: 629 | 630 | join( $join, @crit ) 631 | 632 | as the result message. Default: ' ' (space). 633 | 634 | =item join_all => SCALAR 635 | 636 | By default, only one set of messages are joined and returned in the 637 | result message i.e. if the result is CRITICAL, only the 'critical' 638 | messages are included in the result; if WARNING, only the 'warning' 639 | messages are included; if OK, the 'ok' messages are included (if 640 | supplied) i.e. the default is to return an 'errors-only' type 641 | message. 642 | 643 | If join_all is supplied, however, it will be used as a string to 644 | join the resultant critical, warning, and ok messages together i.e. 645 | all messages are joined and returned. 646 | 647 | =item critical => ARRAYREF 648 | 649 | Additional critical messages to supplement any passed in via add_message(). 650 | 651 | =item warning => ARRAYREF 652 | 653 | Additional warning messages to supplement any passed in via add_message(). 654 | 655 | =item ok => ARRAYREF | SCALAR 656 | 657 | Additional ok messages to supplement any passed in via add_message(). 658 | 659 | =back 660 | 661 | =back 662 | 663 | 664 | =head2 PERFORMANCE DATA METHODS 665 | 666 | =over 4 667 | 668 | =item add_perfdata( label => "size", value => $value, uom => "kB", threshold => $threshold ) 669 | 670 | Add a set of performance data to the object. May be called multiple times. 671 | The performance data is included in the standard plugin output messages by 672 | the various exit methods. 673 | 674 | See the Monitoring::Plugin::Performance documentation for more information on 675 | performance data and the various field definitions, as well as the relevant 676 | section of the Monitoring Plugin guidelines 677 | (https://www.monitoring-plugins.org/doc/guidelines.html#AEN202). 678 | 679 | =back 680 | 681 | 682 | =head1 EXAMPLES 683 | 684 | "Enough talk! Show me some examples!" 685 | 686 | See the file 'check_stuff.pl' in the 't' directory included with the 687 | Monitoring::Plugin distribution for a complete working example of a plugin 688 | script. 689 | 690 | 691 | =head1 VERSIONING 692 | 693 | The Monitoring::Plugin::* modules are currently experimental and so the 694 | interfaces may change up until Monitoring::Plugin hits version 1.0, although 695 | every attempt will be made to keep them as backwards compatible as 696 | possible. 697 | 698 | 699 | =head1 SEE ALSO 700 | 701 | See L for a simple functional interface to a subset 702 | of the available Monitoring::Plugin functionality. 703 | 704 | See also L, L, 705 | L, L, and 706 | L. 707 | 708 | The Monitoring Plugin project page is at http://monitoring-plugins.org. 709 | 710 | 711 | =head1 BUGS 712 | 713 | Please report bugs in these modules to the Monitoring Plugin development team: 714 | devel@monitoring-plugins.org. 715 | 716 | 717 | =head1 AUTHOR 718 | 719 | Maintained by the Monitoring Plugin development team - 720 | https://www.monitoring-plugins.org. 721 | 722 | Originally by Ton Voon, Eton.voon@altinity.comE. 723 | 724 | =head1 COPYRIGHT AND LICENSE 725 | 726 | Copyright (C) 2014 by Monitoring Plugin Team 727 | Copyright (C) 2006-2014 by Nagios Plugin Development Team 728 | 729 | This library is free software; you can redistribute it and/or modify it 730 | under the same terms as Perl itself, either Perl version 5.8.4 or, at your 731 | option, any later version of Perl 5 you may have available. 732 | 733 | =cut 734 | -------------------------------------------------------------------------------- /lib/Monitoring/Plugin/Getopt.pm: -------------------------------------------------------------------------------- 1 | package Monitoring::Plugin::Getopt; 2 | 3 | # 4 | # Monitoring::Plugin::Getopt - OO perl module providing standardised argument 5 | # processing for nagios plugins 6 | # 7 | 8 | use 5.006; 9 | use strict; 10 | use warnings; 11 | 12 | use File::Basename; 13 | use Getopt::Long qw(:config no_ignore_case bundling); 14 | use Carp; 15 | use Params::Validate qw(:all); 16 | use base qw(Class::Accessor); 17 | 18 | use Monitoring::Plugin::Functions; 19 | use Monitoring::Plugin::Config; 20 | use vars qw($VERSION); 21 | $VERSION = $Monitoring::Plugin::Functions::VERSION; 22 | 23 | # Standard defaults 24 | my %DEFAULT = ( 25 | timeout => 15, 26 | verbose => 0, 27 | license => 28 | "This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. 29 | It may be used, redistributed and/or modified under the terms of the GNU 30 | General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).", 31 | ); 32 | # Standard arguments 33 | my @ARGS = ({ 34 | spec => 'usage|?', 35 | help => "-?, --usage\n Print usage information", 36 | }, { 37 | spec => 'help|h', 38 | help => "-h, --help\n Print detailed help screen", 39 | }, { 40 | spec => 'version|V', 41 | help => "-V, --version\n Print version information", 42 | }, { 43 | spec => 'extra-opts:s@', 44 | help => "--extra-opts=[section][\@file]\n Read options from an ini file. See https://www.monitoring-plugins.org/doc/extra-opts.html\n for usage and examples.", 45 | }, { 46 | spec => 'timeout|t=i', 47 | help => "-t, --timeout=INTEGER\n Seconds before plugin times out (default: %s)", 48 | default => $DEFAULT{timeout}, 49 | }, { 50 | spec => 'verbose|v+', 51 | help => "-v, --verbose\n Show details for command-line debugging (can repeat up to 3 times)", 52 | default => $DEFAULT{verbose}, 53 | }, 54 | ); 55 | # Standard arguments we traditionally display last in the help output 56 | my %DEFER_ARGS = map { $_ => 1 } qw(timeout verbose); 57 | 58 | # ------------------------------------------------------------------------- 59 | # Private methods 60 | 61 | sub _die 62 | { 63 | my $self = shift; 64 | my ($msg) = @_; 65 | $msg .= "\n" unless substr($msg, -1) eq "\n"; 66 | Monitoring::Plugin::Functions::_plugin_exit(3, $msg); 67 | } 68 | 69 | # Return the given attribute, if set, including a final newline 70 | sub _attr 71 | { 72 | my $self = shift; 73 | my ($item, $extra) = @_; 74 | $extra = '' unless defined $extra; 75 | return '' unless $self->{_attr}->{$item}; 76 | $self->{_attr}->{$item} . "\n" . $extra; 77 | } 78 | 79 | # Turn argument spec into help-style output 80 | sub _spec_to_help 81 | { 82 | my ($self, $spec, $label) = @_; 83 | 84 | my ($opts, $type) = split /=|:|!/, $spec, 2; 85 | my $optional = ($spec =~ m/:/); 86 | my $boolean = ($spec =~ m/!/); 87 | my (@short, @long); 88 | for (split /\|/, $opts) { 89 | if (length $_ == 1) { 90 | push @short, "-$_"; 91 | } else { 92 | push @long, $boolean ? "--[no-]$_" : "--$_"; 93 | } 94 | } 95 | 96 | my $help = join(', ', @short, @long); 97 | if ($type) { 98 | if (!$label) { 99 | if ($type eq 'i' || $type eq '+' || $type =~ /\d+/) { 100 | $label = 'INTEGER'; 101 | } 102 | else { 103 | $label = 'STRING'; 104 | } 105 | } 106 | 107 | if ($optional) { 108 | $help .= '[=' . $label . ']'; 109 | } 110 | else { 111 | $help .= '=' . $label; 112 | } 113 | } 114 | elsif ($label) { 115 | carp "Label specified, but there's no type in spec '$spec'"; 116 | } 117 | $help .= "\n "; 118 | return $help; 119 | } 120 | 121 | # Options output for plugin -h 122 | sub _options 123 | { 124 | my $self = shift; 125 | 126 | my @args = (); 127 | my @defer = (); 128 | for (@{$self->{_args}}) { 129 | if (exists $DEFER_ARGS{$_->{name}}) { 130 | push @defer, $_; 131 | } else { 132 | push @args, $_; 133 | } 134 | } 135 | 136 | my @options = (); 137 | for my $arg (@args, @defer) { 138 | my $help_array = ref $arg->{help} && ref $arg->{help} eq 'ARRAY' ? $arg->{help} : [ $arg->{help} ]; 139 | my $label_array = $arg->{label} && ref $arg->{label} && ref $arg->{label} eq 'ARRAY' ? $arg->{label} : [ $arg->{label} ]; 140 | my $help_string = ''; 141 | for (my $i = 0; $i <= $#$help_array; $i++) { 142 | my $help = $help_array->[$i]; 143 | # Add spec arguments to help if not already there 144 | if ($help =~ m/^\s*-/) { 145 | $help_string .= $help; 146 | } 147 | else { 148 | $help_string .= $self->_spec_to_help($arg->{spec}, $label_array->[$i]) . $help; 149 | $help_string .= "\n " if $i < $#$help_array; 150 | } 151 | } 152 | 153 | # Add help_string to @options 154 | if ($help_string =~ m/%s/) { 155 | my $default = defined $arg->{default} ? $arg->{default} : ''; 156 | # We only handle '%s' formats here 157 | my $replaced = $help_string; 158 | $replaced =~ s|%s|$default|gmx; 159 | push @options, $replaced; 160 | } else { 161 | push @options, $help_string; 162 | } 163 | } 164 | 165 | return ' ' . join("\n ", @options); 166 | } 167 | 168 | # Output for plugin -? (or missing/invalid args) 169 | sub _usage { 170 | my $self = shift; 171 | my $usage = $self->_attr('usage'); 172 | $usage =~ s|%s|$self->{_attr}->{plugin}|gmx; 173 | return($usage); 174 | } 175 | 176 | # Output for plugin -V 177 | sub _revision 178 | { 179 | my $self = shift; 180 | my $revision = sprintf "%s %s", $self->{_attr}->{plugin}, $self->{_attr}->{version}; 181 | $revision .= sprintf " [%s]", $self->{_attr}->{url} if $self->{_attr}->{url}; 182 | $revision .= "\n"; 183 | $revision; 184 | } 185 | 186 | # Output for plugin -h 187 | sub _help 188 | { 189 | my $self = shift; 190 | my $help = ''; 191 | $help .= $self->_revision . "\n"; 192 | $help .= $self->_attr('license', "\n"); 193 | $help .= $self->_attr('blurb', "\n"); 194 | $help .= $self->_usage ? $self->_usage . "\n" : ''; 195 | $help .= $self->_options ? $self->_options . "\n" : ''; 196 | $help .= $self->_attr('extra', "\n"); 197 | return $help; 198 | } 199 | 200 | # Return a Getopt::Long-compatible option array from the current set of specs 201 | sub _process_specs_getopt_long 202 | { 203 | my $self = shift; 204 | 205 | my @opts = (); 206 | for my $arg (@{$self->{_args}}) { 207 | push @opts, $arg->{spec}; 208 | # Setup names and defaults 209 | my $spec = $arg->{spec}; 210 | # Use first arg as name (like Getopt::Long does) 211 | $spec =~ s/[=:!].*$//; 212 | my $name = (split /\s*\|\s*/, $spec)[0]; 213 | $arg->{name} = $name; 214 | if (defined $self->{$name}) { 215 | $arg->{default} = $self->{$name}; 216 | } else { 217 | $self->{$name} = $arg->{default}; 218 | } 219 | } 220 | 221 | return @opts; 222 | } 223 | 224 | # Check for existence of required arguments 225 | sub _check_required_opts 226 | { 227 | my $self = shift; 228 | 229 | my @missing = (); 230 | for my $arg (@{$self->{_args}}) { 231 | if ($arg->{required} && ! defined $self->{$arg->{name}}) { 232 | push @missing, $arg->{name}; 233 | } 234 | } 235 | if (@missing) { 236 | $self->_die($self->_usage . "\n" . 237 | join("\n", map { sprintf "Missing argument: %s", $_ } @missing) . "\n"); 238 | } 239 | } 240 | 241 | # Process and handle any immediate options 242 | sub _process_opts 243 | { 244 | my $self = shift; 245 | 246 | # Print message and exit for usage, version, help 247 | $self->_die($self->_usage) if $self->{usage}; 248 | $self->_die($self->_revision) if $self->{version}; 249 | $self->_die($self->_help) if $self->{help}; 250 | } 251 | 252 | # ------------------------------------------------------------------------- 253 | # Default opts methods 254 | 255 | sub _load_config_section 256 | { 257 | my $self = shift; 258 | my ($section, $file, $flags) = @_; 259 | $section ||= $self->{_attr}->{plugin}; 260 | 261 | my $Config; 262 | eval { $Config = Monitoring::Plugin::Config->read($file); }; 263 | $self->_die($@) if ($@); 264 | defined $Config 265 | or $self->_die(Monitoring::Plugin::Config->errstr); 266 | 267 | # TODO: is this check sane? Does --extra-opts=foo require a [foo] section? 268 | ## Nevertheless, if we die as UNKNOWN here we should do the same on default 269 | ## file *added eval/_die above*. 270 | $file ||= $Config->mp_getfile(); 271 | $self->_die("Invalid section '$section' in config file '$file'") 272 | unless exists $Config->{$section}; 273 | 274 | return $Config->{$section}; 275 | } 276 | 277 | # Helper method to setup a hash of spec definitions for _cmdline 278 | sub _setup_spec_index 279 | { 280 | my $self = shift; 281 | return if defined $self->{_spec}; 282 | $self->{_spec} = { map { $_->{name} => $_->{spec} } @{$self->{_args}} }; 283 | } 284 | 285 | # Quote values that require it 286 | sub _cmdline_value 287 | { 288 | my $self = shift; 289 | local $_ = shift; 290 | if (m/\s/ && (m/^[^"']/ || m/[^"']$/)) { 291 | return qq("$_"); 292 | } 293 | elsif ($_ eq '') { 294 | return q(""); 295 | } 296 | else { 297 | return $_; 298 | } 299 | } 300 | 301 | # Helper method to format key/values in $hash in a quasi-commandline format 302 | sub _cmdline 303 | { 304 | my $self = shift; 305 | my ($hash) = @_; 306 | $hash ||= $self; 307 | 308 | $self->_setup_spec_index; 309 | 310 | my @args = (); 311 | for my $key (sort keys %$hash) { 312 | # Skip internal keys 313 | next if $key =~ m/^_/; 314 | 315 | # Skip defaults and internals 316 | next if exists $DEFAULT{$key} && $hash->{$key} eq $DEFAULT{$key}; 317 | next if grep { $key eq $_ } qw(help usage version extra-opts); 318 | next unless defined $hash->{$key}; 319 | 320 | # Render arg 321 | my $spec = $self->{_spec}->{$key} || ''; 322 | if ($spec =~ m/[=:].+$/) { 323 | # Arg takes value - may be a scalar or an arrayref 324 | for my $value (ref $hash->{$key} eq 'ARRAY' ? @{$hash->{$key}} : ( $hash->{$key} )) { 325 | $value = $self->_cmdline_value($value); 326 | if (length($key) > 1) { 327 | push @args, sprintf "--%s=%s", $key, $value; 328 | } 329 | else { 330 | push @args, "-$key", $value; 331 | } 332 | } 333 | } 334 | 335 | else { 336 | # Flag - render long or short based on option length 337 | push @args, (length($key) > 1 ? '--' : '-') . $key; 338 | } 339 | } 340 | 341 | return wantarray ? @args : join(' ', @args); 342 | } 343 | 344 | # Process and load extra-opts sections 345 | sub _process_extra_opts 346 | { 347 | my $self = shift; 348 | my ($args) = @_; 349 | 350 | my $extopts_list = $args->{'extra-opts'}; 351 | 352 | my @sargs = (); 353 | for my $extopts (@$extopts_list) { 354 | $extopts ||= $self->{_attr}->{plugin}; 355 | my $section = $extopts; 356 | my $file = ''; 357 | 358 | # Parse section@file 359 | if ($extopts =~ m/^([^@]*)@(.*?)\s*$/) { 360 | $section = $1; 361 | $file = $2; 362 | } 363 | 364 | # Load section args 365 | my $shash = $self->_load_config_section($section, $file); 366 | 367 | # Turn $shash into a series of commandline-like arguments 368 | push @sargs, $self->_cmdline($shash); 369 | } 370 | 371 | # Reset ARGV to extra-opts + original 372 | @ARGV = ( @sargs, @{$self->{_attr}->{argv}} ); 373 | 374 | printf "[extra-opts] %s %s\n", $self->{_attr}->{plugin}, join(' ', @ARGV) 375 | if $args->{verbose} && $args->{verbose} >= 3; 376 | } 377 | 378 | # ------------------------------------------------------------------------- 379 | # Public methods 380 | 381 | # Define plugin argument 382 | sub arg 383 | { 384 | my $self = shift; 385 | my %args; 386 | 387 | # Param name to required boolean 388 | my %params = ( 389 | spec => 1, 390 | help => 1, 391 | default => 0, 392 | required => 0, 393 | label => 0, 394 | ); 395 | 396 | # Named args 397 | if (exists $params{$_[0]} && @_ % 2 == 0) { 398 | %args = validate( @_, \%params ); 399 | } 400 | 401 | # Positional args 402 | else { 403 | my @order = qw(spec help default required label); 404 | @args{@order} = validate_pos(@_, @params{@order}); 405 | } 406 | 407 | # Add to private args arrayref 408 | push @{$self->{_args}}, \%args; 409 | } 410 | 411 | # Process the @ARGV array using the current _args list (possibly exiting) 412 | sub getopts 413 | { 414 | my $self = shift; 415 | 416 | # Collate spec arguments for Getopt::Long 417 | my @opt_array = $self->_process_specs_getopt_long; 418 | 419 | # Capture original @ARGV (for extra-opts games) 420 | $self->{_attr}->{argv} = [ @ARGV ]; 421 | 422 | # Call GetOptions using @opt_array 423 | my $args1 = {}; 424 | my $ok = GetOptions($args1, @opt_array); 425 | # Invalid options - give usage message and exit 426 | $self->_die($self->_usage) unless $ok; 427 | 428 | # Process extra-opts 429 | $self->_process_extra_opts($args1); 430 | 431 | # Call GetOptions again, this time including extra-opts 432 | $ok = GetOptions($self, @opt_array); 433 | # Invalid options - give usage message and exit 434 | $self->_die($self->_usage) unless $ok; 435 | 436 | # Process immediate options (possibly exiting) 437 | $self->_process_opts; 438 | 439 | # Required options (possibly exiting) 440 | $self->_check_required_opts; 441 | 442 | # Setup accessors for options 443 | $self->mk_ro_accessors(grep ! /^_/, keys %$self); 444 | 445 | # Setup default alarm handler for alarm($ng->timeout) in plugin 446 | $SIG{ALRM} = sub { 447 | my $plugin = uc $self->{_attr}->{plugin}; 448 | $plugin =~ s/^CHECK[-_]//i; 449 | $self->_die( 450 | sprintf("%s UNKNOWN - plugin timed out (timeout %ss)", 451 | $plugin, $self->timeout)); 452 | }; 453 | } 454 | 455 | # ------------------------------------------------------------------------- 456 | # Constructor 457 | 458 | sub _init 459 | { 460 | my $self = shift; 461 | 462 | # Check params 463 | my $plugin = basename($ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0); 464 | my %attr = validate( @_, { 465 | usage => 1, 466 | version => 0, 467 | url => 0, 468 | plugin => { default => $plugin }, 469 | blurb => 0, 470 | extra => 0, 471 | 'extra-opts' => 0, 472 | license => { default => $DEFAULT{license} }, 473 | timeout => { default => $DEFAULT{timeout} }, 474 | }); 475 | 476 | # Add attr to private _attr hash (except timeout) 477 | $self->{timeout} = delete $attr{timeout}; 478 | $self->{_attr} = { %attr }; 479 | # Chomp _attr values 480 | chomp foreach values %{$self->{_attr}}; 481 | 482 | # Setup initial args list 483 | $self->{_args} = [ @ARGS ]; 484 | 485 | $self 486 | } 487 | 488 | sub new 489 | { 490 | my $class = shift; 491 | my $self = bless {}, $class; 492 | $self->_init(@_); 493 | } 494 | 495 | # ------------------------------------------------------------------------- 496 | 497 | 1; 498 | 499 | __END__ 500 | 501 | =head1 NAME 502 | 503 | Monitoring::Plugin::Getopt - OO perl module providing standardised argument 504 | processing for Nagios plugins 505 | 506 | 507 | =head1 SYNOPSIS 508 | 509 | use Monitoring::Plugin::Getopt; 510 | 511 | # Instantiate object (usage is mandatory) 512 | $ng = Monitoring::Plugin::Getopt->new( 513 | usage => "Usage: %s -H -w -c ", 514 | version => '0.1', 515 | url => 'http://www.openfusion.com.au/labs/nagios/', 516 | blurb => 'This plugin tests various stuff.', 517 | ); 518 | 519 | # Add argument - named parameters (spec and help are mandatory) 520 | $ng->arg( 521 | spec => 'critical|c=i', 522 | help => q(Exit with CRITICAL status if fewer than INTEGER foobars are free), 523 | required => 1, 524 | default => 10, 525 | ); 526 | 527 | # Add argument - positional parameters - arg spec, help text, 528 | # default value, required? (first two mandatory) 529 | $ng->arg( 530 | 'warning|w=i', 531 | q(Exit with WARNING status if fewer than INTEGER foobars are free), 532 | 5, 533 | 1); 534 | 535 | # Parse arguments and process standard ones (e.g. usage, help, version) 536 | $ng->getopts; 537 | 538 | # Access arguments using named accessors or or via the generic get() 539 | print $ng->opts->warning; 540 | print $ng->opts->get('critical'); 541 | 542 | 543 | 544 | =head1 DESCRIPTION 545 | 546 | Monitoring::Plugin::Getopt is an OO perl module providing standardised and 547 | simplified argument processing for Nagios plugins. It implements 548 | a number of standard arguments itself (--help, --version, 549 | --usage, --timeout, --verbose, and their short form counterparts), 550 | produces standardised nagios plugin help output, and allows 551 | additional arguments to be easily defined. 552 | 553 | 554 | =head2 CONSTRUCTOR 555 | 556 | # Instantiate object (usage is mandatory) 557 | $ng = Monitoring::Plugin::Getopt->new( 558 | usage => 'Usage: %s --hello', 559 | version => '0.01', 560 | ); 561 | 562 | The Monitoring::Plugin::Getopt constructor accepts the following named 563 | arguments: 564 | 565 | =over 4 566 | 567 | =item usage (required) 568 | 569 | Short usage message used with --usage/-? and with missing required 570 | arguments, and included in the longer --help output. Can include 571 | a '%s' sprintf placeholder which will be replaced with the plugin 572 | name e.g. 573 | 574 | usage => qq(Usage: %s -H -p [-v]), 575 | 576 | might be displayed as: 577 | 578 | $ ./check_tcp_range --usage 579 | Usage: check_tcp_range -H -p [-v] 580 | 581 | =item version (required) 582 | 583 | Plugin version number, included in the --version/-V output, and in 584 | the longer --help output. e.g. 585 | 586 | $ ./check_tcp_range --version 587 | check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/] 588 | 589 | =item url 590 | 591 | URL for info about this plugin, included in the --version/-V output, 592 | and in the longer --help output (see preceding 'version' example). 593 | 594 | =item blurb 595 | 596 | Short plugin description, included in the longer --help output 597 | (see below for an example). 598 | 599 | =item license 600 | 601 | License text, included in the longer --help output (see below for an 602 | example). By default, this is set to the standard nagios plugins 603 | GPL license text: 604 | 605 | This nagios plugin is free software, and comes with ABSOLUTELY 606 | NO WARRANTY. It may be used, redistributed and/or modified under 607 | the terms of the GNU General Public Licence (see 608 | http://www.fsf.org/licensing/licenses/gpl.txt). 609 | 610 | Provide your own to replace this text in the help output. 611 | 612 | =item extra 613 | 614 | Extra text to be appended at the end of the longer --help output. 615 | 616 | =item plugin 617 | 618 | Plugin name. This defaults to the basename of your plugin, which is 619 | usually correct, but you can set it explicitly if not. 620 | 621 | =item timeout 622 | 623 | Timeout period in seconds, overriding the standard timeout default 624 | (15 seconds). 625 | 626 | =back 627 | 628 | The full --help output has the following form: 629 | 630 | version string 631 | 632 | license string 633 | 634 | blurb 635 | 636 | usage string 637 | 638 | options list 639 | 640 | extra text 641 | 642 | The 'blurb' and 'extra text' sections are omitted if not supplied. For 643 | example: 644 | 645 | $ ./check_tcp_range -h 646 | check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/] 647 | 648 | This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. 649 | It may be used, redistributed and/or modified under the terms of the GNU 650 | General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt). 651 | 652 | This plugin tests arbitrary ranges/sets of tcp ports for a host. 653 | 654 | Usage: check_tcp_range -H -p [-v] 655 | 656 | Options: 657 | -h, --help 658 | Print detailed help screen 659 | -V, --version 660 | Print version information 661 | -H, --hostname=ADDRESS 662 | Host name or IP address 663 | -p, --ports=STRING 664 | Port numbers to check. Format: comma-separated, colons for ranges, 665 | no spaces e.g. 8700:8705,8710:8715,8760 666 | -t, --timeout=INTEGER 667 | Seconds before plugin times out (default: 15) 668 | -v, --verbose 669 | Show details for command-line debugging (can repeat up to 3 times) 670 | 671 | 672 | =head2 ARGUMENTS 673 | 674 | You can define arguments for your plugin using the arg() method, which 675 | supports both named and positional arguments. In both cases 676 | the C and C arguments are required, while the C