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