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