--- before.pl Fri Oct 1 21:20:03 2004 +++ after.pl Fri Oct 1 21:19:27 2004 @@ -1,19 +1,18 @@ #!/usr/local/bin/perl -# $Id: daily_dhcp_summary.pl,v 1.9 2004/10/01 19:14:09 xxxxxxxx for-review $ +# $Id: sum.pl,v 1.6 2004/10/02 01:12:03 mjd Exp mjd $ # =========================================================================== use strict; -use Carp; +use Carp 'confess'; use Getopt::Long; use File::ReadBackwards; use Getopt::Long; use Time::Local; -my (%Snetworks, %Sbuilding, %Ssize); # used globally -my $TopOfPage; # global -our($network, $building, $avg, $min, $max, $size); # for format +my $DEFAULT_INPUT = '/usr/local/etc/dhcp-tools/report/netdat.all-3600/allinput'; -my(%Opts, $DBG); +my $DBG; +my $SEC_PER_DAY = 86_400; my $INVOKEMAIL = '/usr/sbin/sendmail -oi'; @@ -27,7 +26,7 @@ sub help(;$) { my $msg = shift; - my $version = substr('$Id: daily_dhcp_summary.pl,v 1.9 2004/10/01 19:14:09 xxxxxxxx for-review $',0,79); + my $version = substr('$Id: sum.pl,v 1.6 2004/10/02 01:12:03 mjd Exp mjd $',0,79); my $delimit = "-" x length($version); $msg = $msg? "\n$delimit\n$msg": ""; print STDERR < 0 or $daysback < -360; - - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $t; - $year += 1900; # year is based on 1900 - $yday += 1; # today's day no. (yday is zero-based) - - $yday += $daysback; # note, can't go back more than one year - if ($yday <= 0) { # if last year if non-positive - $year -= 1; - $yday += (365 + ($year % 4? 0: 1)); # add one for leap year - } - return sprintf("%04d-%03d", $year, $yday); + my $t = shift; + # YYYY-DDD + return strftime("%Y-%j", localtime($t)); } -sub get_search_area($) { +sub get_search_area($$) { + my $file = shift; my $setdate = shift; dbgscan("looking for: $setdate"); # # eg: year (e.g: 2004), and yday (e.g: Jan 1 is 1, 09/29/04 is 273) # - my $finame = $Opts{file}; - my $rio = File::ReadBackwards->new($finame); - die "couldn't create obj" if !defined $rio; - + my $rio = File::ReadBackwards->new($file) + or die "couldn't open file '$file': $!"; - my $action = "initial"; - dbgscan("action=$action"); - my $reposition; # initially undefined, will be reposition to start of search - my($recdate, $lastrec, $lastdate, $lastpos); - my $n=0; + my $reposition; # position of start of region of interest while($_ = $rio->readline) { - next if !/^DATE (\d{4}-\d{3})-/; # section start - $recdate = $1; - dbgscan("hdrrec: recdate=$recdate, line follows:", $_, "---"); - if ($action eq "initial" && $recdate le $setdate) { - $lastrec = $_; - $lastdate = $recdate; - $lastpos = $rio->tell; - $action = "scanstart"; - dbgscan("nnl", "initial reset to scanstart", "lastrec=$lastrec", - "lastdate=$lastdate, lastpos=$lastpos") - } elsif ($action eq "scanstart") { - dbgscan("nnl", "action=scanstart, recdate=$recdate, lastdate=$lastdate"); - if ($recdate lt $lastdate) { - $reposition = $lastpos; - dbgscan("nnl", "action=scanstart, recdate < lastdate, " . - "reposition=$reposition; LAST"); - last; - } else { - $lastpos = $rio->tell; - dbgscan("nnl", "action=scanstart, continues with lastpos=$lastpos"); - } + if (/^DATE (\d{4}-\d{3})-/) { # section start + last if $1 lt $setdate; + if ($1 eq $setdate) { + $reposition = $rio->tell; + } } } - dbgscan("done scanning backwards"); + if (!defined $reposition) { - dbgscan("reposition undefined", "action: $action"); - if ($action eq "initial") { - die "all recs > date"; - } else { - dbgscan("reposition reset to start of file"); - $reposition = 0; - } - } else { - dbgscan("reposition had been set to $reposition"); + dbgscan("reposition undefined"); + die "all recs > date"; } + dbgscan("finished first pass"); $rio->close; - return $reposition, $lastrec; + open my($f), "<", $file + or die "Couldn't open file '$file': $!; aborting"; + seek $f, $reposition, 0; + return $f; } sub main() { - local(*F); # read options + my %Opts = (dbg => 0, file => $DEFAULT_INPUT, email => []); Getopt::Long::Configure(qw(ignore_case nopass_through auto_abbrev)); - die "\nGetOptions is unhappy" if - !GetOptions(\%Opts, qw(dbg:i - help|h|? - file:s - use-date:s - email|msg|mail=s@)); + help() unless GetOptions(\%Opts, qw(dbg:i + help|h|? + file:s + use-date:s + email|msg|mail=s@)); help() if $Opts{help}; - $DBG = $Opts{dbg} || 0; + $DBG = $Opts{dbg}; die "illegal dbg value, must be either unspecifed or 0, 1 or 2" if $DBG !~ /^[0-2]$/; - # use default if not specified - $Opts{file} = '/usr/local/etc/dhcp-tools/report/netdat.all-3600/allinput' - if !$Opts{file}; - # simple sanity checks on input file - - my $file = $Opts{file}; - die "file $file doesn't exist" if !-f $file; + my $file = $Opts{file}; + die "file $file doesn't exist" if !-e $file; die "file $file not text" if !-T $file; die "file $file empty" if -z $file; - open(F, $file) or die "can't open file $file: $!"; - close(F) or die "can't close file $file: $!"; - - # need at least one email address (no syntax checks) - - my(@mailto) = @{$Opts{email}} if defined $Opts{email}; - die "no email addresses have been specified" if ! @mailto; - my $mailto = join(", ", @mailto); my $setdate = $Opts{'use-date'}; + my $settime; if ($setdate) { # date sanity check my($setyr, $setmo, $setday); @@ -197,162 +147,118 @@ if $setmo < 1 or $setmo > 12; die "invalid date fmt (not yyyy/mm/dd); bad day" if $setmo < 1 or $setmo > 31; - my $t = timelocal(0,0,0,$setday,$setmo-1,$setyr-1900); - $setdate = get_yr_yday($t); # get specified time's year/day + $settime = timelocal(0,0,0,$setday,$setmo-1,$setyr-1900); } else { - $setdate = get_yr_yday(time, -1); # get yesterday's year/day + $settime = time() - $SEC_PER_DAY; # get yesterday's year/day + } + $setdate = get_yr_yday($settime); # get specified time's year/day + + { + # need at least one email address (no syntax checks) + # die "no email addresses have been specified" if ! @mailto; + my $mailto = join(", ", @{$Opts{email}}); + + if ($mailto) { + open(MAIL, "| $INVOKEMAIL $mailto") + or die "can't open $INVOKEMAIL: $!\n"; + print MAIL "Subject: DHCP Daily Summary\n"; + print MAIL "From: DHCP-Summary-Robot\n"; + print MAIL "\n"; + } else { + open MAIL, ">&STDOUT" or die "Can't dup STDOUT\n"; + } } - # scan backwards thru the file looking for the starting header + # scan backwards through the file looking for the starting header # containing the (earliest) specified yyyy/ddd (year & yday); # returns seek address to reposition file for reading, and the # last header record to include in the report. - my($reposition, $lastrec) = get_search_area($setdate); + my $fh = get_search_area($Opts{file}, $setdate); - open(MAIL, "| $INVOKEMAIL $mailto | tee ") - or die "can't open sendmail: $!\n"; - - select(MAIL); - $= = 9999; # set a very large no. lines per FORMAT'd page so it - # only does one section line per report - - print MAIL "Subject: DHCP Daily Summary\n"; - print MAIL "From: DHCP-Summary-Robot\n"; - print MAIL "\n"; - - open(F, $file) or die "couldn't open file $file: $!"; - seek(F, $reposition, 0); # position to data range start - my $stop_next_hdr = 0; - - my $state = 0; - my ($year, $yday, $timestamp, $txtday, $time); - %Snetworks = %Sbuilding = %Ssize = (); - my $daterecno = 0; - my $relno = -1; - while() { - if (/^D/) { - $relno += 1; - my $endpos = tell(F); - dbgscan("FOR REPORT, relative line no. $relno, endpos: $endpos", - $_,"---"); - if ($stop_next_hdr) { - dbgscan("nnl", - "FOR REPORT, PAST END OF RANGE, END PROCESSING"); - last; + my (%Snetworks, %Sbuilding); + while(<$fh>) { + if (/^DATE \s (\d{4}-\d{3})-.*?/x) { + last if $1 gt $setdate; # past the area of interest + $_ = <$fh> until /^ -----/; # Discard report header + + while (<$fh>) { + last if /^ -----/; # end of report + chomp; + my ($used, $size, $network, $building) = + (split(' ', $_, 7))[0,4,5,6]; + push(@{$Snetworks{$network}}, [$used, $size]) + unless $size == 0; # should never happen + $Sbuilding{$network} ||= $building; } } - if ($_ eq $lastrec) { # ok, read last valid header in range; - # bail at next header - $stop_next_hdr = 1; - dbgscan("nnl", "FOR REPORT, THIS IS THE LAST VALID HEADER"); - } - - chomp; - if (/^DATE \s (\d{4})-(\d{3})-.*? \s => \s (\d+) \s at \s - (.+?) \s (\d\d:\S+)/x) { - summarize($year, $txtday) if defined($yday) and $2 ne $yday; - ($year, $yday, $timestamp, $txtday, $time) = ($1, $2, $3, $4); - $state = 1; - $txtday =~ s/ +/ /g; - $daterecno = $.; - #print "year=($year), yday=($yday), txtday=($txtday), time=($time)\n"; - } elsif ($state == 1) { - if ($_ ne " Used %Used Aband %Aban Size Network Building") { - if ($. - $daterecno > 10) { - err("state==1 but line $./$daterecno not 'Used ...'"); - } else { - next; # keep looking - } - } - $state = 2; - } elsif ($state == 2) { - err("state==2 but line not '---- ...'") if $_ ne " ----- ----- ----- ----- ----- ------------------- ----------------------------"; - $state = 3; - } elsif ($state == 3) { - if (/^ -----/) { - $state = 0; - next; - } - my ($used, $perc, $size, $network, $building) = - (split(' ', $_, 7))[0,1,4,5,6]; - push(@{$Snetworks{$network}}, [$timestamp, $used, $perc, $size]); - $Sbuilding{$network} = $building; - $Ssize{$network} = $size; - } } - summarize($year, $txtday); - close(F); + print_report_header(\*MAIL, $settime); + print_report(\*MAIL, \%Snetworks, \%Sbuilding); + close(MAIL) or die "can't close external mail cmd: $!\n"; } -sub summarize { - my($year, $txtday) = @_; - my %Summary = (); - return if !scalar keys %Snetworks; - - my @netkeys = sort {$Sbuilding{$a} cmp $Sbuilding{$b} or $a cmp $b} - keys %Snetworks; - dbg("1 ** $year, $txtday"); - eval(<{$a} cmp $building->{$b} + or $a cmp $b} + keys %$usage; - confess "couldn't eval TopOfPage\n===\n$TopOfPage\n===\n: $@" if $@ ne ""; - $- = 0; # force formfeed for my $network (@netkeys) { - my @info = @{$Snetworks{$network}}; - my ($min, $max, $average); - my ($sumperc, $count) = (0, 0); - for (@info) { - my ($timestamp, $used, $perc, $size) = @$_; - dbg("2 ** network=($network), timestamp=($timestamp), used=($used), perc=($perc), size=($size)"); - - next if !$size; # should never happen, but would - # give division by zero if it did - # ecompute perc (get more accuracy) - $perc = 100 * $used / $size; - $min = $perc if ! defined $min or $perc < $min; - $max = $perc if ! defined $max or $perc > $max; + my $count = @{$usage->{$network}}; + my ($min, $max, $average) = (100, 0); + my $sumperc = 0; + my $last_size; + for (@{$usage->{$network}}) { + my ($used, $size) = @$_; + dbg("2 ** network=($network), used=($used), size=($size)"); + + # recompute perc (get more accuracy) + $last_size = $size; + my $perc = 100 * $used / $size; + $min = $perc if $perc < $min; + $max = $perc if $perc > $max; $sumperc += $perc; - ++$count; } - if (!$count) { + + if ($count == 0) { $min = $max = $average = "----"; } else { $average = $sumperc / $count; for ($min, $max, $average) { - $_ = sprintf("%3d%%", int(0.5 + $_)); + $_ = sprintf("%3.0f%%", int(0.5 + $_)); } } - dbg("3 ** network=($network): count=($count), sumperc=($sumperc), min=($min), max=($max), average=($average)"); - $Summary{$network} = [$min, $max, $average]; - } - $- = 0; - for(@netkeys) { - local($network, $building, $avg, $min, $max, $size); - $network = $_; - $building = $Sbuilding{$_}; - ($min, $max, $avg) = @{$Summary{$_}}; - $size = $Ssize{$_}; - write MAIL; + dbg("3 ** network=($network): count=($count) sumperc=($sumperc), min=($min), max=($max), average=($average)"); + + printf MAIL "%-19s %-28s %-4s %-4s %-4s %6d\n", + $network, $building->{$network}, + $average, $min, $max, $last_size; } - close(MAIL) or die "can't close external mail cmd: $!\n"; + } main(); - -format MAIL = -^<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<< ^>>> ^>>> ^>>> ^>>>>> -$network, $building, $avg, $min, $max, $size -. - format STDERR = scan: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<