xref: /plan9/sys/src/cmd/ip/gping.c (revision 94aa1c4c0955b2b4e990c9f4679be8e9f67a469b)
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
killall(char * s)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*
emalloc(ulong sz)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*
erealloc(void * v,ulong sz)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*
estrdup(char * s)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
mkcol(int i,int c0,int c1,int c2)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
colinit(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
loadbuf(Machine * m,int * fd)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
label(Point p,int dy,char * text)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
hashmark(Point p,int dy,long v,long vmax,char * label)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
hashmarks(Point p,int dy,int which)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
paritypt(int x)290 paritypt(int x)
291 {
292 	return Pt(x+parity, 0);
293 }
294 
295 Point
datapoint(Graph * g,int x,long v,long vmax)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
drawdatum(Graph * g,int x,long prev,long v,long vmax)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
drawmark(Graph * g,int x)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
redraw(Graph * g,int vmax)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
clearmsg(Graph * g)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
drawmsg(Graph * g,char * msg)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
clearcursor(Graph * g)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
drawcursor(Graph * g,int x)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
update1(Graph * g,long v,long vmax,long mark)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
pinglost(Machine * m,Req *)440 pinglost(Machine *m, Req*)
441 {
442 	m->lostmsgs++;
443 }
444 
445 void
pingreply(Machine * m,Req * r)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
pingclean(Machine * m,ushort seq,vlong now,int)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 /* IPv4 only */
481 void
pingsend(Machine * m)482 pingsend(Machine *m)
483 {
484 	int i;
485 	char buf[128], err[ERRMAX];
486 	Icmphdr *ip;
487 	Req *r;
488 
489 	ip = (Icmphdr *)(buf + IPV4HDR_LEN);
490 	memset(buf, 0, sizeof buf);
491 	r = malloc(sizeof *r);
492 	if(r == nil)
493 		return;
494 
495 	for(i = 32; i < MSGLEN; 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, buf, 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 /* IPv4 only */
521 void
pingrcv(void * arg)522 pingrcv(void *arg)
523 {
524 	int i, n, fd;
525 	uchar buf[512];
526 	ushort x;
527 	vlong now;
528 	Icmphdr *ip;
529 	Ip4hdr *ip4;
530 	Machine *m = arg;
531 
532 	ip4 = (Ip4hdr *)buf;
533 	ip = (Icmphdr *)(buf + IPV4HDR_LEN);
534 	fd = dup(m->pingfd, -1);
535 	for(;;){
536 		n = read(fd, buf, sizeof(buf));
537 		now = nsec();
538 		if(n <= 0)
539 			continue;
540 		if(n < MSGLEN){
541 			print("bad len %d/%d\n", n, MSGLEN);
542 			continue;
543 		}
544 		for(i = 32; i < MSGLEN; i++)
545 			if(buf[i] != (i&0xff))
546 				continue;
547 		x = (ip->seq[1]<<8) | ip->seq[0];
548 		if(ip->type != EchoReply || ip->code != 0)
549 			continue;
550 		lock(m);
551 		pingclean(m, x, now, ip4->ttl);
552 		unlock(m);
553 	}
554 }
555 
556 void
initmach(Machine * m,char * name)557 initmach(Machine *m, char *name)
558 {
559 	char *p;
560 
561 	srand(time(0));
562 	p = strchr(name, '!');
563 	if(p){
564 		p++;
565 		m->name = estrdup(p+1);
566 	}else
567 		p = name;
568 
569 	m->name = estrdup(p);
570 	m->nproc = 1;
571 	m->pingfd = dial(netmkaddr(m->name, "icmp", "1"), 0, 0, 0);
572 	if(m->pingfd < 0)
573 		sysfatal("dialing %s: %r", m->name);
574 	startproc(pingrcv, m);
575 }
576 
577 long
rttscale(long x)578 rttscale(long x)
579 {
580 	if(x == 0)
581 		return 0;
582 	x = 10.0*log10(x) - 20.0;
583 	if(x < 0)
584 		x = 0;
585 	return x;
586 }
587 
588 double
rttunscale(long x)589 rttunscale(long x)
590 {
591 	double dx;
592 
593 	x += 20;
594 	dx = x;
595 	return pow(10.0, dx/10.0);
596 }
597 
598 void
rttval(Machine * m,long * v,long * vmax,long * mark)599 rttval(Machine *m, long *v, long *vmax, long *mark)
600 {
601 	ulong x;
602 
603 	if(m->rttmsgs == 0){
604 		x = m->lastrtt;
605 	} else {
606 		x = m->rttsum/m->rttmsgs;
607 		m->rttsum = m->rttmsgs = 0;
608 		m->lastrtt = x;
609 	}
610 
611 	*v = rttscale(x);
612 	*vmax = Rttmax;
613 	*mark = 0;
614 }
615 
616 void
lostval(Machine * m,long * v,long * vmax,long * mark)617 lostval(Machine *m, long *v, long *vmax, long *mark)
618 {
619 	ulong x;
620 
621 	if(m->rcvdmsgs+m->lostmsgs > 0)
622 		x = (m->lostavg>>1) + (((m->lostmsgs*100)/(m->lostmsgs + m->rcvdmsgs))>>1);
623 	else
624 		x = m->lostavg;
625 	m->lostavg = x;
626 	m->lostmsgs = m->rcvdmsgs = 0;
627 
628 	if(m->unreachable){
629 		m->unreachable = 0;
630 		*mark = 100;
631 	} else
632 		*mark = 0;
633 
634 	*v = x;
635 	*vmax = 100;
636 }
637 
638 jmp_buf catchalarm;
639 
640 void
alarmed(void * a,char * s)641 alarmed(void *a, char *s)
642 {
643 	if(strcmp(s, "alarm") == 0)
644 		notejmp(a, catchalarm, 1);
645 	noted(NDFLT);
646 }
647 
648 void
usage(void)649 usage(void)
650 {
651 	fprint(2, "usage: %s machine [machine...]\n", argv0);
652 	exits("usage");
653 }
654 
655 void
addgraph(int n)656 addgraph(int n)
657 {
658 	Graph *g, *ograph;
659 	int i, j;
660 	static int nadd;
661 
662 	if(n > nelem(menu2str))
663 		abort();
664 	/* avoid two adjacent graphs of same color */
665 	if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
666 		nadd++;
667 	ograph = graph;
668 	graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
669 	for(i=0; i<nmach; i++)
670 		for(j=0; j<ngraph; j++)
671 			graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
672 	free(ograph);
673 	ngraph++;
674 	for(i=0; i<nmach; i++){
675 		g = &graph[i*ngraph+(ngraph-1)];
676 		memset(g, 0, sizeof(Graph));
677 		g->label = menu2str[n]+Opwid;
678 		g->newvalue = newvaluefn[n];
679 		g->update = update1;	/* no other update functions yet */
680 		g->mach = &mach[i];
681 		g->colindex = nadd%Ncolor;
682 	}
683 	present[n] = 1;
684 	nadd++;
685 }
686 
687 int
which2index(int which)688 which2index(int which)
689 {
690 	int i, n;
691 
692 	n = -1;
693 	for(i=0; i<ngraph; i++){
694 		if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
695 			n = i;
696 			break;
697 		}
698 	}
699 	if(n < 0){
700 		fprint(2, "%s: internal error can't drop graph\n", argv0);
701 		killall("error");
702 	}
703 	return n;
704 }
705 
706 int
index2which(int index)707 index2which(int index)
708 {
709 	int i, n;
710 
711 	n = -1;
712 	for(i=0; i<Nmenu2; i++){
713 		if(strcmp(menu2str[i]+Opwid, graph[index].label) == 0){
714 			n = i;
715 			break;
716 		}
717 	}
718 	if(n < 0){
719 		fprint(2, "%s: internal error can't identify graph\n", argv0);
720 		killall("error");
721 	}
722 	return n;
723 }
724 
725 void
dropgraph(int which)726 dropgraph(int which)
727 {
728 	Graph *ograph;
729 	int i, j, n;
730 
731 	if(which > nelem(menu2str))
732 		abort();
733 	/* convert n to index in graph table */
734 	n = which2index(which);
735 	ograph = graph;
736 	graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
737 	for(i=0; i<nmach; i++){
738 		for(j=0; j<n; j++)
739 			graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
740 		free(ograph[i*ngraph+j].data);
741 		freeimage(ograph[i*ngraph+j].overtmp);
742 		for(j++; j<ngraph; j++)
743 			graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
744 	}
745 	free(ograph);
746 	ngraph--;
747 	present[which] = 0;
748 }
749 
750 void
addmachine(char * name)751 addmachine(char *name)
752 {
753 	if(ngraph > 0){
754 		fprint(2, "%s: internal error: ngraph>0 in addmachine()\n", argv0);
755 		usage();
756 	}
757 	if(nmach == NMACH)
758 		sysfatal("too many machines");
759 	initmach(&mach[nmach++], name);
760 }
761 
762 
763 void
resize(void)764 resize(void)
765 {
766 	int i, j, n, startx, starty, x, y, dx, dy, hashdx, ondata;
767 	Graph *g;
768 	Rectangle machr, r;
769 	long v, vmax, mark;
770 	char buf[128];
771 
772 	draw(screen, screen->r, display->white, nil, ZP);
773 
774 	/* label left edge */
775 	x = screen->r.min.x;
776 	y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
777 	dy = (screen->r.max.y - y)/ngraph;
778 	dx = Labspace+stringwidth(mediumfont, "0")+Labspace;
779 	startx = x+dx+1;
780 	starty = y;
781 	for(i=0; i<ngraph; i++,y+=dy){
782 		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
783 		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
784 		label(Pt(x, y), dy, graph[i].label);
785 		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
786 	}
787 
788 	/* label right edge */
789 	dx = Labspace+stringwidth(mediumfont, "0.001")+Labspace;
790 	hashdx = dx;
791 	x = screen->r.max.x - dx;
792 	y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
793 	for(i=0; i<ngraph; i++,y+=dy){
794 		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
795 		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
796 		hashmarks(Pt(x, y), dy, i);
797 		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
798 	}
799 
800 	/* label top edge */
801 	dx = (screen->r.max.x - dx - startx)/nmach;
802 	for(x=startx, i=0; i<nmach; i++,x+=dx){
803 		draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
804 		j = dx/stringwidth(mediumfont, "0");
805 		n = mach[i].nproc;
806 		if(n>1 && j>=1+3+(n>10)+(n>100)){	/* first char of name + (n) */
807 			j -= 3+(n>10)+(n>100);
808 			if(j <= 0)
809 				j = 1;
810 			snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].name, n);
811 		}else
812 			snprint(buf, sizeof buf, "%.*s", j, mach[i].name);
813 		string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP,
814 			mediumfont, buf);
815 	}
816 	/* draw last vertical line */
817 	draw(screen,
818 		Rect(screen->r.max.x-hashdx-1, starty-1, screen->r.max.x-hashdx, screen->r.max.y),
819 		display->black, nil, ZP);
820 
821 	/* create graphs */
822 	for(i=0; i<nmach; i++){
823 		machr = Rect(startx+i*dx, starty, screen->r.max.x, screen->r.max.y);
824 		if(i < nmach-1)
825 			machr.max.x = startx+(i+1)*dx - 1;
826 		else
827 			machr.max.x = screen->r.max.x - hashdx - 1;
828 		y = starty;
829 		for(j=0; j<ngraph; j++, y+=dy){
830 			g = &graph[i*ngraph+j];
831 			/* allocate data */
832 			ondata = g->ndata;
833 			g->ndata = Dx(machr)+1;	/* may be too many if label will be drawn here; so what? */
834 			g->data = erealloc(g->data, g->ndata*sizeof(long));
835 			if(g->ndata > ondata)
836 				memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(long));
837 			/* set geometry */
838 			g->r = machr;
839 			g->r.min.y = y;
840 			g->r.max.y = y+dy - 1;
841 			if(j == ngraph-1)
842 				g->r.max.y = screen->r.max.y;
843 			draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
844 			g->overflow = 0;
845 			*g->msg = 0;
846 			freeimage(g->overtmp);
847 			g->overtmp = nil;
848 			g->overtmplen = 0;
849 			r = g->r;
850 			r.max.y = r.min.y+mediumfont->height;
851 			n = (g->r.max.x - r.min.x)/stringwidth(mediumfont, "9");
852 			if(n > 4){
853 				if(n > Gmsglen)
854 					n = Gmsglen;
855 				r.max.x = r.min.x+stringwidth(mediumfont, "9")*n;
856 				g->overtmplen = n;
857 				g->overtmp = allocimage(display, r, screen->chan, 0, -1);
858 			}
859 			g->newvalue(g->mach, &v, &vmax, &mark);
860 			redraw(g, vmax);
861 		}
862 	}
863 
864 	flushimage(display, 1);
865 }
866 
867 void
eresized(int new)868 eresized(int new)
869 {
870 	lockdisplay(display);
871 	if(new && getwindow(display, Refnone) < 0) {
872 		fprint(2, "%s: can't reattach to window\n", argv0);
873 		killall("reattach");
874 	}
875 	resize();
876 	unlockdisplay(display);
877 }
878 
879 void
dobutton2(Mouse * m)880 dobutton2(Mouse *m)
881 {
882 	int i;
883 
884 	for(i=0; i<Nmenu2; i++)
885 		if(present[i])
886 			memmove(menu2str[i], "drop ", Opwid);
887 		else
888 			memmove(menu2str[i], "add  ", Opwid);
889 	i = emenuhit(3, m, &menu2);
890 	if(i >= 0){
891 		if(!present[i])
892 			addgraph(i);
893 		else if(ngraph > 1)
894 			dropgraph(i);
895 		resize();
896 	}
897 }
898 
899 void
dobutton1(Mouse * m)900 dobutton1(Mouse *m)
901 {
902 	int i, n, dx, dt;
903 	Graph *g;
904 	char *e;
905 	double f;
906 
907 	for(i = 0; i < ngraph*nmach; i++){
908 		if(ptinrect(m->xy, graph[i].r))
909 			break;
910 	}
911 	if(i == ngraph*nmach)
912 		return;
913 
914 	g = &graph[i];
915 	if(g->overtmp == nil)
916 		return;
917 
918 	/* clear any previous message and cursor */
919 	if(g->overflow || *g->msg){
920 		clearmsg(g);
921 		*g->msg = 0;
922 		clearcursor(g);
923 	}
924 
925 	dx = g->r.max.x - m->xy.x;
926 	g->cursor = dx;
927 	dt = dx*pinginterval;
928 	e = &g->msg[sizeof(g->msg)];
929 	seprint(g->msg, e, "%s", ctime(starttime-dt/1000)+11);
930 	g->msg[8] = 0;
931 	n = 8;
932 
933 	switch(index2which(i)){
934 	case Mrtt:
935 		f = rttunscale(g->data[dx]);
936 		seprint(g->msg+n, e, " %3.3g", f/1000000);
937 		break;
938 	case Mlost:
939 		seprint(g->msg+n, e, " %ld%%", g->data[dx]);
940 		break;
941 	}
942 
943 	drawmsg(g, g->msg);
944 	drawcursor(g, m->xy.x);
945 }
946 
947 void
mouseproc(void *)948 mouseproc(void*)
949 {
950 	Mouse mouse;
951 
952 	for(;;){
953 		mouse = emouse();
954 		if(mouse.buttons == 4){
955 			lockdisplay(display);
956 			dobutton2(&mouse);
957 			unlockdisplay(display);
958 		} else if(mouse.buttons == 1){
959 			lockdisplay(display);
960 			dobutton1(&mouse);
961 			unlockdisplay(display);
962 		}
963 	}
964 }
965 
966 void
startproc(void (* f)(void *),void * arg)967 startproc(void (*f)(void*), void *arg)
968 {
969 	int pid;
970 
971 	switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
972 	case -1:
973 		fprint(2, "%s: fork failed: %r\n", argv0);
974 		killall("fork failed");
975 	case 0:
976 		f(arg);
977 		killall("process died");
978 		exits(nil);
979 	}
980 	pids[npid++] = pid;
981 }
982 
983 void
main(int argc,char * argv[])984 main(int argc, char *argv[])
985 {
986 	int i, j;
987 	long v, vmax, mark;
988 	char flags[10], *f, *p;
989 
990 	fmtinstall('V', eipfmt);
991 
992 	f = flags;
993 	pinginterval = 5000;		/* 5 seconds */
994 	ARGBEGIN{
995 	case 'i':
996 		p = ARGF();
997 		if(p == nil)
998 			usage();
999 		pinginterval = atoi(p);
1000 		break;
1001 	default:
1002 		if(f - flags >= sizeof(flags)-1)
1003 			usage();
1004 		*f++ = ARGC();
1005 		break;
1006 	}ARGEND
1007 	*f = 0;
1008 
1009 	for(i=0; i<argc; i++)
1010 		addmachine(argv[i]);
1011 
1012 	for(f = flags; *f; f++)
1013 		switch(*f){
1014 		case 'l':
1015 			addgraph(Mlost);
1016 			break;
1017 		case 'r':
1018 			addgraph(Mrtt);
1019 			break;
1020 		}
1021 
1022 	if(nmach == 0)
1023 		usage();
1024 
1025 	if(ngraph == 0)
1026 		addgraph(Mrtt);
1027 
1028 	for(i=0; i<nmach; i++)
1029 		for(j=0; j<ngraph; j++)
1030 			graph[i*ngraph+j].mach = &mach[i];
1031 
1032 	if(initdraw(nil, nil, argv0) < 0){
1033 		fprint(2, "%s: initdraw failed: %r\n", argv0);
1034 		exits("initdraw");
1035 	}
1036 	colinit();
1037 	einit(Emouse);
1038 	notify(nil);
1039 	startproc(mouseproc, 0);
1040 	display->locking = 1;	/* tell library we're using the display lock */
1041 
1042 	resize();
1043 
1044 	starttime = time(0);
1045 
1046 	unlockdisplay(display); /* display is still locked from initdraw() */
1047 	for(j = 0; ; j++){
1048 		lockdisplay(display);
1049 		if(j == nmach){
1050 			parity = 1-parity;
1051 			j = 0;
1052 			for(i=0; i<nmach*ngraph; i++){
1053 				graph[i].newvalue(graph[i].mach, &v, &vmax, &mark);
1054 				graph[i].update(&graph[i], v, vmax, mark);
1055 			}
1056 			starttime = time(0);
1057 		}
1058 		flushimage(display, 1);
1059 		unlockdisplay(display);
1060 		pingsend(&mach[j%nmach]);
1061 		sleep(pinginterval/nmach);
1062 	}
1063 }
1064