#!/bin/sh

# Topish version 1.00. Copyright 1999-2001. All rights reserved.
#
# This program provides an alternative to the widely available top program
# but is written in standard Bourne Shell script and does not require
# special permissions to run or for installation.
#
# Note that this version will run on Solaris only.
#
# Usage:
#
#     topish [-s time] [-o field] [-u user] [-m match]
#
# Options:
#
#    -s time  specify the time between updates in seconds. Default is 5
#             seconds.
#    -o field sort by the specified field (one of cpu, pid, res, size, th,
#             time). Default is to sort by CPU.
#    -u user  restrict output to just processes belonging to the specified
#             user. Use + to indicate all users (default).
#    -m match restrict output to just those lines that match the given regular
#             expression.
#
# As far as possible this program has been designed to look and run like top
# although there are slight differences in layout and behaviour. Like top
# the screen will update automatically. Window resizing is accomodated and
# options may be changed when the program is running by pressing the
# appropriate command letter. The commands are:
#
#     ^L     redraw screen
#     h or ? help
#     k      kill processes; send a signal to a list of processes
#     m      restrict display to lines matching a regular expression
#     o      specify sort order (cpu, pid, res, size, th, time)
#     q      quit
#     s      change number of seconds between updates
#
# The display may be sorted in the following ways:
#
#     cpu  Highest CPU usage first.
#     pid  Lowest PID first
#     res  Highest resident size first (paged in)
#     size Highest total size first (total image size)
#     th   Highest number of threads first
#     time Longest run time first.
#
# Notable differences from top 3.5beta9:
#
#    * topish displays system up time instead of last pid.
#    * topish does not distinguish jobs on CPU from jobs running, nor does it
#      display the total number of processes on the system.
#    * topish uses a 3 line header and 1 line footer as opposed to top's 6
#      line header.
#    * topish runs lots of processes each updated, top only ever has one
#      process (this probably implies that topish's CPU load is higher).
#    * topish does not display processes' instantaneous priority (like top
#      it displays the niceness). This is to free up horizontal space.
#    * topish displays command line arguments, top merely displays the command.
#    * topish displays only the amounts of swap and RAM free, not the total
#      amounts available or in-use.
#    * topish can sort by number threads and pid, top can't.
#    * top can renice processes, topish can't.
#    * top can run for a limited number of displays, topish can't.
#    * top can ignore idle and/or system processes, topish can't.
#    * top can have the number of processes it displays limited, topish
#      always fills the display.
#    * top can run with 0 seconds between updates, topish has a minimum of
#      one second.
#
# Note that top has advanced somewhat since version 3.5beta9, that's just
# the version I had handy for comparison.
#
# Known Bugs:
#
# It doesn't cope very will with being suspended and may leave the terminal
# driver in a funny state. 
#
# Some code is too Solaris specific.
#
# Porting:
#
# It's probably not impossible to port this program to other variants of
# Unix. It's already intelligent enough to work out which version of
# stty and echo it's using. In principle it could auto-adapt to other
# features of the system.
#
# Version history:
#
# 0.01 sms Created. Posted on Usenet.
# 0.02 sms Added sort options pid and th. Added match option. Posted on Usenet.
# 1.00 sms Added documentation, placed on web site www.finesse.demon.co.uk.
#
# Authors:
#
# sms: Steven Singer, steven@finesse.demon.co.uk.
#
# Copying restrictions:
#
# This program may be copied freely provided the author(s) are clearly
# identified. This means that if you make a change then at the very least you
# should add a line to the version history with your initials stating what
# changes you made, and a line to the Authors section giving your full name and
# e-mail address.
#
# Warranty:
#
# This program is provided "AS IS" with no guarantee of suitability for any
# purpose. No warranty is given.

TTY=`tty` || {
    echo 'topish can only be run when connected to a terminal.' 1>&2
    exit 1
}

if stty > /dev/null 2>&1 ; then
    STTY=stty
else
    STTY=sysv_stty
fi

if test `echo "foo\c" | wc -c` -ne 3 ; then
    ECHONONL=echon
else
    ECHONONL=echoc
fi

TERMINAL=`stty -g`
ERROR=
PAGESIZE=`pagesize`

echon()
{
    echo -n "$@"
}

echoc()
{
    echo "$@\c"
}

sysv_stty()
{
    stty $@ 2>&1 > $TTY
}

resetandexit()
{
    $STTY $TERMINAL
    exit 0
}

getsize()
{
    eval `$STTY -a | egrep '(rows|columns)' | \
	sed 's/\([^=]*\) = \([0-9]*\);*/\1=\2;/g'`
    procs=`expr $rows - 4`
}

displayusage()
{
    echo 'Usage: topish [-s time] [-o field] [-u user] [-m match]'
}

setuperror()
{
    case "$ERROR" in
    "")
	;;
    *)
	echo "$ERROR"
	displayusage
	exit 1
	;;
    esac
}

settime()
{
    case "$1" in
    "")
	;;
    *)
	NEWTIME=`expr \( $1 - 1 \) \* 10 \* \( $1 \> 0 \) 2>&1` && \
	    TIME=$NEWTIME || ERROR=`echo $NEWTIME | cut -d' ' -f2-`
	;;
    esac
}

setfield()
{
    FIELD=`echo $1 | tr 'A-Z' 'a-z'`
    case $FIELD in
    size)
	SORTSEQ='+3nr +4nr'
	;;
    res)
	SORTSEQ='+4nr +3nr'
	;;
    cpu)
	SORTSEQ='+7nr +8nr'
	;;
    time)
	SORTSEQ='+8nr +7nr'
	;;
    pid)
	SORTSEQ='+1n'
	;;
    th)
	SORTSEQ='+2nr +1n'
	;;
    *)
	ERROR="Unknown sort field \"$FIELD\""
	;;
    esac
}

