#!/usr/bin/perl
#
# bottom - display bottom processes. Solaris, Perl.
#
# This is the opposite of top, it displays processes that are using the
# least CPU. It is the companion to the "prstat" command.
#
# 25-Jun-2005, ver 0.91
#
# USAGE:	bottom [interval]
#
# THANKS: Ron Soutter, for suggesting this tool.
#
# COPYRIGHT: Copyright (c) 2005 Brendan Gregg.
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#  (http://www.gnu.org/copyleft/gpl.html)
#
# 23-Jun-2005   Brendan Gregg   Created this.

### Defaults
$INTERVAL = 5;
$ENV{PATH} = "/usr/bin";
$| = 1;

### Check arguments
if (@ARGV > 0) {
	if ($ARGV[0] eq "-h" || $ARGV[0] eq "--help" || @ARGV > 1) {
		&usage();
	}
	$INTERVAL = $ARGV[0];
	$INTERVAL = 1 if $INTERVAL < 1;
}

### Determine screen manipulation
$CLEARSTR = `clear`;
my ($row,$col) = &getwinsz();
$TOP = $row - 3;

%State = qw(S sleep R run O cpu0 T stop Z zombie);

#
# --- Main ---
#
for (;;) {
	# read output from ps. if performance is a concern this could be
	# rewritten to read data from procfs directly; an example of doing
	# this in Perl is in my "prusage" tool.
	@Lines = `ps -eo pid,pcpu,user,vsz,rss,s,pri,nice,time,fname,nlwp`;
	chomp(@Lines);

	### Process data
	foreach $line (@Lines) {
		($pid,$pcpu,$rest) = split(' ',$line,3);
		next if $pid eq "PID";
		$Sort{$pid} = $pcpu;
		$Data{$pid} = "$pcpu $rest";
	}

	### Build report
	$line = 0;
	@Output = ();
	foreach $pid (sort {$Sort{$a} <=> $Sort{$b}}(keys(%Sort))) {
		($pcpu,$user,$vsz,$rss,$s,$pri,$nice,$time,$fname,$nlwp) =
		    split(' ',$Data{$pid});
	
		$nice -= 20;
		$s = $State{$s};
		$vsz = &kbtohuman($vsz);
		$rss = &kbtohuman($rss);

		$out =
		    sprintf("%6s %-8s %5s %5s %-6s %3s %4s %6s %3s%% %s/%s\n",
		    $pid,$user,$vsz,$rss,$s,$pri,$nice,$time,$pcpu,$fname,
		    $nlwp);
		unshift(@Output,$out);
		last if ++$line > $TOP;
	}

	### Print report
	print $CLEARSTR;
	printf("%6s %-8s %5s %5s %-6s %3s %4s %6s %4s %s/%s\n",
	    "PID","USERNAME","SIZE","RSS","STATE","PRI","NICE","TIME",
	    "CPU","PROCESS","NLWP");
	print @Output;

	### Wait interval
	sleep($INTERVAL);
}


# usage - print usage and exit.
#
sub usage {
	print STDERR "USAGE: bottom [interval]\n";
	exit 1;
}

# getwinsz - gets the terminal window size and returns it as x, y.
#       The default size returned is 24x80 if an error is encountered.
#
sub getwinsz {
        my $row = 24;
        my $col = 80;
        my ($xpix,$ypix,$winsize);
        my $TIOCGWINSZ = 21608;         # check /usr/include/sys/termios.h
 
        open(TTY, "+</dev/tty") || return($row,$col);
        ioctl(TTY, $TIOCGWINSZ, $winsize='') || return($row,$col);
        ($row, $col, $xpix, $ypix) = unpack('S4', $winsize);
        return($row,$col);
}

# kbtohuman - turns a Kb count into a human readable format
#
sub kbtohuman {
	my $kb = shift;

	if ($kb < 10000) {
		return "${kb}K";
	} else {
		return sprintf("%.0fM",$kb/1024);
	}
}

