[net.sources] Updated vmpic

steveh@hammer.UUCP (Stephen Hemminger) (12/07/83)

---
Here is an updated version of vmpic, which includes the functions of
netstat.  It runs on 4.1c (all we have so far), but should also work on
4.2bsd.
You still need the Maryland window library (winlib), to compile it.
---
/*	$Header: /usr/src/local/RCS/vmpic.c,v 1.1 83/12/07 08:54:23 steveh Exp $	*/

/*
 * 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
 *
 * 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)
 *		12-Aug-1983 (sch) pull disk names out of memory.
 */

#include <stdio.h>
#include <ctype.h>
#include <sys/param.h>
#include <sys/vm.h>
#include <sys/dk.h>
#include <sys/stat.h>
#include <nlist.h>
#include <sys/buf.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/mbuf.h>

#include <local/window.h>
#include <vaxuba/ubavar.h>
#include <vaxmba/mbavar.h>

#define	LOOPSIZ		(LOOPPAGES / CLSIZE)

/* 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
	{"_boottime"},
#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"},
#define X_MBDINIT	18
	{"_mbdinit" },
#define X_UBDINIT	19
	{"_ubdinit" },
#define X_MBSTAT	20
	{"_mbstat"},
#define X_IFNET		21
	{ "_ifnet"},
	0
};

char	*ctime (), *index ();
double	stat1 ();

char	hostname[32],
	Status_msg[100],
	buf[100];	/* general use string buffer */
/* values read from the kernel */
int	firstfree, maxfree, minfree, lotsfree, desfree, deficit, hz, kmem;
time_t	bootime;
double	etime;
double	avenrun[3];
struct  mbstat	mbstat;

/* Net drive stuff */
off_t	ifnetaddr;
struct	netstat {
	int	n_ipackets;
	int	n_ierrors;
	int	n_opackets;
	int	n_oerrors;
	int	n_collisions;
};

/* Disk drive stuff */
static char	dr_name[DK_NDRIVE][5];

/* 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;
/*	struct forkstat	Forkstat; -- not currently used */
	long		tk_nin;
	long		tk_nout;
	int		lp_nout;
	struct netstat	Net;
} s, s1;			/* s1 == the previous s */

/* 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,
/*	(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,
	(caddr_t) &mbstat,	sizeof mbstat, X_MBSTAT,
	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,
	(caddr_t) &ifnetaddr,	sizeof ifnetaddr, X_IFNET,
	0, 0, 0
};

/* This determines what windows to display */
/* 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])
/*0*/	18, 0,  38, 1,	0,
			Status_msg,
#define	W_PROC (wins[1])
/*1*/	0,  1,  15, 7,	"Processes",
			"Runnable\nDisk Wait\nPage Wait\nSwapped\nSleeping",
#define	W_BOOTIME (wins[2])
/*2*/	18, 1,  39, 1,	0,
			"Last reboot @ ",
#define	W_DISK (wins[3])
/*3*/	20, 2,  12, 7,	"Disk Xfers",
			 0,
#define	W_FAULTS (wins[4])
/*4*/	33, 3,  18, 6,	"Faults",
			"Interrupts\nDZ Pseudo\nSyscalls\nContext Sw",
#define	W_PAGE (wins[5])
/*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])
/*6*/	20, 9,  21, 6,	"CPU Usage",
			"User (normal)\t  %\nUser (nice)\t  %\nSystem\t\t  %\nIdle\t\t  %",
#define	W_MEM (wins[7])
/*7*/	20, 15, 22, 5,	"Memory",
			"Active Virtual       pages\nActive Real\t     pages\nFree\t\t     pages",
#define	W_TERM (wins[8])
/*8*/	0,  18, 15, 5,	"Terminals",
			"Input\nOutput\nLP Out",
#define W_IFNET (wins[9])
/*9*/	43, 9, 20,  7,	"Network",
			"In packets\nIn errors\nOut packets\nOut errors\nCollisions",
#define W_MBUF (wins[10])
/*10*/	43, 16, 26, 4,	"Net buffers",
			"Mbufs     used,     free\nPages     used,     free",
#define	W_UP (wins[11])
/*11*/	22, 20, 20, 1,	0,
			"up",
#define	W_LOAD (wins[12])
/*12*/	22, 21, 34, 1,	0,
			"Load average:",
#define	W_MAIL (wins[13])
/*13*/	22, 22,	4,  1,	0,
			"$Mail",
};

#define	NWINS	(sizeof InitScreen/sizeof *InitScreen)

Win	*wins[NWINS];

