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