|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T t
Length: 26262 (0x6696) Types: TextFile Names: »top.c«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki └─⟦this⟧ »EUUGD11/euug-87hel/sec8/top/top.c«
char *copyright = "Top, version 2.0, copyright (c) 1984, 1986, William LeFebvre"; /* * Top users display for Berkeley Unix * Version 2.0 * * This program may be freely redistributed to other Unix sites, but this * entire comment MUST remain intact. * * Copyright (c) 1984, 1986, William LeFebvre, Rice University * * This program is designed to run on either Berkeley 4.1 or 4.2 Unix. * Compile with the preprocessor constant "FOUR_ONE" set to get an * executable that will run on Berkeley 4.1 Unix. * * The Sun kernel uses scaled integers instead of floating point. * Compilation with the preprocessor variable "sun" gets an executable * that will run on Sun Unix version 1.1 or later ("sun" is automatically * set by the Sun C compiler). * * The Pyramid splits the stack size (p_ssize) into control stack and user * stack sizes. Compilation with the preprocessor variable "pyr" gets an * executable that will run on Pyramids ("pyr" is automatically set by the * Pyramid C compiler). * * See the file "Changes" for more information on version-to-version changes. */ #include <stdio.h> #include <pwd.h> #include <nlist.h> #include <signal.h> #include <setjmp.h> #include <sys/param.h> #include <sys/dir.h> #include <sys/user.h> #include <sys/proc.h> #include <sys/dk.h> #include <sys/vm.h> /* includes specific to top */ #include "layout.h" #include "screen.h" /* interface to screen package */ #include "top.h" #include "top.local.h" #include "boolean.h" /* Special atoi routine returns either a non-negative number or one of: */ #define Infinity -1 #define Invalid -2 /* Size of the stdio buffer given to stdout */ #define Buffersize 2048 /* The buffer the stdio will use */ char stdoutbuf[Buffersize]; /* wish list for kernel symbols */ struct nlist nlst[] = { { "_avenrun" }, #define X_AVENRUN 0 { "_ccpu" }, #define X_CCPU 1 { "_cp_time" }, #define X_CP_TIME 2 { "_hz" }, #define X_HZ 3 { "_mpid" }, #define X_MPID 4 { "_nproc" }, #define X_NPROC 5 { "_proc" }, #define X_PROC 6 { "_total" }, #define X_TOTAL 7 { 0 }, }; /* build Signal masks */ #define Smask(s) (1 << ((s) - 1)) /* for system errors */ extern int errno; /* for getopt: */ extern int optind; extern char *optarg; /* signal handling routines */ int leave(); int onalrm(); int tstop(); int nproc; int mpid; /* kernel "hz" variable -- clock rate */ long hz; /* All this is to calculate the cpu state percentages */ long cp_time[CPUSTATES]; long cp_old[CPUSTATES]; long cp_change[CPUSTATES]; long total_change; long mpid_offset; long avenrun_offset; long cp_time_offset; long total_offset; #ifdef sun long ccpu; long avenrun[3]; #else double ccpu; double avenrun[3]; #endif double logcpu; struct vmtotal total; struct proc *proc; struct proc *pbase; int bytes; int initialized = No; char *myname = "top"; jmp_buf jmp_int; char input_waiting = No; /* routines that don't return int */ struct passwd *getpwent(); char *username(); char *itoa(); char *ctime(); char *rindex(); char *kill_procs(); char *renice_procs(); int proc_compar(); long time(); /* different routines for displaying the user's identification */ /* (values assigned to get_userid) */ char *username(); char *itoa7(); /* display routines that need to be predeclared */ int i_loadave(); int u_loadave(); int i_procstates(); int u_procstates(); int i_cpustates(); int u_cpustates(); int i_memory(); int u_memory(); int i_header(); int u_header(); int i_process(); int u_process(); /* pointers to display routines */ int (*d_loadave)() = i_loadave; int (*d_procstates)() = i_procstates; int (*d_cpustates)() = i_cpustates; int (*d_memory)() = i_memory; int (*d_header)() = i_header; int (*d_process)() = i_process; /* buffer of proc information lines for display updating */ /* unfortunate that this must be declared globally */ char (* screenbuf)[Display_width]; main(argc, argv) int argc; char *argv[]; { register struct proc *pp; register struct proc **prefp; register int i; register int active_procs; register int change; static struct proc **pref; static char tempbuf1[50]; static char tempbuf2[50]; int total_procs; int old_sigmask; int proc_brkdn[7]; int topn = Default_TOPN; int delay = Default_DELAY; int displays = 0; /* indicates unspecified */ long curr_time; char *(*get_userid)() = username; char *uname_field = "USERNAME"; #ifndef FOUR_ONE char ch; char msg_showing = 0; int readfds; struct timeval timeout; #endif char dostates = No; char do_unames = Yes; char do_init = No; char interactive = Maybe; char show_sysprocs = No; char topn_specified = No; char warnings = 0; /* set the buffer for stdout */ setbuffer(stdout, stdoutbuf, Buffersize); /* get our name */ if (argc > 0) { if ((myname = rindex(argv[0], '/')) == 0) { myname = argv[0]; } else { myname++; } } /* process options */ while ((i = getopt(argc, argv, "Sbinus:d:")) != EOF) { switch(i) { case 'u': /* display uid instead of name */ do_unames = No; uname_field = " UID "; get_userid = itoa7; break; case 'S': /* show system processes */ show_sysprocs = Yes; break; case 'i': /* go interactive regardless */ interactive = Yes; break; case 'n': /* batch, or non-interactive */ case 'b': interactive = No; break; case 'd': /* number of displays to show */ if ((i = atoiwi(optarg)) == Invalid || i == 0) { fprintf(stderr, "%s: warning: display count should be positive -- option ignored\n", myname); warnings++; } else { displays = i; } break; case 's': if ((delay = atoi(optarg)) < 0) { fprintf(stderr, "%s: warning: seconds delay should be non-negative -- using default\n", myname); delay = Default_DELAY; warnings++; } break; default: fprintf(stderr, "Usage: %s [-Sbinu] [-d x] [-s x] [number]\n", myname); exit(1); } } /* get count of top processes to display (if any) */ if (optind < argc) { if ((topn = atoiwi(argv[optind])) == Invalid) { fprintf(stderr, "%s: warning: process display count should be non-negative -- using default\n", myname); topn = Default_TOPN; warnings++; } else { topn_specified = Yes; } } /* initialize the kernel memory interface */ init_kernel(); if (initialized != 1) { /* get the list of symbols we want to access in the kernel */ /* errno = 0; ??? */ nlist(VMUNIX, nlst); if (nlst[0].n_type == 0) { fprintf(stderr, "%s: can't nlist image\n", VMUNIX); exit(2); } /* get the symbol values out of kmem */ getkval(nlst[X_PROC].n_value, &proc, sizeof(int), nlst[X_PROC].n_name); getkval(nlst[X_NPROC].n_value, &nproc, sizeof(int), nlst[X_NPROC].n_name); getkval(nlst[X_HZ].n_value, &hz, sizeof(int), nlst[X_HZ].n_name); getkval(nlst[X_CCPU].n_value, &ccpu, sizeof(int), nlst[X_CCPU].n_name); /* some calculations we use later */ mpid_offset = nlst[X_MPID].n_value; avenrun_offset = nlst[X_AVENRUN].n_value; cp_time_offset = nlst[X_CP_TIME].n_value; total_offset = nlst[X_TOTAL].n_value; /* this is used in calculating WCPU -- calculate it ahead of time */ #ifdef sun logcpu = log((double)ccpu / FSCALE); #else logcpu = log(ccpu); #endif /* allocate space for proc structure array and array of pointers */ bytes = nproc * sizeof(struct proc); pbase = (struct proc *)sbrk(bytes); pref = (struct proc **)sbrk(nproc * sizeof(struct proc *)); /* Just in case ... */ if (pbase == (struct proc *)NULL || pref == (struct proc **)NULL) { fprintf(stderr, "%s: can't allocate sufficient memory\n", myname); exit(3); } /* initialize the hashing stuff */ if (do_unames) { init_hash(); } if (do_init) { initialized = 1; kill(0, SIGQUIT); exit(99); } } /* initialize termcap */ init_termcap(); /* * Smart terminals can only display so many processes, precisely * "screen_length - Header_lines". When run on dumb terminals, nothing * fancy is done anyway, so we can display as many processes as the * system can make. But since we never need to remember what is on the * screen, we only allocate a buffer for one screen line. */ if (smart_terminal) { /* can only display (screen_length - Header_lines) processes */ i = screen_length - Header_lines; if (topn > i) /* false even when topn == Infinity */ { fprintf(stderr, "%s: warning: this terminal can only display %d processes.\n", myname, screen_length - Header_lines); topn = i; warnings++; } } else { i = 1; screen_length = nproc + Header_lines; } /* allocate space for the screen buffer */ screenbuf = (char (*)[])sbrk(i * Display_width); if (screenbuf == (char (*)[])NULL) { fprintf(stderr, "%s: can't allocate sufficient memory\n", myname); exit(4); } /* adjust for topn == Infinity */ if (topn == Infinity) { /* * For smart terminals, infinity really means everything that can * be displayed (which just happens to be "i" at this point). * On dumb terminals, infinity means every process in the system! * We only really want to do that if it was explicitly specified. * This is always the case when "Default_TOPN != Infinity". But if * topn wasn't explicitly specified and we are on a dumb terminal * and the default is Infinity, then (and only then) we use * "Nominal_TOPN" instead. */ #if Default_TOPN == Infinity topn = smart_terminal ? i : (topn_specified ? nproc : Nominal_TOPN); #else topn = smart_terminal ? i : nproc; #endif } /* determine interactive state */ if (interactive == Maybe) { interactive = smart_terminal; } /* if # of displays not specified, fill it in */ if (displays == 0) { displays = smart_terminal ? Infinity : 1; } /* hold interrupt signals while setting up the screen and the handlers */ #ifndef FOUR_ONE old_sigmask = sigblock(Smask(SIGINT) | Smask(SIGQUIT) | Smask(SIGTSTP)); #endif init_screen(); signal(SIGINT, leave); signal(SIGQUIT, leave); signal(SIGTSTP, tstop); #ifndef FOUR_ONE sigsetmask(old_sigmask); #endif if (warnings) { fprintf(stderr, "...."); fflush(stderr); /* why must I do this? */ sleep(3 * warnings); } clear(); /* setup the jump buffer for stops */ if (setjmp(jmp_int) != 0) { /* control ends up here after an interrupt */ clear(); reset_display(); } /* * main loop -- repeat while display count is positive or while it * indicates infinity (by being -1) */ while ((displays == -1) || (displays-- > 0)) { /* read all the proc structures in one fell swoop */ getkval(proc, pbase, bytes, "proc array"); /* get the cp_time array */ getkval(cp_time_offset, cp_time, sizeof(cp_time), "_cp_time"); /* get load average array */ getkval(avenrun_offset, avenrun, sizeof(avenrun), "_avenrun"); /* get mpid -- process id of last process */ getkval(mpid_offset, &mpid, sizeof(mpid), "_mpid"); /* get total -- systemwide main memory usage structure */ getkval(total_offset, &total, sizeof(total), "_total"); /* count up process states and get pointers to interesting procs */ total_procs = 0; active_procs = 0; bzero(proc_brkdn, sizeof(proc_brkdn)); prefp = pref; for (pp = pbase, i = 0; i < nproc; pp++, i++) { /* * Place pointers to each valid proc structure in pref[]. * Process slots that are actually in use have a non-zero * status field. Processes with SSYS set are system * processes---these get ignored unless show_sysprocs is set. */ if (pp->p_stat != 0 && (show_sysprocs || ((pp->p_flag & SSYS) == 0))) { total_procs++; proc_brkdn[pp->p_stat]++; if (pp->p_stat != SZOMB) { *prefp++ = pp; active_procs++; } } } /* display the load averages */ (*d_loadave)(mpid, avenrun); /* * Display the current time. * "ctime" always returns a string that looks like this: * * Sun Sep 16 01:03:52 1973 * 012345678901234567890123 * 1 2 * * We want indices 11 thru 18 (length 8). */ curr_time = time(0); if (smart_terminal) { Move_to(screen_width - 8, 0); } else { fputs(" ", stdout); } printf("%-8.8s\n", &(ctime(&curr_time)[11])); /* display process state breakdown */ (*d_procstates)(total_procs, proc_brkdn); /* calculate percentage time in each cpu state */ if (dostates) /* but not the first time */ { total_change = 0; for (i = 0; i < CPUSTATES; i++) { /* calculate changes for each state and overall change */ if (cp_time[i] < cp_old[i]) { /* this only happens when the counter wraps */ change = (int) ((unsigned long)cp_time[i]-(unsigned long)cp_old[i]); } else { change = cp_time[i] - cp_old[i]; } total_change += (cp_change[i] = change); cp_old[i] = cp_time[i]; } (*d_cpustates)(cp_change, total_change); } else { /* we'll do it next time */ if (smart_terminal) { z_cpustates(); } else { putchar('\n'); } dostates = Yes; bzero(cp_old, sizeof(cp_old)); } /* display main memory statistics */ (*d_memory)( pagetok(total.t_rm), pagetok(total.t_arm), pagetok(total.t_vm), pagetok(total.t_avm), pagetok(total.t_free)); i = 0; if (topn > 0) { /* update the header area */ (*d_header)(uname_field); /* sort by cpu percentage (pctcpu) */ qsort(pref, active_procs, sizeof(struct proc *), proc_compar); /* adjust for a lack of processes */ if (active_procs > topn) { active_procs = topn; } /* * Now, show the top "n" processes. The method is slightly * different for dumb terminals, so we will just use two very * similar loops; this increases speed but also code size. */ if (smart_terminal) { for (prefp = pref, i = 0; i < active_procs; prefp++, i++) { pp = *prefp; (*d_process)(i, pp, get_userid); } } else for (prefp = pref, i = 0; i < active_procs; prefp++, i++) { pp = *prefp; /* (only one buffer lien with dumb terminals) */ (*d_process)(0, pp, get_userid); } } /* do end-screen processing */ u_endscreen(i); /* now, flush the output buffer */ fflush(stdout); /* only do the rest if we have more displays to show */ if (displays) { /* switch out for new display on smart terminals */ if (smart_terminal) { d_loadave = u_loadave; d_procstates = u_procstates; d_cpustates = u_cpustates; d_memory = u_memory; d_header = u_header; d_process = u_process; } #ifndef FOUR_ONE if (!interactive) #endif { /* set up alarm */ signal(SIGALRM, onalrm); alarm(delay); /* wait for the rest of it .... */ pause(); } #ifndef FOUR_ONE else { /* wait for either input or the end of the delay period */ readfds = 1; /* for standard input */ timeout.tv_sec = delay; timeout.tv_usec = 0; if (select(32, &readfds, 0, 0, &timeout) > 0) { int newval; char *errmsg; /* something to read -- clear the message area first */ if (msg_showing) { if (smart_terminal) { putcap(clear_line); } msg_showing = No; } /* now read it and act on it */ read(0, &ch, 1); switch(ch) { case '\f': /* redraw screen */ reset_display(); clear(); break; case ' ': /* merely update display */ break; case 'q': /* quit */ quit(0); break; case 'h': /* help */ case '?': reset_display(); clear(); show_help(); standout("Hit any key to continue: "); fflush(stdout); read(0, &ch, 1); clear(); break; case 'e': /* show errors */ if (error_count() == 0) { standout(" Currently no errors to report."); msg_showing = Yes; } else { reset_display(); clear(); show_errors(); standout("Hit any key to continue: "); fflush(stdout); read(0, &ch, 1); clear(); } break; case 'n': /* new number */ case '#': standout("Number of processes to show: "); newval = readline(tempbuf1, 8, Yes); putchar('\r'); if (newval > -1) { if (newval > (i = screen_length - Header_lines)) { standout( " This terminal can only display %d processes.", i); newval = i; msg_showing = Yes; break; } if (newval > topn) { /* zero fill appropriate part of screenbuf */ bzero(screenbuf[topn], (newval - topn) * Display_width); /* redraw header if need be */ if (topn == 0) { d_header = i_header; } } topn = newval; } putcap(clear_line); break; case 's': /* new seconds delay */ standout("Seconds to delay: "); if ((i = readline(tempbuf1, 8, Yes)) > -1) { delay = i; } putchar('\r'); putcap(clear_line); break; case 'd': /* change display count */ standout("Displays to show (currently %s): ", displays == -1 ? "infinite" : itoa(displays)); if ((i = readline(tempbuf1, 10, Yes)) > 0) { displays = i; } else if (i == 0) { quit(0); } putchar('\r'); putcap(clear_line); break; case 'k': /* kill program */ fputs("kill ", stdout); if (readline(tempbuf2, sizeof(tempbuf2), No) > 0) { if ((errmsg = kill_procs(tempbuf2)) != NULL) { putchar('\r'); standout(errmsg); } msg_showing = Yes; } else { putchar('\r'); } putcap(clear_line); break; case 'r': /* renice program */ fputs("renice ", stdout); if (readline(tempbuf2, sizeof(tempbuf2), No) > 0) { if ((errmsg = renice_procs(tempbuf2)) != NULL) { putchar('\r'); standout(errmsg); msg_showing = Yes; } } else { putchar('\r'); } putcap(clear_line); break; default: standout(" Command not understood"); msg_showing = Yes; } } else if (msg_showing) { if (smart_terminal) { putcap(clear_line); } msg_showing = No; } } #endif } } quit(0); } /* * reset_display() - reset all the display routine pointers so that entire * screen will get redrawn. */ reset_display() { d_loadave = i_loadave; d_procstates = i_procstates; d_cpustates = i_cpustates; d_memory = i_memory; d_header = i_header; d_process = i_process; } readline(buffer, size, numeric) char *buffer; int size; int numeric; { register char *ptr = buffer; register char ch; register char cnt = 0; size -= 1; while ((fflush(stdout), read(0, ptr, 1) > 0)) { if ((ch = *ptr) == '\n') { break; } if (ch == ch_kill) { *buffer = '\0'; return(-1); } else if (ch == ch_erase) { if (cnt <= 0) { putchar('\7'); } else { fputs("\b \b", stdout); ptr--; cnt--; } } else if (cnt == size || (numeric && (ch < '0' || ch > '9'))) { putchar('\7'); } else { putchar(ch); ptr++; cnt++; } } *ptr = '\0'; return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt); } /* * signal handlers */ leave() /* exit under normal conditions -- INT handler */ { end_screen(); exit(0); } tstop() { /* move to the lower left */ end_screen(); fflush(stdout); #ifdef FOUR_ONE /* a 4.1 system */ /* send a STOP (uncatchable) to everyone in the process group */ kill(0, SIGSTOP); /* reset the signal handler */ signal(SIGTSTP, tstop); #else /* assume it is a 4.2 system */ /* default the signal handler action */ signal(SIGTSTP, SIG_DFL); /* unblock the signal and send ourselves one */ sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1))); kill(0, SIGTSTP); /* reset the signal handler */ signal(SIGTSTP, tstop); #endif /* reinit screen */ reinit_screen(); /* jump to appropriate place */ longjmp(jmp_int, 1); /*NOTREACHED*/ } quit(status) /* exit under duress */ int status; { end_screen(); exit(status); } onalrm() { return(0); } /* * proc_compar - comparison function for "qsort" * Compares the resource consumption of two processes using five * distinct keys. The keys (in descending order of importance) are: * percent cpu, cpu ticks, state, resident set size, total virtual * memory usage. The process states are ordered as follows (from least * to most important): WAIT, zombie, sleep, stop, start, run. The * array declaration below maps a process state index into a number * that reflects this ordering. */ unsigned char sorted_state[] = { 0, /* not used */ 3, /* sleep */ 1, /* ABANDONED (WAIT) */ 6, /* run */ 5, /* start */ 2, /* zombie */ 4 /* stop */ }; proc_compar(pp1, pp2) struct proc **pp1; struct proc **pp2; { register struct proc *p1; register struct proc *p2; register int result; #ifndef sun register double dresult; #endif /* remove one level of indirection */ p1 = *pp1; p2 = *pp2; /* compare percent cpu (pctcpu) */ #ifdef sun if ((result = p2->p_pctcpu - p1->p_pctcpu) == 0) #else if ((dresult = p2->p_pctcpu - p1->p_pctcpu) == 0) #endif { /* use cpticks to break the tie */ if ((result = p2->p_cpticks - p1->p_cpticks) == 0) { /* use process state to break the tie */ if ((result = sorted_state[p2->p_stat] - sorted_state[p1->p_stat]) == 0) { /* use priority to break the tie */ if ((result = p2->p_pri - p1->p_pri) == 0) { /* use resident set size (rssize) to break the tie */ if ((result = p2->p_rssize - p1->p_rssize) == 0) { /* use total memory to break the tie */ #ifdef pyr result = (p2->p_tsize + p2->p_dsize + p2->p_ussize) - (p1->p_tsize + p1->p_dsize + p1->p_ussize); #else result = (p2->p_tsize + p2->p_dsize + p2->p_ssize) - (p1->p_tsize + p1->p_dsize + p1->p_ssize); #endif } } } } } #ifndef sun else { result = dresult < 0.0 ? -1 : 1; } #endif return(result); } /* routines to translate uids into a string */ char *user_name(euid, ruid) int euid, ruid; { return(username(euid)); } char *user_uid(euid, ruid) int euid, ruid; { return(itoa7(euid)); } /* * These routines handle uid to username mapping. * They use a hashing table scheme to reduce reading overhead. */ struct hash_el { int uid; char name[8]; }; #define H_empty -1 /* simple minded hashing function */ #define hashit(i) ((i) % Table_size) struct hash_el hash_table[Table_size]; init_hash() { register int i; register struct hash_el *h; for (h = hash_table, i = 0; i < Table_size; h++, i++) { h->uid = H_empty; } } char *username(uid) register int uid; { register int index; register int found; register char *name; /* This is incredibly naive, but it'll probably get changed anyway */ index = hashit(uid); while ((found = hash_table[index].uid) != uid) { if (found == H_empty) { /* not here -- get it out of passwd */ index = get_user(uid); break; /* out of while */ } index = (index + 1) % Table_size; } return(hash_table[index].name); } enter_user(uid, name) register int uid; register char *name; { register int length; register int index; register int try; static int uid_count = 0; /* avoid table overflow -- insure at least one empty slot */ if (++uid_count >= Table_size) { fprintf(stderr, "table overflow: too many users\n"); quit(10); } index = hashit(uid); while ((try = hash_table[index].uid) != H_empty) { if (try == uid) { return(index); } index = (index + 1) % Table_size; } hash_table[index].uid = uid; strncpy(hash_table[index].name, name, 8); return(index); } get_user(uid) register int uid; { struct passwd *pwd; register int last_index; while ((pwd = getpwent()) != NULL) { last_index = enter_user(pwd->pw_uid, pwd->pw_name); if (pwd->pw_uid == uid) { return(last_index); } } return(enter_user(uid, itoa7(uid))); } atoiwi(str) char *str; { register int len; len = strlen(str); if (len != 0) { if (strncmp(str, "infinity", len) == 0 || strncmp(str, "all", len) == 0 || strncmp(str, "maximum", len) == 0) { return(Infinity); } else if (str[0] == '-') { return(Invalid); } else { return(atoi(str)); } } return(0); } /* * itoa - convert integer (decimal) to ascii string for positive numbers * only (we don't bother with negative numbers since we know we * don't use them). */ static char buffer[16]; /* shared by the next two routines */ char *itoa(val) register int val; { register char *ptr; ptr = buffer + sizeof(buffer); *--ptr = '\0'; if (val == 0) { *--ptr = '0'; } else while (val != 0) { *--ptr = (val % 10) + '0'; val /= 10; } return(ptr); } /* * itoa7(val) - like itoa, except the number is right justified in a 7 * character field. This code is a duplication of itoa instead of * a front end to a more general routine for efficiency. */ char *itoa7(val) register int val; { register char *ptr; ptr = buffer + sizeof(buffer); *--ptr = '\0'; if (val == 0) { *--ptr = '0'; } else while (val != 0) { *--ptr = (val % 10) + '0'; val /= 10; } while (ptr > buffer + sizeof(buffer) - 7) { *--ptr = ' '; } return(ptr); }