xref: /plan9/sys/src/cmd/stats.c (revision fee9fd16fbcb514a4f462b56ff20c7199c0fc677)
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <auth.h>
5 #include <fcall.h>
6 #include <draw.h>
7 #include <event.h>
8 
9 #define	MAXNUM	10	/* maximum number of numbers on data line */
10 
11 typedef struct Graph	Graph;
12 typedef struct Machine	Machine;
13 
14 struct Graph
15 {
16 	int		colindex;
17 	Rectangle	r;
18 	uvlong		*data;
19 	int		ndata;
20 	char		*label;
21 	void		(*newvalue)(Machine*, uvlong*, uvlong*, int);
22 	void		(*update)(Graph*, uvlong, uvlong);
23 	Machine		*mach;
24 	int		overflow;
25 	Image		*overtmp;
26 };
27 
28 enum
29 {
30 	/* old /dev/swap */
31 	Mem		= 0,
32 	Maxmem,
33 	Swap,
34 	Maxswap,
35 
36 	/* /dev/sysstats */
37 	Procno	= 0,
38 	Context,
39 	Interrupt,
40 	Syscall,
41 	Fault,
42 	TLBfault,
43 	TLBpurge,
44 	Load,
45 	Idle,
46 	InIntr,
47 	/* /net/ether0/stats */
48 	In		= 0,
49 	Link,
50 	Out,
51 	Err0,
52 };
53 
54 struct Machine
55 {
56 	char		*name;
57 	char		*shortname;
58 	int		remote;
59 	int		statsfd;
60 	int		swapfd;
61 	int		etherfd;
62 	int		ifstatsfd;
63 	int		batteryfd;
64 	int		bitsybatfd;
65 	int		tempfd;
66 	int		disable;
67 
68 	uvlong		devswap[4];
69 	uvlong		devsysstat[10];
70 	uvlong		prevsysstat[10];
71 	int		nproc;
72 	int		lgproc;
73 	uvlong		netetherstats[8];
74 	uvlong		prevetherstats[8];
75 	uvlong		batterystats[2];
76 	uvlong		netetherifstats[2];
77 	uvlong		temp[10];
78 
79 	/* big enough to hold /dev/sysstat even with many processors */
80 	char		buf[8*1024];
81 	char		*bufp;
82 	char		*ebufp;
83 };
84 
85 enum
86 {
87 	Mainproc,
88 	Mouseproc,
89 	NPROC,
90 };
91 
92 enum
93 {
94 	Ncolor		= 6,
95 	Ysqueeze	= 2,	/* vertical squeezing of label text */
96 	Labspace	= 2,	/* room around label */
97 	Dot		= 2,	/* height of dot */
98 	Opwid		= 5,	/* strlen("add  ") or strlen("drop ") */
99 	Nlab		= 3,	/* max number of labels on y axis */
100 	Lablen		= 16,	/* max length of label */
101 	Lx		= 4,	/* label tick length */
102 };
103 
104 enum Menu2
105 {
106 	Mbattery,
107 	Mcontext,
108 	Mether,
109 	Methererr,
110 	Metherin,
111 	Metherout,
112 	Mfault,
113 	Midle,
114 	Minintr,
115 	Mintr,
116 	Mload,
117 	Mmem,
118 	Mswap,
119 	Msyscall,
120 	Mtlbmiss,
121 	Mtlbpurge,
122 	Msignal,
123 	Mtemp,
124 	Nmenu2,
125 };
126 
127 char	*menu2str[Nmenu2+1] = {
128 	"add  battery ",
129 	"add  context ",
130 	"add  ether   ",
131 	"add  ethererr",
132 	"add  etherin ",
133 	"add  etherout",
134 	"add  fault   ",
135 	"add  idle    ",
136 	"add  inintr  ",
137 	"add  intr    ",
138 	"add  load    ",
139 	"add  mem     ",
140 	"add  swap    ",
141 	"add  syscall ",
142 	"add  tlbmiss ",
143 	"add  tlbpurge",
144 	"add  802.11b ",
145 	"add  temp    ",
146 	nil,
147 };
148 
149 
150 void	contextval(Machine*, uvlong*, uvlong*, int),
151 	etherval(Machine*, uvlong*, uvlong*, int),
152 	ethererrval(Machine*, uvlong*, uvlong*, int),
153 	etherinval(Machine*, uvlong*, uvlong*, int),
154 	etheroutval(Machine*, uvlong*, uvlong*, int),
155 	faultval(Machine*, uvlong*, uvlong*, int),
156 	intrval(Machine*, uvlong*, uvlong*, int),
157 	inintrval(Machine*, uvlong*, uvlong*, int),
158 	loadval(Machine*, uvlong*, uvlong*, int),
159 	idleval(Machine*, uvlong*, uvlong*, int),
160 	memval(Machine*, uvlong*, uvlong*, int),
161 	swapval(Machine*, uvlong*, uvlong*, int),
162 	syscallval(Machine*, uvlong*, uvlong*, int),
163 	tlbmissval(Machine*, uvlong*, uvlong*, int),
164 	tlbpurgeval(Machine*, uvlong*, uvlong*, int),
165 	batteryval(Machine*, uvlong*, uvlong*, int),
166 	signalval(Machine*, uvlong*, uvlong*, int),
167 	tempval(Machine*, uvlong*, uvlong*, int);
168 
169 Menu	menu2 = {menu2str, nil};
170 int	present[Nmenu2];
171 void	(*newvaluefn[Nmenu2])(Machine*, uvlong*, uvlong*, int init) = {
172 	batteryval,
173 	contextval,
174 	etherval,
175 	ethererrval,
176 	etherinval,
177 	etheroutval,
178 	faultval,
179 	idleval,
180 	inintrval,
181 	intrval,
182 	loadval,
183 	memval,
184 	swapval,
185 	syscallval,
186 	tlbmissval,
187 	tlbpurgeval,
188 	signalval,
189 	tempval,
190 };
191 
192 Image	*cols[Ncolor][3];
193 Graph	*graph;
194 Machine	*mach;
195 Font	*mediumfont;
196 char	*mysysname;
197 char	*mycputype;
198 char	argchars[] = "8bceEfiImlnpstwz";
199 int	pids[NPROC];
200 int 	parity;	/* toggled to avoid patterns in textured background */
201 int	nmach;
202 int	ngraph;	/* totaly number is ngraph*nmach */
203 double	scale = 1.0;
204 int	logscale = 0;
205 int	ylabels = 0;
206 int	oldsystem = 0;
207 int 	sleeptime = 1000;
208 
209 char	*procnames[NPROC] = {"main", "mouse"};
210 
211 void
killall(char * s)212 killall(char *s)
213 {
214 	int i, pid;
215 
216 	pid = getpid();
217 	for(i=0; i<NPROC; i++)
218 		if(pids[i] && pids[i]!=pid)
219 			postnote(PNPROC, pids[i], "kill");
220 	exits(s);
221 }
222 
223 void*
emalloc(ulong sz)224 emalloc(ulong sz)
225 {
226 	void *v;
227 	v = malloc(sz);
228 	if(v == nil) {
229 		fprint(2, "stats: out of memory allocating %ld: %r\n", sz);
230 		killall("mem");
231 	}
232 	memset(v, 0, sz);
233 	return v;
234 }
235 
236 void*
erealloc(void * v,ulong sz)237 erealloc(void *v, ulong sz)
238 {
239 	v = realloc(v, sz);
240 	if(v == nil) {
241 		fprint(2, "stats: out of memory reallocating %ld: %r\n", sz);
242 		killall("mem");
243 	}
244 	return v;
245 }
246 
247 char*
estrdup(char * s)248 estrdup(char *s)
249 {
250 	char *t;
251 	if((t = strdup(s)) == nil) {
252 		fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s);
253 		killall("mem");
254 	}
255 	return t;
256 }
257 
258 void
mkcol(int i,int c0,int c1,int c2)259 mkcol(int i, int c0, int c1, int c2)
260 {
261 	cols[i][0] = allocimagemix(display, c0, DWhite);
262 	cols[i][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c1);
263 	cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2);
264 }
265 
266 void
colinit(void)267 colinit(void)
268 {
269 	mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
270 	if(mediumfont == nil)
271 		mediumfont = font;
272 
273 	/* Peach */
274 	mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
275 	/* Aqua */
276 	mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
277 	/* Yellow */
278 	mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
279 	/* Green */
280 	mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
281 	/* Blue */
282 	mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
283 	/* Grey */
284 	cols[5][0] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
285 	cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
286 	cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF);
287 }
288 
289 int
loadbuf(Machine * m,int * fd)290 loadbuf(Machine *m, int *fd)
291 {
292 	int n;
293 
294 
295 	if(*fd < 0)
296 		return 0;
297 	seek(*fd, 0, 0);
298 	n = read(*fd, m->buf, sizeof m->buf-1);
299 	if(n <= 0){
300 		close(*fd);
301 		*fd = -1;
302 		return 0;
303 	}
304 	m->bufp = m->buf;
305 	m->ebufp = m->buf+n;
306 	m->buf[n] = 0;
307 	return 1;
308 }
309 
310 void
label(Point p,int dy,char * text)311 label(Point p, int dy, char *text)
312 {
313 	char *s;
314 	Rune r[2];
315 	int w, maxw, maxy;
316 
317 	p.x += Labspace;
318 	maxy = p.y+dy;
319 	maxw = 0;
320 	r[1] = '\0';
321 	for(s=text; *s; ){
322 		if(p.y+mediumfont->height-Ysqueeze > maxy)
323 			break;
324 		w = chartorune(r, s);
325 		s += w;
326 		w = runestringwidth(mediumfont, r);
327 		if(w > maxw)
328 			maxw = w;
329 		runestring(screen, p, display->black, ZP, mediumfont, r);
330 		p.y += mediumfont->height-Ysqueeze;
331 	}
332 }
333 
334 Point
paritypt(int x)335 paritypt(int x)
336 {
337 	return Pt(x+parity, 0);
338 }
339 
340 Point
datapoint(Graph * g,int x,uvlong v,uvlong vmax)341 datapoint(Graph *g, int x, uvlong v, uvlong vmax)
342 {
343 	Point p;
344 	double y;
345 
346 	p.x = x;
347 	y = ((double)v)/(vmax*scale);
348 	if(logscale){
349 		/*
350 		 * Arrange scale to cover a factor of 1000.
351 		 * vmax corresponds to the 100 mark.
352 		 * 10*vmax is the top of the scale.
353 		 */
354 		if(y <= 0.)
355 			y = 0;
356 		else{
357 			y = log10(y);
358 			/* 1 now corresponds to the top; -2 to the bottom; rescale */
359 			y = (y+2.)/3.;
360 		}
361 	}
362 	if(y < 0x7fffffff){	/* avoid floating overflow */
363 		p.y = g->r.max.y - Dy(g->r)*y - Dot;
364 		if(p.y < g->r.min.y)
365 			p.y = g->r.min.y;
366 		if(p.y > g->r.max.y-Dot)
367 			p.y = g->r.max.y-Dot;
368 	}else
369 		p.y = g->r.max.y-Dot;
370 	return p;
371 }
372 
373 void
drawdatum(Graph * g,int x,uvlong prev,uvlong v,uvlong vmax)374 drawdatum(Graph *g, int x, uvlong prev, uvlong v, uvlong vmax)
375 {
376 	int c;
377 	Point p, q;
378 
379 	c = g->colindex;
380 	p = datapoint(g, x, v, vmax);
381 	q = datapoint(g, x, prev, vmax);
382 	if(p.y < q.y){
383 		draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
384 		draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
385 		draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
386 	}else{
387 		draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
388 		draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
389 		draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
390 	}
391 
392 }
393 
394 void
redraw(Graph * g,uvlong vmax)395 redraw(Graph *g, uvlong vmax)
396 {
397 	int i, c;
398 
399 	c = g->colindex;
400 	draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
401 	for(i=1; i<Dx(g->r); i++)
402 		drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
403 	drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
404 	g->overflow = 0;
405 }
406 
407 void
update1(Graph * g,uvlong v,uvlong vmax)408 update1(Graph *g, uvlong v, uvlong vmax)
409 {
410 	char buf[48];
411 	int overflow;
412 
413 	if(g->overflow && g->overtmp!=nil)
414 		draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
415 	draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
416 	drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
417 	memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
418 	g->data[0] = v;
419 	g->overflow = 0;
420 	if(logscale)
421 		overflow = (v>10*vmax*scale);
422 	else
423 		overflow = (v>vmax*scale);
424 	if(overflow && g->overtmp!=nil){
425 		g->overflow = 1;
426 		draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);
427 		sprint(buf, "%llud", v);
428 		string(screen, g->overtmp->r.min, display->black, ZP, mediumfont, buf);
429 	}
430 }
431 
432 /* read one line of text from buffer and process integers */
433 int
readnums(Machine * m,int n,uvlong * a,int spanlines)434 readnums(Machine *m, int n, uvlong *a, int spanlines)
435 {
436 	int i;
437 	char *p, *q, *ep;
438 
439 	if(spanlines)
440 		ep = m->ebufp;
441 	else
442 		for(ep=m->bufp; ep<m->ebufp; ep++)
443 			if(*ep == '\n')
444 				break;
445 	p = m->bufp;
446 	for(i=0; i<n && p<ep; i++){
447 		while(p<ep && (!isascii(*p) || !isdigit(*p)) && *p!='-')
448 			p++;
449 		if(p == ep)
450 			break;
451 		a[i] = strtoull(p, &q, 10);
452 		p = q;
453 	}
454 	if(ep < m->ebufp)
455 		ep++;
456 	m->bufp = ep;
457 	return i == n;
458 }
459 
460 /* Network on fd1, mount driver on fd0 */
461 static int
filter(int fd)462 filter(int fd)
463 {
464 	int p[2];
465 
466 	if(pipe(p) < 0){
467 		fprint(2, "stats: can't pipe: %r\n");
468 		killall("pipe");
469 	}
470 
471 	switch(rfork(RFNOWAIT|RFPROC|RFFDG)) {
472 	case -1:
473 		sysfatal("rfork record module");
474 	case 0:
475 		dup(fd, 1);
476 		close(fd);
477 		dup(p[0], 0);
478 		close(p[0]);
479 		close(p[1]);
480 		execl("/bin/aux/fcall", "fcall", nil);
481 		fprint(2, "stats: can't exec fcall: %r\n");
482 		killall("fcall");
483 	default:
484 		close(fd);
485 		close(p[0]);
486 	}
487 	return p[1];
488 }
489 
490 /*
491  * 9fs
492  */
493 int
connect9fs(char * addr)494 connect9fs(char *addr)
495 {
496 	char dir[256], *na;
497 	int fd;
498 
499 	fprint(2, "connect9fs...");
500 	na = netmkaddr(addr, 0, "9fs");
501 
502 	fprint(2, "dial %s...", na);
503 	if((fd = dial(na, 0, dir, 0)) < 0)
504 		return -1;
505 
506 	fprint(2, "dir %s...", dir);
507 //	if(strstr(dir, "tcp"))
508 //		fd = filter(fd);
509 	return fd;
510 }
511 
512 int
old9p(int fd)513 old9p(int fd)
514 {
515 	int p[2];
516 
517 	if(pipe(p) < 0)
518 		return -1;
519 
520 	switch(rfork(RFPROC|RFFDG|RFNAMEG)) {
521 	case -1:
522 		return -1;
523 	case 0:
524 		if(fd != 1){
525 			dup(fd, 1);
526 			close(fd);
527 		}
528 		if(p[0] != 0){
529 			dup(p[0], 0);
530 			close(p[0]);
531 		}
532 		close(p[1]);
533 		if(0){
534 			fd = open("/sys/log/cpu", OWRITE);
535 			if(fd != 2){
536 				dup(fd, 2);
537 				close(fd);
538 			}
539 			execl("/bin/srvold9p", "srvold9p", "-ds", nil);
540 		} else
541 			execl("/bin/srvold9p", "srvold9p", "-s", nil);
542 		return -1;
543 	default:
544 		close(fd);
545 		close(p[0]);
546 	}
547 	return p[1];
548 }
549 
550 
551 /*
552  * exportfs
553  */
554 int
connectexportfs(char * addr)555 connectexportfs(char *addr)
556 {
557 	char buf[ERRMAX], dir[256], *na;
558 	int fd, n;
559 	char *tree;
560 	AuthInfo *ai;
561 
562 	tree = "/";
563 	na = netmkaddr(addr, 0, "exportfs");
564 	if((fd = dial(na, 0, dir, 0)) < 0)
565 		return -1;
566 
567 	ai = auth_proxy(fd, auth_getkey, "proto=p9any role=client");
568 	if(ai == nil)
569 		return -1;
570 
571 	n = write(fd, tree, strlen(tree));
572 	if(n < 0){
573 		close(fd);
574 		return -1;
575 	}
576 
577 	strcpy(buf, "can't read tree");
578 	n = read(fd, buf, sizeof buf - 1);
579 	if(n!=2 || buf[0]!='O' || buf[1]!='K'){
580 		buf[sizeof buf - 1] = '\0';
581 		werrstr("bad remote tree: %s\n", buf);
582 		close(fd);
583 		return -1;
584 	}
585 
586 //	if(strstr(dir, "tcp"))
587 //		fd = filter(fd);
588 
589 	if(oldsystem)
590 		return old9p(fd);
591 
592 	return fd;
593 }
594 
595 int
readswap(Machine * m,uvlong * a)596 readswap(Machine *m, uvlong *a)
597 {
598 	if(strstr(m->buf, "memory\n")){
599 		/* new /dev/swap - skip first 3 numbers */
600 		if(!readnums(m, 7, a, 1))
601 			return 0;
602 		a[0] = a[3];
603 		a[1] = a[4];
604 		a[2] = a[5];
605 		a[3] = a[6];
606 		return 1;
607 	}
608 	return readnums(m, nelem(m->devswap), a, 0);
609 }
610 
611 char*
shortname(char * s)612 shortname(char *s)
613 {
614 	char *p, *e;
615 
616 	p = estrdup(s);
617 	e = strchr(p, '.');
618 	if(e)
619 		*e = 0;
620 	return p;
621 }
622 
623 int
ilog10(uvlong j)624 ilog10(uvlong j)
625 {
626 	int i;
627 
628 	for(i = 0; j >= 10; i++)
629 		j /= 10;
630 	return i;
631 }
632 
633 int
initmach(Machine * m,char * name)634 initmach(Machine *m, char *name)
635 {
636 	int n, fd;
637 	uvlong a[MAXNUM];
638 	char *p, mpt[256], buf[256];
639 
640 	p = strchr(name, '!');
641 	if(p)
642 		p++;
643 	else
644 		p = name;
645 	m->name = estrdup(p);
646 	m->shortname = shortname(p);
647 	m->remote = (strcmp(p, mysysname) != 0);
648 	if(m->remote == 0)
649 		strcpy(mpt, "");
650 	else{
651 		snprint(mpt, sizeof mpt, "/n/%s", p);
652 		fd = connectexportfs(name);
653 		if(fd < 0){
654 			fprint(2, "can't connect to %s: %r\n", name);
655 			return 0;
656 		}
657 		/* BUG? need to use amount() now? */
658 		if(mount(fd, -1, mpt, MREPL, "") < 0){
659 			fprint(2, "stats: mount %s on %s failed (%r); trying /n/sid\n", name, mpt);
660 			strcpy(mpt, "/n/sid");
661 			if(mount(fd, -1, mpt, MREPL, "") < 0){
662 				fprint(2, "stats: mount %s on %s failed: %r\n", name, mpt);
663 				return 0;
664 			}
665 		}
666 	}
667 
668 	snprint(buf, sizeof buf, "%s/dev/swap", mpt);
669 	m->swapfd = open(buf, OREAD);
670 	if(loadbuf(m, &m->swapfd) && readswap(m, a))
671 		memmove(m->devswap, a, sizeof m->devswap);
672 	else{
673 		m->devswap[Maxswap] = 100;
674 		m->devswap[Maxmem] = 100;
675 	}
676 
677 	snprint(buf, sizeof buf, "%s/dev/sysstat", mpt);
678 	m->statsfd = open(buf, OREAD);
679 	if(loadbuf(m, &m->statsfd)){
680 		for(n=0; readnums(m, nelem(m->devsysstat), a, 0); n++)
681 			;
682 		m->nproc = n;
683 	}else
684 		m->nproc = 1;
685 	m->lgproc = ilog10(m->nproc);
686 
687 	snprint(buf, sizeof buf, "%s/net/ether0/stats", mpt);
688 	m->etherfd = open(buf, OREAD);
689 	if(loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1))
690 		memmove(m->netetherstats, a, sizeof m->netetherstats);
691 
692 	snprint(buf, sizeof buf, "%s/net/ether0/ifstats", mpt);
693 	m->ifstatsfd = open(buf, OREAD);
694 	if(loadbuf(m, &m->ifstatsfd)){
695 		/* need to check that this is a wavelan interface */
696 		if(strncmp(m->buf, "Signal: ", 8) == 0 && readnums(m, nelem(m->netetherifstats), a, 1))
697 			memmove(m->netetherifstats, a, sizeof m->netetherifstats);
698 	}
699 
700 	snprint(buf, sizeof buf, "%s/mnt/apm/battery", mpt);
701 	m->batteryfd = open(buf, OREAD);
702 	m->bitsybatfd = -1;
703 	if(m->batteryfd >= 0){
704 		if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
705 			memmove(m->batterystats, a, sizeof(m->batterystats));
706 	}else{
707 		snprint(buf, sizeof buf, "%s/dev/battery", mpt);
708 		m->bitsybatfd = open(buf, OREAD);
709 		if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
710 			memmove(m->batterystats, a, sizeof(m->batterystats));
711 	}
712 	snprint(buf, sizeof buf, "%s/dev/cputemp", mpt);
713 	m->tempfd = open(buf, OREAD);
714 	if(loadbuf(m, &m->tempfd))
715 		for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
716 			 m->temp[n] = a[0];
717 	return 1;
718 }
719 
720 jmp_buf catchalarm;
721 
722 void
alarmed(void * a,char * s)723 alarmed(void *a, char *s)
724 {
725 	if(strcmp(s, "alarm") == 0)
726 		notejmp(a, catchalarm, 1);
727 	noted(NDFLT);
728 }
729 
730 int
needswap(int init)731 needswap(int init)
732 {
733 	return init | present[Mmem] | present[Mswap];
734 }
735 
736 
737 int
needstat(int init)738 needstat(int init)
739 {
740 	return init | present[Mcontext]  | present[Mfault] | present[Mintr] | present[Mload] | present[Midle] |
741 		present[Minintr] | present[Msyscall] | present[Mtlbmiss] | present[Mtlbpurge];
742 }
743 
744 
745 int
needether(int init)746 needether(int init)
747 {
748 	return init | present[Mether] | present[Metherin] | present[Metherout] | present[Methererr];
749 }
750 
751 int
needbattery(int init)752 needbattery(int init)
753 {
754 	return init | present[Mbattery];
755 }
756 
757 int
needsignal(int init)758 needsignal(int init)
759 {
760 	return init | present[Msignal];
761 }
762 
763 int
needtemp(int init)764 needtemp(int init)
765 {
766 	return init | present[Mtemp];
767 }
768 
769 void
readmach(Machine * m,int init)770 readmach(Machine *m, int init)
771 {
772 	int n, i;
773 	uvlong a[nelem(m->devsysstat)];
774 	char buf[32];
775 
776 	if(m->remote && (m->disable || setjmp(catchalarm))){
777 		if (m->disable++ >= 5)
778 			m->disable = 0; /* give it another chance */
779 		memmove(m->devsysstat, m->prevsysstat, sizeof m->devsysstat);
780 		memmove(m->netetherstats, m->prevetherstats, sizeof m->netetherstats);
781 		return;
782 	}
783 	snprint(buf, sizeof buf, "%s", m->name);
784 	if (strcmp(m->name, buf) != 0){
785 		free(m->name);
786 		m->name = estrdup(buf);
787 		free(m->shortname);
788 		m->shortname = shortname(buf);
789 		if(display != nil)	/* else we're still initializing */
790 			eresized(0);
791 	}
792 	if(m->remote){
793 		notify(alarmed);
794 		alarm(5000);
795 	}
796 	if(needswap(init) && loadbuf(m, &m->swapfd) && readswap(m, a))
797 		memmove(m->devswap, a, sizeof m->devswap);
798 	if(needstat(init) && loadbuf(m, &m->statsfd)){
799 		memmove(m->prevsysstat, m->devsysstat, sizeof m->devsysstat);
800 		memset(m->devsysstat, 0, sizeof m->devsysstat);
801 		for(n=0; n<m->nproc && readnums(m, nelem(m->devsysstat), a, 0); n++)
802 			for(i=0; i<nelem(m->devsysstat); i++)
803 				m->devsysstat[i] += a[i];
804 	}
805 	if(needether(init) && loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1)){
806 		memmove(m->prevetherstats, m->netetherstats, sizeof m->netetherstats);
807 		memmove(m->netetherstats, a, sizeof m->netetherstats);
808 	}
809 	if(needsignal(init) && loadbuf(m, &m->ifstatsfd) && strncmp(m->buf, "Signal: ", 8)==0 && readnums(m, nelem(m->netetherifstats), a, 1)){
810 		memmove(m->netetherifstats, a, sizeof m->netetherifstats);
811 	}
812 	if(needbattery(init) && loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
813 		memmove(m->batterystats, a, sizeof(m->batterystats));
814 	if(needbattery(init) && loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
815 		memmove(m->batterystats, a, sizeof(m->batterystats));
816 	if(needtemp(init) && loadbuf(m, &m->tempfd))
817 		for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
818 			 m->temp[n] = a[0];
819 	if(m->remote){
820 		alarm(0);
821 		notify(nil);
822 	}
823 }
824 
825 void
memval(Machine * m,uvlong * v,uvlong * vmax,int)826 memval(Machine *m, uvlong *v, uvlong *vmax, int)
827 {
828 	*v = m->devswap[Mem];
829 	*vmax = m->devswap[Maxmem];
830 }
831 
832 void
swapval(Machine * m,uvlong * v,uvlong * vmax,int)833 swapval(Machine *m, uvlong *v, uvlong *vmax, int)
834 {
835 	*v = m->devswap[Swap];
836 	*vmax = m->devswap[Maxswap];
837 }
838 
839 void
contextval(Machine * m,uvlong * v,uvlong * vmax,int init)840 contextval(Machine *m, uvlong *v, uvlong *vmax, int init)
841 {
842 	*v = m->devsysstat[Context]-m->prevsysstat[Context];
843 	*vmax = sleeptime*m->nproc;
844 	if(init)
845 		*vmax = sleeptime;
846 }
847 
848 /*
849  * bug: need to factor in HZ
850  */
851 void
intrval(Machine * m,uvlong * v,uvlong * vmax,int init)852 intrval(Machine *m, uvlong *v, uvlong *vmax, int init)
853 {
854 	*v = m->devsysstat[Interrupt]-m->prevsysstat[Interrupt];
855 	*vmax = sleeptime*m->nproc*10;
856 	if(init)
857 		*vmax = sleeptime*10;
858 }
859 
860 void
syscallval(Machine * m,uvlong * v,uvlong * vmax,int init)861 syscallval(Machine *m, uvlong *v, uvlong *vmax, int init)
862 {
863 	*v = m->devsysstat[Syscall]-m->prevsysstat[Syscall];
864 	*vmax = sleeptime*m->nproc;
865 	if(init)
866 		*vmax = sleeptime;
867 }
868 
869 void
faultval(Machine * m,uvlong * v,uvlong * vmax,int init)870 faultval(Machine *m, uvlong *v, uvlong *vmax, int init)
871 {
872 	*v = m->devsysstat[Fault]-m->prevsysstat[Fault];
873 	*vmax = sleeptime*m->nproc;
874 	if(init)
875 		*vmax = sleeptime;
876 }
877 
878 void
tlbmissval(Machine * m,uvlong * v,uvlong * vmax,int init)879 tlbmissval(Machine *m, uvlong *v, uvlong *vmax, int init)
880 {
881 	*v = m->devsysstat[TLBfault]-m->prevsysstat[TLBfault];
882 	*vmax = (sleeptime/1000)*10*m->nproc;
883 	if(init)
884 		*vmax = (sleeptime/1000)*10;
885 	if (mycputype && strcmp(mycputype, "mips") == 0)
886 		*vmax *= 50000;		/* mainly for 16-entry tlbs (rb) */
887 }
888 
889 void
tlbpurgeval(Machine * m,uvlong * v,uvlong * vmax,int init)890 tlbpurgeval(Machine *m, uvlong *v, uvlong *vmax, int init)
891 {
892 	*v = m->devsysstat[TLBpurge]-m->prevsysstat[TLBpurge];
893 	*vmax = (sleeptime/1000)*10*m->nproc;
894 	if(init)
895 		*vmax = (sleeptime/1000)*10;
896 }
897 
898 void
loadval(Machine * m,uvlong * v,uvlong * vmax,int init)899 loadval(Machine *m, uvlong *v, uvlong *vmax, int init)
900 {
901 	*v = m->devsysstat[Load];
902 	*vmax = 1000*m->nproc;
903 	if(init)
904 		*vmax = 1000;
905 }
906 
907 void
idleval(Machine * m,uvlong * v,uvlong * vmax,int)908 idleval(Machine *m, uvlong *v, uvlong *vmax, int)
909 {
910 	*v = m->devsysstat[Idle]/m->nproc;
911 	*vmax = 100;
912 }
913 
914 void
inintrval(Machine * m,uvlong * v,uvlong * vmax,int)915 inintrval(Machine *m, uvlong *v, uvlong *vmax, int)
916 {
917 	*v = m->devsysstat[InIntr]/m->nproc;
918 	*vmax = 100;
919 }
920 
921 void
etherval(Machine * m,uvlong * v,uvlong * vmax,int init)922 etherval(Machine *m, uvlong *v, uvlong *vmax, int init)
923 {
924 	*v = m->netetherstats[In]-m->prevetherstats[In] + m->netetherstats[Out]-m->prevetherstats[Out];
925 	*vmax = sleeptime*m->nproc;
926 	if(init)
927 		*vmax = sleeptime;
928 }
929 
930 void
etherinval(Machine * m,uvlong * v,uvlong * vmax,int init)931 etherinval(Machine *m, uvlong *v, uvlong *vmax, int init)
932 {
933 	*v = m->netetherstats[In]-m->prevetherstats[In];
934 	*vmax = sleeptime*m->nproc;
935 	if(init)
936 		*vmax = sleeptime;
937 }
938 
939 void
etheroutval(Machine * m,uvlong * v,uvlong * vmax,int init)940 etheroutval(Machine *m, uvlong *v, uvlong *vmax, int init)
941 {
942 	*v = m->netetherstats[Out]-m->prevetherstats[Out];
943 	*vmax = sleeptime*m->nproc;
944 	if(init)
945 		*vmax = sleeptime;
946 }
947 
948 void
ethererrval(Machine * m,uvlong * v,uvlong * vmax,int init)949 ethererrval(Machine *m, uvlong *v, uvlong *vmax, int init)
950 {
951 	int i;
952 
953 	*v = 0;
954 	for(i=Err0; i<nelem(m->netetherstats); i++)
955 		*v += m->netetherstats[i];
956 	*vmax = (sleeptime/1000)*10*m->nproc;
957 	if(init)
958 		*vmax = (sleeptime/1000)*10;
959 }
960 
961 void
batteryval(Machine * m,uvlong * v,uvlong * vmax,int)962 batteryval(Machine *m, uvlong *v, uvlong *vmax, int)
963 {
964 	*v = m->batterystats[0];
965 	if(m->bitsybatfd >= 0)
966 		*vmax = 184;		// at least on my bitsy...
967 	else
968 		*vmax = 100;
969 }
970 
971 void
signalval(Machine * m,uvlong * v,uvlong * vmax,int)972 signalval(Machine *m, uvlong *v, uvlong *vmax, int)
973 {
974 	ulong l;
975 
976 	*vmax = sleeptime;
977 	l = m->netetherifstats[0];
978 	/*
979 	 * Range is seen to be from about -45 (strong) to -95 (weak); rescale
980 	 */
981 	if(l == 0){	/* probably not present */
982 		*v = 0;
983 		return;
984 	}
985 	*v = 20*(l+95);
986 }
987 
988 void
tempval(Machine * m,uvlong * v,uvlong * vmax,int)989 tempval(Machine *m, uvlong *v, uvlong *vmax, int)
990 {
991 	ulong l;
992 
993 	*vmax = sleeptime;
994 	l = m->temp[0];
995 	if(l == ~0 || l == 0)
996 		*v = 0;
997 	else
998 		*v = (l-20)*27;
999 }
1000 
1001 void
usage(void)1002 usage(void)
1003 {
1004 	fprint(2, "usage: stats [-O] [-S scale] [-LY] [-%s] [machine...]\n", argchars);
1005 	exits("usage");
1006 }
1007 
1008 void
addgraph(int n)1009 addgraph(int n)
1010 {
1011 	Graph *g, *ograph;
1012 	int i, j;
1013 	static int nadd;
1014 
1015 	if(n > nelem(menu2str))
1016 		abort();
1017 	/* avoid two adjacent graphs of same color */
1018 	if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
1019 		nadd++;
1020 	ograph = graph;
1021 	graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
1022 	for(i=0; i<nmach; i++)
1023 		for(j=0; j<ngraph; j++)
1024 			graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
1025 	free(ograph);
1026 	ngraph++;
1027 	for(i=0; i<nmach; i++){
1028 		g = &graph[i*ngraph+(ngraph-1)];
1029 		memset(g, 0, sizeof(Graph));
1030 		g->label = menu2str[n]+Opwid;
1031 		g->newvalue = newvaluefn[n];
1032 		g->update = update1;	/* no other update functions yet */
1033 		g->mach = &mach[i];
1034 		g->colindex = nadd%Ncolor;
1035 	}
1036 	present[n] = 1;
1037 	nadd++;
1038 }
1039 
1040 void
dropgraph(int which)1041 dropgraph(int which)
1042 {
1043 	Graph *ograph;
1044 	int i, j, n;
1045 
1046 	if(which > nelem(menu2str))
1047 		abort();
1048 	/* convert n to index in graph table */
1049 	n = -1;
1050 	for(i=0; i<ngraph; i++)
1051 		if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
1052 			n = i;
1053 			break;
1054 		}
1055 	if(n < 0){
1056 		fprint(2, "stats: internal error can't drop graph\n");
1057 		killall("error");
1058 	}
1059 	ograph = graph;
1060 	graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
1061 	for(i=0; i<nmach; i++){
1062 		for(j=0; j<n; j++)
1063 			graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
1064 		free(ograph[i*ngraph+j].data);
1065 		freeimage(ograph[i*ngraph+j].overtmp);
1066 		for(j++; j<ngraph; j++)
1067 			graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
1068 	}
1069 	free(ograph);
1070 	ngraph--;
1071 	present[which] = 0;
1072 }
1073 
1074 int
addmachine(char * name)1075 addmachine(char *name)
1076 {
1077 	if(ngraph > 0){
1078 		fprint(2, "stats: internal error: ngraph>0 in addmachine()\n");
1079 		usage();
1080 	}
1081 	if(mach == nil)
1082 		nmach = 0;	/* a little dance to get us started with local machine by default */
1083 	mach = erealloc(mach, (nmach+1)*sizeof(Machine));
1084 	memset(mach+nmach, 0, sizeof(Machine));
1085 	if (initmach(mach+nmach, name)){
1086 		nmach++;
1087 		return 1;
1088 	} else
1089 		return 0;
1090 }
1091 
1092 void
labelstrs(Graph * g,char strs[Nlab][Lablen],int * np)1093 labelstrs(Graph *g, char strs[Nlab][Lablen], int *np)
1094 {
1095 	int j;
1096 	uvlong v, vmax;
1097 
1098 	g->newvalue(g->mach, &v, &vmax, 1);
1099 	if(logscale){
1100 		for(j=1; j<=2; j++)
1101 			sprint(strs[j-1], "%g", scale*pow(10., j)*(double)vmax/100.);
1102 		*np = 2;
1103 	}else{
1104 		for(j=1; j<=3; j++)
1105 			sprint(strs[j-1], "%g", scale*(double)j*(double)vmax/4.0);
1106 		*np = 3;
1107 	}
1108 }
1109 
1110 int
labelwidth(void)1111 labelwidth(void)
1112 {
1113 	int i, j, n, w, maxw;
1114 	char strs[Nlab][Lablen];
1115 
1116 	maxw = 0;
1117 	for(i=0; i<ngraph; i++){
1118 		/* choose value for rightmost graph */
1119 		labelstrs(&graph[ngraph*(nmach-1)+i], strs, &n);
1120 		for(j=0; j<n; j++){
1121 			w = stringwidth(mediumfont, strs[j]);
1122 			if(w > maxw)
1123 				maxw = w;
1124 		}
1125 	}
1126 	return maxw;
1127 }
1128 
1129 void
resize(void)1130 resize(void)
1131 {
1132 	int i, j, k, n, startx, starty, x, y, dx, dy, ly, ondata, maxx, wid, nlab;
1133 	Graph *g;
1134 	Rectangle machr, r;
1135 	uvlong v, vmax;
1136 	char buf[128], labs[Nlab][Lablen];
1137 
1138 	draw(screen, screen->r, display->white, nil, ZP);
1139 
1140 	/* label left edge */
1141 	x = screen->r.min.x;
1142 	y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
1143 	dy = (screen->r.max.y - y)/ngraph;
1144 	dx = Labspace+stringwidth(mediumfont, "0")+Labspace;
1145 	startx = x+dx+1;
1146 	starty = y;
1147 	for(i=0; i<ngraph; i++,y+=dy){
1148 		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
1149 		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
1150 		label(Pt(x, y), dy, graph[i].label);
1151 		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
1152 	}
1153 
1154 	/* label top edge */
1155 	dx = (screen->r.max.x - startx)/nmach;
1156 	for(x=startx, i=0; i<nmach; i++,x+=dx){
1157 		draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
1158 		j = dx/stringwidth(mediumfont, "0");
1159 		n = mach[i].nproc;
1160 		if(n>1 && j>=1+3+mach[i].lgproc){	/* first char of name + (n) */
1161 			j -= 3+mach[i].lgproc;
1162 			if(j <= 0)
1163 				j = 1;
1164 			snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].shortname, n);
1165 		}else
1166 			snprint(buf, sizeof buf, "%.*s", j, mach[i].shortname);
1167 		string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP, mediumfont, buf);
1168 	}
1169 
1170 	maxx = screen->r.max.x;
1171 
1172 	/* label right, if requested */
1173 	if(ylabels && dy>Nlab*(mediumfont->height+1)){
1174 		wid = labelwidth();
1175 		if(wid < (maxx-startx)-30){
1176 			/* else there's not enough room */
1177 			maxx -= 1+Lx+wid;
1178 			draw(screen, Rect(maxx, starty, maxx+1, screen->r.max.y), display->black, nil, ZP);
1179 			y = starty;
1180 			for(j=0; j<ngraph; j++, y+=dy){
1181 				/* choose value for rightmost graph */
1182 				g = &graph[ngraph*(nmach-1)+j];
1183 				labelstrs(g, labs, &nlab);
1184 				r = Rect(maxx+1, y, screen->r.max.x, y+dy-1);
1185 				if(j == ngraph-1)
1186 					r.max.y = screen->r.max.y;
1187 				draw(screen, r, cols[g->colindex][0], nil, paritypt(r.min.x));
1188 				for(k=0; k<nlab; k++){
1189 					ly = y + (dy*(nlab-k)/(nlab+1));
1190 					draw(screen, Rect(maxx+1, ly, maxx+1+Lx, ly+1), display->black, nil, ZP);
1191 					ly -= mediumfont->height/2;
1192 					string(screen, Pt(maxx+1+Lx, ly), display->black, ZP, mediumfont, labs[k]);
1193 				}
1194 			}
1195 		}
1196 	}
1197 
1198 	/* create graphs */
1199 	for(i=0; i<nmach; i++){
1200 		machr = Rect(startx+i*dx, starty, maxx, screen->r.max.y);
1201 		if(i < nmach-1)
1202 			machr.max.x = startx+(i+1)*dx - 1;
1203 		y = starty;
1204 		for(j=0; j<ngraph; j++, y+=dy){
1205 			g = &graph[i*ngraph+j];
1206 			/* allocate data */
1207 			ondata = g->ndata;
1208 			g->ndata = Dx(machr)+1;	/* may be too many if label will be drawn here; so what? */
1209 			g->data = erealloc(g->data, g->ndata*sizeof(g->data[0]));
1210 			if(g->ndata > ondata)
1211 				memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(g->data[0]));
1212 			/* set geometry */
1213 			g->r = machr;
1214 			g->r.min.y = y;
1215 			g->r.max.y = y+dy - 1;
1216 			if(j == ngraph-1)
1217 				g->r.max.y = screen->r.max.y;
1218 			draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
1219 			g->overflow = 0;
1220 			r = g->r;
1221 			r.max.y = r.min.y+mediumfont->height;
1222 			r.max.x = r.min.x+stringwidth(mediumfont, "999999999999");
1223 			freeimage(g->overtmp);
1224 			g->overtmp = nil;
1225 			if(r.max.x <= g->r.max.x)
1226 				g->overtmp = allocimage(display, r, screen->chan, 0, -1);
1227 			g->newvalue(g->mach, &v, &vmax, 0);
1228 			redraw(g, vmax);
1229 		}
1230 	}
1231 
1232 	flushimage(display, 1);
1233 }
1234 
1235 void
eresized(int new)1236 eresized(int new)
1237 {
1238 	lockdisplay(display);
1239 	if(new && getwindow(display, Refnone) < 0) {
1240 		fprint(2, "stats: can't reattach to window\n");
1241 		killall("reattach");
1242 	}
1243 	resize();
1244 	unlockdisplay(display);
1245 }
1246 
1247 void
mouseproc(void)1248 mouseproc(void)
1249 {
1250 	Mouse mouse;
1251 	int i;
1252 
1253 	for(;;){
1254 		mouse = emouse();
1255 		if(mouse.buttons == 4){
1256 			lockdisplay(display);
1257 			for(i=0; i<Nmenu2; i++)
1258 				if(present[i])
1259 					memmove(menu2str[i], "drop ", Opwid);
1260 				else
1261 					memmove(menu2str[i], "add  ", Opwid);
1262 			i = emenuhit(3, &mouse, &menu2);
1263 			if(i >= 0){
1264 				if(!present[i])
1265 					addgraph(i);
1266 				else if(ngraph > 1)
1267 					dropgraph(i);
1268 				resize();
1269 			}
1270 			unlockdisplay(display);
1271 		}
1272 	}
1273 }
1274 
1275 void
startproc(void (* f)(void),int index)1276 startproc(void (*f)(void), int index)
1277 {
1278 	int pid;
1279 
1280 	switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
1281 	case -1:
1282 		fprint(2, "stats: fork failed: %r\n");
1283 		killall("fork failed");
1284 	case 0:
1285 		f();
1286 		fprint(2, "stats: %s process exits\n", procnames[index]);
1287 		if(index >= 0)
1288 			killall("process died");
1289 		exits(nil);
1290 	}
1291 	if(index >= 0)
1292 		pids[index] = pid;
1293 }
1294 
1295 void
main(int argc,char * argv[])1296 main(int argc, char *argv[])
1297 {
1298 	int i, j;
1299 	double secs;
1300 	uvlong v, vmax, nargs;
1301 	char args[100];
1302 
1303 	nmach = 1;
1304 	mysysname = getenv("sysname");
1305 	if(mysysname == nil){
1306 		fprint(2, "stats: can't find $sysname: %r\n");
1307 		exits("sysname");
1308 	}
1309 	mysysname = estrdup(mysysname);
1310 	mycputype = getenv("cputype");
1311 
1312 	nargs = 0;
1313 	ARGBEGIN{
1314 	case 'T':
1315 		secs = atof(EARGF(usage()));
1316 		if(secs > 0)
1317 			sleeptime = 1000*secs;
1318 		break;
1319 	case 'S':
1320 		scale = atof(EARGF(usage()));
1321 		if(scale <= 0)
1322 			usage();
1323 		break;
1324 	case 'L':
1325 		logscale++;
1326 		break;
1327 	case 'Y':
1328 		ylabels++;
1329 		break;
1330 	case 'O':
1331 		oldsystem = 1;
1332 		break;
1333 	default:
1334 		if(nargs>=sizeof args || strchr(argchars, ARGC())==nil)
1335 			usage();
1336 		args[nargs++] = ARGC();
1337 	}ARGEND
1338 
1339 	if(argc == 0){
1340 		mach = emalloc(nmach*sizeof(Machine));
1341 		initmach(&mach[0], mysysname);
1342 		readmach(&mach[0], 1);
1343 	}else{
1344 		for(i=j=0; i<argc; i++){
1345 			if (addmachine(argv[i]))
1346 				readmach(&mach[j++], 1);
1347 		}
1348 		if (j == 0)
1349 			exits("connect");
1350 	}
1351 
1352 	for(i=0; i<nargs; i++)
1353 	switch(args[i]){
1354 	default:
1355 		fprint(2, "stats: internal error: unknown arg %c\n", args[i]);
1356 		usage();
1357 	case 'b':
1358 		addgraph(Mbattery);
1359 		break;
1360 	case 'c':
1361 		addgraph(Mcontext);
1362 		break;
1363 	case 'e':
1364 		addgraph(Mether);
1365 		break;
1366 	case 'E':
1367 		addgraph(Metherin);
1368 		addgraph(Metherout);
1369 		break;
1370 	case 'f':
1371 		addgraph(Mfault);
1372 		break;
1373 	case 'i':
1374 		addgraph(Mintr);
1375 		break;
1376 	case 'I':
1377 		addgraph(Mload);
1378 		addgraph(Midle);
1379 		addgraph(Minintr);
1380 		break;
1381 	case 'l':
1382 		addgraph(Mload);
1383 		break;
1384 	case 'm':
1385 		addgraph(Mmem);
1386 		break;
1387 	case 'n':
1388 		addgraph(Metherin);
1389 		addgraph(Metherout);
1390 		addgraph(Methererr);
1391 		break;
1392 	case 'p':
1393 		addgraph(Mtlbpurge);
1394 		break;
1395 	case 's':
1396 		addgraph(Msyscall);
1397 		break;
1398 	case 't':
1399 		addgraph(Mtlbmiss);
1400 		addgraph(Mtlbpurge);
1401 		break;
1402 	case '8':
1403 		addgraph(Msignal);
1404 		break;
1405 	case 'w':
1406 		addgraph(Mswap);
1407 		break;
1408 	case 'z':
1409 		addgraph(Mtemp);
1410 		break;
1411 	}
1412 
1413 	if(ngraph == 0)
1414 		addgraph(Mload);
1415 
1416 	for(i=0; i<nmach; i++)
1417 		for(j=0; j<ngraph; j++)
1418 			graph[i*ngraph+j].mach = &mach[i];
1419 
1420 	if(initdraw(nil, nil, "stats") < 0){
1421 		fprint(2, "stats: initdraw failed: %r\n");
1422 		exits("initdraw");
1423 	}
1424 	colinit();
1425 	einit(Emouse);
1426 	notify(nil);
1427 	startproc(mouseproc, Mouseproc);
1428 	pids[Mainproc] = getpid();
1429 	display->locking = 1;	/* tell library we're using the display lock */
1430 
1431 	resize();
1432 
1433 	unlockdisplay(display); /* display is still locked from initdraw() */
1434 	for(;;){
1435 		for(i=0; i<nmach; i++)
1436 			readmach(&mach[i], 0);
1437 		lockdisplay(display);
1438 		parity = 1-parity;
1439 		for(i=0; i<nmach*ngraph; i++){
1440 			graph[i].newvalue(graph[i].mach, &v, &vmax, 0);
1441 			graph[i].update(&graph[i], v, vmax);
1442 		}
1443 		flushimage(display, 1);
1444 		unlockdisplay(display);
1445 		sleep(sleeptime);
1446 	}
1447 }
1448