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