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