#!/usr/bin/perl -w # psmax - Find processes left behind, not used by users online right now # Max Baker # 12/02/04 use Proc::ProcessTable; use Sys::Utmp; use POSIX; use Getopt::Std; use strict; use vars qw/%users %procs %ut_type_map %proc_map $uid_min %args $DEBUG $All $Kill $Sleep $BadProcs $VERSION /; $VERSION = 0.1; getopts('ahdks:u:',\%args); $DEBUG = $args{d} || 0; $All = $args{a} || 0; $Kill = $args{k} || 0; $Sleep = defined $args{s} ? $args{s} : 100; # Processes created by UIDs less than this will not be disturbed. # Usually 500 or 1000 $uid_min = defined $args{u} ? $args{u} : 500; die &usage if exists $args{h}; &make_uttype_map; &parse_utmp; &getprocs; &showprocs; print "Total Number of Procs shown: $BadProcs\n"; exit($BadProcs); # collect the process statistics sub getprocs { my $p = new Proc::ProcessTable; foreach my $proc (@{$p->table}){ # Make map of processes by PID $proc_map{$proc->pid()}=$proc; # ignore ourselves next if ($proc->pid() == $$); # Sort/Save Procs by UID $procs{$proc->uid()}->{$proc->pid()} = $proc; } } # parse_utmp() - Sorts through the utmp entry to find out # who is online. sub parse_utmp { my $u = new Sys::Utmp; print "UTMP Entries:\n" if $DEBUG; while (my $ut = $u->getutent()){ my ($user,$id,$line,$pid,$t,$host,$time) = ($ut->ut_user(),$ut->ut_id(),$ut->ut_line(),$ut->ut_pid, $ut->ut_type,$ut->ut_host,$ut->ut_time ); my $type = $ut_type_map{$t}; my $uid = getpwnam($user) || ''; my $age = scalar(time) - $time; next unless $uid and $uid >= $uid_min; print "($uid) $user $pid $line $host $age $type\n" if $DEBUG; # Entries w/ type USER_PROCESS are logged in. $users{$uid}++ if $type eq 'USER_PROCESS'; } print "\n" if $DEBUG; $u->endutent; } # make_uttype_map() - Makes reverse lookup of ut_type sub make_uttype_map { foreach my $c (qw/ ACCOUNTING BOOT_TIME DEAD_PROCESS EMPTY INIT_PROCESS LOGIN_PROCESS NEW_TIME OLD_TIME RUN_LVL USER_PROCESS / ){ no strict 'refs'; my $const = &{"Sys::Utmp::$c"}; $ut_type_map{$const}=$c; } } # showprocs() - Sort through resulting processes and display the # processes of people who are not logged in. sub showprocs{ print &hostname,"\n"; print scalar localtime, "\n"; print "Orphaned Process Report\n"; print '-'x50 , "\n"; $BadProcs = 0; foreach my $u (sort {$a <=> $b} keys %procs){ my $name = getpwuid($u); my $u_procs = $procs{$u}; my $logged_in = exists $users{$u}; # Check for system users next unless $u >= $uid_min; # Checked if Logged in. next if ($logged_in and !$All); print "($u) $name ", $logged_in ? '' : '* ', scalar(keys %$u_procs)," procs\n"; my $utime = 0; my $size = 0; my $rss = 0; foreach my $p (keys %$u_procs){ $utime += $u_procs->{$p}->utime(); $size += $u_procs->{$p}->size(); $rss += $u_procs->{$p}->rss(); } printf " Utime : %-.2f sec\n", ($utime/100); printf " Mem : %-.2f MB %-.2f MB Resident\n", ($size/(1024*1024)), ($rss/(1024*1024)); $- -= 3; next unless $u >= $uid_min; foreach my $p (sort {$a <=> $b} keys %$u_procs){ my $proc = $u_procs->{$p}; my $ppid = $proc->ppid(); my $sess = $proc->sess(); my $name = $proc->fname(); my $start = scalar localtime($proc->start()); my $flag = ''; $flag .= 'orphan ' unless exists $proc_map{$ppid}; $flag .= 'no_sess ' unless exists $proc_map{$sess}; $flag .= 'session' if $sess == $p; my $kill = ''; # Kill Process if user not logged in if ($Kill and !$logged_in){ # Try a Hangup Signal first to be polite my $killed = $proc->kill(SIGHUP); select(undef,undef,undef,$Sleep/1000); # If HUP didn't do it, kill it. if (!$killed or $proc->kill(0)){ $proc->kill(SIGKILL); select(undef,undef,undef,$Sleep/1000); # Check if kill did it. if ($proc->kill(0)){ $kill = "SIGKILL failed"; } else { $kill = "SIGKILL"; } } else { $kill = "SIGHUP"; } } format STDOUT_TOP = PID PPID SESS FILE START FLAG KILLED? ------------------------------------------------------------------ . format STDOUT = @#### @#### @#### @<<<<<<<<<<<<<< @<<<<<<<<<<<<<<< @<<<<<<< @<<<<<<<<<<<<<< $p, $ppid,$sess,$name, $start, $flag, $kill . write; $BadProcs++; } } print "\n * Denotes user is not currently logged in.\n" if $All; } sub hostname { my $h = $ENV{HOSTNAME} || `/bin/hostname` || 'Host Unknown'; chomp($h); return $h; } sub usage { return <<"end_usage" psmax - This utility is used to find and remove processes that were left behind by users that are not logged in. A summary of the CPU and MEM usage for each user is given. Consider this a Pure-Perl marriage of 'who' 'ps' and 'kill'. -d - Debug -u - Minimum UID of processes to disturb ($uid_min) Set to 0 for all processes -a - Show processes from everyone logged in -k - Kill processes from people not logged in. -s - Time to wait to let a process die ($Sleep ms) Max Baker 12/02/04 end_usage } =head1 NAME psmax =head1 AUTHOR Max Baker =head1 DESCRIPTION This script is a pure-Perl marriage of ps,who, and kill. It will look for processes that were left behind by people who are not currently logged into a machine. A report is given of CPU and Memory usage for each user, and the processes are optionally killed. Only user processes (UID X or greater) are touched. A SIGHUP is tried before a SIGKILL. Run with C<-h> for a list of command-line arguments. =head1 PREREQUISITES use Proc::ProcessTable; use Sys::Utmp; use POSIX; use Getopt::Std; use strict; =head1 EXIT Status The program exits with the number of "bad" processes seen. =pod OSNAMES linux posix bsd? =pod SCRIPT CATEGORIES Unix/System_administration =cut