4
\$\begingroup\$

This is an update of Using an API to obtain JSON data and get the date string and determine if data is stale

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.

Gitlab Repo

#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use Monitoring::Plugin;
use LWP::UserAgent;
use JSON::Parse 'parse_json';
use JSON::Parse 'assert_valid_json';
use Hash::Flatten qw(:all);
use DateTime;
use DateTime::Format::Strptime;
use File::Basename;
use vars qw($verbose $warning $critical $timeout $result $url $key $format $zone $units);
my $VERSION = '1.1.0';
my $PROGNAME = basename(0ドル);
my $dateNowUTC = DateTime->now;
my $license = qq{ 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
};
my $np = Monitoring::Plugin->new(
 shortname => "DataFreshness",
 usage => "Usage: %s [-U|url=<URL>] [-K|key=<key>] [-f|format=<date_format>] [-u|units=<hours/minutes>] [-z|zone=<timezone>] [-c|--critical=<threshold>] [-w|--warning=<threshold>] [-t|timeout=<timeout>] [-v|verbose]",
 version => $VERSION,
 blurb => "Retrieve JSON data from an http/s url and check an object's date attribute to determine if the data is stale.",
 url => "https://gitlab.com/Jedimaster0/check_http_freshness",
 license => $license,
 plugin => "check_http_freshness",
 timeout => 60,
);
$np->add_arg('url|U=s', "-U, --url=STRING \n URL used to retrieve JSON data. (REQUIRED)", undef, 1);
$np->add_arg('key|K=s', "-K, --key=STRING \n JSON key. Used to find time attribute. (REQUIRED)", undef, 1);
$np->add_arg('format|f=s', "-f, --format=STRING \n Strftime time format (default: %Y%m%dT%H%M%S). \n For format details see: man strftime", '%Y%m%dT%H%M%S', 0);
$np->add_arg('units|u=s', "-u, --units=STRING \n Time units. Can be 'hours', or 'minutes'. (default hours)", 'hours', 0);
$np->add_arg('zone|z=s', "-z, --zone=STRING \n Timezone for JSON date. Can be 'UTC', UTC offset '-0730', or 'America/Boston' (default is UTC)\n See: http://search.cpan.org/dist/DateTime-TimeZone/lib/DateTime/TimeZone/Catalog.pm" , 'UTC', 0);
$np->add_arg('warning|w=s', "-w, --warning=INTEGER \n Warning if data exceeds this time. (default 12 hours)", 12, 0);
$np->add_arg('critical|c=s', "-c, --critical=INTEGER \n Critical if data exceeds this time. (default 24 hours)", 24, 0);
$np->getopts;
my $verbose = $np->opts->verbose;
if ($np->opts->warning >= $np->opts->critical){
 $np->plugin_die( "[ERROR] Warning value must be less than critical value." );
}
if (($np->opts->units ne "hours") and ($np->opts->units ne "minutes")){
 $np->plugin_die( "[ERROR] Time unites must be either hours or minutes." );
}
# 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($np->opts->timeout);
my $response = $ua->get($np->opts->url);
if (!$response->is_success) {
 $np->plugin_die( "[ERROR] HTTP/S request unsuccessful: " . $response->status_line );
}
# Verify the content-type of the response is JSON
my $ok = eval {
 assert_valid_json ($response->content);
 return 1;
};
if ( !$ok ){
 $np->plugin_die( "[ERROR] Response JSON is invalid. Please verify source data.\n" );
} 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->{$np->opts->key}){
 if ($verbose){print "[SUCCESS] JSON KEY FOUND -> " . $np->opts->key . ": ", $flatHash>{$np->opts->key}, "\n";}
 my $result = DATETIME_DIFFERENCE(DATETIME_LOOKUP($np->opts->format, $flatHash->{$np->opts->key}));
 $np->plugin_exit(
 return_code => $np->check_threshold( check => $result, warning => $np->opts->warning, critical => $np->opts->critical),
 message => "delta time is $result " . $np->opts->units . " -w: " . $np->opts->warning . " -c: " . $np->opts->critical,
 );
 } else {
 $np->plugin_die( "[ERROR] Retreived JSON does not contain any data for the specified key: " . $np->opts->key . "\nUse the -v switch to verify the JSON output and use the proper key(s)." );
 }
}
sub DATETIME_LOOKUP {
 my ($dateFormat, $dateFromJSON) = @_;
 my $strp = DateTime::Format::Strptime->new(
 pattern => $dateFormat,
 time_zone => $np->opts->zone,
 on_error => sub { $np->plugin_die( "[ERROR] INVALID TIME FORMAT: $dateFormat OR TIME ZONE: " . $np->opts->zone ); },
 );
 my $dt = $strp->parse_datetime($dateFromJSON);
 if (defined $dt){
 if ($verbose){print "[SUCCESS] Time formatted using -> $dateFormat\n", "[SUCCESS] JSON date converted -> $dt " . $np->opts->zone . "\n";}
 return $dt;
 } else {
 $np->plugin_die( "[ERROR] DATE VARIABLE IS NOT DEFINED. Pattern or timezone incorrect." );
 }
}
# Subtract JSON date/time from now and return delta
sub DATETIME_DIFFERENCE {
 my ($dateInitial) = @_;
 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($np->opts->units) . " " . $np->opts->units . "\n";}
 return $deltaDate->in_units($np->opts->units);
}
toolic
14.5k5 gold badges29 silver badges203 bronze badges
asked Dec 3, 2016 at 0:19
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The code looks pretty good, but here are some suggestions.

There is some unused code which can be deleted, specifically:

use vars qw($verbose $warning $critical $timeout $result $url $key $format $zone $units);

Also, the vars documentation states:

use of this pragma is discouraged

These are also unused:

use File::Basename;
my $PROGNAME = basename(0ドル);

It is best to import only what is needed to avoid namespace pollution. Change the use lines as follows:

use Data::Dumper qw(Dumper); 
use Monitoring::Plugin qw(); 
use LWP::UserAgent qw(); 
use Hash::Flatten qw(flatten); 
use DateTime qw(); 
use DateTime::Format::Strptime qw();

The empty lists (qw()) are not strictly needed in this case, but are there for consistency. They also indicate that no functions will be imported, but the OOP interface will be used.


It is a little strange to see multiple use lines for the same module:

use JSON::Parse 'parse_json';
use JSON::Parse 'assert_valid_json';

This is more customary:

use JSON::Parse qw(parse_json assert_valid_json);

Double quotes interpolate variables, whereas single quotes do not. There is no need to interpolate here:

my $license = qq{ Copyright (C) 2016 Nathan Snow

Using q more clearly shows the intent:

my $license = q{ Copyright (C) 2016 Nathan Snow
answered Mar 31, 2024 at 12:04
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.