diff options
author | Jason Downs <downsj@cvs.openbsd.org> | 1997-08-14 14:00:29 +0000 |
---|---|---|
committer | Jason Downs <downsj@cvs.openbsd.org> | 1997-08-14 14:00:29 +0000 |
commit | 6e46887b7158d427eebc23ab135222ed4df2bdf0 (patch) | |
tree | 3c75f1ac16fc0820700961b0f91b18496054cb6d /usr.bin | |
parent | 44dbc9719c1c86a71535213a3f43c4cee4a4b770 (diff) |
top 3.4, with a few changes. Still needs more work.
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/top/DISCLAIMER | 30 | ||||
-rw-r--r-- | usr.bin/top/Makefile | 22 | ||||
-rw-r--r-- | usr.bin/top/boolean.h | 7 | ||||
-rw-r--r-- | usr.bin/top/commands.c | 511 | ||||
-rw-r--r-- | usr.bin/top/display.c | 1131 | ||||
-rw-r--r-- | usr.bin/top/display.h | 9 | ||||
-rw-r--r-- | usr.bin/top/layout.h | 29 | ||||
-rw-r--r-- | usr.bin/top/loadavg.h | 59 | ||||
-rw-r--r-- | usr.bin/top/machine.c | 818 | ||||
-rw-r--r-- | usr.bin/top/machine.h | 60 | ||||
-rw-r--r-- | usr.bin/top/os.h | 31 | ||||
-rw-r--r-- | usr.bin/top/patchlevel.h | 3 | ||||
-rw-r--r-- | usr.bin/top/screen.c | 496 | ||||
-rw-r--r-- | usr.bin/top/screen.h | 33 | ||||
-rw-r--r-- | usr.bin/top/sigconv.awk | 53 | ||||
-rw-r--r-- | usr.bin/top/top.1 | 321 | ||||
-rw-r--r-- | usr.bin/top/top.c | 998 | ||||
-rw-r--r-- | usr.bin/top/top.h | 38 | ||||
-rw-r--r-- | usr.bin/top/top.local.h | 70 | ||||
-rw-r--r-- | usr.bin/top/username.c | 187 | ||||
-rw-r--r-- | usr.bin/top/utils.c | 457 | ||||
-rw-r--r-- | usr.bin/top/utils.h | 25 | ||||
-rw-r--r-- | usr.bin/top/version.c | 27 |
23 files changed, 5415 insertions, 0 deletions
diff --git a/usr.bin/top/DISCLAIMER b/usr.bin/top/DISCLAIMER new file mode 100644 index 00000000000..eabbe044847 --- /dev/null +++ b/usr.bin/top/DISCLAIMER @@ -0,0 +1,30 @@ +DISCLAIMER + +"top" is distributed free of charge. It should not be considered an +official product of Argonne National Laboratory. William LeFebvre +supports "top" in his spare time and as time permits. + +NO WARRANTY: + +BECAUSE "top" IS DISTRIBUTED FREE OF CHARGE, THERE IS ABSOLUTELY NO +WARRANTY PROVIDED, TO THE EXTENT PERMITTED BY APPLICABLE STATE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING, ARGONNE NATIONAL LABORATORY, +NORTHWESTERN UNIVERSITY, WILLIAM N. LeFEBVRE AND/OR OTHER PARTIES +PROVIDE "top" "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK +AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD +THE "top" PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +IN NO EVENT WILL ARGONNE NATIONAL LABORATORY, NORTHWESTERN UNIVERSITY, +WILLIAM N. LeFEBVRE, AND/OR ANY OTHER PARTY WHO MAY MODIFY AND +REDISTRIBUTE "top", BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY LOST +PROFITS, LOST MONIES, OR OTHER SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE (INCLUDING BUT NOT +LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES +SUSTAINED BY THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH +OTHER PROGRAMS) THE PROGRAM, EVEN IF YOU HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY ANY OTHER PARTY. + +So there! diff --git a/usr.bin/top/Makefile b/usr.bin/top/Makefile new file mode 100644 index 00000000000..aa428c726f2 --- /dev/null +++ b/usr.bin/top/Makefile @@ -0,0 +1,22 @@ +# $OpenBSD: Makefile,v 1.1 1997/08/14 14:00:20 downsj Exp $ +# +# Makefile for OpenBSD top-3.4. + +PROG= top + +CFLAGS+=-I. -I${.CURDIR} +SRCS= commands.c display.c machine.c screen.c top.c username.c utils.c \ + version.c +DPADD= ${LIBTERMLIB} ${LIBM} ${LIBKVM} +LDADD= -ltermlib -lm -lkvm +BINGRP= kmem +BINMODE=2555 + +CLEANFILES+= sigdesc.h + +.depend commands.o: sigdesc.h + +sigdesc.h: + awk -f ${.CURDIR}/sigconv.awk /usr/include/sys/signal.h > sigdesc.h + +.include <bsd.prog.mk> diff --git a/usr.bin/top/boolean.h b/usr.bin/top/boolean.h new file mode 100644 index 00000000000..fd3861cab1c --- /dev/null +++ b/usr.bin/top/boolean.h @@ -0,0 +1,7 @@ +/* $OpenBSD: boolean.h,v 1.1 1997/08/14 14:00:20 downsj Exp $ */ + +/* My favorite names for boolean values */ +#define No 0 +#define Yes 1 +#define Maybe 2 /* tri-state boolean, actually */ + diff --git a/usr.bin/top/commands.c b/usr.bin/top/commands.c new file mode 100644 index 00000000000..a03c4d7ac89 --- /dev/null +++ b/usr.bin/top/commands.c @@ -0,0 +1,511 @@ +/* $OpenBSD: commands.c,v 1.1 1997/08/14 14:00:20 downsj Exp $ */ + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +/* + * This file contains the routines that implement some of the interactive + * mode commands. Note that some of the commands are implemented in-line + * in "main". This is necessary because they change the global state of + * "top" (i.e.: changing the number of processes to display). + */ + +#include "os.h" +#include <ctype.h> +#include <signal.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include "sigdesc.h" /* generated automatically */ +#include "boolean.h" +#include "utils.h" + +extern int errno; + +extern char *copyright; + +/* imported from screen.c */ +extern int overstrike; + +int err_compar(); +char *err_string(); + +/* + * show_help() - display the help screen; invoked in response to + * either 'h' or '?'. + */ + +show_help() + +{ + printf("Top version %s, %s\n", version_string(), copyright); + fputs("\n\n\ +A top users display for Unix\n\ +\n\ +These single-character commands are available:\n\ +\n\ +^L - redraw screen\n\ +q - quit\n\ +h or ? - help; show this text\n", stdout); + + /* not all commands are availalbe with overstrike terminals */ + if (overstrike) + { + fputs("\n\ +Other commands are also available, but this terminal is not\n\ +sophisticated enough to handle those commands gracefully.\n\n", stdout); + } + else + { + fputs("\ +d - change number of displays to show\n\ +e - list errors generated by last \"kill\" or \"renice\" command\n\ +i - toggle the displaying of idle processes\n\ +I - same as 'i'\n\ +k - kill processes; send a signal to a list of processes\n\ +n or # - change number of processes to display\n", stdout); +#ifdef ORDER + fputs("\ +o - specify sort order (size, res, cpu, time)\n", stdout); +#endif + fputs("\ +r - renice a process\n\ +s - change number of seconds to delay between updates\n\ +u - display processes for only one user (+ selects all users)\n\ +\n\ +\n", stdout); + } +} + +/* + * Utility routines that help with some of the commands. + */ + +char *next_field(str) + +register char *str; + +{ + if ((str = strchr(str, ' ')) == NULL) + { + return(NULL); + } + *str = '\0'; + while (*++str == ' ') /* loop */; + + /* if there is nothing left of the string, return NULL */ + /* This fix is dedicated to Greg Earle */ + return(*str == '\0' ? NULL : str); +} + +scanint(str, intp) + +char *str; +int *intp; + +{ + register int val = 0; + register char ch; + + /* if there is nothing left of the string, flag it as an error */ + /* This fix is dedicated to Greg Earle */ + if (*str == '\0') + { + return(-1); + } + + while ((ch = *str++) != '\0') + { + if (isdigit(ch)) + { + val = val * 10 + (ch - '0'); + } + else if (isspace(ch)) + { + break; + } + else + { + return(-1); + } + } + *intp = val; + return(0); +} + +/* + * Some of the commands make system calls that could generate errors. + * These errors are collected up in an array of structures for later + * contemplation and display. Such routines return a string containing an + * error message, or NULL if no errors occurred. The next few routines are + * for manipulating and displaying these errors. We need an upper limit on + * the number of errors, so we arbitrarily choose 20. + */ + +#define ERRMAX 20 + +struct errs /* structure for a system-call error */ +{ + int errno; /* value of errno (that is, the actual error) */ + char *arg; /* argument that caused the error */ +}; + +static struct errs errs[ERRMAX]; +static int errcnt; +static char *err_toomany = " too many errors occurred"; +static char *err_listem = + " Many errors occurred. Press `e' to display the list of errors."; + +/* These macros get used to reset and log the errors */ +#define ERR_RESET errcnt = 0 +#define ERROR(p, e) if (errcnt >= ERRMAX) \ + { \ + return(err_toomany); \ + } \ + else \ + { \ + errs[errcnt].arg = (p); \ + errs[errcnt++].errno = (e); \ + } + +/* + * err_string() - return an appropriate error string. This is what the + * command will return for displaying. If no errors were logged, then + * return NULL. The maximum length of the error string is defined by + * "STRMAX". + */ + +#define STRMAX 80 + +char *err_string() + +{ + register struct errs *errp; + register int cnt = 0; + register int first = Yes; + register int currerr = -1; + int stringlen; /* characters still available in "string" */ + static char string[STRMAX]; + + /* if there are no errors, return NULL */ + if (errcnt == 0) + { + return(NULL); + } + + /* sort the errors */ + qsort((char *)errs, errcnt, sizeof(struct errs), err_compar); + + /* need a space at the front of the error string */ + string[0] = ' '; + string[1] = '\0'; + stringlen = STRMAX - 2; + + /* loop thru the sorted list, building an error string */ + while (cnt < errcnt) + { + errp = &(errs[cnt++]); + if (errp->errno != currerr) + { + if (currerr != -1) + { + if ((stringlen = str_adderr(string, stringlen, currerr)) < 2) + { + return(err_listem); + } + (void) strcat(string, "; "); /* we know there's more */ + } + currerr = errp->errno; + first = Yes; + } + if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0) + { + return(err_listem); + } + first = No; + } + + /* add final message */ + stringlen = str_adderr(string, stringlen, currerr); + + /* return the error string */ + return(stringlen == 0 ? err_listem : string); +} + +/* + * str_adderr(str, len, err) - add an explanation of error "err" to + * the string "str". + */ + +str_adderr(str, len, err) + +char *str; +int len; +int err; + +{ + register char *msg; + register int msglen; + + msg = err == 0 ? "Not a number" : errmsg(err); + msglen = strlen(msg) + 2; + if (len <= msglen) + { + return(0); + } + (void) strcat(str, ": "); + (void) strcat(str, msg); + return(len - msglen); +} + +/* + * str_addarg(str, len, arg, first) - add the string argument "arg" to + * the string "str". This is the first in the group when "first" + * is set (indicating that a comma should NOT be added to the front). + */ + +str_addarg(str, len, arg, first) + +char *str; +int len; +char *arg; +int first; + +{ + register int arglen; + + arglen = strlen(arg); + if (!first) + { + arglen += 2; + } + if (len <= arglen) + { + return(0); + } + if (!first) + { + (void) strcat(str, ", "); + } + (void) strcat(str, arg); + return(len - arglen); +} + +/* + * err_compar(p1, p2) - comparison routine used by "qsort" + * for sorting errors. + */ + +err_compar(p1, p2) + +register struct errs *p1, *p2; + +{ + register int result; + + if ((result = p1->errno - p2->errno) == 0) + { + return(strcmp(p1->arg, p2->arg)); + } + return(result); +} + +/* + * error_count() - return the number of errors currently logged. + */ + +error_count() + +{ + return(errcnt); +} + +/* + * show_errors() - display on stdout the current log of errors. + */ + +show_errors() + +{ + register int cnt = 0; + register struct errs *errp = errs; + + printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); + while (cnt++ < errcnt) + { + printf("%5s: %s\n", errp->arg, + errp->errno == 0 ? "Not a number" : errmsg(errp->errno)); + errp++; + } +} + +/* + * kill_procs(str) - send signals to processes, much like the "kill" + * command does; invoked in response to 'k'. + */ + +char *kill_procs(str) + +char *str; + +{ + register char *nptr; + int signum = SIGTERM; /* default */ + int procnum; + struct sigdesc *sigp; + int uid; + + /* reset error array */ + ERR_RESET; + + /* remember our uid */ + uid = getuid(); + + /* skip over leading white space */ + while (isspace(*str)) str++; + + if (str[0] == '-') + { + /* explicit signal specified */ + if ((nptr = next_field(str)) == NULL) + { + return(" kill: no processes specified"); + } + + if (isdigit(str[1])) + { + (void) scanint(str + 1, &signum); + if (signum <= 0 || signum >= NSIG) + { + return(" invalid signal number"); + } + } + else + { + /* translate the name into a number */ + for (sigp = sigdesc; sigp->name != NULL; sigp++) + { + if (strcmp(sigp->name, str + 1) == 0) + { + signum = sigp->number; + break; + } + } + + /* was it ever found */ + if (sigp->name == NULL) + { + return(" bad signal name"); + } + } + /* put the new pointer in place */ + str = nptr; + } + + /* loop thru the string, killing processes */ + do + { + if (scanint(str, &procnum) == -1) + { + ERROR(str, 0); + } + else + { + /* check process owner if we're not root */ + if (uid && (uid != proc_owner(procnum))) + { + ERROR(str, EACCES); + } + /* go in for the kill */ + else if (kill(procnum, signum) == -1) + { + /* chalk up an error */ + ERROR(str, errno); + } + } + } while ((str = next_field(str)) != NULL); + + /* return appropriate error string */ + return(err_string()); +} + +/* + * renice_procs(str) - change the "nice" of processes, much like the + * "renice" command does; invoked in response to 'r'. + */ + +char *renice_procs(str) + +char *str; + +{ + register char negate; + int prio; + int procnum; + int uid; + + ERR_RESET; + uid = getuid(); + + /* allow for negative priority values */ + if ((negate = (*str == '-')) != 0) + { + /* move past the minus sign */ + str++; + } + + /* use procnum as a temporary holding place and get the number */ + procnum = scanint(str, &prio); + + /* negate if necessary */ + if (negate) + { + prio = -prio; + } + +#if defined(PRIO_MIN) && defined(PRIO_MAX) + /* check for validity */ + if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) + { + return(" bad priority value"); + } +#endif + + /* move to the first process number */ + if ((str = next_field(str)) == NULL) + { + return(" no processes specified"); + } + + /* loop thru the process numbers, renicing each one */ + do + { + if (scanint(str, &procnum) == -1) + { + ERROR(str, 0); + } + + /* check process owner if we're not root */ + else if (uid && (uid != proc_owner(procnum))) + { + ERROR(str, EACCES); + } + else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) + { + ERROR(str, errno); + } + } while ((str = next_field(str)) != NULL); + + /* return appropriate error string */ + return(err_string()); +} + diff --git a/usr.bin/top/display.c b/usr.bin/top/display.c new file mode 100644 index 00000000000..8511187a6f1 --- /dev/null +++ b/usr.bin/top/display.c @@ -0,0 +1,1131 @@ +/* $OpenBSD: display.c,v 1.1 1997/08/14 14:00:21 downsj Exp $ */ + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +/* + * This file contains the routines that display information on the screen. + * Each section of the screen has two routines: one for initially writing + * all constant and dynamic text, and one for only updating the text that + * changes. The prefix "i_" is used on all the "initial" routines and the + * prefix "u_" is used for all the "updating" routines. + * + * ASSUMPTIONS: + * None of the "i_" routines use any of the termcap capabilities. + * In this way, those routines can be safely used on terminals that + * have minimal (or nonexistant) terminal capabilities. + * + * The routines are called in this order: *_loadave, i_timeofday, + * *_procstates, *_cpustates, *_memory, *_message, *_header, + * *_process, u_endscreen. + */ + +#include "os.h" +#include <ctype.h> +#include <time.h> + +#include "screen.h" /* interface to screen package */ +#include "layout.h" /* defines for screen position layout */ +#include "display.h" +#include "top.h" +#include "top.local.h" +#include "boolean.h" +#include "machine.h" /* we should eliminate this!!! */ +#include "utils.h" + +#ifdef DEBUG +FILE *debug; +#endif + +/* imported from screen.c */ +extern int overstrike; + +static int lmpid = 0; +static int last_hi = 0; /* used in u_process and u_endscreen */ +static int lastline = 0; +static int display_width = MAX_COLS; + +#define lineindex(l) ((l)*display_width) + +char *printable(); + +/* things initialized by display_init and used thruout */ + +/* buffer of proc information lines for display updating */ +char *screenbuf = NULL; + +static char **procstate_names; +static char **cpustate_names; +static char **memory_names; + +static int num_procstates; +static int num_cpustates; +static int num_memory; + +static int *lprocstates; +static int *lcpustates; +static int *lmemory; + +static int *cpustate_columns; +static int cpustate_total_length; + +static enum { OFF, ON, ERASE } header_status = ON; + +static int string_count(); +static void summary_format(); +static void line_update(); + +int display_resize() + +{ + register int lines; + + /* first, deallocate any previous buffer that may have been there */ + if (screenbuf != NULL) + { + free(screenbuf); + } + + /* calculate the current dimensions */ + /* if operating in "dumb" mode, we only need one line */ + lines = smart_terminal ? screen_length - Header_lines : 1; + + /* we don't want more than MAX_COLS columns, since the machine-dependent + modules make static allocations based on MAX_COLS and we don't want + to run off the end of their buffers */ + display_width = screen_width; + if (display_width >= MAX_COLS) + { + display_width = MAX_COLS - 1; + } + + /* now, allocate space for the screen buffer */ + screenbuf = (char *)malloc(lines * display_width); + if (screenbuf == (char *)NULL) + { + /* oops! */ + return(-1); + } + + /* return number of lines available */ + /* for dumb terminals, pretend like we can show any amount */ + return(smart_terminal ? lines : Largest); +} + +int display_init(statics) + +struct statics *statics; + +{ + register int lines; + register char **pp; + register int *ip; + register int i; + + /* call resize to do the dirty work */ + lines = display_resize(); + + /* only do the rest if we need to */ + if (lines > -1) + { + /* save pointers and allocate space for names */ + procstate_names = statics->procstate_names; + num_procstates = string_count(procstate_names); + lprocstates = (int *)malloc(num_procstates * sizeof(int)); + + cpustate_names = statics->cpustate_names; + num_cpustates = string_count(cpustate_names); + lcpustates = (int *)malloc(num_cpustates * sizeof(int)); + cpustate_columns = (int *)malloc(num_cpustates * sizeof(int)); + + memory_names = statics->memory_names; + num_memory = string_count(memory_names); + lmemory = (int *)malloc(num_memory * sizeof(int)); + + /* calculate starting columns where needed */ + cpustate_total_length = 0; + pp = cpustate_names; + ip = cpustate_columns; + while (*pp != NULL) + { + if ((i = strlen(*pp++)) > 0) + { + *ip++ = cpustate_total_length; + cpustate_total_length += i + 8; + } + } + } + + /* return number of lines available */ + return(lines); +} + +i_loadave(mpid, avenrun) + +int mpid; +double *avenrun; + +{ + register int i; + + /* i_loadave also clears the screen, since it is first */ + clear(); + + /* mpid == -1 implies this system doesn't have an _mpid */ + if (mpid != -1) + { + printf("last pid: %5d; ", mpid); + } + + printf("load averages"); + + for (i = 0; i < 3; i++) + { + printf("%c %5.2f", + i == 0 ? ':' : ',', + avenrun[i]); + } + lmpid = mpid; +} + +u_loadave(mpid, avenrun) + +int mpid; +double *avenrun; + +{ + register int i; + + if (mpid != -1) + { + /* change screen only when value has really changed */ + if (mpid != lmpid) + { + Move_to(x_lastpid, y_lastpid); + printf("%5d", mpid); + lmpid = mpid; + } + + /* i remembers x coordinate to move to */ + i = x_loadave; + } + else + { + i = x_loadave_nompid; + } + + /* move into position for load averages */ + Move_to(i, y_loadave); + + /* display new load averages */ + /* we should optimize this and only display changes */ + for (i = 0; i < 3; i++) + { + printf("%s%5.2f", + i == 0 ? "" : ", ", + avenrun[i]); + } +} + +i_timeofday(tod) + +time_t *tod; + +{ + /* + * Display the current time. + * "ctime" always returns a string that looks like this: + * + * Sun Sep 16 01:03:52 1973 + * 012345678901234567890123 + * 1 2 + * + * We want indices 11 thru 18 (length 8). + */ + + if (smart_terminal) + { + Move_to(screen_width - 8, 0); + } + else + { + fputs(" ", stdout); + } +#ifdef DEBUG + { + char *foo; + foo = ctime(tod); + fputs(foo, stdout); + } +#endif + printf("%-8.8s\n", &(ctime(tod)[11])); + lastline = 1; +} + +static int ltotal = 0; +static char procstates_buffer[128]; + +/* + * *_procstates(total, brkdn, names) - print the process summary line + * + * Assumptions: cursor is at the beginning of the line on entry + * lastline is valid + */ + +i_procstates(total, brkdn) + +int total; +int *brkdn; + +{ + register int i; + + /* write current number of processes and remember the value */ + printf("%d processes:", total); + ltotal = total; + + /* put out enough spaces to get to column 15 */ + i = digits(total); + while (i++ < 4) + { + putchar(' '); + } + + /* format and print the process state summary */ + summary_format(procstates_buffer, brkdn, procstate_names); + fputs(procstates_buffer, stdout); + + /* save the numbers for next time */ + memcpy(lprocstates, brkdn, num_procstates * sizeof(int)); +} + +u_procstates(total, brkdn) + +int total; +int *brkdn; + +{ + static char new[128]; + register int i; + + /* update number of processes only if it has changed */ + if (ltotal != total) + { + /* move and overwrite */ +#if (x_procstate == 0) + Move_to(x_procstate, y_procstate); +#else + /* cursor is already there...no motion needed */ + /* assert(lastline == 1); */ +#endif + printf("%d", total); + + /* if number of digits differs, rewrite the label */ + if (digits(total) != digits(ltotal)) + { + fputs(" processes:", stdout); + /* put out enough spaces to get to column 15 */ + i = digits(total); + while (i++ < 4) + { + putchar(' '); + } + /* cursor may end up right where we want it!!! */ + } + + /* save new total */ + ltotal = total; + } + + /* see if any of the state numbers has changed */ + if (memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0) + { + /* format and update the line */ + summary_format(new, brkdn, procstate_names); + line_update(procstates_buffer, new, x_brkdn, y_brkdn); + memcpy(lprocstates, brkdn, num_procstates * sizeof(int)); + } +} + +/* + * *_cpustates(states, names) - print the cpu state percentages + * + * Assumptions: cursor is on the PREVIOUS line + */ + +static int cpustates_column; + +/* cpustates_tag() calculates the correct tag to use to label the line */ + +char *cpustates_tag() + +{ + register char *use; + + static char *short_tag = "CPU: "; + static char *long_tag = "CPU states: "; + + /* if length + strlen(long_tag) >= screen_width, then we have to + use the shorter tag (we subtract 2 to account for ": ") */ + if (cpustate_total_length + (int)strlen(long_tag) - 2 >= screen_width) + { + use = short_tag; + } + else + { + use = long_tag; + } + + /* set cpustates_column accordingly then return result */ + cpustates_column = strlen(use); + return(use); +} + +i_cpustates(states) + +register int *states; + +{ + register int i = 0; + register int value; + register char **names = cpustate_names; + register char *thisname; + + /* print tag and bump lastline */ + printf("\n%s", cpustates_tag()); + lastline++; + + /* now walk thru the names and print the line */ + while ((thisname = *names++) != NULL) + { + if (*thisname != '\0') + { + /* retrieve the value and remember it */ + value = *states++; + + /* if percentage is >= 1000, print it as 100% */ + printf((value >= 1000 ? "%s%4.0f%% %s" : "%s%4.1f%% %s"), + i++ == 0 ? "" : ", ", + ((float)value)/10., + thisname); + } + } + + /* copy over values into "last" array */ + memcpy(lcpustates, states, num_cpustates * sizeof(int)); +} + +u_cpustates(states) + +register int *states; + +{ + register int value; + register char **names = cpustate_names; + register char *thisname; + register int *lp; + register int *colp; + + Move_to(cpustates_column, y_cpustates); + lastline = y_cpustates; + lp = lcpustates; + colp = cpustate_columns; + + /* we could be much more optimal about this */ + while ((thisname = *names++) != NULL) + { + if (*thisname != '\0') + { + /* did the value change since last time? */ + if (*lp != *states) + { + /* yes, move and change */ + Move_to(cpustates_column + *colp, y_cpustates); + lastline = y_cpustates; + + /* retrieve value and remember it */ + value = *states; + + /* if percentage is >= 1000, print it as 100% */ + printf((value >= 1000 ? "%4.0f" : "%4.1f"), + ((double)value)/10.); + + /* remember it for next time */ + *lp = *states; + } + } + + /* increment and move on */ + lp++; + states++; + colp++; + } +} + +z_cpustates() + +{ + register int i = 0; + register char **names = cpustate_names; + register char *thisname; + register int *lp; + + /* show tag and bump lastline */ + printf("\n%s", cpustates_tag()); + lastline++; + + while ((thisname = *names++) != NULL) + { + if (*thisname != '\0') + { + printf("%s %% %s", i++ == 0 ? "" : ", ", thisname); + } + } + + /* fill the "last" array with all -1s, to insure correct updating */ + lp = lcpustates; + i = num_cpustates; + while (--i >= 0) + { + *lp++ = -1; + } +} + +/* + * *_memory(stats) - print "Memory: " followed by the memory summary string + * + * Assumptions: cursor is on "lastline" + * for i_memory ONLY: cursor is on the previous line + */ + +char memory_buffer[MAX_COLS]; + +i_memory(stats) + +int *stats; + +{ + fputs("\nMemory: ", stdout); + lastline++; + + /* format and print the memory summary */ + summary_format(memory_buffer, stats, memory_names); + fputs(memory_buffer, stdout); +} + +u_memory(stats) + +int *stats; + +{ + static char new[MAX_COLS]; + + /* format the new line */ + summary_format(new, stats, memory_names); + line_update(memory_buffer, new, x_mem, y_mem); +} + +/* + * *_message() - print the next pending message line, or erase the one + * that is there. + * + * Note that u_message is (currently) the same as i_message. + * + * Assumptions: lastline is consistent + */ + +/* + * i_message is funny because it gets its message asynchronously (with + * respect to screen updates). + */ + +static char next_msg[MAX_COLS + 5]; +static int msglen = 0; +/* Invariant: msglen is always the length of the message currently displayed + on the screen (even when next_msg doesn't contain that message). */ + +i_message() + +{ + while (lastline < y_message) + { + fputc('\n', stdout); + lastline++; + } + if (next_msg[0] != '\0') + { + standout(next_msg); + msglen = strlen(next_msg); + next_msg[0] = '\0'; + } + else if (msglen > 0) + { + (void) clear_eol(msglen); + msglen = 0; + } +} + +u_message() + +{ + i_message(); +} + +static int header_length; + +/* + * *_header(text) - print the header for the process area + * + * Assumptions: cursor is on the previous line and lastline is consistent + */ + +i_header(text) + +char *text; + +{ + header_length = strlen(text); + if (header_status == ON) + { + putchar('\n'); + fputs(text, stdout); + lastline++; + } + else if (header_status == ERASE) + { + header_status = OFF; + } +} + +/*ARGSUSED*/ +u_header(text) + +char *text; /* ignored */ + +{ + if (header_status == ERASE) + { + putchar('\n'); + lastline++; + clear_eol(header_length); + header_status = OFF; + } +} + +/* + * *_process(line, thisline) - print one process line + * + * Assumptions: lastline is consistent + */ + +i_process(line, thisline) + +int line; +char *thisline; + +{ + register char *p; + register char *base; + + /* make sure we are on the correct line */ + while (lastline < y_procs + line) + { + putchar('\n'); + lastline++; + } + + /* truncate the line to conform to our current screen width */ + thisline[display_width] = '\0'; + + /* write the line out */ + fputs(thisline, stdout); + + /* copy it in to our buffer */ + base = smart_terminal ? screenbuf + lineindex(line) : screenbuf; + p = strecpy(base, thisline); + + /* zero fill the rest of it */ + memzero(p, display_width - (p - base)); +} + +u_process(line, newline) + +int line; +char *newline; + +{ + register char *optr; + register int screen_line = line + Header_lines; + register char *bufferline; + + /* remember a pointer to the current line in the screen buffer */ + bufferline = &screenbuf[lineindex(line)]; + + /* truncate the line to conform to our current screen width */ + newline[display_width] = '\0'; + + /* is line higher than we went on the last display? */ + if (line >= last_hi) + { + /* yes, just ignore screenbuf and write it out directly */ + /* get positioned on the correct line */ + if (screen_line - lastline == 1) + { + putchar('\n'); + lastline++; + } + else + { + Move_to(0, screen_line); + lastline = screen_line; + } + + /* now write the line */ + fputs(newline, stdout); + + /* copy it in to the buffer */ + optr = strecpy(bufferline, newline); + + /* zero fill the rest of it */ + memzero(optr, display_width - (optr - bufferline)); + } + else + { + line_update(bufferline, newline, 0, line + Header_lines); + } +} + +u_endscreen(hi) + +register int hi; + +{ + register int screen_line = hi + Header_lines; + register int i; + + if (smart_terminal) + { + if (hi < last_hi) + { + /* need to blank the remainder of the screen */ + /* but only if there is any screen left below this line */ + if (lastline + 1 < screen_length) + { + /* efficiently move to the end of currently displayed info */ + if (screen_line - lastline < 5) + { + while (lastline < screen_line) + { + putchar('\n'); + lastline++; + } + } + else + { + Move_to(0, screen_line); + lastline = screen_line; + } + + if (clear_to_end) + { + /* we can do this the easy way */ + putcap(clear_to_end); + } + else + { + /* use clear_eol on each line */ + i = hi; + while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi) + { + putchar('\n'); + } + } + } + } + last_hi = hi; + + /* move the cursor to a pleasant place */ + Move_to(x_idlecursor, y_idlecursor); + lastline = y_idlecursor; + } + else + { + /* separate this display from the next with some vertical room */ + fputs("\n\n", stdout); + } +} + +display_header(t) + +int t; + +{ + if (t) + { + header_status = ON; + } + else if (header_status == ON) + { + header_status = ERASE; + } +} + +/*VARARGS2*/ +new_message(type, msgfmt, a1, a2, a3) + +int type; +char *msgfmt; +caddr_t a1, a2, a3; + +{ + register int i; + + /* first, format the message */ + (void) snprintf(next_msg, sizeof(next_msg), msgfmt, a1, a2, a3); + + if (msglen > 0) + { + /* message there already -- can we clear it? */ + if (!overstrike) + { + /* yes -- write it and clear to end */ + i = strlen(next_msg); + if ((type & MT_delayed) == 0) + { + type & MT_standout ? standout(next_msg) : + fputs(next_msg, stdout); + (void) clear_eol(msglen - i); + msglen = i; + next_msg[0] = '\0'; + } + } + } + else + { + if ((type & MT_delayed) == 0) + { + type & MT_standout ? standout(next_msg) : fputs(next_msg, stdout); + msglen = strlen(next_msg); + next_msg[0] = '\0'; + } + } +} + +clear_message() + +{ + if (clear_eol(msglen) == 1) + { + putchar('\r'); + } +} + +readline(buffer, size, numeric) + +char *buffer; +int size; +int numeric; + +{ + register char *ptr = buffer; + register char ch; + register char cnt = 0; + register char maxcnt = 0; + + /* allow room for null terminator */ + size -= 1; + + /* read loop */ + while ((fflush(stdout), read(0, ptr, 1) > 0)) + { + /* newline means we are done */ + if ((ch = *ptr) == '\n') + { + break; + } + + /* handle special editing characters */ + if (ch == ch_kill) + { + /* kill line -- account for overstriking */ + if (overstrike) + { + msglen += maxcnt; + } + + /* return null string */ + *buffer = '\0'; + putchar('\r'); + return(-1); + } + else if (ch == ch_erase) + { + /* erase previous character */ + if (cnt <= 0) + { + /* none to erase! */ + putchar('\7'); + } + else + { + fputs("\b \b", stdout); + ptr--; + cnt--; + } + } + /* check for character validity and buffer overflow */ + else if (cnt == size || (numeric && !isdigit(ch)) || + !isprint(ch)) + { + /* not legal */ + putchar('\7'); + } + else + { + /* echo it and store it in the buffer */ + putchar(ch); + ptr++; + cnt++; + if (cnt > maxcnt) + { + maxcnt = cnt; + } + } + } + + /* all done -- null terminate the string */ + *ptr = '\0'; + + /* account for the extra characters in the message area */ + /* (if terminal overstrikes, remember the furthest they went) */ + msglen += overstrike ? maxcnt : cnt; + + /* return either inputted number or string length */ + putchar('\r'); + return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt); +} + +/* internal support routines */ + +static int string_count(pp) + +register char **pp; + +{ + register int cnt; + + cnt = 0; + while (*pp++ != NULL) + { + cnt++; + } + return(cnt); +} + +static void summary_format(str, numbers, names) + +char *str; +int *numbers; +register char **names; + +{ + register char *p; + register int num; + register char *thisname; + register int useM = No; + + /* format each number followed by its string */ + p = str; + while ((thisname = *names++) != NULL) + { + /* get the number to format */ + num = *numbers++; + + /* display only non-zero numbers */ + if (num > 0) + { + /* is this number in kilobytes? */ + if (thisname[0] == 'K') + { + /* yes: format it as a memory value */ + p = strecpy(p, format_k(num)); + + /* skip over the K, since it was included by format_k */ + p = strecpy(p, thisname+1); + } + else + { + p = strecpy(p, itoa(num)); + p = strecpy(p, thisname); + } + } + + /* ignore negative numbers, but display corresponding string */ + else if (num < 0) + { + p = strecpy(p, thisname); + } + } + + /* if the last two characters in the string are ", ", delete them */ + p -= 2; + if (p >= str && p[0] == ',' && p[1] == ' ') + { + *p = '\0'; + } +} + +static void line_update(old, new, start, line) + +register char *old; +register char *new; +int start; +int line; + +{ + register int ch; + register int diff; + register int newcol = start + 1; + register int lastcol = start; + char cursor_on_line = No; + char *current; + + /* compare the two strings and only rewrite what has changed */ + current = old; +#ifdef DEBUG + fprintf(debug, "line_update, starting at %d\n", start); + fputs(old, debug); + fputc('\n', debug); + fputs(new, debug); + fputs("\n-\n", debug); +#endif + + /* start things off on the right foot */ + /* this is to make sure the invariants get set up right */ + if ((ch = *new++) != *old) + { + if (line - lastline == 1 && start == 0) + { + putchar('\n'); + } + else + { + Move_to(start, line); + } + cursor_on_line = Yes; + putchar(ch); + *old = ch; + lastcol = 1; + } + old++; + + /* + * main loop -- check each character. If the old and new aren't the + * same, then update the display. When the distance from the + * current cursor position to the new change is small enough, + * the characters that belong there are written to move the + * cursor over. + * + * Invariants: + * lastcol is the column where the cursor currently is sitting + * (always one beyond the end of the last mismatch). + */ + do /* yes, a do...while */ + { + if ((ch = *new++) != *old) + { + /* new character is different from old */ + /* make sure the cursor is on top of this character */ + diff = newcol - lastcol; + if (diff > 0) + { + /* some motion is required--figure out which is shorter */ + if (diff < 6 && cursor_on_line) + { + /* overwrite old stuff--get it out of the old buffer */ + printf("%.*s", diff, ¤t[lastcol-start]); + } + else + { + /* use cursor addressing */ + Move_to(newcol, line); + cursor_on_line = Yes; + } + /* remember where the cursor is */ + lastcol = newcol + 1; + } + else + { + /* already there, update position */ + lastcol++; + } + + /* write what we need to */ + if (ch == '\0') + { + /* at the end--terminate with a clear-to-end-of-line */ + (void) clear_eol(strlen(old)); + } + else + { + /* write the new character */ + putchar(ch); + } + /* put the new character in the screen buffer */ + *old = ch; + } + + /* update working column and screen buffer pointer */ + newcol++; + old++; + + } while (ch != '\0'); + + /* zero out the rest of the line buffer -- MUST BE DONE! */ + diff = display_width - newcol; + if (diff > 0) + { + memzero(old, diff); + } + + /* remember where the current line is */ + if (cursor_on_line) + { + lastline = line; + } +} + +/* + * printable(str) - make the string pointed to by "str" into one that is + * printable (i.e.: all ascii), by converting all non-printable + * characters into '?'. Replacements are done in place and a pointer + * to the original buffer is returned. + */ + +char *printable(str) + +char *str; + +{ + register char *ptr; + register char ch; + + ptr = str; + while ((ch = *ptr) != '\0') + { + if (!isprint(ch)) + { + *ptr = '?'; + } + ptr++; + } + return(str); +} diff --git a/usr.bin/top/display.h b/usr.bin/top/display.h new file mode 100644 index 00000000000..12d602e069f --- /dev/null +++ b/usr.bin/top/display.h @@ -0,0 +1,9 @@ +/* $OpenBSD: display.h,v 1.1 1997/08/14 14:00:21 downsj Exp $ */ + +/* constants needed for display.c */ + +/* "type" argument for new_message function */ + +#define MT_standout 1 +#define MT_delayed 2 + diff --git a/usr.bin/top/layout.h b/usr.bin/top/layout.h new file mode 100644 index 00000000000..3fe86f8a11a --- /dev/null +++ b/usr.bin/top/layout.h @@ -0,0 +1,29 @@ +/* $OpenBSD: layout.h,v 1.1 1997/08/14 14:00:22 downsj Exp $ */ + +/* + * Top - a top users display for Berkeley Unix + * + * This file defines the locations on tne screen for various parts of the + * display. These definitions are used by the routines in "display.c" for + * cursor addressing. + */ + +#define x_lastpid 10 +#define y_lastpid 0 +#define x_loadave 33 +#define x_loadave_nompid 15 +#define y_loadave 0 +#define x_procstate 0 +#define y_procstate 1 +#define x_brkdn 15 +#define y_brkdn 1 +#define x_mem 8 +#define y_mem 3 +#define y_message 4 +#define x_header 0 +#define y_header 5 +#define x_idlecursor 0 +#define y_idlecursor 4 +#define y_procs 6 + +#define y_cpustates 2 diff --git a/usr.bin/top/loadavg.h b/usr.bin/top/loadavg.h new file mode 100644 index 00000000000..fee726e7307 --- /dev/null +++ b/usr.bin/top/loadavg.h @@ -0,0 +1,59 @@ +/* $OpenBSD: loadavg.h,v 1.1 1997/08/14 14:00:22 downsj Exp $ */ + +/* + * Top - a top users display for Berkeley Unix + * + * Defines required to access load average figures. + * + * This include file sets up everything we need to access the load average + * values in the kernel in a machine independent way. First, it sets the + * typedef "load_avg" to be either double or long (depending on what is + * needed), then it defines these macros appropriately: + * + * loaddouble(la) - convert load_avg to double. + * intload(i) - convert integer to load_avg. + */ + +/* + * We assume that if FSCALE is defined, then avenrun and ccpu are type long. + * If your machine is an exception (mips, perhaps?) then make adjustments + * here. + * + * Defined types: load_avg for load averages, pctcpu for cpu percentages. + */ +#if defined(mips) && !defined(NetBSD) +# include <sys/fixpoint.h> +# if defined(FBITS) && !defined(FSCALE) +# define FSCALE (1 << FBITS) /* mips */ +# endif +#endif + +#ifdef FSCALE +# define FIXED_LOADAVG FSCALE +# define FIXED_PCTCPU FSCALE +#endif + +#ifdef ibm032 +# undef FIXED_LOADAVG +# undef FIXED_PCTCPU +# define FIXED_PCTCPU PCT_SCALE +#endif + + +#ifdef FIXED_PCTCPU + typedef long pctcpu; +# define pctdouble(p) ((double)(p) / FIXED_PCTCPU) +#else +typedef double pctcpu; +# define pctdouble(p) (p) +#endif + +#ifdef FIXED_LOADAVG + typedef long load_avg; +# define loaddouble(la) ((double)(la) / FIXED_LOADAVG) +# define intload(i) ((int)((i) * FIXED_LOADAVG)) +#else + typedef double load_avg; +# define loaddouble(la) (la) +# define intload(i) ((double)(i)) +#endif diff --git a/usr.bin/top/machine.c b/usr.bin/top/machine.c new file mode 100644 index 00000000000..b8def959ae0 --- /dev/null +++ b/usr.bin/top/machine.c @@ -0,0 +1,818 @@ +/* $OpenBSD: machine.c,v 1.1 1997/08/14 14:00:22 downsj Exp $ */ + +/* + * top - a top users display for Unix + * + * SYNOPSIS: For an OpenBSD system + * + * DESCRIPTION: + * This is the machine-dependent module for OpenBSD + * Tested on: + * i386 + * + * LIBS: -lkvm + * + * TERMCAP: -ltermlib + * + * CFLAGS: -DHAVE_GETOPT + * + * AUTHOR: Thorsten Lockert <tholo@sigmasoft.com> + * Adapted from BSD4.4 by Christos Zoulas <christos@ee.cornell.edu> + * Patch for process wait display by Jarl F. Greipsland <jarle@idt.unit.no> + */ + +#include <sys/types.h> +#include <sys/signal.h> +#include <sys/param.h> + +#define LASTPID +#define DOSWAP + +#include "os.h" +#include <stdio.h> +#include <stdlib.h> +#include <nlist.h> +#include <math.h> +#include <kvm.h> +#include <unistd.h> +#include <sys/errno.h> +#include <sys/sysctl.h> +#include <sys/dir.h> +#include <sys/dkstat.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/resource.h> + +#ifdef DOSWAP +#include <err.h> +#include <sys/map.h> +#include <sys/conf.h> +#endif + +static int check_nlist __P((struct nlist *)); +static int getkval __P((unsigned long, int *, int, char *)); +static int swapmode __P((int *, int *)); +extern char* printable __P((char *)); + +#include "top.h" +#include "machine.h" +#include "utils.h" + +/* get_process_info passes back a handle. This is what it looks like: */ + +struct handle +{ + struct kinfo_proc **next_proc; /* points to next valid proc pointer */ + int remaining; /* number of pointers remaining */ +}; + +/* declarations for load_avg */ +#include "loadavg.h" + +#define PP(pp, field) ((pp)->kp_proc . field) +#define EP(pp, field) ((pp)->kp_eproc . field) +#define VP(pp, field) ((pp)->kp_eproc.e_vm . field) + +/* what we consider to be process size: */ +#define PROCSIZE(pp) (VP((pp), vm_tsize) + VP((pp), vm_dsize) + VP((pp), vm_ssize)) + +/* definitions for indices in the nlist array */ +#define X_CP_TIME 0 +#define X_HZ 1 + +#ifdef DOSWAP +#define VM_SWAPMAP 2 +#define VM_NSWAPMAP 3 +#define VM_SWDEVT 4 +#define VM_NSWAP 5 +#define VM_NSWDEV 6 +#define VM_DMMAX 7 +#define VM_NISWAP 8 +#define VM_NISWDEV 9 + +#define X_LASTPID 10 +#elif defined(LASTPID) +#define X_LASTPID 2 +#endif + +static struct nlist nlst[] = { + { "_cp_time" }, /* 0 */ + { "_hz" }, /* 1 */ +#ifdef DOSWAP + { "_swapmap" }, /* 2 */ + { "_nswapmap" }, /* 3 */ + { "_swdevt" }, /* 4 */ + { "_nswap" }, /* 5 */ + { "_nswdev" }, /* 6 */ + { "_dmmax" }, /* 7 */ + { "_niswap" }, /* 8 */ + { "_niswdev" }, /* 9 */ +#endif +#ifdef LASTPID + { "_lastpid" }, /* 2 / 10 */ +#endif + { 0 } +}; + +/* + * These definitions control the format of the per-process area + */ + +static char header[] = + " PID X PRI NICE SIZE RES STATE WAIT TIME CPU COMMAND"; +/* 0123456 -- field to fill in starts at header+6 */ +#define UNAME_START 6 + +#define Proc_format \ + "%5d %-8.8s %3d %4d %5s %5s %-5s %-6.6s %6s %5.2f%% %.14s" + + +/* process state names for the "STATE" column of the display */ +/* the extra nulls in the string "run" are for adding a slash and + the processor number when needed */ + +char *state_abbrev[] = +{ + "", "start", "run\0\0\0", "sleep", "stop", "zomb", +}; + + +static kvm_t *kd; + +/* these are retrieved from the kernel in _init */ + +static long hz; + +/* these are offsets obtained via nlist and used in the get_ functions */ + +static unsigned long cp_time_offset; +#ifdef LASTPID +static unsigned long lastpid_offset; +static pid_t lastpid; +#endif + +/* these are for calculating cpu state percentages */ +static long cp_time[CPUSTATES]; +static long cp_old[CPUSTATES]; +static long cp_diff[CPUSTATES]; + +/* these are for detailing the process states */ + +int process_states[7]; +char *procstatenames[] = { + "", " starting, ", " running, ", " idle, ", " stopped, ", " zombie, ", + NULL +}; + +/* these are for detailing the cpu states */ + +int cpu_states[CPUSTATES]; +char *cpustatenames[] = { + "user", "nice", "system", "interrupt", "idle", NULL +}; + +/* these are for detailing the memory statistics */ + +int memory_stats[8]; +char *memorynames[] = { + "Real: ", "K/", "K act/tot ", "Free: ", "K ", +#ifdef DOSWAP + "Swap: ", "K/", "K used/tot", +#endif + NULL +}; + +/* these are for keeping track of the proc array */ + +static int nproc; +static int onproc = -1; +static int pref_len; +static struct kinfo_proc *pbase; +static struct kinfo_proc **pref; + +/* these are for getting the memory statistics */ + +static int pageshift; /* log base 2 of the pagesize */ + +/* define pagetok in terms of pageshift */ + +#define pagetok(size) ((size) << pageshift) + +int +machine_init(statics) + +struct statics *statics; + +{ + register int i = 0; + register int pagesize; + + if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open")) == NULL) + return -1; + + + /* get the list of symbols we want to access in the kernel */ + (void) kvm_nlist(kd, nlst); + if (nlst[0].n_type == 0) + { + fprintf(stderr, "top: nlist failed\n"); + return(-1); + } + + /* make sure they were all found */ + if (i > 0 && check_nlist(nlst) > 0) + { + return(-1); + } + + /* get the symbol values out of kmem */ + (void) getkval(nlst[X_HZ].n_value, (int *)(&hz), sizeof(hz), + nlst[X_HZ].n_name); + + /* stash away certain offsets for later use */ + cp_time_offset = nlst[X_CP_TIME].n_value; +#ifdef LASTPID + lastpid_offset = nlst[X_LASTPID].n_value; +#endif + + pbase = NULL; + pref = NULL; + onproc = -1; + nproc = 0; + + /* get the page size with "getpagesize" and calculate pageshift from it */ + pagesize = getpagesize(); + pageshift = 0; + while (pagesize > 1) + { + pageshift++; + pagesize >>= 1; + } + + /* we only need the amount of log(2)1024 for our conversion */ + pageshift -= LOG1024; + + /* fill in the statics information */ + statics->procstate_names = procstatenames; + statics->cpustate_names = cpustatenames; + statics->memory_names = memorynames; + + /* all done! */ + return(0); +} + +char *format_header(uname_field) + +register char *uname_field; + +{ + register char *ptr; + + ptr = header + UNAME_START; + while (*uname_field != '\0') + { + *ptr++ = *uname_field++; + } + + return(header); +} + +void +get_system_info(si) + +struct system_info *si; + +{ + long total; + + /* get the cp_time array */ + (void) getkval(cp_time_offset, (int *)cp_time, sizeof(cp_time), + "_cp_time"); +#ifdef LASTPID + (void) getkval(lastpid_offset, (int *)&lastpid, sizeof(lastpid), + "!"); +#endif + + /* convert load averages to doubles */ + { + register int i; + register double *infoloadp; + struct loadavg sysload; + int size = sizeof(sysload); + static int mib[] = { CTL_VM, VM_LOADAVG }; + + if (sysctl(mib, 2, &sysload, &size, NULL, 0) < 0) { + (void) fprintf(stderr, "top: sysctl failed: %s\n", strerror(errno)); + bzero(&total, sizeof(total)); + } + + infoloadp = si->load_avg; + for (i = 0; i < 3; i++) + *infoloadp++ = ((double) sysload.ldavg[i]) / sysload.fscale; + } + + /* convert cp_time counts to percentages */ + total = percentages(CPUSTATES, cpu_states, cp_time, cp_old, cp_diff); + + /* sum memory statistics */ + { + struct vmtotal total; + int size = sizeof(total); + static int mib[] = { CTL_VM, VM_METER }; + + /* get total -- systemwide main memory usage structure */ + if (sysctl(mib, 2, &total, &size, NULL, 0) < 0) { + (void) fprintf(stderr, "top: sysctl failed: %s\n", strerror(errno)); + bzero(&total, sizeof(total)); + } + /* convert memory stats to Kbytes */ + memory_stats[0] = -1; + memory_stats[1] = pagetok(total.t_arm); + memory_stats[2] = pagetok(total.t_rm); + memory_stats[3] = -1; + memory_stats[4] = pagetok(total.t_free); + memory_stats[5] = -1; +#ifdef DOSWAP + if (!swapmode(&memory_stats[6], &memory_stats[7])) { + memory_stats[6] = 0; + memory_stats[7] = 0; + } +#endif + } + + /* set arrays and strings */ + si->cpustates = cpu_states; + si->memory = memory_stats; +#ifdef LASTPID + if (lastpid > 0) + si->last_pid = lastpid; + else +#endif + si->last_pid = -1; +} + +static struct handle handle; + +caddr_t get_process_info(si, sel, compare) + +struct system_info *si; +struct process_select *sel; +int (*compare)(); + +{ + register int i; + register int total_procs; + register int active_procs; + register struct kinfo_proc **prefp; + register struct kinfo_proc *pp; + + /* these are copied out of sel for speed */ + int show_idle; + int show_system; + int show_uid; + int show_command; + + + pbase = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nproc); + if (nproc > onproc) + pref = (struct kinfo_proc **) realloc(pref, sizeof(struct kinfo_proc *) + * (onproc = nproc)); + if (pref == NULL || pbase == NULL) { + (void) fprintf(stderr, "top: Out of memory.\n"); + quit(23); + } + /* get a pointer to the states summary array */ + si->procstates = process_states; + + /* set up flags which define what we are going to select */ + show_idle = sel->idle; + show_system = sel->system; + show_uid = sel->uid != -1; + show_command = sel->command != NULL; + + /* count up process states and get pointers to interesting procs */ + total_procs = 0; + active_procs = 0; + memset((char *)process_states, 0, sizeof(process_states)); + prefp = pref; + for (pp = pbase, i = 0; i < nproc; pp++, i++) + { + /* + * Place pointers to each valid proc structure in pref[]. + * Process slots that are actually in use have a non-zero + * status field. Processes with SSYS set are system + * processes---these get ignored unless show_sysprocs is set. + */ + if (PP(pp, p_stat) != 0 && + (show_system || ((PP(pp, p_flag) & P_SYSTEM) == 0))) + { + total_procs++; + process_states[(unsigned char) PP(pp, p_stat)]++; + if ((PP(pp, p_stat) != SZOMB) && + (show_idle || (PP(pp, p_pctcpu) != 0) || + (PP(pp, p_stat) == SRUN)) && + (!show_uid || EP(pp, e_pcred.p_ruid) == (uid_t)sel->uid)) + { + *prefp++ = pp; + active_procs++; + } + } + } + + /* if requested, sort the "interesting" processes */ + if (compare != NULL) + { + qsort((char *)pref, active_procs, sizeof(struct kinfo_proc *), compare); + } + + /* remember active and total counts */ + si->p_total = total_procs; + si->p_active = pref_len = active_procs; + + /* pass back a handle */ + handle.next_proc = pref; + handle.remaining = active_procs; + return((caddr_t)&handle); +} + +char fmt[MAX_COLS]; /* static area where result is built */ + +char *format_next_process(handle, get_userid) + +caddr_t handle; +char *(*get_userid)(); + +{ + register struct kinfo_proc *pp; + register long cputime; + register double pct; + struct handle *hp; + char waddr[sizeof(void *) * 2 + 3]; /* Hexify void pointer */ + char *p_wait; + + /* find and remember the next proc structure */ + hp = (struct handle *)handle; + pp = *(hp->next_proc++); + hp->remaining--; + + + /* get the process's user struct and set cputime */ + if ((PP(pp, p_flag) & P_INMEM) == 0) { + /* + * Print swapped processes as <pname> + */ + char *comm = PP(pp, p_comm); +#define COMSIZ sizeof(PP(pp, p_comm)) + char buf[COMSIZ]; + (void) strncpy(buf, comm, COMSIZ); + comm[0] = '<'; + (void) strncpy(&comm[1], buf, COMSIZ - 2); + comm[COMSIZ - 2] = '\0'; + (void) strncat(comm, ">", COMSIZ - 1); + comm[COMSIZ - 1] = '\0'; + } + + cputime = (PP(pp, p_uticks) + PP(pp, p_sticks) + PP(pp, p_iticks)) / hz; + + /* calculate the base for cpu percentages */ + pct = pctdouble(PP(pp, p_pctcpu)); + + if (PP(pp, p_wchan)) + if (PP(pp, p_wmesg)) + p_wait = EP(pp, e_wmesg); + else { + snprintf(waddr, sizeof(waddr), "%x", + (unsigned long)(PP(pp, p_wchan)) & ~KERNBASE); + p_wait = waddr; + } + else + p_wait = "-"; + + /* format this entry */ + snprintf(fmt, MAX_COLS, + Proc_format, + PP(pp, p_pid), + (*get_userid)(EP(pp, e_pcred.p_ruid)), + PP(pp, p_priority) - PZERO, + PP(pp, p_nice) - NZERO, + format_k(pagetok(PROCSIZE(pp))), + format_k(pagetok(VP(pp, vm_rssize))), + state_abbrev[(unsigned char) PP(pp, p_stat)], + p_wait, + format_time(cputime), + 100.0 * pct, + printable(PP(pp, p_comm))); + + /* return the result */ + return(fmt); +} + + +/* + * check_nlist(nlst) - checks the nlist to see if any symbols were not + * found. For every symbol that was not found, a one-line + * message is printed to stderr. The routine returns the + * number of symbols NOT found. + */ + +static int check_nlist(nlst) + +register struct nlist *nlst; + +{ + register int i; + + /* check to see if we got ALL the symbols we requested */ + /* this will write one line to stderr for every symbol not found */ + + i = 0; + while (nlst->n_name != NULL) + { + if (nlst->n_type == 0) + { + /* this one wasn't found */ + (void) fprintf(stderr, "kernel: no symbol named `%s'\n", + nlst->n_name); + i = 1; + } + nlst++; + } + + return(i); +} + + +/* + * getkval(offset, ptr, size, refstr) - get a value out of the kernel. + * "offset" is the byte offset into the kernel for the desired value, + * "ptr" points to a buffer into which the value is retrieved, + * "size" is the size of the buffer (and the object to retrieve), + * "refstr" is a reference string used when printing error meessages, + * if "refstr" starts with a '!', then a failure on read will not + * be fatal (this may seem like a silly way to do things, but I + * really didn't want the overhead of another argument). + * + */ + +static int getkval(offset, ptr, size, refstr) + +unsigned long offset; +int *ptr; +int size; +char *refstr; + +{ + if (kvm_read(kd, offset, (char *) ptr, size) != size) + { + if (*refstr == '!') + { + return(0); + } + else + { + fprintf(stderr, "top: kvm_read for %s: %s\n", + refstr, strerror(errno)); + quit(23); + } + } + return(1); +} + +/* comparison routine for qsort */ + +/* + * proc_compare - comparison function for "qsort" + * Compares the resource consumption of two processes using five + * distinct keys. The keys (in descending order of importance) are: + * percent cpu, cpu ticks, state, resident set size, total virtual + * memory usage. The process states are ordered as follows (from least + * to most important): zombie, sleep, stop, start, run. The array + * declaration below maps a process state index into a number that + * reflects this ordering. + */ + +static unsigned char sorted_state[] = +{ + 0, /* not used */ + 4, /* start */ + 5, /* run */ + 2, /* sleep */ + 3, /* stop */ + 1 /* zombie */ +}; + +int +proc_compare(pp1, pp2) + +struct proc **pp1; +struct proc **pp2; + +{ + register struct kinfo_proc *p1; + register struct kinfo_proc *p2; + register int result; + register pctcpu lresult; + + /* remove one level of indirection */ + p1 = *(struct kinfo_proc **) pp1; + p2 = *(struct kinfo_proc **) pp2; + + /* compare percent cpu (pctcpu) */ + if ((lresult = PP(p2, p_pctcpu) - PP(p1, p_pctcpu)) == 0) + { + /* use cpticks to break the tie */ + if ((result = PP(p2, p_cpticks) - PP(p1, p_cpticks)) == 0) + { + /* use process state to break the tie */ + if ((result = sorted_state[(unsigned char) PP(p2, p_stat)] - + sorted_state[(unsigned char) PP(p1, p_stat)]) == 0) + { + /* use priority to break the tie */ + if ((result = PP(p2, p_priority) - PP(p1, p_priority)) == 0) + { + /* use resident set size (rssize) to break the tie */ + if ((result = VP(p2, vm_rssize) - VP(p1, vm_rssize)) == 0) + { + /* use total memory to break the tie */ + result = PROCSIZE(p2) - PROCSIZE(p1); + } + } + } + } + } + else + { + result = lresult < 0 ? -1 : 1; + } + + return(result); +} + + +/* + * proc_owner(pid) - returns the uid that owns process "pid", or -1 if + * the process does not exist. + * It is EXTREMLY IMPORTANT that this function work correctly. + * If top runs setuid root (as in SVR4), then this function + * is the only thing that stands in the way of a serious + * security problem. It validates requests for the "kill" + * and "renice" commands. + */ + +int proc_owner(pid) + +int pid; + +{ + register int cnt; + register struct kinfo_proc **prefp; + register struct kinfo_proc *pp; + + prefp = pref; + cnt = pref_len; + while (--cnt >= 0) + { + pp = *prefp++; + if (PP(pp, p_pid) == (pid_t)pid) + { + return((int)EP(pp, e_pcred.p_ruid)); + } + } + return(-1); +} + +#ifdef DOSWAP +/* + * swapmode is based on a program called swapinfo written + * by Kevin Lahey <kml@rokkaku.atl.ga.us>. + */ + +#define SVAR(var) __STRING(var) /* to force expansion */ +#define KGET(idx, var) \ + KGET1(idx, &var, sizeof(var), SVAR(var)) +#define KGET1(idx, p, s, msg) \ + KGET2(nlst[idx].n_value, p, s, msg) +#define KGET2(addr, p, s, msg) \ + if (kvm_read(kd, (u_long)(addr), p, s) != s) \ + warnx("cannot read %s: %s", msg, kvm_geterr(kd)) + +static int +swapmode(used, total) +int *used; +int *total; +{ + int nswap, nswdev, dmmax, nswapmap, niswap, niswdev; + int s, e, i, l, nfree; + struct swdevt *sw; + long *perdev; + struct map *swapmap, *kswapmap; + struct mapent *mp, *freemp; + + KGET(VM_NSWAP, nswap); + KGET(VM_NSWDEV, nswdev); + KGET(VM_DMMAX, dmmax); + KGET(VM_NSWAPMAP, nswapmap); + KGET(VM_SWAPMAP, kswapmap); /* kernel `swapmap' is a pointer */ + if ((sw = malloc(nswdev * sizeof(*sw))) == NULL || + (perdev = malloc(nswdev * sizeof(*perdev))) == NULL || + (freemp = mp = malloc(nswapmap * sizeof(*mp))) == NULL) + err(1, "malloc"); + KGET1(VM_SWDEVT, sw, nswdev * sizeof(*sw), "swdevt"); + KGET2((long)kswapmap, mp, nswapmap * sizeof(*mp), "swapmap"); + + /* Supports sequential swap */ + if (nlst[VM_NISWAP].n_value != 0) { + KGET(VM_NISWAP, niswap); + KGET(VM_NISWDEV, niswdev); + } else { + niswap = nswap; + niswdev = nswdev; + } + + /* First entry in map is `struct map'; rest are mapent's. */ + swapmap = (struct map *)mp; + if (nswapmap != swapmap->m_limit - (struct mapent *)kswapmap) + errx(1, "panic: nswapmap goof"); + + /* Count up swap space. */ + nfree = 0; + memset(perdev, 0, nswdev * sizeof(*perdev)); + for (mp++; mp->m_addr != 0; mp++) { + s = mp->m_addr; /* start of swap region */ + e = mp->m_addr + mp->m_size; /* end of region */ + nfree += mp->m_size; + + /* + * Swap space is split up among the configured disks. + * + * For interleaved swap devices, the first dmmax blocks + * of swap space some from the first disk, the next dmmax + * blocks from the next, and so on up to niswap blocks. + * + * Sequential swap devices follow the interleaved devices + * (i.e. blocks starting at niswap) in the order in which + * they appear in the swdev table. The size of each device + * will be a multiple of dmmax. + * + * The list of free space joins adjacent free blocks, + * ignoring device boundries. If we want to keep track + * of this information per device, we'll just have to + * extract it ourselves. We know that dmmax-sized chunks + * cannot span device boundaries (interleaved or sequential) + * so we loop over such chunks assigning them to devices. + */ + i = -1; + while (s < e) { /* XXX this is inefficient */ + int bound = roundup(s+1, dmmax); + + if (bound > e) + bound = e; + if (bound <= niswap) { + /* Interleaved swap chunk. */ + if (i == -1) + i = (s / dmmax) % niswdev; + perdev[i] += bound - s; + if (++i >= niswdev) + i = 0; + } else { + /* Sequential swap chunk. */ + if (i < niswdev) { + i = niswdev; + l = niswap + sw[i].sw_nblks; + } + while (s >= l) { + /* XXX don't die on bogus blocks */ + if (i == nswdev-1) + break; + l += sw[++i].sw_nblks; + } + perdev[i] += bound - s; + } + s = bound; + } + } + + *total = 0; + for (i = 0; i < nswdev; i++) { + int xsize, xfree; + + xsize = sw[i].sw_nblks; + xfree = perdev[i]; + *total += xsize; + } + + /* + * If only one partition has been set up via swapon(8), we don't + * need to bother with totals. + */ +#if DEV_BSHIFT < 10 + *used = (*total - nfree) >> (10 - DEV_BSHIFT); + *total >>= 10 - DEV_BSHIFT; +#elif DEV_BSHIFT > 10 + *used = (*total - nfree) >> (DEV_BSHIFT - 10); + *total >>= DEV_BSHIFT - 10; +#endif + free (sw); free (freemp); free (perdev); + return 1; +} +#endif diff --git a/usr.bin/top/machine.h b/usr.bin/top/machine.h new file mode 100644 index 00000000000..f50cb67d30e --- /dev/null +++ b/usr.bin/top/machine.h @@ -0,0 +1,60 @@ +/* $OpenBSD: machine.h,v 1.1 1997/08/14 14:00:23 downsj Exp $ */ + +/* + * This file defines the interface between top and the machine-dependent + * module. It is NOT machine dependent and should not need to be changed + * for any specific machine. + */ + +/* + * the statics struct is filled in by machine_init + */ +struct statics +{ + char **procstate_names; + char **cpustate_names; + char **memory_names; +#ifdef ORDER + char **order_names; +#endif +}; + +/* + * the system_info struct is filled in by a machine dependent routine. + */ + +struct system_info +{ + int last_pid; + double load_avg[NUM_AVERAGES]; + int p_total; + int p_active; /* number of procs considered "active" */ + int *procstates; + int *cpustates; + int *memory; +}; + +/* cpu_states is an array of percentages * 10. For example, + the (integer) value 105 is 10.5% (or .105). + */ + +/* + * the process_select struct tells get_process_info what processes we + * are interested in seeing + */ + +struct process_select +{ + int idle; /* show idle processes */ + int system; /* show system processes */ + int uid; /* only this uid (unless uid == -1) */ + char *command; /* only this command (unless == NULL) */ +}; + +/* routines defined by the machine dependent module */ + +char *format_header(); +char *format_next_process(); + +/* non-int routines typically used by the machine dependent module */ +char *printable(); diff --git a/usr.bin/top/os.h b/usr.bin/top/os.h new file mode 100644 index 00000000000..48ea0d99d04 --- /dev/null +++ b/usr.bin/top/os.h @@ -0,0 +1,31 @@ +/* $OpenBSD: os.h,v 1.1 1997/08/14 14:00:23 downsj Exp $ */ + +#include <sys/types.h> +#include <sys/param.h> /* This defines BSD */ +#if defined(BSD) && !defined(BSD4_4) && !defined(__osf__) +# include <stdio.h> +# include <strings.h> +# define strchr(a, b) index((a), (b)) +# define strrchr(a, b) rindex((a), (b)) +# define memcpy(a, b, c) bcopy((b), (a), (c)) +# define memzero(a, b) bzero((a), (b)) +# define memcmp(a, b, c) bcmp((a), (b), (c)) +#if defined(NeXT) + typedef void sigret_t; +#else + typedef int sigret_t; +#endif + +/* system routines that don't return int */ +char *getenv(); +caddr_t malloc(); + +#else +# include <stdio.h> +# define setbuffer(f, b, s) setvbuf((f), (b), (b) ? _IOFBF : _IONBF, (s)) +# include <string.h> +# include <memory.h> +# include <stdlib.h> +# define memzero(a, b) memset((a), 0, (b)) + typedef void sigret_t; +#endif diff --git a/usr.bin/top/patchlevel.h b/usr.bin/top/patchlevel.h new file mode 100644 index 00000000000..9fc4a61610c --- /dev/null +++ b/usr.bin/top/patchlevel.h @@ -0,0 +1,3 @@ +/* $OpenBSD: patchlevel.h,v 1.1 1997/08/14 14:00:24 downsj Exp $ */ + +#define PATCHLEVEL 4 diff --git a/usr.bin/top/screen.c b/usr.bin/top/screen.c new file mode 100644 index 00000000000..c52c0298c13 --- /dev/null +++ b/usr.bin/top/screen.c @@ -0,0 +1,496 @@ +/* $OpenBSD: screen.c,v 1.1 1997/08/14 14:00:24 downsj Exp $ */ + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +/* This file contains the routines that interface to termcap and stty/gtty. + * + * Paul Vixie, February 1987: converted to use ioctl() instead of stty/gtty. + * + * I put in code to turn on the TOSTOP bit while top was running, but I + * didn't really like the results. If you desire it, turn on the + * preprocessor variable "TOStop". --wnl + */ + +#include "os.h" +#include "top.h" + +#include <sys/ioctl.h> +#ifdef CBREAK +# include <sgtty.h> +# define SGTTY +#else +# ifdef TCGETA +# define TERMIO +# include <termio.h> +# else +# define TERMIOS +# include <termios.h> +# endif +#endif +#if defined(TERMIO) || defined(TERMIOS) +# ifndef TAB3 +# ifdef OXTABS +# define TAB3 OXTABS +# else +# define TAB3 0 +# endif +# endif +#endif +#include "screen.h" +#include "boolean.h" + +extern char *myname; + +int putstdout(); + +int overstrike; +int screen_length; +int screen_width; +char ch_erase; +char ch_kill; +char smart_terminal; +char PC; +char *tgetstr(); +char *tgoto(); +char termcap_buf[1024]; +char string_buffer[1024]; +char home[15]; +char lower_left[15]; +char *clear_line; +char *clear_screen; +char *clear_to_end; +char *cursor_motion; +char *start_standout; +char *end_standout; +char *terminal_init; +char *terminal_end; +short ospeed; + +#ifdef SGTTY +static struct sgttyb old_settings; +static struct sgttyb new_settings; +#endif +#ifdef TERMIO +static struct termio old_settings; +static struct termio new_settings; +#endif +#ifdef TERMIOS +static struct termios old_settings; +static struct termios new_settings; +#endif +static char is_a_terminal = No; +#ifdef TOStop +static int old_lword; +static int new_lword; +#endif + +#define STDIN 0 +#define STDOUT 1 +#define STDERR 2 + +init_termcap(interactive) + +int interactive; + +{ + char *bufptr; + char *PCptr; + char *term_name; + char *getenv(); + int status; + + /* set defaults in case we aren't smart */ + screen_width = MAX_COLS; + screen_length = 0; + + if (!interactive) + { + /* pretend we have a dumb terminal */ + smart_terminal = No; + return; + } + + /* assume we have a smart terminal until proven otherwise */ + smart_terminal = Yes; + + /* get the terminal name */ + term_name = getenv("TERM"); + + /* if there is no TERM, assume it's a dumb terminal */ + /* patch courtesy of Sam Horrocks at telegraph.ics.uci.edu */ + if (term_name == NULL) + { + smart_terminal = No; + return; + } + + /* now get the termcap entry */ + if ((status = tgetent(termcap_buf, term_name)) != 1) + { + if (status == -1) + { + fprintf(stderr, "%s: can't open termcap file\n", myname); + } + else + { + fprintf(stderr, "%s: no termcap entry for a `%s' terminal\n", + myname, term_name); + } + + /* pretend it's dumb and proceed */ + smart_terminal = No; + return; + } + + /* "hardcopy" immediately indicates a very stupid terminal */ + if (tgetflag("hc")) + { + smart_terminal = No; + return; + } + + /* set up common terminal capabilities */ + if ((screen_length = tgetnum("li")) <= 0) + { + screen_length = smart_terminal = 0; + return; + } + + /* screen_width is a little different */ + if ((screen_width = tgetnum("co")) == -1) + { + screen_width = 79; + } + else + { + screen_width -= 1; + } + + /* terminals that overstrike need special attention */ + overstrike = tgetflag("os"); + + /* initialize the pointer into the termcap string buffer */ + bufptr = string_buffer; + + /* get "ce", clear to end */ + if (!overstrike) + { + clear_line = tgetstr("ce", &bufptr); + } + + /* get necessary capabilities */ + if ((clear_screen = tgetstr("cl", &bufptr)) == NULL || + (cursor_motion = tgetstr("cm", &bufptr)) == NULL) + { + smart_terminal = No; + return; + } + + /* get some more sophisticated stuff -- these are optional */ + clear_to_end = tgetstr("cd", &bufptr); + terminal_init = tgetstr("ti", &bufptr); + terminal_end = tgetstr("te", &bufptr); + start_standout = tgetstr("so", &bufptr); + end_standout = tgetstr("se", &bufptr); + + /* pad character */ + PC = (PCptr = tgetstr("pc", &bufptr)) ? *PCptr : 0; + + /* set convenience strings */ + (void) strcpy(home, tgoto(cursor_motion, 0, 0)); + /* (lower_left is set in get_screensize) */ + + /* get the actual screen size with an ioctl, if needed */ + /* This may change screen_width and screen_length, and it always + sets lower_left. */ + get_screensize(); + + /* if stdout is not a terminal, pretend we are a dumb terminal */ +#ifdef SGTTY + if (ioctl(STDOUT, TIOCGETP, &old_settings) == -1) + { + smart_terminal = No; + } +#endif +#ifdef TERMIO + if (ioctl(STDOUT, TCGETA, &old_settings) == -1) + { + smart_terminal = No; + } +#endif +#ifdef TERMIOS + if (tcgetattr(STDOUT, &old_settings) == -1) + { + smart_terminal = No; + } +#endif +} + +init_screen() + +{ + /* get the old settings for safe keeping */ +#ifdef SGTTY + if (ioctl(STDOUT, TIOCGETP, &old_settings) != -1) + { + /* copy the settings so we can modify them */ + new_settings = old_settings; + + /* turn on CBREAK and turn off character echo and tab expansion */ + new_settings.sg_flags |= CBREAK; + new_settings.sg_flags &= ~(ECHO|XTABS); + (void) ioctl(STDOUT, TIOCSETP, &new_settings); + + /* remember the erase and kill characters */ + ch_erase = old_settings.sg_erase; + ch_kill = old_settings.sg_kill; + +#ifdef TOStop + /* get the local mode word */ + (void) ioctl(STDOUT, TIOCLGET, &old_lword); + + /* modify it */ + new_lword = old_lword | LTOSTOP; + (void) ioctl(STDOUT, TIOCLSET, &new_lword); +#endif + /* remember that it really is a terminal */ + is_a_terminal = Yes; + + /* send the termcap initialization string */ + putcap(terminal_init); + } +#endif +#ifdef TERMIO + if (ioctl(STDOUT, TCGETA, &old_settings) != -1) + { + /* copy the settings so we can modify them */ + new_settings = old_settings; + + /* turn off ICANON, character echo and tab expansion */ + new_settings.c_lflag &= ~(ICANON|ECHO); + new_settings.c_oflag &= ~(TAB3); + new_settings.c_cc[VMIN] = 1; + new_settings.c_cc[VTIME] = 0; + (void) ioctl(STDOUT, TCSETA, &new_settings); + + /* remember the erase and kill characters */ + ch_erase = old_settings.c_cc[VERASE]; + ch_kill = old_settings.c_cc[VKILL]; + + /* remember that it really is a terminal */ + is_a_terminal = Yes; + + /* send the termcap initialization string */ + putcap(terminal_init); + } +#endif +#ifdef TERMIOS + if (tcgetattr(STDOUT, &old_settings) != -1) + { + /* copy the settings so we can modify them */ + new_settings = old_settings; + + /* turn off ICANON, character echo and tab expansion */ + new_settings.c_lflag &= ~(ICANON|ECHO); + new_settings.c_oflag &= ~(TAB3); + new_settings.c_cc[VMIN] = 1; + new_settings.c_cc[VTIME] = 0; + (void) tcsetattr(STDOUT, TCSADRAIN, &new_settings); + + /* remember the erase and kill characters */ + ch_erase = old_settings.c_cc[VERASE]; + ch_kill = old_settings.c_cc[VKILL]; + + /* remember that it really is a terminal */ + is_a_terminal = Yes; + + /* send the termcap initialization string */ + putcap(terminal_init); + } +#endif + + if (!is_a_terminal) + { + /* not a terminal at all---consider it dumb */ + smart_terminal = No; + } +} + +end_screen() + +{ + /* move to the lower left, clear the line and send "te" */ + if (smart_terminal) + { + putcap(lower_left); + putcap(clear_line); + fflush(stdout); + putcap(terminal_end); + } + + /* if we have settings to reset, then do so */ + if (is_a_terminal) + { +#ifdef SGTTY + (void) ioctl(STDOUT, TIOCSETP, &old_settings); +#ifdef TOStop + (void) ioctl(STDOUT, TIOCLSET, &old_lword); +#endif +#endif +#ifdef TERMIO + (void) ioctl(STDOUT, TCSETA, &old_settings); +#endif +#ifdef TERMIOS + (void) tcsetattr(STDOUT, TCSADRAIN, &old_settings); +#endif + } +} + +reinit_screen() + +{ + /* install our settings if it is a terminal */ + if (is_a_terminal) + { +#ifdef SGTTY + (void) ioctl(STDOUT, TIOCSETP, &new_settings); +#ifdef TOStop + (void) ioctl(STDOUT, TIOCLSET, &new_lword); +#endif +#endif +#ifdef TERMIO + (void) ioctl(STDOUT, TCSETA, &new_settings); +#endif +#ifdef TERMIOS + (void) tcsetattr(STDOUT, TCSADRAIN, &new_settings); +#endif + } + + /* send init string */ + if (smart_terminal) + { + putcap(terminal_init); + } +} + +get_screensize() + +{ + +#ifdef TIOCGWINSZ + + struct winsize ws; + + if (ioctl (1, TIOCGWINSZ, &ws) != -1) + { + if (ws.ws_row != 0) + { + screen_length = ws.ws_row; + } + if (ws.ws_col != 0) + { + screen_width = ws.ws_col - 1; + } + } + +#else +#ifdef TIOCGSIZE + + struct ttysize ts; + + if (ioctl (1, TIOCGSIZE, &ts) != -1) + { + if (ts.ts_lines != 0) + { + screen_length = ts.ts_lines; + } + if (ts.ts_cols != 0) + { + screen_width = ts.ts_cols - 1; + } + } + +#endif /* TIOCGSIZE */ +#endif /* TIOCGWINSZ */ + + (void) strcpy(lower_left, tgoto(cursor_motion, 0, screen_length - 1)); +} + +standout(msg) + +char *msg; + +{ + if (smart_terminal) + { + putcap(start_standout); + fputs(msg, stdout); + putcap(end_standout); + } + else + { + fputs(msg, stdout); + } +} + +clear() + +{ + if (smart_terminal) + { + putcap(clear_screen); + } +} + +clear_eol(len) + +int len; + +{ + if (smart_terminal && !overstrike && len > 0) + { + if (clear_line) + { + putcap(clear_line); + return(0); + } + else + { + while (len-- > 0) + { + putchar(' '); + } + return(1); + } + } + return(-1); +} + +go_home() + +{ + if (smart_terminal) + { + putcap(home); + } +} + +/* This has to be defined as a subroutine for tputs (instead of a macro) */ + +putstdout(ch) + +char ch; + +{ + putchar(ch); +} + diff --git a/usr.bin/top/screen.h b/usr.bin/top/screen.h new file mode 100644 index 00000000000..1d8fedfb573 --- /dev/null +++ b/usr.bin/top/screen.h @@ -0,0 +1,33 @@ +/* $OpenBSD: screen.h,v 1.1 1997/08/14 14:00:24 downsj Exp $ */ + +/* + * top - a top users display for Unix 4.2 + * + * This file contains all the definitions necessary to use the hand-written + * screen package in "screen.c" + */ + +#define TCputs(str) tputs(str, 1, putstdout) +#define putcap(str) (void)((str) != NULL ? TCputs(str) : 0) +#define Move_to(x, y) TCputs(tgoto(cursor_motion, x, y)) + +/* declare return values for termcap functions */ +char *tgetstr(); +char *tgoto(); + +extern char ch_erase; /* set to the user's erase character */ +extern char ch_kill; /* set to the user's kill character */ +extern char smart_terminal; /* set if the terminal has sufficient termcap + capabilities for normal operation */ + +/* These are some termcap strings for use outside of "screen.c" */ +extern char *cursor_motion; +extern char *clear_line; +extern char *clear_to_end; + +/* rows and columns on the screen according to termcap */ +extern int screen_length; +extern int screen_width; + +/* a function that puts a single character on stdout */ +int putstdout(); diff --git a/usr.bin/top/sigconv.awk b/usr.bin/top/sigconv.awk new file mode 100644 index 00000000000..8c90d8dc749 --- /dev/null +++ b/usr.bin/top/sigconv.awk @@ -0,0 +1,53 @@ +BEGIN { + nsig = 0; + j = 0; + print "/* This file was automatically generated */" + print "/* by the awk script \"sigconv.awk\". */\n" + print "struct sigdesc {" + print " char *name;" + print " int number;" + print "};\n" + print "struct sigdesc sigdesc[] = {" + } + +/^#define[ \t][ \t]*SIG[A-Z]/ { + + j = sprintf("%d", $3); + str = $2; + + if (nsig < j) + nsig = j; + + siglist[j] = sprintf("\"%s\",\t%2d,", \ + substr(str, 4), j); + } +/^#[ \t]*define[ \t][ \t]*SIG[A-Z]/ { + + j = sprintf("%d", $4); + str = $3; + + if (nsig < j) + nsig = j; + + siglist[j] = sprintf("\"%s\",\t%2d,", \ + substr(str, 4), j); + } +/^#[ \t]*define[ \t][ \t]*_SIG[A-Z]/ { + + j = sprintf("%d", $4); + str = $3; + + if (nsig < j) + nsig = j; + + siglist[j] = sprintf("\"%s\",\t%2d,", \ + substr(str, 5), j); + } + +END { + for (n = 1; n <= nsig; n++) + if (siglist[n] != "") + printf(" %s\n", siglist[n]); + + printf(" NULL,\t 0\n};\n"); + } diff --git a/usr.bin/top/top.1 b/usr.bin/top/top.1 new file mode 100644 index 00000000000..3e04fe9bede --- /dev/null +++ b/usr.bin/top/top.1 @@ -0,0 +1,321 @@ +.\" $OpenBSD: top.1,v 1.1 1997/08/14 14:00:25 downsj Exp $ +.nr N 15 +.nr D 5 +.TH TOP 1 Local +.UC 4 +.SH NAME +top \- display and update information about the top cpu processes +.SH SYNOPSIS +.B top +[ +.B \-SbiInqu +] [ +.BI \-d count +] [ +.BI \-s time +] [ +.BI \-o field +] [ +.BI \-U username +] [ +.I number +] +.SH DESCRIPTION +.\" This defines appropriate quote strings for nroff and troff +.ds lq \&" +.ds rq \&" +.if t .ds lq `` +.if t .ds rq '' +.\" Just in case these number registers aren't set yet... +.if \nN==0 .nr N 10 +.if \nD==0 .nr D 5 +.I Top +displays the top +.if !\nN==-1 \nN +processes on the system and periodically updates this information. +.if \nN==-1 \ +\{\ +If standard output is an intelligent terminal (see below) then +as many processes as will fit on the terminal screen are displayed +by default. Otherwise, a good number of them are shown (around 20). +.\} +Raw cpu percentage is used to rank the processes. If +.I number +is given, then the top +.I number +processes will be displayed instead of the default. +.PP +.I Top +makes a distinction between terminals that support advanced capabilities +and those that do not. This +distinction affects the choice of defaults for certain options. In the +remainder of this document, an \*(lqintelligent\*(rq terminal is one that +supports cursor addressing, clear screen, and clear to end of line. +Conversely, a \*(lqdumb\*(rq terminal is one that does not support such +features. If the output of +.I top +is redirected to a file, it acts as if it were being run on a dumb +terminal. +.SH OPTIONS +.TP +.B \-S +Show system processes in the display. Normally, system processes such as +the pager and the swapper are not shown. This option makes them visible. +.TP +.B \-b +Use \*(lqbatch\*(rq mode. In this mode, all input from the terminal is +ignored. Interrupt characters (such as ^C and ^\e) still have an effect. +This is the default on a dumb terminal, or when the output is not a terminal. +.TP +.B \-i +Use \*(lqinteractive\*(rq mode. In this mode, any input is immediately +read for processing. See the section on \*(lqInteractive Mode\*(rq +for an explanation of +which keys perform what functions. After the command is processed, the +screen will immediately be updated, even if the command was not +understood. This mode is the default when standard output is an +intelligent terminal. +.TP +.B \-I +Do not display idle processes. +By default, top displays both active and idle processes. +.TP +.B \-n +Use \*(lqnon-interactive\*(rq mode. This is indentical to \*(lqbatch\*(rq +mode. +.TP +.B \-q +Renice +.I top +to -20 so that it will run faster. This can be used when the system is +being very sluggish to improve the possibility of discovering the problem. +This option can only be used by root. +.TP +.B \-u +Do not take the time to map uid numbers to usernames. Normally, +.I top +will read as much of the file \*(lq/etc/passwd\*(rq as is necessary to map +all the user id numbers it encounters into login names. This option +disables all that, while possibly decreasing execution time. The uid +numbers are displayed instead of the names. +.TP +.BI \-d count +Show only +.I count +displays, then exit. A display is considered to be one update of the +screen. This option allows the user to select the number of displays he +wants to see before +.I top +automatically exits. For intelligent terminals, no upper limit +is set. The default is 1 for dumb terminals. +.TP +.BI \-s time +Set the delay between screen updates to +.I time +seconds. The default delay between updates is \nD seconds. +.TP +.BI \-o field +Sort the process display area on the specified field. The field name is +the name of the column as seen in the output, but in lower case. Likely +values are \*(lqcpu\*(rq, \*(lqsize\*(rq, \*(lqres\*(rq, and \*(lqtime\*(rq, +but may vary on different operating systems. Note that +not all operating systems support this option. +.TP +.BI \-U username +Show only those processes owned by +.IR username . +This option currently only accepts usernames and will not understand +uid numbers. +.PP +Both +.I count +and +.I number +fields can be specified as \*(lqinfinite\*(rq, indicating that they can +stretch as far as possible. This is accomplished by using any proper +prefix of the keywords +\*(lqinfinity\*(rq, +\*(lqmaximum\*(rq, +or +\*(lqall\*(rq. +The default for +.I count +on an intelligent terminal is, in fact, +.BI infinity . +.PP +The environment variable +.B TOP +is examined for options before the command line is scanned. This enables +a user to set his or her own defaults. The number of processes to display +can also be specified in the environment variable +.BR TOP . +The options +.BR \-I , +.BR \-S , +and +.B \-u +are actually toggles. A second specification of any of these options +will negate the first. Thus a user who has the environment variable +.B TOP +set to \*(lq\-I\*(rq may use the command \*(lqtop \-I\*(rq to see idle processes. +.SH "INTERACTIVE MODE" +When +.I top +is running in \*(lqinteractive mode\*(rq, it reads commands from the +terminal and acts upon them accordingly. In this mode, the terminal is +put in \*(lqCBREAK\*(rq, so that a character will be +processed as soon as it is typed. Almost always, a key will be +pressed when +.I top +is between displays; that is, while it is waiting for +.I time +seconds to elapse. If this is the case, the command will be +processed and the display will be updated immediately thereafter +(reflecting any changes that the command may have specified). This +happens even if the command was incorrect. If a key is pressed while +.I top +is in the middle of updating the display, it will finish the update and +then process the command. Some commands require additional information, +and the user will be prompted accordingly. While typing this information +in, the user's erase and kill keys (as set up by the command +.IR stty ) +are recognized, and a newline terminates the input. +.PP +These commands are currently recognized (^L refers to control-L): +.TP +.B ^L +Redraw the screen. +.IP "\fBh\fP\ or\ \fB?\fP" +Display a summary of the commands (help screen). +.TP +.B q +Quit +.IR top. +.TP +.B d +Change the number of displays to show (prompt for new number). +Remember that the next display counts as one, so typing +.B d1 +will make +.I top +show one final display and then immediately exit. +.TP +.B n or # +Change the number of processes to display (prompt for new number). +.TP +.B s +Change the number of seconds to delay between displays +(prompt for new number). +.TP +.B k +Send a signal (\*(lqkill\*(rq by default) to a list of processes. This +acts similarly to the command +.IR kill (1)). +.TP +.B r +Change the priority (the \*(lqnice\*(rq) of a list of processes. +This acts similarly to the command +.IR renice (8)). +.TP +.B u +Display only processes owned by a specific username (prompt for username). +If the username specified is simply \*(lq+\*(rq, then processes belonging +to all users will be displayed. +.TP +.B e +Display a list of system errors (if any) generated by the last +.BR k ill +or +.BR r enice +command. +.TP +.B i +(or +.BR I) +Toggle the display of idle processes. +.SH "THE DISPLAY" +The actual display varies depending on the specific variant of Unix +that the machine is running. This description may not exactly match +what is seen by top running on this particular machine. Differences +are listed at the end of this manual entry. +.PP +The top few lines of the display show general information +about the state of the system, including +the last process id assigned to a process (on most systems), +the three load averages, +the current time, +the number of existing processes, +the number of processes in each state +(sleeping, running, starting, zombies, and stopped), +and a percentage of time spent in each of the processor states +(user, nice, system, and idle). +It also includes information about physial and virtual memory allocation. +.PP +The remainder of the screen displays information about individual +processes. This display is similar in spirit to +.IR ps (1) +but it is not exactly the same. PID is the process id, USERNAME is the name +of the process's owner (if +.B \-u +is specified, a UID column will be substituted for USERNAME), +PRI is the current priority of the process, +NICE is the nice amount (in the range \-20 to 20), +SIZE is the total size of the process (text, data, and stack), +RES is the current amount of resident memory (both SIZE and RES are +given in kilobytes), +STATE is the current state (one of \*(lqsleep\*(rq, \*(lqWAIT\*(rq, +\*(lqrun\*(rq, \*(lqidl\*(rq, \*(lqzomb\*(rq, or \*(lqstop\*(rq), +TIME is the number of system and user cpu seconds that the process has used, +WCPU, when displayed, is the weighted cpu percentage (this is the same +value that +.IR ps (1) +displays as CPU), +CPU is the raw percentage and is the field that is sorted to determine +the order of the processes, and +COMMAND is the name of the command that the process is currently running +(if the process is swapped out, this column is marked \*(lq<swapped>\*(rq). +.SH NOTES +The \*(lqABANDONED\*(rq state (known in the kernel as \*(lqSWAIT\*(rq) was +abandoned, thus the name. A process should never end up in this state. +.SH AUTHOR +William LeFebvre, EECS Department, Northwestern University +.SH ENVIRONMENT +.DT +TOP user-configurable defaults for options. +.SH FILES +.DT +/dev/kmem kernel memory +.br +/dev/mem physical memory +.br +/bsd system image +.SH BUGS +Don't shoot me, but the default for +.B \-I +has changed once again. So many people were confused by the fact that +.I top +wasn't showing them all the processes that I have decided to make the +default behavior show idle processes, just like it did in version 2. +But to appease folks who can't stand that behavior, I have added the +ability to set \*(lqdefault\*(rq options in the environment variable +.B TOP +(see the OPTIONS section). Those who want the behavior that version +3.0 had need only set the environment variable +.B TOP +to \*(lq\-I\*(rq. +.PP +The command name for swapped processes should be tracked down, but this +would make the program run slower. +.PP +As with +.IR ps (1), +things can change while +.I top +is collecting information for an update. The picture it gives is only a +close approximation to reality. +.SH "SEE ALSO" +kill(1), +ps(1), +stty(1), +mem(4), +renice(8) diff --git a/usr.bin/top/top.c b/usr.bin/top/top.c new file mode 100644 index 00000000000..a4368dc906d --- /dev/null +++ b/usr.bin/top/top.c @@ -0,0 +1,998 @@ +/* $OpenBSD: top.c,v 1.1 1997/08/14 14:00:26 downsj Exp $ */ + +char *copyright = + "Copyright (c) 1984 through 1996, William LeFebvre"; + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +/* + * See the file "Changes" for information on version-to-version changes. + */ + +/* + * This file contains "main" and other high-level routines. + */ + +/* + * The following preprocessor variables, when defined, are used to + * distinguish between different Unix implementations: + * + * SIGHOLD - use SVR4 sighold function when defined + * SIGRELSE - use SVR4 sigrelse function when defined + * FD_SET - macros FD_SET and FD_ZERO are used when defined + */ + +#include "os.h" +#include <signal.h> +#include <setjmp.h> +#include <ctype.h> +#include <sys/time.h> + +/* includes specific to top */ +#include "display.h" /* interface to display package */ +#include "screen.h" /* interface to screen package */ +#include "top.h" +#include "top.local.h" +#include "boolean.h" +#include "machine.h" +#include "utils.h" + +/* Size of the stdio buffer given to stdout */ +#define Buffersize 2048 + +/* The buffer that stdio will use */ +char stdoutbuf[Buffersize]; + +/* build Signal masks */ +#define Smask(s) (1 << ((s) - 1)) + +/* for system errors */ +extern int errno; + +/* for getopt: */ +extern int optind; +extern char *optarg; + +/* imported from screen.c */ +extern int overstrike; + +/* signal handling routines */ +sigret_t leave(); +sigret_t onalrm(); +sigret_t tstop(); +#ifdef SIGWINCH +sigret_t winch(); +#endif + +/* internal routines */ +void quit(); + +/* values which need to be accessed by signal handlers */ +static int max_topn; /* maximum displayable processes */ + +/* miscellaneous things */ +char *myname = "top"; +jmp_buf jmp_int; + +/* routines that don't return int */ + +char *username(); +char *ctime(); +char *kill_procs(); +char *renice_procs(); + +#ifdef ORDER +extern int (*proc_compares[])(); +#else +extern int proc_compare(); +#endif +time_t time(); + +caddr_t get_process_info(); + +/* different routines for displaying the user's identification */ +/* (values assigned to get_userid) */ +char *username(); +char *itoa7(); + +/* display routines that need to be predeclared */ +int i_loadave(); +int u_loadave(); +int i_procstates(); +int u_procstates(); +int i_cpustates(); +int u_cpustates(); +int i_memory(); +int u_memory(); +int i_message(); +int u_message(); +int i_header(); +int u_header(); +int i_process(); +int u_process(); + +/* pointers to display routines */ +int (*d_loadave)() = i_loadave; +int (*d_procstates)() = i_procstates; +int (*d_cpustates)() = i_cpustates; +int (*d_memory)() = i_memory; +int (*d_message)() = i_message; +int (*d_header)() = i_header; +int (*d_process)() = i_process; + + +main(argc, argv) + +int argc; +char *argv[]; + +{ + register int i; + register int active_procs; + register int change; + + struct system_info system_info; + struct statics statics; + caddr_t processes; + + static char tempbuf1[50]; + static char tempbuf2[50]; + int old_sigmask; /* only used for BSD-style signals */ + int topn = Default_TOPN; + int delay = Default_DELAY; + int displays = 0; /* indicates unspecified */ + time_t curr_time; + char *(*get_userid)() = username; + char *uname_field = "USERNAME"; + char *header_text; + char *env_top; + char **preset_argv; + int preset_argc = 0; + char **av; + int ac; + char dostates = No; + char do_unames = Yes; + char interactive = Maybe; + char warnings = 0; +#if Default_TOPN == Infinity + char topn_specified = No; +#endif + char ch; + char *iptr; + char no_command = 1; + struct timeval timeout; + struct process_select ps; +#ifdef ORDER + char *order_name = NULL; + int order_index = 0; +#endif +#ifndef FD_SET + /* FD_SET and friends are not present: fake it */ + typedef int fd_set; +#define FD_ZERO(x) (*(x) = 0) +#define FD_SET(f, x) (*(x) = f) +#endif + fd_set readfds; + +#ifdef ORDER + static char command_chars[] = "\f qh?en#sdkriIuo"; +#else + static char command_chars[] = "\f qh?en#sdkriIu"; +#endif +/* these defines enumerate the "strchr"s of the commands in command_chars */ +#define CMD_redraw 0 +#define CMD_update 1 +#define CMD_quit 2 +#define CMD_help1 3 +#define CMD_help2 4 +#define CMD_OSLIMIT 4 /* terminals with OS can only handle commands */ +#define CMD_errors 5 /* less than or equal to CMD_OSLIMIT */ +#define CMD_number1 6 +#define CMD_number2 7 +#define CMD_delay 8 +#define CMD_displays 9 +#define CMD_kill 10 +#define CMD_renice 11 +#define CMD_idletog 12 +#define CMD_idletog2 13 +#define CMD_user 14 +#ifdef ORDER +#define CMD_order 15 +#endif + + /* set the buffer for stdout */ +#ifdef DEBUG + setbuffer(stdout, NULL, 0); +#else + setbuffer(stdout, stdoutbuf, Buffersize); +#endif + + /* get our name */ + if (argc > 0) + { + if ((myname = strrchr(argv[0], '/')) == 0) + { + myname = argv[0]; + } + else + { + myname++; + } + } + + /* initialize some selection options */ + ps.idle = Yes; + ps.system = No; + ps.uid = -1; + ps.command = NULL; + + /* get preset options from the environment */ + if ((env_top = getenv("TOP")) != NULL) + { + av = preset_argv = argparse(env_top, &preset_argc); + ac = preset_argc; + + /* set the dummy argument to an explanatory message, in case + getopt encounters a bad argument */ + preset_argv[0] = "while processing environment"; + } + + /* process options */ + do { + /* if we're done doing the presets, then process the real arguments */ + if (preset_argc == 0) + { + ac = argc; + av = argv; + + /* this should keep getopt happy... */ + optind = 1; + } + + while ((i = getopt(ac, av, "SIbinqus:d:U:o:")) != EOF) + { + switch(i) + { + case 'u': /* toggle uid/username display */ + do_unames = !do_unames; + break; + + case 'U': /* display only username's processes */ + if ((ps.uid = userid(optarg)) == -1) + { + fprintf(stderr, "%s: unknown user\n", optarg); + exit(1); + } + break; + + case 'S': /* show system processes */ + ps.system = !ps.system; + break; + + case 'I': /* show idle processes */ + ps.idle = !ps.idle; + break; + + case 'i': /* go interactive regardless */ + interactive = Yes; + break; + + case 'n': /* batch, or non-interactive */ + case 'b': + interactive = No; + break; + + case 'd': /* number of displays to show */ + if ((i = atoiwi(optarg)) == Invalid || i == 0) + { + fprintf(stderr, + "%s: warning: display count should be positive -- option ignored\n", + myname); + warnings++; + } + else + { + displays = i; + } + break; + + case 's': + if ((delay = atoi(optarg)) < 0) + { + fprintf(stderr, + "%s: warning: seconds delay should be non-negative -- using default\n", + myname); + delay = Default_DELAY; + warnings++; + } + break; + + case 'q': /* be quick about it */ + /* only allow this if user is really root */ + if (getuid() == 0) + { + /* be very un-nice! */ + (void) nice(-20); + } + else + { + fprintf(stderr, + "%s: warning: `-q' option can only be used by root\n", + myname); + warnings++; + } + break; + + case 'o': /* select sort order */ +#ifdef ORDER + order_name = optarg; +#else + fprintf(stderr, + "%s: this platform does not support arbitrary ordering. Sorry.\n", + myname); + warnings++; +#endif + break; + + default: + fprintf(stderr, "\ +Top version %s\n\ +Usage: %s [-ISbinqu] [-d x] [-s x] [-o field] [-U username] [number]\n", + version_string(), myname); + exit(1); + } + } + + /* get count of top processes to display (if any) */ + if (optind < ac) + { + if ((topn = atoiwi(av[optind])) == Invalid) + { + fprintf(stderr, + "%s: warning: process display count should be non-negative -- using default\n", + myname); + warnings++; + } +#if Default_TOPN == Infinity + else + { + topn_specified = Yes; + } +#endif + } + + /* tricky: remember old value of preset_argc & set preset_argc = 0 */ + i = preset_argc; + preset_argc = 0; + + /* repeat only if we really did the preset arguments */ + } while (i != 0); + + /* set constants for username/uid display correctly */ + if (!do_unames) + { + uname_field = " UID "; + get_userid = itoa7; + } + + /* initialize the kernel memory interface */ + if (machine_init(&statics) == -1) + { + exit(1); + } + +#ifdef ORDER + /* determine sorting order index, if necessary */ + if (order_name != NULL) + { + if ((order_index = string_index(order_name, statics.order_names)) == -1) + { + char **pp; + + fprintf(stderr, "%s: '%s' is not a recognized sorting order.\n", + myname, order_name); + fprintf(stderr, "\tTry one of these:"); + pp = statics.order_names; + while (*pp != NULL) + { + fprintf(stderr, " %s", *pp++); + } + fputc('\n', stderr); + exit(1); + } + } +#endif + +#ifdef no_initialization_needed + /* initialize the hashing stuff */ + if (do_unames) + { + init_hash(); + } +#endif + + /* initialize termcap */ + init_termcap(interactive); + + /* get the string to use for the process area header */ + header_text = format_header(uname_field); + + /* initialize display interface */ + if ((max_topn = display_init(&statics)) == -1) + { + fprintf(stderr, "%s: can't allocate sufficient memory\n", myname); + exit(4); + } + + /* print warning if user requested more processes than we can display */ + if (topn > max_topn) + { + fprintf(stderr, + "%s: warning: this terminal can only display %d processes.\n", + myname, max_topn); + warnings++; + } + + /* adjust for topn == Infinity */ + if (topn == Infinity) + { + /* + * For smart terminals, infinity really means everything that can + * be displayed, or Largest. + * On dumb terminals, infinity means every process in the system! + * We only really want to do that if it was explicitly specified. + * This is always the case when "Default_TOPN != Infinity". But if + * topn wasn't explicitly specified and we are on a dumb terminal + * and the default is Infinity, then (and only then) we use + * "Nominal_TOPN" instead. + */ +#if Default_TOPN == Infinity + topn = smart_terminal ? Largest : + (topn_specified ? Largest : Nominal_TOPN); +#else + topn = Largest; +#endif + } + + /* set header display accordingly */ + display_header(topn > 0); + + /* determine interactive state */ + if (interactive == Maybe) + { + interactive = smart_terminal; + } + + /* if # of displays not specified, fill it in */ + if (displays == 0) + { + displays = smart_terminal ? Infinity : 1; + } + + /* hold interrupt signals while setting up the screen and the handlers */ +#ifdef SIGHOLD + sighold(SIGINT); + sighold(SIGQUIT); + sighold(SIGTSTP); +#else + old_sigmask = sigblock(Smask(SIGINT) | Smask(SIGQUIT) | Smask(SIGTSTP)); +#endif + init_screen(); + (void) signal(SIGINT, leave); + (void) signal(SIGQUIT, leave); + (void) signal(SIGTSTP, tstop); +#ifdef SIGWINCH + (void) signal(SIGWINCH, winch); +#endif +#ifdef SIGRELSE + sigrelse(SIGINT); + sigrelse(SIGQUIT); + sigrelse(SIGTSTP); +#else + (void) sigsetmask(old_sigmask); +#endif + if (warnings) + { + fputs("....", stderr); + fflush(stderr); /* why must I do this? */ + sleep((unsigned)(3 * warnings)); + fputc('\n', stderr); + } + + /* setup the jump buffer for stops */ + if (setjmp(jmp_int) != 0) + { + /* control ends up here after an interrupt */ + reset_display(); + } + + /* + * main loop -- repeat while display count is positive or while it + * indicates infinity (by being -1) + */ + + while ((displays == -1) || (displays-- > 0)) + { + /* get the current stats */ + get_system_info(&system_info); + + /* get the current set of processes */ + processes = + get_process_info(&system_info, + &ps, +#ifdef ORDER + proc_compares[order_index]); +#else + proc_compare); +#endif + + /* display the load averages */ + (*d_loadave)(system_info.last_pid, + system_info.load_avg); + + /* display the current time */ + /* this method of getting the time SHOULD be fairly portable */ + time(&curr_time); + i_timeofday(&curr_time); + + /* display process state breakdown */ + (*d_procstates)(system_info.p_total, + system_info.procstates); + + /* display the cpu state percentage breakdown */ + if (dostates) /* but not the first time */ + { + (*d_cpustates)(system_info.cpustates); + } + else + { + /* we'll do it next time */ + if (smart_terminal) + { + z_cpustates(); + } + else + { + putchar('\n'); + } + dostates = Yes; + } + + /* display memory stats */ + (*d_memory)(system_info.memory); + + /* handle message area */ + (*d_message)(); + + /* update the header area */ + (*d_header)(header_text); + + if (topn > 0) + { + /* determine number of processes to actually display */ + /* this number will be the smallest of: active processes, + number user requested, number current screen accomodates */ + active_procs = system_info.p_active; + if (active_procs > topn) + { + active_procs = topn; + } + if (active_procs > max_topn) + { + active_procs = max_topn; + } + + /* now show the top "n" processes. */ + for (i = 0; i < active_procs; i++) + { + (*d_process)(i, format_next_process(processes, get_userid)); + } + } + else + { + i = 0; + } + + /* do end-screen processing */ + u_endscreen(i); + + /* now, flush the output buffer */ + fflush(stdout); + + /* only do the rest if we have more displays to show */ + if (displays) + { + /* switch out for new display on smart terminals */ + if (smart_terminal) + { + if (overstrike) + { + reset_display(); + } + else + { + d_loadave = u_loadave; + d_procstates = u_procstates; + d_cpustates = u_cpustates; + d_memory = u_memory; + d_message = u_message; + d_header = u_header; + d_process = u_process; + } + } + + no_command = Yes; + if (!interactive) + { + /* set up alarm */ + (void) signal(SIGALRM, onalrm); + (void) alarm((unsigned)delay); + + /* wait for the rest of it .... */ + pause(); + } + else while (no_command) + { + /* assume valid command unless told otherwise */ + no_command = No; + + /* set up arguments for select with timeout */ + FD_ZERO(&readfds); + FD_SET(1, &readfds); /* for standard input */ + timeout.tv_sec = delay; + timeout.tv_usec = 0; + + /* wait for either input or the end of the delay period */ + if (select(32, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timeout) > 0) + { + int newval; + char *errmsg; + + /* something to read -- clear the message area first */ + clear_message(); + + /* now read it and convert to command strchr */ + /* (use "change" as a temporary to hold strchr) */ + (void) read(0, &ch, 1); + if ((iptr = strchr(command_chars, ch)) == NULL) + { + /* illegal command */ + new_message(MT_standout, " Command not understood"); + putchar('\r'); + no_command = Yes; + } + else + { + change = iptr - command_chars; + if (overstrike && change > CMD_OSLIMIT) + { + /* error */ + new_message(MT_standout, + " Command cannot be handled by this terminal"); + putchar('\r'); + no_command = Yes; + } + else switch(change) + { + case CMD_redraw: /* redraw screen */ + reset_display(); + break; + + case CMD_update: /* merely update display */ + /* is the load average high? */ + if (system_info.load_avg[0] > LoadMax) + { + /* yes, go home for visual feedback */ + go_home(); + fflush(stdout); + } + break; + + case CMD_quit: /* quit */ + quit(0); + /*NOTREACHED*/ + break; + + case CMD_help1: /* help */ + case CMD_help2: + reset_display(); + clear(); + show_help(); + standout("Hit any key to continue: "); + fflush(stdout); + (void) read(0, &ch, 1); + break; + + case CMD_errors: /* show errors */ + if (error_count() == 0) + { + new_message(MT_standout, + " Currently no errors to report."); + putchar('\r'); + no_command = Yes; + } + else + { + reset_display(); + clear(); + show_errors(); + standout("Hit any key to continue: "); + fflush(stdout); + (void) read(0, &ch, 1); + } + break; + + case CMD_number1: /* new number */ + case CMD_number2: + new_message(MT_standout, + "Number of processes to show: "); + newval = readline(tempbuf1, 8, Yes); + if (newval > -1) + { + if (newval > max_topn) + { + new_message(MT_standout | MT_delayed, + " This terminal can only display %d processes.", + max_topn); + putchar('\r'); + } + + if (newval == 0) + { + /* inhibit the header */ + display_header(No); + } + else if (newval > topn && topn == 0) + { + /* redraw the header */ + display_header(Yes); + d_header = i_header; + } + topn = newval; + } + break; + + case CMD_delay: /* new seconds delay */ + new_message(MT_standout, "Seconds to delay: "); + if ((i = readline(tempbuf1, 8, Yes)) > -1) + { + delay = i; + } + clear_message(); + break; + + case CMD_displays: /* change display count */ + new_message(MT_standout, + "Displays to show (currently %s): ", + displays == -1 ? "infinite" : + itoa(displays)); + if ((i = readline(tempbuf1, 10, Yes)) > 0) + { + displays = i; + } + else if (i == 0) + { + quit(0); + } + clear_message(); + break; + + case CMD_kill: /* kill program */ + new_message(0, "kill "); + if (readline(tempbuf2, sizeof(tempbuf2), No) > 0) + { + if ((errmsg = kill_procs(tempbuf2)) != NULL) + { + new_message(MT_standout, errmsg); + putchar('\r'); + no_command = Yes; + } + } + else + { + clear_message(); + } + break; + + case CMD_renice: /* renice program */ + new_message(0, "renice "); + if (readline(tempbuf2, sizeof(tempbuf2), No) > 0) + { + if ((errmsg = renice_procs(tempbuf2)) != NULL) + { + new_message(MT_standout, errmsg); + putchar('\r'); + no_command = Yes; + } + } + else + { + clear_message(); + } + break; + + case CMD_idletog: + case CMD_idletog2: + ps.idle = !ps.idle; + new_message(MT_standout | MT_delayed, + " %sisplaying idle processes.", + ps.idle ? "D" : "Not d"); + putchar('\r'); + break; + + case CMD_user: + new_message(MT_standout, + "Username to show: "); + if (readline(tempbuf2, sizeof(tempbuf2), No) > 0) + { + if (tempbuf2[0] == '+' && + tempbuf2[1] == '\0') + { + ps.uid = -1; + } + else if ((i = userid(tempbuf2)) == -1) + { + new_message(MT_standout, + " %s: unknown user", tempbuf2); + no_command = Yes; + } + else + { + ps.uid = i; + } + putchar('\r'); + } + else + { + clear_message(); + } + break; + +#ifdef ORDER + case CMD_order: + new_message(MT_standout, + "Order to sort: "); + if (readline(tempbuf2, sizeof(tempbuf2), No) > 0) + { + if ((i = string_index(tempbuf2, statics.order_names)) == -1) + { + new_message(MT_standout, + " %s: unrecognized sorting order", tempbuf2); + no_command = Yes; + } + else + { + order_index = i; + } + putchar('\r'); + } + else + { + clear_message(); + } + break; +#endif + + default: + new_message(MT_standout, " BAD CASE IN SWITCH!"); + putchar('\r'); + } + } + + /* flush out stuff that may have been written */ + fflush(stdout); + } + } + } + } + + quit(0); + /*NOTREACHED*/ +} + +/* + * reset_display() - reset all the display routine pointers so that entire + * screen will get redrawn. + */ + +reset_display() + +{ + d_loadave = i_loadave; + d_procstates = i_procstates; + d_cpustates = i_cpustates; + d_memory = i_memory; + d_message = i_message; + d_header = i_header; + d_process = i_process; +} + +/* + * signal handlers + */ + +sigret_t leave() /* exit under normal conditions -- INT handler */ + +{ + end_screen(); + exit(0); +} + +sigret_t tstop(i) /* SIGTSTP handler */ + +int i; + +{ + /* move to the lower left */ + end_screen(); + fflush(stdout); + + /* default the signal handler action */ + (void) signal(SIGTSTP, SIG_DFL); + + /* unblock the signal and send ourselves one */ +#ifdef SIGRELSE + sigrelse(SIGTSTP); +#else + (void) sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1))); +#endif + (void) kill(0, SIGTSTP); + + /* reset the signal handler */ + (void) signal(SIGTSTP, tstop); + + /* reinit screen */ + reinit_screen(); + + /* jump to appropriate place */ + longjmp(jmp_int, 1); + + /*NOTREACHED*/ +} + +#ifdef SIGWINCH +sigret_t winch(i) /* SIGWINCH handler */ + +int i; + +{ + /* reascertain the screen dimensions */ + get_screensize(); + + /* tell display to resize */ + max_topn = display_resize(); + + /* reset the signal handler */ + (void) signal(SIGWINCH, winch); + + /* jump to appropriate place */ + longjmp(jmp_int, 1); +} +#endif + +void quit(status) /* exit under duress */ + +int status; + +{ + end_screen(); + exit(status); + /*NOTREACHED*/ +} + +sigret_t onalrm() /* SIGALRM handler */ + +{ + /* this is only used in batch mode to break out of the pause() */ + /* return; */ +} + diff --git a/usr.bin/top/top.h b/usr.bin/top/top.h new file mode 100644 index 00000000000..ad65c41aa24 --- /dev/null +++ b/usr.bin/top/top.h @@ -0,0 +1,38 @@ +/* $OpenBSD: top.h,v 1.1 1997/08/14 14:00:26 downsj Exp $ */ + +/* + * Top - a top users display for Berkeley Unix + * + * General (global) definitions + */ + +/* Current major version number */ +#define VERSION 3 + +/* Number of lines of header information on the standard screen */ +#define Header_lines 6 + +/* Maximum number of columns allowed for display */ +#define MAX_COLS 128 + +/* Log base 2 of 1024 is 10 (2^10 == 1024) */ +#define LOG1024 10 + +char *itoa(); +char *itoa7(); + +char *version_string(); + +/* Special atoi routine returns either a non-negative number or one of: */ +#define Infinity -1 +#define Invalid -2 + +/* maximum number we can have */ +#define Largest 0x7fffffff + +/* + * The entire display is based on these next numbers being defined as is. + */ + +#define NUM_AVERAGES 3 + diff --git a/usr.bin/top/top.local.h b/usr.bin/top/top.local.h new file mode 100644 index 00000000000..618e7b4c2f8 --- /dev/null +++ b/usr.bin/top/top.local.h @@ -0,0 +1,70 @@ +/* $OpenBSD: top.local.h,v 1.1 1997/08/14 14:00:26 downsj Exp $ */ + +/* + * Top - a top users display for Berkeley Unix + * + * Definitions for things that might vary between installations. + */ + +/* + * The space command forces an immediate update. Sometimes, on loaded + * systems, this update will take a significant period of time (because all + * the output is buffered). So, if the short-term load average is above + * "LoadMax", then top will put the cursor home immediately after the space + * is pressed before the next update is attempted. This serves as a visual + * acknowledgement of the command. On Suns, "LoadMax" will get multiplied by + * "FSCALE" before being compared to avenrun[0]. Therefore, "LoadMax" + * should always be specified as a floating point number. + */ +#ifndef LoadMax +#define LoadMax 5.0 +#endif + +/* + * "Table_size" defines the size of the hash tables used to map uid to + * username. The number of users in /etc/passwd CANNOT be greater than + * this number. If the error message "table overflow: too many users" + * is printed by top, then "Table_size" needs to be increased. Things will + * work best if the number is a prime number that is about twice the number + * of lines in /etc/passwd. + */ +#ifndef Table_size +#define Table_size 503 +#endif + +/* + * "Nominal_TOPN" is used as the default TOPN when Default_TOPN is Infinity + * and the output is a dumb terminal. If we didn't do this, then + * installations who use a default TOPN of Infinity will get every + * process in the system when running top on a dumb terminal (or redirected + * to a file). Note that Nominal_TOPN is a default: it can still be + * overridden on the command line, even with the value "infinity". + */ +#ifndef Nominal_TOPN +#define Nominal_TOPN 18 +#endif + +#ifndef Default_TOPN +#define Default_TOPN 15 +#endif + +#ifndef Default_DELAY +#define Default_DELAY 5 +#endif + +/* + * If the local system's getpwnam interface uses random access to retrieve + * a record (i.e.: 4.3 systems, Sun "yellow pages"), then defining + * RANDOM_PW will take advantage of that fact. If RANDOM_PW is defined, + * then getpwnam is used and the result is cached. If not, then getpwent + * is used to read and cache the password entries sequentially until the + * desired one is found. + * + * We initially set RANDOM_PW to something which is controllable by the + * Configure script. Then if its value is 0, we undef it. + */ + +#define RANDOM_PW 1 +#if RANDOM_PW == 0 +#undef RANDOM_PW +#endif diff --git a/usr.bin/top/username.c b/usr.bin/top/username.c new file mode 100644 index 00000000000..e7e88c72f57 --- /dev/null +++ b/usr.bin/top/username.c @@ -0,0 +1,187 @@ +/* $OpenBSD: username.c,v 1.1 1997/08/14 14:00:27 downsj Exp $ */ + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +/* + * Username translation code for top. + * + * These routines handle uid to username mapping. + * They use a hashing table scheme to reduce reading overhead. + * For the time being, these are very straightforward hashing routines. + * Maybe someday I'll put in something better. But with the advent of + * "random access" password files, it might not be worth the effort. + * + * Changes to these have been provided by John Gilmore (gnu@toad.com). + * + * The hash has been simplified in this release, to avoid the + * table overflow problems of previous releases. If the value + * at the initial hash location is not right, it is replaced + * by the right value. Collisions will cause us to call getpw* + * but hey, this is a cache, not the Library of Congress. + * This makes the table size independent of the passwd file size. + */ + +#include <stdio.h> +#include <pwd.h> + +#include "top.local.h" +#include "utils.h" + +struct hash_el { + int uid; + char name[9]; +}; + +#define is_empty_hash(x) (hash_table[x].name[0] == 0) + +/* simple minded hashing function */ +/* Uid "nobody" is -2 results in hashit(-2) = -2 which is out of bounds for + the hash_table. Applied abs() function to fix. 2/16/96 tpugh +*/ +#define hashit(i) (abs(i) % Table_size) + +/* K&R requires that statically declared tables be initialized to zero. */ +/* We depend on that for hash_table and YOUR compiler had BETTER do it! */ +struct hash_el hash_table[Table_size]; + +init_hash() + +{ + /* + * There used to be some steps we had to take to initialize things. + * We don't need to do that anymore, but we will leave this stub in + * just in case future changes require initialization steps. + */ +} + +char *username(uid) + +register int uid; + +{ + register int hashindex; + + hashindex = hashit(uid); + if (is_empty_hash(hashindex) || (hash_table[hashindex].uid != uid)) + { + /* not here or not right -- get it out of passwd */ + hashindex = get_user(uid); + } + return(hash_table[hashindex].name); +} + +int userid(username) + +char *username; + +{ + struct passwd *pwd; + + /* Eventually we want this to enter everything in the hash table, + but for now we just do it simply and remember just the result. + */ + + if ((pwd = getpwnam(username)) == NULL) + { + return(-1); + } + + /* enter the result in the hash table */ + enter_user(pwd->pw_uid, username, 1); + + /* return our result */ + return(pwd->pw_uid); +} + +int enter_user(uid, name, wecare) + +register int uid; +register char *name; +int wecare; /* 1 = enter it always, 0 = nice to have */ + +{ + register int hashindex; + +#ifdef DEBUG + fprintf(stderr, "enter_hash(%d, %s, %d)\n", uid, name, wecare); +#endif + + hashindex = hashit(uid); + + if (!is_empty_hash(hashindex)) + { + if (!wecare) + return 0; /* Don't clobber a slot for trash */ + if (hash_table[hashindex].uid == uid) + return(hashindex); /* Fortuitous find */ + } + + /* empty or wrong slot -- fill it with new value */ + hash_table[hashindex].uid = uid; + (void) strncpy(hash_table[hashindex].name, name, 8); + return(hashindex); +} + +/* + * Get a userid->name mapping from the system. + * If the passwd database is hashed (#define RANDOM_PW), we + * just handle this uid. Otherwise we scan the passwd file + * and cache any entries we pass over while looking. + */ + +int get_user(uid) + +register int uid; + +{ + struct passwd *pwd; + +#ifdef RANDOM_PW + /* no performance penalty for using getpwuid makes it easy */ + if ((pwd = getpwuid(uid)) != NULL) + { + return(enter_user(pwd->pw_uid, pwd->pw_name, 1)); + } +#else + + int from_start = 0; + + /* + * If we just called getpwuid each time, things would be very slow + * since that just iterates through the passwd file each time. So, + * we walk through the file instead (using getpwent) and cache each + * entry as we go. Once the right record is found, we cache it and + * return immediately. The next time we come in, getpwent will get + * the next record. In theory, we never have to read the passwd file + * a second time (because we cache everything we read). But in + * practice, the cache may not be large enough, so if we don't find + * it the first time we have to scan the file a second time. This + * is not very efficient, but it will do for now. + */ + + while (from_start++ < 2) + { + while ((pwd = getpwent()) != NULL) + { + if (pwd->pw_uid == uid) + { + return(enter_user(pwd->pw_uid, pwd->pw_name, 1)); + } + (void) enter_user(pwd->pw_uid, pwd->pw_name, 0); + } + /* try again */ + setpwent(); + } +#endif + /* if we can't find the name at all, then use the uid as the name */ + return(enter_user(uid, itoa7(uid), 1)); +} diff --git a/usr.bin/top/utils.c b/usr.bin/top/utils.c new file mode 100644 index 00000000000..14206747614 --- /dev/null +++ b/usr.bin/top/utils.c @@ -0,0 +1,457 @@ +/* $OpenBSD: utils.c,v 1.1 1997/08/14 14:00:27 downsj Exp $ */ + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +/* + * This file contains various handy utilities used by top. + */ + +#include "top.h" +#include "os.h" + +int atoiwi(str) + +char *str; + +{ + register int len; + + len = strlen(str); + if (len != 0) + { + if (strncmp(str, "infinity", len) == 0 || + strncmp(str, "all", len) == 0 || + strncmp(str, "maximum", len) == 0) + { + return(Infinity); + } + else if (str[0] == '-') + { + return(Invalid); + } + else + { + return(atoi(str)); + } + } + return(0); +} + +/* + * itoa - convert integer (decimal) to ascii string for positive numbers + * only (we don't bother with negative numbers since we know we + * don't use them). + */ + + /* + * How do we know that 16 will suffice? + * Because the biggest number that we will + * ever convert will be 2^32-1, which is 10 + * digits. + */ + +char *itoa(val) + +register int val; + +{ + register char *ptr; + static char buffer[16]; /* result is built here */ + /* 16 is sufficient since the largest number + we will ever convert will be 2^32-1, + which is 10 digits. */ + + ptr = buffer + sizeof(buffer); + *--ptr = '\0'; + if (val == 0) + { + *--ptr = '0'; + } + else while (val != 0) + { + *--ptr = (val % 10) + '0'; + val /= 10; + } + return(ptr); +} + +/* + * itoa7(val) - like itoa, except the number is right justified in a 7 + * character field. This code is a duplication of itoa instead of + * a front end to a more general routine for efficiency. + */ + +char *itoa7(val) + +register int val; + +{ + register char *ptr; + static char buffer[16]; /* result is built here */ + /* 16 is sufficient since the largest number + we will ever convert will be 2^32-1, + which is 10 digits. */ + + ptr = buffer + sizeof(buffer); + *--ptr = '\0'; + if (val == 0) + { + *--ptr = '0'; + } + else while (val != 0) + { + *--ptr = (val % 10) + '0'; + val /= 10; + } + while (ptr > buffer + sizeof(buffer) - 7) + { + *--ptr = ' '; + } + return(ptr); +} + +/* + * digits(val) - return number of decimal digits in val. Only works for + * positive numbers. If val <= 0 then digits(val) == 0. + */ + +int digits(val) + +int val; + +{ + register int cnt = 0; + + while (val > 0) + { + cnt++; + val /= 10; + } + return(cnt); +} + +/* + * strecpy(to, from) - copy string "from" into "to" and return a pointer + * to the END of the string "to". + */ + +char *strecpy(to, from) + +register char *to; +register char *from; + +{ + while ((*to++ = *from++) != '\0'); + return(--to); +} + +/* + * string_index(string, array) - find string in array and return index + */ + +int string_index(string, array) + +char *string; +char **array; + +{ + register int i = 0; + + while (*array != NULL) + { + if (strcmp(string, *array) == 0) + { + return(i); + } + array++; + i++; + } + return(-1); +} + +/* + * argparse(line, cntp) - parse arguments in string "line", separating them + * out into an argv-like array, and setting *cntp to the number of + * arguments encountered. This is a simple parser that doesn't understand + * squat about quotes. + */ + +char **argparse(line, cntp) + +char *line; +int *cntp; + +{ + register char *from; + register char *to; + register int cnt; + register int ch; + int length; + int lastch; + register char **argv; + char **argarray; + char *args; + + /* unfortunately, the only real way to do this is to go thru the + input string twice. */ + + /* step thru the string counting the white space sections */ + from = line; + lastch = cnt = length = 0; + while ((ch = *from++) != '\0') + { + length++; + if (ch == ' ' && lastch != ' ') + { + cnt++; + } + lastch = ch; + } + + /* add three to the count: one for the initial "dummy" argument, + one for the last argument and one for NULL */ + cnt += 3; + + /* allocate a char * array to hold the pointers */ + argarray = (char **)malloc(cnt * sizeof(char *)); + + /* allocate another array to hold the strings themselves */ + args = (char *)malloc(length+2); + + /* initialization for main loop */ + from = line; + to = args; + argv = argarray; + lastch = '\0'; + + /* create a dummy argument to keep getopt happy */ + *argv++ = to; + *to++ = '\0'; + cnt = 2; + + /* now build argv while copying characters */ + *argv++ = to; + while ((ch = *from++) != '\0') + { + if (ch != ' ') + { + if (lastch == ' ') + { + *to++ = '\0'; + *argv++ = to; + cnt++; + } + *to++ = ch; + } + lastch = ch; + } + *to++ = '\0'; + + /* set cntp and return the allocated array */ + *cntp = cnt; + return(argarray); +} + +/* + * percentages(cnt, out, new, old, diffs) - calculate percentage change + * between array "old" and "new", putting the percentages i "out". + * "cnt" is size of each array and "diffs" is used for scratch space. + * The array "old" is updated on each call. + * The routine assumes modulo arithmetic. This function is especially + * useful on BSD mchines for calculating cpu state percentages. + */ + +long percentages(cnt, out, new, old, diffs) + +int cnt; +int *out; +register long *new; +register long *old; +long *diffs; + +{ + register int i; + register long change; + register long total_change; + register long *dp; + long half_total; + + /* initialization */ + total_change = 0; + dp = diffs; + + /* calculate changes for each state and the overall change */ + for (i = 0; i < cnt; i++) + { + if ((change = *new - *old) < 0) + { + /* this only happens when the counter wraps */ + change = (int) + ((unsigned long)*new-(unsigned long)*old); + } + total_change += (*dp++ = change); + *old++ = *new++; + } + + /* avoid divide by zero potential */ + if (total_change == 0) + { + total_change = 1; + } + + /* calculate percentages based on overall change, rounding up */ + half_total = total_change / 2l; + for (i = 0; i < cnt; i++) + { + *out++ = (int)((*diffs++ * 1000 + half_total) / total_change); + } + + /* return the total in case the caller wants to use it */ + return(total_change); +} + +/* + * errmsg(errnum) - return an error message string appropriate to the + * error number "errnum". This is a substitute for the System V + * function "strerror" with one important difference: the string + * returned by this function does NOT end in a newline! + * N.B.: there appears to be no reliable way to determine if + * "strerror" exists at compile time, so I make do by providing + * something of similar functionality. + */ + +/* externs referenced by errmsg */ + +extern char *sys_errlist[]; +extern int sys_nerr; + +char *errmsg(errnum) + +int errnum; + +{ + if (errnum > 0 && errnum < sys_nerr) + { + return(sys_errlist[errnum]); + } + return("No error"); +} + +/* format_time(seconds) - format number of seconds into a suitable + * display that will fit within 6 characters. Note that this + * routine builds its string in a static area. If it needs + * to be called more than once without overwriting previous data, + * then we will need to adopt a technique similar to the + * one used for format_k. + */ + +/* Explanation: + We want to keep the output within 6 characters. For low values we use + the format mm:ss. For values that exceed 999:59, we switch to a format + that displays hours and fractions: hhh.tH. For values that exceed + 999.9, we use hhhh.t and drop the "H" designator. For values that + exceed 9999.9, we use "???". + */ + +char *format_time(seconds) + +long seconds; + +{ + register int value; + register int digit; + register char *ptr; + static char result[10]; + + /* sanity protection */ + if (seconds < 0 || seconds > (99999l * 360l)) + { + strcpy(result, " ???"); + } + else if (seconds >= (1000l * 60l)) + { + /* alternate (slow) method displaying hours and tenths */ + snprintf(result, sizeof(result), "%5.1fH", + (double)seconds / (double)(60l * 60l)); + + /* It is possible that the sprintf took more than 6 characters. + If so, then the "H" appears as result[6]. If not, then there + is a \0 in result[6]. Either way, it is safe to step on. + */ + result[6] = '\0'; + } + else + { + /* standard method produces MMM:SS */ + /* we avoid printf as must as possible to make this quick */ + snprintf(result, sizeof(result), "%3d:%02d", seconds / 60l, + seconds % 60l); + } + return(result); +} + +/* + * format_k(amt) - format a kilobyte memory value, returning a string + * suitable for display. Returns a pointer to a static + * area that changes each call. "amt" is converted to a + * string with a trailing "K". If "amt" is 10000 or greater, + * then it is formatted as megabytes (rounded) with a + * trailing "M". + */ + +/* + * Compromise time. We need to return a string, but we don't want the + * caller to have to worry about freeing a dynamically allocated string. + * Unfortunately, we can't just return a pointer to a static area as one + * of the common uses of this function is in a large call to sprintf where + * it might get invoked several times. Our compromise is to maintain an + * array of strings and cycle thru them with each invocation. We make the + * array large enough to handle the above mentioned case. The constant + * NUM_STRINGS defines the number of strings in this array: we can tolerate + * up to NUM_STRINGS calls before we start overwriting old information. + * Keeping NUM_STRINGS a power of two will allow an intelligent optimizer + * to convert the modulo operation into something quicker. What a hack! + */ + +#define NUM_STRINGS 8 + +char *format_k(amt) + +int amt; + +{ + static char retarray[NUM_STRINGS][16]; + static int index = 0; + register char *p; + register char *ret; + register char tag = 'K'; + + p = ret = retarray[index]; + index = (index + 1) % NUM_STRINGS; + + if (amt >= 10000) + { + amt = (amt + 512) / 1024; + tag = 'M'; + if (amt >= 10000) + { + amt = (amt + 512) / 1024; + tag = 'G'; + } + } + + p = strecpy(p, itoa(amt)); + *p++ = tag; + *p = '\0'; + + return(ret); +} diff --git a/usr.bin/top/utils.h b/usr.bin/top/utils.h new file mode 100644 index 00000000000..2b12c4b6ab2 --- /dev/null +++ b/usr.bin/top/utils.h @@ -0,0 +1,25 @@ +/* $OpenBSD: utils.h,v 1.1 1997/08/14 14:00:28 downsj Exp $ */ + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +/* prototypes for functions found in utils.c */ + +int atoiwi(); +char *itoa(); +char *itoa7(); +int digits(); +char *strecpy(); +char **argparse(); +long percentages(); +char *errmsg(); +char *format_time(); +char *format_k(); diff --git a/usr.bin/top/version.c b/usr.bin/top/version.c new file mode 100644 index 00000000000..281cb59bf99 --- /dev/null +++ b/usr.bin/top/version.c @@ -0,0 +1,27 @@ +/* $OpenBSD: version.c,v 1.1 1997/08/14 14:00:28 downsj Exp $ */ + +/* + * Top users/processes display for Unix + * Version 3 + * + * This program may be freely redistributed, + * but this entire comment MUST remain intact. + * + * Copyright (c) 1984, 1989, William LeFebvre, Rice University + * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University + */ + +#include "top.h" +#include "patchlevel.h" + +static char version[16]; + +char *version_string() + +{ + snprintf(version, sizeof(version), "%d.%d", VERSION, PATCHLEVEL); +#ifdef BETA + strcat(version, BETA); +#endif + return(version); +} |