#!/usr/bin/perl -w # # proccheck.pl by Tikiman # # this script monitors the process table for runaway processes # and autokills those that consume too many resources. This generally # occurs when a process that normally blocks gets stuck in a run # state (happens with bnc) or someone is running something # they shouldn't be (password cracked etc) # # this was written for a freebsd box, I believe that the ps # syntax/output is compatible with linux, but probably not # for solaris, etc. $maxcpu = "30"; # max allowed cpu usage percent - may be decimal $maxmem = "30"; # max allowed mem usage percent - may be decimal # these numbers represent the maximum percentage # of resources a process my use before it # it may get killed. $cumcputime = "2"; # minimum number of CPU minutes the process has # to accumulate before it gets killed - this allows # relatively brief but CPU intensive processes # to continue (such as compiling) $interval = 60; # time to sleep between process checks $log = "killed.log"; # logfile (absolute or relative path) - program # output gets dumped here $killproc = 1; # set to true if you want the processes killed, set # to 0 of you want the process just logged $contact = "admin\@risingnet.net"; # contact given in the note to # process owner @friends = qw(root bin ftp); # usernames to ignore when checking # processes - these should be usernames # whose processes you don't want to # bother ##### End config section ##### $kid = fork(); # fork to bg if ($kid) { print "Process $kid sent to background\n"; exit; } $ok{$_}++ foreach (@friends); # load friends into a hash while() { checkProc(); # check processes sleep($interval); # sleep } sub checkProc { open(PROC,"ps au|") or die "Open: $!\n"; # obtain processes ; # remove header line my @info; while() { my @tmp = split; ($user,$pid,$cpu,$mem) = @tmp; next if $ok{$user}; push(@info, [ \@tmp, $pid, $cpu, $mem ]); # temp array } my @bycpu = reverse sort { $a->[2] <=> $b->[2] } @info; # sort my @bymem = reverse sort { $a->[3] <=> $b->[3] } @info; while($tmp = shift(@bycpu)) { # shave off til we're out of lines $tmp->[2] > $maxcpu ? $bad{$tmp->[1]} = $tmp->[0] : last; } while($tmp = shift(@mem)) { $tmp->[3] > $maxmem ? $bad{$tmp->[1]} = $tmp->[0] : last; } open(LOG,">>$log") or die "Can't open log file: $!"; select(LOG); while(($pid,$user) = each %bad) { ($user,$pid,$mem,$cpu,undef,undef,undef,undef,undef,$time,@cpu) = @$user; $string = "Time - ". `date`. "Username - $user\nPID - $pid\n" . "Mem% - $mem CPU% - $cpu\n" . "Cumulative CPU time - $time\n" . "Command: ". join(' ',@cpu); # output print "$string\n\n"; ($min,$sec1,undef) = ($time =~ /(\d+):(\d+)\.(\d+)/); $tottime = $min + ($sec1 / 60); if(($killproc) and ($tottime >= $cumcputime)) { kill(9,$pid) and mailUser($user,$string); # kill errant process } } close(LOG); close(PROC); } sub mailUser { my($user,$string) = @_; $string = "Dear User:\n" . # changing this shouldn't be too hard "The following process owned by you has " . "been killed for excess Memory and/or CPU usage:\n\n" . "$string\n\n" . "Please email $contact if you " . "have any questions.\n\n--Staff\n"; $filename = "/tmp/mem.$$.tmp"; open(TMP,">$filename"); print TMP $string; close(TMP); # this creates a temporary file to mail out - there is probably # some kind of security hole in this? system("cat $filename | mail $user -s Process killed"); unlink($filename); }