#!/usr/bin/perl

# ntp-stats.pl -- John Ackermann   N8UR
# Version 0.95 -- 6 May 2007
# Licensed under General Public License Version 2

# Note that this program is optimized for low memory usage rather than
# for speed.  Consequently, it loops through the stats file several times
# rather than building what could be a very large array in memory.  Feel
# free to change that if you like.

use Getopt::Std;
use Socket;
use POSIX qw(tmpnam);
use IO::File;

###############################################################################
# local variables and paths
# Title and subtitle can be overridden on the command line
###############################################################################

my $title = "NTP Server Performance";
my $subtitle = "As seen by cesium.febo.com";
my $x_axis_label = "MJD";
my $y_axis_label = "Offset (Milliseconds)";
my $grace = "/usr/bin/gracebat";
my $work_dir = "/data/ntp/";
my $ntp_template = "/usr/local/bin/grace-cmd/ntp-stats-combined-template.cmd";

###############################################################################

# Refclocks
my %refclocks = (
	'127.127.1'  => 'local_clock',
	'127.127.4'  => 'wwvb_spectra',
	'127.127.20' => 'nmea',
	'127.127.22' => 'pps',
	'127.127.26' => 'gps_hp',
	'127.127.28' => 'pps_shm',
	'127.127.30' => 'gps_oncore'
	);

# Variables
my %hosts = 0;
my $base = "";
my @ips;
my @peer_list;
my @host_list;
my @seen;
my @octet;
my $item;
my $counter;
my $hostname;
my $ref_base;
my $logfile;
my $reading;
my $tmpfile;
my $infile;
my $outfile;
my $set;
my $set_color;
my $legend;
my $statsfile;
#----------
# display usage
my $opt_string = 'hmt:l:s:i:x:y:z:e:a:b:c:d:r:p:f:';

sub usage() {
print STDERR << "EOF";

usage: $0 [-h] [-t title] [-s subtitle] [-i domain] [-e domain]
	[-l local server] [-m] [-x] -p png filename -f input filename

-h	: this (help) message
-t	: graph title; overrides default
-s	: graph subtitle; overrides default
-i	: include domain; regex
-x	: second include domain; regex
-y	: third include domain; regex
-z	: fourth include domain; regex
-e	: exclude domain; regex
-a	: second exclude domain; regex
-b	: third exclude domain; regex
-c	: fourth exclude domain; regex
-l	: local server to append after refclock ID
-q	: exclude refclock stats
-m	: format X axis in whole numbers for MJD; default is hms
-f	: logfile using full pathname;
	  for output to console, use "-f -"

EOF
}

# main loop
#----------

getopts( "$opt_string", \my %opt ) or usage() and exit;

# print usage
usage() and exit if $opt{h};
usage() and exit if !$opt{f};
usage() and exit if !$opt{p};

# set variables to command line params
if ($opt{t}) {
	$title = $opt{t};
	}

if ($opt{s}) {
	$subtitle = $opt{s};
	}

my $include_domain = "";
if ($opt{i}) {
	$include_domain = $opt{i};
	}

my $include_domain_2 = "";
if ($opt{x}) {
	$include_domain_2 = $opt{x};
	}

my $include_domain_3 = "";
if ($opt{y}) {
	$include_domain_3 = $opt{y};
	}

my $include_domain_4 = "";
if ($opt{z}) {
	$include_domain_4 = $opt{z};
	}

my $include_string = $include_domain . $include_domain_2 . $include_domain_3 . $include_domain_4;

my $exclude_domain = "";
if ($opt{e}) {
	$exclude_domain = $opt{e};
	}

my $exclude_domain_2 = "";
if ($opt{a}) {
	$exclude_domain_2 = $opt{a};
	}

my $exclude_domain_3 = "";
if ($opt{b}) {
	$exclude_domain_3 = $opt{b};
	}

my $exclude_domain_4 = "";
if ($opt{b}) {
	$exclude_domain_4 = $opt{c};
	}

my $local_server = "";
if ($opt{l}) {
	$local_server = $opt{l};
	}

my $exclude_refclocks = 0;
if ($opt{x}) {
	$exclude_refclocks = 1;
	}

my $x_axis_format = "hms";
my $x_axis_precision = "1";
my $x_axis_label = "UTC";
if ($opt{m}) {
	$x_axis_format = "decimal";
	$x_axis_precision = "0";
	$x_axis_label = "MJD";
	}

my $pngfile;
$pngfile = $opt{p};

my $infile;
$infile = $opt{f};

# set up input file
open (INFILE, "$infile") || die "Can't open input file $infile!\n";

#----------


# Read the input file grabbing the server address
while ($reading=<INFILE>) {
	($day,$sec,$address,$status,$offset,$delay,$dispersion,$jitter) =
			split(/\s+/,$reading);
	push (@ips,$address);
	}
