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
13 #include <sys/types.h>
15 #include <sys/cdefs.h>
16 #include <sys/limits.h>
17 #include <sys/resource.h>
18 #include <sys/select.h>
19 #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 static int fmt_flags
= 0;
54 int show_args
= false;
55 int pcpu_stats
= false;
57 /* signal handling routines */
58 static sigret_t
leave(int);
59 static sigret_t
tstop(int);
60 static sigret_t
top_winch(int);
62 static volatile sig_atomic_t leaveflag
;
63 static volatile sig_atomic_t tstopflag
;
64 static volatile sig_atomic_t winchflag
;
66 /* values which need to be accessed by signal handlers */
67 static int max_topn
; /* maximum displayable processes */
69 /* miscellaneous things */
70 struct process_select ps
;
73 /* pointers to display routines */
74 static void (*d_loadave
)(int mpid
, double *avenrun
) = i_loadave
;
75 static void (*d_procstates
)(int total
, int *brkdn
) = i_procstates
;
76 static void (*d_cpustates
)(int *states
) = i_cpustates
;
77 static void (*d_memory
)(int *stats
) = i_memory
;
78 static void (*d_arc
)(int *stats
) = i_arc
;
79 static void (*d_carc
)(int *stats
) = i_carc
;
80 static void (*d_swap
)(int *stats
) = i_swap
;
81 static void (*d_message
)(void) = i_message
;
82 static void (*d_header
)(const char *text
) = i_header
;
83 static void (*d_process
)(int line
, char *thisline
) = i_process
;
85 static void reset_display(void);
87 static const struct option longopts
[] = {
88 { "cpu-display-mode", no_argument
, NULL
, 'C' }, /* differs from orignal */
90 { "thread", no_argument
, NULL
, 'H' },
91 { "idle-procs", no_argument
, NULL
, 'I' },
92 { "jail", required_argument
, NULL
, 'J' },
93 { "per-cpu", no_argument
, NULL
, 'P' },
94 { "system-procs", no_argument
, NULL
, 'S' },
95 { "thread-id", no_argument
, NULL
, 'T' }, /* differs from orignal */
96 { "user", required_argument
, NULL
, 'U' },
97 { "all", no_argument
, NULL
, 'a' },
98 { "batch", no_argument
, NULL
, 'b' },
100 { "displays", required_argument
, NULL
, 'd' },
101 { "interactive", no_argument
, NULL
, 'i' },
102 { "jail-id", no_argument
, NULL
, 'j' },
103 { "display-mode", required_argument
, NULL
, 'm' },
104 /* n is identical to batch */
105 { "sort-order", required_argument
, NULL
, 'o' },
106 { "pid", required_argument
, NULL
, 'p' },
107 { "quick", no_argument
, NULL
, 'q' },
108 { "delay", required_argument
, NULL
, 's' },
109 { "threads", no_argument
, NULL
, 't' },
110 { "uids", no_argument
, NULL
, 'u' },
111 { "version", no_argument
, NULL
, 'v' },
112 { "swap", no_argument
, NULL
, 'w' },
113 { "system-idle-procs", no_argument
, NULL
, 'z' },
120 for (size_t i
= 0; i
< TOP_MAX_UIDS
; ++i
)
129 /* Add the uid if there's room */
130 for (; i
< TOP_MAX_UIDS
; ++i
)
132 if (ps
.uid
[i
] == -1 || ps
.uid
[i
] == uid
)
139 return (i
== TOP_MAX_UIDS
);
146 size_t where
= TOP_MAX_UIDS
;
148 /* Look for the user to remove - no problem if it's not there */
149 for (; i
< TOP_MAX_UIDS
; ++i
)
153 if (ps
.uid
[i
] == uid
)
157 /* Make sure we don't leave a hole in the middle */
158 if (where
!= TOP_MAX_UIDS
)
160 ps
.uid
[where
] = ps
.uid
[i
-1];
166 handle_user(char *buf
, size_t buflen
)
172 new_message(MT_standout
, "Username to show (+ for all): ");
173 if (readline(buf
, buflen
, false) <= 0)
179 if (buf
[0] == '+' || buf
[0] == '-')
190 if ((uid
= userid(buf2
)) == -1)
192 new_message(MT_standout
, " %s: unknown user", buf2
);
208 new_message(MT_standout
, " too many users, reset with '+'");
222 main(int argc
, const char *argv
[])
227 struct system_info system_info
;
228 struct statics statics
;
231 static char tempbuf1
[50];
232 static char tempbuf2
[50];
233 sigset_t old_sigmask
, new_sigmask
;
235 struct timeval delay
= { 2, 0 };
236 int displays
= 0; /* indicates unspecified */
239 char *(*get_userid
)(int) = username
;
240 const char *uname_field
= "USERNAME";
241 const char *header_text
;
243 const char **preset_argv
;
245 const char **av
= NULL
;
247 bool do_unames
= true;
248 char interactive
= 2;
250 char topn_specified
= false;
253 struct timeval timeout
;
254 char *order_name
= NULL
;
259 /* set the buffer for stdout */
262 debug
= fopen("debug.run", "w");
263 setbuffer(stdout
, NULL
, 0);
265 setbuffer(stdout
, stdoutbuf
, Buffersize
);
268 if (setlocale(LC_ALL
, "") == NULL
) {
269 fprintf(stderr
, "invalid locale.\n");
276 /* initialize some selection options */
289 ps
.thread_id
= false;
291 /* get preset options from the environment */
292 if ((env_top
= getenv("TOP")) != NULL
)
294 av
= preset_argv
= argparse(env_top
, &preset_argc
);
297 /* set the dummy argument to an explanatory message, in case
298 getopt encounters a bad argument */
299 preset_argv
[0] = "while processing environment";
302 /* process options */
304 /* if we're done doing the presets, then process the real arguments */
305 if (preset_argc
== 0)
310 /* this should keep getopt happy... */
314 while ((i
= getopt_long(ac
, __DECONST(char * const *, av
), "CSIHPabijJ:nquvzs:d:U:m:o:p:Ttw", longopts
, NULL
)) != EOF
)
318 case 'v': /* show version number */
319 errx(0, "version FreeBSD");
322 case 'u': /* toggle uid/username display */
323 do_unames
= !do_unames
;
326 case 'U': /* display only username's processes */
327 if ((ps
.uid
[0] = userid(optarg
)) == -1)
329 errx(1, "%s: unknown user\n", optarg
);
333 case 'S': /* show system processes */
337 case 'I': /* show idle processes */
341 case 'i': /* go interactive regardless */
345 case 'n': /* batch, or non-interactive */
351 fmt_flags
^= FMT_SHOWARGS
;
354 case 'd': /* number of displays to show */
355 if ((i
= atoiwi(optarg
)) == Invalid
|| i
== 0)
357 warnx("warning: display count should be positive -- option ignored");
366 unsigned long long num
;
369 num
= strtonum(optarg
, 0, INT_MAX
, &errstr
);
370 if (errstr
!= NULL
|| !find_pid(num
)) {
371 fprintf(stderr
, "%s: unknown pid\n", optarg
);
381 double delay_d
= strtod(optarg
, &nptr
);
384 warnx("warning: invalid delay");
387 else if (delay_d
<= 0)
389 warnx("warning: seconds delay should be positive -- using default");
394 delay
.tv_sec
= delay_d
;
395 delay
.tv_usec
= (delay_d
- delay
.tv_sec
) * 1e6
;
400 case 'q': /* be quick about it */
402 i
= setpriority(PRIO_PROCESS
, 0, PRIO_MIN
);
403 if (i
== -1 && errno
!= 0) {
404 warnx("warning: `-q' option failed (%m)");
409 case 'm': /* select display mode */
410 if (strcmp(optarg
, "io") == 0) {
411 displaymode
= DISP_IO
;
412 } else if (strcmp(optarg
, "cpu") == 0) {
413 displaymode
= DISP_CPU
;
415 errx(1, "warning: `-m' option can only take args 'io' or 'cpu'");
419 case 'o': /* select sort order */
432 ps
.thread
= !ps
.thread
;
436 ps
.thread_id
= !ps
.thread_id
;
443 case 'J': /* display only jail's processes */
444 if ((ps
.jid
= jail_getid(optarg
)) == -1)
446 fprintf(stderr
, "%s: unknown jail\n", optarg
);
453 pcpu_stats
= !pcpu_stats
;
461 ps
.kidle
= !ps
.kidle
;
466 "[-abCHIijnPqStuvwz] [-d count] [-J jail] [-m cpu | io] [-o field]\n"
467 " [-p pid] [-s time] [-U username] [number]");
471 /* get count of top processes to display (if any) */
474 if ((topn
= atoiwi(av
[optind
])) == Invalid
)
476 warnx("warning: process display count should be non-negative -- using default");
481 topn_specified
= true;
485 /* tricky: remember old value of preset_argc & set preset_argc = 0 */
489 /* repeat only if we really did the preset arguments */
492 /* set constants for username/uid display correctly */
495 uname_field
= " UID ";
499 /* initialize the kernel memory interface */
500 if (machine_init(&statics
) == -1)
505 /* determine sorting order index, if necessary */
506 if (order_name
!= NULL
)
508 if ((order_index
= string_index(order_name
, statics
.order_names
)) == -1)
510 const char * const *pp
;
512 warnx("'%s' is not a recognized sorting order.", order_name
);
513 fprintf(stderr
, "\tTry one of these:");
514 pp
= statics
.order_names
;
517 fprintf(stderr
, " %s", *pp
++);
524 /* initialize termcap */
525 init_termcap(interactive
);
527 /* get the string to use for the process area header */
528 header_text
= format_header(uname_field
);
530 /* initialize display interface */
531 if ((max_topn
= display_init(&statics
)) == -1)
533 errx(4, "can't allocate sufficient memory");
536 /* print warning if user requested more processes than we can display */
539 warnx("warning: this terminal can only display %d processes.", max_topn
);
543 /* adjust for topn == Infinity */
544 if (topn
== Infinity
)
547 * For smart terminals, infinity really means everything that can
548 * be displayed, or Largest.
549 * On dumb terminals, infinity means every process in the system!
550 * We only really want to do that if it was explicitly specified.
551 * This is always the case when "Default_TOPN != Infinity". But if
552 * topn wasn't explicitly specified and we are on a dumb terminal
553 * and the default is Infinity, then (and only then) we use
554 * "Nominal_TOPN" instead.
556 topn
= smart_terminal
? Largest
:
557 (topn_specified
? Largest
: Nominal_TOPN
);
560 /* set header display accordingly */
561 display_header(topn
> 0);
563 /* determine interactive state */
564 if (interactive
== 2)
566 interactive
= smart_terminal
;
569 /* if # of displays not specified, fill it in */
572 displays
= smart_terminal
? Infinity
: 1;
575 /* hold interrupt signals while setting up the screen and the handlers */
577 sigemptyset(&new_sigmask
);
578 sigaddset(&new_sigmask
, SIGINT
);
579 sigaddset(&new_sigmask
, SIGQUIT
);
580 sigaddset(&new_sigmask
, SIGTSTP
);
581 sigprocmask(SIG_BLOCK
, &new_sigmask
, &old_sigmask
);
583 signal(SIGINT
, leave
);
584 signal(SIGQUIT
, leave
);
585 signal(SIGTSTP
, tstop
);
586 signal(SIGWINCH
, top_winch
);
587 sigprocmask(SIG_SETMASK
, &old_sigmask
, NULL
);
590 fputs("....", stderr
);
599 * main loop -- repeat while display count is positive or while it
600 * indicates infinity (by being -1)
603 while ((displays
== -1) || (displays
-- > 0))
605 int (*compare
)(const void * const, const void * const);
608 /* get the current stats */
609 get_system_info(&system_info
);
611 compare
= compares
[order_index
];
613 /* get the current set of processes */
615 get_process_info(&system_info
, &ps
, compare
);
617 /* display the load averages */
618 (*d_loadave
)(system_info
.last_pid
,
619 system_info
.load_avg
);
621 /* display the battery info (if any) */
622 i_battery(statics
.nbatteries
, system_info
.battery
);
624 /* display the current time */
625 /* this method of getting the time SHOULD be fairly portable */
627 i_uptime(&system_info
.boottime
, &curr_time
);
628 i_timeofday(&curr_time
);
630 /* display process state breakdown */
631 (*d_procstates
)(system_info
.p_total
,
632 system_info
.procstates
);
633 (*d_cpustates
)(system_info
.cpustates
);
635 /* display memory stats */
636 (*d_memory
)(system_info
.memory
);
637 (*d_arc
)(system_info
.arc
);
638 (*d_carc
)(system_info
.carc
);
640 /* display swap stats */
641 (*d_swap
)(system_info
.swap
);
643 /* handle message area */
646 /* update the header area */
647 (*d_header
)(header_text
);
651 /* determine number of processes to actually display */
652 /* this number will be the smallest of: active processes,
653 number user requested, number current screen accommodates */
654 active_procs
= system_info
.p_pactive
;
655 if (active_procs
> topn
)
659 if (active_procs
> max_topn
)
661 active_procs
= max_topn
;
664 /* now show the top "n" processes. */
665 for (i
= 0; i
< active_procs
; i
++)
667 (*d_process
)(i
, format_next_process(processes
, get_userid
,
676 /* do end-screen processing */
679 /* now, flush the output buffer */
680 if (fflush(stdout
) != 0)
682 new_message(MT_standout
, " Write error on stdout");
687 /* only do the rest if we have more displays to show */
690 /* switch out for new display on smart terminals */
699 d_loadave
= u_loadave
;
700 d_procstates
= u_procstates
;
701 d_cpustates
= u_cpustates
;
706 d_message
= u_message
;
708 d_process
= u_process
;
716 select(0, NULL
, NULL
, NULL
, &timeout
);
722 else while (no_command
)
724 /* assume valid command unless told otherwise */
727 /* set up arguments for select with timeout */
729 FD_SET(0, &readfds
); /* for standard input */
738 /* move to the lower left */
742 /* default the signal handler action */
743 signal(SIGTSTP
, SIG_DFL
);
745 /* unblock the signal and send ourselves one */
746 sigsetmask(sigblock(0) & ~(1 << (SIGTSTP
- 1)));
749 /* reset the signal handler */
750 signal(SIGTSTP
, tstop
);
760 /* reascertain the screen dimensions */
763 /* tell display to resize */
764 max_topn
= display_resize();
766 /* reset the signal handler */
767 signal(SIGWINCH
, top_winch
);
774 /* wait for either input or the end of the delay period */
775 sel_ret
= select(2, &readfds
, NULL
, NULL
, &timeout
);
776 if (sel_ret
< 0 && errno
!= EINTR
)
782 const struct command
*cptr
;
784 /* something to read -- clear the message area first */
787 /* now read it and convert to command strchr */
788 /* (use "change" as a temporary to hold strchr) */
789 if (read(0, &ch
, 1) != 1)
791 /* read error: either 0 or -1 */
792 new_message(MT_standout
, " Read error on stdin");
796 if (ch
== '\r' || ch
== '\n') {
800 while (cptr
->c
!= '\0') {
806 if (cptr
->c
== '\0') {
807 new_message(MT_standout
, " Command not understood");
811 if (overstrike
&& !cptr
->available_to_dumb
)
813 new_message(MT_standout
,
814 " Command cannot be handled by this terminal");
821 case CMD_redraw
: /* redraw screen */
825 case CMD_update
: /* merely update display */
836 top_standout("Hit any key to continue: ");
841 case CMD_errors
: /* show errors */
842 if (error_count() == 0)
844 new_message(MT_standout
,
845 " Currently no errors to report.");
854 top_standout("Hit any key to continue: ");
861 new_message(MT_standout
,
862 "Number of processes to show: ");
863 newval
= readline(tempbuf1
, 8, true);
866 if (newval
> max_topn
)
868 new_message(MT_standout
| MT_delayed
,
869 " This terminal can only display %d processes.",
876 /* inhibit the header */
877 display_header(false);
879 else if (newval
> topn
&& topn
== 0)
881 /* redraw the header */
882 display_header(true);
889 case CMD_delay
: /* new seconds delay */
890 new_message(MT_standout
, "Seconds to delay: ");
891 if ((i
= readline(tempbuf1
, 8, false)) > 0)
893 double delay_d
= strtod(tempbuf1
, &nptr
);
894 if (nptr
== tempbuf1
|| delay_d
<= 0)
896 new_message(MT_standout
, " Invalid delay");
902 delay
.tv_sec
= delay_d
;
903 delay
.tv_usec
= (delay_d
- delay
.tv_sec
) * 1e6
;
909 case CMD_grep
: /* grep command name */
910 new_message(MT_standout
,
911 "Grep command name (+ for all): ");
912 if (readline(tempbuf1
, sizeof(tempbuf1
), false) > 0) {
914 if (tempbuf1
[0] == '+' && tempbuf1
[1] == '\0') {
916 } else if ((ps
.command
= strdup(tempbuf1
)) == NULL
)
922 case CMD_displays
: /* change display count */
923 new_message(MT_standout
,
924 "Displays to show (currently %s): ",
925 displays
== -1 ? "infinite" :
927 if ((i
= readline(tempbuf1
, 10, true)) > 0)
938 case CMD_kill
: /* kill program */
939 new_message(0, "kill ");
940 if (readline(tempbuf2
, sizeof(tempbuf2
), false) > 0)
942 if ((errmsg
= kill_procs(tempbuf2
)) != NULL
)
944 new_message(MT_standout
, "%s", errmsg
);
955 case CMD_renice
: /* renice program */
956 new_message(0, "renice ");
957 if (readline(tempbuf2
, sizeof(tempbuf2
), false) > 0)
959 if ((errmsg
= renice_procs(tempbuf2
)) != NULL
)
961 new_message(MT_standout
, "%s", errmsg
);
974 new_message(MT_standout
| MT_delayed
,
975 " %sisplaying idle processes.",
976 ps
.idle
? "D" : "Not d");
982 new_message(MT_standout
| MT_delayed
,
983 " %sisplaying self.",
984 (ps
.self
) ? "D" : "Not d");
989 if (handle_user(tempbuf2
, sizeof(tempbuf2
)))
994 ps
.thread
= !ps
.thread
;
995 new_message(MT_standout
| MT_delayed
,
996 " Displaying threads %s",
997 ps
.thread
? "separately" : "as a count");
998 header_text
= format_header(uname_field
);
1004 ps
.thread_id
= !ps
.thread_id
;
1005 new_message(MT_standout
| MT_delayed
,
1007 ps
.thread_id
? "tid" : "pid");
1008 header_text
= format_header(uname_field
);
1015 new_message(MT_standout
| MT_delayed
,
1016 " Displaying %s CPU",
1017 ps
.wcpu
? "weighted" : "raw");
1018 header_text
= format_header(uname_field
);
1023 displaymode
= displaymode
== DISP_IO
? DISP_CPU
: DISP_IO
;
1024 new_message(MT_standout
| MT_delayed
,
1025 " Displaying %s statistics.",
1026 displaymode
== DISP_IO
? "IO" : "CPU");
1027 header_text
= format_header(uname_field
);
1028 display_header(true);
1029 d_header
= i_header
;
1033 ps
.system
= !ps
.system
;
1034 new_message(MT_standout
| MT_delayed
,
1035 " %sisplaying system processes.",
1036 ps
.system
? "D" : "Not d");
1039 fmt_flags
^= FMT_SHOWARGS
;
1040 show_args
= fmt_flags
& FMT_SHOWARGS
;
1041 new_message(MT_standout
| MT_delayed
,
1042 " %sisplaying process arguments.",
1043 fmt_flags
& FMT_SHOWARGS
? "D" : "Not d");
1046 new_message(MT_standout
,
1048 if (readline(tempbuf2
, sizeof(tempbuf2
), false) > 0)
1050 if ((i
= string_index(tempbuf2
, statics
.order_names
)) == -1)
1052 new_message(MT_standout
,
1053 " %s: unrecognized sorting order", tempbuf2
);
1069 new_message(MT_standout
| MT_delayed
,
1070 " %sisplaying jail ID.",
1071 ps
.jail
? "D" : "Not d");
1072 header_text
= format_header(uname_field
);
1078 new_message(MT_standout
,
1079 "Jail to show (+ for all): ");
1080 if (readline(tempbuf2
, sizeof(tempbuf2
), false) > 0)
1082 if (tempbuf2
[0] == '+' &&
1083 tempbuf2
[1] == '\0')
1087 else if ((i
= jail_getid(tempbuf2
)) == -1)
1089 new_message(MT_standout
,
1090 " %s: unknown jail", tempbuf2
);
1099 new_message(MT_standout
|
1100 MT_delayed
, " Displaying jail "
1103 format_header(uname_field
);
1115 ps
.kidle
= !ps
.kidle
;
1116 new_message(MT_standout
| MT_delayed
,
1117 " %sisplaying system idle process.",
1118 ps
.kidle
? "D" : "Not d");
1122 pcpu_stats
= !pcpu_stats
;
1123 new_message(MT_standout
| MT_delayed
,
1124 " Displaying %sCPU statistics.",
1125 pcpu_stats
? "per-" : "global ");
1127 max_topn
= display_updatecpus(&statics
);
1133 new_message(MT_standout
| MT_delayed
,
1134 " %sisplaying per-process swap usage.",
1135 ps
.swap
? "D" : "Not d");
1136 header_text
= format_header(uname_field
);
1141 new_message(MT_standout
,
1142 "Process id to show (+ for all): ");
1143 if (readline(tempbuf2
, sizeof(tempbuf2
), false) > 0) {
1144 if (tempbuf2
[0] == '+' &&
1145 tempbuf2
[1] == '\0') {
1148 unsigned long long num
;
1151 num
= strtonum(tempbuf2
, 0, INT_MAX
,
1153 if (errstr
!= NULL
|| !find_pid(num
)) {
1154 new_message(MT_standout
,
1159 ps
.pid
= (pid_t
)num
;
1167 assert(false && "reached switch without command");
1172 /* flush out stuff that may have been written */
1185 * reset_display() - reset all the display routine pointers so that entire
1186 * screen will get redrawn.
1192 d_loadave
= i_loadave
;
1193 d_procstates
= i_procstates
;
1194 d_cpustates
= i_cpustates
;
1195 d_memory
= i_memory
;
1199 d_message
= i_message
;
1200 d_header
= i_header
;
1201 d_process
= i_process
;
1209 leave(int i __unused
) /* exit under normal conditions -- INT handler */
1216 tstop(int i __unused
) /* SIGTSTP handler */
1223 top_winch(int i __unused
) /* SIGWINCH handler */
1230 quit(int status
) /* exit under duress */