Merge from vendor branch LIBPCAP:
[dragonfly.git] / contrib / ntp / scripts / summary.pl
1 #!/usr/bin/perl -w
2 # $Id: summary.pl,v 1.2 1999/12/02 01:59:07 stenn Exp $
3 # Perl version of (summary.sh, loop.awk, peer.awk):
4 # Create summaries from xntpd's loop and peer statistics.
5 #
6 # Copyright (c) 1997, 1999 by Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 require 5.003; # "never tested with any other version of Perl"
23 use strict;
24
25 use Getopt::Long;
26
27 my $log_date_pattern = '[12]\d{3}[01]\d[0-3]\d';
28 my $statsdir = "/var/log/ntp";          # directory with input files
29 my $outputdir = "/tmp";                 # directory for output files
30 my $skip_time_steps = 3600.0;           # ignore time offsets larger that this
31 my $startdate = "19700101";             # first data file to use (YYYYMMDD)
32 my $enddate=`date -u +%Y%m%d`; chomp $enddate; --$enddate;
33 my $peer_dist_limit = 400.0;
34
35 my %options = ("directory|input-directory=s" => \$statsdir,
36                "output-directory=s" => \$outputdir,
37                "skip-time-steps:f" => \$skip_time_steps,
38                "start-date=s" => \$startdate,
39                "end-date=s" => \$enddate,
40                "peer-dist-limit=f" => \$peer_dist_limit);
41
42 if ( !GetOptions(%options) )
43 {
44     print STDERR "valid options for $0 are:\n";
45     my $opt;
46     foreach $opt (sort(keys %options)) {
47         print STDERR "\t--$opt\t(default is ";
48         if ( ref($options{$opt}) eq "ARRAY" ) {
49             print STDERR join(", ",  map { "'$_'" } @{$options{$opt}});
50         } else {
51             print STDERR "'${$options{$opt}}'";
52         }
53         print STDERR ")\n";
54     }
55     print STDERR "\n";
56     die;
57 }
58
59 # check possibly current values of options
60 die "$statsdir: no such directory" unless (-d $statsdir);
61 die "$outputdir: no such directory" unless (-d $outputdir);
62 die "$skip_time_steps: skip-time-steps must be positive"
63     unless ($skip_time_steps >= 0.0);
64 die "$startdate: invalid start date|$`|$&|$'"
65     unless ($startdate =~ m/.*$log_date_pattern$/);
66 die "$enddate: invalid end date"
67     unless ($enddate =~ m/.*$log_date_pattern$/);
68
69 $skip_time_steps = 0.128 if ($skip_time_steps == 0);
70
71 sub min
72 {
73     my ($result, @rest) = @_;
74     map { $result = $_ if ($_ < $result) } @rest;
75     return($result);
76 }
77
78 sub max
79 {
80     my ($result, @rest) = @_;
81     map { $result = $_ if ($_ > $result) } @rest;
82     return($result);
83 }
84
85 # calculate mean, range, and standard deviation for offset and frequency
86 sub do_loop
87 {
88     my ($directory, $fname, $out_file) = @_;
89     print "$directory/$fname\n";
90     open INPUT, "$directory/$fname" or warn "can't open $directory/$fname: $!";
91     open OUTPUT, ">>$out_file" or die "can't open $out_file: $!";
92     print OUTPUT "$fname\n";
93     my ($loop_tmax, $loop_fmax) = (-1e9, -1e9);
94     my ($loop_tmin, $loop_fmin) = (1e9, 1e9);
95     my ($loop_time_rms, $loop_freq_rms) = (0, 0);
96     my $loop_count = 0;
97     my $loop_time = 0;
98     my $loop_freq = 0;
99     my ($freq, $offs);
100     my @Fld;
101     while (<INPUT>) {
102         chop;   # strip record separator
103         @Fld = split;
104         next if ($#Fld < 4);
105 #NTPv3: 50529 74356.259 -0.000112 16.1230 8
106 #NTPv3: day, sec.msec, offset, drift_comp, sys_poll
107 #NTPv4: 51333 54734.582 0.000001648 16.981964 0.000001094 0.020938 6
108 #NTPv4: day, sec.msec, offset, drift_comp, sys_error, clock_stabil, sys_poll
109         if ($Fld[2] > $skip_time_steps || $Fld[2] < -$skip_time_steps) {
110             warn "ignoring loop offset $Fld[2] (file $fname, line $.)\n";
111             next
112         }
113         $loop_count++;
114         ($offs, $freq) = ($Fld[2], $Fld[3]);
115         $loop_tmax = max($loop_tmax, $offs);
116         $loop_tmin = min($loop_tmin, $offs);
117         $loop_fmax = max($loop_fmax, $freq);
118         $loop_fmin = min($loop_fmin, $freq);
119         $loop_time += $offs;
120         $loop_time_rms += $offs * $offs;
121         $loop_freq += $freq;
122         $loop_freq_rms += $freq * $freq;
123     }
124     close INPUT;
125     if ($loop_count > 1) {
126         $loop_time /= $loop_count;
127         $loop_time_rms = $loop_time_rms / $loop_count - $loop_time * $loop_time;
128         if ($loop_time_rms < 0) {
129             warn "loop_time_rms: $loop_time_rms < 0";
130             $loop_time_rms = 0;
131         }
132         $loop_time_rms = sqrt($loop_time_rms);
133         $loop_freq /= $loop_count;
134         $loop_freq_rms = $loop_freq_rms / $loop_count - $loop_freq * $loop_freq;
135         if ($loop_freq_rms < 0) {
136             warn "loop_freq_rms: $loop_freq_rms < 0";
137             $loop_freq_rms = 0;
138         }
139         $loop_freq_rms = sqrt($loop_freq_rms);
140         printf OUTPUT
141             ("loop %d, %.0f+/-%.1f, rms %.1f, freq %.2f+/-%0.3f, var %.3f\n",
142              $loop_count, ($loop_tmax + $loop_tmin) / 2 * 1e6,
143              ($loop_tmax - $loop_tmin) / 2 * 1e6, $loop_time_rms * 1e6,
144              ($loop_fmax + $loop_fmin) / 2, ($loop_fmax - $loop_fmin) / 2,
145              $loop_freq_rms);
146     }
147     else {
148         warn "no valid lines in $directory/$fname";
149     }
150     close OUTPUT
151 }
152
153 # calculate mean, standard deviation, maximum offset, mean dispersion,
154 # and maximum distance for each peer
155 sub do_peer
156 {
157     my ($directory, $fname, $out_file) = @_;
158     print "$directory/$fname\n";
159     open INPUT, "$directory/$fname" or warn "can't open $directory/$fname: $!";
160     open OUTPUT, ">>$out_file" or die "can't open $out_file: $!";
161     print OUTPUT "$fname\n";
162 # we toss out all distances greater than one second on the assumption the
163 # peer is in initial acquisition
164     my ($n, $MAXDISTANCE) = (0, 1.0);
165     my %peer_time;
166     my %peer_time_rms;
167     my %peer_count;
168     my %peer_delay;
169     my %peer_disp;
170     my %peer_dist;
171     my %peer_ident;
172     my %peer_tmin;
173     my %peer_tmax;
174     my @Fld;
175     my ($i, $j);
176     my ($dist, $offs);
177     while (<INPUT>) {
178         chop;   # strip record separator
179         @Fld = split;
180         next if ($#Fld < 6);
181 #NTPv3: 50529 83316.249 127.127.8.1 9674 0.008628 0.00000 0.00700
182 #NTPv3: day, sec.msec, addr, status, offset, delay, dispersion
183 #NTPv4: 51333 56042.037 127.127.8.1 94f5 -0.000014657 0.000000000 0.000000000 0.000013214
184 #NTPv4: day, sec.msec, addr, status, offset, delay, dispersion, skew
185
186         $dist = $Fld[6] + $Fld[5] / 2;
187         next if ($dist > $MAXDISTANCE);
188         $offs = $Fld[4];
189         if ($offs > $skip_time_steps || $offs < -$skip_time_steps) {
190             warn "ignoring peer offset $offs (file $fname, line $.)\n";
191             next
192         }
193         $i = $n;
194         for ($j = 0; $j < $n; $j++) {
195             if ($Fld[2] eq $peer_ident{$j}) {
196                 $i = $j;                # peer found
197                 last;
198             }
199         }
200         if ($i == $n) {         # add new peer
201             $peer_ident{$i} = $Fld[2];
202             $peer_tmax{$i} = $peer_dist{$i} = -1e9;
203             $peer_tmin{$i} = 1e9;
204             $peer_time{$i} = $peer_time_rms{$i} = 0;
205             $peer_delay{$i} = $peer_disp{$i} = 0;
206             $peer_count{$i} = 0;
207             $n++;
208         }
209         $peer_count{$i}++;
210         $peer_tmax{$i} = max($peer_tmax{$i}, $offs);
211         $peer_tmin{$i} = min($peer_tmin{$i}, $offs);
212         $peer_dist{$i} = max($peer_dist{$i}, $dist);
213         $peer_time{$i} += $offs;
214         $peer_time_rms{$i} += $offs * $offs;
215         $peer_delay{$i} += $Fld[5];
216         $peer_disp{$i} += $Fld[6];
217     }
218     close INPUT;
219     print OUTPUT
220 "       ident     cnt     mean     rms      max     delay     dist     disp\n";
221     print OUTPUT
222 "==========================================================================\n";
223     my @lines = ();
224     for ($i = 0; $i < $n; $i++) {
225         next if $peer_count{$i} < 2;
226         $peer_time{$i} /= $peer_count{$i};
227         eval { $peer_time_rms{$i} = sqrt($peer_time_rms{$i} / $peer_count{$i} -
228                                          $peer_time{$i} * $peer_time{$i}); };
229         $peer_time_rms{$i} = 0, warn $@ if $@;
230         $peer_delay{$i} /= $peer_count{$i};
231         $peer_disp{$i} /= $peer_count{$i};
232         $peer_tmax{$i} = $peer_tmax{$i} - $peer_time{$i};
233         $peer_tmin{$i} = $peer_time{$i} - $peer_tmin{$i};
234         if ($peer_tmin{$i} > $peer_tmax{$i}) {  # can this happen at all?
235             $peer_tmax{$i} = $peer_tmin{$i};
236         }
237         push @lines, sprintf
238             "%-15s %4d %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f\n",
239             $peer_ident{$i}, $peer_count{$i}, $peer_time{$i} * 1e3,
240             $peer_time_rms{$i} * 1e3, $peer_tmax{$i} * 1e3,
241             $peer_delay{$i} * 1e3, $peer_dist{$i} * 1e3, $peer_disp{$i} * 1e3;
242     }
243     print OUTPUT sort @lines;
244     close OUTPUT;
245 }
246
247 sub do_clock
248 {
249     my ($directory, $fname, $out_file) = @_;
250     print "$directory/$fname\n";
251     open INPUT, "$directory/$fname";
252     open OUTPUT, ">>$out_file" or die "can't open $out_file: $!";
253     print OUTPUT "$fname\n";
254     close INPUT;
255     close OUTPUT;
256 }
257
258 sub peer_summary
259 {
260     my $in_file = shift;
261     my ($i, $j, $n);
262     my (%peer_ident, %peer_count, %peer_mean, %peer_var, %peer_max);
263     my (%peer_1, %peer_2, %peer_3, %peer_4);
264     my $dist;
265     my $max;
266     open INPUT, "<$in_file" or die "can't open $in_file: $!";
267     my @Fld;
268     $n = 0;
269     while (<INPUT>) {
270         chop;   # strip record separator
271         @Fld = split;
272         next if ($#Fld < 7 || $Fld[0] eq 'ident');
273         $i = $n;
274         for ($j = 0; $j < $n; $j++) {
275             if ($Fld[0] eq $peer_ident{$j}) {
276                 $i = $j;
277                 last;                   # peer found
278             }
279         }
280         if ($i == $n) {                 # add new peer
281             $peer_count{$i} = $peer_mean{$i} = $peer_var{$i} = 0;
282             $peer_max{$i} = 0;
283             $peer_1{$i} = $peer_2{$i} = $peer_3{$i} = $peer_4{$i} = 0;
284             $peer_ident{$i} = $Fld[0];
285             ++$n;
286         }
287         $dist = $Fld[6] - $Fld[5] / 2;
288         if ($dist < $peer_dist_limit) {
289             $peer_count{$i}++;
290             $peer_mean{$i} += $Fld[2];
291             $peer_var{$i} += $Fld[3] * $Fld[3];
292             $max = $Fld[4];
293             $peer_max{$i} = max($peer_max{$i}, $max);
294             if ($max > 1) {
295                 $peer_1{$i}++;
296                 if ($max > 5) {
297                     $peer_2{$i}++;
298                     if ($max > 10) {
299                         $peer_3{$i}++;
300                         if ($max > 50) {
301                             $peer_4{$i}++;
302                         }
303                     }
304                 }
305             }
306         }
307         else {
308             warn "dist exceeds limit: $dist (file $in_file, line $.)\n";
309         }
310     }
311     close INPUT;
312     my @lines = ();
313     print
314         "       host     days    mean       rms       max   >1  >5 >10 >50\n";
315     print
316         "==================================================================\n";
317     for ($i = 0; $i < $n; $i++) {
318         next if ($peer_count{$i} < 2);
319         $peer_mean{$i} /= $peer_count{$i};
320         eval { $peer_var{$i} = sqrt($peer_var{$i} / $peer_count{$i} -
321                                     $peer_mean{$i} * $peer_mean{$i}); };
322         $peer_var{$i} = 0, warn $@ if $@;
323         push @lines, sprintf
324             "%-15s %3d %9.3f% 9.3f %9.3f %3d %3d %3d %3d\n",
325             $peer_ident{$i}, $peer_count{$i}, $peer_mean{$i}, $peer_var{$i},
326             $peer_max{$i}, $peer_1{$i}, $peer_2{$i}, $peer_3{$i}, $peer_4{$i};
327     }
328     print sort @lines;
329 }
330
331 my $loop_summary="$outputdir/loop_summary";
332 my $peer_summary="$outputdir/peer_summary";
333 my $clock_summary="$outputdir/clock_summary";
334 my (@loopfiles, @peerfiles, @clockfiles);
335
336 print STDERR "Creating summaries from $statsdir ($startdate to $enddate)\n";
337
338 opendir SDIR, $statsdir or die "directory ${statsdir}: $!";
339 rewinddir SDIR;
340 @loopfiles=sort grep /loop.*$log_date_pattern/, readdir SDIR;
341 rewinddir SDIR;
342 @peerfiles=sort grep /peer.*$log_date_pattern/, readdir SDIR;
343 rewinddir SDIR;
344 @clockfiles=sort grep /clock.*$log_date_pattern/, readdir SDIR;
345 closedir SDIR;
346
347 # remove old summary files
348 map { unlink $_ if -f $_ } ($loop_summary, $peer_summary, $clock_summary);
349
350 my $date;
351 map {
352     $date = $_; $date =~ s/.*($log_date_pattern)$/$1/;
353     if ($date ge $startdate && $date le $enddate) {
354         do_loop $statsdir, $_, $loop_summary;
355     }
356 } @loopfiles;
357
358 map {
359     $date = $_; $date =~ s/.*($log_date_pattern)$/$1/;
360     if ($date ge $startdate && $date le $enddate) {
361         do_peer $statsdir, $_, $peer_summary;
362     }
363 } @peerfiles;
364
365 map {
366     $date = $_; $date =~ s/.*($log_date_pattern)$/$1/;
367     if ($date ge $startdate && $date le $enddate) {
368         do_clock $statsdir, $_, $clock_summary;
369     }
370 } @clockfiles;
371
372 print STDERR "Creating peer summary with limit $peer_dist_limit\n";
373 peer_summary $peer_summary if (-f $peer_summary);