This is a nagios check that will use an API URL, get JSON data, flatten the data into a usable perl hash, and ultimately obtain a date string. Once the date is obtained, it should recognize the strftime
format based on user input and determine the delta hours or minutes. Once the delta time is calculated, it should return critical, warning, or OK, based on the -c
or -w
user inputs. I just started Perl a week ago and need some code review to become better at it.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use LWP::UserAgent;
use Getopt::Std;
use JSON::Parse 'parse_json';
use JSON::Parse 'assert_valid_json';
use Hash::Flatten qw(:all);
use DateTime;
use DateTime::Format::Strptime;
my $plugin_name = "Nagios check_http_freshness";
my $VERSION = "1.0.0";
my $dateNowUTC = DateTime->now;
my $verbose = 0;
$Getopt::Std::STANDARD_HELP_VERSION = "true";
# nagios exit codes
use constant EXIT_OK => 0;
use constant EXIT_WARNING => 1;
use constant EXIT_CRITICAL => 2;
use constant EXIT_UNKNOWN => 3;
#parse cmd opts
my %opts;
getopts('U:K:F:u:t:w:c:z:v', \%opts);
$opts{t} = 60 unless (defined $opts{t});
$opts{w} = 12 unless (defined $opts{w});
$opts{c} = 24 unless (defined $opts{c});
$opts{F} = "%Y%m%dT%H%M%S" unless (defined $opts{F});
$opts{u} = "hours" unless (defined $opts{u});
$opts{z} = "UTC" unless (defined $opts{z});
if (not (defined $opts{U}) || not (defined $opts{K}) ) {
print "[ERROR] INVALID USAGE\n";
HELP_MESSAGE();
exit EXIT_UNKNOWN;
}
if (defined $opts{v}){$verbose = 1;}
if ($opts{w} >= $opts{c}){
print "[ERROR] Warning value must be less than critical value.\n"; HELP_MESSAGE(); exit EXIT_UNKNOWN;
}
if (not ($opts{u} eq "hours") && not ($opts{u} eq "minutes")){
print "[ERROR] Time unites must be either hours or minutes.\n"; HELP_MESSAGE(); exit EXIT_UNKNOWN;
}
# Configure the user agent and settings for the http/s request.
my $ua = LWP::UserAgent->new;
$ua->agent('Mozilla');
$ua->protocols_allowed( [ 'http', 'https'] );
$ua->parse_head(0);
$ua->timeout($opts{t});
my $response = $ua->get($opts{U});
# Verify the content-type of the response is JSON
eval {
assert_valid_json ($response->content);
};
if ( $@ ){
print "[ERROR] Response isn't valid JSON. Please verify source data. \n$@";
exit EXIT_UNKNOWN;
} else {
# Convert the JSON data into a perl hashrefs
my $jsonDecoded = parse_json($response->content);
my $flatHash = flatten($jsonDecoded);
if ($verbose){print "[SUCCESS] JSON FOUND -> ", Dumper($flatHash), "\n";}
if (defined $flatHash->{$opts{K}}){
if ($verbose){print "[SUCCESS] JSON KEY FOUND -> ", $opts{K}, ": ", $flatHash>{$opts{K}}, "\n";}
NAGIOS_STATUS(DATETIME_DIFFERENCE(DATETIME_LOOKUP($opts{F}, $flatHash->{$opts{K}})));
} else {
print "[ERROR] Retreived JSON does not contain any data for the specified key: $opts{K} \nUse the -v switch to verify the JSON output and use the proper key(s).\n";
exit EXIT_UNKNOWN;
}
}
sub DATETIME_LOOKUP {
my $dateFormat = $_[0];
my $dateFromJSON = $_[1];
my $strp = DateTime::Format::Strptime->new(
pattern => $dateFormat,
time_zone => $opts{z},
on_error => sub { print "[ERROR] INVALID TIME FORMAT: $dateFormat OR TIME ZONE: $opts{z} \n$_[1] \n" ; HELP_MESSAGE(); exit EXIT_UNKNOWN; },
);
my $dt = $strp->parse_datetime($dateFromJSON);
if (defined $dt){
if ($verbose){print "[SUCCESS] Time formatted using -> $dateFormat\n", "[SUCCESS] JSON date converted -> $dt $opts{z}\n";}
return $dt;
} else {
print "[ERROR] DATE VARIABLE IS NOT DEFINED. Pattern or timezone incorrect."; exit EXIT_UNKNOWN
}
}
# Subtract JSON date/time from now and return delta
sub DATETIME_DIFFERENCE {
my $dateInitial = $_[0];
my $deltaDate;
# Convert to UTC for standardization of computations and it's just easier to read when everything matches.
$dateInitial->set_time_zone('UTC');
$deltaDate = $dateNowUTC->delta_ms($dateInitial);
if ($verbose){print "[SUCCESS] (NOW) $dateNowUTC UTC - (JSON DATE) $dateInitial ", $dateInitial->time_zone->short_name_for_datetime($dateInitial), " = ", $deltaDate->in_units($opts{u}), " $opts{u} \n";}
return $deltaDate->in_units($opts{u});
}
# Determine nagios exit code
sub NAGIOS_STATUS {
my $deltaTime = $_[0];
if ($deltaTime >= $opts{c}){print "[CRITICAL] Delta $opts{u} ($deltaTime) is >= ($opts{c}) $opts{u}. Data is stale.\n"; exit EXIT_CRITICAL;}
elsif ($deltaTime >= $opts{w}){print "[WARNING] Delta $opts{u} ($deltaTime) is >= ($opts{w}) $opts{u}. Data becoming stale.\n"; exit EXIT_WARNING;}
else {print "[OK] Delta $opts{u} ($deltaTime) are within limits -c $opts{c} and -w $opts{w} \n"; exit EXIT_OK;}
}
sub HELP_MESSAGE {
print <<EOHELP
Retrieve JSON data from an http/s url and check an object's date attribute to determine if the data is stale.
--help shows this message
--version shows version information
USAGE: 0ドル -U http://www.convert-unix-time.com/api?timestamp=now -K url -F %s -z UTC -c 24 -w 12 -v
-U URL to retrieve. (required)
-K JSON key to look for date attribute. (required)
-F Strftime time format (default: %Y%m%dT%H%M%S). For format details see: man strftime
-z Timezone that for the JSON date. Can be "UTC" or UTC offset "-0730" (default is UTC)
Can also be "Ameraca/Boston" See: http://search.cpan.org/dist/DateTime-TimeZone/lib/DateTime/TimeZone/Catalog.pm
-w Warning if data exceeds this time. (default 12 hours)
-c Critical if data exceeds this time. (default 24 hours)
-u Time unites. Can be "hours", or "minutes". (default hours)
-t Timeout in seconds to wait for the URL to load. (default 60)
-v Verbose output.
EOHELP
;}
sub VERSION_MESSAGE {
print <<EOVN
$plugin_name v. $VERSION
Copyright (C) 2016 Nathan Snow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
EOVN
;}
-
\$\begingroup\$ Please do not invalidate answers by editing your question with the updated code. Ask a new question if you want your new code reviewed. \$\endgroup\$user34073– user340732016年12月02日 23:21:27 +00:00Commented Dec 2, 2016 at 23:21
-
\$\begingroup\$ Is this acceptable? codereview.stackexchange.com/questions/148796/… \$\endgroup\$japtain.cack– japtain.cack2016年12月03日 00:27:24 +00:00Commented Dec 3, 2016 at 0:27
-
\$\begingroup\$ Yep, looks nice. \$\endgroup\$user34073– user340732016年12月03日 00:33:19 +00:00Commented Dec 3, 2016 at 0:33
2 Answers 2
You may also want to look at the Nagios::Plugin module or it's successor, Monitoring::Plugin.
In these plugins, the OK, WARNING, CRITICAL and UNKNOWN exit statuses are exported by default, so you do not need to declare them.
To initiate a plugin, you need something like this:
my $plugin = Nagios::Plugin->new(
shortname => $PLUGIN_NAME, # Short name of your plugin
usage => $USAGE, # Your usage message
version => $VERSION # Version number
);
Then you can add your command line options thus:
$plugin->add_arg("w=i", "-w <hours>\n Warning if data exceeds this time", 12, 1);
$plugin->add_arg("c=i", "-c <hours>\n Critical if data exceeds this time", 24, 1);
$plugin->getopts();
and so on. The arguments for the add_arg()
method are spec
, usage
, default
and required
.
You can then compare your result with the warning and critical thresholds. In essence, your NAGIOS_STATUS
subroutine can be broken down to one simple line:
my $status = $plugin->check_threshold(check => $delta_time, warning => $plugin->opts->w, critical => $plugin->opts->c);
Or
my $threshold = $plugin->set_thresholds(warning => $plugin->opts->w, critical => $plugin->opts->c); # Set threshold so you can use it later
my $status = $plugin->check_threshold(check => $delta_time);
Then, to exit with the correct status, you add this line:
$plugin->plugin_exit($status, sprintf("Delta time is %s", $delta_time));
Hope that helps!
-
\$\begingroup\$ Updated: codereview.stackexchange.com/questions/148796/… \$\endgroup\$japtain.cack– japtain.cack2016年12月03日 00:20:22 +00:00Commented Dec 3, 2016 at 0:20
Just a few quick remarks,
# $opts{t} = 60 unless (defined $opts{t});
$opts{t} //= 60;
(check perldoc perlop for //
operator)
# not ($opts{u} eq "hours")
($opts{u} ne "hours")
(ne
as not equal)
Prefer and
over &&
as later is higher precedence operator and is usually used when you want to take advantage of this particular feature (the same goes for ||
vs. or
).
my $ok = eval {
assert_valid_json ($response->content);
1;
};
if (!$ok) { #error..
(reason: https://stackoverflow.com/a/2166162/223226)
# my $dateFormat = $_[0];
# my $dateFromJSON = $_[1];
my ($dateFormat, $dateFromJSON) = @_;
(assign all vars at once from @_
array)
-
\$\begingroup\$ Thanks for the feedback and explanations. I'll probably make those changes in a little bit. \$\endgroup\$japtain.cack– japtain.cack2016年11月26日 23:02:01 +00:00Commented Nov 26, 2016 at 23:02
-
\$\begingroup\$ Perl Best Practices: "It's safer and more comprehensible to use only high-precedence booleans in conditional expressions ... and then use parentheses when you need to vary precedence". This contradicts your advice to prefer
and
over&&
. \$\endgroup\$user125382– user1253822016年12月10日 05:13:16 +00:00Commented Dec 10, 2016 at 5:13