xref: /plan9/sys/src/cmd/ip/gping.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 #include <ip.h>
9 #include "icmp.h"
10 
11 #define	MAXNUM	8	/* maximum number of numbers on data line */
12 
13 typedef struct Graph	Graph;
14 typedef struct Machine	Machine;
15 typedef struct Req	Req;
16 
17 enum {
18 	Gmsglen	= 16,
19 };
20 
21 struct Graph
22 {
23 	int		colindex;
24 	Rectangle	r;
25 	long		*data;
26 	int		ndata;
27 	char		*label;
28 	void		(*newvalue)(Machine*, long*, long*, long*);
29 	void		(*update)(Graph*, long, long, long);
30 	Machine		*mach;
31 	int		overflow;
32 	Image		*overtmp;
33 	int		overtmplen;
34 	char		msg[Gmsglen];
35 	int		cursor;
36 	int		vmax;
37 };
38 
39 enum
40 {
41 	MSGLEN		= 64,
42 
43 	Rttmax		= 50,
44 };
45 
46 struct Req
47 {
48 	int	seq;	/* sequence number */
49 	vlong	time;	/* time sent */
50 	int	rtt;
51 	Req	*next;
52 };
53 
54 struct Machine
55 {
56 	Lock;
57 	char	*name;
58 	int	pingfd;
59 	int	nproc;
60 
61 	int	rttmsgs;
62 	ulong	rttsum;
63 	ulong	lastrtt;
64 
65 	int	lostmsgs;
66 	int	rcvdmsgs;
67 	ulong	lostavg;
68 	int	unreachable;
69 
70 	ushort	seq;
71 	Req	*first;
72 	Req	*last;
73 	Req	*rcvd;
74 
75 	char	buf[1024];
76 	char	*bufp;
77 	char	*ebufp;
78 };
79 
80 enum
81 {
82 	Ncolor		= 6,
83 	Ysqueeze	= 2,	/* vertical squeezing of label text */
84 	Labspace	= 2,	/* room around label */
85 	Dot		= 2,	/* height of dot */
86 	Opwid		= 5,	/* strlen("add  ") or strlen("drop ") */
87 	NPROC		= 128,
88 	NMACH		= 32,
89 };
90 
91 enum Menu2
92 {
93 	Mrtt,
94 	Mlost,
95 	Nmenu2,
96 };
97 
98 char	*menu2str[Nmenu2+1] = {
99 	"add  sec rtt",
100 	"add  % lost ",
101 	nil,
102 };
103 
104 
105 void	rttval(Machine*, long*, long*, long*);
106 void	lostval(Machine*, long*, long*, long*);
107 
108 Menu	menu2 = {menu2str, nil};
109 int		present[Nmenu2];
110 void		(*newvaluefn[Nmenu2])(Machine*, long*, long*, long*) = {
111 	rttval,
112 	lostval,
113 };
114 
115 Image		*cols[Ncolor][3];
116 Graph		*graph;
117 Machine		mach[NMACH];
118 Font		*mediumfont;
119 int		pids[NPROC];
120 int		npid;
121 int 		parity;	/* toggled to avoid patterns in textured background */
122 int		nmach;
123 int		ngraph;	/* totaly number is ngraph*nmach */
124 long		starttime;
125 int		pinginterval;
126 
127 void	dropgraph(int);
128 void	addgraph(int);
129 void	startproc(void (*)(void*), void*);
130 void	resize(void);
131 long	rttscale(long);
132 int	which2index(int);
133 int	index2which(int);
134 
135 void
136 killall(char *s)
137 {
138 	int i, pid;
139 
140 	pid = getpid();
141 	for(i=0; i<NPROC; i++)
142 		if(pids[i] && pids[i]!=pid)
143 			postnote(PNPROC, pids[i], "kill");
144 	exits(s);
145 }
146 
147 void*
148 emalloc(ulong sz)
149 {
150 	void *v;
151 	v = malloc(sz);
152 	if(v == nil) {
153 		fprint(2, "%s: out of memory allocating %ld: %r\n", argv0, sz);
154 		killall("mem");
155 	}
156 	memset(v, 0, sz);
157 	return v;
158 }
159 
160 void*
161 erealloc(void *v, ulong sz)
162 {
163 	v = realloc(v, sz);
164 	if(v == nil) {
165 		fprint(2, "%s: out of memory reallocating %ld: %r\n", argv0, sz);
166 		killall("mem");
167 	}
168 	return v;
169 }
170 
171 char*
172 estrdup(char *s)
173 {
174 	char *t;
175 	if((t = strdup(s)) == nil) {
176 		fprint(2, "%s: out of memory in strdup(%.10s): %r\n", argv0, s);
177 		killall("mem");
178 	}
179 	return t;
180 }
181 
182 void
183 mkcol(int i, int c0, int c1, int c2)
184 {
185 	cols[i][0] = allocimagemix(display, c0, DWhite);
186 	cols[i][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c1);
187 	cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2);
188 }
189 
190 void
191 colinit(void)
192 {
193 	mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
194 	if(mediumfont == nil)
195 		mediumfont = font;
196 
197 	/* Peach */
198 	mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
199 	/* Aqua */
200 	mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
201 	/* Yellow */
202 	mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
203 	/* Green */
204 	mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
205 	/* Blue */
206 	mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
207 	/* Grey */
208 	cols[5][0] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
209 	cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
210 	cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF);
211 }
212 
213 int
214 loadbuf(Machine *m, int *fd)
215 {
216 	int n;
217 
218 
219 	if(*fd < 0)
220 		return 0;
221 	seek(*fd, 0, 0);
222 	n = read(*fd, m->buf, sizeof m->buf);
223 	if(n <= 0){
224 		close(*fd);
225 		*fd = -1;
226 		return 0;
227 	}
228 	m->bufp = m->buf;
229 	m->ebufp = m->buf+n;
230 	return 1;
231 }
232 
233 void
234 label(Point p, int dy, char *text)
235 {
236 	char *s;
237 	Rune r[2];
238 	int w, maxw, maxy;
239 
240 	p.x += Labspace;
241 	maxy = p.y+dy;
242 	maxw = 0;
243 	r[1] = '\0';
244 	for(s=text; *s; ){
245 		if(p.y+mediumfont->height-Ysqueeze > maxy)
246 			break;
247 		w = chartorune(r, s);
248 		s += w;
249 		w = runestringwidth(mediumfont, r);
250 		if(w > maxw)
251 			maxw = w;
252 		runestring(screen, p, display->black, ZP, mediumfont, r);
253 		p.y += mediumfont->height-Ysqueeze;
254 	}
255 }
256 
257 void
258 hashmark(Point p, int dy, long v, long vmax, char *label)
259 {
260 	int y;
261 	int x;
262 
263 	x = p.x + Labspace;
264 	y = p.y + (dy*(vmax-v))/vmax;
265 	draw(screen, Rect(p.x, y-1, p.x+Labspace, y+1), display->black, nil, ZP);
266 	if(dy > 5*mediumfont->height)
267 		string(screen, Pt(x, y-mediumfont->height/2),
268 			display->black, ZP, mediumfont, label);
269 }
270 
271 void
272 hashmarks(Point p, int dy, int which)
273 {
274 	switch(index2which(which)){
275 	case Mrtt:
276 		hashmark(p, dy, rttscale(1000000), Rttmax, "1.");
277 		hashmark(p, dy, rttscale(100000), Rttmax, "0.1");
278 		hashmark(p, dy, rttscale(10000), Rttmax, "0.01");
279 		hashmark(p, dy, rttscale(1000), Rttmax, "0.001");
280 		break;
281 	case Mlost:
282 		hashmark(p, dy, 75, 100, " 75%");
283 		hashmark(p, dy, 50, 100, " 50%");
284 		hashmark(p, dy, 25, 100, " 25%");
285 		break;
286 	}
287 }
288 
289 Point
290 paritypt(int x)
291 {
292 	return Pt(x+parity, 0);
293 }
294 
295 Point
296 datapoint(Graph *g, int x, long v, long vmax)
297 {
298 	Point p;
299 
300 	p.x = x;
301 	p.y = g->r.max.y - Dy(g->r)*v/vmax - Dot;
302 	if(p.y < g->r.min.y)
303 		p.y = g->r.min.y;
304 	if(p.y > g->r.max.y-Dot)
305 		p.y = g->r.max.y-Dot;
306 	return p;
307 }
308 
309 void
310 drawdatum(Graph *g, int x, long prev, long v, long vmax)
311 {
312 	int c;
313 	Point p, q;
314 
315 	c = g->colindex;
316 	p = datapoint(g, x, v, vmax);
317 	q = datapoint(g, x, prev, vmax);
318 	if(p.y < q.y){
319 		draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
320 		draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
321 		draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
322 	}else{
323 		draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
324 		draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
325 		draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
326 	}
327 	g->vmax = vmax;
328 }
329 
330 void
331 drawmark(Graph *g, int x)
332 {
333 	int c;
334 
335 	c = (g->colindex+1)&Ncolor;
336 	draw(screen, Rect(x, g->r.min.y, x+1, g->r.max.y), cols[c][2], nil, ZP);
337 }
338 
339 void
340 redraw(Graph *g, int vmax)
341 {
342 	int i, c;
343 
344 	c = g->colindex;
345 	draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
346 	for(i=1; i<Dx(g->r); i++)
347 		drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
348 	drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
349 }
350 
351 void
352 clearmsg(Graph *g)
353 {
354 	if(g->overtmp != nil)
355 		draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
356 	g->overflow = 0;
357 }
358 
359 void
360 drawmsg(Graph *g, char *msg)
361 {
362 	if(g->overtmp == nil)
363 		return;
364 
365 	/* save previous contents of screen */
366 	draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);
367 
368 	/* draw message */
369 	if(strlen(msg) > g->overtmplen)
370 		msg[g->overtmplen] = 0;
371 	string(screen, g->overtmp->r.min, display->black, ZP, mediumfont, msg);
372 }
373 
374 void
375 clearcursor(Graph *g)
376 {
377 	int x;
378 	long prev;
379 
380 	if(g->overtmp == nil)
381 		return;
382 
383 	if(g->cursor > 0 && g->cursor < g->ndata){
384 		x = g->r.max.x - g->cursor;
385 		prev = 0;
386 		if(g->cursor > 0)
387 			prev = g->data[g->cursor-1];
388 		drawdatum(g, x, prev, g->data[g->cursor], g->vmax);
389 		g->cursor = -1;
390 	}
391 }
392 
393 void
394 drawcursor(Graph *g, int x)
395 {
396 	if(g->overtmp == nil)
397 		return;
398 
399 	draw(screen, Rect(x, g->r.min.y, x+1, g->r.max.y), cols[g->colindex][2], nil, ZP);
400 }
401 
402 void
403 update1(Graph *g, long v, long vmax, long mark)
404 {
405 	char buf[Gmsglen];
406 
407 	/* put back screen value sans message */
408 	if(g->overflow || *g->msg){
409 		clearmsg(g);
410 		g->overflow = 0;
411 	}
412 
413 	draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
414 	drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
415 	if(mark)
416 		drawmark(g, g->r.max.x-1);
417 	memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
418 	g->data[0] = v;
419 	if(v>vmax){
420 		g->overflow = 1;
421 		sprint(buf, "%ld", v);
422 		drawmsg(g, buf);
423 	} else if(*g->msg)
424 		drawmsg(g, g->msg);
425 
426 	if(g->cursor >= 0){
427 		g->cursor++;
428 		if(g->cursor >= g->ndata){
429 			g->cursor = -1;
430 			if(*g->msg){
431 				clearmsg(g);
432 				*g->msg = 0;
433 			}
434 		}
435 	}
436 
437 }
438 
439 void
440 pinglost(Machine *m, Req*)
441 {
442 	m->lostmsgs++;
443 }
444 
445 void
446 pingreply(Machine *m, Req *r)
447 {
448 	ulong x;
449 
450 	x = r->time/1000LL;
451 	m->rttsum += x;
452 	m->rcvdmsgs++;
453 	m->rttmsgs++;
454 }
455 
456 
457 void
458 pingclean(Machine *m, ushort seq, vlong now, int)
459 {
460 	Req **l, *r;
461 	vlong x, y;
462 
463 	y = 10LL*1000000000LL;
464 	for(l = &m->first; *l; ){
465 		r = *l;
466 		x = now - r->time;
467 		if(x > y || r->seq == seq){
468 			*l = r->next;
469 			r->time = x;
470 			if(r->seq != seq)
471 				pinglost(m, r);
472 			else
473 				pingreply(m, r);
474 			free(r);
475 		} else
476 			l = &(r->next);
477 	}
478 }
479 
480 void
481 pingsend(Machine *m)
482 {
483 	char buf[128];
484 	Icmp *ip;
485 	int i;
486 	Req *r;
487 	char err[ERRMAX];
488 
489 	ip = (Icmp*)buf;
490 
491 	r = malloc(sizeof *r);
492 	if(r == nil)
493 		return;
494 
495 	for(i = 32; i < 64; i++)
496 		buf[i] = i;
497 	ip->type = EchoRequest;
498 	ip->code = 0;
499 	ip->seq[0] = m->seq;
500 	ip->seq[1] = m->seq>>8;
501 	r->seq = m->seq;
502 	r->next = nil;
503 	lock(m);
504 	pingclean(m, -1, nsec(), 0);
505 	if(m->first == nil)
506 		m->first = r;
507 	else
508 		m->last->next = r;
509 	m->last = r;
510 	r->time = nsec();
511 	unlock(m);
512 	if(write(m->pingfd, ip, MSGLEN) < MSGLEN){
513 		errstr(err, sizeof err);
514 		if(strstr(err, "unreach")||strstr(err, "exceed"))
515 			m->unreachable++;
516 	}
517 	m->seq++;
518 }
519 
520 void
521 pingrcv(void *arg)
522 {
523 	uchar buf[512];
524 	Icmp *ip;
525 	ushort x;
526 	int i, n, fd;
527 	vlong now;
528 	Machine *m = arg;
529 
530 	ip = (Icmp*)buf;
531 	fd = dup(m->pingfd, -1);
532 	for(;;){
533 		n = read(fd, buf, sizeof(buf));
534 		now = nsec();
535 		if(n <= 0)
536 			continue;
537 		if(n < MSGLEN){
538 			print("bad len %d/%d\n", n, MSGLEN);
539 			continue;
540 		}
541 		for(i = 32; i < MSGLEN; i++)
542 			if(buf[i] != (i&0xff))
543 				continue;
544 		x = (ip->seq[1]<<8)|ip->seq[0];
545 		if(ip->type != EchoReply || ip->code != 0)
546 			continue;
547 		lock(m);
548 		pingclean(m, x, now, ip->ttl);
549 		unlock(m);
550 	}
551 }
552 
553 void
554 initmach(Machine *m, char *name)
555 {
556 	char *p;
557 
558 	srand(time(0));
559 	p = strchr(name, '!');
560 	if(p){
561 		p++;
562 		m->name = estrdup(p+1);
563 	}else
564 		p = name;
565 
566 	m->name = estrdup(p);
567 	m->nproc = 1;
568 	m->pingfd = dial(netmkaddr(m->name, "icmp", "1"), 0, 0, 0);
569 	if(m->pingfd < 0)
570 		sysfatal("dialing %s: %r", m->name);
571 	startproc(pingrcv, m);
572 }
573 
574 long
575 rttscale(long x)
576 {
577 	if(x == 0)
578 		return 0;
579 	x = 10.0*log10(x) - 20.0;
580 	if(x < 0)
581 		x = 0;
582 	return x;
583 }
584 
585 double
586 rttunscale(long x)
587 {
588 	double dx;
589 
590 	x += 20;
591 	dx = x;
592 	return pow(10.0, dx/10.0);
593 }
594 
595 void
596 rttval(Machine *m, long *v, long *vmax, long *mark)
597 {
598 	ulong x;
599 
600 	if(m->rttmsgs == 0){
601 		x = m->lastrtt;
602 	} else {
603 		x = m->rttsum/m->rttmsgs;
604 		m->rttsum = m->rttmsgs = 0;
605 		m->lastrtt = x;
606 	}
607 
608 	*v = rttscale(x);
609 	*vmax = Rttmax;
610 	*mark = 0;
611 }
612 
613 void
614 lostval(Machine *m, long *v, long *vmax, long *mark)
615 {
616 	ulong x;
617 
618 	if(m->rcvdmsgs+m->lostmsgs > 0)
619 		x = (m->lostavg>>1) + (((m->lostmsgs*100)/(m->lostmsgs + m->rcvdmsgs))>>1);
620 	else
621 		x = m->lostavg;
622 	m->lostavg = x;
623 	m->lostmsgs = m->rcvdmsgs = 0;
624 
625 	if(m->unreachable){
626 		m->unreachable = 0;
627 		*mark = 100;
628 	} else
629 		*mark = 0;
630 
631 	*v = x;
632 	*vmax = 100;
633 }
634 
635 jmp_buf catchalarm;
636 
637 void
638 alarmed(void *a, char *s)
639 {
640 	if(strcmp(s, "alarm") == 0)
641 		notejmp(a, catchalarm, 1);
642 	noted(NDFLT);
643 }
644 
645 void
646 usage(void)
647 {
648 	fprint(2, "usage: %s machine [machine...]\n", argv0);
649 	exits("usage");
650 }
651 
652 void
653 addgraph(int n)
654 {
655 	Graph *g, *ograph;
656 	int i, j;
657 	static int nadd;
658 
659 	if(n > nelem(menu2str))
660 		abort();
661 	/* avoid two adjacent graphs of same color */
662 	if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
663 		nadd++;
664 	ograph = graph;
665 	graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
666 	for(i=0; i<nmach; i++)
667 		for(j=0; j<ngraph; j++)
668 			graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
669 	free(ograph);
670 	ngraph++;
671 	for(i=0; i<nmach; i++){
672 		g = &graph[i*ngraph+(ngraph-1)];
673 		memset(g, 0, sizeof(Graph));
674 		g->label = menu2str[n]+Opwid;
675 		g->newvalue = newvaluefn[n];
676 		g->update = update1;	/* no other update functions yet */
677 		g->mach = &mach[i];
678 		g->colindex = nadd%Ncolor;
679 	}
680 	present[n] = 1;
681 	nadd++;
682 }
683 
684 int
685 which2index(int which)
686 {
687 	int i, n;
688 
689 	n = -1;
690 	for(i=0; i<ngraph; i++){
691 		if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
692 			n = i;
693 			break;
694 		}
695 	}
696 	if(n < 0){
697 		fprint(2, "%s: internal error can't drop graph\n", argv0);
698 		killall("error");
699 	}
700 	return n;
701 }
702 
703 int
704 index2which(int index)
705 {
706 	int i, n;
707 
708 	n = -1;
709 	for(i=0; i<Nmenu2; i++){
710 		if(strcmp(menu2str[i]+Opwid, graph[index].label) == 0){
711 			n = i;
712 			break;
713 		}
714 	}
715 	if(n < 0){
716 		fprint(2, "%s: internal error can't identify graph\n", argv0);
717 		killall("error");
718 	}
719 	return n;
720 }
721 
722 void
723 dropgraph(int which)
724 {
725 	Graph *ograph;
726 	int i, j, n;
727 
728 	if(which > nelem(menu2str))
729 		abort();
730 	/* convert n to index in graph table */
731 	n = which2index(which);
732 	ograph = graph;
733 	graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
734 	for(i=0; i<nmach; i++){
735 		for(j=0; j<n; j++)
736 			graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
737 		free(ograph[i*ngraph+j].data);
738 		freeimage(ograph[i*ngraph+j].overtmp);
739 		for(j++; j<ngraph; j++)
740 			graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
741 	}
742 	free(ograph);
743 	ngraph--;
744 	present[which] = 0;
745 }
746 
747 void
748 addmachine(char *name)
749 {
750 	if(ngraph > 0){
751 		fprint(2, "%s: internal error: ngraph>0 in addmachine()\n", argv0);
752 		usage();
753 	}
754 	if(nmach == NMACH)
755 		sysfatal("too many machines");
756 	initmach(&mach[nmach++], name);
757 }
758 
759 
760 void
761 resize(void)
762 {
763 	int i, j, n, startx, starty, x, y, dx, dy, hashdx, ondata;
764 	Graph *g;
765 	Rectangle machr, r;
766 	long v, vmax, mark;
767 	char buf[128];
768 
769 	draw(screen, screen->r, display->white, nil, ZP);
770 
771 	/* label left edge */
772 	x = screen->r.min.x;
773 	y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
774 	dy = (screen->r.max.y - y)/ngraph;
775 	dx = Labspace+stringwidth(mediumfont, "0")+Labspace;
776 	startx = x+dx+1;
777 	starty = y;
778 	for(i=0; i<ngraph; i++,y+=dy){
779 		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
780 		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
781 		label(Pt(x, y), dy, graph[i].label);
782 		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
783 	}
784 
785 	/* label right edge */
786 	dx = Labspace+stringwidth(mediumfont, "0.001")+Labspace;
787 	hashdx = dx;
788 	x = screen->r.max.x - dx;
789 	y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
790 	for(i=0; i<ngraph; i++,y+=dy){
791 		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
792 		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
793 		hashmarks(Pt(x, y), dy, i);
794 		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
795 	}
796 
797 	/* label top edge */
798 	dx = (screen->r.max.x - dx - startx)/nmach;
799 	for(x=startx, i=0; i<nmach; i++,x+=dx){
800 		draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
801 		j = dx/stringwidth(mediumfont, "0");
802 		n = mach[i].nproc;
803 		if(n>1 && j>=1+3+(n>10)+(n>100)){	/* first char of name + (n) */
804 			j -= 3+(n>10)+(n>100);
805 			if(j <= 0)
806 				j = 1;
807 			snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].name, n);
808 		}else
809 			snprint(buf, sizeof buf, "%.*s", j, mach[i].name);
810 		string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP,
811 			mediumfont, buf);
812 	}
813 	/* draw last vertical line */
814 	draw(screen,
815 		Rect(screen->r.max.x-hashdx-1, starty-1, screen->r.max.x-hashdx, screen->r.max.y),
816 		display->black, nil, ZP);
817 
818 	/* create graphs */
819 	for(i=0; i<nmach; i++){
820 		machr = Rect(startx+i*dx, starty, screen->r.max.x, screen->r.max.y);
821 		if(i < nmach-1)
822 			machr.max.x = startx+(i+1)*dx - 1;
823 		else
824 			machr.max.x = screen->r.max.x - hashdx - 1;
825 		y = starty;
826 		for(j=0; j<ngraph; j++, y+=dy){
827 			g = &graph[i*ngraph+j];
828 			/* allocate data */
829 			ondata = g->ndata;
830 			g->ndata = Dx(machr)+1;	/* may be too many if label will be drawn here; so what? */
831 			g->data = erealloc(g->data, g->ndata*sizeof(long));
832 			if(g->ndata > ondata)
833 				memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(long));
834 			/* set geometry */
835 			g->r = machr;
836 			g->r.min.y = y;
837 			g->r.max.y = y+dy - 1;
838 			if(j == ngraph-1)
839 				g->r.max.y = screen->r.max.y;
840 			draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
841 			g->overflow = 0;
842 			*g->msg = 0;
843 			freeimage(g->overtmp);
844 			g->overtmp = nil;
845 			g->overtmplen = 0;
846 			r = g->r;
847 			r.max.y = r.min.y+mediumfont->height;
848 			n = (g->r.max.x - r.min.x)/stringwidth(mediumfont, "9");
849 			if(n > 4){
850 				if(n > Gmsglen)
851 					n = Gmsglen;
852 				r.max.x = r.min.x+stringwidth(mediumfont, "9")*n;
853 				g->overtmplen = n;
854 				g->overtmp = allocimage(display, r, screen->chan, 0, -1);
855 			}
856 			g->newvalue(g->mach, &v, &vmax, &mark);
857 			redraw(g, vmax);
858 		}
859 	}
860 
861 	flushimage(display, 1);
862 }
863 
864 void
865 eresized(int new)
866 {
867 	lockdisplay(display);
868 	if(new && getwindow(display, Refnone) < 0) {
869 		fprint(2, "%s: can't reattach to window\n", argv0);
870 		killall("reattach");
871 	}
872 	resize();
873 	unlockdisplay(display);
874 }
875 
876 void
877 dobutton2(Mouse *m)
878 {
879 	int i;
880 
881 	for(i=0; i<Nmenu2; i++)
882 		if(present[i])
883 			memmove(menu2str[i], "drop ", Opwid);
884 		else
885 			memmove(menu2str[i], "add  ", Opwid);
886 	i = emenuhit(3, m, &menu2);
887 	if(i >= 0){
888 		if(!present[i])
889 			addgraph(i);
890 		else if(ngraph > 1)
891 			dropgraph(i);
892 		resize();
893 	}
894 }
895 
896 void
897 dobutton1(Mouse *m)
898 {
899 	int i, n, dx, dt;
900 	Graph *g;
901 	char *e;
902 	double f;
903 
904 	for(i = 0; i < ngraph*nmach; i++){
905 		if(ptinrect(m->xy, graph[i].r))
906 			break;
907 	}
908 	if(i == ngraph*nmach)
909 		return;
910 
911 	g = &graph[i];
912 	if(g->overtmp == nil)
913 		return;
914 
915 	/* clear any previous message and cursor */
916 	if(g->overflow || *g->msg){
917 		clearmsg(g);
918 		*g->msg = 0;
919 		clearcursor(g);
920 	}
921 
922 	dx = g->r.max.x - m->xy.x;
923 	g->cursor = dx;
924 	dt = dx*pinginterval;
925 	e = &g->msg[sizeof(g->msg)];
926 	seprint(g->msg, e, "%s", ctime(starttime-dt/1000)+11);
927 	g->msg[8] = 0;
928 	n = 8;
929 
930 	switch(index2which(i)){
931 	case Mrtt:
932 		f = rttunscale(g->data[dx]);
933 		seprint(g->msg+n, e, " %3.3g", f/1000000);
934 		break;
935 	case Mlost:
936 		seprint(g->msg+n, e, " %ld%%", g->data[dx]);
937 		break;
938 	}
939 
940 	drawmsg(g, g->msg);
941 	drawcursor(g, m->xy.x);
942 }
943 
944 void
945 mouseproc(void*)
946 {
947 	Mouse mouse;
948 
949 	for(;;){
950 		mouse = emouse();
951 		if(mouse.buttons == 4){
952 			lockdisplay(display);
953 			dobutton2(&mouse);
954 			unlockdisplay(display);
955 		} else if(mouse.buttons == 1){
956 			lockdisplay(display);
957 			dobutton1(&mouse);
958 			unlockdisplay(display);
959 		}
960 	}
961 }
962 
963 void
964 startproc(void (*f)(void*), void *arg)
965 {
966 	int pid;
967 
968 	switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
969 	case -1:
970 		fprint(2, "%s: fork failed: %r\n", argv0);
971 		killall("fork failed");
972 	case 0:
973 		f(arg);
974 		killall("process died");
975 		exits(nil);
976 	}
977 	pids[npid++] = pid;
978 }
979 
980 void
981 main(int argc, char *argv[])
982 {
983 	int i, j;
984 	long v, vmax, mark;
985 	char flags[10], *f, *p;
986 
987 	fmtinstall('V', eipfmt);
988 
989 	f = flags;
990 	pinginterval = 5000;		/* 5 seconds */
991 	ARGBEGIN{
992 	case 'i':
993 		p = ARGF();
994 		if(p == nil)
995 			usage();
996 		pinginterval = atoi(p);
997 		break;
998 	default:
999 		if(f - flags >= sizeof(flags)-1)
1000 			usage();
1001 		*f++ = ARGC();
1002 		break;
1003 	}ARGEND
1004 	*f = 0;
1005 
1006 	for(i=0; i<argc; i++)
1007 		addmachine(argv[i]);
1008 
1009 	for(f = flags; *f; f++)
1010 		switch(*f){
1011 		case 'l':
1012 			addgraph(Mlost);
1013 			break;
1014 		case 'r':
1015 			addgraph(Mrtt);
1016 			break;
1017 		}
1018 
1019 	if(nmach == 0)
1020 		usage();
1021 
1022 	if(ngraph == 0)
1023 		addgraph(Mrtt);
1024 
1025 	for(i=0; i<nmach; i++)
1026 		for(j=0; j<ngraph; j++)
1027 			graph[i*ngraph+j].mach = &mach[i];
1028 
1029 	if(initdraw(nil, nil, argv0) < 0){
1030 		fprint(2, "%s: initdraw failed: %r\n", argv0);
1031 		exits("initdraw");
1032 	}
1033 	colinit();
1034 	einit(Emouse);
1035 	notify(nil);
1036 	startproc(mouseproc, 0);
1037 	display->locking = 1;	/* tell library we're using the display lock */
1038 
1039 	resize();
1040 
1041 	starttime = time(0);
1042 
1043 	unlockdisplay(display); /* display is still locked from initdraw() */
1044 	for(j = 0; ; j++){
1045 		lockdisplay(display);
1046 		if(j == nmach){
1047 			parity = 1-parity;
1048 			j = 0;
1049 			for(i=0; i<nmach*ngraph; i++){
1050 				graph[i].newvalue(graph[i].mach, &v, &vmax, &mark);
1051 				graph[i].update(&graph[i], v, vmax, mark);
1052 			}
1053 			starttime = time(0);
1054 		}
1055 		flushimage(display, 1);
1056 		unlockdisplay(display);
1057 		pingsend(&mach[j%nmach]);
1058 		sleep(pinginterval/nmach);
1059 	}
1060 }
1061