]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/ntp/scripts/monitoring/ntploopwatch
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / ntp / scripts / monitoring / ntploopwatch
1 #!/usr/bin/perl -w
2 ;# --*-perl-*--
3 ;#
4 ;# /src/NTP/ntp4-dev/scripts/monitoring/ntploopwatch,v 4.7 2004/11/14 16:11:05 kardel RELEASE_20050508_A
5 ;#
6 ;# process loop filter statistics file and either
7 ;#     - show statistics periodically using gnuplot
8 ;#     - or print a single plot
9 ;#
10 ;#  Copyright (c) 1992-1998 
11 ;#  Rainer Pruy, Friedrich-Alexander Universität Erlangen-Nürnberg
12 ;#
13 ;#
14 ;#############################################################
15 $0 =~ s!^.*/([^/]+)$!$1!;
16 $F = ' ' x length($0);
17 $|=1;
18
19 $ENV{'SHELL'} = '/bin/sh'; # use bourne shell
20
21 undef($config);
22 undef($workdir);
23 undef($PrintIt);
24 undef($samples);
25 undef($StartTime);
26 undef($EndTime);
27 ($a,$b) if 0;                   # keep -w happy
28 $usage = <<"E-O-P";
29 usage:
30   to watch statistics permanently:
31      $0 [-v[<level>]] [-c <config-file>] [-d <working-dir>]
32      $F [-h <hostname>]
33
34   to get a single print out specify also
35      $F -P[<printer>] [-s<samples>]
36      $F               [-S <start-time>] [-E <end-time>]
37      $F               [-Y <MaxOffs>] [-y <MinOffs>]
38
39 If You like long option names, You can use:
40     -help
41     -c    +config
42     -d    +directory
43     -h    +host
44     -v    +verbose[=<level>]
45     -P    +printer[=<printer>]
46     -s    +samples[=<samples>]
47     -S    +starttime
48     -E    +endtime
49     -Y    +maxy
50     -y    +miny
51
52 If <printer> contains a '/' (slash character) output is directed to 
53 a file of this name instead of delivered to a printer.
54 E-O-P
55
56 ;# add directory to look for lr.pl and timelocal.pl (in front of current list)
57 unshift(@INC,".");
58
59 require "lr.pl";        # linear regresion routines
60
61 $MJD_1970 = 40587;              # from ntp.h (V3)
62 $RecordSize = 48;               # usually a line fits into 42 bytes
63 $MinClip = 1;           # clip Y scales with greater range than this
64
65 ;# largest extension of Y scale from mean value, factor for standart deviation
66 $FuzzLow = 2.2;                 # for side closer to zero
67 $FuzzBig = 1.8;                 # for side farther from zero
68
69 require "ctime.pl";
70 require "timelocal.pl";
71 ;# early distributions of ctime.pl had a bug
72 $ENV{'TZ'} = 'MET' unless defined $ENV{'TZ'} || $[ > 4.010;
73 if (defined(@ctime'MoY))
74 {
75   *Month=*ctime'MoY;
76   *Day=*ctime'DoW;
77 }                                       # ' re-sync emacs fontification
78 else
79 {
80   @Month = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
81   @Day   = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
82 }
83 print @ctime'DoW if 0; # ' re-sync emacs fontification
84
85 ;# max number of days per month
86 @MaxNumDaysPerMonth = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
87
88 ;# config settable parameters
89 $delay = 60;
90 $srcprefix = "./var\@\$STATHOST/loopstats.";
91 $showoffs = 1;
92 $showfreq = 1;
93 $showcmpl = 0;
94 $showoreg = 0;
95 $showfreg = 0;
96 undef($timebase);
97 undef($freqbase);
98 undef($cmplscale);
99 undef($MaxY);
100 undef($MinY);
101 $deltaT  = 512; # indicate sample data gaps greater than $deltaT seconds
102 $verbose = 1;
103
104 while($_ = shift(@ARGV))
105 {
106     (/^[+-]help$/) && die($usage);
107     
108     (/^-c$/ || /^\+config$/) &&
109         (@ARGV || die($usage), $config = shift(@ARGV), next);
110
111     (/^-d$/ || /^\+directory$/) &&
112         (@ARGV || die($usage), $workdir = shift(@ARGV), next);
113
114     (/^-h$/ || /^\+host$/) &&
115         (@ARGV || die($usage), $STATHOST = shift, next);
116     
117     (/^-v(\d*)$/ || /^\+verbose=?(\d*)$/) &&
118         ($verbose=($1 eq "") ? 1 : $1, next);
119
120     (/^-P(\S*)$/ || /^\+[Pp]rinter=?(\S*)$/) &&
121         ($PrintIt = $1, $verbose==1 && ($verbose = 0), next);
122
123     (/^-s(\d*)$/ || /^\+samples=?(\d*)$/) &&
124         (($samples = ($1 eq "") ? (shift || die($usage)): $1), next);
125     
126     (/^-S$/ || /^\+[Ss]tart[Tt]ime$/) &&
127         (@ARGV || die($usage), $StartTime=&date_time_spec2seconds(shift),next);
128
129     (/^-E$/ || /^\+[Ee]nd[Tt]ime$/) &&
130         (@ARGV || die($usage), $EndTime = &date_time_spec2seconds(shift),next);
131     
132     (/^-Y$/ || /^\+[Mm]ax[Yy]$/) &&
133         (@ARGV || die($usage), $MaxY = shift, next);
134     
135     (/^-y$/ || /^\+[Mm]in[Yy]$/) &&
136         (@ARGV || die($usage), $MinY = shift, next);
137     
138     die("$0: unexpected argument \"$_\"\n$usage");
139 }
140
141 if (defined($workdir))
142 {
143   chdir($workdir) ||
144       die("$0: failed to change working dir to \"$workdir\": $!\n");
145 }
146
147 $PrintIt = "ps" if defined($PrintIt) && $PrintIt eq "";
148
149 if (!defined($PrintIt))
150 {
151     defined($samples) &&
152         print "WARNING: your samples value may be shadowed by config file settings\n";
153     defined($StartTime) &&
154         print "WARNING: your StartTime value may be shadowed by config file settings\n";
155     defined($EndTime) &&
156         print "WARNING: your EndTime value may be shadowed by config file settings\n";
157     defined($MaxY) &&
158         print "WARNING: your MaxY value may be shadowed by config file settings\n";
159     defined($MinY) &&
160         print "WARNING: your MinY value may be shadowed by config file settings\n";
161         
162     ;# check operating environment
163     ;# 
164     ;# gnuplot usually has X support
165     ;# I vaguely remember there was one with sunview support
166     ;#
167     ;# If Your plotcmd can display graphics using some other method
168     ;# (Tek window,..) fix the following test
169     ;# (or may be, just disable it)
170     ;#
171     !(defined($ENV{'DISPLAY'}) || defined($ENV{'WINDOW_PARENT'})) &&
172         die("Need window system to monitor statistics\n");
173 }
174
175 ;# configuration file
176 $config = "loopwatch.config" unless defined($config);
177 ($STATHOST = $config) =~ s!.*loopwatch\.config.([^/\.]*)$!$1!
178     unless defined($STATHOST);
179 ($STATTAG = $STATHOST) =~ s/^([^\.\*\s]+)\..*$/$1/;
180
181 $srcprefix =~ s/\$STATHOST/$STATHOST/g;
182
183 ;# plot command 
184 @plotcmd=("gnuplot",
185           '-title', "Ntp loop filter statistics $STATHOST",
186           '-name', "NtpLoopWatch_$STATTAG");
187 $tmpfile = "/tmp/ntpstat.$$";
188
189 ;# other variables
190 $doplot = "";   # assembled command for @plotcmd to display plot
191 undef($laststat);
192
193 ;# plot value ranges
194 undef($mintime);
195 undef($maxtime);
196 undef($minoffs);
197 undef($maxoffs);
198 undef($minfreq);
199 undef($maxfreq);
200 undef($mincmpl);
201 undef($maxcmpl);
202 undef($miny);
203 undef($maxy);
204
205 ;# stop operation if plot command dies
206 sub sigchld
207 {
208   local($pid) = wait;
209   unlink($tmpfile);
210   warn(sprintf("%s: %s died: exit status: %d signal %d\n",
211               $0,
212                (defined($Plotpid) && $Plotpid == $pid)
213                ? "plotcmd" : "unknown child $pid",
214                $?>>8,$? & 0xff)) if $?;
215   exit(1) if $? && defined($Plotpid) && $pid == $Plotpid;
216 }
217 &sigchld if 0;
218 $SIG{'CHLD'} = "sigchld";
219 $SIG{'CLD'} = "sigchld";
220
221 sub abort
222 {
223   unlink($tmpfile);
224   defined($Plotpid) && kill('TERM',$Plotpid);
225   die("$0: received signal SIG$_[$[] - exiting\n");
226 }
227 &abort if 0;    # make -w happy - &abort IS used
228 $SIG{'INT'} = $SIG{'HUP'} = $SIG{'QUIT'} = $SIG{'TERM'} = $SIG{'PIPE'} = "abort";
229
230 ;#
231 sub abs
232 {
233   ($_[$[] < 0) ? -($_[$[]) : $_[$[];
234 }
235
236 sub boolval
237 {
238   local($v) = ($_[$[]);
239
240   return 1 if ($v eq 'yes') || ($v eq 'y');
241   return 1 if ($v =~ /^[0-9]*$/) && ($v != 0);
242   return 0;
243 }
244
245 ;#####################
246 ;# start of real work 
247
248 print "starting plot command (" . join(" ",@plotcmd) . ")\n" if $verbose > 1;
249
250 $Plotpid = open(PLOT,"|-");
251 select((select(PLOT),$|=1)[$[]);        # make PLOT line bufferd
252
253 defined($Plotpid) ||
254     die("$0: failed to start plot command: $!\n");
255
256 unless ($Plotpid)
257 {
258    ;# child == plot command
259    close(STDOUT);
260    open(STDOUT,">&STDERR") ||
261        die("$0: failed to redirect STDOUT of plot command: $!\n");
262    
263    print STDOUT "plot command running as $$\n";
264
265    exec @plotcmd;
266    die("$0: failed to exec (@plotcmd): $!\n");
267    exit(1); # in case ...
268 }
269
270 sub read_config
271 {
272   local($at) = (stat($config))[$[+9];
273   local($_,$c,$v);
274
275   (undef($laststat),(print("stat $config failed: $!\n")),return) if ! defined($at);
276   return if (defined($laststat) && ($laststat == $at));
277   $laststat = $at;
278
279   print "reading configuration from \"$config\"\n" if $verbose;
280
281   open(CF,"<$config") ||
282       (warn("$0: failed to read \"$config\" - using old settings ($!)\n"),
283        return);
284   while(<CF>)
285   {
286     chop;
287     s/^([^\#]*[^\#\s]?)\s*\#.*$//;
288     next if /^\s*$/;
289
290     s/^\s*([^=\s]*)\s*=\s*(.*\S)\s*$/$1=$2/;
291
292     ($c,$v) = split(/=/,$_,2);
293     print "processing \"$c=$v\"\n" if $verbose > 3;
294     ($c eq "delay") && ($delay = $v,1) && next;
295     ($c eq 'samples') && (!defined($PrintIt) || !defined($samples)) &&
296         ($samples = $v,1) && next;
297     ($c eq 'srcprefix') && (($srcprefix=$v)=~s/\$STATHOST/$STATHOST/g,1)
298         && next;
299     ($c eq 'showoffs') &&
300         ($showoffs = boolval($v),1) && next;
301     ($c eq 'showfreq') &&
302         ($showfreq = boolval($v),1) && next;
303     ($c eq 'showcmpl') &&
304         ($showcmpl = boolval($v),1) && next;
305     ($c eq 'showoreg') &&
306         ($showoreg = boolval($v),1) && next;
307     ($c eq 'showfreg') &&
308         ($showfreg = boolval($v),1) && next;
309
310     ($c eq 'exit') && (unlink($tmpfile),die("$0: exit by config request\n"));
311
312     ($c eq 'freqbase' ||
313      $c eq 'cmplscale') &&
314         do {
315             if (! defined($v) || $v eq "" || $v eq 'dynamic')
316             {
317               eval "undef(\$$c);";
318             }
319             else
320             {
321               eval "\$$c = \$v;";
322             }
323             next;
324         };
325     ($c eq 'timebase') &&
326         do {
327             if (! defined($v) || $v eq "" || $v eq "dynamic")
328             {
329               undef($timebase);
330             }
331             else
332             {
333               $timebase=&date_time_spec2seconds($v);
334             }
335         };
336     ($c eq 'EndTime') &&
337         do {
338             next if defined($EndTime) && defined($PrintIt);
339             if (! defined($v) || $v eq "" || $v eq "none")
340             {
341               undef($EndTime);
342             }
343             else
344             {
345               $EndTime=&date_time_spec2seconds($v);
346             }
347         };
348     ($c eq 'StartTime') &&
349         do {
350             next if defined($StartTime) && defined($PrintIt);
351             if (! defined($v) || $v eq "" || $v eq "none")
352             {
353               undef($StartTime);
354             }
355             else
356             {
357               $StartTime=&date_time_spec2seconds($v);
358             }
359         };
360
361     ($c eq 'MaxY') &&
362         do {
363             next if defined($MaxY) && defined($PrintIt);
364             if (! defined($v) || $v eq "" || $v eq "none")
365             {
366               undef($MaxY);
367             }
368             else
369             {
370               $MaxY=$v;
371             }
372         };
373
374     ($c eq 'MinY') &&
375         do {
376             next if defined($MinY) && defined($PrintIt);
377             if (! defined($v) || $v eq "" || $v eq "none")
378             {
379               undef($MinY);
380             }
381             else
382             {
383               $MinY=$v;
384             }
385         };
386
387     ($c eq 'deltaT') &&
388         do {
389             if (!defined($v) || $v eq "")
390             {
391               undef($deltaT);
392             }
393             else
394             {
395               $deltaT = $v;
396             }
397             next;
398         };
399     ($c eq 'verbose') && ! defined($PrintIt) &&
400         do {
401              if (!defined($v) || $v == 0)
402              {
403                $verbose = 0;
404              }
405              else
406              {
407                $verbose = $v;
408              }
409              next;
410         };
411     ;# otherwise: silently ignore unrecognized config line
412   }
413   close(CF);
414   ;# set show defaults when nothing selected
415   $showoffs = $showfreq = $showcmpl = 1
416       unless $showoffs || $showfreq || $showcmpl;
417   if ($verbose > 3)
418   {
419     print  "new configuration:\n";
420     print  "   delay\t= $delay\n";
421     print  "   samples\t= $samples\n";
422     print  "   srcprefix\t= $srcprefix\n";
423     print  "   showoffs\t= $showoffs\n";
424     print  "   showfreq\t= $showfreq\n";
425     print  "   showcmpl\t= $showcmpl\n";
426     print  "   showoreg\t= $showoreg\n";
427     print  "   showfreg\t= $showfreg\n";
428     printf "   timebase\t= %s",defined($timebase)?&ctime($timebase):"dynamic\n";
429     printf "   freqbase\t= %s\n",defined($freqbase)  ?"$freqbase":"dynamic";
430     printf "   cmplscale\t= %s\n",defined($cmplscale)?"$cmplscale":"dynamic";
431     printf "   StartTime\t= %s",defined($StartTime)?&ctime($StartTime):"none\n";
432     printf "   EndTime\t= %s",  defined($EndTime) ?  &ctime($EndTime):"none\n";
433     printf "   MaxY\t= %s",defined($MaxY)? $MaxY      :"none\n";
434     printf "   MinY\t= %s",defined($MinY)? $MinY      :"none\n";
435     print  "   verbose\t= $verbose\n";
436   }
437 print "configuration file read\n" if $verbose > 2;
438 }
439
440 sub make_doplot($$)
441 {
442     my($lo, $lf) = @_;
443     local($c) = ("");
444     local($fmt)
445         = ("%s \"%s\" using 1:%d title '%s <%lf %lf> %6s' with lines");
446     local($regfmt)
447         = ("%s ((%lf * x) + %lf) title 'lin. approx. %s (%f t[h]) %s %f <%f> %6s' with lines");
448     
449     $doplot = "    set title 'NTP loopfilter statistics for $STATHOST  " .
450         "(last $LastCnt samples from $srcprefix*)'\n";
451     
452     local($xts,$xte,$i,$t);
453     
454     local($s,$c) = ("");
455
456     ;# number of integral seconds to get at least 12 tic marks on x axis
457     $t = int(($maxtime - $mintime) / 12 + 0.5);
458     $t = 1 unless $t;           # prevent $t to be zero
459     foreach $i (30,
460                 60,5*60,15*60,30*60,
461                 60*60,2*60*60,6*60*60,12*60*60,
462                 24*60*60,48*60*60)
463     {
464         last if $t < $i;
465         $t = $t - ($t % $i);
466     }
467     print "time label resolution: $t seconds\n" if $verbose > 1;
468     
469     ;# make gnuplot use wall clock time labels instead of NTP seconds
470     for ($c="", $i = $mintime - ($mintime % $t);
471          $i <= $maxtime + $t;
472          $i += $t, $c=",")
473     {
474         $s .= $c;
475         ((int($i / $t) % 2) &&
476          ($s .= sprintf("'' %lf",($i - $LastTimeBase)/3600))) ||
477              (($t <= 60) &&
478               ($s .= sprintf("'%d:%02d:%02d' %lf",
479                              (localtime($i))[$[+2,$[+1,$[+0],
480                              ($i - $LastTimeBase)/3600))) 
481                  || (($t <= 2*60*60) &&
482                      ($s .= sprintf("'%d:%02d' %lf",
483                                     (localtime($i))[$[+2,$[+1],
484                                     ($i - $LastTimeBase)/3600)))
485                      || (($t <= 12*60*60) &&
486                          ($s .= sprintf("'%s %d:00' %lf",
487                                         $Day[(localtime($i))[$[+6]],
488                                         (localtime($i))[$[+2],
489                                         ($i - $LastTimeBase)/3600)))
490                          || ($s .= sprintf("'%d.%d-%d:00' %lf",
491                                            (localtime($i))[$[+3,$[+4,$[+2],
492                                            ($i - $LastTimeBase)/3600));
493     }
494     $doplot .= "set xtics ($s)\n";
495     
496     chop($xts = &ctime($mintime));
497     chop($xte = &ctime($maxtime));
498     $doplot .= "set xlabel 'Start:  $xts    --   Time Scale   --    End:  $xte'\n";
499     $doplot .= "set yrange [" ;
500     $doplot .= defined($MinY) ? sprintf("%lf", $MinY) : $miny;
501     $doplot .= ':';
502     $doplot .= defined($MaxY) ? sprintf("%lf", $MaxY) : $maxy;
503     $doplot .= "]\n";
504     
505     $doplot .= "   plot";
506     $c = "";
507     $showoffs &&
508         ($doplot .= sprintf($fmt,$c,$tmpfile,2,
509                             "offset",
510                             $minoffs,$maxoffs,
511                             "[ms]"),
512          $c = ",");
513     $LastCmplScale = 1 if ! defined($LastCmplScale);
514     $showcmpl &&
515         ($doplot .= sprintf($fmt,$c,$tmpfile,4,
516                             "compliance" .
517                             (&abs($LastCmplScale) > 1
518                              ? " / $LastCmplScale"
519                              : (&abs($LastCmplScale) == 1 ? "" : " * ".(1/$LastCmplScale))),
520                             $mincmpl/$LastCmplScale,$maxcmpl/$LastCmplScale,
521                             ""),
522          $c = ",");
523     $LastFreqBase = 0 if ! defined($LastFreqBase);
524     $LastFreqBaseString = "?" if ! defined($LastFreqBaseString);
525     $FreqScale = 1 if ! defined($FreqScale);
526     $FreqScaleInv = 1 if ! defined($FreqScaleInv);
527     $showfreq &&
528         ($doplot .= sprintf($fmt,$c,$tmpfile,3,
529                             "frequency" .
530                             ($LastFreqBase > 0
531                              ? " - $LastFreqBaseString" 
532                              : ($LastFreqBase == 0 ? "" : " + $LastFreqBaseString")),
533                             $minfreq * $FreqScale - $LastFreqBase,
534                             $maxfreq * $FreqScale - $LastFreqBase,
535                             "[${FreqScaleInv}ppm]"),
536          $c = ",");
537     $showoreg && $showoffs &&
538         ($doplot .= sprintf($regfmt, $c,
539                             $lo->B(),$lo->A(),
540                             "offset   ",
541                             $lo->B(),
542                             (($lo->A()) < 0 ? '-' : '+'),
543                             &abs($lo->A()), $lo->r(),
544                             "[ms]"),
545          $c = ",");
546     $showfreg && $showfreq &&
547         ($doplot .= sprintf($regfmt, $c,
548                             $lf->B() * $FreqScale,
549                             ($lf->A() + $minfreq) * $FreqScale - $LastFreqBase,
550                             "frequency",
551                             $lf->B() * $FreqScale,
552                             (($lf->A() + $minfreq) * $FreqScale - $LastFreqBase) < 0 ? '-' : '+',
553                             &abs(($lf->A() + $minfreq) * $FreqScale - $LastFreqBase),
554                             $lf->r(),
555                             "[${FreqScaleInv}ppm]"),
556          $c = ",");
557     $doplot .= "\n";
558 }
559
560 %F_key   = ();
561 %F_name  = ();
562 %F_size  = ();
563 %F_mtime = ();
564 %F_first = ();
565 %F_last  = ();
566
567 sub genfile
568 {
569     local($cnt,$in,$out,$lo,$lf,@fpos) = @_;
570     
571     local(@F,@t,$t,$lastT) = ();
572     local(@break,@time,@offs,@freq,@cmpl,@loffset,@filekey) = ();
573     local($lm,$l,@f);
574     
575     local($sdir,$sname);
576     
577     ;# allocate some storage for the tables
578     ;# otherwise realloc may get into troubles
579     if (defined($StartTime) && defined($EndTime))
580     {
581         $l = ($EndTime-$StartTime) -$[+1 +1; # worst case: 1 sample per second
582     }
583     else
584     {
585         $l = $cnt + 10;
586     }
587     print "preextending arrays to $l entries\n" if $verbose > 2;
588     $#break =   $l; for ($i=$[; $i<=$l;$i++) { $break[$i] = 0; }
589     $#time =    $l; for ($i=$[; $i<=$l;$i++) { $time[$i] = 0; }
590     $#offs =    $l; for ($i=$[; $i<=$l;$i++) { $offs[$i] = 0; }
591     $#freq =    $l; for ($i=$[; $i<=$l;$i++) { $freq[$i] = 0; }
592     $#cmpl =    $l; for ($i=$[; $i<=$l;$i++) { $cmpl[$i] = 0; }
593     $#loffset = $l; for ($i=$[; $i<=$l;$i++) { $loffset[$i] = 0; }
594     $#filekey = $l; for ($i=$[; $i<=$l;$i++) { $filekey[$i] = 0; }
595     ;# now reduce size again
596     $#break =   $[ - 1;
597     $#time =    $[ - 1;
598     $#offs =    $[ - 1;
599     $#freq =    $[ - 1;
600     $#cmpl =    $[ - 1;
601     $#loffset = $[ - 1;
602     $#filekey = $[ - 1;
603     print "memory allocation ready\n" if $verbose > 2;
604     sleep(3) if $verbose > 1;
605
606     $fpos[$[] = '' if !defined($fpos[$[]);
607
608     if (index($in,"/") < $[)
609     {
610         $sdir = ".";
611         $sname = $in;
612     }
613     else
614     {
615         ($sdir,$sname) = ($in =~ m!^(.*)/([^/]*)!);
616         $sname = "" unless defined($sname);
617     }
618     
619     $Ltime = -1 if ! defined($Ltime);
620     if (!defined($Lsdir) || $Lsdir ne $sdir || $Ltime != (stat($sdir))[$[+9] ||
621         grep($F_mtime{$_} != (stat($F_name{$_}))[$[+9], @F_files))
622         
623     {
624         print "rescanning directory \"$sdir\" for files \"$sname*\"\n"
625             if $verbose > 1;
626
627         ;# rescan directory on changes
628         $Lsdir = $sdir;
629         $Ltime = (stat($sdir))[$[+9];
630         </X{> if 0;             # dummy line - calm down my formatter
631         local(@newfiles) = < ${in}*[0-9] >;
632         local($st_dev,$st_ino,$st_mtime,$st_size,$name,$key,$modified);
633
634         foreach $name (@newfiles)
635         {
636             ($st_dev,$st_ino,$st_size,$st_mtime) =
637                 (stat($name))[$[,$[+1,$[+7,$[+9];
638             $modified = 0;
639             $key = sprintf("%lx|%lu", $st_dev, $st_ino);
640             
641             print "candidate file \"$name\"",
642                   (defined($st_dev) ? "" : " failed: $!"),"\n"
643                       if $verbose > 2;
644             
645             if (! defined($F_key{$name}) || $F_key{$name} ne $key)
646             {
647                 $F_key{$name} = $key;
648                 $modified++;
649             }
650             if (!defined($F_name{$key}) || $F_name{$key} ne $name)
651             {
652                 $F_name{$key} = $name;
653                 $modified++;
654             }
655             if (!defined($F_size{$key}) || $F_size{$key} != $st_size)
656             {
657                 $F_size{$key} = $st_size;
658                 $modified++;
659             }
660             if (!defined($F_mtime{$key}) || $F_mtime{$key} != $st_mtime)
661             {
662                 $F_mtime{$key} = $st_mtime;
663                 $modified++;
664             }
665             if ($modified)
666             {
667                 print "new data \"$name\" key: $key;\n" if $verbose > 1;
668                 print "             size: $st_size; mtime: $st_mtime;\n"
669                     if $verbose > 1;
670                 $F_last{$key} = $F_first{$key} = $st_mtime;
671                 $F_first{$key}--; # prevent zero divide later on
672                 ;# now compute derivated attributes
673                 open(IN, "<$name") ||
674                     do {
675                         warn "$0: failed to open \"$name\": $!";
676                         next;
677                     };
678
679                 while(<IN>)
680                 {
681                     @F = split;
682                     next if @F < 5;
683                     next if $F[$[] eq "";
684                     $t = ($F[$[] - $MJD_1970) * 24 * 60 * 60;
685                     $t += $F[$[+1];
686                     $F_first{$key} = $t;
687                     print "\tfound first entry: $t ",&ctime($t)
688                         if $verbose > 4;
689                     last;
690                 }
691                 seek(IN,
692                      ($st_size > 4*$RecordSize) ? $st_size - 4*$RecordSize : 0,
693                      0);
694                 while(<IN>)
695                 {
696                     @F = split;
697                     next if @F < 5;
698                     next if $F[$[] eq "";
699                     $t = ($F[$[] - $MJD_1970) * 24 * 60 * 60;
700                     $t += $F[$[+1];
701                     $F_last{$key} = $t;
702                     $_ = <IN>;
703                     print "\tfound last entry: $t ", &ctime($t)
704                         if $verbose > 4 && ! defined($_);
705                     last unless defined($_);
706                     redo;
707                     ;# Ok, calm down...
708                     ;# using $_ = <IN> in conjunction with redo
709                     ;# is semantically equivalent to the while loop, but
710                     ;# I needed a one line look ahead and this solution
711                     ;# was what I thought of first
712                     ;# and.. If you do not like it dont look
713                 }
714                 close(IN);
715                 print("             first: ",$F_first{$key},
716                       " last: ",$F_last{$key},"\n") if $verbose > 1;
717             }
718         }
719         ;# now reclaim memory used for files no longer referenced ...
720         local(%Names);
721         grep($Names{$_} = 1,@newfiles);
722         foreach (keys %F_key)
723         {
724             next if defined($Names{$_});
725             delete $F_key{$_};
726             $verbose > 2 && print "no longer referenced: \"$_\"\n";
727         }
728         %Names = ();
729         
730         grep($Names{$_} = 1,values(%F_key));
731         foreach (keys %F_name)
732         {
733             next if defined($Names{$_});
734             delete $F_name{$_};
735             $verbose > 2 && print "unref name($_)= $F_name{$_}\n";
736         }
737         foreach (keys %F_size)
738         {
739             next if defined($Names{$_});
740             delete $F_size{$_};
741             $verbose > 2 && print "unref size($_)\n";
742         }
743         foreach (keys %F_mtime)
744         {
745             next if defined($Names{$_});
746             delete $F_mtime{$_};
747             $verbose > 2 && print "unref mtime($_)\n";
748         }
749         foreach (keys %F_first)
750         {
751             next if defined($Names{$_});
752             delete $F_first{$_};
753             $verbose > 2 && print "unref first($_)\n";
754         }
755         foreach (keys %F_last)
756         {
757             next if defined($Names{$_});
758             delete $F_last{$_};
759             $verbose > 2 && print "unref last($_)\n";
760         }
761         ;# create list sorted by time
762         @F_files = sort {$F_first{$a} <=> $F_first{$b}; } keys(%F_name);
763         if ($verbose > 1)
764         {
765             print "Resulting file list:\n";
766             foreach (@F_files)
767             {
768                 print "\t$_\t$F_name{$_}\n";
769             }
770         }
771     }
772     
773     printf("processing %s; output \"$out\" (%d input files)\n",
774            ((defined($StartTime) && defined($EndTime))
775             ? "time range"
776             : (defined($StartTime) ? "$cnt samples from StartTime" :
777               (defined($EndTime) ? "$cnt samples to EndTime" :
778                  "last $cnt samples"))),
779             scalar(@F_files))
780         if $verbose > 1;
781     
782     ;# open output file - will be input for plotcmd
783     open(OUT,">$out") || 
784         do {
785             warn("$0: cannot create \"$out\": $!\n");
786         };
787     
788     @f = @F_files;
789     if (defined($StartTime))
790     {
791         while (@f && ($F_last{$f[$[]} < $StartTime))
792         {
793             print("shifting ", $F_name{$f[$[]},
794                   " last: ", $F_last{$f[$[]},
795                   " < StartTime: $StartTime\n")
796                 if $verbose > 3;
797             shift(@f);
798         }
799
800
801     }
802     if (defined($EndTime))
803     {
804         while (@f && ($F_first{$f[$#f]} > $EndTime))
805         {
806             print("popping  ", $F_name{$f[$#f]},
807                   " first: ", $F_first{$f[$#f]},
808                   " > EndTime: $EndTime\n")
809                 if $verbose > 3;
810             pop(@f);
811         }
812     }
813     
814     if (@f)
815     {
816         if (defined($StartTime))
817         {
818             print "guess start according to StartTime ($StartTime)\n"
819                 if $verbose > 3;
820
821             if ($fpos[$[] eq 'start')
822             {
823                 if (grep($_ eq $fpos[$[+1],@f))
824                 {
825                     shift(@f) while @f && $f[$[] ne $fpos[$[+1];
826                 }
827                 else
828                 {
829                     @fpos = ('start', $f[$[], undef);
830                 }
831             }
832             else
833             {
834                 @fpos = ('start' , $f[$[], undef);
835             }
836             
837             if (!defined($fpos[$[+2]))
838             {
839                 if ($StartTime <= $F_first{$f[$[]})
840                 {
841                     $fpos[$[+2] = 0;
842                 }
843                 else
844                 {
845                     $fpos[$[+2] =
846                         int($F_size{$f[$[]} *
847                             (($StartTime - $F_first{$f[$[]})/
848                              ($F_last{$f[$[]} - $F_first{$f[$[]})));
849                     $fpos[$[+2] = ($fpos[$[+2] <= 2 * $RecordSize)
850                         ? 0 : $fpos[$[+2] - 2 * $RecordSize;
851                     ;# anyway  as the data may contain "time holes" 
852                     ;# our heuristics may baldly fail
853                     ;# so just start at 0
854                     $fpos[$[+2] = 0;
855                 }
856             }
857         }
858         elsif (defined($EndTime))
859         {
860             print "guess starting point according to EndTime ($EndTime)\n"
861                 if $verbose > 3;
862             
863             if ($fpos[$[] eq 'end')
864             {
865                 if (grep($_ eq $fpos[$[+1],@f))
866                 {
867                     shift(@f) while @f && $f[$[] ne $fpos[$[+1];
868                 }
869                 else
870                 {
871                     @fpos = ('end', $f[$[], undef);
872                 }
873             }
874             else
875             {
876                 @fpos = ('end', $f[$[], undef);
877             }
878             
879             if (!defined($fpos[$[+2]))
880             {
881                 local(@x) = reverse(@f);
882                 local($s,$c) = (0,$cnt);
883                 if ($EndTime < $F_last{$x[$[]})
884                 {
885                     ;# last file will only be used partially
886                     $s = int($F_size{$x[$[]} *
887                              (($EndTime - $F_first{$x[$[]}) /
888                               ($F_last{$x[$[]} - $F_first{$x[$[]})));
889                     $s = int($s/$RecordSize);
890                     $c -= $s - 1;
891                     if ($c <= 0)
892                     {
893                         ;# start is in the same file
894                         $fpos[$[+1] = $x[$[];
895                         $fpos[$[+2] = ($c >=-2) ? 0 : (-$c - 2) * $RecordSize;
896                         shift(@f) while @f && ($f[$[] ne $x[$[]);
897                     }
898                     else
899                     {
900                         shift(@x);
901                     }
902                 }
903                 
904                 if (!defined($fpos[$[+2]))
905                 {
906                     local($_);
907                     while($_ = shift(@x))
908                     {
909                         $s = int($F_size{$_}/$RecordSize);
910                         $c -= $s - 1;
911                         if ($c <= 0)
912                         {
913                             $fpos[$[+1] = $_;
914                             $fpos[$[+2] = ($c>-2) ? 0 : (-$c - 2) * $RecordSize;
915                             shift(@f) while @f && ($f[$[] ne $_);
916                             last;
917                         }
918                     }
919                 }
920             }
921         }
922         else
923         {
924             print "guessing starting point according to count ($cnt)\n"
925                 if $verbose > 3;
926             ;# guess offset to get last available $cnt samples
927             if ($fpos[$[] eq 'cnt')
928             {
929                 if (grep($_ eq $fpos[$[+1],@f))
930                 {
931                     print "old positioning applies\n" if $verbose > 3;
932                     shift(@f) while @f && $f[$[] ne $fpos[$[+1];
933                 }
934                 else
935                 {
936                     @fpos = ('cnt', $f[$[], undef);
937                 }
938             }
939             else
940             {
941                 @fpos = ('cnt', $f[$[], undef);
942             }
943             
944             if (!defined($fpos[$[+2]))
945             {
946                 local(@x) = reverse(@f);
947                 local($s,$c) = (0,$cnt);
948                 
949                 local($_);
950                 while($_ = shift(@x))
951                 {
952                     print "examing \"$_\" $c samples still needed\n"
953                         if $verbose > 4;
954                     $s = int($F_size{$_}/$RecordSize);
955                     $c -= $s - 1;
956                     if ($c <= 0)
957                     {
958                         $fpos[$[+1] = $_;
959                         $fpos[$[+2] = ($c>-2) ? 0 : (-$c - 2) * $RecordSize;
960                         shift(@f) while @f && ($f[$[] ne $_);
961                         last;
962                     }
963                 }
964                 if (!defined($fpos[$[+2]))
965                 {
966                     print "no starting point yet - using start of data\n"
967                         if $verbose > 2;
968                     $fpos[$[+2] = 0;
969                 }
970             }
971         }
972     }
973     print "Ooops, no suitable input file ??\n"
974         if $verbose > 1 && @f <= 0;
975
976     printf("Starting at (%s) \"%s\" offset %ld using %d files\n",
977            $fpos[$[+1],
978            $F_name{$fpos[$[+1]},
979            $fpos[$[+2],
980            scalar(@f))
981         if $verbose > 2;
982
983     $lm = 1;
984     $l = 0;    
985     foreach $key (@f)
986     {
987         $file = $F_name{$key};
988         print "processing file \"$file\"\n" if $verbose > 2;
989         
990         open(IN,"<$file") ||
991             (warn("$0: cannot read \"$file\": $!\n"), next);
992         
993         ;# try to seek to a position nearer to the start of the interesting lines
994         ;# should always affect only first item in @f
995         ($key eq $fpos[$[+1]) &&
996             (($verbose > 1) &&
997              print("Seeking to offset $fpos[$[+2]\n"),
998                 seek(IN,$fpos[$[+2],0) ||
999                     warn("$0: seek(\"$F_name{$key}\" failed: $|\n"));
1000         
1001         while(<IN>)
1002         {
1003             $l++;
1004             ($verbose > 3) &&
1005                 (($l % $lm) == 0 && print("\t$l lines read\n") &&
1006                  (($l ==     2) && ($lm =    10) ||
1007                   ($l ==   100) && ($lm =   100) ||
1008                   ($l ==   500) && ($lm =   500) ||
1009                   ($l ==  1000) && ($lm =  1000) ||
1010                   ($l ==  5000) && ($lm =  5000) ||
1011                   ($l == 10000) && ($lm = 10000)));
1012             
1013             @F = split;
1014             
1015             next if @F < 6;     # no valid input line is this short
1016             next if $F[$[] eq "";
1017             next if ($F[$[] !~ /^\d+$/);
1018             ($F[$[] !~ /^\d+$/) && # A 'never should have happend' error
1019                 die("$0: unexpected input line: >$_<\n");
1020             
1021             ;# modified Julian to UNIX epoch
1022             $t = ($F[$[] - $MJD_1970) * 24 * 60 * 60;
1023             $t += $F[$[+1];     # add seconds + fraction
1024             
1025             ;# multiply offset by 1000 to get ms - try to avoid float op
1026             (($F[$[+2] =~ s/(\d*)\.(\d{3})(\d*)/$1$2.$3/) &&
1027              $F[$[+2] =~ s/0+([\d\.])/($1 eq '.') ? '0.' : $1/e) # strip leading zeros
1028                 || ($F[$[+2] *= 1000);
1029
1030             
1031             ;# skip samples out of specified time range
1032             next if (defined($StartTime) && $StartTime > $t);
1033             next if (defined($EndTime) && $EndTime < $t);
1034             
1035             next if defined($lastT) && $t < $lastT; # backward in time ??
1036             
1037             push(@offs,$F[$[+2]);
1038             push(@freq,$F[$[+3] * (2**20/10**6));
1039             push(@cmpl,$F[$[+5]);
1040             
1041             push(@break, (defined($lastT) && ($t - $lastT > $deltaT))); 
1042             $lastT = $t;
1043             push(@time,$t);
1044             push(@loffset, tell(IN) - length($_));
1045             push(@filekey, $key);
1046             
1047             shift(@break),shift(@time),shift(@offs),
1048             shift(@freq), shift(@cmpl),shift(@loffset),
1049             shift(@filekey)
1050                 if @time > $cnt &&
1051                     ! (defined($StartTime) && defined($EndTime));
1052
1053             last if @time >= $cnt && defined($StartTime) && !defined($EndTime);
1054         }
1055         close(IN);
1056         last if @time >= $cnt && defined($StartTime) && !defined($EndTime);
1057     }
1058     print "input scanned ($l lines/",scalar(@time)," samples)\n"
1059         if $verbose > 1;
1060     
1061     if (@time)
1062     {
1063         local($_,@F);
1064         
1065         local($timebase) unless defined($timebase);
1066         local($freqbase) unless defined($freqbase);
1067         local($cmplscale) unless defined($cmplscale);
1068         
1069         undef $mintime;
1070         undef $maxtime;
1071         undef $minoffs;
1072         undef $maxoffs;
1073         undef $minfreq;
1074         undef $maxfreq;
1075         undef $mincmpl;
1076         undef $maxcmpl;
1077         undef $miny;
1078         undef $maxy ;
1079         
1080         print "computing ranges\n" if $verbose > 2;
1081         
1082         $LastCnt = @time;
1083
1084         ;# @time is in ascending order (;-)
1085         $mintime = $time[$[];
1086         $maxtime = $time[$#time];
1087         unless (defined($timebase))
1088         {
1089             local($time,@X) = (time);
1090             @X = localtime($time);
1091             
1092             ;# compute today 00:00:00
1093             $timebase = $time - ((($X[$[+2]*60)+$X[$[+1])*60+$X[$[]);
1094
1095         }
1096         $LastTimeBase = $timebase;
1097
1098         if ($showoffs)
1099         {
1100             local($i,$m,$f);
1101             
1102             $minoffs = &min(@offs);
1103             $maxoffs = &max(@offs);
1104             
1105             ;# I know, it is not perl style using indices to access arrays,
1106             ;# but I have to proccess two arrays in sync, non-destructively
1107             ;# (otherwise a (shift(@a1),shift(a2)) would do),
1108             ;# I dont like to make copies of these arrays as they may be huge
1109             $i = $[;
1110             $lo->sample(($time[$i]-$timebase)/3600,$offs[$i]),$i++
1111                 while $i <= $#time;
1112
1113             ($minoffs == $maxoffs) && ($minoffs -= 0.1,$maxoffs += 0.1);
1114
1115             $i = $lo->sigma();
1116             $m = $lo->mean();
1117
1118             print "mean offset: $m sigma: $i\n" if $verbose > 2;
1119
1120             if (($maxoffs - $minoffs) > $MinClip)
1121             {
1122                 $f = (&abs($minoffs) < &abs($maxoffs)) ? $FuzzLow : $FuzzBig;
1123                 $miny = (($m - $minoffs) <= ($f * $i))
1124                     ? $minoffs : ($m - $f * $i);
1125                 $f = ($f == $FuzzLow) ? $FuzzBig : $FuzzLow;
1126                 $maxy = (($maxoffs - $m) <= ($f * $i))
1127                     ? $maxoffs : ($m + $f * $i);
1128             }
1129             else
1130             {
1131                 $miny = $minoffs;
1132                 $maxy = $maxoffs;
1133             }
1134             ($maxy-$miny) == 0 &&
1135                 (($maxy,$miny)
1136                  = (($maxoffs - $minoffs) > 0)
1137                  ? ($maxoffs,$minoffs) : ($MinClip,-$MinClip));
1138
1139             $maxy = $MaxY if defined($MaxY) && $MaxY < $maxy;
1140             $miny = $MinY if defined($MinY) && $MinY > $miny;
1141
1142             print  "offset min clipped from $minoffs to $miny\n"
1143                 if $verbose > 2 && $minoffs != $miny;
1144             print  "offset max clipped from $maxoffs to $maxy\n"
1145                 if $verbose > 2 && $maxoffs != $maxy;
1146         }
1147         
1148         if ($showfreq)
1149         {
1150             local($i,$m);
1151             
1152             $minfreq = &min(@freq);
1153             $maxfreq = &max(@freq);
1154             
1155             $i = $[;
1156             $lf->sample(($time[$i]-$timebase)/3600,$freq[$i]-$minfreq),
1157             $i++
1158                 while $i <= $#time;
1159             
1160             $i = $lf->sigma();
1161             $m = $lf->mean() + $minfreq;
1162
1163             print "mean frequency: $m sigma: $i\n" if $verbose > 2;
1164
1165             if (defined($maxy))
1166             {
1167                 local($s) =
1168                     ($maxfreq - $minfreq)
1169                         ? ($maxy - $miny) / ($maxfreq - $minfreq) : 1;
1170
1171                 if (defined($freqbase))
1172                 {
1173                     $FreqScale = 1;
1174                     $FreqScaleInv = "";
1175                 }
1176                 else
1177                 {
1178                     $FreqScale = 1;
1179                     $FreqScale = 10 ** int(log($s)/log(10) - 0.9999);
1180                     $FreqScaleInv =
1181                         ("$FreqScale" =~ /^10(0*)$/) ? "0.${1}1" : 
1182                          ($FreqScale == 1 ? "" : (1/$FreqScale));
1183                     
1184                     $freqbase = ($maxfreq + $minfreq)/ 2 * $FreqScale; #$m * $FreqScale;
1185                     $freqbase -= ($maxy + $miny) / 2; #$lf->mean();
1186
1187                     ;# round resulting freqbase
1188                     ;# to precision of min max difference
1189                     $s = -12;
1190                     $s = int(log(($maxfreq-$minfreq)*$FreqScale)/log(10))-1
1191                         unless ($maxfreq-$minfreq) < 1e-12;
1192                     $s = 10 ** $s;
1193                     $freqbase = int($freqbase / $s) * $s;
1194                 }
1195             }
1196             else
1197             {
1198                 $FreqScale = 1;
1199                 $FreqScaleInv = "";
1200                 $freqbase = $m unless defined($freqbase);
1201                 if (($maxfreq - $minfreq) > $MinClip)
1202                 {
1203                     $f = (&abs($minfreq) < &abs($maxfreq))
1204                         ? $FuzzLow : $FuzzBig;
1205                     $miny = (($freqbase - $minfreq) <= ($f * $i))
1206                         ? ($minfreq-$freqbase) : (- $f * $i);
1207                     $f = ($f == $FuzzLow) ? $FuzzBig : $FuzzLow;
1208                     $maxy = (($maxfreq - $freqbase) <= ($f * $i))
1209                         ? ($maxfreq-$freqbase) : ($f * $i);
1210                 }
1211                 else
1212                 {
1213                     $miny = $minfreq - $freqbase;
1214                     $maxy = $maxfreq - $freqbase;
1215                 }
1216                 ($maxy - $miny) == 0 &&
1217                     (($maxy,$miny) =
1218                      (($maxfreq - $minfreq) > 0)
1219                      ? ($maxfreq-$freqbase,$minfreq-$freqbase) : (0.5,-0.5));
1220                 
1221                 $maxy = $MaxY if defined($MaxY) && $MaxY < $maxy;
1222                 $miny = $MinY if defined($MinY) && $MinY > $miny;
1223
1224                 print("frequency min clipped from ",$minfreq-$freqbase,
1225                       " to $miny\n")
1226                     if $verbose > 2 && $miny != ($minfreq - $freqbase);
1227                 print("frequency max clipped from ",$maxfreq-$freqbase,
1228                       " to $maxy\n")
1229                     if $verbose > 2 && $maxy != ($maxfreq - $freqbase);
1230             }
1231             $LastFreqBaseString =
1232                 sprintf("%g",$freqbase >= 0 ? $freqbase : -$freqbase);
1233             $LastFreqBase = $freqbase;
1234             print "LastFreqBaseString now \"$LastFreqBaseString\"\n"
1235                 if $verbose > 5;
1236         }
1237         else
1238         {
1239             $FreqScale = 1;
1240             $FreqScaleInv = "";
1241             $LastFreqBase = 0;
1242             $LastFreqBaseString = "";
1243         }
1244                 
1245         if ($showcmpl)
1246         {
1247             $mincmpl = &min(@cmpl);
1248             $maxcmpl = &max(@cmpl);
1249
1250             if (!defined($cmplscale))
1251             {
1252                 if (defined($maxy))
1253                 {
1254                     local($cmp)
1255                         = (&abs($miny) > &abs($maxy)) ? &abs($miny) : $maxy;
1256                     $cmplscale = $cmp == $maxy ? 1 : -1;
1257
1258                     foreach (0.01, 0.02, 0.05,
1259                              0.1, 0.2, 0.25, 0.4, 0.5,
1260                              1, 2, 4, 5,
1261                              10, 20, 25, 50,
1262                              100, 200, 250, 500, 1000)
1263                     {
1264                         $cmplscale *= $_, last if $maxcmpl/$_ <= $cmp;
1265                     }
1266                 }
1267                 else
1268                 {
1269                     $cmplscale = 1;
1270                     $miny = $mincmpl ? 0 : -$MinClip;
1271                     $maxy = $maxcmpl+$MinClip;
1272                 }
1273             }
1274             $LastCmplScale = $cmplscale;
1275         }
1276         else
1277         {
1278             $LastCmplScale = 1;
1279         }
1280         
1281         print "creating plot command input file\n" if $verbose > 2;
1282         
1283         
1284         print OUT ("# preprocessed NTP statistics file for $STATHOST\n");
1285         print OUT ("#    timebase is: ",&ctime($LastTimeBase))
1286             if defined($LastTimeBase);
1287         print OUT ("#    frequency is offset by  ",
1288                    ($LastFreqBase >= 0 ? "+" : "-"),
1289                    "$LastFreqBaseString [${FreqScaleInv}ppm]\n");
1290         print OUT ("#    compliance is scaled by $LastCmplScale\n");
1291         print OUT ("# time [h]\toffset [ms]\tfrequency [${FreqScaleInv}ppm]\tcompliance\n");
1292         
1293         printf OUT ("%s%lf\t%lf\t%lf\t%lf\n",
1294                     (shift(@break) ? "\n" : ""),
1295                     (shift(@time) - $LastTimeBase)/3600,
1296                     shift(@offs),
1297                     shift(@freq) * $FreqScale - $LastFreqBase,
1298                     shift(@cmpl) / $LastCmplScale)
1299             while(@time);
1300     }
1301     else
1302     {
1303         ;# prevent plotcmd from processing empty file
1304         print "Creating plot command dummy...\n" if $verbose > 2;
1305         print OUT "# dummy samples\n0 1 2 3\n1 1 2 3\n";
1306         $lo->sample(0,1);
1307         $lo->sample(1,1);
1308         $lf->sample(0,2);
1309         $lf->sample(1,2);
1310         @time = (0, 1); $maxtime = 1; $mintime = 0;
1311         @offs = (1, 1); $maxoffs = 1; $minoffs = 1;
1312         @freq = (2, 2); $maxfreq = 2; $minfreq = 2;
1313         @cmpl = (3, 3); $maxcmpl = 3; $mincmpl = 3;
1314         $LastCnt = 2;
1315         $LastFreqBase = 0;
1316         $LastCmplScale = 1;
1317         $LastTimeBase = 0;
1318         $miny = -$MinClip;
1319         $maxy = 3 + $MinClip;
1320     }
1321     close(OUT);
1322     
1323     print "plot command input file created\n"
1324         if $verbose > 2;
1325         
1326         
1327     if (($fpos[$[] eq 'cnt' && scalar(@loffset) >= $cnt) ||
1328         ($fpos[$[] eq 'start' && $mintime <= $StartTime) ||
1329         ($fpos[$[] eq 'end'))
1330     {
1331         return ($fpos[$[],$filekey[$[],$loffset[$[]);
1332     }
1333     else                        # found to few lines - next time start search earlier in file
1334     {
1335         if ($fpos[$[] eq 'start')
1336         {
1337             ;# the timestamps we got for F_first and F_last guaranteed
1338             ;# that no file is left out
1339             ;# the only thing that could happen is:
1340             ;# we guessed the starting point wrong
1341             ;# compute a new guess from the first record found
1342             ;# if this equals our last guess use data of first record
1343             ;# otherwise try new guess
1344             
1345             if ($fpos[$[+1] eq $filekey[$[] && $loffset[$[] > $fpos[$[+2])
1346             {
1347                 local($noff);
1348                 $noff = $loffset[$[] - ($cnt - @loffset + 1) * $RecordSize;
1349                 $noff = 0 if $noff < 0;
1350                 
1351                 return (@fpos[$[,$[+1], ($noff == $fpos[$[+2]) ? $loffset[$[] : $noff);
1352             }
1353             return ($fpos[$[],$filekey[$[],$loffset[$[]);
1354         }
1355         elsif ($fpos[$[] eq 'end' || $fpos[$[] eq 'cnt')
1356         {
1357             ;# try to start earlier in file
1358             ;# if we already started at the beginning
1359             ;# try to use previous file
1360             ;# this assumes distance to better starting point is at most one file
1361             ;# the primary guess at top of genfile() should usually allow this
1362             ;# assumption
1363             ;# if the offset of the first sample used is within 
1364             ;# a different file than we guessed it must have occurred later
1365             ;# in the sequence of files
1366             ;# this only can happen if our starting file did not contain
1367             ;# a valid sample from the starting point we guessed
1368             ;# however this does not invalidate our assumption, no check needed
1369             local($noff,$key);
1370             if ($fpos[$[+2] > 0)
1371             {
1372                 $noff = $fpos[$[+2] - $RecordSize * ($cnt - @loffset + 1);
1373                 $noff = 0 if $noff < 0;
1374                 return (@fpos[$[,$[+1],$noff);
1375             }
1376             else
1377             {
1378                 if ($fpos[$[+1] eq $F_files[$[])
1379                 {
1380                     ;# first file - and not enough samples
1381                     ;# use data of first sample
1382                     return ($fpos[$[], $filekey[$[], $loffset[$[]);
1383                 }
1384                 else
1385                 {
1386                     ;# search key of previous file
1387                     $key = $F_files[$[];
1388                     @F = reverse(@F_files);
1389                     while ($_ = shift(@F))
1390                     {
1391                         if ($_ eq $fpos[$[+1])
1392                         {
1393                             $key = shift(@F) if @F;
1394                             last;
1395                         }
1396                     }
1397                     $noff = int($F_size{$key} / $RecordSize);
1398                     $noff -= $cnt - @loffset;
1399                     $noff = 0 if $noff < 0;
1400                     $noff *= $RecordSize;
1401                     return ($fpos[$[], $key, $noff);
1402                 }
1403             }
1404         }
1405         else
1406         {
1407             return ();
1408         }
1409         
1410         return 0 if @loffset <= 1 || ($loffset[$#loffset] - $loffset[$[]) <= 1;
1411         
1412         ;# EOF - 1.1 * avg(line) * $cnt
1413         local($val) =  $loffset[$#loffset]
1414             - $cnt * 11 * (($loffset[$#loffset] - $loffset[$[]) / @loffset) / 10;
1415         return ($val < 0) ? 0 : $val;
1416     }
1417 }
1418
1419 $Ltime = -1 if ! defined($Ltime);
1420 $LastFreqBase = 0;
1421 $LastFreqBaseString = "??";
1422
1423 ;# initial setup of plot
1424 print "initialize plotting\n" if $verbose;
1425 if (defined($PrintIt))
1426 {
1427   if ($PrintIt =~ m,/,)
1428   {
1429     print "Saving plot to file $PrintIt\n";
1430     print PLOT "set output '$PrintIt'\n";
1431   }
1432   else
1433   {
1434     print "Printing plot on printer $PrintIt\n";
1435     print PLOT "set output '| lpr -P$PrintIt -h'\n";
1436   }
1437   print PLOT "set terminal postscript landscape color solid 'Helvetica' 10\n";
1438 }
1439 print PLOT "set grid\n";
1440 print PLOT "set tics out\n";
1441 print PLOT "set format y '%g '\n";
1442 printf PLOT "set time 47\n" unless defined($PrintIt);
1443
1444 @filepos =();
1445 while(1)
1446 {
1447   print &ctime(time) if $verbose;
1448
1449   ;# update diplay characteristics
1450   &read_config;# unless defined($PrintIt);
1451
1452   unlink($tmpfile);
1453   my $lo = lr->new();
1454   my $lf = lr->new();
1455     
1456   @filepos = &genfile($samples,$srcprefix,$tmpfile,$lo,$lf,@filepos);
1457
1458   ;# make plotcmd display samples
1459   make_doplot($lo, $lf);
1460   print "Displaying plot...\n" if $verbose > 1;
1461   print "command for plot sub process:\n$doplot----\n" if $verbose > 3;
1462   print PLOT $doplot;
1463 }
1464 continue
1465 {
1466   if (defined($PrintIt))
1467   {
1468     delete $SIG{'CHLD'};
1469     print PLOT "quit\n";
1470     close(PLOT);
1471     if ($PrintIt =~ m,/,)
1472     {
1473       print "Plot saved to file $PrintIt\n";
1474     }
1475     else
1476     {
1477       print "Plot spooled to printer $PrintIt\n";
1478     }
1479     unlink($tmpfile);
1480     exit(0);
1481   }
1482   ;# wait $delay seconds
1483   print "waiting $delay seconds ..." if $verbose > 2;
1484   sleep($delay);
1485   print " continuing\n" if $verbose > 2;
1486   undef($LastFreqBaseString);
1487 }
1488
1489
1490 sub date_time_spec2seconds
1491 {
1492     local($_) = @_;
1493     ;# a date_time_spec consistes of:
1494     ;#  YYYY-MM-DD_HH:MM:SS.ms
1495     ;# values can be omitted from the beginning and default than to
1496     ;# values of current date
1497     ;# values omitted from the end default to lowest possible values
1498
1499     local($time) = time;
1500     local($sec,$min,$hour,$mday,$mon,$year)
1501         = localtime($time);
1502
1503     local($last) = ();
1504
1505     s/^\D*(.*\d)\D*/$1/;        # strip off garbage
1506
1507   PARSE:
1508     {
1509         if (s/^(\d{4})(-|$)//)
1510         {
1511             if ($1 < 1970)
1512             {
1513                 warn("$0: can not handle years before 1970 - year $1 ignored\n");
1514                 return undef;
1515             }
1516             elsif ( $1 >= 2070)
1517             {
1518                 warn("$0: can not handle years past 2070 - year $1 ignored\n");
1519                 return undef;
1520             }
1521             else
1522             {
1523                 $year = $1 % 100; # 0<= $year < 100
1524                                  ;# - interpreted 70 .. 99,00 .. 69
1525             }
1526             $last = $[ + 5;
1527             last PARSE if $_ eq '';
1528             warn("$0: bad date_time_spec: \"$_\" found after YEAR\n"),
1529             return(undef)
1530                 if $2 eq '';
1531         }
1532
1533         if (s/^(\d{1,2})(-|$)//)
1534         {
1535             warn("$0: implausible month $1\n"),return(undef)
1536                 if $1 < 1 || $1 > 12;
1537             $mon = $1 - 1;
1538             $last = $[ + 4;
1539             last PARSE if $_ eq '';
1540             warn("$0: bad date_time_spec: \"$_\" found after MONTH\n"),
1541             return(undef)
1542                 if $2 eq '';
1543         }
1544         else
1545         {
1546             warn("$0: bad date_time_spec \"$_\"\n"),return(undef)
1547                 if defined($last);
1548             
1549         }
1550
1551         if (s/^(\d{1,2})([_ ]|$)//)
1552         {
1553             warn("$0: implausible month day $1 for month ".($mon+1)." (".
1554                  $MaxNumDaysPerMonth[$mon].")$mon\n"),
1555             return(undef)
1556                 if $1 < 1 || $1 > $MaxNumDaysPerMonth[$mon];
1557             $mday = $1;
1558             $last = $[ + 3;
1559             last PARSE if $_ eq '';
1560             warn("$0: bad date_time_spec \"$_\" found after MDAY\n"),
1561             return(undef)
1562                 if $2 eq '';
1563         }
1564         else
1565         {
1566             warn("$0: bad date_time_spec \"$_\"\n"), return undef
1567                 if defined($last);
1568         }
1569
1570         ;# now we face a problem:
1571         ;# if ! defined($last) a prefix of "07:"
1572         ;# can be either 07:MM or 07:ss
1573         ;# to get the second interpretation make the user add
1574         ;# a msec fraction part and check for this special case
1575         if (! defined($last) && s/^(\d{1,2}):(\d{1,2}\.\d+)//)
1576         {
1577             warn("$0: implausible minute $1\n"), return undef
1578                 if $1 < 0 || $1 >= 60;
1579             warn("$0: implausible second $1\n"), return undef
1580                 if $2 < 0 || $2 >= 60;
1581             $min = $1;
1582             $sec = $2;
1583             $last = $[ + 1;
1584             last PARSE if $_ eq '';
1585             warn("$0: bad date_time_spec \"$_\" after SECONDS\n");
1586             return undef;
1587         }
1588         
1589         if (s/^(\d{1,2})(:|$)//)
1590         {
1591             warn("$0: implausible hour $1\n"), return undef
1592                 if $1 < 0 || $1 > 24;
1593             $hour = $1;
1594             $last = $[ + 2;
1595             last PARSE if $_ eq '';
1596             warn("$0: bad date_time_spec found \"$_\" after HOUR\n"),
1597             return undef
1598                 if $2 eq '';
1599         }
1600         else
1601         {
1602             warn("$0: bad date_time_spec \"$_\"\n"), return undef
1603                 if defined($last);
1604         }
1605
1606         if (s/^(\d{1,2})(:|$)//)
1607         {
1608             warn("$0: implausible minute $1\n"), return undef
1609                 if $1 < 0 || $1 >=60;
1610             $min = $1;
1611             $last = $[ + 1;
1612             last PARSE if $_ eq '';
1613             warn("$0: bad date_time_spec found \"$_\" after MINUTE\n"),
1614             return undef
1615                 if $2 eq '';
1616         }
1617         else
1618         {
1619             warn("$0: bad date_time_spec \"$_\"\n"), return undef
1620                 if defined($last);
1621         }
1622
1623         if (s/^(\d{1,2}(\.\d+)?)//)
1624         {
1625             warn("$0: implausible second $1\n"), return undef
1626                 if $1 < 0 || $1 >=60;
1627             $sec = $1;
1628             $last = $[;
1629             last PARSE if $_ eq '';
1630             warn("$0: bad date_time_spec found \"$_\" after SECOND\n");
1631             return undef;
1632         }
1633     }
1634
1635     return $time unless defined($last);
1636
1637     $sec  = 0 if $last > $[;
1638     $min  = 0 if $last > $[ + 1;
1639     $hour = 0 if $last > $[ + 2;
1640     $mday = 1 if $last > $[ + 3;
1641     $mon  = 0 if $last > $[ + 4;
1642     local($rtime) = &timelocal($sec,$min,$hour,$mday,$mon,$year, 0,0, 0);
1643
1644     ;# $rtime may be off if daylight savings time is in effect at given date
1645     return $rtime + ($sec - int($sec))
1646         if $hour == (localtime($rtime))[$[+2];
1647     return
1648         &timelocal($sec,$min,$hour,$mday,$mon,$year, 0,0, 1)
1649             + ($sec - int($sec));
1650 }
1651
1652
1653 sub min
1654 {
1655   local($m) = shift;
1656
1657   grep((($m > $_) && ($m = $_),0),@_);
1658   $m;
1659 }
1660
1661 sub max
1662 {
1663   local($m) = shift;
1664
1665   grep((($m < $_) && ($m = $_),0),@_);
1666   $m;
1667 }