#!/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 "; $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 = < -m -l [ -D ]] -h: Display this help -d : where is an integer, e.g. 23 (as in /var/adm/sa/sa23) -m : where is fairly obvious. With this option specified, the output is sent to the , otherwise STDOUT (STDOUT with -D even -m is specified) -l : where 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 = <) { $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 () { 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 = ) { # 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"; } }