#!/usr/local/bin/perl
###############################################################################
#
#   sarreport (v1.1)
#
#       A simple perl script that executes sar, obtains and formats
#       the output on an hourly basis, gets the average for each
#       hour and dumps it via email.
#
#       Note: the regex to parse the log data expects the default format
#               ([date] "request" status bytes)
#
###############################################################################


$|++; select STDERR; $|++; select STDOUT;
use Getopt::Std;


###########################################################
# Variables

$DEBUG; # debug flag
$to;    # email address
chomp($localhost = `/usr/bin/uname -n`);
$FROM = "sarreport <sys\@$localhost>";
$SAR = '/usr/sbin/sar';  # should work on solaris, irix
$MAILER = '/usr/lib/sendmail';  # sendmail
@OPTS = qw/u b w a q p g r d/;  # get only these for now
%OPTS = (
    'a' => "File Access System Routines",
    'b' => "Buffer Activities",
    'c' => "System Calls",
    'd' => "Block Device Activities",
    'g' => "Paging-Out Activities",
    'k' => "Kernel Memory Allocation Activities",
    'm' => "Message and Semaphore Activities",
    'p' => "Paging-In Activities",
    'q' => "Average Queue Length and Time Occupied",
    'r' => "Unused Memory Pages and Disk Blocks",
    'u' => "CPU Utilization",
    'v' => "Status of Process, Inode and File Tables",
    'w' => "System Swapping and Switching Activities",
    'y' => "Status of Process, Inode and File Tables",
);
@out;
$HOST;  # sar's system identifier line
$logfile;   # logfile path, from the command line option
$LOGERROR;  # null, unless -l is specified but file not found
%LOG;   # hash of arrays to hold the log key(time) => [hits, bytes]
$div = "================================================================================";
@date = localtime(time());
$month = $date[4];
$year = $date[5] + 1900;
$day;   # see the options section
%M = (
    "Jan" => 0,
    "Feb" => 1,
    "Mar" => 2,
    "Apr" => 3,
    "May" => 4,
    "Jun" => 5,
    "Jul" => 6,
    "Aug" => 7,
    "Sep" => 8,
    "Oct" => 9,
    "Nov" => 10,
    "Dec" => 11,
);
# arp
$webre = '^(\S+) (-) (-) \[([^\]]*)\] \"([^\"]*)\" (\d+) (\d+) \"([^\"]*)\" \"([
^\"]*)\"';
# /arp
$USAGE = <<USAGE;
Usage: sarreport [ -h | -d <day> -m <email> -l <logfile> [ -D ]]
    -h: Display this help
    -d <day>: where <day> is an integer, e.g. 23 (as in /var/adm/sa/sa23)
    -m <email>: where <email> is fairly obvious. With this option specified,
                the output is sent to the <email>, otherwise STDOUT (STDOUT
                with -D even -m is specified)
    -l <logfile>: where <logfile> is a location (absolute path) of the http
                  server logfile (compatible with both Apache and Netscape)
                  to calculate the hit counts for each sar report entry
    -D: Debugging flag; displays teh sar command in STDERR
USAGE
$NOFILE = <<NOFILE;
The -d you specified does not exist in /var/adm/sa !!!

$USAGE
NOFILE
$NOMAILER = <<NOMAILER;
sarreport error: cannot find $MAILER. No mail will be sent.
NOMAILER
$PRINTLOG;  # switch to tell whether to print log-related info


###########################################################
# Options

getopts('Dhd:m:l:', \%O);
die $USAGE if $O{'h'};
$DEBUG++ if $O{'D'};
$to = $O{'m'} || undef;
$logfile = $O{'l'} || undef;
$day = $O{'d'} || $date[3];
$day = 0 . "$day" if length($day) == 1;
$dayfile = "/var/adm/sa/sa${day}";
die $NOFILE unless -e "$dayfile";


###########################################################
# Main Routine

%LOG = parseLog($logfile) if $logfile;
$PRINTLOG = 1 if ($logfile) && (! $LOGERROR);
# get the sar output and collect data
foreach $o (@OPTS) {
    my($label);
    my($l) = 0;
    push(@out, "$div\n");
    push(@out, "$OPTS{$o}\n\n");
    for $t (0..23) {
        my($ave);
        my($dump);
        my($s) = $t;
        $s = 0 . $s if length($s) == 1;
        my($e) = $t + 1;
        $e = 0 . $e if length($e) == 1;
        open(R, "$SAR -f $dayfile -s $t:00 -e $t:59 -i 300 -$o |");
        print STDERR "$SAR -f $dayfile -s $t:00 -e $t:59 -i 300 -$o\n" if $DEBUG;
        while (<R>) {
            $HOST = $_ if (! $HOST) && ($l == 1);
            if (($l ==3) && (! $label)) {
                $label = $_;
                $label =~ s/^......../        /;
                $label =~ s/$/   hits (bytes)/ if $PRINTLOG;
                push(@out, $label);
            }
            unless ($o eq 'd') {
                if (m/^Average/) {
                    $ave = $_;
                    $ave =~ s/^Average/$s - $e/;
                    push(@out, $ave);
                }
            } else {
                push(@out, $_) if $dump;
                if (m/^Average/) {
                    $ave = $_;
                    $ave=~ s/^Average/$s - $e/;
                    push(@out, $ave);
                    $dump++;
                }
            }
            $l++;
        }
    }
    my($dump2); # switch for device again
    open(R, "$SAR -f $dayfile -s 0:00 -e 23:59 -i 300 -$o |");
    print STDERR "$SAR -f $dayfile -s 0:00 -e 23:59 -i 300 -$o\n" if $DEBUG;
    while (<R>) {
        unless ($o eq 'd') {
            if (m/^Average/) {
                push(@out, $_);
            }
        } else {
            push(@out, $_) if $dump2;
            if (m/^Average/) {
                push(@out, $_);
                $dump2++;
            }
        }
    }
    push(@out, "\n");
}

