shithub: stats

Download patch

ref: 71b28b13a644ec33be4151aae0e01391517fe64c
author: Jacob Moody <[email protected]>
date: Thu Aug 3 19:58:34 EDT 2023

done

--- /dev/null
+++ b/stats.c
@@ -1,0 +1,1471 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+
+#define	MAXNUM	10	/* maximum number of numbers on data line */
+
+typedef struct Graph	Graph;
+typedef struct Machine	Machine;
+
+struct Graph
+{
+	int		colindex;
+	Rectangle	r;
+	int		*data;
+	int		ndata;
+	char		*label;
+	void		(*newvalue)(Machine*, uvlong*, uvlong*, int);
+	void		(*update)(Graph*, uvlong, uvlong);
+	Machine		*mach;
+	int		overflow;
+	Image		*overtmp;
+	uvlong		hiwater;
+};
+
+enum
+{
+	/* /dev/swap */
+	Mem		= 0,
+	Maxmem,
+	Swap,
+	Maxswap,
+	Reclaim,
+	Maxreclaim,
+	Kern,
+	Maxkern,
+	Draw,
+	Maxdraw,
+
+	/* /dev/sysstats */
+	Procno	= 0,
+	Context,
+	Interrupt,
+	Syscall,
+	Fault,
+	TLBfault,
+	TLBpurge,
+	Load,
+	Idle,
+	InIntr,
+
+	/* /net/ether0/stats */
+	In		= 0,
+	Link,
+	Out,
+	Err0,
+};
+
+struct Machine
+{
+	char		*name;
+	char		*shortname;
+	int		remote;
+	int		statsfd;
+	int		swapfd;
+	int		etherfd;
+	int		ifstatsfd;
+	int		batteryfd;
+	int		bitsybatfd;
+	int		tempfd;
+	int		disable;
+
+	uvlong		devswap[10];
+	uvlong		devsysstat[10];
+	uvlong		prevsysstat[10];
+	int		nproc;
+	int		lgproc;
+	uvlong		netetherstats[8];
+	uvlong		prevetherstats[8];
+	uvlong		batterystats[2];
+	uvlong		netetherifstats[2];
+	uvlong		temp[10];
+
+	/* big enough to hold /dev/sysstat even with many processors */
+	char		buf[8*1024];
+	char		*bufp;
+	char		*ebufp;
+};
+
+enum
+{
+	Mainproc,
+	Inputproc,
+	NPROC,
+};
+
+enum
+{
+	Ncolor		= 6,
+	Ysqueeze	= 2,	/* vertical squeezing of label text */
+	Labspace	= 2,	/* room around label */
+	Dot		= 2,	/* height of dot */
+	Opwid		= 5,	/* strlen("add  ") or strlen("drop ") */
+	Nlab		= 3,	/* max number of labels on y axis */
+	Lablen		= 16,	/* max length of label */
+	Lx		= 4,	/* label tick length */
+};
+
+enum Menu2
+{
+	Mbattery,
+	Mcontext,
+	Mether,
+	Methererr,
+	Metherin,
+	Metherout,
+	Mfault,
+	Midle,
+	Minintr,
+	Mintr,
+	Mload,
+	Mmem,
+	Mswap,
+	Mreclaim,
+	Mkern,
+	Mdraw,
+	Msyscall,
+	Mtlbmiss,
+	Mtlbpurge,
+	Msignal,
+	Mtemp,
+	Nmenu2,
+};
+
+char	*menu2str[Nmenu2+1] = {
+	"add  battery ",
+	"add  context ",
+	"add  ether   ",
+	"add  ethererr",
+	"add  etherin ",
+	"add  etherout",
+	"add  fault   ",
+	"add  idle    ",
+	"add  inintr  ",
+	"add  intr    ",
+	"add  load    ",
+	"add  mem     ",
+	"add  swap    ",
+	"add  reclaim ",
+	"add  kern    ",
+	"add  draw    ",
+	"add  syscall ",
+	"add  tlbmiss ",
+	"add  tlbpurge",
+	"add  802.11b ",
+	"add  temp    ",
+	nil,
+};
+
+
+void	contextval(Machine*, uvlong*, uvlong*, int),
+	etherval(Machine*, uvlong*, uvlong*, int),
+	ethererrval(Machine*, uvlong*, uvlong*, int),
+	etherinval(Machine*, uvlong*, uvlong*, int),
+	etheroutval(Machine*, uvlong*, uvlong*, int),
+	faultval(Machine*, uvlong*, uvlong*, int),
+	intrval(Machine*, uvlong*, uvlong*, int),
+	inintrval(Machine*, uvlong*, uvlong*, int),
+	loadval(Machine*, uvlong*, uvlong*, int),
+	idleval(Machine*, uvlong*, uvlong*, int),
+	memval(Machine*, uvlong*, uvlong*, int),
+	swapval(Machine*, uvlong*, uvlong*, int),
+	reclaimval(Machine*, uvlong*, uvlong*, int),
+	kernval(Machine*, uvlong*, uvlong*, int),
+	drawval(Machine*, uvlong*, uvlong*, int),
+	syscallval(Machine*, uvlong*, uvlong*, int),
+	tlbmissval(Machine*, uvlong*, uvlong*, int),
+	tlbpurgeval(Machine*, uvlong*, uvlong*, int),
+	batteryval(Machine*, uvlong*, uvlong*, int),
+	signalval(Machine*, uvlong*, uvlong*, int),
+	tempval(Machine*, uvlong*, uvlong*, int);
+
+Menu	menu2 = {menu2str, nil};
+int	present[Nmenu2];
+void	(*newvaluefn[Nmenu2])(Machine*, uvlong*, uvlong*, int init) = {
+	batteryval,
+	contextval,
+	etherval,
+	ethererrval,
+	etherinval,
+	etheroutval,
+	faultval,
+	idleval,
+	inintrval,
+	intrval,
+	loadval,
+	memval,
+	swapval,
+	reclaimval,
+	kernval,
+	drawval,
+	syscallval,
+	tlbmissval,
+	tlbpurgeval,
+	signalval,
+	tempval,
+};
+
+Image	*cols[Ncolor][3];
+Graph	*graph;
+Machine	*mach;
+char	*mysysname;
+char	argchars[] = "8bcdeEfiIkmlnprstwz";
+int	pids[NPROC];
+int 	parity;	/* toggled to avoid patterns in textured background */
+int	nmach;
+int	ngraph;	/* totaly number is ngraph*nmach */
+double	scale = 1.0;
+int	logscale = 0;
+int	ylabels = 0;
+int	sleeptime = 1000;
+int	batteryperiod = 1000;
+int	tempperiod = 1000;
+
+char	*procnames[NPROC] = {"main", "input"};
+
+void
+killall(char *s)
+{
+	int i, pid;
+
+	pid = getpid();
+	for(i=0; i<NPROC; i++)
+		if(pids[i] && pids[i]!=pid)
+			postnote(PNPROC, pids[i], "kill");
+	exits(s);
+}
+
+void*
+emalloc(ulong sz)
+{
+	void *v;
+	v = malloc(sz);
+	if(v == nil) {
+		fprint(2, "stats: out of memory allocating %ld: %r\n", sz);
+		killall("mem");
+	}
+	memset(v, 0, sz);
+	return v;
+}
+
+void*
+erealloc(void *v, ulong sz)
+{
+	v = realloc(v, sz);
+	if(v == nil) {
+		fprint(2, "stats: out of memory reallocating %ld: %r\n", sz);
+		killall("mem");
+	}
+	return v;
+}
+
+char*
+estrdup(char *s)
+{
+	char *t;
+	if((t = strdup(s)) == nil) {
+		fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s);
+		killall("mem");
+	}
+	return t;
+}
+
+void
+mkcol(int i, int c0, int c1, int c2)
+{
+	cols[i][0] = allocimagemix(display, c0, DWhite);
+	cols[i][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c1);
+	cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2);
+}
+
+void
+colinit(void)
+{
+	/* Peach */
+	mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
+	/* Aqua */
+	mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
+	/* Yellow */
+	mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
+	/* Green */
+	mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
+	/* Blue */
+	mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
+	/* Grey */
+	cols[5][0] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
+	cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
+	cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF);
+}
+
+int
+loadbuf(Machine *m, int *fd)
+{
+	int n;
+
+
+	if(*fd < 0)
+		return 0;
+	seek(*fd, 0, 0);
+	n = read(*fd, m->buf, sizeof m->buf-1);
+	if(n <= 0){
+		close(*fd);
+		*fd = -1;
+		return 0;
+	}
+	m->bufp = m->buf;
+	m->ebufp = m->buf+n;
+	m->buf[n] = 0;
+	return 1;
+}
+
+void
+label(Point p, int dy, char *text)
+{
+	char *s;
+	Rune r[2];
+	int w, maxw, maxy;
+
+	p.x += Labspace;
+	maxy = p.y+dy;
+	maxw = 0;
+	r[1] = '\0';
+	for(s=text; *s; ){
+		if(p.y+font->height-Ysqueeze > maxy)
+			break;
+		w = chartorune(r, s);
+		s += w;
+		w = runestringwidth(font, r);
+		if(w > maxw)
+			maxw = w;
+		runestring(screen, p, display->black, ZP, font, r);
+		p.y += font->height-Ysqueeze;
+	}
+}
+
+Point
+paritypt(int x)
+{
+	return Pt(x+parity, 0);
+}
+
+Point
+datapoint(Graph *g, int x, uvlong v, uvlong vmax)
+{
+	Point p;
+	double y;
+
+	p.x = x;
+	y = ((double)v)/(vmax*scale);
+	if(logscale){
+		/*
+		 * Arrange scale to cover a factor of 1000.
+		 * vmax corresponds to the 100 mark.
+		 * 10*vmax is the top of the scale.
+		 */
+		if(y <= 0.)
+			y = 0;
+		else{
+			y = log10(y);
+			/* 1 now corresponds to the top; -2 to the bottom; rescale */
+			y = (y+2.)/3.;
+		}
+	}
+	if(y >= 1.)
+		y = 1;
+	if(y <= 0.)
+		y = 0;
+	p.y = g->r.max.y - Dy(g->r)*y - Dot;
+	if(p.y < g->r.min.y)
+		p.y = g->r.min.y;
+	if(p.y > g->r.max.y-Dot)
+		p.y = g->r.max.y-Dot;
+	return p;
+}
+
+void
+drawdatum(Graph *g, int x, uvlong prev, uvlong v, uvlong vmax)
+{
+	int c;
+	Point p, q;
+
+	c = g->colindex;
+	p = datapoint(g, x, v, vmax);
+	q = datapoint(g, x, prev, vmax);
+	if(p.y < q.y){
+		draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
+		draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
+		draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
+	}else{
+		draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
+		draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
+		draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
+	}
+
+}
+
+void
+labelstrs(Graph *g, char strs[Nlab][Lablen], int *np)
+{
+	int j;
+	uvlong v, vmax;
+
+	g->newvalue(g->mach, &v, &vmax, 1);
+	if(vmax == 0)
+		vmax = 1;
+	if(g->hiwater > vmax)
+		vmax = g->hiwater;
+	if(logscale){
+		for(j=1; j<=2; j++)
+			sprint(strs[j-1], "%g", scale*pow(10., j)*(double)vmax/100.);
+		*np = 2;
+	}else{
+		for(j=1; j<=3; j++)
+			sprint(strs[j-1], "%g", scale*(double)j*(double)vmax/4.0);
+		*np = 3;
+	}
+}
+
+int
+labelwidth(void)
+{
+	int i, j, n, w, maxw;
+	char strs[Nlab][Lablen];
+
+	maxw = 0;
+	for(i=0; i<ngraph; i++){
+		/* choose value for rightmost graph */
+		labelstrs(&graph[ngraph*(nmach-1)+i], strs, &n);
+		for(j=0; j<n; j++){
+			w = stringwidth(font, strs[j]);
+			if(w > maxw)
+				maxw = w;
+		}
+	}
+	return maxw;
+}
+
+
+int
+drawlabels(int maxx)
+{
+	int x, j, k, y, dy, dx, starty, startx, nlab, ly;
+	int wid;
+	Graph *g;
+	char labs[Nlab][Lablen];
+	Rectangle r;
+
+	/* label left edge */
+	x = screen->r.min.x;
+	y = screen->r.min.y + Labspace+font->height+Labspace;
+	dy = (screen->r.max.y - y)/ngraph;
+	dx = Labspace+stringwidth(font, "0")+Labspace;
+	startx = x+dx+1;
+	starty = y;
+	dx = (screen->r.max.x - startx)/nmach;
+
+	if(!ylabels || !dy>Nlab*(font->height+1))
+		return maxx;
+
+	/* if there's not enough room */
+	if((wid = labelwidth()) >= dx-10)
+		return maxx;
+
+	maxx -= 1+Lx+wid;
+	draw(screen, Rect(maxx, starty, maxx+1, screen->r.max.y), display->black, nil, ZP);
+	y = starty;
+	for(j=0; j<ngraph; j++, y+=dy){
+		/* choose value for rightmost graph */
+		g = &graph[ngraph*(nmach-1)+j];
+		labelstrs(g, labs, &nlab);
+		r = Rect(maxx+1, y, screen->r.max.x, y+dy-1);
+		if(j == ngraph-1)
+			r.max.y = screen->r.max.y;
+		draw(screen, r, cols[g->colindex][0], nil, paritypt(r.min.x));
+		for(k=0; k<nlab; k++){
+			ly = y + (dy*(nlab-k)/(nlab+1));
+			draw(screen, Rect(maxx+1, ly, maxx+1+Lx, ly+1), display->black, nil, ZP);
+			ly -= font->height/2;
+			string(screen, Pt(maxx+1+Lx, ly), display->black, ZP, font, labs[k]);
+		}
+	}
+	return maxx;
+}
+
+
+void
+redraw(Graph *g, uvlong vmax)
+{
+	int i, c;
+
+	c = g->colindex;
+	draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
+	for(i=1; i<Dx(g->r); i++)
+		drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
+	drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
+	g->overflow = 0;
+}
+
+void
+update1(Graph *g, uvlong v, uvlong vmax)
+{
+	char buf[48];
+	int overflow;
+
+	if(v > g->hiwater && v > vmax){
+		overflow = 1;
+		g->hiwater = v;
+		drawlabels(screen->r.max.x);
+		redraw(g, g->hiwater);
+	} else
+		overflow = 0;
+	if(v > g->hiwater)
+		g->hiwater = v;
+	if(g->hiwater > vmax)
+		vmax = g->hiwater;
+	if(g->overflow && g->overtmp!=nil)
+		draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
+	draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
+	drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
+	memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
+	g->data[0] = v;
+	g->overflow = 0;
+	if(overflow && g->overtmp!=nil){
+		g->overflow = 1;
+		draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);
+		sprint(buf, "%llud", v);
+		string(screen, g->overtmp->r.min, display->black, ZP, font, buf);
+	}
+}
+
+/* read one line of text from buffer and process integers */
+int
+readnums(Machine *m, int n, uvlong *a, int spanlines)
+{
+	int i;
+	char *p, *ep;
+
+	if(spanlines)
+		ep = m->ebufp;
+	else
+		for(ep=m->bufp; ep<m->ebufp; ep++)
+			if(*ep == '\n')
+				break;
+	p = m->bufp;
+	for(i=0; i<n && p<ep; i++){
+		while(p<ep && (!isascii(*p) || !isdigit(*p)) && *p!='-')
+			p++;
+		if(p == ep)
+			break;
+		a[i] = strtoull(p, &p, 10);
+	}
+	if(ep < m->ebufp)
+		ep++;
+	m->bufp = ep;
+	return i == n;
+}
+
+int
+readswap(Machine *m, uvlong *a)
+{
+	static int xxx = 0;
+
+	if(strstr(m->buf, "memory\n")){
+		/* new /dev/swap - skip first 3 numbers */
+		if(!readnums(m, 7, a, 1))
+			return 0;
+
+		a[Mem] = a[3];
+		a[Maxmem] = a[4];
+		a[Swap] = a[5];
+		a[Maxswap] = a[6];
+
+		a[Reclaim] = 0;
+		a[Maxreclaim] = 0;
+		if(m->bufp = strstr(m->buf, "reclaim")){
+			while(m->bufp > m->buf && m->bufp[-1] != '\n')
+				m->bufp--;
+			a[Reclaim] = strtoull(m->bufp, &m->bufp, 10);
+			while(*m->bufp++ == '/')
+				a[Maxreclaim] = strtoull(m->bufp, &m->bufp, 10);
+		}
+
+		a[Kern] = 0;
+		a[Maxkern] = 0;
+		if(m->bufp = strstr(m->buf, "kernel malloc")){
+			while(m->bufp > m->buf && m->bufp[-1] != '\n')
+				m->bufp--;
+			a[Kern] = strtoull(m->bufp, &m->bufp, 10);
+			while(*m->bufp++ == '/')
+				a[Maxkern] = strtoull(m->bufp, &m->bufp, 10);
+		}
+
+		a[Draw] = 0;
+		a[Maxdraw] = 0;
+		if(m->bufp = strstr(m->buf, "kernel draw")){
+			while(m->bufp > m->buf && m->bufp[-1] != '\n')
+				m->bufp--;
+			a[Draw] = strtoull(m->bufp, &m->bufp, 10);
+			while(*m->bufp++ == '/')
+				a[Maxdraw] = strtoull(m->bufp, &m->bufp, 10);
+		}
+
+		return 1;
+	}
+
+	a[Reclaim] = 0;
+	a[Maxreclaim] = 0;
+	a[Kern] = 0;
+	a[Maxkern] = 0;
+	a[Draw] = 0;
+	a[Maxdraw] = 0;
+
+	return readnums(m, 4, a, 0);
+}
+
+char*
+shortname(char *s)
+{
+	char *p, *e;
+
+	p = estrdup(s);
+	e = strchr(p, '.');
+	if(e)
+		*e = 0;
+	return p;
+}
+
+int
+ilog10(uvlong j)
+{
+	int i;
+
+	for(i = 0; j >= 10; i++)
+		j /= 10;
+	return i;
+}
+
+int
+initmach(Machine *m, char *name)
+{
+	int n;
+	uvlong a[MAXNUM];
+	char *p, mpt[256], buf[256];
+
+	p = strchr(name, '!');
+	if(p)
+		p++;
+	else
+		p = name;
+	m->name = estrdup(p);
+	m->shortname = shortname(p);
+	m->remote = (strcmp(p, mysysname) != 0);
+	if(m->remote == 0)
+		strcpy(mpt, "");
+	else{
+		Waitmsg *w;
+		int pid;
+
+		snprint(mpt, sizeof mpt, "/n/%s", p);
+
+		pid = fork();
+		switch(pid){
+		case -1:
+			fprint(2, "can't fork: %r\n");
+			return 0;
+		case 0:
+			execl("/bin/rimport", "rimport", name, "/", mpt, nil);
+			fprint(2, "can't exec: %r\n");
+			exits("exec");
+		}
+		w = wait();
+		if(w == nil || w->pid != pid || w->msg[0] != '\0'){
+			free(w);
+			return 0;
+		}
+		free(w);
+	}
+
+	snprint(buf, sizeof buf, "%s/dev/swap", mpt);
+	m->swapfd = open(buf, OREAD);
+	if(loadbuf(m, &m->swapfd) && readswap(m, a))
+		memmove(m->devswap, a, sizeof m->devswap);
+
+	snprint(buf, sizeof buf, "%s/dev/sysstat", mpt);
+	m->statsfd = open(buf, OREAD);
+	if(loadbuf(m, &m->statsfd)){
+		for(n=0; readnums(m, nelem(m->devsysstat), a, 0); n++)
+			;
+		m->nproc = n;
+	}else
+		m->nproc = 1;
+	m->lgproc = ilog10(m->nproc);
+
+	snprint(buf, sizeof buf, "%s/net/ether0/stats", mpt);
+	m->etherfd = open(buf, OREAD);
+	if(loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1))
+		memmove(m->netetherstats, a, sizeof m->netetherstats);
+
+	snprint(buf, sizeof buf, "%s/net/ether0/ifstats", mpt);
+	m->ifstatsfd = open(buf, OREAD);
+	if(loadbuf(m, &m->ifstatsfd)){
+		/* need to check that this is a wavelan interface */
+		if(strncmp(m->buf, "Signal: ", 8) == 0 && readnums(m, nelem(m->netetherifstats), a, 1))
+			memmove(m->netetherifstats, a, sizeof m->netetherifstats);
+	}
+
+	snprint(buf, sizeof buf, "%s/mnt/apm/battery", mpt);
+	m->batteryfd = open(buf, OREAD);
+	if(m->batteryfd < 0){
+		snprint(buf, sizeof buf, "%s/mnt/pm/battery", mpt);
+		m->batteryfd = open(buf, OREAD);
+	}
+	m->bitsybatfd = -1;
+	if(m->batteryfd >= 0){
+		batteryperiod = 10000;
+		if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
+			memmove(m->batterystats, a, sizeof(m->batterystats));
+	}else{
+		snprint(buf, sizeof buf, "%s/dev/battery", mpt);
+		m->bitsybatfd = open(buf, OREAD);
+		if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
+			memmove(m->batterystats, a, sizeof(m->batterystats));
+	}
+	snprint(buf, sizeof buf, "%s/dev/cputemp", mpt);
+	m->tempfd = open(buf, OREAD);
+	if(m->tempfd < 0){
+		tempperiod = 5000;
+		snprint(buf, sizeof buf, "%s/mnt/pm/cputemp", mpt);
+		m->tempfd = open(buf, OREAD);
+	}
+	if(loadbuf(m, &m->tempfd))
+		for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
+			 m->temp[n] = a[0];
+	return 1;
+}
+
+jmp_buf catchalarm;
+
+int
+alarmed(void *a, char *s)
+{
+	if(strcmp(s, "alarm") == 0)
+		notejmp(a, catchalarm, 1);
+	return 0;
+}
+
+int
+needswap(int init)
+{
+	return init | present[Mmem] | present[Mswap] | present[Mreclaim] | present[Mkern] | present[Mdraw];
+}
+
+
+int
+needstat(int init)
+{
+	return init | present[Mcontext]  | present[Mfault] | present[Mintr] | present[Mload] | present[Midle] |
+		present[Minintr] | present[Msyscall] | present[Mtlbmiss] | present[Mtlbpurge];
+}
+
+
+int
+needether(int init)
+{
+	return init | present[Mether] | present[Metherin] | present[Metherout] | present[Methererr];
+}
+
+int
+needbattery(int init)
+{
+	static uint step = 0;
+
+	if(++step*sleeptime >= batteryperiod){
+		step = 0;
+		return init | present[Mbattery];
+	}
+
+	return 0;
+}
+
+int
+needsignal(int init)
+{
+	return init | present[Msignal];
+}
+
+int
+needtemp(int init)
+{
+	static uint step = 0;
+
+	if(++step*sleeptime >= tempperiod){
+		step = 0;
+		return init | present[Mtemp];
+	}
+
+	return 0;
+}
+
+void
+readmach(Machine *m, int init)
+{
+	int n, i;
+	uvlong a[nelem(m->devsysstat)];
+	char buf[32];
+
+	if(m->remote && (m->disable || setjmp(catchalarm))){
+		if (m->disable++ >= 5)
+			m->disable = 0; /* give it another chance */
+		memmove(m->devsysstat, m->prevsysstat, sizeof m->devsysstat);
+		memmove(m->netetherstats, m->prevetherstats, sizeof m->netetherstats);
+		return;
+	}
+	snprint(buf, sizeof buf, "%s", m->name);
+	if (strcmp(m->name, buf) != 0){
+		free(m->name);
+		m->name = estrdup(buf);
+		free(m->shortname);
+		m->shortname = shortname(buf);
+		if(display != nil)	/* else we're still initializing */
+			eresized(0);
+	}
+	if(m->remote){
+		atnotify(alarmed, 1);
+		alarm(5000);
+	}
+	if(needswap(init) && loadbuf(m, &m->swapfd) && readswap(m, a))
+		memmove(m->devswap, a, sizeof m->devswap);
+	if(needstat(init) && loadbuf(m, &m->statsfd)){
+		memmove(m->prevsysstat, m->devsysstat, sizeof m->devsysstat);
+		memset(m->devsysstat, 0, sizeof m->devsysstat);
+		for(n=0; n<m->nproc && readnums(m, nelem(m->devsysstat), a, 0); n++)
+			for(i=0; i<nelem(m->devsysstat); i++)
+				m->devsysstat[i] += a[i];
+	}
+	if(needether(init) && loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1)){
+		memmove(m->prevetherstats, m->netetherstats, sizeof m->netetherstats);
+		memmove(m->netetherstats, a, sizeof m->netetherstats);
+	}
+	if(needsignal(init) && loadbuf(m, &m->ifstatsfd) && strncmp(m->buf, "Signal: ", 8)==0 && readnums(m, nelem(m->netetherifstats), a, 1)){
+		memmove(m->netetherifstats, a, sizeof m->netetherifstats);
+	}
+	if(needbattery(init)){
+		if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
+			memmove(m->batterystats, a, sizeof(m->batterystats));
+		else if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
+			memmove(m->batterystats, a, sizeof(m->batterystats));
+	}
+	if(needtemp(init) && loadbuf(m, &m->tempfd))
+		for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
+			 m->temp[n] = a[0];
+	if(m->remote){
+		alarm(0);
+		atnotify(alarmed, 0);
+	}
+}
+
+void
+memval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->devswap[Mem];
+	*vmax = m->devswap[Maxmem];
+	if(*vmax == 0)
+		*vmax = 1;
+}
+
+void
+swapval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->devswap[Swap];
+	*vmax = m->devswap[Maxswap];
+	if(*vmax == 0)
+		*vmax = 1;
+}
+
+void
+reclaimval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->devswap[Reclaim];
+	*vmax = m->devswap[Maxreclaim];
+	if(*vmax == 0)
+		*vmax = 1;
+}
+
+void
+kernval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->devswap[Kern];
+	*vmax = m->devswap[Maxkern];
+	if(*vmax == 0)
+		*vmax = 1;
+}
+
+void
+drawval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->devswap[Draw];
+	*vmax = m->devswap[Maxdraw];
+	if(*vmax == 0)
+		*vmax = 1;
+}
+
+void
+contextval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+	*v = (m->devsysstat[Context]-m->prevsysstat[Context])&0xffffffff;
+	*vmax = sleeptime*m->nproc;
+	if(init)
+		*vmax = sleeptime;
+}
+
+/*
+ * bug: need to factor in HZ
+ */
+void
+intrval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+	*v = (m->devsysstat[Interrupt]-m->prevsysstat[Interrupt])&0xffffffff;
+	*vmax = sleeptime*m->nproc*10;
+	if(init)
+		*vmax = sleeptime*10;
+}
+
+void
+syscallval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+	*v = (m->devsysstat[Syscall]-m->prevsysstat[Syscall])&0xffffffff;
+	*vmax = sleeptime*m->nproc;
+	if(init)
+		*vmax = sleeptime;
+}
+
+void
+faultval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+	*v = (m->devsysstat[Fault]-m->prevsysstat[Fault])&0xffffffff;
+	*vmax = sleeptime*m->nproc;
+	if(init)
+		*vmax = sleeptime;
+}
+
+void
+tlbmissval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+	*v = (m->devsysstat[TLBfault]-m->prevsysstat[TLBfault])&0xffffffff;
+	*vmax = (sleeptime/1000)*10*m->nproc;
+	if(init)
+		*vmax = (sleeptime/1000)*10;
+}
+
+void
+tlbpurgeval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+	*v = (m->devsysstat[TLBpurge]-m->prevsysstat[TLBpurge])&0xffffffff;
+	*vmax = (sleeptime/1000)*10*m->nproc;
+	if(init)
+		*vmax = (sleeptime/1000)*10;
+}
+
+void
+loadval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+	*v = m->devsysstat[Load];
+	*vmax = 1000*m->nproc;
+	if(init)
+		*vmax = 1000;
+}
+
+void
+idleval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->devsysstat[Idle]/m->nproc;
+	*vmax = 100;
+}
+
+void
+inintrval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->devsysstat[InIntr]/m->nproc;
+	*vmax = 100;
+}
+
+void
+etherval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->netetherstats[In]-m->prevetherstats[In] + m->netetherstats[Out]-m->prevetherstats[Out];
+	*vmax = sleeptime;
+}
+
+void
+etherinval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->netetherstats[In]-m->prevetherstats[In];
+	*vmax = sleeptime;
+}
+
+void
+etheroutval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->netetherstats[Out]-m->prevetherstats[Out];
+	*vmax = sleeptime;
+}
+
+void
+ethererrval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	int i;
+
+	*v = 0;
+	for(i=Err0; i<nelem(m->netetherstats); i++)
+		*v += m->netetherstats[i]-m->prevetherstats[i];
+	*vmax = (sleeptime/1000)*10;
+}
+
+void
+batteryval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	*v = m->batterystats[0];
+	if(m->bitsybatfd >= 0)
+		*vmax = 184;		// at least on my bitsy...
+	else
+		*vmax = 100;
+}
+
+void
+signalval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	ulong l;
+
+	*vmax = sleeptime;
+	l = m->netetherifstats[0];
+	/*
+	 * Range is seen to be from about -45 (strong) to -95 (weak); rescale
+	 */
+	if(l == 0){	/* probably not present */
+		*v = 0;
+		return;
+	}
+	*v = 20*(l+95);
+}
+
+void
+tempval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+	ulong l;
+
+	*vmax = 100;
+	l = m->temp[0];
+	if(l == ~0 || l == 0)
+		*v = 0;
+	else
+		*v = l;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: stats [-O] [-S scale] [-LY] [-%s] [machine...]\n", argchars);
+	exits("usage");
+}
+
+void
+addgraph(int n)
+{
+	Graph *g, *ograph;
+	int i, j;
+	static int nadd;
+
+	if(n > nelem(menu2str))
+		abort();
+	/* avoid two adjacent graphs of same color */
+	if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
+		nadd++;
+	ograph = graph;
+	graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
+	for(i=0; i<nmach; i++)
+		for(j=0; j<ngraph; j++)
+			graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
+	free(ograph);
+	ngraph++;
+	for(i=0; i<nmach; i++){
+		g = &graph[i*ngraph+(ngraph-1)];
+		memset(g, 0, sizeof(Graph));
+		g->label = menu2str[n]+Opwid;
+		g->newvalue = newvaluefn[n];
+		g->update = update1;	/* no other update functions yet */
+		g->mach = &mach[i];
+		g->colindex = nadd%Ncolor;
+	}
+	present[n] = 1;
+	nadd++;
+}
+
+void
+dropgraph(int which)
+{
+	Graph *ograph;
+	int i, j, n;
+
+	if(which > nelem(menu2str))
+		abort();
+	/* convert n to index in graph table */
+	n = -1;
+	for(i=0; i<ngraph; i++)
+		if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
+			n = i;
+			break;
+		}
+	if(n < 0){
+		fprint(2, "stats: internal error can't drop graph\n");
+		killall("error");
+	}
+	ograph = graph;
+	graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
+	for(i=0; i<nmach; i++){
+		for(j=0; j<n; j++)
+			graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
+		free(ograph[i*ngraph+j].data);
+		freeimage(ograph[i*ngraph+j].overtmp);
+		for(j++; j<ngraph; j++)
+			graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
+	}
+	free(ograph);
+	ngraph--;
+	present[which] = 0;
+}
+
+int
+addmachine(char *name)
+{
+	if(ngraph > 0){
+		fprint(2, "stats: internal error: ngraph>0 in addmachine()\n");
+		usage();
+	}
+	if(mach == nil)
+		nmach = 0;	/* a little dance to get us started with local machine by default */
+	mach = erealloc(mach, (nmach+1)*sizeof(Machine));
+	memset(mach+nmach, 0, sizeof(Machine));
+	if (initmach(mach+nmach, name)){
+		nmach++;
+		return 1;
+	} else
+		return 0;
+}
+
+void
+resize(void)
+{
+	int i, j, n, startx, starty, x, y, dx, dy, ondata, maxx;
+	Graph *g;
+	Rectangle machr, r;
+	uvlong v, vmax;
+	char buf[128];
+
+	draw(screen, screen->r, display->white, nil, ZP);
+
+	/* label left edge */
+	x = screen->r.min.x;
+	y = screen->r.min.y + Labspace+font->height+Labspace;
+	dy = (screen->r.max.y - y)/ngraph;
+	dx = Labspace+stringwidth(font, "0")+Labspace;
+	startx = x+dx+1;
+	starty = y;
+	for(i=0; i<ngraph; i++,y+=dy){
+		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
+		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
+		label(Pt(x, y), dy, graph[i].label);
+		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
+	}
+
+	/* label top edge */
+	dx = (screen->r.max.x - startx)/nmach;
+	for(x=startx, i=0; i<nmach; i++,x+=dx){
+		draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
+		j = dx/stringwidth(font, "0");
+		n = mach[i].nproc;
+		if(n>1 && j>=1+3+mach[i].lgproc){	/* first char of name + (n) */
+			j -= 3+mach[i].lgproc;
+			if(j <= 0)
+				j = 1;
+			snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].shortname, n);
+		}else
+			snprint(buf, sizeof buf, "%.*s", j, mach[i].shortname);
+		string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP, font, buf);
+	}
+
+	maxx = drawlabels(screen->r.max.x);
+
+	/* create graphs */
+	for(i=0; i<nmach; i++){
+		machr = Rect(startx+i*dx, starty, startx+(i+1)*dx - 1, screen->r.max.y);
+		if(i == nmach-1)
+			machr.max.x = maxx;
+		y = starty;
+		for(j=0; j<ngraph; j++, y+=dy){
+			g = &graph[i*ngraph+j];
+			/* allocate data */
+			ondata = g->ndata;
+			g->ndata = Dx(machr)+1;	/* may be too many if label will be drawn here; so what? */
+			g->data = erealloc(g->data, g->ndata*sizeof(ulong));
+			if(g->ndata > ondata)
+				memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(ulong));
+			/* set geometry */
+			g->r = machr;
+			g->r.min.y = y;
+			g->r.max.y = y+dy - 1;
+			if(j == ngraph-1)
+				g->r.max.y = screen->r.max.y;
+			draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
+			g->overflow = 0;
+			r = g->r;
+			r.max.y = r.min.y+font->height;
+			r.max.x = r.min.x+stringwidth(font, "999999999999");
+			freeimage(g->overtmp);
+			g->overtmp = nil;
+			if(r.max.x <= g->r.max.x)
+				g->overtmp = allocimage(display, r, screen->chan, 0, -1);
+			g->newvalue(g->mach, &v, &vmax, 0);
+			if(vmax == 0)
+				vmax = 1;
+			if(g->hiwater > vmax)
+				vmax = g->hiwater;
+			redraw(g, vmax);
+		}
+	}
+
+	flushimage(display, 1);
+}
+
+void
+eresized(int new)
+{
+	lockdisplay(display);
+	if(new && getwindow(display, Refnone) < 0) {
+		fprint(2, "stats: can't reattach to window\n");
+		killall("reattach");
+	}
+	resize();
+	unlockdisplay(display);
+}
+
+void
+inputproc(void)
+{
+	Event e;
+	int i;
+
+	for(;;){
+		switch(eread(Emouse|Ekeyboard, &e)){
+		case Emouse:
+			if(e.mouse.buttons == 4){
+				lockdisplay(display);
+				for(i=0; i<Nmenu2; i++)
+					if(present[i])
+						memmove(menu2str[i], "drop ", Opwid);
+					else
+						memmove(menu2str[i], "add  ", Opwid);
+				i = emenuhit(3, &e.mouse, &menu2);
+				if(i >= 0){
+					if(!present[i])
+						addgraph(i);
+					else if(ngraph > 1)
+						dropgraph(i);
+					resize();
+				}
+				unlockdisplay(display);
+			}
+			break;
+		case Ekeyboard:
+			if(e.kbdc==Kdel || e.kbdc=='q')
+				killall(nil);
+			break;
+		}
+	}
+}
+
+void
+startproc(void (*f)(void), int index)
+{
+	int pid;
+
+	switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
+	case -1:
+		fprint(2, "stats: fork failed: %r\n");
+		killall("fork failed");
+	case 0:
+		f();
+		fprint(2, "stats: %s process exits\n", procnames[index]);
+		if(index >= 0)
+			killall("process died");
+		exits(nil);
+	}
+	if(index >= 0)
+		pids[index] = pid;
+}
+
+void
+main(int argc, char *argv[])
+{
+	int i, j;
+	double secs;
+	uvlong v, vmax, nargs;
+	char args[100];
+
+	quotefmtinstall();
+
+	nmach = 1;
+	mysysname = getenv("sysname");
+	if(mysysname == nil){
+		fprint(2, "stats: can't find $sysname: %r\n");
+		exits("sysname");
+	}
+
+	nargs = 0;
+	ARGBEGIN{
+	case 'T':
+		secs = atof(EARGF(usage()));
+		if(secs > 0)
+			sleeptime = 1000*secs;
+		break;
+	case 'S':
+		scale = atof(EARGF(usage()));
+		if(scale <= 0)
+			usage();
+		break;
+	case 'L':
+		logscale++;
+		break;
+	case 'Y':
+		ylabels++;
+		break;
+	case 'O':
+		break;
+	default:
+		if(nargs>=sizeof args || strchr(argchars, ARGC())==nil)
+			usage();
+		args[nargs++] = ARGC();
+	}ARGEND
+
+	if(argc == 0){
+		mach = emalloc(nmach*sizeof(Machine));
+		initmach(&mach[0], mysysname);
+		readmach(&mach[0], 1);
+	}else{
+		rfork(RFNAMEG);
+		for(i=j=0; i<argc; i++){
+			if (addmachine(argv[i]))
+				readmach(&mach[j++], 1);
+		}
+		if (j == 0)
+			exits("connect");
+	}
+
+	for(i=0; i<nargs; i++)
+	switch(args[i]){
+	default:
+		fprint(2, "stats: internal error: unknown arg %c\n", args[i]);
+		usage();
+	case 'b':
+		addgraph(Mbattery);
+		break;
+	case 'c':
+		addgraph(Mcontext);
+		break;
+	case 'e':
+		addgraph(Mether);
+		break;
+	case 'E':
+		addgraph(Metherin);
+		addgraph(Metherout);
+		break;
+	case 'f':
+		addgraph(Mfault);
+		break;
+	case 'i':
+		addgraph(Mintr);
+		break;
+	case 'I':
+		addgraph(Mload);
+		addgraph(Midle);
+		addgraph(Minintr);
+		break;
+	case 'l':
+		addgraph(Mload);
+		break;
+	case 'm':
+		addgraph(Mmem);
+		break;
+	case 'n':
+		addgraph(Metherin);
+		addgraph(Metherout);
+		addgraph(Methererr);
+		break;
+	case 'p':
+		addgraph(Mtlbpurge);
+		break;
+	case 'r':
+		addgraph(Mreclaim);
+		break;
+	case 's':
+		addgraph(Msyscall);
+		break;
+	case 't':
+		addgraph(Mtlbmiss);
+		addgraph(Mtlbpurge);
+		break;
+	case '8':
+		addgraph(Msignal);
+		break;
+	case 'w':
+		addgraph(Mswap);
+		break;
+	case 'k':
+		addgraph(Mkern);
+		break;
+	case 'd':
+		addgraph(Mdraw);
+		break;
+	case 'z':
+		addgraph(Mtemp);
+		break;
+	}
+
+	if(ngraph == 0)
+		addgraph(Mload);
+
+	for(i=0; i<nmach; i++)
+		for(j=0; j<ngraph; j++)
+			graph[i*ngraph+j].mach = &mach[i];
+
+	if(initdraw(nil, nil, "stats") < 0){
+		fprint(2, "stats: initdraw failed: %r\n");
+		exits("initdraw");
+	}
+	display->locking = 1;	/* tell library we're using the display lock */
+	colinit();
+	einit(Emouse|Ekeyboard);
+	startproc(inputproc, Inputproc);
+	pids[Mainproc] = getpid();
+
+	resize();
+
+	unlockdisplay(display); /* display is still locked from initdraw() */
+	for(;;){
+		for(i=0; i<nmach; i++)
+			readmach(&mach[i], 0);
+		lockdisplay(display);
+		parity = 1-parity;
+		for(i=0; i<nmach*ngraph; i++){
+			graph[i].newvalue(graph[i].mach, &v, &vmax, 0);
+			if(vmax == 0)
+				vmax = 1;
+			graph[i].update(&graph[i], v, vmax);
+		}
+		flushimage(display, 1);
+		unlockdisplay(display);
+		sleep(sleeptime);
+	}
+}