#!/usr/bin/perl
# nacctstats.pl v0.8 by Thomas Prokosch <thomas@nadev.net>
#
# This script analyses logfile from the net accounting daemon net-acctd.

require POSIX;
use Sys::Hostname;

# calculates the beginning of a day, in epoch format
# $_[0]: the time to process, in seconds
sub epochday {
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=gmtime($_[0]);
    ($_[0]-(($hour*60+$min)*60+$sec))/(24*3600);
}

# converts the unix epoch time format into human readable form
# $_[0]: the time to represent, in seconds
sub printdatetime {
    my @monids=("Jan", "Feb", "Mar", "Apr", "May", "Jun",
                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($_[0]);
    $year+=1900;
    if ($_[1] eq date) {
        sprintf("%2d-%s-%d", $mday, $monids[$mon], $year);
    } elsif ($_[1] eq short) {
        sprintf("%2d-%s", $mday, $monids[$mon]);
    } else {
        sprintf("%2d-%s-%d %2d:%02d:%02d",
                $mday, $monids[$mon], $year, $hour, $min, $sec);
    }
}

# converts bytes into K, M, G, T bytes
# $val: the number of bytes
# $len: the length of the resulting string, $len>=1
sub printbytes {
    my ($val, $len, $suffix)=($_[0], $_[1], "B");
    $len--;
    if ($val>1024) { $val/=1024; $suffix="K"; }
    if ($val>1024) { $val/=1024; $suffix="M"; }
    if ($val>1024) { $val/=1024; $suffix="G"; }
    if ($val>1024) { $val/=1024; $suffix="T"; }
    sprintf("%$len.1f%s", $val, $suffix);
}

# prints percentages
# $val:     the value to represent
# $overall: the value representing 100%
sub printpercent {
    my ($val, $overall)=@_;
    if ($overall==0) { return(" undef"); }
    $val=$val*100/$overall;
    if ($val<1&&$val!=0) {
        "  < 1%";
    } else {
        sprintf("%5.1f%%", $val);
    }
}

# prints a bar
# $val:  the value which should be visualized
# $most: the maximum value which represents 100%
# $len:  the length of the bar at 100%
sub printbar {
    my ($val, $most, $len)=@_;
    $len-=2;
    if ($most==0) {
        "|".(" "x$len)."|";
    } else {
        my $asterisk=POSIX::floor($val*$len/$most+.5);
        "|".("*"x$asterisk).(" "x($len-$asterisk))."|";
    }
}

# process a log file record, line by line
# $_: the logfile entry, the string itself
sub procrec {
    # check integrity
    $_=$_[0]; if ($_!~
    /(\d+)\s+(\d+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/) {
        $toprecord{'corrupted'}++; return;
    }
    $toprecord{'parsed'}++;

    # split into individual fields, process time stamp
    my ($time, $proto, $srcip, $srcpt, $destip, $destpt, $count, $len, $iface)=
        ($1, $2, $3, $4, $5, $6, $7, $8, $9, $0);
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($time);
    my $eday=epochday($time);

    # update current time frame
    $toprecord{'first'}=$time if ($toprecord{'first'}==0);
    $toprecord{'first'}=$time if ($time<$toprecord{'first'});
    $toprecord{'last'}=$time if ($time>$toprecord{'last'});

    # doing some stats
    $toprecord{'overall'}+=$len;
    $protocol[$proto]+=$len;
    $hours[$hour]+=$len;
    $toprecord{'hour'}=$hour if ($hours[$hour]>$hours[$toprecord{'hour'}]);
    $days[$eday]+=$len;
    $toprecord{'day'}=$eday if ($days[$eday]>$days[$toprecord{'day'}]);
}

# print some general statistics
sub overallstats {
    print("\n--- Overall statistics ----------------".("-"x40)."\n\n");
    my $numdays=($toprecord{'last'}-$toprecord{'first'})/(24*3600);
    printf("Time evaluated: %s to %s (%0.1f days)\n",
           printdatetime($toprecord{'first'}),
           printdatetime($toprecord{'last'}),
           $numdays);
    printf("Program running time: %d min %02d sec\n",
           (time-$runningtime)/60, (time-$runningtime)%60);
    print("Number of corrupted logfile entries: $toprecord{'corrupted'}\n");
    print("Number of parsed logfile entries: $toprecord{'parsed'}\n");
    print("Number of parsed lines per second: ".
          ($toprecord{'parsed'}/(time-$runningtime))."\n");
    print("Overall data transferred: ".
          printbytes($toprecord{'overall'}, 0)."\n");
    print("Average traffic per day/week/month: ".
          printbytes($toprecord{'overall'}/$numdays, 0)." / ".
          printbytes($toprecord{'overall'}*7/$numdays, 0)." / ".
          printbytes($toprecord{'overall'}*30/$numdays, 0)."\n") if ($numdays);
    printf("Highest traffic - hour: %d\n", $toprecord{'hour'});
    printf((" "x16)."- day: %s\n",
           printdatetime($toprecord{'day'}*24*3600,date));
}

# print statistics on protocols
sub protocolstats {
    print("\n\n--- Protocol statistics ---------------".("-"x40)."\n\n");
    print("TCP:  ".printbytes($protocol[getprotobyname("tcp" )], 8)."\n");
    print("UDP:  ".printbytes($protocol[getprotobyname("udp") ], 8)."\n");
    print("ICMP: ".printbytes($protocol[getprotobyname("icmp")], 8)."\n");
    print("IGMP: ".printbytes($protocol[getprotobyname("igmp")], 8)."\n");
}

# show an hourly diagram
sub hourlystats {
    print("\n\n--- Hourly statistics -----------------".("-"x40)."\n\n");
    for (my $i=0; $i<24; $i++) {
        printf("%2d-%2d: %s %s %s\n", $i, $i+1, printbytes($hours[$i], 7),
               printpercent($hours[$i], $toprecord{'overall'}),
               printbar($hours[$i], $hours[$toprecord{'hour'}], 58));
    }
}

# monthly/weekly/daily grid
sub gridstats {
    my ($day, $pday, $pmon);         # current and previous processed day/month
    my ($fday, $lday)=0;                   # first and last day of current week
    my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
    my @msum, $wsum;                                  # monthly and weekly sums

    sub line {
        ("-"x70)."+".("-"x8)."\n";
    }

    sub weekly_start {
        $fday=$day-$wday+1;
        $pday=$fday-1;
        $lday=$day-$wday+7;
        $wsum=0;
        print(printdatetime(24*3600*$fday, short).":".
              printdatetime(24*3600*$lday, short));
    }

    sub weekly_end {
        print(" "x(8*($lday-$pday))." |".printbytes($wsum, 8)."\n")
            if (defined($pday));
    }

    sub monthly_start {
        @msum=0; $wsum=0;
    }

    sub monthly_end {
        my @monids=("January", "February", "March", "April",
                    "May", "June", "July", "August",
                    "September", "October", "November", "December");
        print(line);
        printf("%13s", $monids[$pmon]);
        for (my $day=1; $day<=7; $day++) {
            print(printbytes($msum[$day], 8));
        }
        print(" |".printbytes($msum[0], 8)."\n".line);
    }

    print("\n\n--- Statistical grid ------------------".("-"x40)."\n\n");
    print("         week     Mon     Tue     Wed     ".
          "Thu     Fri     Sat     Sun |     Sum\n".line);
    monthly_start;
    DAY: for ($day=0; $day<scalar(@days); $day++) {
        next DAY if (!defined($days[$day]));
        ($sec, $min, $hour, $mday, $mon, $year,
         $wday, $yday, $isdst)=localtime($day*24*3600);
        $wday=7 if ($wday==0);  # Sundays gets day 7, 0 is reserved for the sum
        if (defined($pmon)&&($mon!=$pmon)) {
            weekly_end; monthly_end; monthly_start; weekly_start;
        } elsif ($day>$lday) {
            weekly_end; weekly_start;
        }
        print(" "x(8*($day-$pday-1)));
        print(printbytes($days[$day], 8));
        $msum[0]+=$days[$day];
        $msum[$wday]+=$days[$day];                # update monthly sum (by day)
        $wsum+=$days[$day];                                 # update weekly sum
        $pday=$day;             # we are done, set this day as the previous day
        $pmon=$mon;      # ... and this month as the month previously worked on
    }
    weekly_end; monthly_end;
}

# a statistic of every day encountered
sub dailystats {
    print("\n\n--- Daily statistics ------------------".("-"x40)."\n\n");
    for (my $i=0; $i<scalar(@days); $i++) {
        printf("%s:%s%s %s\n", printdatetime(24*3600*$i, date),
               printbytes($days[$i], 8),
               printpercent($days[$i], $toprecord{'overall'}),
               printbar($days[$i], $days[$toprecord{'day'}], 53))
            if (defined($days[$i]));
    }
}

# init vars, main loop: read each line from input and process
$runningtime=time;
$toprecord{'corrupted'}=0;
$toprecord{'parsed'}=0;
@hostinfo=gethostbyname(hostname||"localhost");
printf STDERR "What do I care if you typed your own logfiles?\n" if (-t STDIN);
while (<STDIN>) {
    procrec($_);
}
printf("Traffic Report for %s\n\n", $hostinfo[0]);
overallstats;
protocolstats;
hourlystats;
gridstats;
dailystats;
