2 * Top users/processes display for Unix
4 * This program may be freely redistributed,
5 * but this entire comment MUST remain intact.
7 * Copyright (c) 1984, 1989, William LeFebvre, Rice University
8 * Copyright (c) 1989 - 1994, William LeFebvre, Northwestern University
9 * Copyright (c) 1994, 1995, William LeFebvre, Argonne National Laboratory
10 * Copyright (c) 1996, William LeFebvre, Group sys Consulting
15 #include <sys/types.h>
17 #include <sys/cdefs.h>
18 #include <sys/limits.h>
19 #include <sys/resource.h>
20 #include <sys/select.h>
21 #include <sys/signal.h>
35 #include "display.h" /* interface to display package */
36 #include "screen.h" /* interface to screen package */
42 /* Size of the stdio buffer given to stdout */
43 #define Buffersize 2048
46 "Copyright (c) 1984 through 1996, William LeFebvre";
48 typedef void sigret_t;
50 /* The buffer that stdio will use */
51 static char stdoutbuf[Buffersize];
53 /* build Signal masks */
54 #define Smask(s) (1 << ((s) - 1))
57 static int fmt_flags = 0;
58 int pcpu_stats = false;
60 /* signal handling routines */
61 static sigret_t leave(int);
62 static sigret_t tstop(int);
63 static sigret_t top_winch(int);
65 static volatile sig_atomic_t leaveflag;
66 static volatile sig_atomic_t tstopflag;
67 static volatile sig_atomic_t winchflag;
69 /* values which need to be accessed by signal handlers */
70 static int max_topn; /* maximum displayable processes */
72 /* miscellaneous things */
73 struct process_select ps;
74 const char * myname = "top";
77 /* pointers to display routines */
78 static void (*d_loadave)(int mpid, double *avenrun) = i_loadave;
79 static void (*d_procstates)(int total, int *brkdn) = i_procstates;
80 static void (*d_cpustates)(int *states) = i_cpustates;
81 static void (*d_memory)(int *stats) = i_memory;
82 static void (*d_arc)(int *stats) = i_arc;
83 static void (*d_carc)(int *stats) = i_carc;
84 static void (*d_swap)(int *stats) = i_swap;
85 static void (*d_message)(void) = i_message;
86 static void (*d_header)(const char *text) = i_header;
87 static void (*d_process)(int line, char *thisline) = i_process;
89 static void reset_display(void);
91 static const struct option longopts[] = {
92 { "cpu-display-mode", no_argument, NULL, 'C' }, /* differs from orignal */
94 { "thread", no_argument, NULL, 'H' },
95 { "idle-procs", no_argument, NULL, 'I' },
96 { "jail", required_argument, NULL, 'J' },
97 { "per-cpu", no_argument, NULL, 'P' },
98 { "system-procs", no_argument, NULL, 'S' },
99 { "thread-id", no_argument, NULL, 'T' }, /* differs from orignal */
100 { "user", required_argument, NULL, 'U' },
101 { "all", no_argument, NULL, 'a' },
102 { "batch", no_argument, NULL, 'b' },
104 { "displays", required_argument, NULL, 'd' },
105 { "interactive", no_argument, NULL, 'i' },
106 { "jail-id", no_argument, NULL, 'j' },
107 { "display-mode", required_argument, NULL, 'm' },
108 /* n is identical to batch */
109 { "sort-order", required_argument, NULL, 'o' },
110 { "pid", required_argument, NULL, 'p' },
111 { "quick", no_argument, NULL, 'q' },
112 { "delay", required_argument, NULL, 's' },
113 { "threads", no_argument, NULL, 't' },
114 { "uids", no_argument, NULL, 'u' },
115 { "version", no_argument, NULL, 'v' },
116 { "swap", no_argument, NULL, 'w' },
117 { "system-idle-procs", no_argument, NULL, 'z' }
123 for (size_t i = 0; i < TOP_MAX_UIDS; ++i)
132 /* Add the uid if there's room */
133 for (; i < TOP_MAX_UIDS; ++i)
135 if (ps.uid[i] == -1 || ps.uid[i] == uid)
142 return (i == TOP_MAX_UIDS);
149 size_t where = TOP_MAX_UIDS;
151 /* Look for the user to remove - no problem if it's not there */
152 for (; i < TOP_MAX_UIDS; ++i)
156 if (ps.uid[i] == uid)
160 /* Make sure we don't leave a hole in the middle */
161 if (where != TOP_MAX_UIDS)
163 ps.uid[where] = ps.uid[i-1];
169 handle_user(char *buf, size_t buflen)
175 new_message(MT_standout, "Username to show (+ for all): ");
176 if (readline(buf, buflen, false) <= 0)
182 if (buf[0] == '+' || buf[0] == '-')
193 if ((uid = userid(buf2)) == -1)
195 new_message(MT_standout, " %s: unknown user", buf2);
211 new_message(MT_standout, " too many users, reset with '+'");
225 main(int argc, char *argv[])
230 struct system_info system_info;
231 struct statics statics;
234 static char tempbuf1[50];
235 static char tempbuf2[50];
236 int old_sigmask; /* only used for BSD-style signals */
239 int displays = 0; /* indicates unspecified */
242 char *(*get_userid)(int) = username;
243 const char *uname_field = "USERNAME";
244 const char *header_text;
246 const char **preset_argv;
250 bool dostates = false;
251 bool do_unames = true;
252 char interactive = 2;
254 char topn_specified = false;
257 struct timeval timeout;
258 char *order_name = NULL;
262 /* set the buffer for stdout */
265 debug = fopen("debug.run", "w");
266 setbuffer(stdout, NULL, 0);
268 setbuffer(stdout, stdoutbuf, Buffersize);
273 if ((myname = strrchr(argv[0], '/')) == 0)
286 /* initialize some selection options */
299 ps.thread_id = false;
301 /* get preset options from the environment */
302 if ((env_top = getenv("TOP")) != NULL)
304 av = preset_argv = argparse(env_top, &preset_argc);
307 /* set the dummy argument to an explanatory message, in case
308 getopt encounters a bad argument */
309 preset_argv[0] = "while processing environment";
312 /* process options */
314 /* if we're done doing the presets, then process the real arguments */
315 if (preset_argc == 0)
320 /* this should keep getopt happy... */
324 while ((i = getopt_long(ac, av, "CSIHPabijJ:nquvzs:d:U:m:o:p:Ttw", longopts, NULL)) != EOF)
328 case 'v': /* show version number */
329 fprintf(stderr, "%s: version FreeBSD\n", myname);
333 case 'u': /* toggle uid/username display */
334 do_unames = !do_unames;
337 case 'U': /* display only username's processes */
338 if ((ps.uid[0] = userid(optarg)) == -1)
340 fprintf(stderr, "%s: unknown user\n", optarg);
345 case 'S': /* show system processes */
349 case 'I': /* show idle processes */
353 case 'i': /* go interactive regardless */
357 case 'n': /* batch, or non-interactive */
363 fmt_flags ^= FMT_SHOWARGS;
366 case 'd': /* number of displays to show */
367 if ((i = atoiwi(optarg)) == Invalid || i == 0)
370 "%s: warning: display count should be positive -- option ignored\n",
380 unsigned long long num;
383 num = strtonum(optarg, 0, INT_MAX, &errstr);
384 if (errstr != NULL || !find_pid(num)) {
385 fprintf(stderr, "%s: unknown pid\n", optarg);
394 delay = strtod(optarg, NULL);
397 "%s: warning: seconds delay should be positive -- using default\n",
405 case 'q': /* be quick about it */
407 i = setpriority(PRIO_PROCESS, 0, PRIO_MIN);
408 if (i == -1 && errno != 0) {
410 "%s: warning: `-q' option failed (%m)\n", myname);
415 case 'm': /* select display mode */
416 if (strcmp(optarg, "io") == 0) {
417 displaymode = DISP_IO;
418 } else if (strcmp(optarg, "cpu") == 0) {
419 displaymode = DISP_CPU;
422 "%s: warning: `-m' option can only take args "
429 case 'o': /* select sort order */
442 ps.thread = !ps.thread;
446 ps.thread_id = !ps.thread_id;
453 case 'J': /* display only jail's processes */
454 if ((ps.jid = jail_getid(optarg)) == -1)
456 fprintf(stderr, "%s: unknown jail\n", optarg);
463 pcpu_stats = !pcpu_stats;
471 ps.kidle = !ps.kidle;
476 "Usage: %s [-abCHIijnPqStuvwz] [-d count] [-m io | cpu] [-o field] [-p pid]\n"
477 " [-s time] [-J jail] [-U username] [number]\n",
483 /* get count of top processes to display (if any) */
486 if ((topn = atoiwi(av[optind])) == Invalid)
489 "%s: warning: process display count should be non-negative -- using default\n",
495 topn_specified = true;
499 /* tricky: remember old value of preset_argc & set preset_argc = 0 */
503 /* repeat only if we really did the preset arguments */
506 /* set constants for username/uid display correctly */
509 uname_field = " UID ";
513 /* initialize the kernel memory interface */
514 if (machine_init(&statics) == -1)
519 /* determine sorting order index, if necessary */
520 if (order_name != NULL)
522 if ((order_index = string_index(order_name, statics.order_names)) == -1)
524 const char * const *pp;
526 fprintf(stderr, "%s: '%s' is not a recognized sorting order.\n",
528 fprintf(stderr, "\tTry one of these:");
529 pp = statics.order_names;
532 fprintf(stderr, " %s", *pp++);
539 /* initialize termcap */
540 init_termcap(interactive);
542 /* get the string to use for the process area header */
543 header_text = format_header(uname_field);
545 /* initialize display interface */
546 if ((max_topn = display_init(&statics)) == -1)
548 fprintf(stderr, "%s: can't allocate sufficient memory\n", myname);
552 /* print warning if user requested more processes than we can display */
556 "%s: warning: this terminal can only display %d processes.\n",
561 /* adjust for topn == Infinity */
562 if (topn == Infinity)
565 * For smart terminals, infinity really means everything that can
566 * be displayed, or Largest.
567 * On dumb terminals, infinity means every process in the system!
568 * We only really want to do that if it was explicitly specified.
569 * This is always the case when "Default_TOPN != Infinity". But if
570 * topn wasn't explicitly specified and we are on a dumb terminal
571 * and the default is Infinity, then (and only then) we use
572 * "Nominal_TOPN" instead.
574 topn = smart_terminal ? Largest :
575 (topn_specified ? Largest : Nominal_TOPN);
578 /* set header display accordingly */
579 display_header(topn > 0);
581 /* determine interactive state */
582 if (interactive == 2)
584 interactive = smart_terminal;
587 /* if # of displays not specified, fill it in */
590 displays = smart_terminal ? Infinity : 1;
593 /* hold interrupt signals while setting up the screen and the handlers */
594 old_sigmask = sigblock(Smask(SIGINT) | Smask(SIGQUIT) | Smask(SIGTSTP));
596 signal(SIGINT, leave);
597 signal(SIGQUIT, leave);
598 signal(SIGTSTP, tstop);
599 signal(SIGWINCH, top_winch);
600 sigsetmask(old_sigmask);
603 fputs("....", stderr);
612 * main loop -- repeat while display count is positive or while it
613 * indicates infinity (by being -1)
616 while ((displays == -1) || (displays-- > 0))
618 int (*compare)(const void * const, const void * const);
621 /* get the current stats */
622 get_system_info(&system_info);
624 compare = compares[order_index];
626 /* get the current set of processes */
628 get_process_info(&system_info, &ps, compare);
630 /* display the load averages */
631 (*d_loadave)(system_info.last_pid,
632 system_info.load_avg);
634 /* display the current time */
635 /* this method of getting the time SHOULD be fairly portable */
637 i_uptime(&system_info.boottime, &curr_time);
638 i_timeofday(&curr_time);
640 /* display process state breakdown */
641 (*d_procstates)(system_info.p_total,
642 system_info.procstates);
644 /* display the cpu state percentage breakdown */
645 if (dostates) /* but not the first time */
647 (*d_cpustates)(system_info.cpustates);
651 /* we'll do it next time */
663 /* display memory stats */
664 (*d_memory)(system_info.memory);
665 (*d_arc)(system_info.arc);
666 (*d_carc)(system_info.carc);
668 /* display swap stats */
669 (*d_swap)(system_info.swap);
671 /* handle message area */
674 /* update the header area */
675 (*d_header)(header_text);
679 /* determine number of processes to actually display */
680 /* this number will be the smallest of: active processes,
681 number user requested, number current screen accomodates */
682 active_procs = system_info.p_pactive;
683 if (active_procs > topn)
687 if (active_procs > max_topn)
689 active_procs = max_topn;
692 /* now show the top "n" processes. */
693 for (i = 0; i < active_procs; i++)
695 (*d_process)(i, format_next_process(processes, get_userid,
704 /* do end-screen processing */
707 /* now, flush the output buffer */
708 if (fflush(stdout) != 0)
710 new_message(MT_standout, " Write error on stdout");
715 /* only do the rest if we have more displays to show */
718 /* switch out for new display on smart terminals */
727 d_loadave = u_loadave;
728 d_procstates = u_procstates;
729 d_cpustates = u_cpustates;
734 d_message = u_message;
736 d_process = u_process;
749 else while (no_command)
751 /* assume valid command unless told otherwise */
754 /* set up arguments for select with timeout */
756 FD_SET(0, &readfds); /* for standard input */
757 timeout.tv_sec = delay;
766 /* move to the lower left */
770 /* default the signal handler action */
771 signal(SIGTSTP, SIG_DFL);
773 /* unblock the signal and send ourselves one */
774 sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
777 /* reset the signal handler */
778 signal(SIGTSTP, tstop);
788 /* reascertain the screen dimensions */
791 /* tell display to resize */
792 max_topn = display_resize();
794 /* reset the signal handler */
795 signal(SIGWINCH, top_winch);
802 /* wait for either input or the end of the delay period */
803 sel_ret = select(2, &readfds, NULL, NULL, &timeout);
804 if (sel_ret < 0 && errno != EINTR)
810 const struct command *cptr;
812 /* something to read -- clear the message area first */
815 /* now read it and convert to command strchr */
816 /* (use "change" as a temporary to hold strchr) */
817 if (read(0, &ch, 1) != 1)
819 /* read error: either 0 or -1 */
820 new_message(MT_standout, " Read error on stdin");
824 if (ch == '\r' || ch == '\n') {
828 while (cptr->c != '\0') {
834 if (cptr->c == '\0') {
835 new_message(MT_standout, " Command not understood");
839 if (overstrike && !cptr->available_to_dumb)
841 new_message(MT_standout,
842 " Command cannot be handled by this terminal");
849 case CMD_redraw: /* redraw screen */
853 case CMD_update: /* merely update display */
854 /* is the load average high? */
855 if (system_info.load_avg[0] > LoadMax)
857 /* yes, go home for visual feedback */
871 top_standout("Hit any key to continue: ");
876 case CMD_errors: /* show errors */
877 if (error_count() == 0)
879 new_message(MT_standout,
880 " Currently no errors to report.");
889 top_standout("Hit any key to continue: ");
896 new_message(MT_standout,
897 "Number of processes to show: ");
898 newval = readline(tempbuf1, 8, true);
901 if (newval > max_topn)
903 new_message(MT_standout | MT_delayed,
904 " This terminal can only display %d processes.",
911 /* inhibit the header */
912 display_header(false);
914 else if (newval > topn && topn == 0)
916 /* redraw the header */
917 display_header(true);
924 case CMD_delay: /* new seconds delay */
925 new_message(MT_standout, "Seconds to delay: ");
926 if ((i = readline(tempbuf1, 8, true)) > -1)
928 if ((delay = i) == 0)
936 case CMD_displays: /* change display count */
937 new_message(MT_standout,
938 "Displays to show (currently %s): ",
939 displays == -1 ? "infinite" :
941 if ((i = readline(tempbuf1, 10, true)) > 0)
952 case CMD_kill: /* kill program */
953 new_message(0, "kill ");
954 if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
956 if ((errmsg = kill_procs(tempbuf2)) != NULL)
958 new_message(MT_standout, "%s", errmsg);
969 case CMD_renice: /* renice program */
970 new_message(0, "renice ");
971 if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
973 if ((errmsg = renice_procs(tempbuf2)) != NULL)
975 new_message(MT_standout, "%s", errmsg);
988 new_message(MT_standout | MT_delayed,
989 " %sisplaying idle processes.",
990 ps.idle ? "D" : "Not d");
996 new_message(MT_standout | MT_delayed,
997 " %sisplaying self.",
998 (ps.self) ? "D" : "Not d");
1003 if (handle_user(tempbuf2, sizeof(tempbuf2)))
1008 ps.thread = !ps.thread;
1009 new_message(MT_standout | MT_delayed,
1010 " Displaying threads %s",
1011 ps.thread ? "separately" : "as a count");
1012 header_text = format_header(uname_field);
1018 ps.thread_id = !ps.thread_id;
1019 new_message(MT_standout | MT_delayed,
1021 ps.thread_id ? "tid" : "pid");
1022 header_text = format_header(uname_field);
1029 new_message(MT_standout | MT_delayed,
1030 " Displaying %s CPU",
1031 ps.wcpu ? "weighted" : "raw");
1032 header_text = format_header(uname_field);
1037 displaymode = displaymode == DISP_IO ? DISP_CPU : DISP_IO;
1038 header_text = format_header(uname_field);
1039 display_header(true);
1040 d_header = i_header;
1044 ps.system = !ps.system;
1047 fmt_flags ^= FMT_SHOWARGS;
1050 new_message(MT_standout,
1052 if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
1054 if ((i = string_index(tempbuf2, statics.order_names)) == -1)
1056 new_message(MT_standout,
1057 " %s: unrecognized sorting order", tempbuf2);
1073 new_message(MT_standout | MT_delayed,
1074 " %sisplaying jail ID.",
1075 ps.jail ? "D" : "Not d");
1076 header_text = format_header(uname_field);
1082 new_message(MT_standout,
1083 "Jail to show (+ for all): ");
1084 if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
1086 if (tempbuf2[0] == '+' &&
1087 tempbuf2[1] == '\0')
1091 else if ((i = jail_getid(tempbuf2)) == -1)
1093 new_message(MT_standout,
1094 " %s: unknown jail", tempbuf2);
1103 new_message(MT_standout |
1104 MT_delayed, " Displaying jail "
1107 format_header(uname_field);
1119 ps.kidle = !ps.kidle;
1120 new_message(MT_standout | MT_delayed,
1121 " %sisplaying system idle process.",
1122 ps.kidle ? "D" : "Not d");
1126 pcpu_stats = !pcpu_stats;
1127 new_message(MT_standout | MT_delayed,
1128 " Displaying %sCPU statistics.",
1129 pcpu_stats ? "per-" : "global ");
1131 max_topn = display_updatecpus(&statics);
1137 new_message(MT_standout | MT_delayed,
1138 " %sisplaying per-process swap usage.",
1139 ps.swap ? "D" : "Not d");
1140 header_text = format_header(uname_field);
1145 new_message(MT_standout,
1146 "Process id to show (+ for all): ");
1147 if (readline(tempbuf2, sizeof(tempbuf2), false) > 0) {
1148 if (tempbuf2[0] == '+' &&
1149 tempbuf2[1] == '\0') {
1152 unsigned long long num;
1155 num = strtonum(tempbuf2, 0, INT_MAX,
1157 if (errstr != NULL || !find_pid(num)) {
1158 new_message(MT_standout,
1163 ps.pid = (pid_t)num;
1171 assert("reached switch without command");
1176 /* flush out stuff that may have been written */
1189 * reset_display() - reset all the display routine pointers so that entire
1190 * screen will get redrawn.
1196 d_loadave = i_loadave;
1197 d_procstates = i_procstates;
1198 d_cpustates = i_cpustates;
1199 d_memory = i_memory;
1203 d_message = i_message;
1204 d_header = i_header;
1205 d_process = i_process;
1213 leave(int i __unused) /* exit under normal conditions -- INT handler */
1220 tstop(int i __unused) /* SIGTSTP handler */
1227 top_winch(int i __unused) /* SIGWINCH handler */
1234 quit(int status) /* exit under duress */