Repository
Munin (2.0)
Last change
2020-03-16
Graph Categories
Family
auto
Capabilities
Language
Perl
License
NTP

ntp_states

Name

ntp_states - Plugin to monitor NTP states. States are the Select Field as documented at http://www.eecis.udel.edu/~mills/ntp/html/decode.html#peer

Configuration

The following configuration parameters are used by this plugin:

[ntp_states]
 env.lowercase - Lowercase hostnames after lookup
 env.show_syspeer_stratum - Display the stratum of the system peer, field sys_peer_stratum

Set the variable env.lowercase to anything to lowercase hostnames.

Default Configuration

[ntp_states]
 env.lowercase <undefined>
 env.show_syspeer_stratum <undefined>

Authors

Original author unknown. Rewritten by Kenyon Ralph kenyon@kenyonralph.com.

License

Same as munin.

Magic Markers

#%# family=auto
#%# capabilities=autoconf
#!@@PERL@@ -w
# -*- mode: cperl; cperl-indent-level: 8; -*-

=head1 NAME

ntp_states - Plugin to monitor NTP states. States are the Select Field as
documented at http://www.eecis.udel.edu/~mills/ntp/html/decode.html#peer

=head1 CONFIGURATION

The following configuration parameters are used by this plugin:

 [ntp_states]
  env.lowercase - Lowercase hostnames after lookup
  env.show_syspeer_stratum - Display the stratum of the system peer, field sys_peer_stratum

Set the variable env.lowercase to anything to lowercase hostnames.

=head2 DEFAULT CONFIGURATION

 [ntp_states]
  env.lowercase <undefined>
  env.show_syspeer_stratum <undefined>

=head1 AUTHORS

Original author unknown. Rewritten by Kenyon Ralph <kenyon@kenyonralph.com>.

=head1 LICENSE

Same as munin.

=head1 MAGIC MARKERS

 #%# family=auto
 #%# capabilities=autoconf

=cut

use English qw( -no_match_vars );
use Munin::Plugin;
use strict;
use warnings;
use File::Spec;
eval { require Net::DNS; Net::DNS->import(); };
my $has_net_dns = $EVAL_ERROR ? 0 : 1;

# Include /usr/local/sbin in the path to use the ntpq installed by
# ports on FreeBSD instead of the base system, which is probably
# older.
$ENV{'PATH'} = File::Spec->catdir(File::Spec->rootdir(), 'usr', 'local', 'sbin') . ":" . $ENV{'PATH'};

if ($ARGV[0] and $ARGV[0] eq "autoconf") {
        if (!$has_net_dns) {
                print "no (missing perl module Net::DNS)\n";
        } else {
                `ntpq -c help >/dev/null 2>/dev/null`;
                if ($CHILD_ERROR eq "0") {
                        if (`ntpq -n -c peers | wc -l` > 0) {
                                print "yes\n";
                        } else {
                                print "no (ntpq -p returned no peers)\n";
                        }
                } else {
                        print "no (ntpq not found)\n";
                }
        }
        exit 0;
}

die "Aborting due to missing perl module 'Net::DNS'" unless ($has_net_dns);

my %stateval = (
                 "reject"    => 0,
                 "falsetick" => 1,
                 "excess"    => 2,
                 "backup"    => 3,
                 "outlyer"   => 4,
                 "outlier"   => 4,
                 "candidate" => 5,
                 "sys.peer"  => 6,
                 "pps.peer"  => 7,
                 "insane"    => 8
               );
my %peers_condition;
my $syspeer_stratum_value = 15;
my $resolver = Net::DNS::Resolver->new;
$resolver->tcp_timeout(5);
$resolver->udp_timeout(5);

