chris@umcp-cs.UUCP (08/01/83)
Vmpic is a slightly expanded and much modified version of vmstat. Vmpic shows the statistics in windows and is therefore easier(?) to read. It uses the Maryland Windows Library and it will run only on 4.1BSD (well, actually I just can't try it on 4.1a or 4.1c but I severely doubt it would work). - Chris : Run this shell script with "sh" not "csh" PATH=:/bin:/usr/bin:/usr/ucb export PATH all=FALSE if [ $1x = -ax ]; then all=TRUE fi /bin/echo 'Extracting vmpic.c' sed 's/^X//' <<'//go.sysin dd *' >vmpic.c #ifndef lint static char rcsid[] = "@(#)$Header: vmpic.v Revision 1.1 83/03/18 19:21:09 chris Stab$ vmpic.c U of Maryland ACT 5-Mar-1983"; #endif X/* * vmpic - vmstat using full screen * % cc -O -s -o vmpic vmpic.c -lwinlib -ljobs -ltermlib %i cc -O -s -o /usr/local/vmpic vmpic.c -lwinlib -ljobs -ltermlib; chmod g+s /usr/local/vmpic * * Joe Pallas 5-Mar-1983 * Modified: 5-Mar-1983 ACT for window library, load average * 7-Mar-1983 ACT for multiple windows * 8-Mar-1983 ACT highlight system %cpu if >= 40.0 * 18-Mar-1983 ACT added ^L check, minor changes * 19-Mar-1983 Andrew & Patrick hacked for vmpic login... * (a REALLY horrible hack!) * 19-Mar-1983 ACT Cleaned up above mod a bit * 2-May-1983 ACT Cleaned up yet more (fix from JIP) */ #include <stdio.h> #include <sys/param.h> #include <sys/vm.h> #include <sys/dk.h> #include <sys/stat.h> #include <nlist.h> #include <sys/ioctl.h> #include <local/window.h> #define LOOPSIZ (LOOPPAGES / CLSIZE) X/* This one is an /etc/config option; not available in h files */ struct nlist nml[] = { #define X_CPTIME 0 {"_cp_time"}, #define X_RATE 1 {"_rate"}, #define X_TOTAL 2 {"_total"}, #define X_DEFICIT 3 {"_deficit"}, #define X_FORKSTAT 4 {"_forkstat"}, #define X_SUM 5 {"_sum"}, #define X_FIRSTFREE 6 {"_firstfree"}, #define X_MAXFREE 7 {"_maxfree"}, #define X_BOOTIME 8 {"_bootime"}, #define X_DK_XFER 9 {"_dk_xfer"}, #define X_TK_NIN 10 {"_tk_nin"}, #define X_TK_NOUT 11 {"_tk_nout"}, #define X_AVENRUN 12 {"_avenrun"}, #define X_LOTSFREE 13 {"_lotsfree"}, #define X_DESFREE 14 {"_desfree"}, #define X_MINFREE 15 {"_minfree"}, #define X_HZ 16 {"_hz"}, #define X_LP_NOUT 17 {"_lp_nout"}, 0 }; char *ctime (); double stat1 (); char buf[100]; int firstfree, maxfree, minfree, lotsfree, desfree, deficit, hz, kmem; time_t bootime; double etime; double avenrun[3]; X/* Grouping of stuff so can use structure assignment */ struct { long time[CPUSTATES]; long xfer[DK_NDRIVE]; struct vmmeter Rate; struct vmtotal Total; struct vmmeter Sum; X/* struct forkstat Forkstat; -- not currently used */ long tk_nin; long tk_nout; int lp_nout; } s, s1; /* s1 == the previous s */ X/* This structure is used to specify what to read from the kernel. k_item is the address to read into, k_size is the number of bytes, and k_index is the index into the namelist "nml" which determines the kernel address. */ struct kvalues { caddr_t k_item; off_t k_size; int k_index; }; struct kvalues LoopTable[] = { (caddr_t) s.time, sizeof s.time, X_CPTIME, (caddr_t) s.xfer, sizeof s.xfer, X_DK_XFER, (caddr_t) &s.Rate, sizeof s.Rate, X_RATE, (caddr_t) &s.Sum, sizeof s.Sum, X_SUM, X/* (caddr_t) &s.Forkstat, sizeof s.Forkstat, X_FORKSTAT, */ (caddr_t) &s.Total, sizeof s.Total, X_TOTAL, (caddr_t) &s.tk_nin, sizeof s.tk_nin, X_TK_NIN, (caddr_t) &s.tk_nout, sizeof s.tk_nout, X_TK_NOUT, (caddr_t) &deficit, sizeof deficit, X_DEFICIT, (caddr_t) avenrun, sizeof avenrun, X_AVENRUN, (caddr_t) &s.lp_nout, sizeof s.lp_nout, X_LP_NOUT, 0, 0, 0 }; struct kvalues InitTable[] = { (caddr_t) &firstfree, sizeof firstfree, X_FIRSTFREE, (caddr_t) &maxfree, sizeof maxfree, X_MAXFREE, (caddr_t) &bootime, sizeof bootime, X_BOOTIME, (caddr_t) &lotsfree, sizeof lotsfree, X_LOTSFREE, (caddr_t) &desfree, sizeof desfree, X_DESFREE, (caddr_t) &minfree, sizeof minfree, X_MINFREE, (caddr_t) &hz, sizeof hz, X_HZ, 0, 0, 0 }; X/* This determines what windows to display */ X/* The 4 ints specify the position & size; the two strings are optional, and are the label and initial contents for the window */ struct initscreen { int i_x, i_y, i_xe, i_ye; char *i_lbl, *i_str; } InitScreen[] = { #define W_TIME (wins[0]) X/*0*/ 18, 0, 38, 1, 0, "$SYSTEM STATUS", #define W_PROC (wins[1]) X/*1*/ 0, 1, 15, 7, "Processes", "Runnable\nDisk Wait\nPage Wait\nSwapped\nSleeping", #define W_BOOTIME (wins[2]) X/*2*/ 18, 1, 39, 1, 0, "Last reboot @ ", #define W_DISK (wins[3]) X/*3*/ 17, 2, 12, 6, "Disk Xfers", "D0\nD1\nD2\nD3", #define W_FAULTS (wins[4]) X/*4*/ 32, 3, 18, 6, "Faults", "Interrupts\nDZ Pseudo\nSyscalls\nContext Sw", #define W_PAGE (wins[5]) X/*5*/ 0, 8, 19, 10, "Paging", "Reclaims\nPage Ins\nPage Outs\nPages Freed\nDeficit\nScan Rate\nSwap Ins\nSwap Outs", #define W_CPU (wins[6]) X/*6*/ 22, 9, 21, 6, "CPU Usage", "User (normal)\t %\nUser (nice)\t %\nSystem\t\t %\nIdle\t\t %", #define W_MEM (wins[7]) X/*7*/ 22, 15, 28, 5, "Memory", "Active Virtual pages\nActive Real\t pages\nFree\t\t pages", #define W_TERM (wins[8]) X/*8*/ 0, 18, 15, 5, "Terminals", "Input\nOutput\nLP Out", #define W_UP (wins[9]) X/*9*/ 22, 20, 20, 1, 0, "up", #define W_LOAD (wins[10]) X/*10*/ 22, 21, 34, 1, 0, "Load average:", #define W_MAIL (wins[11]) X/*11*/ 22, 22, 4, 1, 0, "$Mail", }; #define NWINS (sizeof InitScreen/sizeof *InitScreen) Win *wins[NWINS]; main (argc, argv) int argc; char **argv; { register i; register struct initscreen *ip; register struct vmmeter *v; time_t now, mtime; int days, hrs, mins; int iter, sleeptime, nintv; char *str; char mail[300]; double f; struct stat st; int NewMail = 0; /* Read the namelist */ nlist ("/vmunix", nml); if (nml[0].n_type == 0) { fprintf (stderr, "No /vmunix namelist\n"); exit (1); } if (nml[X_LP_NOUT].n_type == 0) nml[X_LP_NOUT].n_value = 0; /* Open /dev/kmem */ kmem = open ("/dev/kmem", 0); if (kmem < 0) { fprintf (stderr, "Can't open /dev/kmem\n"); exit (1); } if (argc < 2) { iter = 1; sleeptime = 0; #if 0 /* This is the way it *should* be done, */ if (getppid () == 1) { /* Parent is init, must be a "login shell" */ #else /* but with our "fakelogin" program ppid != 1, sigh */ if (getuid () == 199) { /* ugly ... */ #endif iter = 0; sleeptime = 5; } } else if (argc == 2) iter = 0, sleeptime = atoi (argv[1]); else if (argc >= 3) iter = atoi (argv[2]), sleeptime = atoi (argv[1]); if (ReadKernel (InitTable)) exit (1); time (&now); nintv = now - bootime; if (nintv <= 0 || nintv > 60*60*24*365*10) { fprintf (stderr, "Time makes no sense: namelist must be wrong\n"); exit (1); } /* Initialize windows */ if (Winit (0, 0)) { fprintf (stderr, "Sorry, this terminal doesn't support windows\n"); exit (1); } for (ip = InitScreen; ip < &InitScreen[NWINS]; ip++) { register Win *w; w = Wopen (0, ip->i_x, ip->i_y, ip->i_xe, ip->i_ye, 0, 0); if (w == 0) { Wcleanup (); fprintf (stderr, "Sorry, your screen is too small\n"); exit (1); } wins[ip - InitScreen] = w; Woncursor (w, 0); Wnewline (w, 1); Wwrap (w, 0); if (ip -> i_ye > 1) { Wframe (w); if (ip -> i_lbl) Wlabel (w, ip -> i_lbl, 0, 1); } if (str = ip -> i_str) { if (*str == '$') {/* Use inverse video */ Wsetmode (w, WINVERSE); str++; } Wputs (str, w); Wsetmode (w, 0); } } WSetRealCursor = 1; WRCurRow = 0; WRCurCol = 0; sprintf (mail, "/usr/spool/mail/%s", getenv ("USER")); if (stat (mail, &st) >= 0) mtime = st.st_mtime; sprintf (buf, "%.24s", ctime (&bootime)); Wputs (buf, W_BOOTIME); Whide (W_MAIL); /* This is the main loop. It should be a for(;;) but then everything would be way over on the right. */ loop: time (&now); /* Read the kernel stuff */ if (ReadKernel (LoopTable)) Wexit (1); /* Put info into windows */ WAcursor (W_TIME, 0, 14); sprintf (buf, "%.24s", ctime (&now)); Wputs (buf, W_TIME); etime = 0.; for (i = 0; i < CPUSTATES; i++) etime += s.time[i] - s1.time[i]; if (etime == 0.) etime = 1.; #define WPR(w,y,x,fmt,n) WAcursor (w, y, x), \ sprintf (buf, fmt, n), \ Wputs (buf, w) /* Processes */ WPR (W_PROC, 0, 10, "%3d", s.Total.t_rq);/* Running */ WPR (W_PROC, 1, 10, "%3d", s.Total.t_dw);/* Disk Wait */ WPR (W_PROC, 2, 10, "%3d", s.Total.t_pw);/* Page Wait */ WPR (W_PROC, 3, 10, "%3d", s.Total.t_sw);/* Swapped */ WPR (W_PROC, 4, 10, "%3d", s.Total.t_sl);/* Sleeping */ /* Virtual memory */ WPR (W_MEM, 0, 15, "%5d", s.Total.t_avm / 2);/* AVM */ WPR (W_MEM, 1, 15, "%5d", s.Total.t_rm / 2);/* ARM */ WAcursor (W_MEM, 2, 15); sprintf (buf, "%5d", i = s.Total.t_free / 2); if (i < minfree) Wsetmode (W_MEM, WBLINK|WINVERSE); else if (i < desfree) Wsetmode (W_MEM, WINVERSE); Wputs (buf, W_MEM); /* Free space */ Wsetmode (W_MEM, 0); v = nintv != 1 ? &s.Sum : &s.Rate; /* Paging */ WPR (W_PAGE, 0, 12, "%5d",/* Reclaims */ (v -> v_pgrec - (v -> v_xsfrec + v -> v_xifrec)) / nintv); WPR (W_PAGE, 1, 12, "%5d", v -> v_pgin / nintv);/* Pageins */ WPR (W_PAGE, 2, 12, "%5d", v -> v_pgout / nintv);/* Pageouts */ WPR (W_PAGE, 3, 12, "%5d", v -> v_dfree / nintv);/* Freed per sec */ WPR (W_PAGE, 4, 12, "%5d", deficit / 2);/* Shortfall */ f = (60.0 * v -> v_scan) / (LOOPSIZ * nintv); if (f >= 0.01) Wsetmode (W_PAGE, WINVERSE); WPR (W_PAGE, 5, 12, "%5.2f", f); Wsetmode (W_PAGE, 0); WPR (W_PAGE, 6, 12, "%5d", v -> v_swpin / nintv);/* Swap ins */ WPR (W_PAGE, 7, 12, "%5d", v -> v_swpout / nintv);/* Swap outs */ etime /= 60.; /* Disk transfers */ for (i = 0; i < DK_NDRIVE; i++) WPR (W_DISK, i, 6, "%4.0f", (s.xfer[i]-s1.xfer[i])/etime); /* Faults */ WPR (W_FAULTS, 0, 12, "%4d", v -> v_intr / nintv - hz);/* Interrupts */ WPR (W_FAULTS, 1, 12, "%4d", v -> v_pdma / nintv);/* DZ pseudo intr */ WPR (W_FAULTS, 2, 12, "%4d", v -> v_syscall / nintv);/* System calls */ WPR (W_FAULTS, 3, 12, "%4d", v -> v_swtch / nintv);/* Context sws */ /* CPU usage */ WPR (W_CPU, 0, 15, "%3.0f", stat1 (0));/* User mode (normal) */ WPR (W_CPU, 1, 15, "%3.0f", stat1 (1));/* User mode (nice) */ f = stat1 (2); if (f >= 40.) Wsetmode (W_CPU, WINVERSE); WPR (W_CPU, 2, 15, "%3.0f", f);/* System */ Wsetmode (W_CPU, 0); WPR (W_CPU, 3, 15, "%3.0f", stat1 (3));/* Idle */ /* Terminal I/O */ WPR (W_TERM, 0, 8, "%5.0f", (s.tk_nin-s1.tk_nin)/etime);/* In */ WPR (W_TERM, 1, 8, "%5.0f", (s.tk_nout-s1.tk_nout)/etime);/* Out */ WPR (W_TERM, 2, 8, "%5.0f", (s.lp_nout-s1.lp_nout)/etime);/* LP Out */ /* Load average */ WAcursor (W_LOAD, 0, 14); for (i = 0; i < 3; i++) { sprintf (buf, "%5.2f", avenrun[i]); if (avenrun[i] > 6.) Wsetmode (W_LOAD, WINVERSE); Wputs (buf, W_LOAD); Wsetmode (W_LOAD, 0); if (i != 2) Wputc (' ', W_LOAD); } /* Uptime */ WAcursor (W_UP, 0, 3); Wclear (W_UP, 0); i = now - bootime; days = i / (60*60*24); i -= days * 60*60*24; hrs = i / (60*60); i -= hrs * 60*60; mins = i / 60; i = 0; if (days) { sprintf (buf, "%d day%s", days, days == 1 ? "" : "s"); Wputs (buf, W_UP); i = 1; } if (hrs && mins) sprintf (buf, "%s%2d:%02d", i ? ", " : "", hrs, mins); else { buf[0] = 0; if (hrs > 0) sprintf (buf, "%s%d hr%s", i ? ", " : "", hrs, hrs == 1 ? "" : "s"); if (mins > 0) sprintf (buf, "%s%d min%s", i ? ", " : "", mins, mins == 1 ? "" : "s"); } Wputs (buf, W_UP); /* Mail */ if (stat (mail, &st) >= 0 && st.st_size) { if (st.st_mtime < st.st_atime) NewMail = 0; else if (st.st_mtime > mtime) NewMail++, mtime = st.st_mtime; } else NewMail = 0; if (NewMail) Wunhide (W_MAIL); else Whide (W_MAIL); /* Update screen and loop if desired */ Wrefresh (0); if (InputPending) { char c; while (InputPending) { read (0, &c, 1); if (c == 014) ScreenGarbaged++; ioctl (0, FIONREAD, &InputPending); } Wrefresh (0); } if (--iter == 0) Wexit (0); nintv = 1; s1 = s; if (sleeptime) sleep (sleeptime); goto loop; } X/* Return the %cpu for s.time[row] */ double stat1 (row) { double t; register i; t = 0.; for (i = 0; i < CPUSTATES; i++) t += s.time[i] - s1.time[i]; if (t == 0.) t = 1.; return (s.time[row] - s1.time[row]) * 100. / t; } X/* Read kernel values according to table starting at k */ ReadKernel (k) register struct kvalues *k; { while (k -> k_item) { lseek (kmem, (long) nml[k -> k_index].n_value, 0); if (read (kmem, (char *) k->k_item, k->k_size) != k->k_size) { fprintf (stderr, "Error reading kmem at %d\r\n", nml[k -> k_index].n_value); return -1; } k++; } return 0; } X/* The standard sleep creates problems because it uses longjmp() on an alarm to return. This one uses a flag instead, so is much safer. */ X/* @(#)sleep.c 4.1 (Berkeley) 12/21/80 */ #include <signal.h> static alarmed; sleep(n) unsigned n; { int sleepx(); unsigned altime; int (*alsig)() = SIG_DFL; int (*sigset())(); if (n==0) return; altime = alarm(1000); /* time to maneuver */ if (altime) { if (altime > n) altime -= n; else { n = altime; altime = 1; } } alsig = sigset(SIGALRM, sleepx); alarmed = 0; alarm(n); while (!alarmed) pause(); sigset (SIGALRM, alsig); alarm (altime); } static sleepx() { alarmed++; } //go.sysin dd * made=TRUE if [ $made = TRUE ]; then /bin/chmod 644 vmpic.c /bin/echo -n ' '; /bin/ls -ld vmpic.c fi /bin/echo 'Extracting vmpic.1l' sed 's/^X//' <<'//go.sysin dd *' >vmpic.1l X.TH VMPIC 1L "U of M Local" X.UC X.SH NAME vmpic \- report virtual memory and system statistics in windows X.SH SYNOPSIS X.B vmpic [ X.I interval [ X.I count ] ] X.SH DESCRIPTION X.B Vmpic delves into the system and normally reports certain statistics kept about process, virtual memory, disk, trap and cpu activity. The optional X.I interval argument causes X.B vmpic to report once each X.I interval seconds; ``vmpic 5'' will print what the system is doing every five seconds; this is a good choice of printing interval since this is how often the statistics are sampled in the system. If a X.I count is given, the statistics are repeated X.I count times. The display is done in windows, with each window being labeled as to genral contents and with one line per statistic in each window. Standout mode is used to highlight things which are considered unusual or bad for response. The screen can be completely redrawn by typing control-L. X.TP 10 X.I Processes X.B Running processes are those trying to use the CPU. Processes in X.B "Disk Wait" are waiting for disk (or other short-term I/O) operations to complete. Processes in X.B "Page Wait" are waiting for free memory pages. Process X.B Sleeping are those that are runnable or short sleepers (< 20 seconds); X.B "Swapped" processes are those that are runnable but swapped. X.TP 10 X.I Memory Information about the usage of virtual and real memory. X.B "Active Virtual" pages are those which belong to processes that are running or have run in the last 20 seconds. A ``page'' here is 1024 bytes. X.B "Active Real" pages are those which are in use by processes and are actually in core (as opposed to "virtually" existing, being out on disk). X.B Free memory is the number of pages not currently in use. X.TP 10 X.I Page Information about page faults and paging activity, and swaps. These are averaged each five seconds, and given in units per second. X.B Reclaims are pages that were freed but taken back by the process from which they were freed before being allocated anywhere else. Reclaims also include text pages that were given up by a process that exited, but taken back by a new instance of the same program. X.B "Page Ins" are pages that were transferered in from disk. X.B "Page Outs" are pages that were freed and transferred out to disk. X.B "Pages Freed" is the number of pages freed per second. This counts pages that were reclaimed, so is not necessarily the number of new free pages. X.B Deficit is the number of pages needed to swap someone in. Normally this will always be zero; if it is nonzero, the paging system is getting overloaded. The X.B "Scan Rate" is the rate at which the pageout daemon is running through physical memory and freeing pages. As the scan rate goes up, so does the number of pages freed, and probably the number of reclaims. If the pageout daemon gets back around to a page and it's still free, it gets paged out. I'm not sure just what units the scan rate is in. X.B "Swap Ins" is the number of process swap ins in the last 5 seconds; X.B "Swap Outs" is the number of process swap outs. X.TP 10 X.I "Disk Xfers" Disk operations per second (this field is system dependent). The numbers after each X.B D are the disk numbers. On our system, disk 0 is /usr, /, and some paging; disk 1 is /u; and disk 2 is /g, /tmp, and more paging. X.TP 10 X.I Faults Trap/interrupt rate averages per second over last 5 seconds. X.B Interrupts is interrupts per second, not including the clock. X.B "DZ Pseudo" is the number of DZ PDMA (Pseudo DMA) interrupts. X.B Syscalls is system calls per second. X.B "Context Sw" is the cpu context switch rate (switches/sec). X.TP 10 X.I "CPU Usage" is a breakdown of percentage usage of CPU time. X.B "User (normal)" is the time spent running normal priority user processes. X.B "User (nice)" is the time spent running low priority ("niced") processes. X.B System is the time spent running system code, and X.B Idle is the amount of time the CPU has nothing to do. X.TP 10 X.I Terminals shows the number of characters read and written on ttys per second averaged over the last interval. X.B Input is the number of characters read and X.B Output is the number of characters written. X.TP 10 X.I Mail A X.B Mail window pops up if you get new mail. This is for those of you who have nothing better to do than wait for mail and watch the show. X.SH FILES X/dev/kmem, /vmunix X.SH SEE ALSO The sections starting with ``Interpreting system activity'' in X.I "Setting up the Fourth Berkeley Software Tape" by W. Joy, O. Babaoglu, and K. Sklower X.SH AUTHORS Joe Pallas and Chris Torek X.sp The code was originally derived from X.IR vmstat (1). X.SH BUGS The way CPU time is computed can cause the number of interrupts to be printed as "-1" occasionally. X.br Won't run on very small screens. X.br Should include lots of other goodies. X.SH CAVEATS I don't guarantee that any of the above is correct. --Chris //go.sysin dd * made=TRUE if [ $made = TRUE ]; then /bin/chmod 644 vmpic.1l /bin/echo -n ' '; /bin/ls -ld vmpic.1l fi -- In-Real-Life: Chris Torek, Univ of MD Comp Sci UUCP: {seismo,allegra,brl-bmd}!umcp-cs!chris CSNet: chris@umcp-cs ARPA: chris.umcp-cs@UDel-Relay