xref: /openbsd-src/usr.bin/top/display.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /* $OpenBSD: display.c,v 1.33 2007/11/30 10:39:01 otto Exp $	 */
2 
3 /*
4  *  Top users/processes display for Unix
5  *  Version 3
6  *
7  * Copyright (c) 1984, 1989, William LeFebvre, Rice University
8  * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 /*
32  *  This file contains the routines that display information on the screen.
33  *  Each section of the screen has two routines:  one for initially writing
34  *  all constant and dynamic text, and one for only updating the text that
35  *  changes.  The prefix "i_" is used on all the "initial" routines and the
36  *  prefix "u_" is used for all the "updating" routines.
37  *
38  *  ASSUMPTIONS:
39  *        None of the "i_" routines use any of the termcap capabilities.
40  *        In this way, those routines can be safely used on terminals that
41  *        have minimal (or nonexistent) terminal capabilities.
42  *
43  *        The routines are called in this order:  *_loadave, i_timeofday,
44  *        *_procstates, *_cpustates, *_memory, *_message, *_header,
45  *        *_process, u_endscreen.
46  */
47 
48 #include <sys/types.h>
49 #include <sys/sched.h>
50 #include <curses.h>
51 #include <errno.h>
52 #include <stdio.h>
53 #include <ctype.h>
54 #include <err.h>
55 #include <signal.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <unistd.h>
59 
60 #include "screen.h"		/* interface to screen package */
61 #include "layout.h"		/* defines for screen position layout */
62 #include "display.h"
63 #include "top.h"
64 #include "boolean.h"
65 #include "machine.h"		/* we should eliminate this!!! */
66 #include "utils.h"
67 
68 #ifdef DEBUG
69 FILE           *debug;
70 #endif
71 
72 static int      display_width = MAX_COLS;
73 
74 static char    *cpustates_tag(int);
75 static int      string_count(char **);
76 static void     summary_format(char *, size_t, int *, char **);
77 static int	readlinedumb(char *, int);
78 
79 #define lineindex(l) ((l)*display_width)
80 
81 /* things initialized by display_init and used throughout */
82 
83 /* buffer of proc information lines for display updating */
84 char           *screenbuf = NULL;
85 
86 static char   **procstate_names;
87 static char   **cpustate_names;
88 static char   **memory_names;
89 
90 static int      num_procstates;
91 static int      num_cpustates;
92 
93 static int     *lprocstates;
94 static int64_t **lcpustates;
95 
96 static int     *cpustate_columns;
97 static int      cpustate_total_length;
98 
99 /* display ips */
100 int y_mem;
101 int y_message;
102 int y_header;
103 int y_idlecursor;
104 int y_procs;
105 extern int ncpu;
106 int Header_lines;
107 
108 int header_status = Yes;
109 
110 static int
111 empty(void)
112 {
113 	return OK;
114 }
115 
116 static int
117 myfputs(const char *s)
118 {
119 	return fputs(s, stdout);
120 }
121 
122 static int (*addstrp)(const char *);
123 static int (*printwp)(const char *, ...);
124 static int (*standoutp)(void);
125 static int (*standendp)(void);
126 
127 int
128 display_resize(void)
129 {
130 	int display_lines;
131 
132 	/* calculate the current dimensions */
133 	/* if operating in "dumb" mode, we only need one line */
134 	display_lines = smart_terminal ? screen_length - Header_lines : 1;
135 
136 	y_idlecursor = y_message = 3 + ncpu;
137 	if (screen_length <= y_message)
138 		y_idlecursor = y_message = screen_length - 1;
139 
140 	/*
141 	 * we don't want more than MAX_COLS columns, since the
142 	 * machine-dependent modules make static allocations based on
143 	 * MAX_COLS and we don't want to run off the end of their buffers
144 	 */
145 	display_width = screen_width;
146 	if (display_width >= MAX_COLS)
147 		display_width = MAX_COLS - 1;
148 
149 	/* return number of lines available */
150 	/* for dumb terminals, pretend like we can show any amount */
151 	return (smart_terminal ? display_lines : Largest);
152 }
153 
154 int
155 display_init(struct statics * statics)
156 {
157 	int display_lines, *ip, i, cpu;
158 	char **pp;
159 
160 	if (smart_terminal) {
161 		addstrp = addstr;
162 		printwp = (int(*)(const char *, ...))printw;
163 		standoutp = standout;
164 		standendp = standend;
165 	} else {
166 		addstrp = myfputs;
167 		printwp = printf;
168 		standoutp = empty;
169 		standendp = empty;
170 	}
171 
172 	y_mem = 2 + ncpu;
173 	y_header = 4 + ncpu;
174 	y_procs = 5 + ncpu;
175 	Header_lines = 5 + ncpu;
176 
177 	/* call resize to do the dirty work */
178 	display_lines = display_resize();
179 
180 	/* only do the rest if we need to */
181 	/* save pointers and allocate space for names */
182 	procstate_names = statics->procstate_names;
183 	num_procstates = string_count(procstate_names);
184 	lprocstates = calloc(num_procstates, sizeof(int));
185 	if (lprocstates == NULL)
186 		err(1, NULL);
187 
188 	cpustate_names = statics->cpustate_names;
189 	num_cpustates = string_count(cpustate_names);
190 	lcpustates = calloc(ncpu, sizeof(int64_t *));
191 	if (lcpustates == NULL)
192 		err(1, NULL);
193 	for (cpu = 0; cpu < ncpu; cpu++) {
194 		lcpustates[cpu] = calloc(num_cpustates, sizeof(int64_t));
195 		if (lcpustates[cpu] == NULL)
196 			err(1, NULL);
197 	}
198 
199 	cpustate_columns = calloc(num_cpustates, sizeof(int));
200 	if (cpustate_columns == NULL)
201 		err(1, NULL);
202 
203 	memory_names = statics->memory_names;
204 
205 	/* calculate starting columns where needed */
206 	cpustate_total_length = 0;
207 	pp = cpustate_names;
208 	ip = cpustate_columns;
209 	while (*pp != NULL) {
210 		if ((i = strlen(*pp++)) > 0) {
211 			*ip++ = cpustate_total_length;
212 			cpustate_total_length += i + 8;
213 		}
214 	}
215 
216 	if (display_lines < 0)
217 		display_lines = 0;
218 
219 	/* return number of lines available */
220 	return (display_lines);
221 }
222 
223 void
224 i_loadave(pid_t mpid, double *avenrun)
225 {
226 	if (screen_length > 1 || !smart_terminal) {
227 		int i;
228 
229 		move(0, 0);
230 		clrtoeol();
231 
232 		addstrp("load averages");
233 		/* mpid == -1 implies this system doesn't have an _mpid */
234 		if (mpid != -1)
235 			printwp("last pid: %5ld;  ", (long) mpid);
236 
237 		for (i = 0; i < 3; i++)
238 			printwp("%c %5.2f", i == 0 ? ':' : ',', avenrun[i]);
239 	}
240 }
241 
242 /*
243  *  Display the current time.
244  *  "ctime" always returns a string that looks like this:
245  *
246  *	Sun Sep 16 01:03:52 1973
247  *      012345678901234567890123
248  *	          1         2
249  *
250  *  We want indices 11 thru 18 (length 8).
251  */
252 
253 void
254 i_timeofday(time_t * tod)
255 {
256 	if (screen_length > 1 || !smart_terminal) {
257 		if (smart_terminal) {
258 			move(0, screen_width - 8);
259 		} else {
260 			if (fputs("    ", stdout) == EOF)
261 				exit(1);
262 		}
263 #ifdef DEBUG
264 		{
265 			char *foo;
266 			foo = ctime(tod);
267 			addstrp(foo);
268 		}
269 #endif
270 		printwp("%-8.8s", &(ctime(tod)[11]));
271 		putn();
272 	}
273 }
274 
275 /*
276  *  *_procstates(total, brkdn, names) - print the process summary line
277  *
278  *  Assumptions:  cursor is at the beginning of the line on entry
279  */
280 void
281 i_procstates(int total, int *brkdn)
282 {
283 	if (screen_length > 2 || !smart_terminal) {
284 		int i;
285 		char procstates_buffer[MAX_COLS];
286 
287 		move(1, 0);
288 		clrtoeol();
289 		/* write current number of processes and remember the value */
290 		printwp("%d processes:", total);
291 
292 		if (smart_terminal)
293 			move(1, 15);
294 		else {
295 			/* put out enough spaces to get to column 15 */
296 			i = digits(total);
297 			while (i++ < 4) {
298 				if (putchar(' ') == EOF)
299 					exit(1);
300 			}
301 		}
302 
303 		/* format and print the process state summary */
304 		summary_format(procstates_buffer, sizeof(procstates_buffer), brkdn,
305 		    procstate_names);
306 
307 		addstrp(procstates_buffer);
308 		putn();
309 	}
310 }
311 
312 /*
313  *  *_cpustates(states, names) - print the cpu state percentages
314  *
315  *  Assumptions:  cursor is on the PREVIOUS line
316  */
317 
318 /* cpustates_tag() calculates the correct tag to use to label the line */
319 
320 static char *
321 cpustates_tag(int cpu)
322 {
323 	if (screen_length > 3 || !smart_terminal) {
324 		static char *tag;
325 		static int cpulen, old_width;
326 		int i;
327 
328 		if (cpulen == 0 && ncpu > 1) {
329 			/* compute length of the cpu string */
330 			for (i = ncpu; i > 0; cpulen++, i /= 10)
331 				continue;
332 		}
333 
334 		if (old_width == screen_width) {
335 			if (ncpu > 1) {
336 				/* just store the cpu number in the tag */
337 				i = tag[3 + cpulen];
338 				snprintf(tag + 3, cpulen + 1, "%.*d", cpulen, cpu);
339 				tag[3 + cpulen] = i;
340 			}
341 		} else {
342 			/*
343 			 * use a long tag if it will fit, otherwise use short one.
344 			 */
345 			free(tag);
346 			if (cpustate_total_length + 10 + cpulen >= screen_width)
347 				i = asprintf(&tag, "CPU%.*d: ", cpulen, cpu);
348 			else
349 				i = asprintf(&tag, "CPU%.*d states: ", cpulen, cpu);
350 			if (i == -1)
351 				tag = NULL;
352 			else
353 				old_width = screen_width;
354 		}
355 		return (tag);
356 	} else
357 		return ('\0');
358 }
359 
360 void
361 i_cpustates(int64_t *ostates)
362 {
363 	int i, cpu, value;
364 	int64_t *states;
365 	char **names = cpustate_names, *thisname;
366 
367 	for (cpu = 0; cpu < ncpu; cpu++) {
368 		/* now walk thru the names and print the line */
369 		names = cpustate_names;
370 		i = 0;
371 		states = ostates + (CPUSTATES * cpu);
372 
373 		if (screen_length > 2 + cpu || !smart_terminal) {
374 			move(2 + cpu, 0);
375 			clrtoeol();
376 			addstrp(cpustates_tag(cpu));
377 
378 			while ((thisname = *names++) != NULL) {
379 				if (*thisname != '\0') {
380 					/* retrieve the value and remember it */
381 					value = *states++;
382 
383 					/* if percentage is >= 1000, print it as 100% */
384 					printwp((value >= 1000 ? "%s%4.0f%% %s" :
385 					    "%s%4.1f%% %s"), i++ == 0 ? "" : ", ",
386 					    ((float) value) / 10., thisname);
387 				}
388 			}
389 			putn();
390 		}
391 	}
392 }
393 
394 /*
395  *  *_memory(stats) - print "Memory: " followed by the memory summary string
396  */
397 void
398 i_memory(int *stats)
399 {
400 	if (screen_length > y_mem || !smart_terminal) {
401 		char memory_buffer[MAX_COLS];
402 
403 		move(y_mem, 0);
404 		clrtoeol();
405 		addstrp("Memory: ");
406 
407 		/* format and print the memory summary */
408 		summary_format(memory_buffer, sizeof(memory_buffer), stats,
409 		    memory_names);
410 		addstrp(memory_buffer);
411 		putn();
412 	}
413 }
414 
415 /*
416  *  *_message() - print the next pending message line, or erase the one
417  *                that is there.
418  */
419 
420 /*
421  *  i_message is funny because it gets its message asynchronously (with
422  *	respect to screen updates).
423  */
424 
425 static char     next_msg[MAX_COLS + 5];
426 static int      msgon = 0;
427 
428 void
429 i_message(void)
430 {
431 	move(y_message, 0);
432 	if (next_msg[0] != '\0') {
433 		standoutp();
434 		addstrp(next_msg);
435 		standendp();
436 		clrtoeol();
437 		msgon = TRUE;
438 		next_msg[0] = '\0';
439 	} else if (msgon) {
440 		clrtoeol();
441 		msgon = FALSE;
442 	}
443 }
444 
445 /*
446  *  *_header(text) - print the header for the process area
447  */
448 
449 void
450 i_header(char *text)
451 {
452 	if (header_status == Yes && (screen_length > y_header
453               || !smart_terminal)) {
454 		if (!smart_terminal) {
455 			putn();
456 			if (fputs(text, stdout) == EOF)
457 				exit(1);
458 			putn();
459 		} else {
460 			move(y_header, 0);
461 			clrtoeol();
462 			addstrp(text);
463 		}
464 	}
465 }
466 
467 /*
468  *  *_process(line, thisline) - print one process line
469  */
470 
471 void
472 i_process(int line, char *thisline, int hl)
473 {
474 	/* make sure we are on the correct line */
475 	move(y_procs + line, 0);
476 
477 	/* truncate the line to conform to our current screen width */
478 	thisline[display_width] = '\0';
479 
480 	/* write the line out */
481 	if (hl && smart_terminal)
482 		standoutp();
483 	addstrp(thisline);
484 	if (hl && smart_terminal)
485 		standendp();
486 	putn();
487 	clrtoeol();
488 }
489 
490 void
491 u_endscreen(void)
492 {
493 	if (smart_terminal) {
494 		clrtobot();
495 		/* move the cursor to a pleasant place */
496 		move(y_idlecursor, x_idlecursor);
497 	} else {
498 		/*
499 		 * separate this display from the next with some vertical
500 		 * room
501 		 */
502 		if (fputs("\n\n", stdout) == EOF)
503 			exit(1);
504 	}
505 }
506 
507 void
508 display_header(int status)
509 {
510 	header_status = status;
511 }
512 
513 void
514 new_message(int type, const char *msgfmt,...)
515 {
516 	va_list ap;
517 
518 	va_start(ap, msgfmt);
519 	/* first, format the message */
520 	vsnprintf(next_msg, sizeof(next_msg), msgfmt, ap);
521 	va_end(ap);
522 
523 	if (next_msg[0] != '\0') {
524 		/* message there already -- can we clear it? */
525 		/* yes -- write it and clear to end */
526 		if ((type & MT_delayed) == 0) {
527 			move(y_message, 0);
528 			if (type & MT_standout)
529 				standoutp();
530 			addstrp(next_msg);
531 			if (type & MT_standout)
532 				standendp();
533 			clrtoeol();
534 			msgon = TRUE;
535 			next_msg[0] = '\0';
536 			if (smart_terminal)
537 				refresh();
538 		}
539 	}
540 }
541 
542 void
543 clear_message(void)
544 {
545 	move(y_message, 0);
546 	clrtoeol();
547 }
548 
549 
550 static int
551 readlinedumb(char *buffer, int size)
552 {
553 	char *ptr = buffer, ch, cnt = 0, maxcnt = 0;
554 	extern volatile sig_atomic_t leaveflag;
555 	ssize_t len;
556 
557 	/* allow room for null terminator */
558 	size -= 1;
559 
560 	/* read loop */
561 	while ((fflush(stdout), (len = read(STDIN_FILENO, ptr, 1)) > 0)) {
562 
563 		if (len == 0 || leaveflag) {
564 			end_screen();
565 			exit(0);
566 		}
567 
568 		/* newline means we are done */
569 		if ((ch = *ptr) == '\n')
570 			break;
571 
572 		/* handle special editing characters */
573 		if (ch == ch_kill) {
574 			/* return null string */
575 			*buffer = '\0';
576 			putr();
577 			return (-1);
578 		} else if (ch == ch_erase) {
579 			/* erase previous character */
580 			if (cnt <= 0) {
581 				/* none to erase! */
582 				if (putchar('\7') == EOF)
583 					exit(1);
584 			} else {
585 				if (fputs("\b \b", stdout) == EOF)
586 					exit(1);
587 				ptr--;
588 				cnt--;
589 			}
590 		}
591 		/* check for character validity and buffer overflow */
592 		else if (cnt == size || !isprint(ch)) {
593 			/* not legal */
594 			if (putchar('\7') == EOF)
595 				exit(1);
596 		} else {
597 			/* echo it and store it in the buffer */
598 			if (putchar(ch) == EOF)
599 				exit(1);
600 			ptr++;
601 			cnt++;
602 			if (cnt > maxcnt)
603 				maxcnt = cnt;
604 		}
605 	}
606 
607 	/* all done -- null terminate the string */
608 	*ptr = '\0';
609 
610 	/* return either inputted number or string length */
611 	putr();
612 	return (cnt == 0 ? -1 : cnt);
613 }
614 
615 int
616 readline(char *buffer, int size)
617 {
618 	size_t cnt;
619 
620 	/* allow room for null terminator */
621 	size -= 1;
622 
623 	if (smart_terminal)
624 		getnstr(buffer, size);
625 	else
626 		return readlinedumb(buffer, size);
627 
628 	cnt = strlen(buffer);
629 	if (cnt > 0 && buffer[cnt - 1] == '\n')
630 		buffer[cnt - 1] = '\0';
631 	return (cnt == 0 ? -1 : cnt);
632 }
633 
634 /* internal support routines */
635 static int
636 string_count(char **pp)
637 {
638 	int cnt;
639 
640 	cnt = 0;
641 	while (*pp++ != NULL)
642 		cnt++;
643 	return (cnt);
644 }
645 
646 #define	COPYLEFT(to, from)				\
647 	do {						\
648 		len = strlcpy((to), (from), left);	\
649 		if (len >= left)			\
650 			return;				\
651 		p += len;				\
652 		left -= len;				\
653 	} while (0)
654 
655 static void
656 summary_format(char *buf, size_t left, int *numbers, char **names)
657 {
658 	char *p, *thisname;
659 	size_t len;
660 	int num;
661 
662 	/* format each number followed by its string */
663 	p = buf;
664 	while ((thisname = *names++) != NULL) {
665 		/* get the number to format */
666 		num = *numbers++;
667 
668 		if (num >= 0) {
669 			/* is this number in kilobytes? */
670 			if (thisname[0] == 'K') {
671 				/* yes: format it as a memory value */
672 				COPYLEFT(p, format_k(num));
673 
674 				/*
675 				 * skip over the K, since it was included by
676 				 * format_k
677 				 */
678 				COPYLEFT(p, thisname + 1);
679 			} else if (num > 0) {
680 				len = snprintf(p, left, "%d%s", num, thisname);
681 				if (len == (size_t)-1 || len >= left)
682 					return;
683 				p += len;
684 				left -= len;
685 			}
686 		} else {
687 			/*
688 			 * Ignore negative numbers, but display corresponding
689 			 * string.
690 			 */
691 			COPYLEFT(p, thisname);
692 		}
693 	}
694 
695 	/* if the last two characters in the string are ", ", delete them */
696 	p -= 2;
697 	if (p >= buf && p[0] == ',' && p[1] == ' ')
698 		*p = '\0';
699 }
700 
701 /*
702  *  printable(str) - make the string pointed to by "str" into one that is
703  *	printable (i.e.: all ascii), by converting all non-printable
704  *	characters into '?'.  Replacements are done in place and a pointer
705  *	to the original buffer is returned.
706  */
707 char *
708 printable(char *str)
709 {
710 	char *ptr, ch;
711 
712 	ptr = str;
713 	while ((ch = *ptr) != '\0') {
714 		if (!isprint(ch))
715 			*ptr = '?';
716 		ptr++;
717 	}
718 	return (str);
719 }
720 
721 
722 /*
723  *  show_help() - display the help screen; invoked in response to
724  *		either 'h' or '?'.
725  */
726 void
727 show_help(void)
728 {
729 	if (smart_terminal) {
730 		clear();
731 		nl();
732 	}
733 	printwp("These single-character commands are available:\n"
734 	    "\n"
735 	    "^L           - redraw screen\n"
736 	    "+            - reset any g, p, or u filters\n"
737 	    "C            - toggle the display of command line arguments\n"
738 	    "d count      - show `count' displays, then exit\n"
739 	    "e            - list errors generated by last \"kill\" or \"renice\" command\n"
740 	    "h | ?        - help; show this text\n"
741 	    "g string     - filter on command name (g+ selects all commands)\n"
742 	    "I | i        - toggle the display of idle processes\n"
743 	    "k [-sig] pid - send signal `-sig' to process `pid'\n"
744 	    "n|# count    - show `count' processes\n"
745 	    "o field      - specify sort order (size, res, cpu, time, pri)\n"
746 	    "P pid        - highlight process `pid' (P+ switches highlighting off)\n"
747 	    "p pid        - display process by `pid' (p+ selects all processes)\n"
748 	    "q            - quit\n"
749 	    "r count pid  - renice process `pid' to nice value `count'\n"
750 	    "S            - toggle the display of system processes\n"
751 	    "s time       - change delay between displays to `time' seconds\n"
752 	    "T            - toggle the display of threads\n"
753 	    "u user       - display processes for `user' (u+ selects all users)\n"
754 	    "\n");
755 
756 	if (smart_terminal) {
757 		nonl();
758 		refresh();
759 	}
760 }
761 
762 /*
763  *  show_errors() - display on stdout the current log of errors.
764  */
765 void
766 show_errors(void)
767 {
768 	struct errs *errp = errs;
769 	int cnt = 0;
770 
771 	if (smart_terminal) {
772 		clear();
773 		nl();
774 	}
775 	printwp("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
776 	while (cnt++ < errcnt) {
777 		printf("%5s: %s\n", errp->arg,
778 		    errp->err == 0 ? "Not a number" : strerror(errp->err));
779 		errp++;
780 	}
781 	if (smart_terminal) {
782 		nonl();
783 		refresh();
784 	}
785 }
786 
787 void
788 anykey(void)
789 {
790 	int ch;
791 	ssize_t len;
792 
793 	standoutp();
794 	addstrp("Hit any key to continue: ");
795 	standendp();
796 	if (smart_terminal)
797 		refresh();
798 	else
799 		fflush(stdout);
800 	while (1) {
801 		len = read(STDIN_FILENO, &ch, 1);
802 		if (len == -1 && errno == EINTR)
803 			continue;
804 		if (len == 0)
805 			exit(1);
806 		break;
807 	}
808 }
809