# Returns a hash whose keys are peer IP addresses and values are
# condition numbers.
sub make_hash {

# ntpq -c associations output:
#ind assid status  conf reach auth condition  last_event cnt
#===========================================================
#  1 63933  931a   yes   yes  none   outlier    sys_peer  1
#  2 63934  943a   yes   yes  none candidate    sys_peer  3

        foreach my $line (`ntpq -c associations`) {
                if ($line =~ m/^\s*\d+/) {
                        my (undef, undef, $assid, undef, undef, undef, undef, $condition_str, undef, undef) = split(/\s+/, $line);
                        chomp(my $peerinfo = `ntpq -n -c "readvar $assid srcadr"`);
                        $peerinfo =~ s/\s//g;
                        my ($peer_addr) = ($peerinfo =~ m/srcadr=(.*)/);

                        # some states get the last letter cut off in
                        # ntp version 4.2.4p8 (and probably others) in
                        # the associations output
                        if ($condition_str eq "candidat") {
                                $condition_str = "candidate";
                        }
                        if ($condition_str eq "falsetic") {
                                $condition_str = "falsetick";
                        }

                        # save the sys.peer's stratum if configured to graph it
                        if ($condition_str eq "sys.peer" and exists $ENV{"show_syspeer_stratum"}) {
                                chomp(my $stratum = `ntpq -n -c "readvar $assid stratum"`);
                                $stratum =~ s/\s//g;
                                ($syspeer_stratum_value) = ($stratum =~ m/stratum=(.*)/);
                        }

                        if (exists $stateval{$condition_str}) {
                          $peers_condition{$peer_addr} = $stateval{$condition_str};
                        } else {
                          $peers_condition{$peer_addr} = 9;
                        }
                }
        }
}

# Takes an address and returns a pair: the field name, and the
# Internet hostname
sub make_names {
        my $addr = shift;
        my $host;
        my $packet = $resolver->query($addr);

        # Can use core perl routines (from the Socket module) to do
        # the address -> hostname lookup in perls newer than 5.10.1
        # with, but that's all I have to test with right now. So using
        # libnet-dns-perl.

        if ($packet) {
                my @answer = $packet->answer;
                foreach my $rr (@answer) {
                        if ("PTR" eq $rr->type) {
                                $host = $rr->ptrdname;
                        }
                }
        }

        $host = defined $host ? $host : $addr;
        my $hostname = $host;
        my $fieldname = "peer_" . $hostname;
        $fieldname = lc $fieldname if exists $ENV{"lowercase"};
        $fieldname = clean_fieldname($fieldname);

        return ($fieldname, $hostname);
}

&make_hash;

if ($ARGV[0] and $ARGV[0] eq "config") {
        print "graph_title NTP states\n";
        print "graph_args --base 1000 --vertical-label state --lower-limit 0\n";
        print "graph_category time\n";
        print "graph_info These are graphs of the states of this system's NTP peers. The states translate as follows: 0=reject, 1=falsetick, 2=excess, 3=backup, 4=outlyer, 5=candidate, 6=system peer, 7=PPS peer. See http://www.eecis.udel.edu/~mills/ntp/html/decode.html for more information on the meaning of these conditions. If show_syspeer_stratum is specified, the sys.peer stratum is also graphed.\n";

        foreach my $addr (keys %peers_condition) {
                my ($fieldname, $hostname) = &make_names($addr);
                print "$fieldname.label $hostname\n";
        }

        # print config for the sys.peer's stratum if configured to graph it
        if (exists $ENV{"show_syspeer_stratum"}) {
            print "sys_peer_stratum.label sys.peer stratum\n";
            print "sys_peer_stratum.draw LINE1\n";
            print "sys_peer_stratum.colour 000000\n";
        }

        exit 0;
}

foreach my $addr (keys %peers_condition) {
        my ($fieldname, $hostname) = &make_names($addr);
        print "$fieldname.value ", $peers_condition{$addr}, "\n";
}
# include the sys.peer's stratum if configured to graph it
if (exists $ENV{"show_syspeer_stratum"} ) {
    print "sys_peer_stratum.value ", $syspeer_stratum_value, "\n";
}

exit 0;

# vim:syntax=perl