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