#!/usr/local/bin/perl -T # # imapTool # # Analyzes the imap server for various bits of information to provide usage # data. This information is useful for capacity planning as well as tracking # down server abusers. This program requires that the imapd be a U-W server # running with my patch to provide additional data. Contact me directly for # the patch which logs getrusage() info. This also requires that syslog be # configured to log mail.debug information. Please adjust paths as necessary. # There is now a web page for this: http://www.carumba.com/talk/imap/ # # This is pretty Solaris centric. YMMV. No small animals were harmed in the # production of this code. Distributed under the GPL license. # # $Id: imapTool,v 1.4 2000/11/28 15:11:03 jauderho Exp jauderho $ # (C) Copyright 1998-2000 Jauder Ho # # 051598 jauderho [first pass at system analysis] # 051798 jauderho [all stats are now tracked. need to add disk usage and] # [account info. have to figure out how to track max] # [concurrent users] # 051898 jauderho [slightly improved sorting in place. reports account info] # 052198 jauderho [more minor fixups] # 060298 jauderho [first attempt at trying to calculate the max number of] # [users online at one time. need to put logical sections in] # 070798 jauderho [modified match routine to work with v10.234 of u-w imapd] # [added code to find out new accounts added this week] # 112800 jauderho [fixed minor date formatting bug pointed out by] # [marc@merlins.org. added -w back in, comment out if] # [necessary] # my $imapHome = "/var/imap"; my $logFile = "/var/log/syslog"; my $maxOnlineUsers=0; my $waterMark=5; # Ignore sessions that last less than x secs. my $recently=604800; # Look more closely at recent sessions (1 week). my ($date,$ihost,$process,$user,$host,$start,$et,$ut,$st); my ($mss,$iss,$minf,$majf,$sw,$bi,$bo,$mi,$mo,$nsig,$vcs,$ics); my ($sessionCounter,$userCounter,$hostCounter,$etCounter,$swCounter); my ($startDate,$endDate); my $totalCPUTime; my $avgmajpf; my $worsemajpf; my $onlineUsers; my $maxOnlineUsersTime; my $activeAcctCounter; my @lsfiles; my (%seenUser,%seenHost,%inActiveAcct,%newAcct,%worsemajpf,%onlineTimes); sub timeConvert; if ($ARGV[0]) { $logFile = $logFile . ".$ARGV[0]"; } open(INPUT,"$logFile") or die "Cannot open file $logFile\n"; for () { # Try a reasonable pattern. May need adjusting. if (/u\s\S+\sfrom\s/) { my ($starttime,$endtime); chomp(); # Chomp! # Here's the really really ugly full pattern match. # Feel free to make it better. ($date,$ihost,$process,$user,$host,$start,$et,$ut,$st,$mss,$iss,$minf,$majf,$sw,$bi,$bo,$mi,$mo,$nsig,$vcs,$ics) = /^(\w+\s+\d+ \d+:\d+:\d+) (\w+) imapd\[(\d+)]: u (\S+) from (\S+ \S+|\S+) start (\d+) et (\d+) ut (\S+) st (\S+) mss (\d+) iss (\d+) minf (\d+) majf (\d+) sw (\d+) bi (\d+) bo (\d+) mi (\d+) mo (\d+) nsig (\d+) vcs (\d+) ics (\d+)$/; # Log start and end times for session $starttime = $start; $endtime = ($start + $et); # If the start and end time are equal, it is pretty likely # that it is a mail checking/biff type program that is falsely # inflating the logs next unless (($endtime - $starttime) > $waterMark); $onlineTimes{$starttime} = "on"; $onlineTimes{$endtime} = "off"; # Count and add some statistics $startDate = $date unless defined($startDate); $sessionCounter++; $userCounter++ unless # Ignore unknown ($seenUser{$user}++ || $user =~ /\?\?\?/); # user # Obviously this is Transmeta specific. You want to fix this # up. I no longer use this code myself so am unable to see the # output to fix it in a more generic manner. Submit patches. # jauderho [112800] if ($host =~ /transmeta/) { # Ignore the IP address if the hostname is known. # This is new in v10.234 of the imapd server. $host =~ s/ \[\d+\.\d+\.\d+\.\d+\]//; } $seenHost{$host}++ unless ($host =~ /UNKNOWN/); $etCounter = $etCounter + $et; $swCounter = $swCounter + $sw; $totalCPUTime = $totalCPUTime + $ut + $st; # Create the worse page fault session array if ($et > $waterMark) { $worsemajpf{$majf/$et} = $date; } } } # Get date of last entry in log file $endDate = $date; # Silly hack to fix the $host variable. See above for explanation. if ($host =~ /transmeta/) { $host =~ s/ \[\d+\.\d+\.\d+\.\d+\]//; } # Find the worse page fault session for (keys %worsemajpf) { if ($_ > $worsemajpf) { $worsemajpf = $_; } $avgmajpf = $avgmajpf + $_; } # Find out the number of accounts on the system opendir(IMAPDIR,"$imapHome") or die "Cannnot stat directory $imapHome\n"; @lsfiles = grep !/^\.\.?$/, readdir(IMAPDIR); closedir(IMAPDIR); # Find accounts that have been used recently (set to 1 week). Adjust # accordingly if you want to know different stats. for (@lsfiles) { my @stats; if (-f "$imapHome/$_/INBOX") { (@stats) = stat("$imapHome/$_/INBOX"); if ((time - $recently) < $stats[9]) { $activeAcctCounter++; } else { $inActiveAcct{$stats[9]} = $_; } } elsif ($_ == "imapshared" ) { # Don't do anything for now } else { warn "ERROR: $imapHome/$_/INBOX does not exist\n"; } if (-f "$imapHome/$_/.inbox.create") { (@stats) = stat("$imapHome/$_/.inbox.create"); if ((time - $recently) < $stats[9]) { $newAcct{$stats[9]} = $_; } } } # Count the numer of hosts. for (keys %seenHost) { $hostCounter++; } # Shoot out all the info print "******************************************************************\n", "*** imapd server usage from $startDate to $endDate ***\n", "******************************************************************\n\n"; print "NOTE: Only sessions longer than $waterMark secs are considered.\n\n"; print "***************************\n", "*** Summary Information ***\n", "***************************\n\n", "Total users: \t\t$userCounter\n", "Total hosts: \t\t$hostCounter\n", "Total sessions: \t$sessionCounter\n", "Total connect time: \t", timeConvert($etCounter), "\n"; printf "Total CPU time: \t%.2f secs\n\n", $totalCPUTime; printf "Average connect time per session: \t%s\n", timeConvert(int($etCounter/$sessionCounter)); printf "Average CPU time per session: \t\t%.2f secs\n", $totalCPUTime/$sessionCounter; print "Total swaps: \t\t\t\t$swCounter\n"; printf "Average swaps per session : \t\t%d\n\n", $swCounter/$sessionCounter; printf "Average major page faults per second per session: %.3f\n", $avgmajpf/$sessionCounter; printf "Worst case major page faults per second per session: %.3f at %s\n\n", $worsemajpf, $worsemajpf{$worsemajpf}; print "***************************\n", "*** Account Information ***\n", "***************************\n\n", "There are @{[$#lsfiles+1]} accounts.\n", "There were $activeAcctCounter active accounts in the last week.\n\n", "Inactive accounts for more than 1 week:\n"; for (sort keys %inActiveAcct) { my $temptime; $temptime = scalar localtime($_); $temptime =~ s/^\w+\s(.*)\s\d+$/$1/; print "$temptime\t $inActiveAcct{$_}\n"; } print "\n"; print "New accounts created in the last week:\n"; for (sort keys %newAcct) { my $temptime; $temptime = scalar localtime($_); $temptime =~ s/^\w+\s(.*)\s\d+$/$1/; print "$temptime\t $newAcct{$_}\n"; } print "\n"; print "Most recent user: $user\@$host ($date)\n", " time: ", timeConvert($et), "\n\n"; for (sort keys %onlineTimes) { if ($onlineTimes{$_} eq "on") { $onlineUsers++; } elsif ($onlineTimes{$_} eq "off") { $onlineUsers--; } if ($onlineUsers > $maxOnlineUsers) { $maxOnlineUsers = $onlineUsers; $maxOnlineUsersTime = $_; } } $maxOnlineUsersTime = scalar localtime($maxOnlineUsersTime); $maxOnlineUsersTime =~ s/^\w+\s(.*)\s\d+$/$1/; # Format time to look like # others print "Maximum number of concurrent users: $maxOnlineUsers ", "at $maxOnlineUsersTime\n\n"; print "******************************\n", "*** Connection Information ***\n", "******************************\n\n", "Host\t\t\t\t Number of connections\n", "----\t\t\t\t ---------------------\n"; # Fantastically nasty way to do a reverse value sort by data. This prints out # the host with the highest number of connections followed by alphabetical # sort of hosts with the same number of connections. for (reverse sort { $seenHost{$a} <=> $seenHost{$b} || $b cmp $a} keys %seenHost) { printf "%-33s$seenHost{$_}\n", $_; } print "\n"; ## The End ## sub timeConvert { my ($secs) = @_; my ($days,$hours,$mins); my $string; # Transform secs into hrs, mins, secs $mins = int($secs/60); $secs = $secs % 60; $hours = int($mins/60); $mins = $mins % 60; $days = int($hours/24); $hours = $hours % 24; $days = $days . " day" . ($days > 1 ? "s" : ""); $hours = $hours . " hr" . ($hours > 1 ? "s" : ""); $mins = $mins . " min" . ($mins > 1 ? "s" : ""); $secs = $secs . " sec" . ($secs > 1 ? "s" : ""); # On the fly string reformatting. English is not a good language # to program for. $string = ($days gt "0 day" ? "$days " : "") . ($hours gt "0 hr" ? "$hours " : "") . ($mins gt "0 min" ? "$mins " : "") . ($secs gt "0 sec" ? "$secs" : "0 sec"); return "$string"; }