From 11c1eba093fb07b31b637d13cb59f4650b2af9db Mon Sep 17 00:00:00 2001 From: csmall <> Date: Thu, 30 May 2002 03:44:46 +0000 Subject: [PATCH] Added jims top and moved old files --- oldtop.c | 1766 +++++++++++++++++++++++ oldtop.h | 228 +++ top.c | 4153 ++++++++++++++++++++++++++++++++---------------------- top.h | 581 +++++--- 4 files changed, 4836 insertions(+), 1892 deletions(-) create mode 100644 oldtop.c create mode 100644 oldtop.h diff --git a/oldtop.c b/oldtop.c new file mode 100644 index 00000000..2faaf0cf --- /dev/null +++ b/oldtop.c @@ -0,0 +1,1766 @@ +/* + * top.c - show top CPU processes + * + * Copyright (c) 1992 Branko Lankester + * Copyright (c) 1992 Roger Binns + * Copyright (c) 1997 Michael K. Johnson + * + * Snarfed and HEAVILY modified in december 1992 for procps + * by Michael K. Johnson, johnsonm@sunsite.unc.edu. + * + * Modified Michael K. Johnson's ps to make it a top program. + * Also borrowed elements of Roger Binns kmem based top program. + * Changes made by Robert J. Nation (nation@rocket.sanders.lockheed.com) + * 1/93 + * + * Modified by Michael K. Johnson to be more efficient in cpu use + * 2/21/93 + * + * Changed top line to use uptime for the load average. Also + * added SIGTSTP handling. J. Cowley, 19 Mar 1993. + * + * Modified quite a bit by Michael Shields (mjshield@nyx.cs.du.edu) + * 1994/04/02. Secure mode added. "d" option added. Argument parsing + * improved. Switched order of tick display to user, system, nice, idle, + * because it makes more sense that way. Style regularized (to K&R, + * more or less). Cleaned up much throughout. Added cumulative mode. + * Help screen improved. + * + * Fixed kill buglet brought to my attention by Rob Hooft. + * Problem was mixing of stdio and read()/write(). Added + * getnum() to solve problem. + * 12/30/93 Michael K. Johnson + * + * Added toggling output of idle processes via 'i' key. + * 3/29/94 Gregory K. Nickonov + * + * Fixed buglet where rawmode wasn't getting restored. + * Added defaults for signal to send and nice value to use. + * 5/4/94 Jon Tombs. + * + * Modified 1994/04/25 Michael Shields + * Merged previous changes to 0.8 into 0.95. + * Allowed the use of symbolic names (e.g., "HUP") for signal input. + * Rewrote getnum() into getstr(), getint(), getsig(), etc. + * + * Modified 1995 Helmut Geyer + * added kmem top functionality (configurable fields) + * configurable order of process display + * Added options for dis/enabling uptime, statistics, and memory info. + * fixed minor bugs for ELF systems (e.g. SIZE, RSS fields) + * + * Modified 1996/05/18 Helmut Geyer + * Use of new interface and general cleanup. The code should be far more + * readable than before. + * + * Modified 1996/06/25 Zygo Blaxell + * Added field scaling code for programs that run more than two hours or + * take up more than 100 megs. We have lots of both on our production line. + * + * Modified 1998/02/21 Kirk Bauer + * Added the 'u' option to display only a selected user... plus it will + * take into account that not all 20 top processes are actually shown, + * so it can fit more onto the screen. I think this may help the + * 'don't show idle' mode, but I'm not sure. + * + * Modified 1997/07/27 & 1999/01/27 Tim Janik + * added `-p' option to display specific process ids. + * process sorting is by default disabled in this case. + * added `N' and `A' keys to sort the tasks Numerically by pid or + * sort them by Age (newest first). + * + * Modified 1999/10/22 Tim Janik + * miscellaneous minor fixes, including "usage: ..." output for + * unrecognized options. + * + * Modified 2000/02/07 Jakub Jelinek + * Only load System.map when we are going to display WCHAN. + * Show possible error messages from that load using SHOWMESSAGE. + * + * Modified 2000/07/10 Michael K. Johnson + * Integrated a patch to display SMP information. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proc/sysinfo.h" +#include "proc/procps.h" +#include "proc/whattime.h" +#include "proc/sig.h" +#include "proc/version.h" +#include "proc/readproc.h" +#include "proc/status.h" +#include "proc/devname.h" +#include "proc/compare.h" + +#define PUTP(x) (tputs(x,1,putchar)) +#define BAD_INPUT -30 + +#include "top.h" /* new header for top specific things */ + +static int *cpu_mapping; +static int nr_cpu; + +/*####################################################################### + *#### Startup routines: parse_options, get_options, ############## + *#### setup_terminal and main ############## + *####################################################################### + */ + + /* + * parse the options string as read from the config file(s). + * if top is in secure mode, disallow changing of the delay time between + * screen updates. + */ +static void parse_options(char *Options, int secure) +{ + int i; + for (i = 0; i < strlen(Options); i++) { + switch (Options[i]) { + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!secure) + Sleeptime = (float) Options[i] - '0'; + break; + case 'S': + Cumulative = 1; + headers[22][1] = 'C'; + break; + case 's': + Secure = 1; + break; + case 'i': + Noidle = 1; + break; + case 'm': + show_memory = 0; + header_lines -= 2; + break; + case 'M': + sort_type = S_MEM; + reset_sort_options(); + register_sort_function( -1, (cmp_t)mem_sort); + break; + case 'l': + show_loadav = 0; + header_lines -= 1; + break; + case 'P': + sort_type = S_PCPU; + reset_sort_options(); + register_sort_function( -1, (cmp_t)pcpu_sort); + break; + case 'N': + sort_type = S_NONE; + reset_sort_options(); + break; + case 'A': + sort_type = S_AGE; + reset_sort_options(); + register_sort_function( -1, (cmp_t)age_sort); + break; + case 't': + show_stats = 0; + header_lines -= 2; + break; + case 'T': + sort_type = S_TIME; + reset_sort_options(); + register_sort_function( -1, (cmp_t)time_sort); + break; + case 'c': + show_cmd = 0; + break; + case '\n': + break; + case 'I': + Irixmode = 0; + break; + default: + fprintf(stderr, "Wrong configuration option %c\n", i); + exit(1); + break; + } + } +} + +/* + * Read the configuration file(s). There are two files, once SYS_TOPRC + * which should only contain the secure switch and a sleeptime + * value iff ordinary users are to use top in secure mode only. + * + * The other file is $HOME/RCFILE. + * The configuration file should contain two lines (any of which may be + * empty). The first line specifies the fields that are to be displayed + * in the order you want them to. Uppercase letters specify fields + * displayed by default, lowercase letters specify fields not shown by + * default. The order of the letters in this line corresponds to the + * order of the displayed fileds. + * + * all Options but 'q' can be read from this config file + * The delay time option syntax differs from the commandline syntax: + * only integer values between 2 and 9 seconds are recognized + * (this is for standard configuration, so I think this should do). + * + * usually this file is not edited by hand, but written from top using + * the 'W' command. + */ + +static void get_options(void) +{ + FILE *fp; + char *pt; + char *rcfile = NULL; /* path to rc file... */ + char *home = NULL; /* path of user's home directory... */ + size_t home_length = 0; /* length of path... */ + char Options[256] = ""; + int i; + + nr_cpu = sysconf (_SC_NPROCESSORS_ONLN); + if (nr_cpu < 1) nr_cpu = 1; + cpu_mapping = (int *) xmalloc (sizeof (int) * nr_cpu); + /* read cpuname */ + for (i=0; i< nr_cpu; i++) cpu_mapping[i]=i; + header_lines = 6 + nr_cpu; + fp = fopen(SYS_TOPRC, "r"); + if (fp != NULL) { + fgets(Options, 254, fp); + fclose(fp); + } + parse_options(Options, 0); + strcpy(Options, ""); + + if ( (home = getenv("HOME")) != NULL) { + home_length = strlen(home); + } + + if ( (rcfile = malloc(home_length + strlen(RCFILE) + 2))) { + if (home != NULL) { + strcpy(rcfile, home); + strcat(rcfile, "/"); + } + strcat(rcfile, RCFILE); + fp = fopen(rcfile, "r"); + if (fp == NULL) { + strcpy(Fields, DEFAULT_SHOW); + } else { + if (fgets(Fields, 254, fp) != NULL) { + pt = strchr(Fields, '\n'); + if (pt) *pt = 0; + } + fgets(Options, 254, fp); + fclose(fp); + } + + free(rcfile); + } + parse_options(Options, getuid()? Secure : 0); +} + +/* + * Set up the terminal attributes. + */ +static void setup_terminal(void) +{ + char *termtype; + struct termios newtty; + if (!Batch) + termtype = getenv("TERM"); + else + termtype = "dumb"; + if (!termtype) { + /* In theory, $TERM should never not be set, but in practice, + some gettys don't. Fortunately, vt100 is nearly always + correct (or pretty close). */ + termtype = "VT100"; + /* fprintf(stderr, PROGNAME ": $TERM not set\n"); */ + /* exit(1); */ + } + + /* + * Get termcap entries and window size. + */ + if(tgetent(NULL, termtype) != 1) { + fprintf(stderr, PROGNAME ": Unknown terminal \"%s\" in $TERM\n", + termtype); + exit(1); + } + + cm = tgetstr("cm", 0); + top_clrtobot = tgetstr("cd", 0); + cl = tgetstr("cl", 0); + top_clrtoeol = tgetstr("ce", 0); + ho = tgetstr("ho", 0); + md = tgetstr("md", 0); + mr = tgetstr("mr", 0); + me = tgetstr("me", 0); + + + if (Batch) return; /* the rest doesn't apply to batch mode */ + if (tcgetattr(0, &Savetty) == -1) { + perror(PROGNAME ": tcgetattr() failed"); + error_end(errno); + } + newtty = Savetty; + newtty.c_lflag &= ~ICANON; + newtty.c_lflag &= ~ECHO; + newtty.c_cc[VMIN] = 1; + newtty.c_cc[VTIME] = 0; + if (tcsetattr(0, TCSAFLUSH, &newtty) == -1) { + printf("cannot put tty into raw mode\n"); + error_end(1); + } + tcgetattr(0, &Rawtty); +} + +static int parseint(const char *src, const char *err) +{ + char *endp; + int num; + int len; + num = strtol(src, &endp, 0); + if (*endp == '\0') return num; + /* also accept prefixes of: infinite, infinity, maximum, all */ + len = strlen(src); + if(len<1) goto fail; + if(len<9 && !strncmp(src,"infinite",len)) return INT_MAX; + if(len<9 && !strncmp(src,"infinity",len)) return INT_MAX; + if(len<8 && !strncmp(src,"maximum" ,len)) return INT_MAX; + if(len<4 && !strncmp(src,"all" ,len)) return INT_MAX; +fail: + fprintf(stderr, err, src); + exit(1); +} + +static double parseflt(const char *src, const char *err) +{ + char *endp; + double num; + int len; + num = strtod(src, &endp); + if (*endp == '\0') return num; + /* also accept prefixes of: infinite, infinity, maximum, all */ + len = strlen(src); + if(len<1) goto fail; + if(len<9 && !strncmp(src,"infinite",len)) return (double)INT_MAX; + if(len<9 && !strncmp(src,"infinity",len)) return (double)INT_MAX; + if(len<8 && !strncmp(src,"maximum" ,len)) return (double)INT_MAX; + if(len<4 && !strncmp(src,"all" ,len)) return (double)INT_MAX; +fail: + fprintf(stderr, err, src); + exit(1); +} + +int main(int argc, char **argv) +{ + /* For select(2). */ + struct timeval tv; + fd_set in; + /* For parsing arguments. */ + char *cp; + /* The key read in. */ + char c; + + struct sigaction sact; + + setlocale(LC_ALL, ""); + get_options(); + + /* set to PCPU sorting */ + register_sort_function( -1, (cmp_t)pcpu_sort); + + /* + * Parse arguments. + */ + (void)argc; + argv++; + while (*argv) { + cp = *argv++; + while (*cp) { + switch (*cp) { + case 'd': + if (cp[1]) { + Sleeptime = parseflt(++cp, PROGNAME ": Bad delay time %s'\n"); + goto breakargv; + } else if (*argv) { /* last char in an argv, use next as arg */ + Sleeptime = parseflt(cp = *argv++, PROGNAME ": Bad delay time %s'\n"); + goto breakargv; + } else { + fprintf(stderr, "-d requires an argument\n"); + exit(1); + } + break; + case 'n': + if (cp[1]) { + Loops = parseint(++cp, PROGNAME ": Bad value %s'\n"); + goto breakargv; + } else if (*argv) { /* last char in an argv, use next as arg */ + Loops = parseint(cp = *argv++, PROGNAME ": Bad value %s'\n"); + goto breakargv; + } + break; + + case 'q': + if (!getuid()) + /* set priority to -10 in order to stay above kswapd */ + if (setpriority(PRIO_PROCESS, getpid(), -10)) { + /* We check this just for paranoia. It's not + fatal, and shouldn't happen. */ + perror(PROGNAME ": setpriority() failed"); + } + Sleeptime = 0; + break; + case 'p': + if (monpids_index >= monpids_max) { + fprintf(stderr, PROGNAME ": More than %u process ids specified\n", + monpids_max); + exit(1); + } + if (cp[1]) { + if (sscanf(++cp, "%d", &monpids[monpids_index]) != 1 || + monpids[monpids_index] < 0 || monpids[monpids_index] > 65535) { + fprintf(stderr, PROGNAME ": Bad process id `%s'\n", cp); + exit(1); + } + } else if (*argv) { /* last char in an argv, use next as arg */ + if (sscanf(cp = *argv++, "%d", &monpids[monpids_index]) != 1 || + monpids[monpids_index] < 0 || monpids[monpids_index] > 65535) { + fprintf(stderr, PROGNAME ": Bad process id `%s'\n", cp); + exit(1); + } + } else { + fprintf(stderr, "-p requires an argument\n"); + exit(1); + } + if (!monpids[monpids_index]) + monpids[monpids_index] = getpid(); + /* default to no sorting when monitoring process ids */ + if (!monpids_index++) { + sort_type = S_NONE; + reset_sort_options(); + } + cp = "_"; + break; + case 'b': + Batch = 1; + break; + case 'c': + show_cmd = !show_cmd; + break; + case 'S': + Cumulative = 1; + break; + case 'i': + Noidle = 1; + break; + case 's': + Secure = 1; + break; + case 'C': + CPU_states = 1; + break; + case '-': + break; /* Just ignore it */ + case 'v': + case 'V': + fprintf(stdout, "top (%s)\n", procps_version); + exit(0); + case 'h': + fprintf(stdout, "usage: " PROGNAME " -hvbcisqS -d delay -p pid -n iterations\n"); + exit(0); + default: + fprintf(stderr, PROGNAME ": Unknown argument `%c'\n", *cp); + fprintf(stdout, "usage: " PROGNAME " -hvbcisqS -d delay -p pid -n iterations\n"); + exit(1); + } + cp++; + } + breakargv: + } + + if (nr_cpu > 1 && CPU_states) + header_lines++; + + meminfo(); /* need kb_main_total value filled in */ + + setup_terminal(); + window_size(0); + /* + * Set up signal handlers. + */ + sact.sa_handler = end; + sact.sa_flags = 0; + sigemptyset(&sact.sa_mask); + sigaction(SIGHUP, &sact, NULL); + sigaction(SIGINT, &sact, NULL); + sigaction(SIGQUIT, &sact, NULL); + sact.sa_handler = stop; + sact.sa_flags = SA_RESTART; + sigaction(SIGTSTP, &sact, NULL); + sact.sa_handler = window_size; + sigaction(SIGWINCH, &sact, NULL); + sigaction(SIGCONT, &sact, NULL); + + /* loop, collecting process info and sleeping */ + while (1) { + if (Loops > 0) + Loops--; + /* display the tasks */ + show_procs(); + /* sleep & wait for keyboard input */ + if (Loops == 0) + end(0); + if (!Batch) + { + tv.tv_sec = Sleeptime; + tv.tv_usec = (Sleeptime - (int) Sleeptime) * 1000000; + FD_ZERO(&in); + FD_SET(0, &in); + if (select(1, &in, 0, 0, &tv) > 0 && read(0, &c, 1) == 1) + do_key(c); + } else { + sleep(Sleeptime); + } + } +} + +/*####################################################################### + *#### Signal handled routines: error_end, end, stop, window_size ### + *#### Small utilities: make_header, getstr, getint, getfloat, getsig ### + *####################################################################### + */ + + + /* + * end when exiting with an error. + */ +static void error_end(int rno) +{ + if (!Batch) + tcsetattr(0, TCSAFLUSH, &Savetty); + PUTP(tgoto(cm, 0, Lines - 1)); + fputs("\r\n", stdout); + exit(rno); +} +/* + * Normal end of execution. + */ +static void end(int signo) +{ + (void)signo; + if (!Batch) + tcsetattr(0, TCSAFLUSH, &Savetty); + PUTP(tgoto(cm, 0, Lines - 1)); + fputs("\r\n", stdout); + exit(0); +} + +/* + * SIGTSTP catcher. + */ +static void stop(int signo) +{ + (void)signo; + /* Reset terminal. */ + if (!Batch) + tcsetattr(0, TCSAFLUSH, &Savetty); + PUTP(tgoto(cm, 0, Lines - 3)); + fflush(stdout); + raise(SIGSTOP); + /* Later... */ + if (!Batch) + tcsetattr (0, TCSAFLUSH, &Rawtty); +} + +/* + * Reads the window size and clear the window. This is called on setup, + * and also catches SIGWINCHs, and adjusts Maxlines. Basically, this is + * the central place for window size stuff. + */ +static void window_size(int signo) +{ + struct winsize ws; + (void)signo; + if((ioctl(1, TIOCGWINSZ, &ws) != -1) && (ws.ws_col>73) && (ws.ws_row>7)){ + Cols = ws.ws_col; + Lines = ws.ws_row; + }else{ + Cols = tgetnum("co"); + Lines = tgetnum("li"); + } + if (!Batch) + clear_screen(); + /* + * calculate header size, length of cmdline field ... + */ + Numfields = make_header(); +} + +/* + * this prints a possible message from open_psdb_message + */ +static void top_message(const char *format, ...) { + va_list arg; + int n; + char buffer[512]; + + va_start (arg, format); + n = vsnprintf (buffer, 512, format, arg); + va_end (arg); + if (n > -1 && n < 512) + SHOWMESSAGE(("%s", buffer)); +} + +/* + * this adjusts the lines needed for the header to the current value + */ +static int make_header(void) +{ + int i, j; + + j = 0; + for (i = 0; i < strlen(Fields); i++) { + if (Fields[i] < 'a') { + pflags[j++] = Fields[i] - 'A'; + if (Fields[i] == 'U' && CL_wchan_nout == -1) { + CL_wchan_nout = 0; + /* for correct handling of WCHAN fields, we have to do distingu + * between kernel versions */ + /* get kernel symbol table, if needed */ + if (open_psdb_message(NULL, top_message)) { + CL_wchan_nout = 1; + } else { + psdbsucc = 1; + } + } + } + } + strcpy(Header, ""); + for (i = 0; i < j; i++) + strcat(Header, headers[pflags[i]]); + /* readjust window size ... */ + Maxcmd = Cols - strlen(Header) + 7; + Maxlines = Display_procs ? Display_procs : Lines - header_lines; + if (Maxlines > Lines - header_lines) + Maxlines = Lines - header_lines; + return (j); +} + + + +/* + * Get a string from the user; the base of getint(), et al. This really + * ought to handle long input lines and errors better. NB: The pointer + * returned is a statically allocated buffer, so don't expect it to + * persist between calls. + */ +static char *getstr(void) +{ + static char line[BUFSIZ]; /* BUFSIZ from ; arbitrary */ + int i = 0; + + /* Must make sure that buffered IO doesn't kill us. */ + fflush(stdout); + fflush(stdin); /* Not POSIX but ok */ + + do { + read(STDIN_FILENO, &line[i], 1); + } while (line[i++] != '\n' && i < sizeof(line)); + line[--i] = 0; + + return (line); +} + + +/* + * Get an integer from the user. Display an error message and + * return BAD_INPUT if it's invalid; else return the number. + */ +static int getint(void) +{ + char *line; + int i; + int r; + + line = getstr(); + + for (i = 0; line[i]; i++) { + if (!isdigit(line[i]) && line[i] != '-') { + SHOWMESSAGE(("That's not a number!")); + return (BAD_INPUT); + } + } + + /* An empty line is a legal error (hah!). */ + if (!line[0]) + return (BAD_INPUT); + + sscanf(line, "%d", &r); + return (r); +} + + +/* + * Get a float from the user. Just like getint(). + */ +static float getfloat(void) +{ + char *line; + int i; + float r; + char *savelocale; + + line = getstr(); + + for (i = 0; line[i]; i++) { + if (!isdigit(line[i]) && line[i] != '.' && line[i] != '-') { + SHOWMESSAGE(("That's not a float!")); + return (BAD_INPUT); + } + } + + /* An empty line is a legal error (hah!). */ + if (!line[0]) + return (BAD_INPUT); + + savelocale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + sscanf(line, "%f", &r); + setlocale(LC_NUMERIC, savelocale); + return (r); +} + + +/* + * Get a signal number or name from the user. Return the number, or -1 + * on error. + */ +static int getsig(void) +{ + char *line; + + /* This is easy. */ + line = getstr(); + return signal_name_to_number(line); +} + +/*####################################################################### + *#### Routine for sorting on used time, resident memory and %CPU ##### + *#### It would be easy to include full sorting capability as in ##### + *#### ps, but I think there is no real use for something that ##### + *#### complicated. Using register_sort_function or parse_sort_opt ##### + *#### you just have to do the natural thing and it will work. ##### + *####################################################################### + */ + +static int time_sort (proc_t **P, proc_t **Q) +{ + if (Cumulative) { + if( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) < + ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) + return -1; + if( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) > + ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) + return 1; + } else { + if( ((*P)->utime + (*P)->stime) < ((*Q)->utime + (*Q)->stime)) + return -1; + if( ((*P)->utime + (*P)->stime) > ((*Q)->utime + (*Q)->stime)) + return 1; + } + return 0; +} + +static int pcpu_sort (proc_t **P, proc_t **Q) +{ + if( (*P)->pcpu < (*Q)->pcpu ) return -1; + if( (*P)->pcpu > (*Q)->pcpu ) return 1; + return 0; +} + +static int mem_sort (proc_t **P, proc_t **Q) +{ + if( (*P)->vm_rss < (*Q)->vm_rss ) return -1; + if( (*P)->vm_rss > (*Q)->vm_rss ) return 1; + return 0; +} + +int age_sort (proc_t **P, proc_t **Q) +{ + if( (*P)->start_time < (*Q)->start_time ) return -1; + if( (*P)->start_time > (*Q)->start_time ) return 1; + return 0; +} + +/*####################################################################### + *#### Routines handling the field selection/ordering screens: ######## + *#### show_fields, change_order, change_fields ######## + *####################################################################### + */ + + /* + * Display the specification line of all fields. Upper case indicates + * a displayed field, display order is according to the order of the + * letters. A short description of each field is shown as well. + * The description of a displayed field is marked by a leading + * asterisk (*). + */ +static void show_fields(void) +{ + int i, row, col; + char *p; + + clear_screen(); + PUTP(tgoto(cm, 3, 0)); + printf("Current Field Order: %s\n", Fields); + for (i = 0; i < sizeof headers / sizeof headers[0]; ++i) { + row = i % (Lines - 3) + 3; + col = i / (Lines - 3) * 40; + PUTP(tgoto(cm, col, row)); + for (p = headers[i]; *p == ' '; ++p); + printf("%c %c: %-10s = %s", (strchr(Fields, i + 'A') != NULL) ? '*' : ' ', i + 'A', + p, headers2[i]); + } +} + +/* + * change order of displayed fields + */ +static void change_order(void) +{ + char c, ch, *p; + int i; + + show_fields(); + for (;;) { + PUTP(tgoto(cm, 0, 0)); + PUTP(top_clrtoeol); + PUTP(tgoto(cm, 3, 0)); + PUTP(mr); + printf("Current Field Order: %s", Fields); + PUTP(me); + putchar('\n'); + PUTP(tgoto(cm, 0, 1)); + printf("Upper case characters move a field to the left, lower case to the right"); + fflush(stdout); + if (!Batch) { /* should always be true, but... */ + tcsetattr(0, TCSAFLUSH, &Rawtty); + read(0, &c, 1); + tcsetattr(0, TCSAFLUSH, &Savetty); + } + i = toupper(c) - 'A'; + if ((p = strchr(Fields, i + 'A')) != NULL) { + if (isupper(c)) + p--; + if ((p[1] != '\0') && (p >= Fields)) { + ch = p[0]; + p[0] = p[1]; + p[1] = ch; + } + } else if ((p = strchr(Fields, i + 'a')) != NULL) { + if (isupper(c)) + p--; + if ((p[1] != '\0') && (p >= Fields)) { + ch = p[0]; + p[0] = p[1]; + p[1] = ch; + } + } else { + break; + } + } + Numfields = make_header(); +} +/* + * toggle displayed fields + */ +static void change_fields(void) +{ + int i, changed = 0; + int row, col; + char c, *p; + char tmp[2] = " "; + + show_fields(); + for (;;) { + PUTP(tgoto(cm, 0, 0)); + PUTP(top_clrtoeol); + PUTP(tgoto(cm, 3, 0)); + PUTP(mr); + printf("Current Field Order: %s", Fields); + PUTP(me); + putchar('\n'); + PUTP(tgoto(cm, 0, 1)); + if (!Batch) { /* should always be true, but... */ + printf("Toggle fields with a-x, any other key to return: "); + fflush(stdout); + tcsetattr(0, TCSAFLUSH, &Rawtty); + read(0, &c, 1); + tcsetattr(0, TCSAFLUSH, &Savetty); + } + i = toupper(c) - 'A'; + if (i >= 0 && i < sizeof headers / sizeof headers[0]) { + row = i % (Lines - 3) + 3; + col = i / (Lines - 3) * 40; + PUTP(tgoto(cm, col, row)); + if ((p = strchr(Fields, i + 'A')) != NULL) { /* deselect Field */ + *p = i + 'a'; + putchar(' '); + } else if ((p = strchr(Fields, i + 'a')) != NULL) { /* select previously */ + *p = i + 'A'; /* deselected field */ + putchar('*'); + } else { /* select new field */ + tmp[0] = i + 'A'; + strcat(Fields, tmp); + putchar('*'); + } + changed = 1; + fflush(stdout); + } else + break; + } + if (changed) + Numfields = make_header(); +} + +/* Do the scaling stuff: interprets time in seconds, formats it to + * fit width, and returns pointer to static char*. + */ +static char *scale_time(int t,int width) +{ + static char buf[100]; + + /* Try successively higher units until it fits */ + + sprintf(buf,"%d:%02d",t/60,t%60); /* minutes:seconds */ + if (strlen(buf)<=width) + return buf; + + t/=60; /* minutes */ + sprintf(buf,"%dm",t); + if (strlen(buf)<=width) + return buf; + + t/=60; /* hours */ + sprintf(buf,"%dh",t); + if (strlen(buf)<=width) + return buf; + + t/=24; /* days */ + sprintf(buf,"%dd",t); + if (strlen(buf)<=width) + return buf; + + t/=7; /* weeks */ + sprintf(buf,"%dw",t); + return buf; /* this is our last try; + if it still doesn't fit, too bad. */ + + /* FIXME: if someone has a 16-way SMP running over a year... */ +} + +/* scale_k(k,width,unit) - interprets k as a count, formats to fit width. + if unit is 0, k is a byte count; 1 is a kilobyte + count; 2 for megabytes; 3 for gigabytes. +*/ + +static char *scale_k(int k,int width,int unit) +{ + /* kilobytes, megabytes, gigabytes, too-big-for-int-bytes */ + static double scale[]={1024,1024*1024,1024*1024*1024,0}; + /* kilo, mega, giga, tera */ + static char unitletters[]={'K','M','G','T',0}; + static char buf[100]; + char *up; + double *dp; + + /* Try successively higher units until it fits */ + + sprintf(buf,"%d",k); + if (strlen(buf)<=width) + return buf; + + for (up=unitletters+unit,dp=scale ; *dp ; ++dp,++up) { + sprintf(buf,"%.1f%c",k / *dp,*up); + if (strlen(buf)<=width) + return buf; + sprintf(buf,"%d%c",(int)(k / *dp),*up); + if (strlen(buf)<=width) + return buf; + } + + /* Give up; give them what we got on our shortest attempt */ + return buf; +} + +/* + *####################################################################### + *#### Routines handling the main top screen: ######## + *#### show_task_info, show_procs, show_memory, do_stats ######## + *####################################################################### + */ + /* + * Displays infos for a single task + */ +static void show_task_info(proc_t *task) +{ + int i,j; + unsigned int t; + char *cmdptr; + char tmp[2048], tmp2[2048] = "", tmp3[2048] = "", *p; + + for (i = 0; i < Numfields; i++) { + tmp[0] = 0; + switch (pflags[i]) { + case P_PID: + sprintf(tmp, "%5d ", task->pid); + break; + case P_PPID: + sprintf(tmp, "%5d ", task->ppid); + break; + case P_EUID: + sprintf(tmp, "%4d ", task->euid); + break; + case P_EUSER: + sprintf(tmp, "%-8.8s ", task->euser); + break; + case P_PCPU: + sprintf(tmp, "%4.1f ", (float)task->pcpu / 10); + break; + case P_LCPU: + sprintf(tmp, "%2d ", task->processor); + break; + case P_PMEM: { + unsigned pmem; + pmem = task->vm_rss * 1000ULL / kb_main_total; + if (pmem > 999) pmem = 999; + sprintf(tmp, "%2u.%u ", pmem/10U, pmem%10U); + } + break; + case P_TTY: { + char outbuf[9]; + dev_to_tty(outbuf, 8, task->tty, task->pid, ABBREV_DEV); + sprintf(tmp, "%-8.8s ", outbuf); + } + break; + case P_PRI: + sprintf(tmp, "%3.3s ", scale_k(task->priority, 3, 0)); + break; + case P_NICE: + sprintf(tmp, "%3.3s ", scale_k(task->nice, 3, 0)); + break; + case P_PAGEIN: + sprintf(tmp, "%6.6s ", scale_k(task->maj_flt, 6, 0)); + break; + case P_TSIZ: + sprintf(tmp, "%5.5s ", + scale_k(((task->end_code - task->start_code) / 1024), 5, 1)); + break; + case P_DSIZ: + sprintf(tmp, "%5.5s ", + scale_k(((task->vsize - task->end_code) / 1024), 5, 1)); + break; + case P_SIZE: + sprintf(tmp, "%5.5s ", scale_k((task->size << CL_pg_shift), 5, 1)); + break; + case P_TRS: + sprintf(tmp, "%4.4s ", scale_k((task->trs << CL_pg_shift), 4, 1)); + break; + case P_SWAP: + sprintf(tmp, "%4.4s ", + scale_k(((task->size - task->resident) << CL_pg_shift), 4, 1)); + break; + case P_SHARE: + sprintf(tmp, "%5.5s ", scale_k((task->share << CL_pg_shift), 5, 1)); + break; + case P_A: + sprintf(tmp, "%3.3s ", "NYI"); + break; + case P_WP: + sprintf(tmp, "%3.3s ", "NYI"); + break; + case P_DT: + sprintf(tmp, "%3.3s ", scale_k(task->dt, 3, 0)); + break; + case P_RSS: /* rss, not resident (which includes IO memory) */ + sprintf(tmp, "%4.4s ", + scale_k((task->rss << CL_pg_shift), 4, 1)); + break; + case P_WCHAN: + if (!CL_wchan_nout) + sprintf(tmp, "%-9.9s ", wchan(task->wchan)); + else + sprintf(tmp, "%-9lx", task->wchan); + break; + case P_STAT: + sprintf(tmp, "%-4.4s ", status(task)); + break; + case P_TIME: + t = (task->utime + task->stime) / Hertz; + if (Cumulative) + t += (task->cutime + task->cstime) / Hertz; + sprintf(tmp, "%6.6s ", scale_time(t,6)); + break; + case P_COMMAND: + if (!show_cmd && task->cmdline && *(task->cmdline)) { + j=0; + while(((task->cmdline)[j] != NULL) && (strlen(tmp3)<1020)){ +/* #if 0 */ /* This is useless? FIXME */ + if (j > 0) + strcat(tmp3, " "); +/* #endif */ + strncat(tmp3, (task->cmdline)[j], 1000); + j++; + } + cmdptr = tmp3; + } else { + cmdptr = task->cmd; + } + if (strlen(cmdptr) > Maxcmd) + cmdptr[Maxcmd - 1] = 0; + sprintf(tmp, "%s", cmdptr); + tmp3[0]=0; + break; + case P_FLAGS: + sprintf(tmp, "%8lx ", task->flags); + break; + } + strcat(tmp2, tmp); + } + if (strlen(tmp2) > Cols - 1) + tmp2[Cols - 1] = 0; + + /* take care of cases like: + perl -e 'foo + bar foo bar + foo + # end of perl script' + */ + for (p=tmp2;*p;++p) + if (!isgraph(*p)) + *p=' '; + + printf("\n%s", tmp2); + PUTP(top_clrtoeol); +} + +/* + * This is the real program! Read process info and display it. + * One could differentiate options of readproctable2, perhaps it + * would be useful to support the PROC_UID and PROC_TTY + * as command line options. + */ +static void show_procs(void) +{ + static proc_t **p_table=NULL; + static int proc_flags; + int count; + int ActualLines; + float elapsed_time; + static int first=0; + + if (first==0) { + proc_flags=PROC_FILLMEM|PROC_FILLCMD|PROC_FILLUSR|PROC_FILLSTATUS|PROC_FILLSTAT; + if (monpids_index) + proc_flags |= PROC_PID; + p_table=readproctab2(proc_flags, p_table, monpids); + elapsed_time = get_elapsed_time(); + do_stats(p_table, elapsed_time, 0); + sleep(1); + first=1; + } + if (first && Batch) + fputs("\n\n",stdout); + /* Display the load averages. */ + PUTP(ho); + PUTP(md); + if (show_loadav) { + printf("%s", sprint_uptime()); + PUTP(top_clrtoeol); + putchar('\n'); + } + p_table=readproctab2(proc_flags, p_table, monpids); + /* Immediately find out the elapsed time for the frame. */ + elapsed_time = get_elapsed_time(); + /* Display the system stats, calculate percent CPU time + * and sort the list. */ + do_stats(p_table, elapsed_time,1); + /* Display the memory and swap space usage. */ + show_meminfo(); + if (strlen(Header) + 2 > Cols) + Header[Cols - 2] = 0; + PUTP(mr); + fputs(Header, stdout); + PUTP(top_clrtoeol); + PUTP(me); + + /* + * Finally! Loop through to find the top task, and display it. + * Lather, rinse, repeat. + */ + count = 0; + ActualLines = 0; + while ((ActualLines < Maxlines) && (p_table[count]->pid!=-1)) { + char Stat; + + Stat = p_table[count]->state; + + if ( (!Noidle || (Stat != 'S' && Stat != 'Z')) && + ( (CurrUser[0] == '\0') || + (!strcmp((char *)CurrUser,p_table[count]->euser) ) ) ) { + + /* + * Show task info. + */ + show_task_info(p_table[count]); + if (!Batch) + ActualLines++; + } + count++; + } + PUTP(top_clrtobot); + PUTP(tgoto(cm, 0, header_lines - 2)); + fflush(stdout); +} + + +/* + * Finds the current time (in microseconds) and calculates the time + * elapsed since the last update. This is essential for computing + * percent CPU usage. + */ +static float get_elapsed_time(void) +{ + struct timeval t; + static struct timeval oldtime; + struct timezone timez; + float elapsed_time; + + gettimeofday(&t, &timez); + elapsed_time = (t.tv_sec - oldtime.tv_sec) + + (float) (t.tv_usec - oldtime.tv_usec) / 1000000.0; + oldtime.tv_sec = t.tv_sec; + oldtime.tv_usec = t.tv_usec; + return (elapsed_time); +} + +/* + * Reads the memory info and displays it. Returns the total memory + * available, for use in percent memory usage calculations. + */ +static void show_meminfo(void) +{ + meminfo(); /* read+parse /proc/meminfo */ + if (show_memory) { + printf( + "Mem: %8dK total, %8dK used, %8dK free, %8dK buffers", + kb_main_total, + kb_main_used, + kb_main_free, + kb_main_buffers + ); + PUTP(top_clrtoeol); + putchar('\n'); + printf( + "Swap: %8dK total, %8dK used, %8dK free, %8dK cached", + kb_swap_total, + kb_swap_used, + kb_swap_free, + kb_main_cached + ); + PUTP(top_clrtoeol); + putchar('\n'); + } + PUTP(me); + PUTP(top_clrtoeol); + putchar('\n'); +} + + +/* + * Calculates the number of tasks in each state (running, sleeping, etc.). + * Calculates the CPU time in each state (system, user, nice, etc). + * Calculates percent cpu usage for each task. + */ +static void do_stats(proc_t** p, float elapsed_time, int pass) +{ + proc_t *this; + int arrindex, total_time, cpumap, i, n = 0; + int sleeping = 0, stopped = 0, zombie = 0, running = 0; + double system_ticks, user_ticks, nice_ticks, idle_ticks; + static int prev_count = 0; + int systime, usrtime; + + /* start with one page as a reasonable allocate size */ + static int save_history_size = + sizeof(long)*1024 / sizeof(struct save_hist); + static struct save_hist *save_history; + struct save_hist *New_save_hist; + + if (!save_history) + save_history = xcalloc(NULL, sizeof(struct save_hist)*save_history_size); + New_save_hist = xcalloc(NULL, sizeof(struct save_hist)*save_history_size); + + /* + * Make a pass through the data to get stats. + */ + arrindex = 0; + while (p[n]->pid != -1) { + this = p[n]; + switch (this->state) { + case 'S': + case 'D': + sleeping++; + break; + case 'T': + stopped++; + break; + case 'Z': + zombie++; + break; + case 'R': + running++; + break; + default: + /* Don't know how to handle this one. */ + break; + } + + /* + * Calculate time in this process. Time is sum of user time + * (usrtime) plus system time (systime). + */ + total_time = this->utime + this->stime; + if (arrindex >= save_history_size) { + save_history_size *= 2; + save_history = xrealloc(save_history, sizeof(struct save_hist)*save_history_size); + New_save_hist = xrealloc(New_save_hist, sizeof(struct save_hist)*save_history_size); + } + New_save_hist[arrindex].ticks = total_time; + New_save_hist[arrindex].pid = this->pid; + systime = this->stime; + usrtime = this->utime; + New_save_hist[arrindex].stime = systime; + New_save_hist[arrindex].utime = usrtime; + + /* find matching entry from previous pass */ + for (i = 0; i < prev_count; i++) { + if (save_history[i].pid == this->pid) { + total_time -= save_history[i].ticks; + systime -= save_history[i].stime; + usrtime -= save_history[i].utime; + + i = prev_count; + } + } + + /* + * Calculate percent cpu time for this task. + */ + this->pcpu = (total_time * 10 * 100/Hertz) / elapsed_time; + if (this->pcpu > 999) + this->pcpu = 999; + + arrindex++; + n++; + } + + /* + * Display stats. + */ + if (pass > 0 && show_stats) { + printf("%d processes: %d sleeping, %d running, %d zombie, " + "%d stopped", + n, sleeping, running, zombie, stopped); + PUTP(top_clrtoeol); + putchar('\n'); + four_cpu_numbers(&user_ticks,&nice_ticks,&system_ticks,&idle_ticks); + printf("CPU states:" + " %# 5.1f%% user, %# 5.1f%% system," + " %# 5.1f%% nice, %# 5.1f%% idle", + user_ticks, + system_ticks, + nice_ticks, + idle_ticks + ); + PUTP(top_clrtoeol); + putchar('\n'); + } + /* + * Save this frame's information. + */ + for (i = 0; i < n; i++) { + /* copy the relevant info for the next pass */ + save_history[i].pid = New_save_hist[i].pid; + save_history[i].ticks = New_save_hist[i].ticks; + save_history[i].stime = New_save_hist[i].stime; + save_history[i].utime = New_save_hist[i].utime; + } + free(New_save_hist); + + prev_count = n; + qsort(p, n, sizeof(proc_t*), (void*)mult_lvl_cmp); +} + + +/* + * Process keyboard input during the main loop + */ +static void do_key(char c) +{ + int numinput, i; + char rcfile[MAXNAMELEN]; + FILE *fp; + + /* + * First the commands which don't require a terminal mode switch. + */ + if (c == 'q') + end(0); + else if (c == ' ') + return; + else if (c == 12) { + clear_screen(); + return; + } else if (c == 'I') { + Irixmode=(Irixmode) ? 0 : 1; + return; + } + + /* + * Switch the terminal to normal mode. (Will the original + * attributes always be normal? Does it matter? I suppose the + * shell will be set up the way the user wants it.) + */ + if (!Batch) tcsetattr(0, TCSANOW, &Savetty); + + /* + * Handle the rest of the commands. + */ + switch (c) { + case '?': + case 'h': + PUTP(cl); PUTP(ho); putchar('\n'); PUTP(mr); + printf("Proc-Top Revision 1.2"); + PUTP(me); putchar('\n'); + printf("Secure mode "); + PUTP(md); + fputs(Secure ? "on" : "off", stdout); + PUTP(me); + fputs("; cumulative mode ", stdout); + PUTP(md); + fputs(Cumulative ? "on" : "off", stdout); + PUTP(me); + fputs("; noidle mode ", stdout); + PUTP(md); + fputs(Noidle ? "on" : "off", stdout); + PUTP(me); + fputs("\n\n", stdout); + printf("%s\n\nPress any key to continue", Secure ? SECURE_HELP_SCREEN : HELP_SCREEN); + if (!Batch) tcsetattr(0, TCSANOW, &Rawtty); + (void) getchar(); + break; + case 'i': + Noidle = !Noidle; + SHOWMESSAGE(("No-idle mode %s", Noidle ? "on" : "off")); + break; + case 'u': + SHOWMESSAGE(("Which User (Blank for All): ")); + strcpy(CurrUser,getstr()); + break; + case 'k': + if (Secure) + SHOWMESSAGE(("\aCan't kill in secure mode")); + else { + int pid, signo; + PUTP(md); + SHOWMESSAGE(("PID to kill: ")); + pid = getint(); + if (pid == BAD_INPUT) + break; + PUTP(top_clrtoeol); + SHOWMESSAGE(("Kill process %d with what signal? [15] ", pid)); + PUTP(me); + signo = getsig(); + /* FIXME: -1 may mean an unknown signal */ + if (signo == -1) + signo = SIGTERM; + if (kill(pid, signo)) + SHOWMESSAGE(("\aKill of PID %d with %d failed: %s", + pid, signo, strerror(errno))); + } + break; + case 'l': + SHOWMESSAGE(("Display load average %s", !show_loadav ? "on" : "off")); + if (show_loadav) { + show_loadav = 0; + header_lines--; + } else { + show_loadav = 1; + header_lines++; + } + Numfields = make_header(); + break; + case 'm': + SHOWMESSAGE(("Display memory information %s", !show_memory ? "on" : "off")); + if (show_memory) { + show_memory = 0; + header_lines -= 2; + } else { + show_memory = 1; + header_lines += 2; + } + Numfields = make_header(); + break; + case 'M': + SHOWMESSAGE(("Sort by memory usage")); + sort_type = S_MEM; + reset_sort_options(); + register_sort_function(-1, (cmp_t)mem_sort); + break; + case 'n': + case '#': + printf("Processes to display (0 for unlimited): "); + numinput = getint(); + if (numinput != -1) { + Display_procs = numinput; + window_size(0); + } + break; + case 'r': + if (Secure) + SHOWMESSAGE(("\aCan't renice in secure mode")); + else { + int pid, val; + + printf("PID to renice: "); + pid = getint(); + if (pid == BAD_INPUT) + break; + PUTP(tgoto(cm, 0, header_lines - 2)); + PUTP(top_clrtoeol); + printf("Renice PID %d to value: ", pid); + val = getint(); + if (val == BAD_INPUT) + val = 10; + if (setpriority(PRIO_PROCESS, pid, val)) + SHOWMESSAGE(("\aRenice of PID %d to %d failed: %s", + pid, val, strerror(errno))); + } + break; + case 'P': + SHOWMESSAGE(("Sort by CPU usage")); + sort_type = S_PCPU; + reset_sort_options(); + register_sort_function(-1, (cmp_t)pcpu_sort); + break; + case 'A': + SHOWMESSAGE(("Sort by age")); + sort_type = S_AGE; + reset_sort_options(); + register_sort_function(-1, (cmp_t)age_sort); + break; + case 'N': + SHOWMESSAGE(("Sort numerically by pid")); + sort_type = S_NONE; + reset_sort_options(); + break; + case 'c': + show_cmd = !show_cmd; + SHOWMESSAGE(("Show %s", show_cmd ? "command names" : "command line")); + break; + case 'S': + Cumulative = !Cumulative; + SHOWMESSAGE(("Cumulative mode %s", Cumulative ? "on" : "off")); + if (Cumulative) + headers[22][1] = 'C'; + else + headers[22][1] = ' '; + Numfields = make_header(); + break; + case 's': + if (Secure) + SHOWMESSAGE(("\aCan't change delay in secure mode")); + else { + double tmp; + printf("Delay between updates: "); + tmp = getfloat(); + if (!(tmp < 0)) + Sleeptime = tmp; + } + break; + case 't': + SHOWMESSAGE(("Display summary information %s", !show_stats ? "on" : "off")); + if (show_stats) { + show_stats = 0; + header_lines -= 2; + } else { + show_stats = 1; + header_lines += 2; + } + Numfields = make_header(); + break; + case 'T': + SHOWMESSAGE(("Sort by %stime", Cumulative ? "cumulative " : "")); + sort_type = S_TIME; + reset_sort_options(); + register_sort_function( -1, (cmp_t)time_sort); + break; + case 'f': + case 'F': + change_fields(); + break; + case 'o': + case 'O': + change_order(); + break; + case 'W': + if (Secure) + SHOWMESSAGE(("\aCan't write configuration in secure mode")); + else { + if (getenv("HOME")) { + strcpy(rcfile, getenv("HOME")); + strcat(rcfile, "/"); + strcat(rcfile, RCFILE); + fp = fopen(rcfile, "w"); + if (fp != NULL) { + fprintf(fp, "%s\n", Fields); + i = (int) Sleeptime; + if (i < 2) + i = 2; + if (i > 9) + i = 9; + fprintf(fp, "%d", i); + if (Secure) + fprintf(fp, "%c", 's'); + if (Cumulative) + fprintf(fp, "%c", 'S'); + if (!show_cmd) + fprintf(fp, "%c", 'c'); + if (Noidle) + fprintf(fp, "%c", 'i'); + if (!show_memory) + fprintf(fp, "%c", 'm'); + if (!show_loadav) + fprintf(fp, "%c", 'l'); + if (!show_stats) + fprintf(fp, "%c", 't'); + if (!Irixmode) + fprintf(fp, "%c", 'I'); + fprintf(fp, "\n"); + fclose(fp); + SHOWMESSAGE(("Wrote configuration to %s", rcfile)); + } else { + SHOWMESSAGE(("Couldn't open %s", rcfile)); + } + } else { + SHOWMESSAGE(("Couldn't get $HOME -- not saving")); + } + } + break; + default: + SHOWMESSAGE(("\aUnknown command `%c' -- hit `h' for help", c)); + } + + /* + * Return to raw mode. + */ + if (!Batch) tcsetattr(0, TCSANOW, &Rawtty); + return; +} + + +/*##################################################################### + *####### A readproctable function that uses already allocated ##### + *####### table entries. ##### + *##################################################################### + */ +#define Do(x) (flags & PROC_ ## x) + +static proc_t** readproctab2(int flags, proc_t** tab, ...) { + PROCTAB* PT = NULL; + static proc_t *buff; + int n = 0; + static int len = 0; + va_list ap; + + va_start(ap, tab); /* pass through args to openproc */ + if (Do(UID)) { + /* temporary variables to ensure that va_arg() instances + * are called in the right order + */ + uid_t* u; + int i; + + u = va_arg(ap, uid_t*); + i = va_arg(ap, int); + PT = openproc(flags, u, i); + } + else if (Do(PID)) { + PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */ + /* work around a bug in openproc() */ + PT->procfs = NULL; + /* share some process time, since we skipped opendir("/proc") */ + usleep (50*1000); + } + else if (Do(TTY) || Do(STAT)) + PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */ + else + PT = openproc(flags); + va_end(ap); + buff = (proc_t *) 1; + while (ncmdline) { + free((void*)*tab[n]->cmdline); + tab[n]->cmdline = NULL; + } + buff = readproc(PT, tab[n]); + if (buff) n++; + } + if (buff) { + do { /* (ii) not yet allocated chunks */ + tab = xrealloc(tab, (n+1)*sizeof(proc_t*));/* realloc as we go, using */ + buff = readproc(PT, NULL); /* final null to terminate */ + if(buff) tab[n]=buff; + len++; + n++; + } while (buff); /* stop when NULL reached */ + tab[n-1] = xcalloc(NULL, sizeof (proc_t)); + tab[n-1]->pid=-1; /* Mark end of Table */ + } else { + if (n == len) { + tab = xrealloc(tab, (n+1)*sizeof(proc_t*)); + tab[n] = xcalloc(NULL, sizeof (proc_t)); + len++; + } + tab[n]->pid=-1; /* Use this instead of NULL when not at the end of */ + } /* the allocated space */ + closeproc(PT); + return tab; +} diff --git a/oldtop.h b/oldtop.h new file mode 100644 index 00000000..e7cc2e2e --- /dev/null +++ b/oldtop.h @@ -0,0 +1,228 @@ +/* + * top.h header file 1996/05/18, + * + * function prototypes, global data definitions and string constants. + */ + +static proc_t** readproctab2(int flags, proc_t** tab, ...); +static void parse_options(char *Options, int secure); +static void get_options(void); +static void error_end(int rno); +static void end(int signo); +static void stop(int signo); +static void window_size(int signo); +static int make_header(void); +static char *getstr(void); +static int getsig(void); +static float getfloat(void); +static int time_sort(proc_t **P, proc_t **Q); +static int pcpu_sort(proc_t **P, proc_t **Q); +static int mem_sort(proc_t **P, proc_t **Q); +static int age_sort(proc_t **P, proc_t **Q); +static void show_fields(void); +static void change_order(void); +static void change_fields(void); +static void show_task_info(proc_t *task); +static void show_procs(void); +static float get_elapsed_time(void); +static void show_meminfo(void); +static void do_stats(proc_t** p, float elapsed_time, int pass); +static void do_key(char c); + + +/* configurable field display support */ + +static int pflags[30]; +static int Numfields; + + + /* Name of the config file (in $HOME) */ +#ifndef RCFILE +#define RCFILE ".toprc" +#endif + +#ifndef SYS_TOPRC +#define SYS_TOPRC "/etc/toprc" +#endif + +#define MAXLINES 2048 +#define MAXNAMELEN 1024 + +/* this is what procps top does by default, so let's do this, if nothing is + * specified + */ +#ifndef DEFAULT_SHOW +/* 0 1 2 3 */ +/* 0123456789012345678901234567890 */ +#define DEFAULT_SHOW "AbcDgHIjklMnoTP|qrsuzyV{EFWX" +#endif +static char Fields[256] = ""; + + +/* This structure stores some critical information from one frame to + the next. mostly used for sorting. Added cumulative and resident fields. */ +struct save_hist { + int ticks; + int pid; + int pcpu; + int utime; + int stime; +}; + + /* The original terminal attributes. */ +static struct termios Savetty; + /* The new terminal attributes. */ +static struct termios Rawtty; + /* Cached termcap entries. */ +static char *cm, *cl, *top_clrtobot, *top_clrtoeol, *ho, *md, *me, *mr; + /* Current window size. Note that it is legal to set Display_procs + larger than can fit; if the window is later resized, all will be ok. + In other words: Display_procs is the specified max number of + processes to display (zero for infinite), and Maxlines is the actual + number. */ +static int Lines, Cols, Maxlines, Display_procs; + /* Maximum length to display of the command line of a process. */ +static unsigned Maxcmd; + + /* Controls how long we sleep between screen updates. Accurate to + microseconds. */ +static float Sleeptime = 5; + /* for opening/closing the system map */ +static int psdbsucc = 0; + /* Mode flags. */ +static int Irixmode = 1; +static int Secure = 0; +static int Cumulative = 0; +static int Noidle = 0; + +static int CPU_states = 0; +static char CurrUser[BUFSIZ]; + +static int CL_pg_shift = (PAGE_SHIFT - 10); +static int CL_wchan_nout = -1; + +static int show_stats = 1; /* show status summary */ +static int show_memory = 1; /* show memory summary */ +static int show_loadav = 1; /* show load average and uptime */ +static int show_cmd = 1; /* show command name instead of commandline */ + +static pid_t monpids[520]; /* randomly chosen value */ +static const int monpids_max = sizeof(monpids)/sizeof(pid_t); +static int monpids_index = 0; + +static int Loops = -1; /* number of iterations. -1 loops forever */ +static int Batch = 0; /* batch mode. Collect no input, dumb output */ + +/* sorting order: cpu%, mem, time (cumulative, if in cumulative mode) */ +enum { + S_PCPU, S_MEM, S_TIME, S_AGE, S_NONE +}; +/* default sorting by CPU% */ +static int sort_type = S_PCPU; + +/* flags for each possible field. At the moment up to 30 are supported */ +enum { + P_PID, P_PPID, P_EUID, P_EUSER, + P_PCPU, P_PMEM, P_TTY, P_PRI, + P_NICE, P_PAGEIN, P_TSIZ, P_DSIZ, + P_SIZE, P_TRS, P_SWAP, P_SHARE, + P_A, P_WP, P_DT, P_RSS, + P_WCHAN, P_STAT, P_TIME, P_COMMAND, + P_LCPU, P_FLAGS, P_END +}; +/* corresponding headers */ +static char *headers[] = +{ + " PID ", " PPID ", " UID ", + "USER ", "%CPU ", "%MEM ", + "TTY ", "PRI ", " NI ", + "PAGEIN ", "TSIZE ", "DSIZE ", + " SIZE ", " TRS ", "SWAP ", + "SHARE ", " A ", " WP ", + " D ", " RSS ", "WCHAN ", + "STAT ", " TIME ", "COMMAND", + "LC ", + " FLAGS " +}; +/* corresponding field desciptions */ +static char *headers2[] = +{ + "Process Id", "Parent Process Id", "User Id", + "User Name", "CPU Usage", "Memory Usage", + "Controlling tty", "Priority", "Nice Value", + "Page Fault Count", "Code Size (kb)", "Data+Stack Size (kb)", + "Virtual Image Size (kb)", "Resident Text Size (kb)", "Swapped kb", + "Shared Pages (kb)", "Accessed Page count", "Write Protected Pages", + "Dirty Pages", "Resident Set Size (kb)", "Sleeping in Function", + "Process Status", "CPU Time", "Command", + "Last used CPU (expect this to change regularly)", + "Task Flags (see linux/sched.h)" +}; + + /* The header printed at the top of the process list.*/ +static char Header[MAXLINES]; + + /* The response to the interactive 'h' command. */ +#define HELP_SCREEN "\ +Interactive commands are:\n\ +\n\ +space\tUpdate display\n\ +^L\tRedraw the screen\n\ +fF\tadd and remove fields\n\ +oO\tChange order of displayed fields\n\ +h or ?\tPrint this list\n\ +S\tToggle cumulative mode\n\ +i\tToggle display of idle proceses\n\ +I\tToggle between Irix and Solaris views (SMP-only)\n\ +c\tToggle display of command name/line\n\ +l\tToggle display of load average\n\ +m\tToggle display of memory information\n\ +t\tToggle display of summary information\n\ +k\tKill a task (with any signal)\n\ +r\tRenice a task\n\ +N\tSort by pid (Numerically)\n\ +A\tSort by age\n\ +P\tSort by CPU usage\n\ +M\tSort by resident memory usage\n\ +T\tSort by time / cumulative time\n\ +u\tShow only a specific user\n\ +n or #\tSet the number of process to show\n\ +s\tSet the delay in seconds between updates\n\ +W\tWrite configuration file ~/.toprc\n\ +q\tQuit" +#define SECURE_HELP_SCREEN "\ +Interactive commands available in secure mode are:\n\ +\n\ +space\tUpdate display\n\ +^L\tRedraw the screen\n\ +fF\tadd and remove fields\n\ +h or ?\tPrint this list\n\ +S\tToggle cumulative mode\n\ +i\tToggle display of idle proceses\n\ +c\tToggle display of command name/line\n\ +l\tToggle display of load average\n\ +m\tToggle display of memory information\n\ +t\tToggle display of summary information\n\ +n or #\tSet the number of process to show\n\ +u\tShow only a specific user\n\ +oO\tChange order of displayed fields\n\ +W\tWrite configuration file ~/.toprc\n\ +q\tQuit" + + /* Number of lines needed to display the header information. */ +static int header_lines; + +/* ############## Some Macro definitions for screen handling ######### */ + /* String to use in error messages. */ +#define PROGNAME "top" + /* Clear the screen. */ +#define clear_screen() \ + printf("%s", cl) + /* Show an error in the context of the spiffy full-screen display. */ +#define SHOWMESSAGE(x) do { \ + printf("%s%s%s%s", tgoto(cm, 0, header_lines-2), top_clrtoeol,md,mr); \ + printf x; \ + printf ("%s",me); \ + fflush(stdout); \ + sleep(2); \ + } while (0) diff --git a/top.c b/top.c index 2faaf0cf..95ad2893 100644 --- a/top.c +++ b/top.c @@ -1,1766 +1,2539 @@ +/* top.c - Source file: show Linux processes */ /* - * top.c - show top CPU processes - * - * Copyright (c) 1992 Branko Lankester - * Copyright (c) 1992 Roger Binns - * Copyright (c) 1997 Michael K. Johnson - * - * Snarfed and HEAVILY modified in december 1992 for procps - * by Michael K. Johnson, johnsonm@sunsite.unc.edu. - * - * Modified Michael K. Johnson's ps to make it a top program. - * Also borrowed elements of Roger Binns kmem based top program. - * Changes made by Robert J. Nation (nation@rocket.sanders.lockheed.com) - * 1/93 - * - * Modified by Michael K. Johnson to be more efficient in cpu use - * 2/21/93 - * - * Changed top line to use uptime for the load average. Also - * added SIGTSTP handling. J. Cowley, 19 Mar 1993. - * - * Modified quite a bit by Michael Shields (mjshield@nyx.cs.du.edu) - * 1994/04/02. Secure mode added. "d" option added. Argument parsing - * improved. Switched order of tick display to user, system, nice, idle, - * because it makes more sense that way. Style regularized (to K&R, - * more or less). Cleaned up much throughout. Added cumulative mode. - * Help screen improved. - * - * Fixed kill buglet brought to my attention by Rob Hooft. - * Problem was mixing of stdio and read()/write(). Added - * getnum() to solve problem. - * 12/30/93 Michael K. Johnson - * - * Added toggling output of idle processes via 'i' key. - * 3/29/94 Gregory K. Nickonov - * - * Fixed buglet where rawmode wasn't getting restored. - * Added defaults for signal to send and nice value to use. - * 5/4/94 Jon Tombs. - * - * Modified 1994/04/25 Michael Shields - * Merged previous changes to 0.8 into 0.95. - * Allowed the use of symbolic names (e.g., "HUP") for signal input. - * Rewrote getnum() into getstr(), getint(), getsig(), etc. - * - * Modified 1995 Helmut Geyer - * added kmem top functionality (configurable fields) - * configurable order of process display - * Added options for dis/enabling uptime, statistics, and memory info. - * fixed minor bugs for ELF systems (e.g. SIZE, RSS fields) - * - * Modified 1996/05/18 Helmut Geyer - * Use of new interface and general cleanup. The code should be far more - * readable than before. - * - * Modified 1996/06/25 Zygo Blaxell - * Added field scaling code for programs that run more than two hours or - * take up more than 100 megs. We have lots of both on our production line. - * - * Modified 1998/02/21 Kirk Bauer - * Added the 'u' option to display only a selected user... plus it will - * take into account that not all 20 top processes are actually shown, - * so it can fit more onto the screen. I think this may help the - * 'don't show idle' mode, but I'm not sure. - * - * Modified 1997/07/27 & 1999/01/27 Tim Janik - * added `-p' option to display specific process ids. - * process sorting is by default disabled in this case. - * added `N' and `A' keys to sort the tasks Numerically by pid or - * sort them by Age (newest first). - * - * Modified 1999/10/22 Tim Janik - * miscellaneous minor fixes, including "usage: ..." output for - * unrecognized options. - * - * Modified 2000/02/07 Jakub Jelinek - * Only load System.map when we are going to display WCHAN. - * Show possible error messages from that load using SHOWMESSAGE. - * - * Modified 2000/07/10 Michael K. Johnson - * Integrated a patch to display SMP information. + * Copyright (c) 2002 - James C. Warner, + * All rights reserved. + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. */ - +#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include #include -#include #include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "proc/sysinfo.h" -#include "proc/procps.h" -#include "proc/whattime.h" -#include "proc/sig.h" -#include "proc/version.h" -#include "proc/readproc.h" -#include "proc/status.h" +#include +#include +#include + /* + I am listing precisely why each header is needed because of the + construction of libproc -- separate header files may not always be + available and function names are not normalized. We have avoided + some library routine(s) as overkill and have subsumed some others. + */ + /* need: 1 define + dev_to_tty */ #include "proc/devname.h" -#include "proc/compare.h" + /* need: (ksym.c) open_psdb_message, wchan, close_psdb (redhat only) */ +#include "proc/procps.h" + /* need: 2 types + openproc, readproc, closeproc */ +#include "proc/readproc.h" +#ifdef UGH_ITS_4_RH + /* need: get_signal2 */ +#include "proc/signals.h" +#else + /* need: signal_name_to_number */ +#include "proc/sig.h" +#endif + /* need: status */ +#include "proc/status.h" + /* need: meminfo stuff */ +#include "proc/sysinfo.h" + /* need: procps_version */ +#include "proc/version.h" + /* need: sprint_uptime */ +#include "proc/whattime.h" -#define PUTP(x) (tputs(x,1,putchar)) -#define BAD_INPUT -30 +#include "top.h" -#include "top.h" /* new header for top specific things */ -static int *cpu_mapping; -static int nr_cpu; +/*###### Miscellaneous global stuff ####################################*/ -/*####################################################################### - *#### Startup routines: parse_options, get_options, ############## - *#### setup_terminal and main ############## - *####################################################################### - */ + /* The original and new terminal attributes */ +static struct termios Savedtty, + Rawtty; +static int Ttychanged = 0; - /* - * parse the options string as read from the config file(s). - * if top is in secure mode, disallow changing of the delay time between - * screen updates. - */ -static void parse_options(char *Options, int secure) + /* Program names used in error messages and 'rc' file names */ +static char *Myname, + *Myrealname; + + /* The Name of the config file(s), dynamically constructed */ +static char RCfile [OURPATHSZ], + RCfile_Sys [SMLBUFSIZ]; + + /* The run-time acquired page size */ +static int Page_size; + + /* SMP and Irix/Solaris mode support */ +static int Cpu_tot, + *Cpu_map; + + /* Specific process id monitoring support */ +static pid_t Monpids [MONPIDMAX] = { 0 }; +static int Monpidsidx = 0; + + /* A postponed error message */ +static char Msg_delayed [SMLBUFSIZ]; +static int Msg_awaiting = 0; + + /* Configurable Display support ##################################*/ + +static unsigned PFlags [PFLAGSSIZ]; +static char CurFields [PFLAGSSIZ], + ColHeadings [SMLBUFSIZ], + ColUsername [USRNAMSIZ]; +static int NumFields; + + /* Current window size. + note: Max_tasks is the specified number of processes to display + (zero for infinite), and Max_lines is the actual number + which, in turn, depends on screen size and summary info. */ +static int Screen_cols, Screen_rows, + Max_lines, Max_tasks; + + /* Number of lines needed to display the summary information + which is never less than 2 since it also must accomodate the + message line + column headings -- set soley by mkheadings! */ +static int HSum_lines; + + /* Maximum length to display of a process' command name/line + (excluding padding due to terminfo strings) */ +static int Max_cmd; + + /* Controls how long we sleep between screen updates. + Accurate to microseconds (when not in 'Batch' mode). */ +static float Delay_time = DEF_DELAY; + + /* Special flags dealing with the kernel symbol table */ +static int No_ksyms = -1, /* set to '0' if ksym avail, '1' otherwise */ + PSDBopen = 0; /* set to '1' if psdb opened (now postponed) */ + + /* The Mode flags. + Except for Batch and Loops, all of them are preserved in the + rc file. Thus, once personalized it stays personalized! + note: the 'letter' shown is the On condition, some default to Off */ +static int Batch = 0, /* batch mode, collect no input, dumb output */ + Loops = -1, /* number of iterations, -1 loops forever */ + Secure_mode = 0, /* set if some functionality restricted */ + Irix_mode = 1, /* 'I' - Irix vs. Solaris view (SMP-only) */ + Sort_normal = 1, /* 'R' - reversed column sort order */ + HSum_loadav = 1, /* 'l' - display load avg and uptime summary */ + HSum_states = 1, /* 't' - display task/cpu(s) states summary */ + HSum_memory = 1, /* 'm' - display memory summary */ + Show_ctimes = 1, /* 'S' - show times as cumulative */ + Show_cmdlin = 0, /* 'c' - show cmdline vs. name */ + Show_idleps = 1, /* 'i' - show idle processes (all processes) */ + Show_hicols = 0, /* 'x' - show sort column highlighted */ + Show_hirows = 1, /* 'y' - show running task(s) highlighted */ + Show_colors = 0, /* 'z' - show in color (vs. mono) */ + Show_hibold = 1, /* 'b' - rows and/or col bold (vs. reverse) */ + Show_cpusum = 1; /* '1' - show combined cpu stats (vs. each) */ + + /* Current current sort type (this too is preserved in the rc file) + * and handler -- goodbye libproc mult_lvl_cmp, etc. overkill. */ +static int Sort_type = -1; +static QSORT_t Sort_func = NULL; + + /* These are our gosh darn 'Fields' ! + They MUST be kept in sync with pflags !! */ +static FTAB_t Fieldstab[] = { +/* head fmts width scale sort desc + ----------- ------- ------ ------ ------ ---------------------- */ + { " PID ", "%5d ", -1, -1, S_PID, "Process Id" }, + { " PPID ", "%5d ", -1, -1, -1, "Parent Process Pid" }, + { " UID ", "%4d ", -1, -1, -1, "User Id" }, + { "USER ", "%-8.8s ", -1, -1, S_USR, "User Name" }, + { "GROUP ", "%-8.8s ", -1, -1, -1, "Group Name" }, + { "TTY ", "%-8.8s ", 8, -1, S_TTY, "Controlling Tty" }, + { " PR ", "%3ld ", -1, -1, -1, "Priority" }, + { " NI ", "%3ld ", -1, -1, -1, "Nice value" }, + { "#C ", "%2d ", -1, -1, -1, "Last used cpu (SMP)" }, + { "%CPU ", "%#4.1f ", -1, -1, S_CPU, "CPU usage" }, + { " TIME ", "%6.6s ", 6, -1, S_TME, "CPU Time" }, + { " TIME+ ", "%9.9s ", 9, -1, S_TME, "CPU Time, hundredths" }, + { "%MEM ", "%#4.1f ", -1, -1, S_MEM, "Memory usage (RES)" }, + { " VIRT ", "%5.5s ", 5, SK_Kb, -1, "Virtual Image (kb)" }, + { "SWAP ", "%4.4s ", 4, SK_Kb, -1, "Swapped size (kb)" }, + { " RES ", "%4.4s ", 4, SK_Kb, S_MEM, "Resident size (kb)" }, + { "CODE ", "%4.4s ", 4, SK_Kb, -1, "Code size (kb)" }, + { "DATA ", "%4.4s ", 4, SK_Kb, -1, "Data+Stack size (kb)" }, + { " SHR ", "%4.4s ", 4, SK_Kb, -1, "Shared Mem size (kb)" }, + { "nFLT ", "%4.4s ", 4, SK_no, -1, "Page Fault count" }, + { "nDRT ", "%4.4s ", 4, SK_no, -1, "Dirty Pages count" }, + { "STA ", "%3.3s ", -1, -1, -1, "Process Status" }, + /** next entry's special: '.head' will be formatted using table entry's own + '.fmts' plus runtime supplied conversion args! */ + { "Command ", "%-*.*s ", -1, -1, S_CMD, "Command line/name" }, + { "WCHAN ", "%-9.9s ", -1, -1, -1, "Sleeping in Function" }, + /** next entry's special: the 0's will be replaced with '.'! */ +#ifdef UPCASE_HEXES + { "Flags ", "%08lX ", -1, -1, -1, "Task Flags " } +#else + { "Flags ", "%08lx ", -1, -1, -1, "Task Flags " } +#endif +}; + + /* Miscellaneous Color stuff #####################################*/ + + /* Our three basic colors -- + they can be manipulated by the 'tweak_colors' routine */ +static int Base_color = BASEcolor, + Msgs_color = MSGScolor, + Head_color = HEADcolor; + + /* Some cap's stuff to reduce runtime calls -- + to accomodate 'Batch' mode, they begin life as empty strings */ +static char Cap_bold [CAPBUFSIZ] = "", + Cap_clr_eol [CAPBUFSIZ] = "", + Cap_clr_eos [CAPBUFSIZ] = "", + Cap_clr_scr [CAPBUFSIZ] = "", + Cap_curs_norm [CAPBUFSIZ] = "", + Cap_curs_huge [CAPBUFSIZ] = "", + Cap_home [CAPBUFSIZ] = "", + Cap_norm [CAPBUFSIZ] = "", + Cap_reverse [CAPBUFSIZ] = "", + Caps_off [CAPBUFSIZ] = ""; +static char Sum_color [CLRBUFSIZ] = "", + Msg_color [CLRBUFSIZ] = "", + Pmt_color [CLRBUFSIZ] = "", + Hdr_color [CLRBUFSIZ] = "", + Row_color_norm [CLRBUFSIZ] = "", + Row_color_high [CLRBUFSIZ] = ""; +static int Cap_can_goto = 0, + Len_row_high = 0, + Len_row_norm = 0; + + /* Development/debug stuff #######################################*/ + +#ifdef TRAK_MAXCAPS +static int Max_pads = 0, Min_pads = MAXINT, + Max_rbuf = 0, Min_rbuf = MAXINT; +#endif + +#ifdef TRAK_MAXBUFS + BUF2INT(fmtmk, buf) + BUF2INT(show_special, tmp) + BUF2INT(ask_str, buf) + BUF2INT(scale_num, buf) + BUF2INT(scale_tics, buf) + BUF2INT(std_err, buf) + BUF2INT(frame_states, tmp) + BUF2INT(mkcol, tmp) + BUF2INT(show_a_task, cbuf) + BUF2INT(show_a_task, rbuf) + BUF2INT(rcfiles_read, fbuf) + BUF2INT(rcfiles_read, RCfile) + BUF2INT(rcfiles_read, RCfile_Sys) + BUF2INT(do_key, ColUsername) + BUF2INT(mkheadings, CurFields) + BUF2INT(mkheadings, ColHeadings) + BUF2INT(main, not_really_tmp) +#endif + + +/*###### Sort callbacks ################################################*/ + +static int sort_cmd (proc_t **P, proc_t **Q) { - int i; - for (i = 0; i < strlen(Options); i++) { - switch (Options[i]) { - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (!secure) - Sleeptime = (float) Options[i] - '0'; - break; - case 'S': - Cumulative = 1; - headers[22][1] = 'C'; - break; - case 's': - Secure = 1; - break; - case 'i': - Noidle = 1; - break; - case 'm': - show_memory = 0; - header_lines -= 2; - break; - case 'M': - sort_type = S_MEM; - reset_sort_options(); - register_sort_function( -1, (cmp_t)mem_sort); - break; - case 'l': - show_loadav = 0; - header_lines -= 1; - break; - case 'P': - sort_type = S_PCPU; - reset_sort_options(); - register_sort_function( -1, (cmp_t)pcpu_sort); - break; - case 'N': - sort_type = S_NONE; - reset_sort_options(); - break; - case 'A': - sort_type = S_AGE; - reset_sort_options(); - register_sort_function( -1, (cmp_t)age_sort); - break; - case 't': - show_stats = 0; - header_lines -= 2; - break; - case 'T': - sort_type = S_TIME; - reset_sort_options(); - register_sort_function( -1, (cmp_t)time_sort); - break; - case 'c': - show_cmd = 0; - break; - case '\n': - break; - case 'I': - Irixmode = 0; - break; - default: - fprintf(stderr, "Wrong configuration option %c\n", i); - exit(1); - break; - } - } + /* if a process doesn't have a cmdline, we'll consider it a kernel thread + -- since show_a_task gives such tasks special treatment, we must too */ + if (Show_cmdlin && ((*P)->cmdline || (*Q)->cmdline)) { + if (!(*P)->cmdline) return SORT_lt; + if (!(*Q)->cmdline) return SORT_gt; + if ( 0 > strncmp((*P)->cmdline[0], (*Q)->cmdline[0], (unsigned)Max_cmd) ) + return SORT_lt; + if ( 0 < strncmp((*P)->cmdline[0], (*Q)->cmdline[0], (unsigned)Max_cmd) ) + return SORT_gt; + } else { + /* this part also handles the compare if both are kernel threads */ + if ( 0 > strcmp((*P)->cmd, (*Q)->cmd) ) return SORT_lt; + if ( 0 < strcmp((*P)->cmd, (*Q)->cmd) ) return SORT_gt; + } + return 0; } -/* - * Read the configuration file(s). There are two files, once SYS_TOPRC - * which should only contain the secure switch and a sleeptime - * value iff ordinary users are to use top in secure mode only. - * - * The other file is $HOME/RCFILE. - * The configuration file should contain two lines (any of which may be - * empty). The first line specifies the fields that are to be displayed - * in the order you want them to. Uppercase letters specify fields - * displayed by default, lowercase letters specify fields not shown by - * default. The order of the letters in this line corresponds to the - * order of the displayed fileds. - * - * all Options but 'q' can be read from this config file - * The delay time option syntax differs from the commandline syntax: - * only integer values between 2 and 9 seconds are recognized - * (this is for standard configuration, so I think this should do). - * - * usually this file is not edited by hand, but written from top using - * the 'W' command. - */ -static void get_options(void) +static int sort_cpu (proc_t **P, proc_t **Q) { - FILE *fp; - char *pt; - char *rcfile = NULL; /* path to rc file... */ - char *home = NULL; /* path of user's home directory... */ - size_t home_length = 0; /* length of path... */ - char Options[256] = ""; - int i; - - nr_cpu = sysconf (_SC_NPROCESSORS_ONLN); - if (nr_cpu < 1) nr_cpu = 1; - cpu_mapping = (int *) xmalloc (sizeof (int) * nr_cpu); - /* read cpuname */ - for (i=0; i< nr_cpu; i++) cpu_mapping[i]=i; - header_lines = 6 + nr_cpu; - fp = fopen(SYS_TOPRC, "r"); - if (fp != NULL) { - fgets(Options, 254, fp); - fclose(fp); - } - parse_options(Options, 0); - strcpy(Options, ""); - - if ( (home = getenv("HOME")) != NULL) { - home_length = strlen(home); - } - - if ( (rcfile = malloc(home_length + strlen(RCFILE) + 2))) { - if (home != NULL) { - strcpy(rcfile, home); - strcat(rcfile, "/"); - } - strcat(rcfile, RCFILE); - fp = fopen(rcfile, "r"); - if (fp == NULL) { - strcpy(Fields, DEFAULT_SHOW); - } else { - if (fgets(Fields, 254, fp) != NULL) { - pt = strchr(Fields, '\n'); - if (pt) *pt = 0; - } - fgets(Options, 254, fp); - fclose(fp); - } - - free(rcfile); - } - parse_options(Options, getuid()? Secure : 0); + if ( (*P)->pcpu < (*Q)->pcpu ) return SORT_lt; + if ( (*P)->pcpu > (*Q)->pcpu ) return SORT_gt; + return 0; } -/* - * Set up the terminal attributes. - */ -static void setup_terminal(void) + +static int sort_mem (proc_t **P, proc_t **Q) { - char *termtype; - struct termios newtty; - if (!Batch) - termtype = getenv("TERM"); - else - termtype = "dumb"; - if (!termtype) { - /* In theory, $TERM should never not be set, but in practice, - some gettys don't. Fortunately, vt100 is nearly always - correct (or pretty close). */ - termtype = "VT100"; - /* fprintf(stderr, PROGNAME ": $TERM not set\n"); */ - /* exit(1); */ - } - - /* - * Get termcap entries and window size. - */ - if(tgetent(NULL, termtype) != 1) { - fprintf(stderr, PROGNAME ": Unknown terminal \"%s\" in $TERM\n", - termtype); - exit(1); - } - - cm = tgetstr("cm", 0); - top_clrtobot = tgetstr("cd", 0); - cl = tgetstr("cl", 0); - top_clrtoeol = tgetstr("ce", 0); - ho = tgetstr("ho", 0); - md = tgetstr("md", 0); - mr = tgetstr("mr", 0); - me = tgetstr("me", 0); - - - if (Batch) return; /* the rest doesn't apply to batch mode */ - if (tcgetattr(0, &Savetty) == -1) { - perror(PROGNAME ": tcgetattr() failed"); - error_end(errno); - } - newtty = Savetty; - newtty.c_lflag &= ~ICANON; - newtty.c_lflag &= ~ECHO; - newtty.c_cc[VMIN] = 1; - newtty.c_cc[VTIME] = 0; - if (tcsetattr(0, TCSAFLUSH, &newtty) == -1) { - printf("cannot put tty into raw mode\n"); - error_end(1); - } - tcgetattr(0, &Rawtty); + if ( (*P)->resident < (*Q)->resident ) return SORT_lt; + if ( (*P)->resident > (*Q)->resident ) return SORT_gt; + /* still equal? we'll try to fix that... */ + if ( (*P)->size < (*Q)->size ) return SORT_lt; + if ( (*P)->size > (*Q)->size ) return SORT_gt; + return 0; } -static int parseint(const char *src, const char *err) + +static int sort_pid (proc_t **P, proc_t **Q) { - char *endp; - int num; - int len; - num = strtol(src, &endp, 0); - if (*endp == '\0') return num; - /* also accept prefixes of: infinite, infinity, maximum, all */ - len = strlen(src); - if(len<1) goto fail; - if(len<9 && !strncmp(src,"infinite",len)) return INT_MAX; - if(len<9 && !strncmp(src,"infinity",len)) return INT_MAX; - if(len<8 && !strncmp(src,"maximum" ,len)) return INT_MAX; - if(len<4 && !strncmp(src,"all" ,len)) return INT_MAX; -fail: - fprintf(stderr, err, src); - exit(1); + if ( (*P)->pid < (*Q)->pid ) return SORT_lt; + if ( (*P)->pid > (*Q)->pid ) return SORT_gt; + return 0; } -static double parseflt(const char *src, const char *err) + +static int sort_tme (proc_t **P, proc_t **Q) { - char *endp; - double num; - int len; - num = strtod(src, &endp); - if (*endp == '\0') return num; - /* also accept prefixes of: infinite, infinity, maximum, all */ - len = strlen(src); - if(len<1) goto fail; - if(len<9 && !strncmp(src,"infinite",len)) return (double)INT_MAX; - if(len<9 && !strncmp(src,"infinity",len)) return (double)INT_MAX; - if(len<8 && !strncmp(src,"maximum" ,len)) return (double)INT_MAX; - if(len<4 && !strncmp(src,"all" ,len)) return (double)INT_MAX; -fail: - fprintf(stderr, err, src); - exit(1); + if (Show_ctimes) { + if ( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) + < ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) + return SORT_lt; + if ( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) + > ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) + return SORT_gt; + } else { + if ( ((*P)->utime + (*P)->stime) < ((*Q)->utime + (*Q)->stime)) + return SORT_lt; + if ( ((*P)->utime + (*P)->stime) > ((*Q)->utime + (*Q)->stime)) + return SORT_gt; + } + return 0; } -int main(int argc, char **argv) + +static int sort_tty (proc_t **P, proc_t **Q) { - /* For select(2). */ - struct timeval tv; - fd_set in; - /* For parsing arguments. */ - char *cp; - /* The key read in. */ - char c; - - struct sigaction sact; - - setlocale(LC_ALL, ""); - get_options(); - - /* set to PCPU sorting */ - register_sort_function( -1, (cmp_t)pcpu_sort); - - /* - * Parse arguments. - */ - (void)argc; - argv++; - while (*argv) { - cp = *argv++; - while (*cp) { - switch (*cp) { - case 'd': - if (cp[1]) { - Sleeptime = parseflt(++cp, PROGNAME ": Bad delay time %s'\n"); - goto breakargv; - } else if (*argv) { /* last char in an argv, use next as arg */ - Sleeptime = parseflt(cp = *argv++, PROGNAME ": Bad delay time %s'\n"); - goto breakargv; - } else { - fprintf(stderr, "-d requires an argument\n"); - exit(1); - } - break; - case 'n': - if (cp[1]) { - Loops = parseint(++cp, PROGNAME ": Bad value %s'\n"); - goto breakargv; - } else if (*argv) { /* last char in an argv, use next as arg */ - Loops = parseint(cp = *argv++, PROGNAME ": Bad value %s'\n"); - goto breakargv; - } - break; - - case 'q': - if (!getuid()) - /* set priority to -10 in order to stay above kswapd */ - if (setpriority(PRIO_PROCESS, getpid(), -10)) { - /* We check this just for paranoia. It's not - fatal, and shouldn't happen. */ - perror(PROGNAME ": setpriority() failed"); - } - Sleeptime = 0; - break; - case 'p': - if (monpids_index >= monpids_max) { - fprintf(stderr, PROGNAME ": More than %u process ids specified\n", - monpids_max); - exit(1); - } - if (cp[1]) { - if (sscanf(++cp, "%d", &monpids[monpids_index]) != 1 || - monpids[monpids_index] < 0 || monpids[monpids_index] > 65535) { - fprintf(stderr, PROGNAME ": Bad process id `%s'\n", cp); - exit(1); - } - } else if (*argv) { /* last char in an argv, use next as arg */ - if (sscanf(cp = *argv++, "%d", &monpids[monpids_index]) != 1 || - monpids[monpids_index] < 0 || monpids[monpids_index] > 65535) { - fprintf(stderr, PROGNAME ": Bad process id `%s'\n", cp); - exit(1); - } - } else { - fprintf(stderr, "-p requires an argument\n"); - exit(1); - } - if (!monpids[monpids_index]) - monpids[monpids_index] = getpid(); - /* default to no sorting when monitoring process ids */ - if (!monpids_index++) { - sort_type = S_NONE; - reset_sort_options(); - } - cp = "_"; - break; - case 'b': - Batch = 1; - break; - case 'c': - show_cmd = !show_cmd; - break; - case 'S': - Cumulative = 1; - break; - case 'i': - Noidle = 1; - break; - case 's': - Secure = 1; - break; - case 'C': - CPU_states = 1; - break; - case '-': - break; /* Just ignore it */ - case 'v': - case 'V': - fprintf(stdout, "top (%s)\n", procps_version); - exit(0); - case 'h': - fprintf(stdout, "usage: " PROGNAME " -hvbcisqS -d delay -p pid -n iterations\n"); - exit(0); - default: - fprintf(stderr, PROGNAME ": Unknown argument `%c'\n", *cp); - fprintf(stdout, "usage: " PROGNAME " -hvbcisqS -d delay -p pid -n iterations\n"); - exit(1); - } - cp++; - } - breakargv: - } - - if (nr_cpu > 1 && CPU_states) - header_lines++; - - meminfo(); /* need kb_main_total value filled in */ - - setup_terminal(); - window_size(0); - /* - * Set up signal handlers. - */ - sact.sa_handler = end; - sact.sa_flags = 0; - sigemptyset(&sact.sa_mask); - sigaction(SIGHUP, &sact, NULL); - sigaction(SIGINT, &sact, NULL); - sigaction(SIGQUIT, &sact, NULL); - sact.sa_handler = stop; - sact.sa_flags = SA_RESTART; - sigaction(SIGTSTP, &sact, NULL); - sact.sa_handler = window_size; - sigaction(SIGWINCH, &sact, NULL); - sigaction(SIGCONT, &sact, NULL); - - /* loop, collecting process info and sleeping */ - while (1) { - if (Loops > 0) - Loops--; - /* display the tasks */ - show_procs(); - /* sleep & wait for keyboard input */ - if (Loops == 0) - end(0); - if (!Batch) - { - tv.tv_sec = Sleeptime; - tv.tv_usec = (Sleeptime - (int) Sleeptime) * 1000000; - FD_ZERO(&in); - FD_SET(0, &in); - if (select(1, &in, 0, 0, &tv) > 0 && read(0, &c, 1) == 1) - do_key(c); - } else { - sleep(Sleeptime); - } - } + if ( (*P)->tty < (*Q)->tty ) return SORT_lt; + if ( (*P)->tty > (*Q)->tty ) return SORT_gt; + /* still equal? we'll try to fix that... */ + return sort_pid(P, Q); } -/*####################################################################### - *#### Signal handled routines: error_end, end, stop, window_size ### - *#### Small utilities: make_header, getstr, getint, getfloat, getsig ### - *####################################################################### - */ - - /* - * end when exiting with an error. - */ -static void error_end(int rno) +static int sort_usr (proc_t **P, proc_t **Q) { - if (!Batch) - tcsetattr(0, TCSAFLUSH, &Savetty); - PUTP(tgoto(cm, 0, Lines - 1)); - fputs("\r\n", stdout); - exit(rno); -} -/* - * Normal end of execution. - */ -static void end(int signo) -{ - (void)signo; - if (!Batch) - tcsetattr(0, TCSAFLUSH, &Savetty); - PUTP(tgoto(cm, 0, Lines - 1)); - fputs("\r\n", stdout); - exit(0); + if ( 0 > strcmp((*P)->euser, (*Q)->euser) ) return SORT_lt; + if ( 0 < strcmp((*P)->euser, (*Q)->euser) ) return SORT_gt; + /* still equal? we'll try to fix that... */ + return sort_pid(P, Q); } -/* - * SIGTSTP catcher. - */ -static void stop(int signo) -{ - (void)signo; - /* Reset terminal. */ - if (!Batch) - tcsetattr(0, TCSAFLUSH, &Savetty); - PUTP(tgoto(cm, 0, Lines - 3)); - fflush(stdout); - raise(SIGSTOP); - /* Later... */ - if (!Batch) - tcsetattr (0, TCSAFLUSH, &Rawtty); -} - -/* - * Reads the window size and clear the window. This is called on setup, - * and also catches SIGWINCHs, and adjusts Maxlines. Basically, this is - * the central place for window size stuff. - */ -static void window_size(int signo) -{ - struct winsize ws; - (void)signo; - if((ioctl(1, TIOCGWINSZ, &ws) != -1) && (ws.ws_col>73) && (ws.ws_row>7)){ - Cols = ws.ws_col; - Lines = ws.ws_row; - }else{ - Cols = tgetnum("co"); - Lines = tgetnum("li"); - } - if (!Batch) - clear_screen(); - /* - * calculate header size, length of cmdline field ... - */ - Numfields = make_header(); -} - -/* - * this prints a possible message from open_psdb_message - */ -static void top_message(const char *format, ...) { - va_list arg; - int n; - char buffer[512]; - - va_start (arg, format); - n = vsnprintf (buffer, 512, format, arg); - va_end (arg); - if (n > -1 && n < 512) - SHOWMESSAGE(("%s", buffer)); -} - -/* - * this adjusts the lines needed for the header to the current value - */ -static int make_header(void) -{ - int i, j; - - j = 0; - for (i = 0; i < strlen(Fields); i++) { - if (Fields[i] < 'a') { - pflags[j++] = Fields[i] - 'A'; - if (Fields[i] == 'U' && CL_wchan_nout == -1) { - CL_wchan_nout = 0; - /* for correct handling of WCHAN fields, we have to do distingu - * between kernel versions */ - /* get kernel symbol table, if needed */ - if (open_psdb_message(NULL, top_message)) { - CL_wchan_nout = 1; - } else { - psdbsucc = 1; - } - } - } - } - strcpy(Header, ""); - for (i = 0; i < j; i++) - strcat(Header, headers[pflags[i]]); - /* readjust window size ... */ - Maxcmd = Cols - strlen(Header) + 7; - Maxlines = Display_procs ? Display_procs : Lines - header_lines; - if (Maxlines > Lines - header_lines) - Maxlines = Lines - header_lines; - return (j); -} - - - -/* - * Get a string from the user; the base of getint(), et al. This really - * ought to handle long input lines and errors better. NB: The pointer - * returned is a statically allocated buffer, so don't expect it to - * persist between calls. - */ -static char *getstr(void) -{ - static char line[BUFSIZ]; /* BUFSIZ from ; arbitrary */ - int i = 0; - - /* Must make sure that buffered IO doesn't kill us. */ - fflush(stdout); - fflush(stdin); /* Not POSIX but ok */ - - do { - read(STDIN_FILENO, &line[i], 1); - } while (line[i++] != '\n' && i < sizeof(line)); - line[--i] = 0; - - return (line); -} - - -/* - * Get an integer from the user. Display an error message and - * return BAD_INPUT if it's invalid; else return the number. - */ -static int getint(void) -{ - char *line; - int i; - int r; - - line = getstr(); - - for (i = 0; line[i]; i++) { - if (!isdigit(line[i]) && line[i] != '-') { - SHOWMESSAGE(("That's not a number!")); - return (BAD_INPUT); - } - } - - /* An empty line is a legal error (hah!). */ - if (!line[0]) - return (BAD_INPUT); - - sscanf(line, "%d", &r); - return (r); -} - - -/* - * Get a float from the user. Just like getint(). - */ -static float getfloat(void) -{ - char *line; - int i; - float r; - char *savelocale; - - line = getstr(); - - for (i = 0; line[i]; i++) { - if (!isdigit(line[i]) && line[i] != '.' && line[i] != '-') { - SHOWMESSAGE(("That's not a float!")); - return (BAD_INPUT); - } - } - - /* An empty line is a legal error (hah!). */ - if (!line[0]) - return (BAD_INPUT); - - savelocale = setlocale(LC_NUMERIC, NULL); - setlocale(LC_NUMERIC, "C"); - sscanf(line, "%f", &r); - setlocale(LC_NUMERIC, savelocale); - return (r); -} - - -/* - * Get a signal number or name from the user. Return the number, or -1 - * on error. - */ -static int getsig(void) -{ - char *line; - - /* This is easy. */ - line = getstr(); - return signal_name_to_number(line); -} - -/*####################################################################### - *#### Routine for sorting on used time, resident memory and %CPU ##### - *#### It would be easy to include full sorting capability as in ##### - *#### ps, but I think there is no real use for something that ##### - *#### complicated. Using register_sort_function or parse_sort_opt ##### - *#### you just have to do the natural thing and it will work. ##### - *####################################################################### - */ - -static int time_sort (proc_t **P, proc_t **Q) -{ - if (Cumulative) { - if( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) < - ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) - return -1; - if( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) > - ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) - return 1; - } else { - if( ((*P)->utime + (*P)->stime) < ((*Q)->utime + (*Q)->stime)) - return -1; - if( ((*P)->utime + (*P)->stime) > ((*Q)->utime + (*Q)->stime)) - return 1; - } - return 0; -} - -static int pcpu_sort (proc_t **P, proc_t **Q) -{ - if( (*P)->pcpu < (*Q)->pcpu ) return -1; - if( (*P)->pcpu > (*Q)->pcpu ) return 1; - return 0; -} - -static int mem_sort (proc_t **P, proc_t **Q) -{ - if( (*P)->vm_rss < (*Q)->vm_rss ) return -1; - if( (*P)->vm_rss > (*Q)->vm_rss ) return 1; - return 0; -} - -int age_sort (proc_t **P, proc_t **Q) -{ - if( (*P)->start_time < (*Q)->start_time ) return -1; - if( (*P)->start_time > (*Q)->start_time ) return 1; - return 0; -} - -/*####################################################################### - *#### Routines handling the field selection/ordering screens: ######## - *#### show_fields, change_order, change_fields ######## - *####################################################################### - */ + +/*###### Tiny useful routine(s) ########################################*/ /* - * Display the specification line of all fields. Upper case indicates - * a displayed field, display order is according to the order of the - * letters. A short description of each field is shown as well. - * The description of a displayed field is marked by a leading - * asterisk (*). - */ -static void show_fields(void) + * This routine isolates ALL user input and ensures that we + * wont be mixing I/O from stdio and low-level read() requests */ +static int chin (int ech, char *buf, unsigned cnt) { - int i, row, col; - char *p; + int rc; - clear_screen(); - PUTP(tgoto(cm, 3, 0)); - printf("Current Field Order: %s\n", Fields); - for (i = 0; i < sizeof headers / sizeof headers[0]; ++i) { - row = i % (Lines - 3) + 3; - col = i / (Lines - 3) * 40; - PUTP(tgoto(cm, col, row)); - for (p = headers[i]; *p == ' '; ++p); - printf("%c %c: %-10s = %s", (strchr(Fields, i + 'A') != NULL) ? '*' : ' ', i + 'A', - p, headers2[i]); - } -} - -/* - * change order of displayed fields - */ -static void change_order(void) -{ - char c, ch, *p; - int i; - - show_fields(); - for (;;) { - PUTP(tgoto(cm, 0, 0)); - PUTP(top_clrtoeol); - PUTP(tgoto(cm, 3, 0)); - PUTP(mr); - printf("Current Field Order: %s", Fields); - PUTP(me); - putchar('\n'); - PUTP(tgoto(cm, 0, 1)); - printf("Upper case characters move a field to the left, lower case to the right"); - fflush(stdout); - if (!Batch) { /* should always be true, but... */ - tcsetattr(0, TCSAFLUSH, &Rawtty); - read(0, &c, 1); - tcsetattr(0, TCSAFLUSH, &Savetty); - } - i = toupper(c) - 'A'; - if ((p = strchr(Fields, i + 'A')) != NULL) { - if (isupper(c)) - p--; - if ((p[1] != '\0') && (p >= Fields)) { - ch = p[0]; - p[0] = p[1]; - p[1] = ch; - } - } else if ((p = strchr(Fields, i + 'a')) != NULL) { - if (isupper(c)) - p--; - if ((p[1] != '\0') && (p >= Fields)) { - ch = p[0]; - p[0] = p[1]; - p[1] = ch; - } - } else { - break; - } - } - Numfields = make_header(); -} -/* - * toggle displayed fields - */ -static void change_fields(void) -{ - int i, changed = 0; - int row, col; - char c, *p; - char tmp[2] = " "; - - show_fields(); - for (;;) { - PUTP(tgoto(cm, 0, 0)); - PUTP(top_clrtoeol); - PUTP(tgoto(cm, 3, 0)); - PUTP(mr); - printf("Current Field Order: %s", Fields); - PUTP(me); - putchar('\n'); - PUTP(tgoto(cm, 0, 1)); - if (!Batch) { /* should always be true, but... */ - printf("Toggle fields with a-x, any other key to return: "); - fflush(stdout); - tcsetattr(0, TCSAFLUSH, &Rawtty); - read(0, &c, 1); - tcsetattr(0, TCSAFLUSH, &Savetty); - } - i = toupper(c) - 'A'; - if (i >= 0 && i < sizeof headers / sizeof headers[0]) { - row = i % (Lines - 3) + 3; - col = i / (Lines - 3) * 40; - PUTP(tgoto(cm, col, row)); - if ((p = strchr(Fields, i + 'A')) != NULL) { /* deselect Field */ - *p = i + 'a'; - putchar(' '); - } else if ((p = strchr(Fields, i + 'a')) != NULL) { /* select previously */ - *p = i + 'A'; /* deselected field */ - putchar('*'); - } else { /* select new field */ - tmp[0] = i + 'A'; - strcat(Fields, tmp); - putchar('*'); - } - changed = 1; - fflush(stdout); - } else - break; - } - if (changed) - Numfields = make_header(); -} - -/* Do the scaling stuff: interprets time in seconds, formats it to - * fit width, and returns pointer to static char*. - */ -static char *scale_time(int t,int width) -{ - static char buf[100]; - - /* Try successively higher units until it fits */ - - sprintf(buf,"%d:%02d",t/60,t%60); /* minutes:seconds */ - if (strlen(buf)<=width) - return buf; - - t/=60; /* minutes */ - sprintf(buf,"%dm",t); - if (strlen(buf)<=width) - return buf; - - t/=60; /* hours */ - sprintf(buf,"%dh",t); - if (strlen(buf)<=width) - return buf; - - t/=24; /* days */ - sprintf(buf,"%dd",t); - if (strlen(buf)<=width) - return buf; - - t/=7; /* weeks */ - sprintf(buf,"%dw",t); - return buf; /* this is our last try; - if it still doesn't fit, too bad. */ - - /* FIXME: if someone has a 16-way SMP running over a year... */ -} - -/* scale_k(k,width,unit) - interprets k as a count, formats to fit width. - if unit is 0, k is a byte count; 1 is a kilobyte - count; 2 for megabytes; 3 for gigabytes. -*/ - -static char *scale_k(int k,int width,int unit) -{ - /* kilobytes, megabytes, gigabytes, too-big-for-int-bytes */ - static double scale[]={1024,1024*1024,1024*1024*1024,0}; - /* kilo, mega, giga, tera */ - static char unitletters[]={'K','M','G','T',0}; - static char buf[100]; - char *up; - double *dp; - - /* Try successively higher units until it fits */ - - sprintf(buf,"%d",k); - if (strlen(buf)<=width) - return buf; - - for (up=unitletters+unit,dp=scale ; *dp ; ++dp,++up) { - sprintf(buf,"%.1f%c",k / *dp,*up); - if (strlen(buf)<=width) - return buf; - sprintf(buf,"%d%c",(int)(k / *dp),*up); - if (strlen(buf)<=width) - return buf; - } - - /* Give up; give them what we got on our shortest attempt */ - return buf; -} - -/* - *####################################################################### - *#### Routines handling the main top screen: ######## - *#### show_task_info, show_procs, show_memory, do_stats ######## - *####################################################################### - */ - /* - * Displays infos for a single task - */ -static void show_task_info(proc_t *task) -{ - int i,j; - unsigned int t; - char *cmdptr; - char tmp[2048], tmp2[2048] = "", tmp3[2048] = "", *p; - - for (i = 0; i < Numfields; i++) { - tmp[0] = 0; - switch (pflags[i]) { - case P_PID: - sprintf(tmp, "%5d ", task->pid); - break; - case P_PPID: - sprintf(tmp, "%5d ", task->ppid); - break; - case P_EUID: - sprintf(tmp, "%4d ", task->euid); - break; - case P_EUSER: - sprintf(tmp, "%-8.8s ", task->euser); - break; - case P_PCPU: - sprintf(tmp, "%4.1f ", (float)task->pcpu / 10); - break; - case P_LCPU: - sprintf(tmp, "%2d ", task->processor); - break; - case P_PMEM: { - unsigned pmem; - pmem = task->vm_rss * 1000ULL / kb_main_total; - if (pmem > 999) pmem = 999; - sprintf(tmp, "%2u.%u ", pmem/10U, pmem%10U); - } - break; - case P_TTY: { - char outbuf[9]; - dev_to_tty(outbuf, 8, task->tty, task->pid, ABBREV_DEV); - sprintf(tmp, "%-8.8s ", outbuf); - } - break; - case P_PRI: - sprintf(tmp, "%3.3s ", scale_k(task->priority, 3, 0)); - break; - case P_NICE: - sprintf(tmp, "%3.3s ", scale_k(task->nice, 3, 0)); - break; - case P_PAGEIN: - sprintf(tmp, "%6.6s ", scale_k(task->maj_flt, 6, 0)); - break; - case P_TSIZ: - sprintf(tmp, "%5.5s ", - scale_k(((task->end_code - task->start_code) / 1024), 5, 1)); - break; - case P_DSIZ: - sprintf(tmp, "%5.5s ", - scale_k(((task->vsize - task->end_code) / 1024), 5, 1)); - break; - case P_SIZE: - sprintf(tmp, "%5.5s ", scale_k((task->size << CL_pg_shift), 5, 1)); - break; - case P_TRS: - sprintf(tmp, "%4.4s ", scale_k((task->trs << CL_pg_shift), 4, 1)); - break; - case P_SWAP: - sprintf(tmp, "%4.4s ", - scale_k(((task->size - task->resident) << CL_pg_shift), 4, 1)); - break; - case P_SHARE: - sprintf(tmp, "%5.5s ", scale_k((task->share << CL_pg_shift), 5, 1)); - break; - case P_A: - sprintf(tmp, "%3.3s ", "NYI"); - break; - case P_WP: - sprintf(tmp, "%3.3s ", "NYI"); - break; - case P_DT: - sprintf(tmp, "%3.3s ", scale_k(task->dt, 3, 0)); - break; - case P_RSS: /* rss, not resident (which includes IO memory) */ - sprintf(tmp, "%4.4s ", - scale_k((task->rss << CL_pg_shift), 4, 1)); - break; - case P_WCHAN: - if (!CL_wchan_nout) - sprintf(tmp, "%-9.9s ", wchan(task->wchan)); - else - sprintf(tmp, "%-9lx", task->wchan); - break; - case P_STAT: - sprintf(tmp, "%-4.4s ", status(task)); - break; - case P_TIME: - t = (task->utime + task->stime) / Hertz; - if (Cumulative) - t += (task->cutime + task->cstime) / Hertz; - sprintf(tmp, "%6.6s ", scale_time(t,6)); - break; - case P_COMMAND: - if (!show_cmd && task->cmdline && *(task->cmdline)) { - j=0; - while(((task->cmdline)[j] != NULL) && (strlen(tmp3)<1020)){ -/* #if 0 */ /* This is useless? FIXME */ - if (j > 0) - strcat(tmp3, " "); -/* #endif */ - strncat(tmp3, (task->cmdline)[j], 1000); - j++; - } - cmdptr = tmp3; - } else { - cmdptr = task->cmd; - } - if (strlen(cmdptr) > Maxcmd) - cmdptr[Maxcmd - 1] = 0; - sprintf(tmp, "%s", cmdptr); - tmp3[0]=0; - break; - case P_FLAGS: - sprintf(tmp, "%8lx ", task->flags); - break; - } - strcat(tmp2, tmp); - } - if (strlen(tmp2) > Cols - 1) - tmp2[Cols - 1] = 0; - - /* take care of cases like: - perl -e 'foo - bar foo bar - foo - # end of perl script' - */ - for (p=tmp2;*p;++p) - if (!isgraph(*p)) - *p=' '; - - printf("\n%s", tmp2); - PUTP(top_clrtoeol); -} - -/* - * This is the real program! Read process info and display it. - * One could differentiate options of readproctable2, perhaps it - * would be useful to support the PROC_UID and PROC_TTY - * as command line options. - */ -static void show_procs(void) -{ - static proc_t **p_table=NULL; - static int proc_flags; - int count; - int ActualLines; - float elapsed_time; - static int first=0; - - if (first==0) { - proc_flags=PROC_FILLMEM|PROC_FILLCMD|PROC_FILLUSR|PROC_FILLSTATUS|PROC_FILLSTAT; - if (monpids_index) - proc_flags |= PROC_PID; - p_table=readproctab2(proc_flags, p_table, monpids); - elapsed_time = get_elapsed_time(); - do_stats(p_table, elapsed_time, 0); - sleep(1); - first=1; - } - if (first && Batch) - fputs("\n\n",stdout); - /* Display the load averages. */ - PUTP(ho); - PUTP(md); - if (show_loadav) { - printf("%s", sprint_uptime()); - PUTP(top_clrtoeol); - putchar('\n'); - } - p_table=readproctab2(proc_flags, p_table, monpids); - /* Immediately find out the elapsed time for the frame. */ - elapsed_time = get_elapsed_time(); - /* Display the system stats, calculate percent CPU time - * and sort the list. */ - do_stats(p_table, elapsed_time,1); - /* Display the memory and swap space usage. */ - show_meminfo(); - if (strlen(Header) + 2 > Cols) - Header[Cols - 2] = 0; - PUTP(mr); - fputs(Header, stdout); - PUTP(top_clrtoeol); - PUTP(me); - - /* - * Finally! Loop through to find the top task, and display it. - * Lather, rinse, repeat. - */ - count = 0; - ActualLines = 0; - while ((ActualLines < Maxlines) && (p_table[count]->pid!=-1)) { - char Stat; - - Stat = p_table[count]->state; - - if ( (!Noidle || (Stat != 'S' && Stat != 'Z')) && - ( (CurrUser[0] == '\0') || - (!strcmp((char *)CurrUser,p_table[count]->euser) ) ) ) { - - /* - * Show task info. - */ - show_task_info(p_table[count]); - if (!Batch) - ActualLines++; - } - count++; - } - PUTP(top_clrtobot); - PUTP(tgoto(cm, 0, header_lines - 2)); - fflush(stdout); + fflush(stdout); + if (!ech) + rc = read(STDIN_FILENO, buf, cnt); + else { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty); + rc = read(STDIN_FILENO, buf, cnt); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty); + } + /* may be the beginning of a lengthy escape sequence */ + tcflush(STDIN_FILENO, TCIFLUSH); + return rc; /* note: we do NOT produce a vaid 'string' */ } -/* - * Finds the current time (in microseconds) and calculates the time - * elapsed since the last update. This is essential for computing - * percent CPU usage. - */ -static float get_elapsed_time(void) + /* + * This routine simply formats whatever the caller wants and + * returns a pointer to the resulting 'const char' string... */ +static const char *fmtmk (const char *fmts, ...) { - struct timeval t; - static struct timeval oldtime; + static char buf[BIGBUFSIZ]; /* with help stuff, our buffer */ + va_list va; /* requirements exceed 1k */ + + va_start(va, fmts); + vsprintf(buf, fmts, va); + va_end(va); +#ifdef TRAK_MAXBUFS + MAXBUFS(fmtmk, buf); +#endif + return (const char *)buf; +} + + + /* + * What need be said... */ +static char *strim (int sp, char *str) +{ + const char *ws = "\b\f\n\r\t\v"; + char *p; + + /* this guy was originally designed just to trim the rc file lines and + any 'open_psdb_message' result which arrived with an inappropriate + newline (thanks to 'sysmap_mmap') -- but when tabs (^I) were found + in some proc cmdlines, a choice was offered twix space or null... */ + if (sp) + while ((p = strpbrk(str, ws))) *p = ' '; + else + if ((p = strpbrk(str, ws))) *p = 0; + return str; +} + + + /* + * This guy just facilitates Batch and protects against dumb ttys. */ +static char *tg2 (int x, int y) +{ + return Cap_can_goto ? tgoto(cursor_address, x, y) : (char *)"\n"; +} + + +/*###### Misc Color/Highlighting support ###############################*/ + + /* + * Make the appropriate caps/color strings and set some + * lengths which are used to distinguish twix the displayed + * columns and an actual printf row! + * note: we avoid the use of background color so as to maximize + * compatibility with the user's xterm settings */ +static void capsmk (void) +{ + /* macro to test if a basic (non-color) capability is valid + thanks: Floyd Davidson */ +#define tIF(s) s ? s : "" + static int capsdone = 0; + + /* just a precaution for the future, we aren't called now... */ + if (Batch) return; + + /* these are the unchangeable puppies, so we only do 'em once */ + if (!capsdone) { + strcpy(Cap_bold, tIF(enter_bold_mode)); + strcpy(Cap_clr_eol, tIF(clr_eol)); + strcpy(Cap_clr_eos, tIF(clr_eos)); + strcpy(Cap_clr_scr, tIF(clear_screen)); + strcpy(Cap_curs_huge, tIF(cursor_visible)); + strcpy(Cap_curs_norm, tIF(cursor_normal)); + strcpy(Cap_home, tIF(cursor_home)); + strcpy(Cap_norm, tIF(exit_attribute_mode)); + strcpy(Cap_reverse, tIF(enter_reverse_mode)); + sprintf(Caps_off, "%s%s", Cap_norm, tIF(orig_pair)); + if (tgoto(cursor_address, 1, 1)) Cap_can_goto = 1; + capsdone = 1; + } + + /* now do the changeable guys... */ + if (Show_colors && max_colors > 0) { + strcpy(Sum_color, tparm(set_a_foreground, Base_color)); + sprintf(Msg_color, "%s%s" + , tparm(set_a_foreground, Msgs_color), Cap_reverse); + sprintf(Pmt_color, "%s%s" + , tparm(set_a_foreground, Msgs_color), Cap_bold); + sprintf(Hdr_color, "%s%s" + , tparm(set_a_foreground, Head_color), Cap_reverse); + sprintf(Row_color_norm, "%s%s" + , Caps_off, tparm(set_a_foreground, Base_color)); + } else { + Sum_color[0] = '\0'; + strcpy(Msg_color, Cap_reverse); + strcpy(Pmt_color, Cap_bold); + strcpy(Hdr_color, Cap_reverse); + strcpy(Row_color_norm, Cap_norm); + } + + sprintf(Row_color_high, "%s%s" + , Row_color_norm, Show_hibold ? Cap_bold : Cap_reverse); + Len_row_norm = strlen(Row_color_norm); + Len_row_high = strlen(Row_color_high); + +#undef tIF +} + + + /* + * Show an error, but not right now. + * Due to the postponed opening of ksym, using open_psdb_message, + * if P_WCHAN had been selected and the program is restarted, the + * message would otherwise be displayed prematurely */ +static void msg_save (const char *fmts, ...) +{ + char tmp[SMLBUFSIZ]; + va_list va; + + va_start(va, fmts); + vsprintf(tmp, fmts, va); + va_end(va); + /* we'll add some extra attention grabbers to whatever this is */ + sprintf(Msg_delayed, "\a*** %s ***", strim(0, tmp)); + Msg_awaiting = 1; +} + + + /* + * Show an error message (caller may include a '\a' for sound) */ +static void show_msg (const char *str) +{ + printf("%s%s %s %s%s" + , tg2(0, MSG_line) + , Msg_color + , str + , Caps_off + , Cap_clr_eol); + fflush(stdout); + sleep(MSG_SLEEP); + Msg_awaiting = 0; +} + + + /* + * Show an input prompt + larger cursor */ +static void show_pmt (const char *str) +{ + printf("%s%s%s: %s%s" + , tg2(0, MSG_line) + , Pmt_color + , str + , Cap_curs_huge + , Caps_off); + fflush(stdout); +} + + + /* + * Show lines with specially formatted elements, but only output + * what will fit within the current screen width. + * Our special formatting consists of: + * "some text <_delimiter_> some more text <_delimiter_>...\n" + * Where <_delimiter_> is a single byte in the range of: + * \01 through \07 (more properly \0001 - \0007) + * and is used to select an 'attribute' from a capabilities table + * which is then applied to the *preceding* substring. + * + * Once recognized, the delimiter is replaced with a null character + * and viola, we've got a substring ready to output! Strings or + * substrings without delimiters will receive the Cap_norm attribute. + * + * Caution: + * This routine treats all non-delimiter bytes as displayable + * data subject to our screen width marching orders. If callers + * embed non-display data like tabs or terminfo strings in our + * glob, a line will truncate incorrectly at best. Worse case + * would be truncation of an embedded tty escape sequence. + * + * Tabs must always be avoided or our efforts are wasted and + * lines will wrap. To lessen but not eliminate the risk of + * terminfo string truncation, such non-display stuff should + * be placed at the beginning of a "short" line. */ +static void show_special (const char *glob) +{ + static char *captab[] = { /* Cap's/Delim's */ + Cap_norm, Cap_norm, Cap_bold, /* \00, \01, \02 */ + Sum_color, /* \03 */ + Msg_color, Pmt_color, /* \04, \05 */ + Hdr_color, /* \06 */ + Row_color_high }; /* \07 */ + char tmp[BIGBUFSIZ], *cap, *lin_end, *sub_beg, *sub_end; + int room; + + /* handle multiple lines passed in a bunch */ + while ((lin_end = strchr(glob, '\n'))) { + + /* create a local copy we can extend and otherwise abuse */ + memcpy(tmp, glob, (unsigned)(lin_end - glob)); +#ifdef TRAK_MAXBUFS + MAXBUFS(show_special, tmp); +#endif + /* zero terminate this part and prepare to parse substrings */ + tmp[lin_end - glob] = '\0'; + room = Screen_cols; + sub_beg = sub_end = tmp; + + while (*sub_beg) { + switch (*sub_end) { + case '\00' : /* no end delim, captab makes normal */ + *(sub_end + 1) = '\0'; /* extend str end, then fall through */ + case '\01' ... '\07' : + cap = captab[(int)*sub_end]; + *sub_end = '\0'; + printf("%s%.*s%s", cap, room, sub_beg, Caps_off); + room -= (sub_end - sub_beg); + sub_beg = ++sub_end; + break; + default : /* nothin' special, just text */ + ++sub_end; + } + + if (0 >= room) break; /* skip substrings that won't fit */ + } /* end: while 'subtrings' */ + + printf("%s\n", Cap_clr_eol); /* emulate truncated newline */ + glob = ++lin_end; /* point to next line (maybe) */ + + } /* end: while 'lines' */ + + /* if there's anything left in the glob (by virtue of no trailing '\n'), + it probably means caller wants to retain cursor position on this final + line -- ok then, we'll just do our 'fit-to-screen' thingy... */ + if (strlen(glob)) printf("%.*s", Screen_cols, glob); + fflush(stdout); +} + + + /* + * Change colors used in display */ +static void tweak_colors (void) +{ +#define kbdABORT 'q' +#define kbdAPPLY '\n' + int clrssav = Show_colors, boldsav = Show_hibold, + basesav = Base_color, msgssav = Msgs_color, headsav = Head_color; + int clr = Base_color, *pclr = &Base_color; + char ch, tgt = 'B'; + + if (0 >= max_colors) { + show_msg("\aNo colors to map!"); + return; + } + if (!Show_colors) { + Show_colors = 1; + capsmk(); + } + printf("%s%s", Cap_clr_scr, Cap_curs_huge); + + do { + /* this string is well above ISO C89's minimum requirements! */ + show_special(fmtmk(COLOR_sample + , Cap_home, Myname, procps_version, tgt, clr)); + chin(0, &ch, 1); + switch (ch) { + case 'B' : + pclr = &Base_color; + clr = *pclr; + tgt = ch; + break; + case 'M' : + pclr = &Msgs_color; + clr = *pclr; + tgt = ch; + break; + case 'H' : + pclr = &Head_color; + clr = *pclr; + tgt = ch; + break; + case '0' ... '7' : + clr = ch - '0'; + break; + case 'b' : + Show_hibold = !Show_hibold; + break; + case 'z' : + Show_colors = !Show_colors; + break; + } + *pclr = clr; + capsmk(); + } while (kbdAPPLY != ch && kbdABORT != ch); + + if (kbdABORT == ch) { + Show_colors = clrssav; Show_hibold = boldsav; + Base_color = basesav; Msgs_color = msgssav; Head_color = headsav; + capsmk(); + } + putp(Cap_curs_norm); + +#undef kbdABORT +#undef kbdAPPLY +} + + +/*###### Small utility routines ########################################*/ + + /* + * Get a string from the user */ +static char *ask_str (const char *prompt) +{ + static char buf[GETBUFSIZ]; + + show_pmt(prompt); + memset(buf, '\0', sizeof(buf)); + chin(1, buf, sizeof(buf) - 1); + putp(Cap_curs_norm); + +#ifdef TRAK_MAXBUFS + MAXBUFS(ask_str, buf); +#endif + return strim(0, buf); +} + + + /* + * Get a float from the user */ +static float get_float (const char *prompt) +{ + char *line; + float f; + + if (!(*(line = ask_str(prompt)))) return -1; + /* note: we're not allowing negative floats */ + if (strcspn(line, ".1234567890")) { + show_msg("\aNot valid"); + return -1; + } + sscanf(line, "%f", &f); + return f; +} + + + /* + * Get an integer from the user */ +static int get_int (const char *prompt) +{ + char *line; + int n; + + if (!(*(line = ask_str(prompt)))) return -1; + /* note: we've got to allow negative ints (renice) */ + if (strcspn(line, "-1234567890")) { + show_msg("\aNot valid"); + return -1; + } + sscanf(line, "%d", &n); + return n; +} + + + /* + * Set the number of fields/columns to display. + * Create the field/column headings and set maximum cmdline length. + * Establish the heading/summary lines currently in use. + * Adjust the number of tasks to display. */ +static void mkheadings (void) +{ + const char *h; + int i, needpsdb = 0; + + /* build our PFlags array and establish a tentative NumFields */ + for (i = 0, NumFields = 0; i < (int)strlen(CurFields); i++) { + if (isupper(CurFields[i])) + PFlags[NumFields++] = CurFields[i] - 'A'; + } + + /* build a preliminary ColHeadings not to exceed screen width */ + ColHeadings[0] = '\0'; + for (i = 0; i < NumFields; i++) { + h = Fieldstab[PFlags[i]].head; + /* oops, won't fit -- we're outta here... */ + if (Screen_cols < (int)(strlen(ColHeadings) + strlen(h))) break; + strcat(ColHeadings, h); + } + + /* establish the final NumFields and prepare to grow the command + column heading via Max_cmd -- it may be a fib if P_CMD wasn't + encountered, but that's ok because it won't be displayed anyway */ + NumFields = i; + Max_cmd = Screen_cols + - (strlen(ColHeadings) - strlen(Fieldstab[P_CMD].head)) - 1; + + /* now we can build the true run-time ColHeadings and format the + command column heading if P_CMD is really being displayed */ + ColHeadings[0] = '\0'; + for (i = 0; i < NumFields; i++) { + /* are we gonna' need the kernel symbol table? */ + if (P_WCHAN == PFlags[i]) needpsdb = 1; + h = Fieldstab[PFlags[i]].head; + if (P_CMD == PFlags[i]) + strcat(ColHeadings, fmtmk(Fieldstab[P_CMD].fmts, Max_cmd, Max_cmd, h)); + else + strcat(ColHeadings, h); + } + + /* set the number of heading lines and calc display height */ + HSum_lines = SUMMINLINS; + if (HSum_loadav) HSum_lines += 1; + if (HSum_states) { + if (Show_cpusum) HSum_lines += 2; + /* no tellin' how darn many cpus they might have -- if they exceed + screen height, they'll have to suffer scroll... + (Max_lines may go negative, which is as good as 0) */ + else HSum_lines += Cpu_tot + 1; + } + if (HSum_memory) HSum_lines += 2; + + Max_lines = Screen_rows - HSum_lines; + if (Max_tasks && Max_tasks < Max_lines) + Max_lines = Max_tasks; + + /* do we (still) need the kernel symbol table? */ + if (needpsdb) { + if (-1 == No_ksyms) { + No_ksyms = 0; + if (open_psdb_message(NULL, msg_save)) + /* why so counter-intuitive, couldn't open_psdb_message + mirror sysmap_mmap -- that func does all the work anyway? */ + No_ksyms = 1; + else + PSDBopen = 1; + } + } +#ifdef UGH_ITS_4_RH + else if (PSDBopen) { + close_psdb(); + PSDBopen = 0; + No_ksyms = -1; + } +#endif + +#ifdef TRAK_MAXBUFS + MAXBUFS(mkheadings, CurFields); + MAXBUFS(mkheadings, ColHeadings); +#endif +} + + + /* + * Do some scaling stuff. + * We'll interpret 'num' as one of the following types and + * try to format it to fit 'width'. + * SK_no (0) it's a byte count + * SK_Kb (1) it's kilobytes + * SK_Mb (2) it's megabytes + * SK_Gb (3) it's gigabytes */ +static char *scale_num (unsigned num, const unsigned width, const unsigned type) +{ + /* kilobytes, megabytes, gigabytes, too-big-for-int-bytes */ + static double scale[] = { 1024, 1024*1024, 1024*1024*1024, 0 }; + /* kilo, mega, giga, none */ +#ifdef UPCASE_SCALE + static char nextup[] = { 'K', 'M', 'G', 0 }; +#else + static char nextup[] = { 'k', 'm', 'g', 0 }; +#endif + static char buf[TNYBUFSIZ]; + double *dp; + char *up; + + /* try an unscaled version first... */ + sprintf(buf, "%d", num); +#ifdef TRAK_MAXBUFS + MAXBUFS(scale_num, buf); +#endif + if (strlen(buf) <= width) + return buf; + + /* now try successively higher types until it fits */ + for (up = nextup + type, dp = scale; *dp; ++dp, ++up) { + /* the most accurate version */ + sprintf(buf, "%.1f%c", num / *dp, *up); + if (strlen(buf) <= width) + return buf; + /* the integer version */ + sprintf(buf, "%d%c", (int)(num / *dp), *up); + if (strlen(buf) <= width) + return buf; + } + /* well shoot, this outta' fit... */ + return (char *)"?"; +} + + + /* + * Do some scaling stuff. + * Format 'tics' to fit 'width' */ +static char *scale_tics (TICS_t tics, const unsigned width) +{ + static struct { + unsigned div; + const char *fmt; + } ttab[] = { + /* minutes hours days weeks */ +#ifdef UPCASE_SCALE + { 60, "%uM" }, { 60, "%uH" }, { 24, "%uD" }, { 7, "%uW" } +#else + { 60, "%um" }, { 60, "%uh" }, { 24, "%ud" }, { 7, "%uw" } +#endif + }; + static char buf[TNYBUFSIZ]; + unsigned i, t; + + /* try successively higher units until it fits */ + t = tics / Hertz; + sprintf(buf, "%d:%02d.%02d" /* minutes:seconds.tenths */ + , t/60, t%60, (int)((tics*100)/Hertz)%100); +#ifdef TRAK_MAXBUFS + MAXBUFS(scale_tics, buf); +#endif + + if (strlen(buf) <= width) + return buf; + sprintf(buf, "%d:%02d", t/60, t%60); /* minutes:seconds */ + if (strlen(buf) <= width) + return buf; + + /* try successively: minutes; hours; days; weeks */ + for (i = 0; i < MAXtbl(ttab); i++) { + t /= ttab[i].div; + sprintf(buf, ttab[i].fmt, t); + if (strlen(buf) <= width) + return buf; + }; + /* well shoot, this outta' fit... */ + return (char *)"?"; +} + + + /* + * Calculate and return the elapsed time since the last update + * which is then used in % CPU calc's. */ +static float time_elapsed (void) +{ + static struct timeval oldtimev; + struct timeval timev; struct timezone timez; - float elapsed_time; + float et; - gettimeofday(&t, &timez); - elapsed_time = (t.tv_sec - oldtime.tv_sec) - + (float) (t.tv_usec - oldtime.tv_usec) / 1000000.0; - oldtime.tv_sec = t.tv_sec; - oldtime.tv_usec = t.tv_usec; - return (elapsed_time); + gettimeofday(&timev, &timez); + et = (timev.tv_sec - oldtimev.tv_sec) + + (float)(timev.tv_usec - oldtimev.tv_usec) / 1000000.0; + oldtimev.tv_sec = timev.tv_sec; + oldtimev.tv_usec = timev.tv_usec; + return et; } -/* - * Reads the memory info and displays it. Returns the total memory - * available, for use in percent memory usage calculations. - */ -static void show_meminfo(void) + +/*###### Exit and Signal handled routines ##############################*/ + + /* + * The usual program end -- + * called only by functions in this section. */ +static void bye_bye (int eno, const char *str) { - meminfo(); /* read+parse /proc/meminfo */ - if (show_memory) { - printf( - "Mem: %8dK total, %8dK used, %8dK free, %8dK buffers", - kb_main_total, - kb_main_used, - kb_main_free, - kb_main_buffers - ); - PUTP(top_clrtoeol); - putchar('\n'); - printf( - "Swap: %8dK total, %8dK used, %8dK free, %8dK cached", - kb_swap_total, - kb_swap_used, - kb_swap_free, - kb_main_cached - ); - PUTP(top_clrtoeol); - putchar('\n'); - } - PUTP(me); - PUTP(top_clrtoeol); - putchar('\n'); +#ifdef UGH_ITS_4_RH + if (PSDBopen) + close_psdb(); +#endif + if (!Batch) + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty); + printf("%s%s\n", tg2(0, Screen_rows), Cap_curs_norm); + + if (str) { + if (eno) perror(str); + else fputs(str, stderr); + } +#ifdef TRAK_MAXCAPS + fprintf(stderr, + "\nTRAK_MAXCAPS report:" + "\n\tBasic lengths:" + "\n\t Cap_home = %d, Cap_bold = %d, Cap_norm = %d, Cap_reverse = %d" + "\n\t Cap_clr_eol = %d, Cap_clr_eos = %d, Cap_clr_scr = %d" + "\n\t Cap_curs_norm = %d, Cap_curs_huge = %d" + "\n\t Caps_off = %d" + "\n\tColor lengths:" + "\n\t Sum_color = %d, Msg_color = %d, Pmt_color = %d, Hdr_color = %d" + "\n\t Row_color_norm = %d, Len_row_norm = '%d'" + "\n\t Row_color_high = %d, Len_row_high = '%d'" + "\n\tMax_pads = %d, Min_pads = %d" + "\n\tMax_rbuf = %d, Min_rbuf = %d" + "\n" + , strlen(Cap_home), strlen(Cap_bold), strlen(Cap_norm), strlen(Cap_reverse) + , strlen(Cap_clr_eol), strlen(Cap_clr_eos), strlen(Cap_clr_scr) + , strlen(Cap_curs_norm), strlen(Cap_curs_huge) + , strlen(Caps_off) + , strlen(Sum_color), strlen(Msg_color), strlen(Pmt_color), strlen(Hdr_color) + , strlen(Row_color_norm), Len_row_norm + , strlen(Row_color_high), Len_row_high + , Max_pads, Min_pads + , Max_rbuf, Min_rbuf); +#endif +#ifdef TRAK_MAXBUFS + fprintf(stderr, + "\nTRAK_MAXBUFS report:" + "\n\tused size\tfunction, buffer" + "\n\t---- ----\t-----------------------" + ARPTBUF(fmtmk, buf) + ARPTBUF(show_special, tmp) + ARPTBUF(ask_str, buf) + ARPTBUF(scale_num, buf) + ARPTBUF(scale_tics, buf) + ARPTBUF(std_err, buf) + ARPTBUF(frame_states, tmp) + ARPTBUF(mkcol, tmp) + ARPTBUF(show_a_task, cbuf) + ARPTBUF(show_a_task, rbuf) + ARPTBUF(rcfiles_read, fbuf) + ARPTBUF(rcfiles_read, RCfile) + ARPTBUF(rcfiles_read, RCfile_Sys) + ARPTBUF(do_key, ColUsername) + ARPTBUF(mkheadings, CurFields) + ARPTBUF(mkheadings, ColHeadings) + ARPTBUF(main, not_really_tmp) + "\n" + , AUSEBUF(fmtmk, buf), ASIZBUF(fmtmk, buf) + , AUSEBUF(show_special, tmp), ASIZBUF(show_special, tmp) + , AUSEBUF(ask_str, buf), ASIZBUF(ask_str, buf) + , AUSEBUF(scale_num, buf), ASIZBUF(scale_num, buf) + , AUSEBUF(scale_tics, buf), ASIZBUF(scale_tics, buf) + , AUSEBUF(std_err, buf), ASIZBUF(std_err, buf) + , AUSEBUF(frame_states, tmp), ASIZBUF(frame_states, tmp) + , AUSEBUF(mkcol, tmp), ASIZBUF(mkcol, tmp) + , AUSEBUF(show_a_task, cbuf), ASIZBUF(show_a_task, cbuf) + , AUSEBUF(show_a_task, rbuf), ASIZBUF(show_a_task, rbuf) + , AUSEBUF(rcfiles_read, fbuf), ASIZBUF(rcfiles_read, fbuf) + , AUSEBUF(rcfiles_read, RCfile) , ASIZBUF(rcfiles_read, RCfile) + , AUSEBUF(rcfiles_read, RCfile_Sys) , ASIZBUF(rcfiles_read, RCfile_Sys) + , AUSEBUF(do_key, ColUsername), ASIZBUF(do_key, ColUsername) + , AUSEBUF(mkheadings, CurFields), ASIZBUF(mkheadings, CurFields) + , AUSEBUF(mkheadings, ColHeadings), ASIZBUF(mkheadings, ColHeadings) + , AUSEBUF(main, not_really_tmp), ASIZBUF(main, not_really_tmp)); +#endif + +#if defined(TRAK_MAXCAPS) || defined(TRAK_MAXBUFS) + fprintf(stderr, + "\nbye_bye's Summary report:" + "\n\tprogram names:" + "\n\t Myrealname = %s, Myname = %s" + "\n\tterminal = '%s'" + "\n\t device = %s, ncurses = v%s" + "\n\t max_colors = %d, max_pairs = %d" + "\n\t Cap_can_goto = %s" + "\n\tScreen_cols = %d, Screen_rows = %d" + "\n\tNumFields = %d, HSum_lines = %d" + "\n\tMax_lines = %d, Max_cmd = %d, Max_tasks = %d" + "\n\tPage_size = %d" + "\n" + , Myrealname, Myname +#ifdef PRETENDNOCAP + , "dumb" +#else + , termname() +#endif + , ttyname(STDOUT_FILENO), NCURSES_VERSION + , max_colors, max_pairs + , Cap_can_goto ? "yes" : "No!" + , Screen_cols, Screen_rows + , NumFields, HSum_lines + , Max_lines, Max_cmd, Max_tasks + , Page_size); +#endif + + if (str && !eno) eno = 1; + exit(eno); } -/* - * Calculates the number of tasks in each state (running, sleeping, etc.). - * Calculates the CPU time in each state (system, user, nice, etc). - * Calculates percent cpu usage for each task. - */ -static void do_stats(proc_t** p, float elapsed_time, int pass) + /* + * Standard error handler to normalize the look of all err o/p */ +static void std_err (const char *str) { - proc_t *this; - int arrindex, total_time, cpumap, i, n = 0; - int sleeping = 0, stopped = 0, zombie = 0, running = 0; - double system_ticks, user_ticks, nice_ticks, idle_ticks; - static int prev_count = 0; - int systime, usrtime; - - /* start with one page as a reasonable allocate size */ - static int save_history_size = - sizeof(long)*1024 / sizeof(struct save_hist); - static struct save_hist *save_history; - struct save_hist *New_save_hist; - - if (!save_history) - save_history = xcalloc(NULL, sizeof(struct save_hist)*save_history_size); - New_save_hist = xcalloc(NULL, sizeof(struct save_hist)*save_history_size); + static char buf[SMLBUFSIZ]; - /* - * Make a pass through the data to get stats. - */ - arrindex = 0; - while (p[n]->pid != -1) { - this = p[n]; - switch (this->state) { - case 'S': - case 'D': - sleeping++; - break; - case 'T': - stopped++; - break; - case 'Z': - zombie++; - break; - case 'R': - running++; - break; - default: - /* Don't know how to handle this one. */ - break; - } - - /* - * Calculate time in this process. Time is sum of user time - * (usrtime) plus system time (systime). - */ - total_time = this->utime + this->stime; - if (arrindex >= save_history_size) { - save_history_size *= 2; - save_history = xrealloc(save_history, sizeof(struct save_hist)*save_history_size); - New_save_hist = xrealloc(New_save_hist, sizeof(struct save_hist)*save_history_size); - } - New_save_hist[arrindex].ticks = total_time; - New_save_hist[arrindex].pid = this->pid; - systime = this->stime; - usrtime = this->utime; - New_save_hist[arrindex].stime = systime; - New_save_hist[arrindex].utime = usrtime; - - /* find matching entry from previous pass */ - for (i = 0; i < prev_count; i++) { - if (save_history[i].pid == this->pid) { - total_time -= save_history[i].ticks; - systime -= save_history[i].stime; - usrtime -= save_history[i].utime; - - i = prev_count; - } - } - - /* - * Calculate percent cpu time for this task. - */ - this->pcpu = (total_time * 10 * 100/Hertz) / elapsed_time; - if (this->pcpu > 999) - this->pcpu = 999; - - arrindex++; - n++; - } - - /* - * Display stats. - */ - if (pass > 0 && show_stats) { - printf("%d processes: %d sleeping, %d running, %d zombie, " - "%d stopped", - n, sleeping, running, zombie, stopped); - PUTP(top_clrtoeol); - putchar('\n'); - four_cpu_numbers(&user_ticks,&nice_ticks,&system_ticks,&idle_ticks); - printf("CPU states:" - " %# 5.1f%% user, %# 5.1f%% system," - " %# 5.1f%% nice, %# 5.1f%% idle", - user_ticks, - system_ticks, - nice_ticks, - idle_ticks - ); - PUTP(top_clrtoeol); - putchar('\n'); - } - /* - * Save this frame's information. - */ - for (i = 0; i < n; i++) { - /* copy the relevant info for the next pass */ - save_history[i].pid = New_save_hist[i].pid; - save_history[i].ticks = New_save_hist[i].ticks; - save_history[i].stime = New_save_hist[i].stime; - save_history[i].utime = New_save_hist[i].utime; - } - free(New_save_hist); - - prev_count = n; - qsort(p, n, sizeof(proc_t*), (void*)mult_lvl_cmp); + fflush(stdout); + /* we'll use our own buffer so callers can still use fmtmk() */ + sprintf(buf, "\t%s: %s\n", Myname, str); +#ifdef TRAK_MAXBUFS + MAXBUFS(std_err, buf); +#endif + if (!Ttychanged) { + fprintf(stderr, buf); + exit(1); + } + /* not to worry, he'll change our exit code to 1 due to 'buf' */ + bye_bye(0, buf); } -/* - * Process keyboard input during the main loop - */ -static void do_key(char c) + /* + * Normal end of execution. + * catches: + * SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT and SIGTERM */ +static void stop (int dont_care_sig) { - int numinput, i; - char rcfile[MAXNAMELEN]; - FILE *fp; + bye_bye(0, NULL); +} - /* - * First the commands which don't require a terminal mode switch. - */ - if (c == 'q') - end(0); - else if (c == ' ') - return; - else if (c == 12) { - clear_screen(); - return; - } else if (c == 'I') { - Irixmode=(Irixmode) ? 0 : 1; - return; - } - /* - * Switch the terminal to normal mode. (Will the original - * attributes always be normal? Does it matter? I suppose the - * shell will be set up the way the user wants it.) - */ - if (!Batch) tcsetattr(0, TCSANOW, &Savetty); + /* + * Suspend ourself. + * catches: + * SIGTSTP, SIGTTIN and SIGTTOU */ +static void suspend (int dont_care_sig) +{ + /* reset terminal */ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty); + printf("%s%s", tg2(0, Screen_rows), Cap_curs_norm); + fflush(stdout); + raise(SIGSTOP); + /* later, after SIGCONT... */ + if (!Batch) + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty); +} - /* - * Handle the rest of the commands. - */ - switch (c) { - case '?': - case 'h': - PUTP(cl); PUTP(ho); putchar('\n'); PUTP(mr); - printf("Proc-Top Revision 1.2"); - PUTP(me); putchar('\n'); - printf("Secure mode "); - PUTP(md); - fputs(Secure ? "on" : "off", stdout); - PUTP(me); - fputs("; cumulative mode ", stdout); - PUTP(md); - fputs(Cumulative ? "on" : "off", stdout); - PUTP(me); - fputs("; noidle mode ", stdout); - PUTP(md); - fputs(Noidle ? "on" : "off", stdout); - PUTP(me); - fputs("\n\n", stdout); - printf("%s\n\nPress any key to continue", Secure ? SECURE_HELP_SCREEN : HELP_SCREEN); - if (!Batch) tcsetattr(0, TCSANOW, &Rawtty); - (void) getchar(); - break; - case 'i': - Noidle = !Noidle; - SHOWMESSAGE(("No-idle mode %s", Noidle ? "on" : "off")); - break; - case 'u': - SHOWMESSAGE(("Which User (Blank for All): ")); - strcpy(CurrUser,getstr()); - break; - case 'k': - if (Secure) - SHOWMESSAGE(("\aCan't kill in secure mode")); - else { - int pid, signo; - PUTP(md); - SHOWMESSAGE(("PID to kill: ")); - pid = getint(); - if (pid == BAD_INPUT) - break; - PUTP(top_clrtoeol); - SHOWMESSAGE(("Kill process %d with what signal? [15] ", pid)); - PUTP(me); - signo = getsig(); - /* FIXME: -1 may mean an unknown signal */ - if (signo == -1) - signo = SIGTERM; - if (kill(pid, signo)) - SHOWMESSAGE(("\aKill of PID %d with %d failed: %s", - pid, signo, strerror(errno))); - } - break; - case 'l': - SHOWMESSAGE(("Display load average %s", !show_loadav ? "on" : "off")); - if (show_loadav) { - show_loadav = 0; - header_lines--; - } else { - show_loadav = 1; - header_lines++; - } - Numfields = make_header(); - break; - case 'm': - SHOWMESSAGE(("Display memory information %s", !show_memory ? "on" : "off")); - if (show_memory) { - show_memory = 0; - header_lines -= 2; - } else { - show_memory = 1; - header_lines += 2; - } - Numfields = make_header(); - break; - case 'M': - SHOWMESSAGE(("Sort by memory usage")); - sort_type = S_MEM; - reset_sort_options(); - register_sort_function(-1, (cmp_t)mem_sort); - break; - case 'n': - case '#': - printf("Processes to display (0 for unlimited): "); - numinput = getint(); - if (numinput != -1) { - Display_procs = numinput; - window_size(0); - } - break; - case 'r': - if (Secure) - SHOWMESSAGE(("\aCan't renice in secure mode")); - else { - int pid, val; - printf("PID to renice: "); - pid = getint(); - if (pid == BAD_INPUT) - break; - PUTP(tgoto(cm, 0, header_lines - 2)); - PUTP(top_clrtoeol); - printf("Renice PID %d to value: ", pid); - val = getint(); - if (val == BAD_INPUT) - val = 10; - if (setpriority(PRIO_PROCESS, pid, val)) - SHOWMESSAGE(("\aRenice of PID %d to %d failed: %s", - pid, val, strerror(errno))); - } - break; - case 'P': - SHOWMESSAGE(("Sort by CPU usage")); - sort_type = S_PCPU; - reset_sort_options(); - register_sort_function(-1, (cmp_t)pcpu_sort); - break; - case 'A': - SHOWMESSAGE(("Sort by age")); - sort_type = S_AGE; - reset_sort_options(); - register_sort_function(-1, (cmp_t)age_sort); - break; - case 'N': - SHOWMESSAGE(("Sort numerically by pid")); - sort_type = S_NONE; - reset_sort_options(); - break; - case 'c': - show_cmd = !show_cmd; - SHOWMESSAGE(("Show %s", show_cmd ? "command names" : "command line")); - break; - case 'S': - Cumulative = !Cumulative; - SHOWMESSAGE(("Cumulative mode %s", Cumulative ? "on" : "off")); - if (Cumulative) - headers[22][1] = 'C'; - else - headers[22][1] = ' '; - Numfields = make_header(); - break; - case 's': - if (Secure) - SHOWMESSAGE(("\aCan't change delay in secure mode")); - else { - double tmp; - printf("Delay between updates: "); - tmp = getfloat(); - if (!(tmp < 0)) - Sleeptime = tmp; - } - break; - case 't': - SHOWMESSAGE(("Display summary information %s", !show_stats ? "on" : "off")); - if (show_stats) { - show_stats = 0; - header_lines -= 2; - } else { - show_stats = 1; - header_lines += 2; - } - Numfields = make_header(); - break; - case 'T': - SHOWMESSAGE(("Sort by %stime", Cumulative ? "cumulative " : "")); - sort_type = S_TIME; - reset_sort_options(); - register_sort_function( -1, (cmp_t)time_sort); - break; + /* + * Set the screen dimensions and call the real workhorse. + * catches: + * SIGWINCH and SIGCONT */ +static void window_resize (int dont_care_sig) +{ + struct winsize w; + + Screen_cols = columns; + Screen_rows = lines; + if (-1 != (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w))) { + Screen_cols = w.ws_col; + Screen_rows = w.ws_row; + } + /* he'll calculate header size, length of command field, etc ... */ + mkheadings(); +} + + +/*###### Startup routines ##############################################*/ + + /* + * Parse command line arguments + * (and force ol' main into a much needed diet) + * Note: it's assumed that the rc file(s) have already been read + * and our job is to see if any of those options are to be + * overridden -- we'll force some on and negate others in our + * best effort to honor the user's wishes... */ +static void parse_argvs (char **argv) +{ + /* difference(s) from traditional top: + -q (zero delay time) eliminated as redundant, use -d0 + -p (pid monitoring) allows comma delimited list + no deprecated/illegal use of 'goto' with 'breakargv:' + bunched args are actually handled properly and none are ignored + (we tolerate No whitespace and No switches -- maybe too tolerant) */ + const char *usage = " -hv | -bcisS -d delay -n iterations -p pid [,pid ...]\n"; + float tmp_delay = MAXFLOAT; + char *p; + + ++argv; + while (*argv) { + char *cp = *(argv++); + + while (*cp) { + switch (*cp) { + case '\0': + case '-': + break; + case 'b': + Batch = 1; + break; + case 'c': + Show_cmdlin = !Show_cmdlin; + break; + case 'd': + if (cp[1]) ++cp; + else if (*argv) cp = *argv++; + else std_err("-d requires argument"); + /* a negative delay will be dealt with shortly... */ + if (1 != sscanf(cp, "%f", &tmp_delay)) + std_err(fmtmk("bad delay '%s'", cp)); + break; + case 'h': case 'H': + case 'v': case 'V': + std_err(fmtmk("\t%s\nusage:\t%s%s" + , procps_version, Myname, usage)); + case 'i': + Show_idleps = !Show_idleps; + break; + case 'n': + if (cp[1]) cp++; + else if (*argv) cp = *argv++; + else std_err("-n requires argument"); + if (1 != sscanf(cp, "%d", &Loops) || 0 > Loops) + std_err(fmtmk("bad iterations arg '%s'", cp)); + break; + case 'p': + if (cp[1]) cp++; + else if (*argv) cp = *argv++; + else std_err("-p requires argument"); + do { + if (Monpidsidx >= MONPIDMAX) + std_err(fmtmk("pid limit (%d) exceeded", MONPIDMAX)); + if (1 != sscanf(cp, "%d", &Monpids[Monpidsidx]) + || 0 > Monpids[Monpidsidx]) + std_err(fmtmk("bad pid '%s'", cp)); + if (!Monpids[Monpidsidx]) + Monpids[Monpidsidx] = getpid(); + Monpidsidx++; + if (!(p = strchr(cp, ','))) + break; + cp = ++p; + } while (*cp); + break; + case 's': + Secure_mode = 1; + break; + case 'S': + Show_ctimes = !Show_ctimes; + break; + default : + std_err(fmtmk("unknown argument '%c'\nusage:\t%s%s" + , *cp, Myname, usage)); + + } /* end: switch */ + /* advance cp and jump over any numerical args used above */ + cp += strspn(++cp, "-.1234567890 "); + + } /* end: while *cp */ + } /* end: while *argv */ + + /* fixup delay time, maybe... */ + if (MAXFLOAT != tmp_delay) { + if (Secure_mode || 0 > tmp_delay) + msg_save("Delay time Not changed"); + else + Delay_time = tmp_delay; + } + + /* set the default sort type, maybe... */ + if (-1 == Sort_type || Monpidsidx) { + Sort_type = S_PID; + Sort_func = (QSORT_t)sort_pid; + } +} + + + /* + * Parse the options string as read from line two of the "local" + * configuration file. */ +static void parse_rc (char *opts) +{ + unsigned i; + + for (i = 0; i < strlen(opts); i++) { + switch (opts[i]) { + case 'b': + Show_hibold = 1; + break; + case 'c': + Show_cmdlin = 1; + break; + case 'C': + Sort_type = S_CMD; + Sort_func = (QSORT_t)sort_cmd; + break; + case 'E': + Sort_type = S_USR; + Sort_func = (QSORT_t)sort_usr; + break; + case 'i': + Show_idleps = 1; + break; + case 'I': + Irix_mode = 1; + break; + case 'l': + HSum_loadav = 1; + break; + case 'm': + HSum_memory = 1; + break; + case 'M': + Sort_type = S_MEM; + Sort_func = (QSORT_t)sort_mem; + break; + case 'P': + Sort_type = S_PID; + Sort_func = (QSORT_t)sort_pid; + break; + case 'R': + Sort_normal = 1; + break; + case 'S': + Show_ctimes = 1; + break; + case 't': + HSum_states = 1; + break; + case 'T': + Sort_type = S_TME; + Sort_func = (QSORT_t)sort_tme; + break; + case 'U': + Sort_type = S_CPU; + Sort_func = (QSORT_t)sort_cpu; + break; + case 'x': + Show_hicols = 1; + break; + case 'y': + Show_hirows = 1; + break; + case 'Y': + Sort_type = S_TTY; + Sort_func = (QSORT_t)sort_tty; + break; + case 'z': + Show_colors = 1; + break; + case '1': + Show_cpusum = 1; + break; + case ' ' : /* these serve as rc file comments */ + case '\t': /* and will be treated as 'eol' */ + case '\n': + return; + default : + std_err(fmtmk("bad config option - '%c'", opts[i])); + } + } +} + + + /* + * Build the two RC file names then try to read 'em. + * + * '/etc/RCfile_Sys' contains two lines consisting of the secure mode + * switch and an update interval. It's presence limits what ordinary + * users are allowed to do. + * + * '$HOME/RCfile' contains five lines. + * line 1: specifies the fields that are to be displayed + * and their order. Uppercase letters == displayed, + * lowercase letters == not shown. + * line 2: specifies miscellaneous display options and + * toggles along with the prefered sort order. + * line 3: specifies maximum processes, 0 == unlimited. + * line 4: specifies the update interval. If run in secure + * mode, this value will be ignored except for root. + * line 5: specifies the 3 basic color values. */ +static void rcfiles_read (void) +{ + char fbuf[RCFBUFSIZ]; + FILE *fp; + + strcpy(RCfile_Sys, fmtmk("/etc/%src", Myrealname)); +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, RCfile_Sys); +#endif + if (getenv("HOME")) + strcpy(RCfile, fmtmk("%s%c", getenv("HOME"), '/')); + strcat(RCfile, fmtmk(".%src", Myname)); +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, RCfile); +#endif + + fp = fopen(RCfile_Sys, "r"); + if (fp) { + fbuf[0] = '\0'; + fgets(fbuf, sizeof(fbuf), fp); /* sys rc file, line #1 */ +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, fbuf); +#endif + if (strchr(fbuf, 's')) Secure_mode = 1; + + fbuf[0] = '\0'; + fgets(fbuf, sizeof(fbuf), fp); /* sys rc file, line #2 */ +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, fbuf); +#endif + fclose(fp); + sscanf(fbuf, "%f", &Delay_time); + } + + fp = fopen(RCfile, "r"); + if (fp) { + fbuf[0] = '\0'; + if (fgets(fbuf, sizeof(fbuf), fp)) { /* rc file, line #1 */ +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, fbuf); +#endif + strcpy(CurFields, strim(0, fbuf)); + /* Now that we've found an rc file, we'll honor those + preferences by first turning off everything... */ + Irix_mode = 0; + HSum_states = HSum_memory = HSum_loadav = Show_cpusum = 0; + Show_cmdlin = Show_ctimes = Show_idleps = Sort_normal = 0; + Show_colors = Show_hicols = Show_hirows = Show_hibold = 0; + + fbuf[0] = '\0'; + fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #2 */ +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, fbuf); +#endif + /* we could subsume this next guy since we're the only caller + -- but we're both too fat already... */ + parse_rc(strim(0, fbuf)); + + fbuf[0] = '\0'; + fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #3 */ +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, fbuf); +#endif + sscanf(fbuf, "%d", &Max_tasks); + + fbuf[0] = '\0'; + fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #4 */ +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, fbuf); +#endif + if (!Secure_mode || !getuid()) + sscanf(fbuf, "%f", &Delay_time); + + fbuf[0] = '\0'; + fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #5 */ +#ifdef TRAK_MAXBUFS + MAXBUFS(rcfiles_read, fbuf); +#endif + sscanf(fbuf, "%d,%d,%d", &Base_color, &Msgs_color, &Head_color); + } + fclose(fp); + } + /* protect against meddling leading to a possible fault -- + shorter would kinda' work, longer ain't healthy for us! */ + if (strlen(CurFields) != strlen(DEF_FIELDS)) + strcpy(CurFields, DEF_FIELDS); + + /* lastly, establish the true runtime secure mode */ + Secure_mode = getuid() ? Secure_mode : 0; +} + + + /* + * Set up the terminal attributes */ +static void terminal_set (void) +{ + struct termios newtty; + + /* first the ncurses part... */ +#ifdef PRETENDNOCAP + setupterm((char *)"dumb", STDOUT_FILENO, NULL); +#else + setupterm(NULL, STDOUT_FILENO, NULL); +#endif + /* now our part... */ + if (!Batch) { + if (-1 == tcgetattr(STDIN_FILENO, &Savedtty)) + std_err("tcgetattr() failed"); + capsmk(); + newtty = Savedtty; + newtty.c_lflag &= ~ICANON; + newtty.c_lflag &= ~ECHO; + newtty.c_cc[VMIN] = 1; + newtty.c_cc[VTIME] = 0; + + Ttychanged = 1; + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtty)) { + putp(Cap_clr_scr); + std_err(fmtmk("Failed tty set: %s", strerror(errno))); + } + tcgetattr(STDIN_FILENO, &Rawtty); + putp(Cap_clr_scr); + fflush(stdout); + } +} + + +/*###### Field Selection/Ordering routines #############################*/ + + /* + * Display the current fields and their order. + * Upper case indicates a displayed field, display order is + * according to the order of the letters. + * + * A short description of each field is shown as well and is + * marked by a leading asterisk (*) if currently displayed. + * + * After all fields have been displayed, some extra explanatory + * text is then output */ +static void display_fields (void) +{ + const char *p, *x; + int i, cmax = Screen_cols / 2, rmax = Screen_rows - 3; + + /* we're relying on our callers to first clear the screen -- + thus 'fields_toggle' can avoid screen flicker since he's + too lazy to handle his own asterisk (*) logic */ + putp(Cap_bold); + for (i = 0; i < MAXtbl(Fieldstab); ++i) { + /* advance past any leading spaces */ + for (p = Fieldstab[i].head; ' ' == *p; ++p) + ; + printf("%s%c %c: %-10s = %s" + , tg2((i / rmax) * cmax, (i % rmax) + 3) + , strchr(CurFields, i + 'A') ? '*' : ' ' + , i + 'A' + , p + , Fieldstab[i].desc); + } + putp(Row_color_norm); + x = FIELDS_xtra; + while ((p = strchr(x, '\n'))) { + ++i; + printf("%s%.*s" + , tg2((i / rmax) * cmax, (i % rmax) + 3) + , p - x, x); + x = ++p; + } + putp(Caps_off); +} + + + /* + * Change order of displayed fields. */ +static void fields_reorder (void) +{ + static char prompt[] = + "Upper case characters move field left, lower case right"; + char c, *p; + int i; + + printf("%s%s", Cap_clr_scr, Cap_curs_huge); + display_fields(); + do { + show_special(fmtmk(FIELDS_current + , Cap_home, Myname, CurFields, prompt)); + chin(0, &c, 1); + i = toupper(c) - 'A'; + if (i < 0 || i >= MAXtbl(Fieldstab)) + break; + if (((p = strchr(CurFields, i + 'A'))) + || ((p = strchr(CurFields, i + 'a')))) { + if (isupper(c)) p--; + if (('\0' != p[1]) && (p >= CurFields)) { + c = p[0]; + p[0] = p[1]; + p[1] = c; + } + } + } while (1); + putp(Cap_curs_norm); + mkheadings(); +} + + + /* + * Toggle displayed fields. */ +static void fields_toggle (void) +{ + static char prompt[] = + "Toggle fields with a-x, type any other key to return"; + char c, *p; + int i; + + printf("%s%s", Cap_clr_scr, Cap_curs_huge); + do { + display_fields(); + show_special(fmtmk(FIELDS_current + , Cap_home, Myname, CurFields, prompt)); + chin(0, &c, 1); + i = toupper(c) - 'A'; + if (i < 0 || i >= MAXtbl(Fieldstab)) + break; + if ((p = strchr(CurFields, i + 'A'))) + *p = i + 'a'; + else if ((p = strchr(CurFields, i + 'a'))) + *p = i + 'A'; + } while (1); + putp(Cap_curs_norm); + mkheadings(); +} + + +/*###### Library Alternatives ##########################################*/ + + /* + * Handle our own memory stuff without the risk of leaving the + * user's terminal in an ugly state should things go sour. */ +static const char *alloc_msg = "Failed memory allocate (%d bytes)"; + +static void *alloc_c (unsigned numb) +{ + void * p; + + if (!numb) ++numb; + if (!(p = calloc(1, numb))) + std_err(fmtmk(alloc_msg, numb)); + return p; +} + + +static void *alloc_r (void *q, unsigned numb) +{ + void *p; + + if (!numb) ++numb; + if (!(p = realloc(q, numb))) + std_err(fmtmk(alloc_msg, numb)); + return p; +} + + + /* + * This guy is modeled on libproc's readproctab function except + * we reuse and extend any prior proc_t's. He's been customized + * for our specific needs and to avoid the use of */ +static proc_t **readprocs (proc_t **tbl) +{ +#define PTRsz sizeof(proc_t *) /* eyeball candy */ +#define ENTsz sizeof(proc_t) + static int flags = PROC_FILLMEM | PROC_FILLCMD | PROC_FILLUSR + | PROC_FILLSTATUS | PROC_FILLSTAT; + static unsigned savmax = 0; /* first time, bypass: (i) */ + proc_t *ptsk = (proc_t *)-1; /* first time, force: (ii) */ + unsigned curmax = 0; /* every time */ + PROCTAB* PT; + + if (Monpidsidx) { + PT = openproc(flags | PROC_PID, Monpids); + /* work around a previous bug in openproc (now corrected) */ + PT->procfs = NULL; + } else + PT = openproc(flags); + + /* i) Allocated Chunks: *Existing* table; refresh + reuse */ + while (curmax < savmax) { + if (tbl[curmax]->cmdline) { + free(*tbl[curmax]->cmdline); + tbl[curmax]->cmdline = NULL; + } + if (!(ptsk = readproc(PT, tbl[curmax]))) break; + ++curmax; + } + + /* ii) Unallocated Chunks: *New* or *Existing* table; extend + fill */ + while (ptsk) { + /* realloc as we go, keeping 'tbl' ahead of 'currmax++' */ + tbl = alloc_r(tbl, (curmax + 1) * PTRsz); + /* here, readproc will allocate the underlying proc_t stg */ + if ((ptsk = readproc(PT, NULL))) + tbl[curmax++] = ptsk; + } + closeproc(PT); + + /* iii) Chunkless: make 'eot' entry, after possible extension */ + if (curmax >= savmax) { + tbl = alloc_r(tbl, (curmax + 1) * PTRsz); + /* here, we must allocate the underlying proc_t stg ourselves */ + tbl[curmax] = alloc_c(ENTsz); + savmax = curmax + 1; + } + /* this frame's end, but not necessarily end of allocated space */ + tbl[curmax]->pid = -1; + return tbl; + +#undef PTRsz +#undef ENTsz +} + + +/*###### Main screen routines ##########################################*/ + + /* + * Process keyboard input during the main loop plus the three + * special immediate keys used with help processing. + * (thus making us only slightly recursive) */ +static void do_key (unsigned c) +{ +#define kbdCTRL_L 12 + /* standardized 'secure mode' errors */ + const char *smerror = "\aCan't %s in secure mode"; + + switch (c) { + case 'b': + if (!Show_hicols && !Show_hirows) + show_msg("\aNothing to highlight!"); + else { + Show_hibold = !Show_hibold; + } + capsmk(); + break; + + case 'c': + Show_cmdlin = !Show_cmdlin; + break; + + case 'C': + Sort_type = S_CMD; + Sort_func = (QSORT_t)sort_cmd; + break; + + case 'E': + Sort_type = S_USR; + Sort_func = (QSORT_t)sort_usr; + break; + case 'f': case 'F': - change_fields(); - break; + fields_toggle(); + break; + + case 'i': + Show_idleps = !Show_idleps; + break; + + case 'I': + if (Cpu_tot > 1) { + Irix_mode = !Irix_mode; + show_msg(fmtmk("Irix mode %s", Irix_mode ? "On" : "Off")); + } else + show_msg("\aIrix mode requires SMP!"); + break; + + case 'k': + if (Secure_mode) { + show_msg(fmtmk(smerror, "kill")); + } else { + int sig, pid = get_int("PID to kill"); + + if (-1 != pid) { +#ifdef UGH_ITS_4_RH + sig = get_signal2( +#else + sig = signal_name_to_number( +#endif + ask_str(fmtmk("Kill PID %d with signal [%i]" + , pid, DEF_SIGNAL))); + if (-1 == sig) sig = DEF_SIGNAL; + if (sig && kill(pid, sig)) + show_msg(fmtmk("\aKill of PID '%d' with '%d' failed: %s" + , pid, sig, strerror(errno))); + } + } + break; + + case 'l': + HSum_loadav = !HSum_loadav; + mkheadings(); + break; + + case 'm': + HSum_memory = !HSum_memory; + mkheadings(); + break; + + case 'M': + Sort_type = S_MEM; + Sort_func = (QSORT_t)sort_mem; + break; + + case 'n': + case '#': + { int num; + + if (-1 != (num = get_int("Processes to display (0 = unlimited)"))) { + Max_tasks = num; + window_resize(0); + } + } + break; + case 'o': case 'O': - change_order(); - break; + fields_reorder(); + break; + + case 'P': + Sort_type = S_PID; + Sort_func = (QSORT_t)sort_pid; + break; + + case 'q': + stop(0); + + case 'r': + if (Secure_mode) + show_msg(fmtmk(smerror, "renice")); + else { + int pid, val; + + pid = get_int("PID to renice"); + if (-1 == pid) break; + val = get_int(fmtmk("Renice PID %d to value", pid)); + if (setpriority(PRIO_PROCESS, (unsigned)pid, val)) + show_msg(fmtmk("\aRenice of PID %d to %d failed: %s" + , pid, val, strerror(errno))); + } + break; + + case 'R': + Sort_normal = !Sort_normal; + break; + + case 's': + case 'd': + if (Secure_mode) + show_msg(fmtmk(smerror, "change delay")); + else { + float tmp = + get_float(fmtmk("Change delay from %.1f to", Delay_time)); + if (tmp > -1) Delay_time = tmp; + } + break; + + case 'S': + Show_ctimes = !Show_ctimes; + show_msg(fmtmk("Cumulative time %s", Show_ctimes ? "On" : "Off")); + break; + + case 't': + HSum_states = !HSum_states; + mkheadings(); + break; + + case 'T': + Sort_type = S_TME; + Sort_func = (QSORT_t)sort_tme; + break; + + case 'u': + strcpy(ColUsername, ask_str("Which User (Blank for All)")); +#ifdef TRAK_MAXBUFS + MAXBUFS(do_key, ColUsername); +#endif + break; + + case 'U': + Sort_type = S_CPU; + Sort_func = (QSORT_t)sort_cpu; + break; + case 'W': - if (Secure) - SHOWMESSAGE(("\aCan't write configuration in secure mode")); - else { - if (getenv("HOME")) { - strcpy(rcfile, getenv("HOME")); - strcat(rcfile, "/"); - strcat(rcfile, RCFILE); - fp = fopen(rcfile, "w"); - if (fp != NULL) { - fprintf(fp, "%s\n", Fields); - i = (int) Sleeptime; - if (i < 2) - i = 2; - if (i > 9) - i = 9; - fprintf(fp, "%d", i); - if (Secure) - fprintf(fp, "%c", 's'); - if (Cumulative) - fprintf(fp, "%c", 'S'); - if (!show_cmd) - fprintf(fp, "%c", 'c'); - if (Noidle) - fprintf(fp, "%c", 'i'); - if (!show_memory) - fprintf(fp, "%c", 'm'); - if (!show_loadav) - fprintf(fp, "%c", 'l'); - if (!show_stats) - fprintf(fp, "%c", 't'); - if (!Irixmode) - fprintf(fp, "%c", 'I'); - fprintf(fp, "\n"); - fclose(fp); - SHOWMESSAGE(("Wrote configuration to %s", rcfile)); - } else { - SHOWMESSAGE(("Couldn't open %s", rcfile)); - } - } else { - SHOWMESSAGE(("Couldn't get $HOME -- not saving")); - } - } - break; + { FILE *fp = fopen(RCfile, "w"); + + if (fp) { + fprintf(fp, "%s\t\t# Fields displayed and ordering\n" + , CurFields); + fprintf(fp, "%s%s%s%s%s%s%s%s%s%s%s%s%s%c" + "\t\t\t\t# Misc options (spelled poorly)\n" + , Show_cmdlin ? "c" : "", Sort_normal ? "R" : "" + , HSum_loadav ? "l" : "", Show_hirows ? "y" : "" + , HSum_memory ? "m" : "", Show_hicols ? "x" : "" + , Show_idleps ? "i" : "", Show_ctimes ? "S" : "" + , Irix_mode ? "I" : "", HSum_states ? "t" : "" + , Show_hibold ? "b" : "", Show_colors ? "z" : "" + , Show_cpusum ? "1" : "", Sort_type); + fprintf(fp, "%d\t\t\t\t\t# Number of tasks shown\n" + , Max_tasks); + fprintf(fp, "%.1f\t\t\t\t\t# Delay between updates\n" + , Delay_time); + fprintf(fp, "%u,%u,%u\t\t\t\t\t# Base, Msgs & Head colors\n" + , Base_color, Msgs_color, Head_color); + fclose(fp); + show_msg(fmtmk("Wrote configuration to '%s'", RCfile)); + } else + show_msg(fmtmk("\aFailed '%s' open: %s", RCfile, strerror(errno))); + } + break; + + case 'x': + Show_hicols = !Show_hicols; + capsmk(); + break; + + case 'y': + Show_hirows = !Show_hirows; + capsmk(); + break; + + case 'Y': + Sort_type = S_TTY; + Sort_func = (QSORT_t)sort_tty; + break; + + case 'z': + Show_colors = !Show_colors; + capsmk(); + break; + + case 'Z': + tweak_colors(); + break; + + case '?': + case 'h': + { char ch; + + printf("%s%s", Cap_clr_scr, Cap_curs_huge); + /* this string is well above ISO C89's minimum requirements! */ + show_special(fmtmk(HELP_data + , Myname, procps_version + , Secure_mode ? "On" : "Off", Show_ctimes ? "On" : "Off", Delay_time + , Secure_mode ? "" : HELP_unsecured)); + chin(0, &ch, 1); + putp(Cap_curs_norm); + /* our help screen currently provides for three 'immediate' keys, + two of which conflict with the main process display keys */ + switch (ch) { + case 'j' : + strcpy(CurFields, JOB_FIELDS); + ch = 'T'; /* force sort on time */ + break; + case 'm' : + strcpy(CurFields, MEM_FIELDS); + ch = 'M'; /* force sort on %mem/res */ + break; + case 'u' : + strcpy(CurFields, USR_FIELDS); + ch = 'E'; /* force sort on user */ + break; + default : + ch = 0; + } + if (ch) { + /* besides resetting the sort environment with the call to us + below, the 'Show_' manipulations will provide our most subtle + hint as to what the user has just wrought */ + Show_hibold = Show_hicols = Sort_normal = 1; + capsmk(); + mkheadings(); + do_key((unsigned)ch); + } + } + break; + + case ' ': + case kbdCTRL_L: + putp(Cap_clr_scr); + break; + + case '1': + Show_cpusum = !Show_cpusum; + mkheadings(); + break; + + case '\n': /* just ignore it */ + break; + default: - SHOWMESSAGE(("\aUnknown command `%c' -- hit `h' for help", c)); - } + show_msg("\aUnknown command -- try 'h' for help"); + } - /* - * Return to raw mode. - */ - if (!Batch) tcsetattr(0, TCSANOW, &Rawtty); - return; +#undef kbdCTRL_L +#undef smERROR } -/*##################################################################### - *####### A readproctable function that uses already allocated ##### - *####### table entries. ##### - *##################################################################### - */ -#define Do(x) (flags & PROC_ ## x) +#ifdef UGH_ITS_4_RH + /* + * Obtain memory information and display it. + * Return the total memory available as a page count which is + * then used in % memory calc's. */ +static unsigned frame_memory (void) +{ + /* don't be mislead by the proc/sysinfo subscripts, they're just poorly + chosen names for enumerations apparently designed to make source + lines as imbalanced and as long as possible */ + unsigned long long **memarray; -static proc_t** readproctab2(int flags, proc_t** tab, ...) { - PROCTAB* PT = NULL; - static proc_t *buff; - int n = 0; - static int len = 0; - va_list ap; + if (!(memarray = meminfo())) + std_err("Failed /proc/meminfo read"); - va_start(ap, tab); /* pass through args to openproc */ - if (Do(UID)) { - /* temporary variables to ensure that va_arg() instances - * are called in the right order - */ - uid_t* u; - int i; + if (HSum_memory) { + show_special(fmtmk(MEMORY_line1 + , BYTES_2K(memarray[meminfo_main][meminfo_total]) + , BYTES_2K(memarray[meminfo_main][meminfo_used]) + , BYTES_2K(memarray[meminfo_main][meminfo_free]) + , BYTES_2K(memarray[meminfo_main][meminfo_buffers]))); - u = va_arg(ap, uid_t*); - i = va_arg(ap, int); - PT = openproc(flags, u, i); - } - else if (Do(PID)) { - PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */ - /* work around a bug in openproc() */ - PT->procfs = NULL; - /* share some process time, since we skipped opendir("/proc") */ - usleep (50*1000); - } - else if (Do(TTY) || Do(STAT)) - PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */ - else - PT = openproc(flags); - va_end(ap); - buff = (proc_t *) 1; - while (ncmdline) { - free((void*)*tab[n]->cmdline); - tab[n]->cmdline = NULL; - } - buff = readproc(PT, tab[n]); - if (buff) n++; - } - if (buff) { - do { /* (ii) not yet allocated chunks */ - tab = xrealloc(tab, (n+1)*sizeof(proc_t*));/* realloc as we go, using */ - buff = readproc(PT, NULL); /* final null to terminate */ - if(buff) tab[n]=buff; - len++; - n++; - } while (buff); /* stop when NULL reached */ - tab[n-1] = xcalloc(NULL, sizeof (proc_t)); - tab[n-1]->pid=-1; /* Mark end of Table */ - } else { - if (n == len) { - tab = xrealloc(tab, (n+1)*sizeof(proc_t*)); - tab[n] = xcalloc(NULL, sizeof (proc_t)); - len++; - } - tab[n]->pid=-1; /* Use this instead of NULL when not at the end of */ - } /* the allocated space */ - closeproc(PT); - return tab; + show_special(fmtmk(MEMORY_line2 + , BYTES_2K(memarray[meminfo_swap][meminfo_total]) + , BYTES_2K(memarray[meminfo_swap][meminfo_used]) + , BYTES_2K(memarray[meminfo_swap][meminfo_free]) + , BYTES_2K(memarray[meminfo_total][meminfo_cached]))); + } + + return PAGE_CNT(memarray[meminfo_main][meminfo_total]); +} + +#else + + /* + * Obtain memory information and display it. */ +static void frame_memory (void) +{ + meminfo(); + if (HSum_memory) { + show_special(fmtmk(MEMORY_line1 + , kb_main_total + , kb_main_used + , kb_main_free + , kb_main_buffers)); + + show_special(fmtmk(MEMORY_line2 + , kb_swap_total + , kb_swap_used + , kb_swap_free + , kb_main_cached)); + } +} +#endif /* end: UGH_ITS_4_RH */ + + + /* + * State display *Helper* function to calc and display the state + * percentages for a single cpu. In this way, we can support + * the following environments without the usual code bloat. + * 1 - single cpu machines + * 2 - modest smp boxes with room for each cpu's percentages + * 3 - massive smp guys leaving little or no room for process + * display and thus requiring the Show_cpusum toggle */ +static void frame_smp (FILE *fp, const char *fmt, CPUS_t *cpu, const char *pfx) +{ + /* we'll trim to zero if we get negative time ticks, + which has happened with some SMP kernels (pre-2.4?) */ +#define TRIMz(x) ((tz = (long)x) < 0 ? 0 : tz) + TICS_t u_tics, s_tics, n_tics, i_tics; + long u_frme, s_frme, n_frme, i_frme, tot_frme, tz; + + if (4 != fscanf(fp, fmt, &u_tics, &n_tics, &s_tics, &i_tics)) + std_err("Failed /proc/stat read"); + + u_frme = TRIMz(u_tics - cpu->u); + s_frme = TRIMz(s_tics - cpu->s); + n_frme = TRIMz(n_tics - cpu->n); + i_frme = TRIMz(i_tics - cpu->i); + tot_frme = u_frme + s_frme + n_frme + i_frme; + if (1 > tot_frme) tot_frme = 1; + + /* display some kinda' cpu state percentages + (who or what is explained by the passed prefix) */ + show_special(fmtmk(STATES_line2 + , pfx + , (float)u_frme * 100 / tot_frme + , (float)s_frme * 100 / tot_frme + , (float)n_frme * 100 / tot_frme + , (float)i_frme * 100 / tot_frme)); + + /* remember for next time around */ + cpu->u = u_tics; + cpu->s = s_tics; + cpu->n = n_tics; + cpu->i = i_tics; + +#undef TRIMz +} + + + /* + * Calc the number of tasks in each state (run, sleep, etc) + * Calc percent cpu usage for each task (pcpu) + * Calc the cpu(s) percent in each state (user, system, nice, idle) */ +static void frame_states (proc_t **p, int show) +{ + static HIST_t *hist_sav = NULL; + static unsigned hist_siz; + static int hist_tot, showsav; + static CPUS_t *smpcpu; + HIST_t *hist_new; + unsigned total, running, sleeping, stopped, zombie; + float etime; + int i; + + if (!hist_sav) { + hist_tot = 0; + /* room for 512 HIST_t's (if Page_size == 4k) */ + hist_siz = (Page_size / sizeof(HIST_t)); + hist_sav = alloc_c(hist_siz); + /* note: we allocate one more CPUS_t than Cpu_tot so that the last + slot can hold tics representing the /proc/stat cpu summary + (first line read) -- that slot supports Show_cpusum */ + smpcpu = alloc_c((1 + Cpu_tot) * sizeof(CPUS_t)); + showsav = Show_cpusum; + } + + hist_new = alloc_c(hist_siz); + total = running = sleeping = stopped = zombie = 0; + etime = time_elapsed(); + + /* make a pass through the data to get stats */ + while (-1 != p[total]->pid) { + TICS_t tics; + proc_t *this = p[total]; + + switch (this->state) { + case 'S': + case 'D': + sleeping++; + break; + case 'T': + stopped++; + break; + case 'Z': + zombie++; + break; + case 'R': + running++; + break; + } + + if (total * sizeof(HIST_t) >= hist_siz) { + hist_siz += (Page_size / sizeof(HIST_t)); + hist_sav = alloc_r(hist_sav, hist_siz); + hist_new = alloc_r(hist_new, hist_siz); + } + + /* calculate time in this process; the sum of user time (utime) + + system time (stime) -- but PLEASE dont waste time and effort + calculating and saving data that goes unused, like the old top! */ + hist_new[total].pid = this->pid; + hist_new[total].tics = tics = (this->utime + this->stime); + + /* find matching entry from previous pass and make ticks elapsed */ + for (i = 0; i < hist_tot; i++) { + if (this->pid == hist_sav[i].pid) { + tics -= hist_sav[i].tics; + break; + } + } + + /* finally calculate an integer version of %cpu for this task + and plug it into the unfilled slot in proc_t */ + this->pcpu = (tics * 1000 / Hertz) / etime; + if (this->pcpu > 999) this->pcpu = 999; + + /* if in Solaris mode, adjust cpu percentage not only for the cpu + the process is running on, but for all cpus together */ + if (!Irix_mode) this->pcpu /= Cpu_tot; + + total++; + } /* end: while 'p[total]->pid' */ + + if (show) { + FILE *fp; + + /* whoa, we've changed modes -- gotta' clean old histories */ + if (Show_cpusum != showsav) { + /* fresh start for the last slot in the history area */ + if (Show_cpusum) memset(&smpcpu[Cpu_tot], '\0', sizeof(CPUS_t)); + /* fresh start for the true smpcpu history area */ + else memset(smpcpu, '\0', Cpu_tot * sizeof(CPUS_t)); + showsav = Show_cpusum; + } + + /* display Task states */ + show_special(fmtmk(STATES_line1 + , total, running, sleeping, stopped, zombie)); + + /* now arrange to calculate and display states for all Cpu's */ + if (!(fp = fopen("/proc/stat", "r"))) + std_err(fmtmk("Failed /proc/stat open: %s", strerror(errno))); + + if (Show_cpusum) + /* retrieve and display just the 1st /proc/stat line */ + frame_smp(fp, CPU_FMTS_JUST1, &smpcpu[Cpu_tot], "Cpu(s) state:"); + else { + char tmp[SMLBUFSIZ]; + + /* skip the 1st line, which reflects total cpu states */ + if (!fgets(tmp, sizeof(tmp), fp)) + std_err("Failed /proc/stat read"); +#ifdef TRAK_MAXBUFS + MAXBUFS(frame_states, tmp); +#endif + /* now do each cpu's states separately */ + for (i = 0; i < Cpu_tot; i++) { + sprintf(tmp, "%-6scpu%-2d:" /* [ cpu states as ] */ + , i ? " " : "State" /* 'State cpu0 : ... ' */ + , Irix_mode ? i : Cpu_map[i]); /* ' cpu1 : ... ' */ + frame_smp(fp, CPU_FMTS_MULTI, &smpcpu[i], tmp); + } + } + + fclose(fp); + } /* end: if 'show' */ + + /* save this frame's information */ + hist_tot = total; + memcpy(hist_sav, hist_new, hist_siz); + free(hist_new); + /* finally, sort the processes on whatever... */ + qsort(p, total, sizeof(proc_t *), (QSORT_t)Sort_func); +} + + + /* + * Task display *Helper* function to handle highlighted + * column transitions. */ +static void mkcol (unsigned idx, int sta, int *pad, char *buf, ...) +{ + char tmp[COLBUFSIZ]; + va_list va; + + va_start(va, buf); + if (!Show_hicols || Sort_type != Fieldstab[idx].sort) { + vsprintf(buf, Fieldstab[idx].fmts, va); + } else { + vsprintf(tmp, Fieldstab[idx].fmts, va); + sprintf(buf, "%s%s", Row_color_high, tmp); + *pad += Len_row_high; + if (!Show_hirows || 'R' != sta) { + strcat(buf, Row_color_norm); + *pad += Len_row_norm; + } + } + va_end(va); +#ifdef TRAK_MAXBUFS + MAXBUFS(mkcol, tmp); +#endif +} + + + /* + * Displays information for a single task. */ +#ifdef UGH_ITS_4_RH +static void show_a_task (proc_t *task, unsigned mempgs) +#else +static void show_a_task (proc_t *task) +#endif +{ + /* the following macro is used for those columns that are NOT sortable + so as to avoid the function call overhead since mkcol cannot be made + inline -- if additional sort columns are added, change the appropriate + switch label's usage to lower case and thus invoke the real function */ +#define MKCOL(idx,sta,pad,buf,arg) \ + sprintf(buf, Fieldstab[idx].fmts, arg) + char rbuf[ROWBUFSIZ]; + int i, x, pad; + + pad = 0; + rbuf[0] = '\0'; + + for (i = 0; i < NumFields; i++) { + char cbuf[COLBUFSIZ]; + unsigned f, s, w; + + cbuf[0] = '\0'; + f = PFlags[i]; + s = Fieldstab[f].scale; + w = Fieldstab[f].width; + + switch (f) { + case P_CMD: + { char *cmdptr, cmdnam[ROWBUFSIZ]; + + if (Show_cmdlin) { + cmdnam[0] = '\0'; + if (task->cmdline) { + x = 0; + do { + /* whoa, during a kernel build, parts of the make + process will create cmdlines in excess of 3000 bytes + but *without* the typical intervening nulls */ + strcat(cmdnam + , fmtmk("%.*s ", Max_cmd, task->cmdline[x++])); + /* whoa, gnome's xscreensaver had a ^I in his cmdline + creating a line wrap when the window was maximized & + the tab came into view -- that in turn, whacked + our heading lines so we'll strim those !#@*#!... */ + strim(1, cmdnam); + /* enough, don't ya think? */ + if (Max_cmd < (int)strlen(cmdnam)) + break; + } while (task->cmdline[x]); + } else { + /* if this process really doesn't have a cmdline, we'll + consider it a kernel thread and display it uniquely + [ we need sort_cmd's complicity in this plot ] */ + strcpy(cmdnam, fmtmk("( %s )", task->cmd)); + } + cmdptr = cmdnam; + } else + cmdptr = task->cmd; + /* hurry up, before cmdptr goes out of scope... */ + mkcol(f, task->state, &pad, cbuf, Max_cmd, Max_cmd, cmdptr); + } + break; + case P_CODE: + MKCOL(f, task->state, &pad, cbuf + , scale_num(PAGES_2K(task->trs), w, s)); + break; + case P_CPU: + mkcol(f, task->state, &pad, cbuf, (float)task->pcpu / 10); + break; + case P_DATA: + MKCOL(f, task->state, &pad, cbuf + , scale_num(PAGES_2K(task->drs), w, s)); + break; + case P_DIRTY: + MKCOL(f, task->state, &pad, cbuf + , scale_num((unsigned)task->dt, w, s)); + break; + case P_FAULT: + MKCOL(f, task->state, &pad, cbuf + , scale_num(task->maj_flt, w, s)); + break; + case P_FLAGS: + MKCOL(f, task->state, &pad, cbuf, task->flags); + for (x = 0; x < (int)strlen(cbuf); x++) + if ('0' == cbuf[x]) cbuf[x] = '.'; + break; + case P_GROUP: + MKCOL(f, task->state, &pad, cbuf, task->egroup); + break; + case P_MEM: + mkcol(f, task->state, &pad, cbuf +#ifdef UGH_ITS_4_RH + , (float)task->resident * 100 / mempgs); +#else + , (float)PAGES_2K(task->resident) * 100 / kb_main_total); +#endif + break; + case P_NCPU: +#ifdef UGH_ITS_4_RH + MKCOL(f, task->state, &pad, cbuf, task->lproc); +#else + MKCOL(f, task->state, &pad, cbuf, task->processor); +#endif + break; + case P_NI: + MKCOL(f, task->state, &pad, cbuf, task->nice); + break; + case P_PID: + mkcol(f, task->state, &pad, cbuf, task->pid); + break; + case P_PPID: + MKCOL(f, task->state, &pad, cbuf, task->ppid); + break; + case P_PR: + MKCOL(f, task->state, &pad, cbuf, task->priority); + break; + case P_RES: + /* 'rss' vs 'resident' (which includes IO memory) ? + -- we'll ensure that VIRT = SWAP + RES */ + mkcol(f, task->state, &pad, cbuf + , scale_num(PAGES_2K(task->resident), w, s)); + break; + case P_SHR: + MKCOL(f, task->state, &pad, cbuf + , scale_num(PAGES_2K(task->share), w, s)); + break; + case P_STA: + MKCOL(f, task->state, &pad, cbuf, status(task)); + break; + case P_SWAP: + MKCOL(f, task->state, &pad, cbuf + , scale_num(PAGES_2K(task->size - task->resident), w, s)); + break; + case P_TIME: + case P_TIME2: + { TICS_t t; + + t = task->utime + task->stime; + if (Show_ctimes) + t += (task->cutime + task->cstime); + mkcol(f, task->state, &pad, cbuf, scale_tics(t, w)); + } + break; + case P_TTY: + { char tmp[TNYBUFSIZ]; + + dev_to_tty(tmp, Fieldstab[f].width + , task->tty, task->pid, ABBREV_DEV); + mkcol(f, task->state, &pad, cbuf, tmp); + } + break; + case P_UID: + MKCOL(f, task->state, &pad, cbuf, task->euid); + break; + case P_USER: + mkcol(f, task->state, &pad, cbuf, task->euser); + break; + case P_VIRT: + MKCOL(f, task->state, &pad, cbuf + , scale_num(PAGES_2K(task->size), w, s)); + break; + case P_WCHAN: + if (No_ksyms) +#ifdef UPCASE_HEXES + MKCOL(f, task->state, &pad, cbuf + , fmtmk("x%08lX", (long)task->wchan)); +#else + MKCOL(f, task->state, &pad, cbuf + , fmtmk("x%08lx", (long)task->wchan)); +#endif + else + MKCOL(f, task->state, &pad, cbuf, wchan(task->wchan)); + break; + + } /* end: switch 'PFlags[i]' */ +#ifdef TRAK_MAXBUFS + MAXBUFS(show_a_task, cbuf); +#endif + strcat(rbuf, cbuf); + + } /* end: for 'NumFields' */ +#ifdef TRAK_MAXBUFS + MAXBUFS(show_a_task, rbuf); +#endif + + /* This row buffer could be stuffed with parameterized strings. + We are thus advised to always use tputs/putp, but it works just + fine with good ol' printf... */ + printf("\n%s%.*s%s%s" + , (Show_hirows && 'R' == task->state) ? Row_color_high : Row_color_norm + , Screen_cols + pad + , rbuf + , Caps_off + , Cap_clr_eol); + +#ifdef TRAK_MAXCAPS + if (pad > Max_pads) Max_pads = pad; + if (pad < Min_pads) Min_pads = pad; + /* now that we have TRAK_MAXBUFS, the next two duplicate some effort */ + if ((int)strlen(rbuf) > Max_rbuf) Max_rbuf = (int)strlen(rbuf); + if ((int)strlen(rbuf) < Min_rbuf) Min_rbuf = (int)strlen(rbuf); +#endif + +#undef MKCOL +} + + + /* + * Read all process info and display it. */ +static void show_everything (void) +{ + static proc_t **p_table = NULL; + int ntask, nline; +#ifdef UGH_ITS_4_RH + unsigned mempgs; +#endif + + if (!p_table) { + p_table = readprocs(p_table); + frame_states(p_table, 0); + sleep(1); + } else + putp(Batch ? "\n" : Cap_home); + + /* + ** Display Load averages */ + if (HSum_loadav) + show_special(fmtmk(LOADAV_line, Myname, sprint_uptime())); + + /* + ** Display System stats (also calc 'pcpu' and sort p_table) */ + p_table = readprocs(p_table); + frame_states(p_table, HSum_states); + + /* + ** Display Memory and Swap space usage */ +#ifdef UGH_ITS_4_RH + mempgs = frame_memory(); +#else + frame_memory(); +#endif + + /* + ** Display Headings for columns */ + printf("%s%s%s%s%s" + , tg2(0, MSG_line + 1) + , Hdr_color + , ColHeadings + , Caps_off + , Cap_clr_eol); + + /* Finally! Loop through to find each task, and display it ... + ... lather, rinse, repeat */ + ntask = nline = 0; + while (-1 != p_table[ntask]->pid && nline < Max_lines) { + if ((Show_idleps + || ('S' != p_table[ntask]->state && 'Z' != p_table[ntask]->state)) + && ((!ColUsername[0]) + || (!strcmp(ColUsername, p_table[ntask]->euser)) ) ) { + /* + ** Display a process Row */ +#ifdef UGH_ITS_4_RH + show_a_task(p_table[ntask], mempgs); +#else + show_a_task(p_table[ntask]); +#endif + if (!Batch) ++nline; + } + ++ntask; + } + + printf("%s%s%s", Cap_clr_eos, tg2(0, MSG_line), Cap_clr_eol); + fflush(stdout); +} + + +/*###### Entry point ###################################################*/ + +int main (int dont_care_argc, char **argv) +{ + char not_really_tmp[OURPATHSZ]; + int i; + + /* setup our program name(s)... */ + Myname = strrchr(argv[0], '/'); + if (Myname) ++Myname; else Myname = argv[0]; + Myrealname = Myname; + memset(not_really_tmp, '\0', sizeof(not_really_tmp)); + /* proper permissions should deny symlinks to /usr/bin for ordinary + users, but root may have employed them -- Myrealname will be used + in constructing the global rc filename ... */ + if (-1 != readlink(argv[0], not_really_tmp, sizeof(not_really_tmp) - 1)) { + Myrealname = strrchr(not_really_tmp, '/'); + if (Myrealname) ++Myrealname; else Myrealname = not_really_tmp; +#ifdef TRAK_MAXBUFS + MAXBUFS(main, not_really_tmp); +#endif + } + + /* setup some important system stuff... */ + Page_size = getpagesize(); + Cpu_tot = sysconf(_SC_NPROCESSORS_ONLN); + if (1 > Cpu_tot) Cpu_tot = 1; + Cpu_map = alloc_r(NULL, sizeof(int) * Cpu_tot); + for (i = 0; i < Cpu_tot; i++) + Cpu_map[i] = i; + + rcfiles_read(); + parse_argvs(argv); + terminal_set(); + window_resize(0); + + /* set up signal handlers */ + signal(SIGALRM, stop); + signal(SIGHUP, stop); + signal(SIGINT, stop); + signal(SIGPIPE, stop); + signal(SIGQUIT, stop); + signal(SIGTERM, stop); + signal(SIGTSTP, suspend); + signal(SIGTTIN, suspend); + signal(SIGTTOU, suspend); + signal(SIGCONT, window_resize); + signal(SIGWINCH, window_resize); + + /* loop, collecting process info and sleeping */ + do { + struct timeval tv; + fd_set fs; + char c; + + show_everything(); + if (Msg_awaiting) show_msg(Msg_delayed); + if (0 < Loops) --Loops; + if (!Loops) stop(0); + + if (Batch) + sleep((unsigned)Delay_time); + else { + /* Linux reports time not slept, so we must reinit every time */ + tv.tv_sec = Delay_time; + tv.tv_usec = (Delay_time - (int)Delay_time) * 1000000; + FD_ZERO(&fs); + FD_SET(STDIN_FILENO, &fs); + if (0 < select(STDIN_FILENO+1, &fs, NULL, NULL, &tv) + && 0 < chin(0, &c, 1)) + do_key((unsigned)c); + } + } while (1); + + return 0; } diff --git a/top.h b/top.h index e7cc2e2e..84f3a772 100644 --- a/top.h +++ b/top.h @@ -1,228 +1,405 @@ +/* top.h - Header file: show Linux processes */ /* - * top.h header file 1996/05/18, - * - * function prototypes, global data definitions and string constants. + * Copyright (c) 2002 - James C. Warner, + * All rights reserved. + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. */ +#ifndef _Itop +#define _Itop -static proc_t** readproctab2(int flags, proc_t** tab, ...); -static void parse_options(char *Options, int secure); -static void get_options(void); -static void error_end(int rno); -static void end(int signo); -static void stop(int signo); -static void window_size(int signo); -static int make_header(void); -static char *getstr(void); -static int getsig(void); -static float getfloat(void); -static int time_sort(proc_t **P, proc_t **Q); -static int pcpu_sort(proc_t **P, proc_t **Q); -static int mem_sort(proc_t **P, proc_t **Q); -static int age_sort(proc_t **P, proc_t **Q); -static void show_fields(void); -static void change_order(void); -static void change_fields(void); -static void show_task_info(proc_t *task); -static void show_procs(void); -static float get_elapsed_time(void); -static void show_meminfo(void); -static void do_stats(proc_t** p, float elapsed_time, int pass); -static void do_key(char c); + /* Determines for whom we're destined, debian or !#@*#! redhat ----- */ +//#define UGH_ITS_4_RH /* use the redhat libproc conventions */ + + /* Defines intended to be played with ------------------------------ */ +//#define UPCASE_HEXES /* show any hex values in upper case */ +//#define UPCASE_SCALE /* show scaled times & memory upper case */ +//#define UPCASE_SUMMK /* show memory summary kilobytes with 'K' */ + +//#define COLOR_SLEEPY /* purples and blues (sissy colors) */ +//#define COLOR_CONSOL /* for linux consoles (white on black) */ + + /* Development/Debugging defines ----------------------------------- */ +//#define TRAK_MAXCAPS /* track & report misc terminfo stuff */ +//#define TRAK_MAXBUFS /* track & report internal buffer usage */ + + /* Special define to test extremely dumb terminals! ---------------- */ +//#define PRETENDNOCAP /* use a terminal without essential caps */ -/* configurable field display support */ +/*###### Some Typedef's and Enum's #####################################*/ -static int pflags[30]; -static int Numfields; + /* This typedef attempts to ensure consistent 'ticks' handling. */ +typedef unsigned long TICS_t; + + /* This structure consolidates the information that's used + in a variety of display roles. */ +typedef struct { + const char *head; /* name for column headings + toggle/reorder fields */ + const char *fmts; /* sprintf format string for field display */ + const int width; /* field width, if applicable */ + const int scale; /* scale_num type, if applicable */ + const int sort; /* sort type, if applicable (used soley by mkcol) */ + const char *desc; /* description for toggle/reorder fields */ +} FTAB_t; + + /* This structure stores one piece of critical 'history' + information from one frame to the next -- we don't calc + and save data that goes unused like the old top! */ +typedef struct { + int pid; + TICS_t tics; +} HIST_t; + + /* This structure serves single (non-smp), multiple (smp) and + consolidated (smp, but Show_cpusum == 1) environments. It is + used to calculate a frame's cpu(s) state percentages */ +typedef struct { + TICS_t u, /* ticks count as represented in /proc/stat */ + n, /* (not in the order of our display) */ + s, + i; +} CPUS_t; + + /* Sorted columns support. */ +typedef int (*QSORT_t)(const void *, const void *); +enum sort { + S_CMD = 'C', S_MEM = 'M', S_TME = 'T', S_PID = 'P', S_TTY = 'Y', + S_CPU = 'U', S_USR = 'E' +}; + + /* The scaling 'type' used with scale_num() -- this is how + the passed number is interpreted should scaling be necessary */ +enum scale_num { + SK_no, SK_Kb, SK_Mb, SK_Gb +}; + + /* Flags for each possible field. + At the moment 32 are supported [ see PFlags/PFLAGSSIZ ] */ +enum pflag { + P_PID, P_PPID, P_UID, P_USER, P_GROUP, P_TTY, + P_PR, P_NI, + P_NCPU, P_CPU, P_TIME, P_TIME2, + P_MEM, P_VIRT, P_SWAP, P_RES, P_CODE, P_DATA, P_SHR, + P_FAULT, P_DIRTY, + P_STA, P_CMD, P_WCHAN, P_FLAGS +}; - /* Name of the config file (in $HOME) */ -#ifndef RCFILE -#define RCFILE ".toprc" +/*###### Some Miscellaneous Macro definitions ##########################*/ + + /* Message line placement at runtime */ +#define MSG_line ( HSum_lines - 2 ) + + /* Used as return argument to achieve normal/reversed sorts + in the sort callbacks */ +#define SORT_lt ( Sort_normal ? 1 : -1 ) +#define SORT_gt ( Sort_normal ? -1 : 1 ) + + /* Convert some proc stuff into vaules we can actually use */ +#define BYTES_2K(n) (unsigned)( (n) >> 10 ) +#define PAGES_2B(n) (unsigned)( (n) * Page_size ) +#define PAGES_2K(n) BYTES_2K(PAGES_2B(n)) +#define PAGE_CNT(n) (unsigned)( (n) / Page_size ) + + /* Yield table size as 'int' */ +#define MAXtbl(t) ( (int)(sizeof(t)/sizeof(t[0])) ) + + +/*###### Special Macros (debug and/or informative) #####################*/ + + /* Orderly end, with any sort of message - see fmtmk */ +#define debug_END(s) { \ + static void std_err (const char *); \ + fputs(Cap_clr_scr, stdout); \ + std_err(s); \ + } + +#ifdef TRAK_MAXBUFS + /* Provide for tracking maximum buffer sizes */ +#define ARPTBUF(f,b) "\n\t%4d of %d\t" #f ", " #b +#define ASIZBUF(f,b) Siz_ ## f ## _ ## b +#define AUSEBUF(f,b) Use_ ## f ## _ ## b +#define BUF2INT(f,b) static int AUSEBUF(f,b) = 0, ASIZBUF(f,b) = 0; +#define MAXBUFS(f,b) { \ + if ((int)strlen(b) > AUSEBUF(f,b)) \ + AUSEBUF(f,b) = (int)strlen(b); \ + ASIZBUF(f,b) = (int)sizeof(b); \ + } #endif -#ifndef SYS_TOPRC -#define SYS_TOPRC "/etc/toprc" + +/*###### Some Miscellaneous constants ##################################*/ + + /* The default value for the 'k' (kill) request */ +#define DEF_SIGNAL SIGTERM + + /* The default delay twix updates */ +#define DEF_DELAY 2.0 + + /* The length of time a 'message' is displayed */ +#define MSG_SLEEP 1 + + /* Minimum summary lines (thus, just msg line + col heads) */ +#define SUMMINLINS 2 + + /* Specific process id monitoring support (command line only) */ +#define MONPIDMAX 12 + + /* Miscellaneous buffer sizes with liberal values + -- mostly just to pinpoint source code usage/dependancies */ +#define PFLAGSSIZ 32 +#define CAPBUFSIZ 32 +#define CLRBUFSIZ 64 +#define GETBUFSIZ 32 +#define TNYBUFSIZ 32 +#define SMLBUFSIZ 256 +#define MEDBUFSIZ 512 +#define OURPATHSZ 1024 +#define BIGBUFSIZ 2048 +#define RCFBUFSIZ SMLBUFSIZ +#define USRNAMSIZ GETBUFSIZ +#define COLBUFSIZ SMLBUFSIZ + CLRBUFSIZ +#define ROWBUFSIZ MEDBUFSIZ + CLRBUFSIZ + + /* Color stuff... + note: we avoid the use of background color so as to maximize + compatibility with the user's xterm settings */ +#ifdef COLOR_SLEEPY +#define BASEcolor COLOR_MAGENTA +#define MSGScolor COLOR_CYAN +#define HEADcolor COLOR_BLUE +#else +#ifdef COLOR_CONSOL +#define BASEcolor COLOR_CYAN +#define MSGScolor COLOR_RED +#define HEADcolor COLOR_WHITE +#else +#define BASEcolor COLOR_RED +#define MSGScolor COLOR_RED +#define HEADcolor COLOR_YELLOW +#endif #endif -#define MAXLINES 2048 -#define MAXNAMELEN 1024 -/* this is what procps top does by default, so let's do this, if nothing is - * specified - */ -#ifndef DEFAULT_SHOW -/* 0 1 2 3 */ -/* 0123456789012345678901234567890 */ -#define DEFAULT_SHOW "AbcDgHIjklMnoTP|qrsuzyV{EFWX" +/*###### Display Support *Data* ########################################*/ + + /* The default fields displayed and their order, + if nothing is specified by the loser, oops user */ +#define DEF_FIELDS "AbcDefGHiJkLMNOPqrstuVWxy" + /* Help screen selectable grouped fields */ +#define JOB_FIELDS "ABWdefikqrstuxyLJMGHVNOPC" +#define MEM_FIELDS "AMNOPQRSTUWbcdefiklxyVGHJ" +#define USR_FIELDS "CDEFABWghiknopqrstuxyLJMV" + + + /* These are the possible fscanf formats used in /proc/stat + reads during history processing. */ +#define CPU_FMTS_MULTI "cpu%*d %lu %lu %lu %lu\n" +#define CPU_FMTS_JUST1 "cpu %lu %lu %lu %lu\n" + + + /* Summary Lines specially formatted string(s) -- + see 'show_special' for syntax details + other cautions. */ +#define LOADAV_line "%s -\03%s\n" +#define STATES_line1 "Tasks:\03" \ + " %3u \02total,\03 %3u \02running,\03 %3u \02sleeping,\03" \ + " %3u \02stopped,\03 %3u \02zombie\03\n" +#define STATES_line2 "%s\03" \ + " %#5.1f%% \02user,\03 %#5.1f%% \02system,\03" \ + " %#5.1f%% \02nice,\03 %#5.1f%% \02idle\03\n" +#ifdef UPCASE_SUMMK +#define MEMORY_line1 "Mem: \03" \ + " %8uK \02total,\03 %8uK \02used,\03" \ + " %8uK \02free,\03 %8uK \02buffers\03\n" +#define MEMORY_line2 "Swap:\03" \ + " %8uK \02total,\03 %8uK \02used,\03" \ + " %8uK \02free,\03 %8uK \02cached\03\n" +#else +#define MEMORY_line1 "Mem: \03" \ + " %8uk \02total,\03 %8uk \02used,\03" \ + " %8uk \02free,\03 %8uk \02buffers\03\n" +#define MEMORY_line2 "Swap:\03" \ + " %8uk \02total,\03 %8uk \02used,\03" \ + " %8uk \02free,\03 %8uk \02cached\03\n" #endif -static char Fields[256] = ""; -/* This structure stores some critical information from one frame to - the next. mostly used for sorting. Added cumulative and resident fields. */ -struct save_hist { - int ticks; - int pid; - int pcpu; - int utime; - int stime; -}; + /* Colors Help specially formatted string(s) -- + see 'show_special' for syntax details + other cautions. */ +#define COLOR_sample \ + "%s%s's \01Help for color mapping\02 - %s\n" \ + "\n" \ + " color -\03 08:41:24 up 1 day, 6:07, 7 users, load average:\n" \ + " Tasks:\03 64 \02total,\03 2 \02running,\03 62 \02sleeping,\03 0 \02stopped,\03\n" \ + " State cpu0 :\03 76.5%% \02user,\03 11.2%% \02system,\03 0.0%% \02nice,\03\n" \ + " \01 Nasty Message! \04 -or- \01Input Prompt\05\n" \ + " \01 PID TTY PR NI %%CPU TIME+ VIRT SWAP STA Command \06\n" \ + " 17284 \03 pts/2 \07 8 0 0.0 0:00.75 1380 0 S /bin/bas\03\n" \ + " \01 8601 pts/1 7 -10 0.4 0:00.03 916 0 R< color -b\07\n" \ + " 11005 \03 ? \07 9 0 0.0 0:02.50 2852 1008 S amor -se\03\n" \ + " 2924 \03 ? \07 9 -1 0.0 1:08.16 30040 20m S< X :0\03\n" \ + " (normal toggles available: \01b\02 = bold/reverse; \01z\02 = color/mono)\n" \ + "\n\n" \ + "Select \01target\02 as upper case letter:\n" \ + " B\02 = Base color,\01 H\02 = Column Headings,\01 M\02 = Messages/Prompts\n" \ + "Select \01color\02 as number:\n" \ + " 0\02 = black,\01 1\02 = red, \01 2\02 = green,\01 3\02 = yellow,\n" \ + " 4\02 = blue, \01 5\02 = magenta,\01 6\02 = cyan, \01 7\02 = white\n" \ + "\n" \ + "Selected: \01target\02 \01 %c \04; \01color\02 \01 %d \04\n" \ + " press 'q' to abort or to commit " \ + "" - /* The original terminal attributes. */ -static struct termios Savetty; - /* The new terminal attributes. */ -static struct termios Rawtty; - /* Cached termcap entries. */ -static char *cm, *cl, *top_clrtobot, *top_clrtoeol, *ho, *md, *me, *mr; - /* Current window size. Note that it is legal to set Display_procs - larger than can fit; if the window is later resized, all will be ok. - In other words: Display_procs is the specified max number of - processes to display (zero for infinite), and Maxlines is the actual - number. */ -static int Lines, Cols, Maxlines, Display_procs; - /* Maximum length to display of the command line of a process. */ -static unsigned Maxcmd; - /* Controls how long we sleep between screen updates. Accurate to - microseconds. */ -static float Sleeptime = 5; - /* for opening/closing the system map */ -static int psdbsucc = 0; - /* Mode flags. */ -static int Irixmode = 1; -static int Secure = 0; -static int Cumulative = 0; -static int Noidle = 0; + /* Keyboard Help specially formatted string(s) -- + see 'show_special' for syntax details + other cautions. */ +#define HELP_data \ + "%s's \01Help for interactive commands\02 - %s\n" \ + "status: \01Secure mode \03%s\02;\01 Cumulative mode \03%s\02; \01Delay time \03%.1f\02 seconds\n" \ + " sp or ^L Redraw screen\n" \ + " o or O Rearrange fields\n" \ + " f or F Add and remove fields, or select group now from:\n" \ + " \01j\02) job fields; \01m\02) memory fields; \01u\02) user fields\n" \ + " Z Change color mapping\05\n" \ + "\n" \ + "C,M,P,T,U,Y,E Sort: \01C\02) cmd; \01M\02) mem; \01P\02) pid; \01T\02) time; \01U\02) cpu; \01Y\02) tty; \01E\02) user\n" \ + " R Toggle normal/reverse sort for any of above\n" \ + " l,t,m Toggle summary: \01l\02) load avg; \01t\02) task/cpu stats; \01m\02) mem info\n" \ + " c,i,S Toggle: \01c\02) cmd name/line; \01i\02) idle tasks; \01S\02) cumulative time\n" \ + " x,y\05 Toggle highlights: \01x\02) sort field; \01y\02) running tasks\n" \ + " z,b\05 Toggle: \01z\02) color/mono; \01b\02) bold/reverse if 'x' or 'y'\n" \ + " 1,I SMP views:\01 1\02) single/separate states; \01I\02) Irix/Solaris mode\n" \ + "\n" \ + "%s" \ + " u Show specific user only\n" \ + " # or n Set maximum tasks displayed\n" \ + " W Write configuration file\n" \ + " q Quit\n" \ + "Press any key to continue " \ + "" -static int CPU_states = 0; -static char CurrUser[BUFSIZ]; + /* This guy goes above the 'u' help text (maybe) */ +#define HELP_unsecured \ + " k Kill a task\n" \ + " r Renice a task\n" \ + " s or d Set update interval\n" \ + "" -static int CL_pg_shift = (PAGE_SHIFT - 10); -static int CL_wchan_nout = -1; -static int show_stats = 1; /* show status summary */ -static int show_memory = 1; /* show memory summary */ -static int show_loadav = 1; /* show load average and uptime */ -static int show_cmd = 1; /* show command name instead of commandline */ + /* Fields Reorder/Toggle specially formatted string(s) -- + see 'show_special' for syntax details + other cautions + note: the leading newline below serves really dumb terminals; + if there's no 'cursor_home', the screen will be a mess + but this part will still be functional. */ +#define FIELDS_current \ + "\n%s%s's\01 Current Fields\02: \01 %s \04\n%s " \ + "" -static pid_t monpids[520]; /* randomly chosen value */ -static const int monpids_max = sizeof(monpids)/sizeof(pid_t); -static int monpids_index = 0; + /* Some extra explanatory text which accompanies the Fields display. + note: the newlines cannot actually be used, they just serve as + substring delimiters for the 'display_fields' routine. */ +#define FIELDS_xtra \ + "Flags field:\n" \ + " 0x00000001 PF_ALIGNWARN\n" \ + " 0x00000002 PF_STARTING\n" \ + " 0x00000004 PF_EXITING\n" \ + " 0x00000040 PF_FORKNOEXEC\n" \ + " 0x00000100 PF_SUPERPRIV\n" \ + " 0x00000200 PF_DUMPCORE\n" \ + " 0x00000400 PF_SIGNALED\n" \ + " 0x00000800 PF_MEMALLOC\n" \ + " 0x00040000 PF_KERNTHREAD (2.5)\n" \ + " 0x00100000 PF_USEDFPU (thru 2.4)\n" \ + " 0x00400000 PF_ATOMICALLOC\n" \ + "\n" \ + "Memory notes:\n" \ + " VIRT = SWAP + RES\n" \ + " RES = CODE + DATA\n" \ + "" + +/*###### Some Prototypes (ha!) #########################################*/ -static int Loops = -1; /* number of iterations. -1 loops forever */ -static int Batch = 0; /* batch mode. Collect no input, dumb output */ + /* None of these are necessary when the source file is properly + * organized -- they're here for documentation purposes ! + * Note also that functions are alphabetical within a group to aid + * source code navigation, which often influences choice of identifers. */ +/*------ Sort callbacks ------------------------------------------------*/ +//atic int sort_cmd (proc_t **P, proc_t **Q); +//atic int sort_cpu (proc_t **P, proc_t **Q); +//atic int sort_mem (proc_t **P, proc_t **Q); +//atic int sort_pid (proc_t **P, proc_t **Q); +//atic int sort_tme (proc_t **P, proc_t **Q); +//atic int sort_tty (proc_t **P, proc_t **Q); +//atic int sort_usr (proc_t **P, proc_t **Q); +/*------ Tiny useful routine(s) ----------------------------------------*/ +//atic int chin (int ech, char *buf, unsigned cnt); +//atic const char *fmtmk (const char *fmts, ...); +//atic char *strim (int sp, char *str); +//atic char *tg2 (int x, int y); +/*------ Misc Color/Highlighting support -------------------------------*/ +//atic void capsmk (void); +//atic void msg_save (const char *fmts, ...); +//atic void show_msg (const char *str); +//atic void show_pmt (const char *str); +//atic void show_special (const char *glob); +//atic void tweak_colors (void); +/*------ Small utility routines ----------------------------------------*/ +//atic char *ask_str (const char *prompt); +//atic float get_float (const char *prompt); +//atic int get_int (const char *prompt); +//atic void mkheadings (void); +//atic char *scale_num (unsigned num, const unsigned width, const unsigned type); +//atic char *scale_tics (TICS_t tics, const unsigned width); +//atic float time_elapsed (void); +/*------ Exit and Signal handled routines ------------------------------*/ +//atic void bye_bye (int eno, const char *str); +//atic void std_err (const char *str); +//atic void stop (int dont_care_sig); +//atic void suspend (int dont_care_sig); +//atic void window_resize (int dont_care_sig); +/*------ Startup routines ----------------------------------------------*/ +//atic void parse_argvs (char **argv); +//atic void parse_rc (char *opts); +//atic void rcfiles_read (void); +//atic void terminal_set (void); +/*------ Field Selection/Ordering routines -----------------------------*/ +//atic void display_fields (void); +//atic void fields_reorder (void); +//atic void fields_toggle (void); +/*------ Library Alternatives ------------------------------------------*/ +//atic void *alloc_c (unsigned numb); +//atic void *alloc_r (void *q, unsigned numb); +//atic proc_t **readprocs (proc_t **tbl); +/*------ Main screen routines ------------------------------------------*/ +//atic void do_key (unsigned c); +#ifdef UGH_ITS_4_RH +//atic unsigned frame_memory (void); +#else +//atic void frame_memory (void); +#endif +//atic void frame_smp (FILE *fp, const char *fmt, CPUS_t *cpu, const char *pfx); +//atic void frame_states (proc_t **p, int show); +//atic void mkcol (unsigned idx, int sta, int *pad, char *buf, ...); +#ifdef UGH_ITS_4_RH +//atic void show_a_task (proc_t *task, unsigned mempgs); +#else +//atic void show_a_task (proc_t *task); +#endif +//atic void show_everything (void); +/*------ Entry point ---------------------------------------------------*/ +// int main (int dont_care_argc, char **argv); -/* sorting order: cpu%, mem, time (cumulative, if in cumulative mode) */ -enum { - S_PCPU, S_MEM, S_TIME, S_AGE, S_NONE -}; -/* default sorting by CPU% */ -static int sort_type = S_PCPU; -/* flags for each possible field. At the moment up to 30 are supported */ -enum { - P_PID, P_PPID, P_EUID, P_EUSER, - P_PCPU, P_PMEM, P_TTY, P_PRI, - P_NICE, P_PAGEIN, P_TSIZ, P_DSIZ, - P_SIZE, P_TRS, P_SWAP, P_SHARE, - P_A, P_WP, P_DT, P_RSS, - P_WCHAN, P_STAT, P_TIME, P_COMMAND, - P_LCPU, P_FLAGS, P_END -}; -/* corresponding headers */ -static char *headers[] = -{ - " PID ", " PPID ", " UID ", - "USER ", "%CPU ", "%MEM ", - "TTY ", "PRI ", " NI ", - "PAGEIN ", "TSIZE ", "DSIZE ", - " SIZE ", " TRS ", "SWAP ", - "SHARE ", " A ", " WP ", - " D ", " RSS ", "WCHAN ", - "STAT ", " TIME ", "COMMAND", - "LC ", - " FLAGS " -}; -/* corresponding field desciptions */ -static char *headers2[] = -{ - "Process Id", "Parent Process Id", "User Id", - "User Name", "CPU Usage", "Memory Usage", - "Controlling tty", "Priority", "Nice Value", - "Page Fault Count", "Code Size (kb)", "Data+Stack Size (kb)", - "Virtual Image Size (kb)", "Resident Text Size (kb)", "Swapped kb", - "Shared Pages (kb)", "Accessed Page count", "Write Protected Pages", - "Dirty Pages", "Resident Set Size (kb)", "Sleeping in Function", - "Process Status", "CPU Time", "Command", - "Last used CPU (expect this to change regularly)", - "Task Flags (see linux/sched.h)" -}; + /* just sanity check(s)... */ +#if USRNAMSIZ < GETBUFSIZ + #error "Jeeze, USRNAMSIZ Must NOT be less than GETBUFSIZ !" +#endif - /* The header printed at the top of the process list.*/ -static char Header[MAXLINES]; - - /* The response to the interactive 'h' command. */ -#define HELP_SCREEN "\ -Interactive commands are:\n\ -\n\ -space\tUpdate display\n\ -^L\tRedraw the screen\n\ -fF\tadd and remove fields\n\ -oO\tChange order of displayed fields\n\ -h or ?\tPrint this list\n\ -S\tToggle cumulative mode\n\ -i\tToggle display of idle proceses\n\ -I\tToggle between Irix and Solaris views (SMP-only)\n\ -c\tToggle display of command name/line\n\ -l\tToggle display of load average\n\ -m\tToggle display of memory information\n\ -t\tToggle display of summary information\n\ -k\tKill a task (with any signal)\n\ -r\tRenice a task\n\ -N\tSort by pid (Numerically)\n\ -A\tSort by age\n\ -P\tSort by CPU usage\n\ -M\tSort by resident memory usage\n\ -T\tSort by time / cumulative time\n\ -u\tShow only a specific user\n\ -n or #\tSet the number of process to show\n\ -s\tSet the delay in seconds between updates\n\ -W\tWrite configuration file ~/.toprc\n\ -q\tQuit" -#define SECURE_HELP_SCREEN "\ -Interactive commands available in secure mode are:\n\ -\n\ -space\tUpdate display\n\ -^L\tRedraw the screen\n\ -fF\tadd and remove fields\n\ -h or ?\tPrint this list\n\ -S\tToggle cumulative mode\n\ -i\tToggle display of idle proceses\n\ -c\tToggle display of command name/line\n\ -l\tToggle display of load average\n\ -m\tToggle display of memory information\n\ -t\tToggle display of summary information\n\ -n or #\tSet the number of process to show\n\ -u\tShow only a specific user\n\ -oO\tChange order of displayed fields\n\ -W\tWrite configuration file ~/.toprc\n\ -q\tQuit" - - /* Number of lines needed to display the header information. */ -static int header_lines; - -/* ############## Some Macro definitions for screen handling ######### */ - /* String to use in error messages. */ -#define PROGNAME "top" - /* Clear the screen. */ -#define clear_screen() \ - printf("%s", cl) - /* Show an error in the context of the spiffy full-screen display. */ -#define SHOWMESSAGE(x) do { \ - printf("%s%s%s%s", tgoto(cm, 0, header_lines-2), top_clrtoeol,md,mr); \ - printf x; \ - printf ("%s",me); \ - fflush(stdout); \ - sleep(2); \ - } while (0) +#endif /* _Itop */ +