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