main (argc, argv)
int argc;
char **argv;
{
	register	int i,j;
	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;
	int		leave();

	/* 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 are name -mpic then we have been called by login */
		if (**argv == '-') {
			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);

	read_names();

	time (&now);
	gethostname(hostname, sizeof hostname);
	if(islower(hostname[0]))
		hostname[0] = toupper(hostname[0]);
	sprintf(Status_msg, "$%s Status", hostname);

	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);

	/* Label Disk windows */
	for(i=0; i<DK_NDRIVE; i++) {
		WAcursor(W_DISK, i, 0);
		Wputs(dr_name[i], W_DISK);
	}
	
	/* 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 */

	/* Network I/O */
	netstat(&s.Net);
	
	WPR (W_IFNET, 0, 16, "%4d", s.Net.n_ipackets-s1.Net.n_ipackets);
 	i = s.Net.n_ierrors-s1.Net.n_ierrors;
	if( i > 10)
		Wsetmode(W_IFNET, WINVERSE);
	WPR (W_IFNET, 1, 16, "%4d", i);
	Wsetmode(W_IFNET, 0);
	WPR (W_IFNET, 2, 16, "%4d", s.Net.n_opackets-s1.Net.n_opackets);
 	i = s.Net.n_oerrors-s1.Net.n_oerrors;
	if( i > 10)
		Wsetmode(W_IFNET, WINVERSE);
	WPR (W_IFNET, 3, 16, "%4d", i);
	Wsetmode(W_IFNET, 0);
	i = s.Net.n_collisions-s1.Net.n_collisions;
	if( i > 10)
		Wsetmode(W_IFNET, WINVERSE);
	WPR (W_IFNET, 4, 16, "%4d", i);
	Wsetmode(W_IFNET, 0);
	
	/* Network buffers  */
	WPR (W_MBUF, 0, 5, "%4d", mbstat.m_mbufs - mbstat.m_mbfree);
	if (mbstat.m_mbfree < 10)
		Wsetmode(W_MBUF, WINVERSE);
	WPR (W_MBUF, 0, 15, "%4d", mbstat.m_mbfree);
	Wsetmode(W_MBUF, 0);
	WPR (W_MBUF, 1, 5, "%4d", mbstat.m_clusters - mbstat.m_clfree);
	if (mbstat.m_clfree < 4)
		Wsetmode(W_MBUF, WINVERSE);
	WPR (W_MBUF, 1, 15, "%4d", mbstat.m_clfree);
	Wsetmode(W_MBUF, 0);

	/* 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;
}

/* 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;
}

/* 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;
}

/* The standard sleep creates problems because it uses longjmp() on an alarm
   to return.  This one uses a flag instead, so is much safer. */

/* @(#)sleep.c	4.1 (Berkeley) 12/21/80 */

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++;
}


/*
 * Read the drive names out of kmem.
 */

#define steal(where, var) lseek(kmem, where, 0); read(kmem, &var, sizeof var)

read_names()
{
	struct mba_device mdev;
	register struct mba_device *mp;
	struct mba_driver mdrv;
	short two_char;
	register char *cp = (char *) &two_char;
	struct uba_device udev, *up;
	struct uba_driver udrv;

	mp = (struct mba_device *) nml[X_MBDINIT].n_value;
	up = (struct uba_device *) nml[X_UBDINIT].n_value;
	if (up == 0) {
		fprintf(stderr, "vmstat: Disk init info not in namelist\n");
		exit(1);
	}
	if (mp) for (;;) {
		steal(mp++, mdev);
		if (mdev.mi_driver == 0)
			break;
		if (mdev.mi_dk < 0 || mdev.mi_alive == 0)
			continue;
		steal(mdev.mi_driver, mdrv);
		steal(mdrv.md_dname, two_char);
		sprintf(dr_name[mdev.mi_dk], "%c%c%d",
			cp[0], cp[1], mdev.mi_unit);
	}
	for (;;) {
		steal(up++, udev);
		if (udev.ui_driver == 0)
			break;
		if (udev.ui_dk < 0 || udev.ui_alive == 0)
			continue;
		steal(udev.ui_driver, udrv);
		steal(udrv.ud_dname, two_char);
		sprintf(dr_name[udev.ui_dk], "%c%c",
			cp[0], cp[1], udev.ui_unit);
	}
}

netstat(np)
	register struct netstat *np;
{
	struct ifnet ifnet;
	off_t addr = ifnetaddr;

	bzero((char *) np, sizeof (struct netstat));

	while(addr) {
		steal(addr, ifnet);

		np->n_ipackets += ifnet.if_ipackets;
		np->n_ierrors += ifnet.if_ierrors;
		np->n_opackets += ifnet.if_opackets;
		np->n_ierrors += ifnet.if_ierrors;
		np->n_collisions += ifnet.if_collisions;

		addr = (off_t) ifnet.if_next;
	}
}