setuser()
{
    case "$1" in
    "")
	;;
    +)
	USEROPT="-e"
	;;
    *)
	USEROPT="-u $1"
	;;
    esac
}

setmatch()
{
    case "$1" in
    "")
        MATCHEXP=.
        ;;
    *)
        MATCHEXP=`echo "$1" | sed 's,/,\\\\/,g'`
        ;;
    esac
}

setfield cpu
setmatch ''
setuser '+'
settime 5

while test $# -gt 0
do
    case $1 in
    -s)
	settime $2
	setuperror || exit 1
	shift 2
	;;
    -o)
	setfield $2
	setuperror || exit 1
	shift 2
	;;
    -u)
	setuser $2
	setuperror || exit 1
	shift 2
	;;
    -m)
	setmatch "$2"
	setuperror || exit 1
	shift 2
	;;
    *)
	echo "Unknown option $1"
	displayusage
	exit 1
	;;
    esac
done

trap getsize WINCH
trap resetandexit INT QUIT TERM

getsize

while :
do
    DISPLAY=`clear; uptime; \
    vmstat 1 2 | awk 'NR == 4 \
	{if ($4 > 9999) s = int($4/1024) "M"; else s = $4 "k"; \
	if ($5 > 9999) f = int($5/1024) "M"; else f = $5 "k"; \
	printf "r:%3d, b:%3d, w:%3d, free:%6s swap,%6s real, " \
	"CPU:%3d%% us,%3d%% sy,%3d%% id\n",  $1, $2, $3, s, f, $20, $21, $22}'
    echo 'USERNAME   PID TH  SIZE   RES NI STATE %CPU   TIME COMMAND'
    /usr/bin/ps $USEROPT -o user= -o pid= -o nlwp= -o vsz= -o rss= -o \
	nice= -o s= -o pcpu= -o time= -o args= | \
    awk '{\
	if ($6 == "Z") { \
	    for(i=NF; i>=6; i--) \
		$(i+1) = $i; \
	    $6 = "--"; \
	} \
        if (split($9,d,"-") == 1) {\
            days = 0; t = d[1]; \
        } else {\
            days = d[1]; t = d[2]; \
        } \
        if (split(t,time,":") == 2) \
             secs = time[2] + time[1]*60; \
        else \
            secs = time[3] + time[2]*60 + time[1]*3600; \
        secs += days*86400; \
        printf "%s %s %s %s %s %s %s %s %d", $1,$2,$3,$4,$5,$6,$7,$8,secs; \
	for(i=10; i<=NF; i++) \
	    printf " %s", $i; \
        printf "\n"; \
    }' | sort $SORTSEQ | awk 'BEGIN { \
	state["O"] = "cpu"; state["S"] = "sleep"; state["R"] = "run"; \
    state["Z"] = "zomb"; state["T"] = "stop"} '"l < $procs "'{ \
	if ($4 > 9999) rs = int($4/1024) "M"; else rs = $4 "k"; \
	if ($5 > 9999) vs = int($5/1024) "M"; else vs = $5 "k"; \
	if ($9 <= 59999) \
            ti = sprintf("%3d:%02d", int($9/60), $9 % 60); \
	else {\
	    ti = $9 / 3600; \
	    if (ti <= 999.9) \
		ti = sprintf("%5.1fH", ti); \
	    else \
		ti = sprintf("%5.1fD", ti/24); \
	} \
	st = state[$7]; \
	if (length($1) > 8) us = substr($1, 1, 8); else us = $1; \
	line = sprintf "%-8s %5d%3d%6s%6s %2s %-5s%5s %5s", us, $2, $3, rs, \
	vs, $6, st, $8, ti; for(i=10; i<=NF; i++) line = line " " $i; \
	'"if (line ~ /$MATCHEXP/) {print substr(line, 1, $columns); l++}}"`
    echo "$DISPLAY"
    $ECHONONL "$ERROR"
    ERROR=
    $STTY -echo -icanon min 0 time $TIME
    COMMAND=`(trap TSTP; dd bs=1 count=1 2>/dev/null)`
    $STTY echo icanon
    case "$COMMAND" in
    q)
	exit 0
	;;
    o)
	$ECHONONL 'Enter name of field to sort by: '
	read FIELD
	setfield $FIELD
	;;
    s)
	$ECHONONL 'Enter number of seconds between updates: '
	read SECS
	settime "$SECS"
	;;
    u)
	$ECHONONL 'Enter the user to display for (+ for all): '
	read USER
	setuser $USER
	;;
    k)
	$ECHONONL 'Kill: '
	read KILL
	case "$KILL" in
	*-l*)
	    ERROR="Option -l not recognised."
	    ;;
	"")
	    ;;
	*)
	    ERROR=`kill $KILL 2>&1`
	    ;;
	esac
	;;
    m)
	$ECHONONL 'Enter the regular expression to match: '
	MATCH=`head -1`
	setmatch "$MATCH"
	;;
    h|\?)
	clear
	cat << EOF
Topish version 1.00, Copyright 1999-2001, Steven Singer

A cheap replacement for top requiring no special permissions.

These single-character commands are available:

^L     - redraw screen
h or ? - show this text
k      - kill processes; send a signal to a list of processes
m      - restrict display to lines matching a regular expression
o      - specify sort order (size, res, cpu, time, pid, th)
q      - quit
s      - change number of seconds between updates

Press return or enter to continue
EOF
	read FIELD
	;;
    ""|)
	;;
    *)
	ERROR="Command not understood."
	;;
    esac
done
