1#! @PATH_PERL@ -w 2# $Id$ 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 22require 5.003; # "never tested with any other version of Perl" 23use strict; 24 25use Getopt::Long; 26 27my $log_date_pattern = '[12]\d{3}[01]\d[0-3]\d'; 28my $statsdir = "/var/log/ntp"; # directory with input files 29my $outputdir = "/tmp"; # directory for output files 30my $skip_time_steps = 3600.0; # ignore time offsets larger that this 31my $startdate = "19700101"; # first data file to use (YYYYMMDD) 32my $enddate=`date -u +%Y%m%d`; chomp $enddate; --$enddate; 33my $peer_dist_limit = 400.0; 34 35my %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 42if ( !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 60die "$statsdir: no such directory" unless (-d $statsdir); 61die "$outputdir: no such directory" unless (-d $outputdir); 62die "$skip_time_steps: skip-time-steps must be positive" 63 unless ($skip_time_steps >= 0.0); 64die "$startdate: invalid start date|$`|$&|$'" 65 unless ($startdate =~ m/.*$log_date_pattern$/); 66die "$enddate: invalid end date" 67 unless ($enddate =~ m/.*$log_date_pattern$/); 68 69$skip_time_steps = 0.128 if ($skip_time_steps == 0); 70 71sub min 72{ 73 my ($result, @rest) = @_; 74 map { $result = $_ if ($_ < $result) } @rest; 75 return($result); 76} 77 78sub 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 86sub 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 155sub 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 247sub 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 258sub 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 331my $loop_summary="$outputdir/loop_summary"; 332my $peer_summary="$outputdir/peer_summary"; 333my $clock_summary="$outputdir/clock_summary"; 334my (@loopfiles, @peerfiles, @clockfiles); 335 336print STDERR "Creating summaries from $statsdir ($startdate to $enddate)\n"; 337 338opendir SDIR, $statsdir or die "directory ${statsdir}: $!"; 339rewinddir SDIR; 340@loopfiles=sort grep /loop.*$log_date_pattern/, readdir SDIR; 341rewinddir SDIR; 342@peerfiles=sort grep /peer.*$log_date_pattern/, readdir SDIR; 343rewinddir SDIR; 344@clockfiles=sort grep /clock.*$log_date_pattern/, readdir SDIR; 345closedir SDIR; 346 347# remove old summary files 348map { unlink $_ if -f $_ } ($loop_summary, $peer_summary, $clock_summary); 349 350my $date; 351map { 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 358map { 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 365map { 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 372print STDERR "Creating peer summary with limit $peer_dist_limit\n"; 373peer_summary $peer_summary if (-f $peer_summary); 374