unshift(@out, "$LOGERROR\n\n") if $LOGERROR;
unshift(@out, "$HOST\n");


###########################################################
# Reporting

$TP = $LOG{'hits'} || 0;
$TB = formatBytes($LOG{'bytes'});
unless ($to) {
    foreach (@out) {
        if (m/^(\d{2})/) {
            my($t) = $1;
            my($hits) = $LOG{$t}->[0] || 0;
            my($bytes) = formatBytes($LOG{$t}->[1]);
            s/$/   $hits ($bytes)/ if $PRINTLOG;
        }
        print;
    }
    if ($PRINTLOG) {
        print "$div\n";
        print "Total hits: $TP\n";
        print "Total bytes transferred: $TB\n";
    }
} else {
    open(M, "| $MAILER -t") || warn $NOMAILER;
    print M "From: $FROM\n";
    print M "To: $to\n";
    print M "Subject: Sar Report on $HOST\n\n";
    print STDERR "Subject: Sar Report on $HOST\n\n" if $DEBUG;
    foreach (@out) {
        if (m/^(\d{2})/) {
            my($t) = $1;
            my($hits) = $LOG{$t}->[0] || 0;
            my($bytes) = formatBytes($LOG{$t}->[1]);
            s/$/   $hits ($bytes)/ if $PRINTLOG;
        }
        print M;
        print STDERR if $DEBUG;
    }
    if ($PRINTLOG) {
        print M "$div\n";
        print M "Total hits: $TP\n";
        print M "Total bytes transferred: $TB\n";
        if ($DEBUG) {
            print STDERR "$div\n";
            print STDERR "Total hits: $TP\n";
            print STDERR "Total bytes transferred: $TB\n";
        }
    }
    close(M);
}


###########################################################
# parseLog($LOG); return %LOG or sets $LOGERROR

sub parseLog {
    my($log) = shift;
    my(%LOG);
    unless (-r $log) {
        $LOGERROR = "Could not read $log you specified with -l option";
        print STDERR "$LOGERROR\n" if $DEBUG;
        return %empty;
    } else {
        my($t0) = time();
        print STDERR "Starting to parse the log <$log>..." if $DEBUG;
        open(L, "<$log");
        my($line);
        while ($line = <L>) {
            # I need to recheck the below $line -- the end of the line was chopped off
#            $line =~ m'\[(\d{1,2})/(\w{3})/(\d{4}):(\d{2}):.+?"(?:GET|POST|HEAD)\s\S+\s;
# $webre: $1 = IP	$2 = nada	$3 = userauth
#         $4 = date	$5 = url	$6 = returncode
#         $7 = docsize	$8 = referrer	$9 = browsertype

            $line =~ m/$webre/;

            (my($d) = $1) =~ s,(\d+).*,$1,;
            (my($m) = $2) =~ s,\d+/(\w+).*,$1,;
            (my($y) = $3) =~ s,\d+/\w+/(\d+).*:,$1,;
            (my($t) = $4) =~ s,\d+/\w+/\d+:(.*),$1,;
            my($b) = $5;
            if (($d eq $day) && ($M{$m} == $month) && ($y == $year)) {
                $LOG{$t}->[0]++;    # hits
                $LOG{$t}->[1] += $b;    # bytes
                $LOG{"hits"}++;     # total hits
                $LOG{"bytes"} += $b;    # total bytes
            }
        }
        my($t1) = time();
        my($duration) = $t1 - $t0;
        print STDERR " done ($duration sec)\n" if $DEBUG;
        return %LOG;
    }
}


###########################################################
# formatBytes($bytes); return $bytes_string

sub formatBytes {
    my($bytes) = shift;
    if ($bytes > (1024 * 1024)) {
        $bytes = $bytes / (1024 * 1024);
        $bytes = sprintf('%.2f', $bytes);
        return "${bytes}MB";
    } elsif ($bytes > 1024) {
        $bytes = $bytes / 1024;
        $bytes = sprintf('%.2f', $bytes);
        return "${bytes}KB";
    } elsif (! $bytes) {
        return "0 bytes";
    } else {
        return "$bytes bytes";
    }
}
