[net.sources] Top users display for Unix -- new version

William LeFebvre <phil@rice.ARPA> (12/03/84)

This message contains a shar file distribution for "top".  "Top" is a
program that will display and update information about a Berkeley Unix
(either 4.1 or 4.2) system, including the top cpu using processes.  It
would be similar to continually doing a "ps aux" except that it doesn't
consume half of the cpu to gather the information.  By default, it will
display the top ten processes and update this information every five
seconds.  I have noticed that this requires about 10% of the cpu.  Of
course, the percentage decreases as the time delay increases.

Not having access to a Bell Unix machine, I cannot say how or if this
program will work on such a system.

Just run the shar file through your favorite shell, then read the README.

This is a new version of "top", version 1.6 to be precise.  This
includes several fixes and enhancements, including #ifdef code for the
SUN.  Judicious sprinkling of the "register" keyword and some rewriting
have also occurred in an attempt to make it a little faster.

Note that this software has a copyright notice.  However, as is clearly
stated in the program itself, I give permission to freely redistribute
"top" to other Unix sites.

Enjoy!

                                William LeFebvre
				Department of Computer Science
				Rice University
                                <phil@Rice.arpa>

------------------------------- CUT HERE -------------------------------
echo 'Start of distribution file top.shar:'
echo 'Extracting Changes...'
sed 's/^X//' > Changes << '/'
XTue Nov 20 1984 - wnl (1.6)
X	Added an "exit" if sbrk's fail.  Added changes from Jonathon
X	Feiber at Sun:  ifdef SUN to make top work on Suns (they don't use
X	doubles in the proc structure), register declarations, check for
X	getting a user structure that has disappeared since the proc array
X	was read (it used to die, now it just shows the process as swapped).
X
XTue Nov 13 1984 - wnl (1.5)
X	If the number of displayable processes ("active_procs") was less
X	than the number of requested processes ("topn"), top would
X	segmentation fault.  This bug has been fixed.  Thanks to Prentiss
X	Riddle at ut-sally for pointing out the existence of this bug.
X
XTue Oct 23 1984 - wnl (1.4)
X	Finally fixed the hash table bug that caused processes owned by
X	root to sometimes appear with either no name or a different name
X	that had UID 0 (such as "operator").  Removed all the ifdef DEBUG
X	blocks to make top ready for distribution to the real world.
X
XSun Apr  8 1984 - wnl (still 1.3)
X	Made some slight changes to the display format.  It now looks more
X	aesthetically pleasing.  Added some preprocessor constants so that
X	the two defaults (number of processes and seconds of delay) easier
X	to change.
X
XThu Apr  5 1984 - wnl (1.3)
X	Changed the order in which things are done at initialization time.
X	This way, if an error occurs before starting the main loop, curses
X	will never get started.  Also changed other error handlers so that
X	endwin() is called before any flavor of exit.  Specifying a number
X	of processes that is more than the screen can handle is no longer
X	fatal.  It displays a warning message and pretends the user
X	specified the maximum for the screen.  Finally cured all the TSTP
X	blues (well, almost all).  I removed my TSTP handler and convinced
X	the system to always use the one that curses sets up.  Turns out
X	that "sleep" was stepping all over it during a pause.  So, I don't
X	use sleep anymore.  The only problem that remains with it now is
X	redrawing the old display before updating it after a pause.
X
XTue Apr  3 1984 - wnl (from 1.0 to 1.2)
X	I changed the format of the TIME column from just "seconds" to
X	"minutes:seconds".  I also made pausing work correctly.  Screen
X	redraws with an up to date display.  For compatability with 4.2, I
X	changed the name of the "zero" function to "bzero".  The makefile
X	has been altered to handle versions for 4.1 and 4.2, and README
X	has been updated to reflect these recent changes.
/
echo 'Extracting Makefile4.1...'
sed 's/^X//' > Makefile4.1 << '/'
X# Makefile for 4.1 version of "top"
X
X# values for two defaults in "top"
XTOPN = 10
XDELAY = 5
X
X# various other useful things
XMAN = mancat
XTROFF = xtroff
XTARFILES = Makefile Makefile4.1 Makefile4.2 Changes README top.c bzero.c top.1
XCFLAGS = -O
X
Xtop: top.o bzero.o
X	cc $(CFLAGS) -o top top.o bzero.o -lcurses -ltermcap -lm
X
Xtop.o: top.c
X	cc -c $(CFLAGS) -DDefault_TOPN=$(TOPN) -DDefault_DELAY=$(DELAY) top.c
X
X# This will switch the Makefile over to a 4.2 version
X4.2:
X	rm Makefile
X	ln Makefile4.2 Makefile
X
Xman:
X	nroff -$(MAN) top.1 | cat -s >top.cat
X
Xtroff:
X	$(TROFF) -man top.1
X
Xtar:
X	tar cvf top.tar $(TARFILES)
/
echo 'Extracting Makefile4.2...'
sed 's/^X//' > Makefile4.2 << '/'
X# Makefile for 4.2 version of "top"
X
X# values for two defaults in "top"
XTOPN = 10
XDELAY = 5
X
XMAN = man
XTROFF = xtroff
XTARFILES = Makefile Makefile4.1 Makefile4.2 Changes README top.c bzero.c top.1
XCFLAGS = -DFOUR_TWO -O
X# To make a version for the SUN, comment out the previous line and
X# uncomment the following line:
X#CFLAGS = -DFOUR_TWO -DSUN -O
X
Xtop: top.o
X	cc $(CFLAGS) -o top top.o -lcurses -ltermcap -lm
X
Xtop.o: top.c
X	cc -c $(CFLAGS) -DDefault_TOPN=$(TOPN) -DDefault_DELAY=$(DELAY) top.c
X
X# This will switch the Makefile over to a 4.1 version
X4.1:
X	rm Makefile
X	ln Makefile4.1 Makefile
X
Xman:
X	nroff -$(MAN) -rN$(TOPN) -rD$(DELAY) top.1 | cat -s >top.cat
X
Xtroff:
X	$(TROFF) -man -rN$(TOPN) -rD$(DELAY) top.1
X
Xtar:
X	tar cvf top.tar $(TARFILES)
/
echo 'Extracting README...'
sed 's/^X//' > README << '/'
XThis file contains a few comments about "top".
X
X"top" is a program that will give continual reports about the state of the
Xsystem, including a list of the top cpu using processes.  It requires read
Xaccess to the memory files "/dev/kmem" and "/dev/mem" as well as the system
Ximage "/vmunix".  Some installations have these files protected from general
Xaccess.  These sites would have to install this program in the same way that
Xprograms such as "ps" are installed.
X
XThere are a few things that need to be checked before compiling the program:
X
XThe most important item is the internal hash table size.  This size is
Xdefined in the program with the preprocessor variable "Table_size".  This
Xconstant MUST be larger than the number of lines in the file /etc/passwd.
XIt is advisable that this number be about twice the number of lines, and
Xthat it be a prime number (since it dictates the size of the hash table).
XMake sure this is checked before compilation.
X
XThe next thing is the routine called "bzero".  This function simply zeros
Xa block of memory.  The file "bzero.c" in this distribution is written for
Xa VAX and will not work on any other type of machine.  This routine is not
Xneeded under 4.2, since it is already defined in the C run time library.
XThe routine is very easy to write, but it is something that should be as
Xfast as possible.  If the local machine is not a VAX then "bzero.c" will
Xhave to be replaced with something else.  If you can't come up with a
Xslick method, there is a very simple C equivalent (although it will not be
Xvery fast) that will work:
X
X	bzero(memory, amount)
X	
X	char *memory;
X	int amount;
X	
X	{
X	    while (amount > 0)
X	    {
X		*memory++ = 0;
X		amount--;
X	    }
X	}
X
XThere are two Makefiles in this distribution.  One makes a version of top
Xfor a 4.1 system (called "Makefile4.1") and the other makes a version for a
X4.2 system (called "Makefile4.2").  Rename or link the version that is
Xappropriate for your machine to "Makefile" before running make.  There are
Xsome differences compiling and linking the different versions.  For example:
X"bzero" is already defined in the 4.2 C library, so that routine is not needed
Xunder 4.2.  There are also differences in the source, which are
Xconditionally compiled.  Both of these cases are handled correctly by the
Xappropriate Makefile.
X
XFinally, there are two preprocessor variables that are defined at compile
Xtime by the makefile.  These are "Default_TOPN" and "Default_DELAY".  Their
Xvalues are the defaults used for the top number of processes to be displayed
Xand the number of seconds to delay between displays, respectively.  They are
Xset by the Makefile variables "TOPN" and "DELAY", respecitvely.  These
Xconstants are preset as follows:  TOPN=10, DELAY=5.  These can be overriden
Xby either changing the Makefile or by specifying the change on the make
Xcommand line (with something like "make TOPN=15").
X
XIf you make a change to "top" that you feel would be beneficial to others
Xwho use this program, or if you find and fix a bug, feel free to send me the
Xchange.
X
XEnjoy!
X
X                                William LeFebvre
X				Department of Computer Science
X				Rice University
X                                ARPANet address: <phil@Rice.arpa>
X
X				U.S. Mail address:
X				    William LeFebvre
X				    P.O. Box 1892
X				    Department of Computer Science
X				    Houston, TX  77251
/
echo 'Extracting bzero.c...'
sed 's/^X//' > bzero.c << '/'
X/*
X *  Fast, sleazy, and ugly zero function.
X *
X *  Note that this will only work on a VAX, but it is real easy to write a
X *  similar function for whatever machine you may need.  If nothing else,
X *  just a simple loop in C will suffice.
X *
X *  Dave Johnson, Rice University.
X *
X *  Enhanced by William LeFebvre of Rice University to handle zeroing more
X *  than 64K.
X */
X
X# define   K	1024
X
X/*
X *  bzero(memory, amount) - set "amount" bytes starting at "memory" to the
X *			    value 0.
X */
X
Xbzero(memory, amount)
X
Xchar *memory;
Xint  amount;
X
X{
X    while (amount >= 64*K)
X    {
X	_bzero64(memory, 64*K-1);
X	memory += 64*K-1;
X	amount -= 64*K-1;
X    }
X    _bzero64(memory, amount);
X}
X
X_bzero64(memory, amount)
X
Xchar *memory;
Xint  amount;
X
X{
X    asm("	movc5	$0, (sp), $0, 8(ap), *4(ap)");
X}
/
echo 'Extracting top.c...'
sed 's/^X//' > top.c << '/'
Xchar *copyright = "Top, version 1.6, copyright (c) 1984, William LeFebvre";
X
X/*
X *  Top users display for Berkeley Unix
X *  Version 1.6
X *
X *  This program may be freely redistributed to other Unix sites, but this
X *  entire comment MUST remain intact.
X *
X *  Copyright (c) 1984, William LeFebvre, Rice University
X *
X *  This program is designed to run on either Berkeley 4.1 or 4.2 Unix.
X *  Compile with the preprocessor variable "FOUR_TWO" set to get an
X *  executable that will run on Berkeley 4.2 Unix.
X *
X *  The Sun kernel uses scaled integers instead of floating point so compile
X *  with the preprocessor variable "SUN" to get an executable that will run
X *  on Sun Unix version 1.1 or later.
X *
X *  Fixes and enhancements since version 1.5:
X *
X *  Jonathon Feiber at sun:
X *	added "#ifdef SUN" code to make top work on a Sun,
X *	fixed race bug in getkval for getting user structure,
X *	efficiency improvements:  added register variables and
X *		removed the function hashit
X */
X
X#include <curses.h>
X#include <stdio.h>
X#include <pwd.h>
X#include <nlist.h>
X#include <signal.h>
X#ifdef FOUR_TWO
X#include <machine/pte.h>
X#else
X#include <sys/pte.h>
X#endif
X#include <sys/param.h>
X#include <sys/dir.h>
X#include <sys/user.h>
X#include <sys/proc.h>
X#include <sys/dk.h>
X#include <sys/vm.h>
X
X/* obvious file names */
X
X#define VMUNIX	"/vmunix"
X#define KMEM	"/dev/kmem"
X#define MEM	"/dev/mem"
X
X/*
X *  The number of users in /etc/passwd CANNOT be greater than Table_size!
X *  If you get the message "table overflow: too many users", then increase
X *  Table_size.  Since this is the size of a hash table, things will work
X *  best if it is a prime number that is about twice the number of users.
X */
X
X#define Table_size	421
X
X/* Number of lines of header information on the standard screen */
X# define Header_lines	5
X
X/* wish list for kernel symbols */
X
Xstruct nlist nlst[] = {
X    { "_avenrun" },
X#define X_AVENRUN	0
X    { "_ccpu" },
X#define X_CCPU		1
X    { "_cp_time" },
X#define X_CP_TIME	2
X    { "_hz" },
X#define X_HZ		3
X    { "_mpid" },
X#define X_MPID		4
X    { "_nproc" },
X#define X_NPROC		5
X    { "_proc" },
X#define X_PROC		6
X    { 0 },
X};
X
X/* useful externals */
Xextern int errno;
Xextern char *sys_errlist[];
Xextern int LINES;
X
X/* signal handling routines */
Xint leave();
Xint onalrm();
X
X/* file descriptors for memory devices */
Xint kmem;
Xint mem;
X
Xint nproc;
Xint mpid;
X
X/* kernel "hz" variable -- clock rate */
Xlong hz;
X
X/* All this is to calculate the cpu state percentages */
X
Xlong cp_time[CPUSTATES];
Xlong cp_old[CPUSTATES];
Xlong cp_change[CPUSTATES];
Xlong total_change;
Xlong cp_time_offset;
Xlong mpid_offset;
Xlong avenrun_offset;
X
X#ifdef SUN
Xlong ccpu;
Xlong avenrun[3];
X#else
Xdouble ccpu;
Xdouble avenrun[3];
X#endif
Xdouble logcpu;
X
Xstruct proc *proc;
Xstruct proc *pbase;
Xint bytes;
X
Xstruct user u;
X
X/* Verbose process state names */
X
Xchar *state_name[] = {
X    "", "sleeping", "ABANDONED", "running", "starting", "zombie", "stopped"
X};
X
X/* process state names for the "STATE" column of the display */
X
Xchar *state_abbrev[] = {
X    "", "sleep", "WAIT", "run", "idl", "zomb", "stop"
X};
X
X/* cpu state names for percentages */
X
Xchar *cpu_state[] = {
X    "user", "nice", "system", "idle"
X};
X
X/* routines that don't return int */
X
Xstruct passwd *getpwent();
Xchar *username();
Xchar *ctime();
Xchar *rindex();
X
Xint proc_compar();
Xdouble log();
Xdouble exp();
X
Xmain(argc, argv)
X
Xint  argc;
Xchar *argv[];
X
X{
X    register struct proc *pp;
X    register struct proc **prefp;
X    register int i;
X    register int active_procs;
X    register int change;
X    register long cputime;
X
X    struct proc **pref;
X    int total_procs;
X    int proc_brkdn[7];
X    int topn = Default_TOPN;
X    int delay = Default_DELAY;
X    long curr_time;
X    long time();
X    char do_cpu = 0;
X    char *myname = "top";
X    double pctcpu;
X
X    /* get our name */
X    if (argc > 0)
X    {
X	if ((myname = rindex(argv[0], '/')) == 0)
X	{
X	    myname = argv[0];
X	}
X	else
X	{
X	    myname++;
X	}
X    }
X
X    /* check for time delay option */
X    if (argc > 1 && argv[1][0] == '-')
X    {
X	if (argv[1][1] != 's')
X	{
X	    fprintf(stderr, "Usage: %s [-sn] [number]\n", myname);
X	    exit(1);
X	}
X	delay = atoi(&(argv[1][2]));
X	argc--;
X	argv++;
X    }
X
X    /* get count of top processes to display (if any) */
X    if (argc > 1)
X    {
X	topn = atoi(argv[1]);
X    }
X
X    /* open kmem and mem */
X    if ((kmem = open(KMEM, 0)) < 0)
X    {
X	perror(KMEM);
X	exit(1);
X    }
X    if ((mem = open(MEM, 0)) < 0)
X    {
X	perror(MEM);
X	exit(1);
X    }
X
X    /* get the list of symbols we want to access in the kernel */
X    errno = 0;
X    nlist(VMUNIX, nlst);
X    if (nlst[0].n_type == 0)
X    {
X	fprintf(stderr, "%s: can't nlist image\n", VMUNIX);
X	exit(1);
X    }
X
X    /* get the symbol values out of kmem */
X    getkval(nlst[X_PROC].n_value,  &proc,  sizeof(int), nlst[X_PROC].n_name);
X    getkval(nlst[X_NPROC].n_value, &nproc, sizeof(int), nlst[X_NPROC].n_name);
X    getkval(nlst[X_CCPU].n_value,  &ccpu,  sizeof(int), nlst[X_CCPU].n_name);
X    getkval(nlst[X_HZ].n_value,    &hz,    sizeof(int), nlst[X_HZ].n_name);
X
X    /* some calculations we use later */
X
X    cp_time_offset = nlst[X_CP_TIME].n_value;
X    mpid_offset = nlst[X_MPID].n_value;
X    avenrun_offset = nlst[X_AVENRUN].n_value;
X
X    /* this is used in calculating WCPU -- calculate it ahead of time */
X#ifdef SUN
X    logcpu = log((double)ccpu / FSCALE);
X#else
X    logcpu = log(ccpu);
X#endif
X
X    /* allocate space for proc structure array and array of pointers */
X    bytes = nproc * sizeof(struct proc);
X    pbase = (struct proc *)sbrk(bytes);
X    pref  = (struct proc **)sbrk(nproc * sizeof(struct proc *));
X
X    /* Just in case ... */
X    if (pbase == (struct proc *)NULL || pref == (struct proc **)NULL)
X    {
X	fprintf(stderr, "%s: can't allocate sufficient memory\n", myname);
X	exit(1);
X    }
X
X    /* initialize the hashing stuff */
X    init_hash();
X
X    /* initialize curses and screen (last) */
X    initscr();
X    erase();
X    clear();
X    refresh();
X
X    /* setup signal handlers */
X    signal(SIGINT, leave);
X    signal(SIGQUIT, leave);
X
X    /* can only display (LINES - Header_lines) processes */
X    if (topn > LINES - Header_lines)
X    {
X	printw("Warning: this terminal can only display %d processes...\n",
X	    LINES - Header_lines);
X	refresh();
X	sleep(2);
X	topn = LINES - Header_lines;
X	clear();
X    }
X
X    /* main loop ... */
X
X    while (1)		/* while(1)'s are forever ... */
X    {
X	/* read all the proc structures in one fell swoop */
X	getkval(proc, pbase, bytes, "proc array");
X
X	/* get the cp_time array */
X	getkval(cp_time_offset, cp_time, sizeof(cp_time), "_cp_time");
X
X	/* get load average array */
X	getkval(avenrun_offset, avenrun, sizeof(avenrun), "_avenrun");
X
X	/* get mpid -- process id of last process */
X	getkval(mpid_offset, &mpid, sizeof(mpid), "_mpid");
X
X	/* count up process states and get pointers to interesting procs */
X	total_procs = 0;
X	active_procs = 0;
X	bzero(proc_brkdn, sizeof(proc_brkdn));
X	prefp = pref;
X	for (pp = pbase, i = 0; i < nproc; pp++, i++)
X	{
X	    /* place pointers to each valid proc structure in pref[] */
X	    /* (processes with SSYS set are system processes) */
X	    if (pp->p_pid != 0 && pp->p_stat != 0 && (pp->p_flag & SSYS) == 0)
X	    {
X		total_procs++;
X		proc_brkdn[pp->p_stat]++;
X		if (pp->p_stat != SZOMB)
X		{
X		    *prefp++ = pp;
X		    active_procs++;
X		}
X	    }
X	}
X
X	/* display the load averages */
X	printw("last pid: %d;  load averages", mpid);
X	{
X	    for (i = 0; i < 3; i++)
X	    {
X		printw("%c %4.2f",
X		    i == 0 ? ':' : ',',
X#ifdef SUN
X		    (double)avenrun[i] / FSCALE);
X#else
X		    avenrun[i]);
X#endif
X	    }
X	}
X
X	/*
X	 *  Display the current time.
X	 *  "ctime" always returns a string that looks like this:
X	 *  
X	 *	Sun Sep 16 01:03:52 1973
X	 *      012345678901234567890123
X	 *	          1         2
X	 *
X	 *  We want indices 11 thru 18 (length 8).
X	 */
X
X	curr_time = time(0);
X	move(0, 79-8);
X	printw("%-8.8s\n", &(ctime(&curr_time)[11]));
X
X	/* display process state breakdown */
X	printw("%d processes", total_procs);
X	for (i = 1; i < 7; i++)
X	{
X	    if (proc_brkdn[i] != 0)
X		printw("%c %d %s%s",
X			i == 1 ? ':' : ',',
X			proc_brkdn[i],
X			state_name[i],
X			(i == SZOMB) && (proc_brkdn[i] > 1) ? "s" : "");
X	}
X
X	/* calculate percentage time in each cpu state */
X	printw("\nCpu states: ");
X	if (do_cpu)	/* but not the first time */
X	{
X	    total_change = 0;
X	    for (i = 0; i < CPUSTATES; i++)
X	    {
X		/* calculate changes for each state and overall change */
X		if (cp_time[i] < cp_old[i])
X		{
X		    /* this only happens when the counter wraps */
X		    change = (int)
X			((unsigned long)cp_time[i]-(unsigned long)cp_old[i]);
X		}
X		else
X		{
X		    change = cp_time[i] - cp_old[i];
X		}
X		total_change += (cp_change[i] = change);
X	    }
X	    for (i = 0; i < CPUSTATES; i++)
X	    {
X		printw("%s%4.1f%% %s",
X			i == 0 ? "" : ", ",
X			((float)cp_change[i] / (float)total_change) * 100.0,
X			cpu_state[i]);
X		cp_old[i] = cp_time[i];
X	    }
X	}
X	else
X	{
X	    /* we'll do it next time */
X	    for (i = 0; i < CPUSTATES; i++)
X	    {
X		printw("%s      %s",
X			i == 0 ? "" : ", ",
X			cpu_state[i]);
X		cp_old[i] = 0;
X	    }
X	    do_cpu = 1;
X	}
X	printw("\n");
X
X	if (topn > 0)
X	{
X	    printw("\n  PID USERNAME PRI NICE   SIZE   RES STATE   TIME   WCPU    CPU COMMAND\n");
X    
X	    /* sort by cpu percentage (pctcpu) */
X	    qsort(pref, active_procs, sizeof(struct proc *), proc_compar);
X    
X	    /* now, show the top whatever */
X	    if (active_procs > topn)
X	    {
X		/* adjust for a lack of processes */
X		active_procs = topn;
X	    }
X	    for (prefp = pref, i = 0; i < active_procs; prefp++, i++)
X	    {
X		pp = *prefp;
X		if (getu(pp) == -1)
X		{
X		    strcpy(u.u_comm, "<swapped>");
X		    cputime = 0;
X		}
X		else
X		{
X		    cputime = 
X#ifdef FOUR_TWO
X			(u.u_ru.ru_utime.tv_sec + u.u_ru.ru_stime.tv_sec);
X#else
X			(int)((float)(u.u_vm.vm_utime + u.u_vm.vm_stime)/hz);
X#endif
X		}
X#ifdef SUN
X		pctcpu = (double)pp->p_pctcpu / FSCALE;
X#else
X		pctcpu = pp->p_pctcpu;
X#endif
X		printw("%5d %-8.8s %3d %4d%6dK %4dK %-5s%4d:%02d %5.2f%% %5.2f%% %-14.14s\n",
X		    pp->p_pid,
X		    username(pp->p_uid),
X		    pp->p_pri - PZERO,
X		    pp->p_nice - NZERO,
X		    (pp->p_tsize + pp->p_dsize + pp->p_ssize) >> 1,
X		    pp->p_rssize >> 1,
X		    state_abbrev[pp->p_stat],
X		    cputime / 60l,
X		    cputime % 60l,
X#ifdef SUN
X		    (100.0 * pctcpu / (1.0 - exp(pp->p_time * logcpu))),
X		    (100.0 * pctcpu),
X#else
X		    (100.0 * pctcpu / (1.0 - exp(pp->p_time * logcpu))),
X		    (100.0 * pctcpu),
X#endif
X		    u.u_comm);
X	    }
X	}
X	refresh();
X
X	/* wait ... */
X	signal(SIGALRM, onalrm);
X	alarm(delay);
X	pause();
X
X	/* clear for new display */
X	erase();
X    }
X}
X
X/*
X *  signal handlers
X */
X
Xleave()			/* exit under normal conditions -- INT handler */
X
X{
X    move(LINES - 1, 0);
X    refresh();
X    endwin();
X    exit(0);
X}
X
Xquit(status)		/* exit under duress */
X
Xint status;
X
X{
X    endwin();
X    exit(status);
X}
X
Xonalrm()
X
X{
X    return(0);
X}
X
X/*
X *  comparison function for "qsort"
X */
X
Xproc_compar(p1, p2)
X
Xstruct proc **p1;
Xstruct proc **p2;
X
X{
X    if ((*p1)->p_pctcpu < (*p2)->p_pctcpu)
X    {
X	return(1);
X    }
X    else
X    {
X	return(-1);
X    }
X}
X
X/*
X *  These routines handle uid to username mapping.
X *  They use a hashing table scheme to reduce reading overhead.
X */
X
Xstruct hash_el {
X    int  uid;
X    char name[8];
X};
X
X#define    H_empty	-1
X
X/* simple minded hashing function */
X#define    hashit(i)	((i) % Table_size)
X
Xstruct hash_el hash_table[Table_size];
X
Xinit_hash()
X
X{
X    register int i;
X    register struct hash_el *h;
X
X    for (h = hash_table, i = 0; i < Table_size; h++, i++)
X    {
X	h->uid = H_empty;
X    }
X
X    setpwent();
X}
X
Xchar *username(uid)
X
Xregister int uid;
X
X{
X    register int index;
X    register int found;
X    register char *name;
X
X    /* This is incredibly naive, but it'll probably get changed anyway */
X    index = hashit(uid);
X    while ((found = hash_table[index].uid) != uid)
X    {
X	if (found == H_empty)
X	{
X	    /* not here -- get it out of passwd */
X	    index = get_user(uid);
X	    break;		/* out of while */
X	}
X	index = index++ % Table_size;
X    }
X    return(hash_table[index].name);
X}
X
Xenter_user(uid, name)
X
Xregister int  uid;
Xregister char *name;
X
X{
X    register int length;
X    register int index;
X    register int try;
X    static int uid_count = 0;
X
X    /* avoid table overflow -- insure at least one empty slot */
X    if (++uid_count >= Table_size)
X    {
X	fprintf(stderr, "table overflow: too many users\n");
X	quit(1);
X    }
X
X    index = hashit(uid);
X    while ((try = hash_table[index].uid) != H_empty)
X    {
X	if (try == uid)
X	{
X	    return(index);
X	}
X	index = index++ % Table_size;
X    }
X    hash_table[index].uid = uid;
X    strncpy(hash_table[index].name, name, 8);
X    return(index);
X}
X
Xget_user(uid)
X
Xregister int uid;
X
X{
X    struct passwd *pwd;
X    static char buff[20];
X    register int last_index;
X
X    while ((pwd = getpwent()) != NULL)
X    {
X	last_index = enter_user(pwd->pw_uid, pwd->pw_name);
X	if (pwd->pw_uid == uid)
X	{
X	    return(last_index);
X	}
X    }
X    sprintf(buff, "%d", uid);
X    return(enter_user(uid, buff));
X}
X
X/*
X *  All of this stuff gets things out of the memory files.
X */
X
X/*
X *  Get the user structure for the process who's proc structure is in p.  The
X *  user structure is returned in u.
X */
Xgetu(p)
X
Xregister struct proc *p;
X
X{
X    struct pte uptes[UPAGES];
X    register caddr_t upage;
X    register struct pte *pte;
X    register nbytes, n;
X
X    /*
X     *  Check if the process is currently loaded or swapped out.  The way we
X     *  get the u area is totally different for the two cases.  For this
X     *  application, we just don't bother if the process is swapped out.
X     */
X    if ((p->p_flag & SLOAD) == 0)
X    {
X	return(-1);
X    }
X
X    /*
X     *  Process is currently in memory, we hope!
X     */
X    if (!getkval(p->p_addr, uptes, sizeof(uptes), "p->p_addr"))
X    {
X	/* we can't seem to get to it, so pretend it's swapped out */
X	return(-1);
X    } 
X    upage = (caddr_t)&u;
X    pte = uptes;
X    for (nbytes = sizeof(u); nbytes > 0; nbytes -= NBPG)
X    {
X    	lseek(mem, pte++->pg_pfnum * NBPG, 0);
X	n = MIN(nbytes, NBPG);
X	if (read(mem, upage, n) != n)
X	{
X	    /* we can't seem to get to it, so pretend it's swapped out */
X	    return(-1);
X	}
X	upage += n;
X    }
X    return(0);
X}
X
X/*
X *  get the value of something from /dev/kmem
X */
X
Xgetkval(offset, ptr, size, refstr)
X
Xlong offset;
Xint *ptr;
Xint size;
Xchar *refstr;
X
X{
X    if (lseek(kmem, offset, 0) == -1)
X    {
X	fprintf(stderr, "%s: lseek to %s: %s\n",
X	    KMEM, refstr, sys_errlist[errno]);
X	quit(1);
X    }
X    if (read(kmem, ptr, size) == -1)
X    {
X	if (strcmp(refstr, "p->p_addr") == 0) 
X	{
X	    /* we lost the race with the kernel, process isn't in memory */
X	    return(0);
X	} 
X	else 
X	{
X	    fprintf(stderr, "%s: reading %s: %s\n",
X		KMEM, refstr, sys_errlist[errno]);
X	    quit(1);
X	}
X    }
X    return(1);
X}
X
X/*
X *  outc - real subroutine that writes a character; for termcap
X */
X
Xoutc(ch)
X
Xregister char ch;
X
X{
X    putchar(ch);
X}
/
echo 'Extracting top.1...'
sed 's/^X//' > top.1 << '/'
X.TH TOP 1 Local
X.UC 4
X.SH NAME
Xtop \- display and update information about the top cpu processes
X.SH SYNOPSIS
X.B top
X[
X.BI \-s time
X] [
X.I number
X]
X.SH DESCRIPTION
X.\" This defines appropriate quote strings for nroff and troff
X.ds lq \&"
X.ds rq \&"
X.if t .ds lq ``
X.if t .ds rq ''
X.\" Just in case the command line didn't set these number registers...
X.if !\nN .nr N 10
X.if !\nD .nr D 5
X.I Top
Xdisplays the top \nN processes on the system and periodically
Xupdates this information.  It uses raw cpu percentage
Xto determine the top processes.  The
X.IR curses (3)
Xpackage is used to do semi-optimal screen updating.  If
X.I number
Xis given, then the top
X.I number
Xprocesses will be displayed instead of the default amount of \nN.
XThe
X.B \-s
Xoption sets the delay between screen updates to
X.I time
Xseconds.  The default delay between updates is \nD seconds.
X.PP
XThe top few lines of the display show general information about the state of
Xthe system, including the last process id assigned to a process, the three
Xload averages, the current time, the number of existing processes, the number
Xof processes in each state
X(sleeping, ABANDONED, running, starting, zombies, and stopped),
Xand a percentage of time
Xspent in each of the processor states (user, nice, system, and idle).
X.PP
XThe remainder of the screen displays information about individual
Xprocesses.  This display is similar in spirit to
X.IR ps (1)
Xbut it is not exactly the same.  PID is the process id, USERNAME is the name
Xof the process's owner, PRI is the current priority of the process, NICE is
Xthe nice amount (in the range \-20 to 20), SIZE is the total size of the
Xprocess (text, data, and stack), RES is the current amount of resident
Xmemory (both SIZE and RES are given in kilobytes), STATE is the current
Xstate (one of \*(lqsleep\*(rq, \*(lqWAIT\*(rq, \*(lqrun\*(rq,
X\*(lqidl\*(rq, \*(lqzomb\*(rq, or \*(lqstop\*(rq), TIME is
Xthe number of system and user
Xcpu seconds that the process has used, WCPU is the weighted cpu percentage
X(this is the same value that
X.IR ps (1)
Xdisplays as CPU), CPU is the raw percentage and is the field that is sorted
Xto determine the order of the processes, and COMMAND is the name of the 
Xcommand that the process is currently running (if the process is swapped
Xout, this column is marked \*(lq<swapped>\*(rq).
X.SH NOTES
XThe \*(lqABANDONED\*(rq state (known in the kernel as \*(lqSWAIT\*(rq) was
Xabandoned, thus the name.  A process should never end up in this state.
X.SH AUTHOR
XWilliam LeFebvre, Rice University graduate student
X.SH FILES
X.DT
X/dev/kmem		kernel memory
X.br
X/dev/mem		physical memory
X.br
X/vmunix 		system image
X.SH BUGS
XThe command name for swapped processes should be tracked down, but this
Xwould make the program run slower.
X.PP
XAs with
X.IR ps (1),
Xthings can change while
X.I top
Xis collecting information for an update.  The picture it gives is only a
Xclose approximation to reality.
X.SH "SEE ALSO"
Xps(1), mem(4)
/
echo 'Distribution file top.shar complete.'