#!@@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

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

=head2 DEFAULT CONFIGURATION

 [ntp_states]
  env.lowercase <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;
use Net::DNS;

# 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'};

my %stateval = (
                 "reject"    => 0,
                 "falsetick" => 1,
                 "excess"    => 2,
                 "backup"    => 3,
                 "outlyer"   => 4,
                 "candidate" => 5,
                 "sys.peer"  => 6,
                 "pps.peer"  => 7
               );
my %peers_condition;
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   outlyer    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";
                        }

                        $peers_condition{$peer_addr} = $stateval{$condition_str};
                }
        }
}

# 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);
}

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

&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.\n";

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

        exit 0;
}

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

exit 0;

# vim:syntax=perl
