#!/local/bin/perl -w--*-perl-*-
;#
;# $Id$
;#
;# process loop filter statistics file and either
;#     - show statistics periodically using gnuplot
;#     - or print a single plot
;#
;#  Copyright (c) 1992 
;#  Rainer Pruy Friedrich-Alexander Universitaet Erlangen-Nuernberg
;#
;# This code may be modified and used freely
;# provided the credits remain intact.
;#
;#
;#############################################################
$0 =~ s!^.*/([^/]+)$!\1!;
$F = ' ' x length($0);
$|=1;
;# save typing $[+ in array indices
$[=0;

undef($config);
undef($workdir);
undef($PrintIt);
undef($samples);
undef($StartTime);
undef($EndTime);

$usage = <<"E-O-P";
usage:
  to watch statistics permanently:
     $0 [-v[<level>]] [-c <config-file>] [-d <working-dir>]

  to get a single print out specify also
     $F -P[<printer>] [-s<samples>] [-S <start-time>] [-E <end-time>]
     $F               [-O <MaxOffs>] [-o <MinOffs>]
E-O-P

;# add directory to look for lr.pl and timelocal.pl (in front of current list)
unshift(@INC,"/src/NTP/v3/xntp/monitoring");

require "lr.pl";	# linear regresion routines

$MJD_1970 = 40587;		# from ntp.h (V3)

require "ctime.pl";
require "timelocal.pl";
;# early distributions of ctime.pl had a bug
$ENV{'TZ'} = 'MET' unless defined $ENV{'TZ'} || $[ > 4.010;
if (defined(@ctime'MoY))
{
  *Month=*ctime'MoY;
  *Day=*ctime'DoW;
}
else
{
  @Month = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
  @Day   = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
}
;# max number of days per month
@MaxNumDaysPerMonth = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

while($_ = shift(@ARGV))
{
    /^-c$/ && do {
		    @ARGV || die($usage);
		    $config = shift(@ARGV);
		    next;
		};
    /^-d$/ &&
	do {
	    @ARGV || die($usage);
	    $workdir = shift(@ARGV);
	    next;
	};
    /^-h$/ &&
	do {
	    @ARGV || die($usage);
	    $STATHOST = shift;
	    next;
	};
    
    /^-v(\d*)$/ && (($verbose=($1 eq "") ? 1 : $1),1) && next;

    /^-P(\w*)$/ && (($PrintIt = $1),1) && next;

    /^-s(\d*)$/ && (($samples = ($1 eq "") ? 1000 : $1),1) && next;

    /^-S$/ &&
	do {
	    @ARGV || die($usage);
	    $StartTime = &date_time_spec2seconds(shift);
	    next;
	};

    /^-E$/ &&
	do {
	    @ARGV || die($usage);
	    $EndTime = &date_time_spec2seconds(shift);
	    next;
	};
    
    /^-O$/ &&
	do {
	    @ARGV || die($usage);
	    $MaxOffs = shift;
	    next;
	};
    
    /^-o$/ &&
	do {
	    @ARGV || die($usage);
	    $MinOffs = shift;
	    next;
	};
    
    die("$0: unexpected argument \"$_\"\n$usage");
}

if (defined($workdir))
{
  chdir($workdir) ||
      die("$0: failed to change working dir to \"$workdir\": $!\n");
}

$PrintIt = "ps" if defined($PrintIt) && $PrintIt eq "";

if (!defined($PrintIt))
{
    defined($samples) &&
	print "WARNING: your samples value may be shadowed by config file settings\n";
    defined($StartTime) &&
	print "WARNING: your StartTime value may be shadowed by config file settings\n";
    defined($EndTime) &&
	print "WARNING: your EndTime value may be shadowed by config file settings\n";
    defined($MaxOffs) &&
	print "WARNING: your MaxOffs value may be shadowed by config file settings\n";
    defined($MinOffs) &&
	print "WARNING: your MinOffs value may be shadowed by config file settings\n";
	
    ;# check operating environment
    ;# 
    ;# gnuplot usually has X support
    ;# I vaguely remember there was one with sunview support
    ;#
    ;# If Your plotcmd can display graphics using some other method
    ;# (Tek window,..) fix the following test
    ;# (or may be, just disable it)
    ;#
    !(defined($ENV{'DISPLAY'}) || defined($ENV{'WINDOW_PARENT'})) &&
	do {
	    die("Need window system to monitor statistics\n");
	};
}

;# configuration file
$config = "loopwatch.config" unless defined($config);
($STATHOST = $config) =~ s!.*loopwatch\.config.([^/\.]*)$!\1!
    unless defined($STATHOST);
($STATTAG = $STATHOST) =~ s/^([^\.]+)\..*$/\1/;

;# plot command 
@plotcmd=("gnuplot", '-mono',
	  '-title', "Ntp loop filter statistics $STATHOST     ($$)",
	  '-name', "NtpLoopWatch_$STATTAG");
$tmpfile = "/tmp/ntpstat.$$";

;# config settable parameters
$delay = 60;
$srcprefix = "loopstats:$STATHOST:";
$showoffset = 1;
$showfreq = 1;
$showcmpl  = 1;
$showoreg = 1;
$showfreg = 1;
undef($timebase);
undef($freqbase);
undef($cmplscale);
$deltaT    = 100;       # sample gap indicator measured in seconds
$verbose = (defined($PrintIt) ? 0 : 1) unless defined($verbose);
$DumbScale = 0;

;# other variables
$srcfile = "";	# set on each run of loop
$doplot = "";	# assembled command for @plotcmd to display plot
undef($laststat);

;# plot value ranges
undef($mintime);
undef($maxtime);
undef($minoffs);
undef($maxoffs);
undef($minfreq);
undef($maxfreq);
undef($mincmpl);
undef($maxcmpl);

;# stop operation if plot command dies
sub sigchld
{
  local($pid) = wait;
  unlink($tmpfile);
  die(sprintf("%s: child %d died: exit status: %d signal %d  - exiting\n",
	      $0,$pid,$?>>8,$? & 0xff));
}
&sigchld if 0;
$SIG{'CHLD'} = "sigchld";
$SIG{'CLD'} = "sigchld";

sub abort
{
  unlink($tmpfile);
  die("$0: received signal SIG$_[$[] - exiting\n");
}
&abort if 0;	# make -w happy - &abort IS used
$SIG{'INT'} = $SIG{'HUP'} = $SIG{'QUIT'} = $SIG{'TERM'} = $SIG{'PIPE'} = "abort";

;#
sub abs
{
  ($_[$[] < 0) ? -($_[$[]) : $_[$[];
}

;#####################
;# start of real work 

print "starting plot command (" . join(" ",@plotcmd) . ")\n" if $verbose;

$pid = open(PLOT,"|-");
select((select(PLOT),$|=1)[$[]);	# make PLOT line bufferd

defined($pid) ||
    die("$0: failed to start plot command: $!\n");

unless ($pid)
{
   ;# child == plot command
   close(STDOUT);
   open(STDOUT,">&STDERR") ||
       die("$0: failed to redirect STDOUT of plot command: $!\n");
   
   print STDOUT "plot command running as $$\n";

   exec @plotcmd;
   die("$0: failed to exec (@plotcmd): $!\n");
   exit(1); # in case ...
}

sub read_config
{
  local($at) = (stat($config))[$[+9];
  local($_,$c,$v);

  (undef($laststat),(print("stat $config failed: $!\n")),return) if ! defined($at);
  return if (defined($laststat) && ($laststat == $at));
  $laststat = $at;

  print "rereading configuration from \"$config\"\n" if $verbose;

  open(CF,"<$config") ||
      do {
	  warn("$0: failed to read \"$config\" - using old settings ($!)\n");
	  return;
      };
  while(<CF>)
  {
    chop;
    s/^([^\#]*[^\#\s]?)\s*\#.*$//;
    next if /^\s*$/;

    s/^\s*([^=\s]*)\s*=\s*(.*\S)\s*$/\1=\2/;

    ($c,$v) = split(/=/,$_,2);
    print "processing \"$c=$v\"\n" if $verbose > 3;
    ($c eq "delay") && ($delay = $v,1) && next;
    ($c eq 'samples') && (!defined($PrintIt) || !defined($samples)) &&
	($samples = $v,1) && next;
    ($c eq 'srcprefix') && ($srcprefix=$v,1) && next;
    ($c eq 'showoffset') &&
	($showoffset = ($v eq 'yes' || $v eq 'y' || $v != 0),1) && next;
    ($c eq 'showfreq') &&
	($showfreq = ($v eq 'yes' || $v eq 'y' || $v != 0),1) && next;
    ($c eq 'showcmpl') &&
	($showcmpl = ($v eq 'yes' || $v eq 'y' || $v != 0),1) && next;
    ($c eq 'showoreg') &&
	($showoreg = ($v eq 'yes' || $v eq 'y' || $v != 0),1) && next;
    ($c eq 'showfreg') &&
	($showfreg = ($v eq 'yes' || $v eq 'y' || $v != 0),1) && next;

    ($c eq 'exit') && (unlink($tmpfile),die("$0: exit by config request\n"));

    ($c eq 'freqbase' ||
     $c eq 'cmplscale') &&
	do {
	    if (! defined($v) || $v eq "" || $v eq 'dynamic')
	    {
	      eval "undef(\$$c);";
	    }
	    else
	    {
	      eval "\$$c = \$v;";
	    }
	    next;
	};
    ($c eq 'timebase') &&
	do {
	    if (! defined($v) || $v eq "" || $v eq "dynamic")
	    {
	      undef($timebase);
	    }
	    else
	    {
	      $timebase=&date_time_spec2seconds($v);
	    }
	};
    ($c eq 'EndTime') &&
	do {
	    next if defined($EndTime) && defined($PrintIt);
	    if (! defined($v) || $v eq "" || $v eq "none")
	    {
	      undef($EndTime);
	    }
	    else
	    {
	      $EndTime=&date_time_spec2seconds($v);
	    }
	};
    ($c eq 'StartTime') &&
	do {
	    next if defined($StartTime) && defined($PrintIt);
	    if (! defined($v) || $v eq "" || $v eq "none")
	    {
	      undef($StartTime);
	    }
	    else
	    {
	      $StartTime=&date_time_spec2seconds($v);
	    }
	};

    ($c eq 'MaxOffset') &&
	do {
	    next if defined($MaxOffset) && defined($PrintIt);
	    if (! defined($v) || $v eq "" || $v eq "none")
	    {
	      undef($MaxOffset);
	    }
	    else
	    {
	      $MaxOffset=$v;
	    }
	};

    ($c eq 'MinOffset') &&
	do {
	    next if defined($MinOffset) && defined($PrintIt);
	    if (! defined($v) || $v eq "" || $v eq "none")
	    {
	      undef($MinOffset);
	    }
	    else
	    {
	      $MinOffset=$v;
	    }
	};

    ($c eq 'deltaT') &&
	do {
	    if (!defined($v) || $v eq "")
	    {
	      undef($deltaT);
	    }
	    else
	    {
	      $deltaT = $v;
	    }
	    next;
	};
    ($c eq 'verbose') && ! defined($PrintIt) &&
	do {
	     if (!defined($v) || $v == 0)
	     {
	       $verbose = 0;
	     }
	     else
	     {
	       $verbose = $v;
	     }
	     next;
	};
    ($c eq 'DumbScale') &&
	do {
	    if (!defined($v) || $v == 0)
	    {
	      $DumbScale = 0;
	    } 
	    else
	    {
	      $DumbScale = $v;
	    }
	};
    ;# otherwise: silently ignore unrecognized config line
  }
  close(CF);
  ;# show all if nothing selected
  $showoffset = $showfreq = $showcmpl = 1
      unless $showoffset || $showfreq || $showcmpl;
  if ($verbose > 3)
  {
    print  "new configuration:\n";
    print  "\tdelay      = $delay\n";
    print  "\tsamples    = $samples\n";
    print  "\tsrcprefix  = $srcprefix\n";
    print  "\tshowoffset = $showoffset\n";
    print  "\tshowfreq   = $showfreq\n";
    print  "\tshowcmpl   = $showcmpl\n";
    print  "\tshoworeg   = $showoreg\n";
    print  "\tshowfreg   = $showfreg\n";
    printf "\ttimebase   = %s",  defined($timebase)  ? &ctime($timebase) : "dynamic\n";
    printf "\tfreqbase   = %s\n",defined($freqbase)  ? "$freqbase"       : "dynamic";
    printf "\tcmplscale  = %s\n",defined($cmplscale) ? "$cmplscale"      : "dynamic";
    print  "\tDumbScale  = $DumbScale\n";
    printf "\tStartTime  = %s", defined($StartTime)  ? &ctime($StartTime) : "none\n";
    printf "\tEndTime    = %s",   defined($EndTime)  ? &ctime($EndTime) : "none\n";
    printf "\tMaxOffset  = %s",   defined($MaxOffset) ? $MaxOffset : "none\n";
    printf "\tMinOffset  = %s",   defined($MinOffset) ? $MinOffset : "none\n";
    print  "\tverbose    = $verbose\n";
  }
print "configuration file read\n" if $verbose > 2;
}

sub make_doplot
{
  local($c, $fmt) = ("","%s \"%s\" using 1:%d title '%s <%lf %lf> %6s' with lines");
  local($regfmt) = ("%s ((%lf * x) + %lf) title 'lin. approx. %s (%f t[h]) %s %f <%f> %6s' with lines");

  $doplot = "    set title 'NTP loopfilter statistics for $STATHOST  " .
	    "(last $LastCnt samples from $srcfile)'\n";

  local($xts,$xte,$i,$t);

  if ($DumbScale)
  {
    $t = &ctime($LastTimeBase);
    chop($t);
    $doplot .= "   set xlabel 'timebase is $t'\n";
    $doplot .= "   set xtics\n";
  }
  else
  {
    local($s,$c) = ("");
    $t = $maxtime - $mintime;

    $t /= 12;			# minimum number of tic marks on x-axis

    $t = int($t + 0.5);		# integral seconds
    $t = 1 unless $t;		# preveal later error
    foreach $i (30,60,5*60,15*60,30*60,60*60,2*60*60,6*60*60,12*60*60,24*60*60,48*60*60)
    {
      last if $t < $i;
      $t = $t - ($t % $i);
    }

    print "time label resolution: $t seconds\n" if $verbose > 1;

    for ($c="",$i = $mintime - ($mintime % $t); $i <= $maxtime + $t; $i += $t,$c=",")
    {
      $s .= $c;
      if (int($i / $t) % 2)
      {
	$s .= sprintf("'' %lf",($i - $LastTimeBase)/3600);
      }
      elsif ($t <= 60)
      {
	$s .= sprintf("'%d:%02d:%02d' %lf",
		      (localtime($i))[2,1,0],
		      ($i - $LastTimeBase)/3600);
      }
      elsif ($t <= 2*60*60)
      {
	$s .= sprintf("'%d:%02d' %lf",
		      (localtime($i))[2,1],
		      ($i - $LastTimeBase)/3600);
      }
      elsif ($t <= 12*60*60)
      {
	$s .= sprintf("'%s %d:00' %lf",
		      $Day[(localtime($i))[6]],
		      (localtime($i))[2],
		      ($i - $LastTimeBase)/3600);
      }
      else
      {
	$s .= sprintf("'%d.%d-%d:00' %lf",
		      (localtime($i))[3,4,2],
		      ($i - $LastTimeBase)/3600);
      }
    }
    
    $doplot .= "set xtics ($s)\n";

    chop($xts = &ctime($mintime));
    chop($xte = &ctime($maxtime));
    $doplot .= "set xlabel 'Start:  $xts    --   Time Scale   --    End:  $xte'\n";
    if (!defined($MaxOffset) && ! defined($MinOffset))
    {
      $doplot .= "set autoscale y\n";
    }
    else
    {
      $doplot .= "set yrange [" ;
      $doplot .= defined($MinOffset)
		  ? sprintf("%lf",
			    &max($MinOffset,
				 &min($minoffs,
				      $minfreq - $LastFreqBase,
				      $mincmpl/$LastCmplScale)))
		  : &min($minoffs,$minfreq - $LastFreqBase,$mincmpl/$LastCmplScale);
      $doplot .= ':';
      $doplot .= defined($MaxOffset)
		  ? sprintf("%lf",
			    &min($MaxOffset,
				 &max($maxoffs,
				      $maxfreq - $LastFreqBase,
				      $maxcmpl/$LastCmplScale)))
		  : &max($maxoffs,$maxfreq - $LastFreqBase,$maxcmpl/$LastCmplScale);
      $doplot .= "]\n";
    }
  }

  $doplot .= "   plot";

  if ($showoffset)
  {
    $doplot .= sprintf($fmt,$c,$tmpfile,2,
		       "offset",
		       $minoffs,$maxoffs,
		       "[ms]");
    $c = ",";
  }
  if ($showcmpl)
  {
    $doplot .= sprintf($fmt,$c,$tmpfile,4,
		       "compliance" .
		        (&abs($LastCmplScale) > 1
			 ? " / $LastCmplScale"
			 : (&abs($LastCmplScale) == 1 ? "" : " * ".(1/$LastCmplScale))),
		       $mincmpl/$LastCmplScale,$maxcmpl/$LastCmplScale,
		       "");
    $c = ",";
  }
  if ($showfreq)
  {
    $doplot .= sprintf($fmt,$c,$tmpfile,3,
		       "frequency" .
		       ($LastFreqBase > 0
			? " - $LastFreqBaseString" 
			: ($LastFreqBase == 0 ? "" : " + $LastFreqBaseString")),
		       $minfreq-$LastFreqBase,$maxfreq-$LastFreqBase,
		       "[ppm]");
    $c = ",";
  }
  if ($showoreg)
  {
    $doplot .= sprintf($regfmt, $c, &lr_B('offs'),&lr_A('offs'),
		       "offset   ", &lr_B('offs'),(&lr_A('offs') < 0 ? '-' : '+'),
		       &abs(&lr_A('offs')), &lr_r('offs'),
		       "[ms]");
    $c = ",";
  }
  if ($showfreg)
  {
    $doplot .= sprintf($regfmt, $c, &lr_B('freq'),&lr_A('freq'),
		       "frequency", &lr_B('freq'),(&lr_A('freq') < 0 ? '-' : '+'),
		       &abs(&lr_A('freq')), &lr_r('freq'),
		       "[ppm]");
    $c = ",";
  }
 $doplot .= "\n";
}

sub genfile
{
  local($cnt,$in,$out,$loff) = @_;

  local(@F,@t,$t,$lastT) = ();
  local(@break,@time,@offs,@freq,@cmpl,@loffset) = ();
  local($lm);

  open(IN,"<$in") ||
      do {
	  warn("$0: cannot read \"$in\": $!\n");
      };
  seek(IN, $loff, 1) if (defined($loff) && $loff ne 0);
  open(OUT,">$out") || 
      do {
	  warn("$0: cannot create \"$out\": $!\n");
      };
  print "processing last $cnt samples from \"$in\" to \"$out\"\n" if $verbose > 1;

  $lm = 1;

  while(<IN>)
  {
    if ($verbose > 3)
    {
      print "\t$. lines read\n" if ($. % $lm) == 0;

      ($. ==     2) && ($lm =    10);
      ($. ==   100) && ($lm =   100);
      ($. ==   500) && ($lm =   500);
      ($. ==  1000) && ($lm =  1000);
      ($. ==  5000) && ($lm =  5000);
      ($. == 10000) && ($lm = 10000);
    }

    @F = split;
    
    next if @F < 5;		# no valid input line is this short
    next if $F[0] eq "";
    ($F[0] !~ /^\d+$/) && # Ehh ??
	die("$0: unexpected input line: $_\n");

    ;# modified Julian to UNIX epoch
    $t = ($F[0] - $MJD_1970) * 24 * 60 * 60;
    $t += $F[1];		# add seconds + fraction

    ;# multiply offset by 1000 to get ms - try to avoid float op
    if ($F[2] =~ s/(\d*)\.(\d{3})(\d*)/\1\2.\3/) 
    {
	$F[2] =~ s/0+([\d\.])/($1 eq '.') ? '0.' : $1/e; # strip leading zeros
    }
    else
    {
	$F[2] *= 1000;
    }

    ;# skip samples out of specified time range
    next if (defined($StartTime) && $StartTime > $t);
    next if (defined($EndTime) && $EndTime < $t);

    next if defined($lastT) && $t < $lastT;	# backward in time ??

    push(@offs,$F[2]);
    push(@freq,$F[3] * (2**20/10**6));
    push(@cmpl,$F[4]);

    push(@break, (defined($lastT) && ($t - $lastT > $deltaT)));  # use a string ??
    $lastT = $t;
    push(@time,$t);
    push(@loffset, tell(IN));
    
    if (@time > $cnt && ! (defined($StartTime) && defined($EndTime)))
    {
      shift(@break);
      shift(@time);
      shift(@offs);
      shift(@freq);
      shift(@cmpl);
      shift(@loffset);
    }
  }
  print "input file scanned ($. lines/",scalar(@time)," samples)\n"
      if $verbose > 2;
  close(IN);

  &lr_init('offs');
  &lr_init('freq');

  if (@time)
  {
    local($_,@F);

    local($timebase) unless defined($timebase);
    local($freqbase) unless defined($freqbase);
    local($cmplscale) unless defined($cmplscale);

    undef($mintime,$maxtime,$minoffs,$maxoffs,$minfreq,$maxfreq,$mincmpl,$maxcmpl);

    print "computing ranges\n" if $verbose > 2;

    ;# @time is in ascending order (;-)
    $mintime = @time[$[];
    $maxtime = @time[$#time];

    foreach (@offs)
    {
      $minoffs = $_ unless defined($minoffs) && $minoffs <= $_;
      $maxoffs = $_ unless defined($maxoffs) && $maxoffs >= $_;
      $minoffs = $MinOffset if defined($MinOffset) && $MinOffset > $minoffs;
      $maxoffs = $MaxOffset if defined($MaxOffset) && $MaxOffset < $maxoffs;
    }

    foreach (@freq)
    {
      $minfreq = $_ unless defined($minfreq) && $minfreq <= $_;
      $maxfreq = $_ unless defined($maxfreq) && $maxfreq >= $_;
    }

    foreach (@cmpl)
    {
      $mincmpl = $_ unless defined($mincmpl) && $mincmpl <= $_;
      $maxcmpl = $_ unless defined($maxcmpl) && $maxcmpl >= $_;
    }

    ;# think about scales

    ;# time scale: is derived from time samples
    ;#             values are seconds since start of current day
    ;#             
    ;# offset scale is [ms] - this is not touched in any way
    ;#
    ;# freq scale: here only vertical offset is adjusted to a base value
    ;#             ideally base value is intrinsic drift of oscillator
    ;#
    ;# cmpl scale: values are small positive integers including zero
    ;#             keep it zero based, but adjust scaling
    ;#             make it negative if offset is all negative
    {
      local($cmp,$pos);

      unless (defined($timebase))
      {
	local($time,@X) = (time);
	@X = localtime($time);

	;# compute today 00:00:00
	$timebase = $time - ((($X[2] * 60) + $X[1]) * 60 + $X[0]);
      }

      ;# compute freqbase  -------------
      unless (defined($freqbase))
      {
	if ($minfreq == $maxfreq)
	{
	  $freqbase = $minfreq;	# rare special case ...
	}
	else
	{
	  $pos = 1;
	  $freqbase = ($maxfreq + $minfreq)/2 - ($maxoffs + $minoffs)/2;
	  $cmp = 1;
	  $pos++,$cmp *= 10 while(($maxfreq - $minfreq) * $cmp < 1);
	  $freqbase = int($freqbase * $cmp)/$cmp;
	  ;#$freqbase += 1/$cmp if (($freqbase - $minfreq) < $minoffs);
	}
	$pos = 8 unless defined($pos);
      }
      else
      {
	$pos = 8;
      }
      $LastFreqBaseString = sprintf("%.${pos}f",$freqbase >= 0 ? $freqbase : -$freqbase);
      print "LastFreqBaseString now \"$LastFreqBaseString\"\n" if $verbose > 4;

      ;# compute cmplscale -------------
      unless (defined($cmplscale))
      {
	if ($maxcmpl)
	{
	  if (&abs($minoffs) > &abs($maxoffs))
	  {
	    $cmp = &abs($minoffs);
	    $cmplscale = -1;
	  }
	  else
	  {
	    $cmp = $maxoffs;  # no &abs here (;-)
	    $cmplscale = 1;
	  }
	  foreach $s (0.01,0.05,0.1,0.2,0.25,0.5,1,2,4,5,10,20,50,100,200,500,1000)
	  {
	    $cmplscale *= $s, last if ($maxcmpl/$s <= $cmp);
	  }
	}
	else
	{
	  $cmplscale = 1;
	}
      }
    }

    print "creating plot command input file\n" if $verbose > 2;

    $LastCnt = @time;
    $LastTimeBase  = $timebase;
    $LastFreqBase  = $freqbase;
    $LastFreqBaseString = sprintf("%f",$freqbase >= 0 ? $freqbase : -$freqbase)
	unless defined($LastFreqBaseString);
    $LastCmplScale = $cmplscale;

    print OUT "# preprocessed NTP statistics file for $STATHOST\n";
    print OUT "#    timebase is: ",&ctime($LastTimeBase)
	if defined($LastTimeBase);
    print OUT "#    frequency is offset by  ",
	       ($LastFreqBase >= 0 ? "+" : "-"),
	       "$LastFreqBaseString\n";
    print OUT "#    compliance is scaled by $LastCmplScale\n";
    print OUT "# time [h]\toffset [ms]\tfrequency [ppm]\tcompliance\n";

    while(@time)
    {
      &lr_sample(($time[$[] - $timebase)/3600,$offs[$[],'offs');
      &lr_sample(($time[$[] - $timebase)/3600,$freq[$[]-$LastFreqBase,'freq');

      printf OUT ("%s%lf\t%lf\t%lf\t%lf\n",
		  (shift(@break) ? "\n" : ""),
		  (shift(@time) - $LastTimeBase)/3600,
		  shift(@offs),
		  shift(@freq) - $LastFreqBase,
		  shift(@cmpl) / $LastCmplScale);
    }
  }
  else
  {
      ;# prevent plotcmd from processing empty file
      print "Creating plot command dummy...\n" if $verbose > 2;
      print OUT "# dummy samples\n0 1 2 3\n1 1 2 3\n";
      &lr_sample(0,1,'offs');
      &lr_sample(1,1,'offs');
      &lr_sample(0,2,'freq');
      &lr_sample(1,2,'freq');
      @time = (0, 1); $maxtime = 1; $mintime = 0;
      @offs = (1, 1); $maxoffs = 1; $minoffs = 1;
      @freq = (2, 2); $maxfreq = 2; $minfreq = 2;
      @cmpl = (3, 3); $maxcmpl = 3; $mincmpl = 3;
      $LastCnt = 2;
      $LastFreqBase = 0;
      $LastCmplScale = 1;
      $LastTimeBase = 0;
  }
  close(OUT);

  print "plot command input file created\n" if $verbose > 2;

  if (@loffset >= $cnt)
    {
      return @loffset[$[];
    }
  else # found to few lines - next time start search earlier in file
    {
      return 0 if @loffset <= 1 || ($loffset[$#loffset] - $loffset[$[]) <= 1;

      ;# EOF - 1.1 * avg(line) * $cnt
      local($val) =  $loffset[$#loffset]
	             - $cnt * 11 * (($loffset[$#loffset] - $loffset[$[]) / @loffset) / 10;
      return ($val < 0) ? 0 : $val;
    }
}

;# initial setup of plot
print "initialize plotting\n" if $verbose;
if (defined($PrintIt))
{
  print "Printing plot on printer $PrintIt\n";
  print PLOT "set output '| lpr -P$PrintIt -h'\n";
  print PLOT "set terminal postscript landscape monochrome 'Courier' 10\n";
}
print PLOT "set grid\n";
print PLOT "set tics out\n";
print PLOT "set format y '%g '\n";
printf PLOT "set time 50,24\n" unless defined($PrintIt);

$filepos = 0;
while(1)
{
  print &ctime(time) if $verbose;

  ;# update diplay characteristics
  &read_config;

  ;# take src data file and extract last $samples to $tmpfile
  $srcfile = $srcprefix . $Month[(localtime(time))[$[+4]];
  unlink($tmpfile);
  $filepos = &genfile($samples,$srcfile,$tmpfile,$filepos);

  ;# make plotcmd display samples
  &make_doplot;
  print "command for plot sub process:\n$doplot----\n" if $verbose > 2;
  print PLOT $doplot;
}
continue
{
  if (defined($PrintIt))
  {
    delete $SIG{'CHLD'};
    print PLOT "quit\n";
    close(PLOT);
    print "Plot spooled to printer $PrintIt\n";
    unlink($tmpfile);
    exit(0);
  }
  ;# wait $delay seconds
  print "waiting $delay seconds ..." if $verbose > 1;
  sleep($delay);
  print " continuing\n" if $verbose > 1;
  undef($LastFreqBaseString);
}


sub date_time_spec2seconds
{
    local($_) = @_;
    ;# a date_time_spec consistes of:
    ;#  YYYY-MM-DD_HH:MM:SS.ms
    ;# values can be omitted from the beginning and default than to
    ;# values of current date
    ;# values omitted from the end default to lowest possible values

    local($time) = time;
    local($sec,$min,$hour,$mday,$mon,$year)
	= localtime($time);

    local($last) = ();

    s/^\D*(.*\d)\D*/\1/;	# strip off garbage

  PARSE:
    {
	if (s/^(\d{4})(-|$)//)
	{
	    if ($1 < 1970)
	    {
		warn("$0: can not handle years before 1970 - year $1 ignored\n");
		return undef;
	    }
	    elsif ( $1 >= 2070)
	    {
		warn("$0: can not handle years past 2070 - year $1 ignored\n");
		return undef;
	    }
	    else
	    {
		$year = $1 % 100; # 0<= $year < 100
				 ;# - interpreted 70 .. 99,00 .. 69
	    }
	    $last = $[ + 5;
	    last PARSE if $_ eq '';
	    warn("$0: bad date_time_spec: \"$_\" found after YEAR\n"),
	    return(undef)
		if $2 eq '';
	}

	if (s/^(\d{1,2})(-|$)//)
	{
	    warn("$0: implausible month $1\n"),return(undef)
		if $1 < 1 || $1 > 12;
	    $mon = $1 - 1;
	    $last = $[ + 4;
	    last PARSE if $_ eq '';
	    warn("$0: bad date_time_spec: \"$_\" found after MONTH\n"),
	    return(undef)
		if $2 eq '';
	}
	else
	{
	    warn("$0: bad date_time_spec \"$_\"\n"),return(undef)
		if defined($last);
	    
	}

	if (s/^(\d{1,2})([_ ]|$)//)
	{
	    warn("$0: implausible month day $1 for month ".($mon+1)." (".
		 $MaxNumDaysPerMonth[$mon].")$mon\n"),
	    return(undef)
		if $1 < 1 || $1 > $MaxNumDaysPerMonth[$mon];
	    $mday = $1;
	    $last = $[ + 3;
	    last PARSE if $_ eq '';
	    warn("$0: bad date_time_spec \"$_\" found after MDAY\n"),
	    return(undef)
		if $2 eq '';
	}
	else
	{
	    warn("$0: bad date_time_spec \"$_\"\n"), return undef
		if defined($last);
	}

	;# now we face a problem:
 	;# if ! defined($last) a prefix of "07:"
	;# can be either 07:MM or 07:ss
	;# to get the second interpretation make the user add
 	;# a msec fraction part and check for this special case
	if (! defined($last) && s/^(\d{1,2}):(\d{1,2}\.\d+)//)
	{
	    warn("$0: implausible minute $1\n"), return undef
		if $1 < 0 || $1 >= 60;
	    warn("$0: implausible second $1\n"), return undef
		if $2 < 0 || $2 >= 60;
	    $min = $1;
	    $sec = $2;
	    $last = $[ + 1;
	    last PARSE if $_ eq '';
	    warn("$0: bad date_time_spec \"$_\" after SECONDS\n");
	    return undef;
	}
	
	if (s/^(\d{1,2})(:|$)//)
	{
	    warn("$0: implausible hour $1\n"), return undef
		if $1 < 0 || $1 > 24;
	    $hour = $1;
	    $last = $[ + 2;
	    last PARSE if $_ eq '';
	    warn("$0: bad date_time_spec found \"$_\" after HOUR\n"),
	    return undef
		if $2 eq '';
	}
	else
	{
	    warn("$0: bad date_time_spec \"$_\"\n"), return undef
		if defined($last);
	}

	if (s/^(\d{1,2})(:|$)//)
	{
	    warn("$0: implausible minute $1\n"), return undef
		if $1 < 0 || $1 >=60;
	    $min = $1;
	    $last = $[ + 1;
	    last PARSE if $_ eq '';
	    warn("$0: bad date_time_spec found \"$_\" after MINUTE\n"),
	    return undef
		if $2 eq '';
	}
	else
	{
	    warn("$0: bad date_time_spec \"$_\"\n"), return undef
		if defined($last);
	}

	if (s/^(\d{1,2}(\.\d+)?)//)
	{
	    warn("$0: implausible second $1\n"), return undef
		if $1 < 0 || $1 >=60;
	    $sec = $1;
	    $last = $[;
	    last PARSE if $_ eq '';
	    warn("$0: bad date_time_spec found \"$_\" after SECOND\n");
	    return undef;
	}
    }

    return $time unless defined($last);

    $sec  = 0 if $last > $[;
    $min  = 0 if $last > $[ + 1;
    $hour = 0 if $last > $[ + 2;
    $mday = 1 if $last > $[ + 3;
    $mon  = 0 if $last > $[ + 4;
    local($rtime) = &timelocal($sec,$min,$hour,$mday,$mon,$year, 0,0, 0);

    ;# $rtime may be off if daylight savings time is in effect at given date
    return $rtime + ($sec - int($sec))
	if $hour == (localtime($rtime))[$[+2];
    return
	&timelocal($sec,$min,$hour,$mday,$mon,$year, 0,0, 1)
	    + ($sec - int($sec));
}


sub min
{
  local($m) = shift;

  grep((($m > $_) && ($m = $_),0),@_);
  return $m;
}

sub max
{
  local($m) = shift;

  grep((($m < $_) && ($m = $_),0),@_);
  return $m;
}