close INFILE;

# Create a list of the unique server addresses
%seen = ();
foreach $item (@ips) {
    push(@peer_list, $item) unless $seen{$item}++;

}

# Process the input file once for each peer adress found
foreach $item (@peer_list) {
	# Get the hostname
	$hostname = lc gethostbyaddr(inet_aton($item),AF_INET);
	
	# If no reverse match
	if ($hostname eq "") {
		# Check to see if it's a refclock
		@octet = split(/\./,$item);
		$refbase = $octet[0].".".$octet[1].".".$octet[2];
		if (exists $refclocks{$refbase}) {
			if ($exclude_refclocks) {
				$item = "";
				$hostname = "";
				}
			else {
				$hostname = $refclocks{$refbase};
				if ($local_server) {
					$hostname .= "\." . $local_server;
					}
				}

			}

		}
	if ($hostname eq "") {
		$hostname = $item;
		}

	# LOCAL HACK
	if ($item eq "24.123.66.139") { $hostname = "meow.febo.com"; }

	# If there's an include list, is this host on it?
	if ( ($include_string) && ($hostname !~ m/$include_string/i) ) {
		$item = "";
		$hostname = "";
		}

	if ( ($exclude_domain) && ($hostname =~ m/$exclude_domain/i) ) {
		$item = "";
		$hostname = "";
		}

	if ( ($exclude_domain_2) && ($hostname =~ m/$exclude_domain_2/i) ) {
		$item = "";
		$hostname = "";
		}

	if ( ($exclude_domain_3) && ($hostname =~ m/$exclude_domain_3/i) ) {
		$item = "";
		$hostname = "";
		}

	if ( ($exclude_domain_4) && ($hostname =~ m/$exclude_domain_4/i) ) {
		$item = "";
		$hostname = "";
		}

	# Loop through the input file, dumping
	# all records for this server to a file called hostname.dat
	if ($item) {
		push (@host_list,$hostname);
		$logfile = $work_dir . $hostname . ".dat";
		open (LOG, ">$logfile") || die "Can't open $logfile!\n";
		open (INFILE, "$infile") || die "Can't open $infile!\n";
		while ($reading=<INFILE>) {
			($day,$sec,$address,$status,$offset,$delay,
				$dispersion,$jitter) = split(/\s+/,$reading);
			if ($address eq $item) {
				# $day is MJD and $sec is seconds since UTC
				# midnight, so we need to subtract 1/2 day
				$mjd = $day + ($sec/86400) - 0.5;
				print LOG $mjd," ",$offset, $delay," ",
					" ",$dispersion," ",$jitter,"\n";
				}
			}
		close INFILE;
		close LOG;
		}
	}


# Sort the array of hosts
@host_list = sort (@host_list);

# Create a temporary file for the grace cmd file
do { $tmpfile = tmpnam() }
	until $outfile = IO::File->new($tmpfile, O_RDWR|O_CREAT|O_EXCL);
$infile = new IO::File $ntp_template, "r";

if (!$infile) {die "Couldn't open temporary file!\n";}

# Write the top part of the grace cmd file
while ($reading = <$infile>) {
	$reading =~ s/##TITLE##/$title/;
	$reading =~ s/##SUBTITLE##/$subtitle/;
	$reading =~ s/##XAXIS LABEL##/$x_axis_label/;
	$reading =~ s/##XAXIS FORMAT##/$x_axis_format/;
	$reading =~ s/##XAXIS PRECISION##/$x_axis_precision/;
	$reading =~ s/##YAXIS LABEL##/$y_axis_label/;
	print $outfile $reading;
	}
close $infile;
		
# Write the set data, one stanza for each server
$counter = 0;

foreach $item (@host_list) {
	$statsfile = $work_dir . $item . ".dat";
	$set = "s". $counter;
	$set_color = $counter+1;
	$legend = $item;

	# Write the set information, one stanza per set
	print $outfile "    $set hidden false\n";
	print $outfile "    $set type xy\n";
	print $outfile "    $set line type 1\n";
	print $outfile "    $set line linestyle 1\n";
	print $outfile "    $set line linewidth 1.0\n";
	print $outfile "    $set line color $set_color\n";
	print $outfile "    $set line pattern 1\n";
	print $outfile "    $set legend  \"$legend\"\n";
	print $outfile "read  block pipe \"cat $statsfile\"\n";
	print $outfile "    target $set\n";
	print $outfile "    block xy \"1:2\"\n";
	print $outfile "    y=y*1000\n";
	print $outfile "    autoscale\n";

	$counter++;
	}

# Generate the graph
my @args = ($grace,"-nosafe","-b",$tmpfile,"-hdevice","PNG",
	"-hardcopy","-printfile",$pngfile);
system(@args) == 0 or die "system @args failed: $?";

unlink($tmpfile) or die "Couldn't unlink $tmpfile!";
