/* * Copyright (c) 1984 through 2008, William LeFebvre * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * * Neither the name of William LeFebvre nor the names of other * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Top users/processes display for Unix * Version 3 */ /* * This file contains the routines that display information on the screen. * Each section of the screen has two routines: one for initially writing * all constant and dynamic text, and one for only updating the text that * changes. The prefix "i_" is used on all the "initial" routines and the * prefix "u_" is used for all the "updating" routines. * * ASSUMPTIONS: * None of the "i_" routines use any of the termcap capabilities. * In this way, those routines can be safely used on terminals that * have minimal (or nonexistant) terminal capabilities. * * The routines should be called in this order: *_loadave, *_uptime, * i_timeofday, *_procstates, *_cpustates, *_memory, *_swap, * *_message, *_header, *_process, *_endscreen. */ #include "os.h" #include #include #include #include #include #include "top.h" #include "machine.h" #include "screen.h" /* interface to screen package */ #include "layout.h" /* defines for screen position layout */ #include "display.h" #include "boolean.h" #include "utils.h" #ifdef ENABLE_COLOR #include "color.h" #endif #define CURSOR_COST 8 #define MESSAGE_DISPLAY_TIME 5 /* imported from screen.c */ extern int overstrike; static int lmpid = -1; static int display_width = MAX_COLS; /* cursor positions of key points on the screen are maintained here */ /* layout.h has static definitions, but we may change our minds on some of the positions as we make decisions about what needs to be displayed */ static int x_lastpid = X_LASTPID; static int y_lastpid = Y_LASTPID; static int x_loadave = X_LOADAVE; static int y_loadave = Y_LOADAVE; static int x_minibar = X_MINIBAR; static int y_minibar = Y_MINIBAR; static int x_uptime = X_UPTIME; static int y_uptime = Y_UPTIME; static int x_procstate = X_PROCSTATE; static int y_procstate = Y_PROCSTATE; static int x_cpustates = X_CPUSTATES; static int y_cpustates = Y_CPUSTATES; static int x_kernel = X_KERNEL; static int y_kernel = Y_KERNEL; static int x_mem = X_MEM; static int y_mem = Y_MEM; static int x_swap = X_SWAP; static int y_swap = Y_SWAP; static int y_message = Y_MESSAGE; static int x_header = X_HEADER; static int y_header = Y_HEADER; static int x_idlecursor = X_IDLECURSOR; static int y_idlecursor = Y_IDLECURSOR; static int y_procs = Y_PROCS; /* buffer and colormask that describes the content of the screen */ /* these are singly dimensioned arrays -- the row boundaries are determined on the fly. */ static char *screenbuf = NULL; static char *colorbuf = NULL; static char scratchbuf[MAX_COLS]; static int bufsize = 0; /* lineindex tells us where the beginning of a line is in the buffer */ #define lineindex(l) ((l)*MAX_COLS) /* screen's cursor */ static int curr_x, curr_y; static int curr_color; /* virtual cursor */ static int virt_x, virt_y; static char **procstate_names; static char **cpustate_names; static char **memory_names; static char **swap_names; static char **kernel_names; static int num_procstates; static int num_cpustates; static int num_memory; static int num_swap; static int num_kernel; static int *lprocstates; static int *lcpustates; static int *cpustate_columns; static int cpustate_total_length; static int header_status = Yes; /* pending messages are stored in a circular buffer, where message_first is the next one to display, and message_last is the last one in the buffer. Counters wrap around at MAX_MESSAGES. The buffer is empty when message_first == message_last and full when message_last + 1 == message_first. The pointer message_current holds the message currently being displayed, or "" if there is none. */ #define MAX_MESSAGES 16 static char *message_buf[MAX_MESSAGES]; static int message_first = 0; static int message_last = 0; static struct timeval message_time = {0, 0}; static char *message_current = NULL; static int message_length = 0; static int message_hold = 1; static int message_barrier = No; #ifdef ENABLE_COLOR static int load_cidx[3]; static int header_cidx; static int *cpustate_cidx; static int *memory_cidx; static int *swap_cidx; static int *kernel_cidx; #else #define memory_cidx NULL #define swap_cidx NULL #define kernel_cidx NULL #endif /* internal support routines */ /* * static int string_count(char **pp) * * Pointer "pp" points to an array of string pointers, which is * terminated by a NULL. Return the number of string pointers in * this array. */ static int string_count(char **pp) { register int cnt = 0; if (pp != NULL) { while (*pp++ != NULL) { cnt++; } } return(cnt); } void display_clear() { dprintf("display_clear\n"); screen_clear(); memzero(screenbuf, bufsize); memzero(colorbuf, bufsize); curr_x = curr_y = 0; } /* * void display_move(int x, int y) * * Efficiently move the cursor to x, y. This assumes the cursor is * currently located at curr_x, curr_y, and will only use cursor * addressing when it is less expensive than overstriking what's * already on the screen. */ void display_move(int x, int y) { char buff[128]; char *p; char *bufp; char *colorp; int cnt = 0; int color = curr_color; dprintf("display_move(%d, %d): curr_x %d, curr_y %d\n", x, y, curr_x, curr_y); /* are we in a position to do this without cursor addressing? */ if (curr_y < y || (curr_y == y && curr_x <= x)) { /* start buffering up what it would take to move there by rewriting what's on the screen */ cnt = CURSOR_COST; p = buff; /* one newline for every line */ while (cnt > 0 && curr_y < y) { #ifdef ENABLE_COLOR if (color != 0) { p = strcpyend(p, color_setstr(0)); color = 0; cnt -= 5; } #endif *p++ = '\n'; curr_y++; curr_x = 0; cnt--; } /* write whats in the screenbuf */ bufp = &screenbuf[lineindex(curr_y) + curr_x]; colorp = &colorbuf[lineindex(curr_y) + curr_x]; while (cnt > 0 && curr_x < x) { #ifdef ENABLE_COLOR if (color != *colorp) { color = *colorp; p = strcpyend(p, color_setstr(color)); cnt -= 5; } #endif if ((*p = *bufp) == '\0') { /* somwhere on screen we haven't been before */ *p = *bufp = ' '; } p++; bufp++; colorp++; curr_x++; cnt--; } } /* move the cursor */ if (cnt > 0) { /* screen rewrite is cheaper */ *p = '\0'; fputs(buff, stdout); curr_color = color; } else { screen_move(x, y); } /* update our position */ curr_x = x; curr_y = y; } /* * display_write(int x, int y, int newcolor, int eol, char *new) * * Optimized write to the display. This writes characters to the * screen in a way that optimizes the number of characters actually * sent, by comparing what is being written to what is already on * the screen (according to screenbuf and colorbuf). The string to * write is "new", the first character of "new" should appear at * screen position x, y. If x is -1 then "new" begins wherever the * cursor is currently positioned. The string is written with color * "newcolor". If "eol" is true then the remainder of the line is * cleared. It is expected that "new" will have no newlines and no * escape sequences. */ void display_write(int x, int y, int newcolor, int eol, char *new) { char *bufp; char *colorp; int ch; int diff; dprintf("display_write(%d, %d, %d, %d, \"%s\")\n", x, y, newcolor, eol, new); /* dumb terminal handling here */ if (!smart_terminal) { if (x != -1) { /* make sure we are on the right line */ while (curr_y < y) { putchar('\n'); curr_y++; curr_x = 0; } /* make sure we are on the right column */ while (curr_x < x) { putchar(' '); curr_x++; } } /* write */ fputs(new, stdout); curr_x += strlen(new); return; } /* adjust for "here" */ if (x == -1) { x = virt_x; y = virt_y; } else { virt_x = x; virt_y = y; } /* a pointer to where we start */ bufp = &screenbuf[lineindex(y) + x]; colorp = &colorbuf[lineindex(y) + x]; /* main loop */ while ((ch = *new++) != '\0') { /* if either character or color are different, an update is needed */ /* but only when the screen is wide enough */ if (x < display_width && (ch != *bufp || newcolor != *colorp)) { /* check cursor */ if (y != curr_y || x != curr_x) { /* have to move the cursor */ display_move(x, y); } /* write character */ #ifdef ENABLE_COLOR if (curr_color != newcolor) { fputs(color_setstr(newcolor), stdout); curr_color = newcolor; } #endif putchar(ch); *bufp = ch; *colorp = curr_color; curr_x++; } /* move */ x++; virt_x++; bufp++; colorp++; } /* eol handling */ if (eol && *bufp != '\0') { dprintf("display_write: clear-eol (bufp = \"%s\")\n", bufp); /* make sure we are color 0 */ #ifdef ENABLE_COLOR if (curr_color != 0) { fputs(color_setstr(0), stdout); curr_color = 0; } #endif /* make sure we are at the end */ if (x != curr_x || y != curr_y) { screen_move(x, y); curr_x = x; curr_y = y; } /* clear to end */ screen_cleareol(strlen(bufp)); /* clear out whats left of this line's buffer */ diff = display_width - x; if (diff > 0) { memzero(bufp, diff); memzero(colorp, diff); } } } void display_fmt(int x, int y, int newcolor, int eol, char *fmt, ...) { va_list argp; va_start(argp, fmt); vsnprintf(scratchbuf, MAX_COLS, fmt, argp); display_write(x, y, newcolor, eol, scratchbuf); } void display_cte() { int len; int y; char *p; int need_clear = 0; /* is there anything out there that needs to be cleared? */ p = &screenbuf[lineindex(virt_y) + virt_x]; if (*p != '\0') { need_clear = 1; } else { /* this line is clear, what about the rest? */ y = virt_y; while (++y < screen_length) { if (screenbuf[lineindex(y)] != '\0') { need_clear = 1; break; } } } if (need_clear) { dprintf("display_cte: clearing\n"); /* we will need this later */ len = lineindex(virt_y) + virt_x; /* move to x and y, then clear to end */ display_move(virt_x, virt_y); if (!screen_cte()) { /* screen has no clear to end, so do it by hand */ p = &screenbuf[len]; len = strlen(p); if (len > 0) { screen_cleareol(len); } while (++virt_y < screen_length) { display_move(0, virt_y); p = &screenbuf[lineindex(virt_y)]; len = strlen(p); if (len > 0) { screen_cleareol(len); } } } /* clear the screenbuf */ memzero(&screenbuf[len], bufsize - len); memzero(&colorbuf[len], bufsize - len); } } static void summary_format(int x, int y, int *numbers, char **names, int *cidx) { register int num; register char *thisname; register char *lastname = NULL; register int color; /* format each number followed by its string */ while ((thisname = *names++) != NULL) { /* get the number to format */ num = *numbers++; color = 0; /* display only non-zero numbers */ if (num != 0) { /* write the previous name */ if (lastname != NULL) { display_write(-1, -1, 0, 0, lastname); } #ifdef ENABLE_COLOR if (cidx != NULL) { /* choose a color */ color = color_test(*cidx++, num); } #endif /* write this number if positive */ if (num > 0) { display_write(x, y, color, 0, itoa(num)); } /* defer writing this name */ lastname = thisname; /* next iteration will not start at x, y */ x = y = -1; } } /* if the last string has a separator on the end, it has to be written with care */ if (lastname != NULL) { if ((num = strlen(lastname)) > 1 && lastname[num-2] == ',' && lastname[num-1] == ' ') { display_fmt(-1, -1, 0, 1, "%.*s", num-2, lastname); } else { display_write(-1, -1, 0, 1, lastname); } } } static void summary_format_memory(int x, int y, long *numbers, char **names, int *cidx) { register long num; register int color; register char *thisname; register char *lastname = NULL; /* format each number followed by its string */ while ((thisname = *names++) != NULL) { /* get the number to format */ num = *numbers++; color = 0; /* display only non-zero numbers */ if (num != 0) { /* write the previous name */ if (lastname != NULL) { display_write(-1, -1, 0, 0, lastname); } /* defer writing this name */ lastname = thisname; #ifdef ENABLE_COLOR /* choose a color */ color = color_test(*cidx++, num); #endif /* is this number in kilobytes? */ if (thisname[0] == 'K') { display_write(x, y, color, 0, format_k(num)); lastname++; } else { display_write(x, y, color, 0, itoa((int)num)); } /* next iteration will not start at x, y */ x = y = -1; } } /* if the last string has a separator on the end, it has to be written with care */ if (lastname != NULL) { if ((num = strlen(lastname)) > 1 && lastname[num-2] == ',' && lastname[num-1] == ' ') { display_fmt(-1, -1, 0, 1, "%.*s", num-2, lastname); } else { display_write(-1, -1, 0, 1, lastname); } } } /* * int display_resize() * * Reallocate buffer space needed by the display package to accomodate * a new screen size. Must be called whenever the screen's size has * changed. Returns the number of lines available for displaying * processes or -1 if there was a problem allocating space. */ int display_resize() { register int top_lines; register int newsize; /* calculate the current dimensions */ /* if operating in "dumb" mode, we only need one line */ top_lines = smart_terminal ? screen_length : 1; /* we don't want more than MAX_COLS columns, since the machine-dependent modules make static allocations based on MAX_COLS and we don't want to run off the end of their buffers */ display_width = screen_width; if (display_width >= MAX_COLS) { display_width = MAX_COLS - 1; } /* see how much space we need */ newsize = top_lines * (MAX_COLS + 1); /* reallocate only if we need more than we already have */ if (newsize > bufsize) { /* deallocate any previous buffer that may have been there */ if (screenbuf != NULL) { free(screenbuf); } if (colorbuf != NULL) { free(colorbuf); } /* allocate space for the screen and color buffers */ bufsize = newsize; screenbuf = (char *)calloc(bufsize, sizeof(char)); colorbuf = (char *)calloc(bufsize, sizeof(char)); if (screenbuf == NULL || colorbuf == NULL) { /* oops! */ return(-1); } } else { /* just clear them out */ memzero(screenbuf, bufsize); memzero(colorbuf, bufsize); } /* adjust total lines on screen to lines available for procs */ top_lines -= y_procs; /* return number of lines available */ /* for dumb terminals, pretend like we can show any amount */ return(smart_terminal ? top_lines : Largest); } int display_lines() { return(smart_terminal ? screen_length : Largest); } int display_columns() { return(display_width); } /* * int display_init(struct statics *statics) * * Initialize the display system based on information in the statics * structure. Returns the number of lines available for displaying * processes or -1 if there was an error. */ int display_init(struct statics *statics) { register int top_lines; register char **pp; register char *p; register int *ip; register int i; /* certain things may influence the screen layout, so look at those first */ /* More than one core will shif the parts of the display down */ if (enable_ncpus != 0 && n_cpus > 1) { /* adjust screen placements */ y_mem = y_mem + n_cpus -1; y_swap = y_swap + n_cpus -1; y_message = y_message + n_cpus -1; y_header = y_header + n_cpus -1; y_idlecursor = y_idlecursor + n_cpus -1; y_procs = y_procs + n_cpus -1; } /* a kernel line shifts parts of the display down */ kernel_names = statics->kernel_names; if ((num_kernel = string_count(kernel_names)) > 0) { /* adjust screen placements */ y_mem++; y_swap++; y_message++; y_header++; y_idlecursor++; y_procs++; } /* a swap line shifts parts of the display down one */ swap_names = statics->swap_names; if ((num_swap = string_count(swap_names)) > 0) { /* adjust screen placements */ y_message++; y_header++; y_idlecursor++; y_procs++; } /* call resize to do the dirty work */ top_lines = display_resize(); /* * save pointers and allocate space for names. Even if top_lines <= -1 * the code will dereference many of these pointers and arrays. */ procstate_names = statics->procstate_names; num_procstates = string_count(procstate_names); lprocstates = (int *)calloc(num_procstates, sizeof(int)); cpustate_names = statics->cpustate_names; num_cpustates = string_count(cpustate_names); lcpustates = (int *)calloc(num_cpustates, sizeof(int)); cpustate_columns = (int *)calloc(num_cpustates, sizeof(int)); memory_names = statics->memory_names; num_memory = string_count(memory_names); /* calculate starting columns where needed */ cpustate_total_length = 0; pp = cpustate_names; ip = cpustate_columns; while (*pp != NULL) { *ip++ = cpustate_total_length; if ((i = strlen(*pp++)) > 0) { cpustate_total_length += i + 8; } } #ifdef ENABLE_COLOR /* set up color tags for loadavg */ load_cidx[0] = color_tag("1min"); load_cidx[1] = color_tag("5min"); load_cidx[2] = color_tag("15min"); /* find header color */ header_cidx = color_tag("header"); /* color tags for cpu states */ cpustate_cidx = (int *)malloc(num_cpustates * sizeof(int)); i = 0; p = strcpyend(scratchbuf, "cpu."); while (i < num_cpustates) { strcpy(p, cpustate_names[i]); cpustate_cidx[i++] = color_tag(scratchbuf); } /* color tags for kernel */ if (num_kernel > 0) { kernel_cidx = (int *)malloc(num_kernel * sizeof(int)); i = 0; p = strcpyend(scratchbuf, "kernel."); while (i < num_kernel) { strcpy(p, homogenize(kernel_names[i]+1)); kernel_cidx[i++] = color_tag(scratchbuf); } } /* color tags for memory */ memory_cidx = (int *)malloc(num_memory * sizeof(int)); i = 0; p = strcpyend(scratchbuf, "memory."); while (i < num_memory) { strcpy(p, homogenize(memory_names[i]+1)); memory_cidx[i++] = color_tag(scratchbuf); } /* color tags for swap */ if (num_swap > 0) { swap_cidx = (int *)malloc(num_swap * sizeof(int)); i = 0; p = strcpyend(scratchbuf, "swap."); while (i < num_swap) { strcpy(p, homogenize(swap_names[i]+1)); swap_cidx[i++] = color_tag(scratchbuf); } } #endif /* return number of lines available (or error) */ return(top_lines); } static void pr_loadavg(double avg, int i) { int color = 0; #ifdef ENABLE_COLOR color = color_test(load_cidx[i], (int)(avg * 100)); #endif display_fmt(x_loadave + X_LOADAVEWIDTH * i, y_loadave, color, 0, avg < 10.0 ? " %5.2f" : " %5.1f", avg); display_write(-1, -1, 0, 0, (i < 2 ? "," : ";")); } void i_loadave(int mpid, double *avenrun) { register int i; /* mpid == -1 implies this system doesn't have an _mpid */ if (mpid != -1) { display_fmt(0, 0, 0, 0, "last pid: %5d; load avg:", mpid); x_loadave = X_LOADAVE; } else { display_write(0, 0, 0, 0, "load averages:"); x_loadave = X_LOADAVE - X_LASTPIDWIDTH; } for (i = 0; i < 3; i++) { pr_loadavg(avenrun[i], i); } lmpid = mpid; } void u_loadave(int mpid, double *avenrun) { register int i; if (mpid != -1) { /* change screen only when value has really changed */ if (mpid != lmpid) { display_fmt(x_lastpid, y_lastpid, 0, 0, "%5d", mpid); lmpid = mpid; } } /* display new load averages */ for (i = 0; i < 3; i++) { pr_loadavg(avenrun[i], i); } } static char minibar_buffer[64]; #define MINIBAR_WIDTH 20 void i_minibar(int (*formatter)(char *, int)) { (void)((*formatter)(minibar_buffer, MINIBAR_WIDTH)); display_write(x_minibar, y_minibar, 0, 0, minibar_buffer); } void u_minibar(int (*formatter)(char *, int)) { (void)((*formatter)(minibar_buffer, MINIBAR_WIDTH)); display_write(x_minibar, y_minibar, 0, 0, minibar_buffer); } static int uptime_days; static int uptime_hours; static int uptime_mins; static int uptime_secs; void i_uptime(time_t *bt, time_t *tod) { time_t uptime; if (*bt != -1) { uptime = *tod - *bt; uptime += 30; uptime_days = uptime / 86400; uptime %= 86400; uptime_hours = uptime / 3600; uptime %= 3600; uptime_mins = uptime / 60; uptime_secs = uptime % 60; /* * Display the uptime. */ display_fmt(x_uptime, y_uptime, 0, 0, " up %d+%02d:%02d:%02d", uptime_days, uptime_hours, uptime_mins, uptime_secs); } } void u_uptime(time_t *bt, time_t *tod) { i_uptime(bt, tod); } void i_timeofday(time_t *tod) { /* * 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). */ int x; /* where on the screen do we start? */ x = (smart_terminal ? screen_width : 79) - 8; /* but don't bump in to uptime */ if (x < x_uptime + 19) { x = x_uptime + 19; } /* display it */ display_fmt(x, 0, 0, 1, "%-8.8s", &(ctime(tod)[11])); } static int ltotal = 0; static int lthreads = 0; /* * *_procstates(total, brkdn, names) - print the process summary line */ void i_procstates(int total, int *brkdn, int threads) { /* write current number of processes and remember the value */ display_fmt(0, y_procstate, 0, 0, "%d %s: ", total, threads ? "threads" : "processes"); ltotal = total; /* remember where the summary starts */ x_procstate = virt_x; if (total > 0) { /* format and print the process state summary */ summary_format(-1, -1, brkdn, procstate_names, NULL); /* save the numbers for next time */ memcpy(lprocstates, brkdn, num_procstates * sizeof(int)); lthreads = threads; } } void u_procstates(int total, int *brkdn, int threads) { /* if threads state has changed, do a full update */ if (lthreads != threads) { i_procstates(total, brkdn, threads); return; } /* update number of processes only if it has changed */ if (ltotal != total) { display_fmt(0, y_procstate, 0, 0, "%d", total); /* if number of digits differs, rewrite the label */ if (digits(total) != digits(ltotal)) { display_fmt(-1, -1, 0, 0, " %s: ", threads ? "threads" : "processes"); x_procstate = virt_x; } /* save new total */ ltotal = total; } /* see if any of the state numbers has changed */ if (total > 0 && memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0) { /* format and update the line */ summary_format(x_procstate, y_procstate, brkdn, procstate_names, NULL); memcpy(lprocstates, brkdn, num_procstates * sizeof(int)); } } /* * *_cpustates(states, names) - print the cpu state percentages */ /* cpustates_tag() calculates the correct tag to use to label the line */ char * cpustates_tag() { register char *use; static char *short_tag = "CPU: "; static char *long_tag = "CPU states: "; /* if length + strlen(long_tag) >= screen_width, then we have to use the shorter tag (we subtract 2 to account for ": ") */ if (cpustate_total_length + (int)strlen(long_tag) - 2 >= screen_width) { use = short_tag; } else { use = long_tag; } /* set x_cpustates accordingly then return result */ x_cpustates = strlen(use); return(use); } void i_cpustates(int *states) { int value; char **names; char *thisname; int *colp; int color = 0; int cpu; #ifdef ENABLE_COLOR int *cidx = cpustate_cidx; #endif /* initialize */ names = cpustate_names; colp = cpustate_columns; /* print tag */ if (enable_ncpus !=0 && n_cpus > 1) { for (cpu = 0; cpu < n_cpus; ++cpu) { int y_pos = y_cpustates; y_pos = y_pos + cpu; colp = cpustate_columns; names = cpustate_names; display_write(0, y_cpustates+cpu, 0, 0, cpustates_tag()); /* now walk thru the names and print the line */ while ((thisname = *names++) != NULL) { if (*thisname != '\0') { /* retrieve the value and remember it */ value = *states; #ifdef ENABLE_COLOR /* determine color number to use */ color = color_test(*cidx++, value/10); #endif /* if percentage is >= 1000, print it as 100% */ display_fmt(x_cpustates + *colp, y_pos, color, 0, (value >= 1000 ? "%4.0f%% %s%s" : "%4.1f%% %s%s"), ((float)value)/10., thisname, *names != NULL ? ", " : ""); } /* increment */ colp++; states++; } /* copy over values into "last" array */ memcpy(lcpustates, states, num_cpustates * sizeof(int)); } } else { display_write(0, y_cpustates, 0, 0, cpustates_tag()); /* now walk thru the names and print the line */ while ((thisname = *names++) != NULL) { if (*thisname != '\0') { /* retrieve the value and remember it */ value = *states; #ifdef ENABLE_COLOR /* determine color number to use */ color = color_test(*cidx++, value/10); #endif /* if percentage is >= 1000, print it as 100% */ display_fmt(x_cpustates + *colp, y_cpustates, color, 0, (value >= 1000 ? "%4.0f%% %s%s" : "%4.1f%% %s%s"), ((float)value)/10., thisname, *names != NULL ? ", " : ""); } /* increment */ colp++; states++; } /* copy over values into "last" array */ memcpy(lcpustates, states, num_cpustates * sizeof(int)); } } void u_cpustates(int *states) { int value; char **names = cpustate_names; char *thisname; int *lp; int *colp; int color = 0; int cpu; #ifdef ENABLE_COLOR int *cidx = cpustate_cidx; #endif if (enable_ncpus != 0 && n_cpus > 1 ) { for (cpu = 0; cpu < n_cpus; ++cpu) { lp = lcpustates; int y_pos = y_cpustates; y_pos = y_pos + cpu; colp = cpustate_columns; char **names = cpustate_names; /* we could be much more optimal about this */ while ((thisname = *names++) != NULL) { if (*thisname != '\0') { /* yes, change it */ /* retrieve value and remember it */ value = *states; #ifdef ENABLE_COLOR /* determine color number to use */ color = color_test(*cidx, value/10); #endif /* if percentage is >= 1000, print it as 100% */ display_fmt(x_cpustates + *colp, y_pos, color, 0, (value >= 1000 ? "%4.0f" : "%4.1f"), ((double)value)/10.); #ifdef ENABLE_COLOR cidx++; #endif } /* increment and move on */ lp++; states++; colp++; } } } else { lp = lcpustates; colp = cpustate_columns; /* we could be much more optimal about this */ while ((thisname = *names++) != NULL) { if (*thisname != '\0') { /* did the value change since last time? */ if (*lp != *states) { /* yes, change it */ /* retrieve value and remember it */ value = *states; #ifdef ENABLE_COLOR /* determine color number to use */ color = color_test(*cidx, value/10); #endif /* if percentage is >= 1000, print it as 100% */ display_fmt(x_cpustates + *colp, y_cpustates, color, 0, (value >= 1000 ? "%4.0f" : "%4.1f"), ((double)value)/10.); /* remember it for next time */ *lp = value; } #ifdef ENABLE_COLOR cidx++; #endif } /* increment and move on */ lp++; states++; colp++; } } } void z_cpustates() { register int i = 0; register char **names = cpustate_names; register char *thisname; register int *lp; int cpu; /* print tag */ if (enable_ncpus != 0 && n_cpus > 1) { for (cpu = 0; cpu < n_cpus; ++cpu) { display_write(0, y_cpustates + cpu, 0, 0, cpustates_tag()); char **names = cpustate_names; i = 0; while ((thisname = *names++) != NULL) { if (*thisname != '\0') { display_fmt(-1, -1, 0, 0, "%s %% %s", i++ == 0 ? "" : ", ", thisname); } } /* fill the "last" array with all -1s, to insure correct updating */ lp = lcpustates; i = num_cpustates; while (--i >= 0) { *lp++ = -1; } } } else { display_write(0, y_cpustates, 0, 0, cpustates_tag()); while ((thisname = *names++) != NULL) { if (*thisname != '\0') { display_fmt(-1, -1, 0, 0, "%s %% %s", i++ == 0 ? "" : ", ", thisname); } } /* fill the "last" array with all -1s, to insure correct updating */ lp = lcpustates; i = num_cpustates; while (--i >= 0) { *lp++ = -1; } } } /* * *_kernel(stats) - print "Kernel: " followed by the kernel summary string * * Assumptions: cursor is on "lastline", the previous line */ void i_kernel(int *stats) { if (num_kernel > 0) { display_write(0, y_kernel, 0, 0, "Kernel: "); /* format and print the kernel summary */ summary_format(x_kernel, y_kernel, stats, kernel_names, kernel_cidx); } } void u_kernel(int *stats) { if (num_kernel > 0) { /* format the new line */ summary_format(x_kernel, y_kernel, stats, kernel_names, kernel_cidx); } } /* * *_memory(stats) - print "Memory: " followed by the memory summary string * * Assumptions: cursor is on "lastline", the previous line */ void i_memory(long *stats) { display_write(0, y_mem, 0, 0, "Memory: "); /* format and print the memory summary */ summary_format_memory(x_mem, y_mem, stats, memory_names, memory_cidx); } void u_memory(long *stats) { /* format the new line */ summary_format_memory(x_mem, y_mem, stats, memory_names, memory_cidx); } /* * *_swap(stats) - print "Swap: " followed by the swap summary string * * Assumptions: cursor is on "lastline", the previous line * * These functions only print something when num_swap > 0 */ void i_swap(long *stats) { if (num_swap > 0) { /* print the tag */ display_write(0, y_swap, 0, 0, "Swap: "); /* format and print the swap summary */ summary_format_memory(x_swap, y_swap, stats, swap_names, swap_cidx); } } void u_swap(long *stats) { if (num_swap > 0) { /* format the new line */ summary_format_memory(x_swap, y_swap, stats, swap_names, swap_cidx); } } /* * *_message() - print the next pending message line, or erase the one * that is there. * * Note that u_message is (currently) the same as i_message. * * Assumptions: lastline is consistent */ /* * i_message is funny because it gets its message asynchronously (with * respect to screen updates). Messages are taken out of the * circular message_buf and displayed one at a time. */ void i_message(struct timeval *now) { struct timeval my_now; int i = 0; dprintf("i_message(%08x)\n", now); /* if now is NULL we have to get it ourselves */ if (now == NULL) { time_get(&my_now); now = &my_now; } /* now that we have been called, messages no longer need to be held */ message_hold = 0; dprintf("i_message: now %d, message_time %d\n", now->tv_sec, message_time.tv_sec); if (smart_terminal) { /* is it time to change the message? */ if (timercmp(now, &message_time, > )) { /* yes, free the current message */ dprintf("i_message: timer expired\n"); if (message_current != NULL) { free(message_current); message_current = NULL; } /* is there a new message to be displayed? */ if (message_first != message_last) { /* move index to next message */ if (++message_first == MAX_MESSAGES) message_first = 0; /* make the next message the current one */ message_current = message_buf[message_first]; /* show it */ dprintf("i_message: showing \"%s\"\n", message_current); display_move(0, y_message); screen_standout(message_current); i = strlen(message_current); /* set the expiration timer */ message_time = *now; message_time.tv_sec += MESSAGE_DISPLAY_TIME; /* clear the rest of the line */ screen_cleareol(message_length - i); putchar('\r'); message_length = i; } else { /* just clear what was there before, if anything */ if (message_length > 0) { display_move(0, y_message); screen_cleareol(message_length); putchar('\r'); message_length = 0; } } } } } void u_message(struct timeval *now) { i_message(now); } static int header_length; /* * *_header(text) - print the header for the process area * * Assumptions: cursor is on the previous line and lastline is consistent */ void i_header(char *text) { int header_color = 0; #ifdef ENABLE_COLOR header_color = color_test(header_cidx, 0); #endif header_length = strlen(text); if (header_status) { display_write(x_header, y_header, header_color, 1, text); } } /*ARGSUSED*/ void u_header(char *text) { int header_color = 0; #ifdef ENABLE_COLOR header_color = color_test(header_cidx, 0); #endif display_write(x_header, y_header, header_color, 1, header_status ? text : ""); } /* * *_process(line, thisline) - print one process line * * Assumptions: lastline is consistent */ void i_process(int line, char *thisline) { /* truncate the line to conform to our current screen width */ thisline[display_width] = '\0'; /* write the line out */ display_write(0, y_procs + line, 0, 1, thisline); } void u_process(int line, char *new_line) { i_process(line, new_line); } void i_endscreen() { if (smart_terminal) { /* move the cursor to a pleasant place */ display_move(x_idlecursor, y_idlecursor); } else { /* separate this display from the next with some vertical room */ fputs("\n\n", stdout); } fflush(stdout); } void u_endscreen() { if (smart_terminal) { /* clear-to-end the display */ display_cte(); /* move the cursor to a pleasant place */ display_move(x_idlecursor, y_idlecursor); fflush(stdout); } else { /* separate this display from the next with some vertical room */ fputs("\n\n", stdout); } } void display_header(int t) { header_status = t != 0; } void message_mark() { message_barrier = Yes; } void message_expire() { message_time.tv_sec = 0; message_time.tv_usec = 0; } void message_flush() { message_first = message_last; message_time.tv_sec = 0; message_time.tv_usec = 0; } /* * void new_message_v(char *msgfmt, va_list ap) * * Display a message in the message area. This function takes a va_list for * the arguments. Safe to call before display_init. This function only * queues a message for display, and allowed for multiple messages to be * queued. The i_message function drains the queue and actually writes the * messages on the display. */ void new_message_v(char *msgfmt, va_list ap) { int i; int empty; char msg[MAX_COLS]; /* if message_barrier is active, remove all pending messages */ if (message_barrier) { message_flush(); message_barrier = No; } /* first, format the message */ (void) vsnprintf(msg, sizeof(msg), msgfmt, ap); /* where in the buffer will it go? */ i = message_last + 1; if (i >= MAX_MESSAGES) i = 0; /* make sure the buffer is not full */ if (i != message_first) { /* insert it in to message_buf */ message_buf[i] = strdup(msg); dprintf("new_message_v: new message inserted in slot %d\n", i); /* remember if the buffer is empty and set the index */ empty = message_last == message_first; message_last = i; /* is message_buf otherwise empty and have we started displaying? */ if (empty && !message_hold) { /* we can display the message now */ i_message(NULL); } } } /* * void new_message(int type, char *msgfmt, ...) * * Display a message in the message area. It is safe to call this function * before display_init. Messages logged before the display is drawn will be * held and displayed later. */ void new_message(char *msgfmt, ...) { va_list ap; va_start(ap, msgfmt); new_message_v(msgfmt, ap); va_end(ap); } /* * void message_error(char *msgfmt, ...) * * Put an error message in the message area. It is safe to call this function * before display_init. Messages logged before the display is drawn will be * held and displayed later. */ void message_error(char *msgfmt, ...) { va_list ap; va_start(ap, msgfmt); new_message_v(msgfmt, ap); fflush(stdout); va_end(ap); } /* * void message_clear() * * Clear message area and flush all pending messages. */ void message_clear() { /* remove any existing message */ if (message_current != NULL) { display_move(0, y_message); screen_cleareol(message_length); free(message_current); message_current = 0; } /* flush all pending messages */ message_flush(); } /* * void message_prompt_v(int so, char *msgfmt, va_list ap) * * Place a prompt in the message area. A prompt is different from a * message as follows: it is displayed immediately, overwriting any * message that may already be there, it may be highlighted in standout * mode (if "so" is true), the cursor is left to rest at the end of the * prompt. This call causes all pending messages to be flushed. */ void message_prompt_v(int so, char *msgfmt, va_list ap) { char msg[MAX_COLS]; int i; /* clear out the message buffer */ message_flush(); /* format the message */ i = vsnprintf(msg, sizeof(msg), msgfmt, ap); /* this goes over any existing message */ display_move(0, y_message); /* clear the entire line */ screen_cleareol(message_length); /* show the prompt */ if (so) { screen_standout(msg); } else { fputs(msg, stdout); } /* make it all visible */ fflush(stdout); /* even though we dont keep a copy of the prompt, track its length */ message_length = i < MAX_COLS ? i : MAX_COLS; } /* * void message_prompt(char *msgfmt, ...) * * Place a prompt in the message area (see message_prompt_v). */ void message_prompt(char *msgfmt, ...) { va_list ap; va_start(ap, msgfmt); message_prompt_v(Yes, msgfmt, ap); va_end(ap); } void message_prompt_plain(char *msgfmt, ...) { va_list ap; va_start(ap, msgfmt); message_prompt_v(No, msgfmt, ap); va_end(ap); } /* * int readline(char *buffer, int size, int numeric) * * Read a line of input from the terminal. The line is placed in * "buffer" not to exceed "size". If "numeric" is true then the input * can only consist of digits. This routine handles all character * editing while keeping the terminal in cbreak mode. If "numeric" * is true then the number entered is returned. Otherwise the number * of character read in to "buffer" is returned. */ int readline(char *buffer, int size, int numeric) { register char *ptr = buffer; register char ch; register char cnt = 0; /* allow room for null terminator */ size -= 1; /* read loop */ while ((fflush(stdout), read(0, ptr, 1) > 0)) { /* newline or return means we are done */ if ((ch = *ptr) == '\n' || ch == '\r') { break; } /* handle special editing characters */ if (ch == ch_kill) { /* return null string */ *buffer = '\0'; putchar('\r'); return(-1); } else if (ch == ch_werase) { /* erase previous word */ if (cnt <= 0) { /* none to erase! */ putchar('\7'); } else { /* * First: remove all spaces till the first-non-space * Second: remove all non-spaces till the first-space */ while(cnt > 0 && ptr[-1] == ' ') { fputs("\b \b", stdout); ptr--; cnt--; } while(cnt > 0 && ptr[-1] != ' ') { fputs("\b \b", stdout); ptr--; cnt--; } } } else if (ch == ch_erase) { /* erase previous character */ if (cnt <= 0) { /* none to erase! */ putchar('\7'); } else { fputs("\b \b", stdout); ptr--; cnt--; } } /* check for character validity and buffer overflow */ else if (cnt == size || (numeric && !isdigit((int)ch)) || !isprint((int)ch)) { /* not legal */ putchar('\7'); } else { /* echo it and store it in the buffer */ putchar(ch); ptr++; cnt++; } } /* all done -- null terminate the string */ *ptr = '\0'; /* add response length to message_length */ message_length += cnt; /* return either inputted number or string length */ putchar('\r'); return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt); } void display_pagerstart() { display_clear(); } void display_pagerend() { char ch; screen_standout("Hit any key to continue: "); fflush(stdout); (void) read(0, &ch, 1); } void display_pager(char *fmt, ...) { va_list ap; int ch; char readch; char buffer[MAX_COLS]; char *data; /* format into buffer */ va_start(ap, fmt); (void) vsnprintf(buffer, MAX_COLS, fmt, ap); va_end(ap); data = buffer; while ((ch = *data++) != '\0') { putchar(ch); if (ch == '\n') { if (++curr_y >= screen_length - 1) { screen_standout("...More..."); fflush(stdout); (void) read(0, &readch, 1); putchar('\r'); switch(readch) { case '\r': case '\n': curr_y--; break; case 'q': return; default: curr_y = 0; } } } } }