#!/usr/local/bin/perl
# $Id: sum.pl,v 1.6 2004/10/02 01:12:03 mjd Exp mjd $
# ===========================================================================

use strict;
use Carp 'confess';
use Getopt::Long;
use File::ReadBackwards;
use Getopt::Long;
use Time::Local;

my $DEFAULT_INPUT = '/usr/local/etc/dhcp-tools/report/netdat.all-3600/allinput';

my $DBG;
my $SEC_PER_DAY = 86_400;

my $INVOKEMAIL = '/usr/sbin/sendmail -oi';

sub dbg($) {
    print STDERR "$_[0]\n" if $DBG==2;
}

sub err($) {
    confess "ERR: $_[0] at input line no. $.:\n$_\n";
}

sub help(;$) {
    my $msg = shift;
    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 <<HELP;

Runtime options:
$delimit
$version
$delimit
dbg  : turn on debug mode, copious output to STDERR (default: OFF)
       write debug output to STDERR. Default is 0 (off), 1 (some output),
       2 (a lot). All lines for dbg=1 start w/ 'scan:', and search/process
       the header lines ( =~ /^D/ ) in file.
help : shows this help
file : if present, specifies input file. Default:
       $DEFAULT_INPUT
use-date: by default, just yesterday's stats. Can set a specific date, as
          yyyy/mm/dd.
email: can appear one or more times. Specify recipient email address.
$msg
HELP
    exit 1;
}

our(@show);
sub dbgscan(@) {
    return if !$DBG;
    local(@show) = @_;
    return if ! scalar @show;
    if ($show[0] eq "nnl")  { # nnl = NoNewLine
        shift @show;
    } else {
        print STDERR "\n";
    }
    s/\s*$// for(@show);
    write STDERR for(@show);
}


use POSIX 'strftime';
sub get_yr_yday($) {
    # inputs: unixtime stamp (eg: time());

    my $t = shift;
    # YYYY-DDD
    return strftime("%Y-%j", localtime($t));
}

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 $rio = File::ReadBackwards->new($file)
      or die "couldn't open file '$file': $!";

    my $reposition;  #  position of start of region of interest
    while($_ = $rio->readline) {
        if (/^DATE (\d{4}-\d{3})-/) { # section start
          last if $1 lt $setdate;
          if ($1 eq $setdate) {
            $reposition = $rio->tell;
          } 
        }
    }

    if (!defined $reposition) {
        dbgscan("reposition undefined");
        die "all recs > date";
    }

    dbgscan("finished first pass");
    $rio->close;

    open my($f), "<", $file 
      or die "Couldn't open file '$file': $!; aborting";
    seek $f, $reposition, 0;
    return $f;
}

sub main() {

    # read options

    my %Opts = (dbg => 0, file => $DEFAULT_INPUT, email => []);
    Getopt::Long::Configure(qw(ignore_case  nopass_through  auto_abbrev));
    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};
    die "illegal dbg value, must be either unspecifed or 0, 1 or 2"
        if $DBG !~ /^[0-2]$/;

    # simple sanity checks on input 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;

    my $setdate = $Opts{'use-date'};
    my $settime;
    if ($setdate) { # date sanity check
        my($setyr, $setmo, $setday);

        die "invalid date fmt (not yyyy/mm/dd)"
            if $setdate !~ m#^(\d{4})/(\d\d)/(\d\d)$#;
        ($setyr, $setmo, $setday) = ($1, $2, $3);

        die "invalid date fmt (not yyyy/mm/dd); bad year"
            if $setyr < 2004 or $setyr > 2200;
        die "invalid date fmt (not yyyy/mm/dd); bad month"
            if $setmo < 1 or $setmo > 12;
        die "invalid date fmt (not yyyy/mm/dd); bad day"
            if $setmo < 1 or $setmo > 31;
        $settime = timelocal(0,0,0,$setday,$setmo-1,$setyr-1900);
    } else {
        $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 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 $fh = get_search_area($Opts{file}, $setdate);

    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;
            }
        }
    }
    print_report_header(\*MAIL, $settime);
    print_report(\*MAIL, \%Snetworks, \%Sbuilding);
    close(MAIL) or die "can't close external mail cmd: $!\n";
}

sub print_report_header {
    my $fh = shift;
    my $when = shift;
    my ($date) = (strftime("%a %b %e, %Y", localtime($when)));
    $date =~ tr/ //s;

    print $fh <<TOP_OF_PAGE;

             * DHCP NETWORK SUMMARY FOR $date *

Network              Building                      avg%  min%  max%  #Nodes
-------------------  ----------------------------  ----  ----  ----  ------
TOP_OF_PAGE
}

sub print_report {
    my $fh = shift;
    my ($usage, $building) = @_;

    return unless %$usage;

    my @netkeys = sort {$building->{$a} cmp $building->{$b} 
                        or           $a cmp $b}
            keys %$usage;

    for my $network (@netkeys) {
        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;
        }

        if ($count == 0) {
            $min = $max = $average = "----";
        } else {
            $average = $sumperc / $count;
            for ($min, $max, $average) {
                $_ = sprintf("%3.0f%%", int(0.5 + $_));
            }
        }
        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;
    }

}


main();

format STDERR =
scan: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$_
scan:     ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
$_
.
