#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <auth.h>
#include <fcall.h>
#include <draw.h>
#include <event.h>
#include <ip.h>
#include "icmp.h"

#define MAXNUM  8       /* maximum number of numbers on data line */

typedef struct Graph    Graph;
typedef struct Machine  Machine;
typedef struct Req      Req;

enum {
       Gmsglen = 16,
};

struct Graph
{
       int             colindex;
       Rectangle       r;
       long            *data;
       int             ndata;
       char            *label;
       void            (*newvalue)(Machine*, long*, long*, long*);
       void            (*update)(Graph*, long, long, long);
       Machine         *mach;
       int             overflow;
       Image           *overtmp;
       int             overtmplen;
       char            msg[Gmsglen];
       int             cursor;
       int             vmax;
};

enum
{
       MSGLEN          = 64,

       Rttmax          = 50,
};

struct Req
{
       int     seq;    /* sequence number */
       vlong   time;   /* time sent */
       Req     *next;
};

struct Machine
{
       Lock;
       char    *name;
       int     version;
       int     pingfd;
       int     nproc;

       int     rttmsgs;
       ulong   rttsum;
       ulong   lastrtt;

       int     lostmsgs;
       int     rcvdmsgs;
       ulong   lostavg;
       int     unreachable;

       ushort  seq;
       Req     *list;
};

enum
{
       Ncolor          = 6,
       Ysqueeze        = 2,    /* vertical squeezing of label text */
       Labspace        = 2,    /* room around label */
       Dot             = 2,    /* height of dot */
       Opwid           = 5,    /* strlen("add  ") or strlen("drop ") */
       NPROC           = 128,
       NMACH           = 32,
};

enum Menu2
{
       Mrtt,
       Mlost,
       Nmenu2,
};

char    *menu2str[Nmenu2+1] = {
       "add  sec rtt",
       "add  % lost ",
       nil,
};


void    rttval(Machine*, long*, long*, long*);
void    lostval(Machine*, long*, long*, long*);

Menu    menu2 = {menu2str, nil};
int             present[Nmenu2];
void            (*newvaluefn[Nmenu2])(Machine*, long*, long*, long*) = {
       rttval,
       lostval,
};

Image           *cols[Ncolor][3];
Graph           *graph;
Machine         mach[NMACH];
int             pids[NPROC];
int             npid;
int             parity; /* toggled to avoid patterns in textured background */
int             nmach;
int             ngraph; /* totaly number is ngraph*nmach */
long            starttime;
int             pinginterval;

void    dropgraph(int);
void    addgraph(int);
void    startproc(void (*)(void*), void*);
void    resize(void);
long    rttscale(long);
int     which2index(int);
int     index2which(int);

void
killall(char *s)
{
       int i, pid;

       pid = getpid();
       for(i=0; i<NPROC; i++)
               if(pids[i] && pids[i]!=pid)
                       postnote(PNPROC, pids[i], "kill");
       exits(s);
}

void*
emalloc(ulong sz)
{
       void *v;
       v = malloc(sz);
       if(v == nil) {
               fprint(2, "%s: out of memory allocating %ld: %r\n", argv0, sz);
               killall("mem");
       }
       memset(v, 0, sz);
       return v;
}

void*
erealloc(void *v, ulong sz)
{
       v = realloc(v, sz);
       if(v == nil) {
               fprint(2, "%s: out of memory reallocating %ld: %r\n", argv0, sz);
               killall("mem");
       }
       return v;
}

char*
estrdup(char *s)
{
       char *t;
       if((t = strdup(s)) == nil) {
               fprint(2, "%s: out of memory in strdup(%.10s): %r\n", argv0, s);
               killall("mem");
       }
       return t;
}

void
mkcol(int i, int c0, int c1, int c2)
{
       cols[i][0] = allocimagemix(display, c0, DWhite);
       cols[i][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c1);
       cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2);
}

void
colinit(void)
{
       /* Peach */
       mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
       /* Aqua */
       mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
       /* Yellow */
       mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
       /* Green */
       mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
       /* Blue */
       mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
       /* Grey */
       cols[5][0] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
       cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
       cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF);
}

void
label(Point p, int dy, char *text)
{
       char *s;
       Rune r[2];
       int w, maxw, maxy;

       p.x += Labspace;
       maxy = p.y+dy;
       maxw = 0;
       r[1] = '\0';
       for(s=text; *s; ){
               if(p.y+font->height-Ysqueeze > maxy)
                       break;
               w = chartorune(r, s);
               s += w;
               w = runestringwidth(font, r);
               if(w > maxw)
                       maxw = w;
               runestring(screen, p, display->black, ZP, font, r);
               p.y += font->height-Ysqueeze;
       }
}

void
hashmark(Point p, int dy, long v, long vmax, char *label)
{
       int y;
       int x;

       x = p.x + Labspace;
       y = p.y + (dy*(vmax-v))/vmax;
       draw(screen, Rect(p.x, y-1, p.x+Labspace, y+1), display->black, nil, ZP);
       if(dy > 5*font->height)
               string(screen, Pt(x, y-font->height/2),
                       display->black, ZP, font, label);
}

void
hashmarks(Point p, int dy, int which)
{
       switch(index2which(which)){
       case Mrtt:
               hashmark(p, dy, rttscale(1000000), Rttmax, "1.");
               hashmark(p, dy, rttscale(100000), Rttmax, "0.1");
               hashmark(p, dy, rttscale(10000), Rttmax, "0.01");
               hashmark(p, dy, rttscale(1000), Rttmax, "0.001");
               break;
       case Mlost:
               hashmark(p, dy, 75, 100, " 75%");
               hashmark(p, dy, 50, 100, " 50%");
               hashmark(p, dy, 25, 100, " 25%");
               break;
       }
}

Point
paritypt(int x)
{
       return Pt(x+parity, 0);
}

Point
datapoint(Graph *g, int x, long v, long vmax)
{
       Point p;

       p.x = x;
       p.y = g->r.max.y - Dy(g->r)*v/vmax - Dot;
       if(p.y < g->r.min.y)
               p.y = g->r.min.y;
       if(p.y > g->r.max.y-Dot)
               p.y = g->r.max.y-Dot;
       return p;
}

void
drawdatum(Graph *g, int x, long prev, long v, long vmax)
{
       int c;
       Point p, q;

       c = g->colindex;
       p = datapoint(g, x, v, vmax);
       q = datapoint(g, x, prev, vmax);
       if(p.y < q.y){
               draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
               draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
               draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
       }else{
               draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
               draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
               draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
       }
       g->vmax = vmax;
}

void
drawmark(Graph *g, int x)
{
       int c;

       c = (g->colindex+1)&Ncolor;
       draw(screen, Rect(x, g->r.min.y, x+1, g->r.max.y), cols[c][2], nil, ZP);
}

void
redraw(Graph *g, int vmax)
{
       int i, c;

       c = g->colindex;
       draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
       for(i=1; i<Dx(g->r); i++)
               drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
       drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
}

void
clearmsg(Graph *g)
{
       if(g->overtmp != nil)
               draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
       g->overflow = 0;
}

void
drawmsg(Graph *g, char *msg)
{
       if(g->overtmp == nil)
               return;

       /* save previous contents of screen */
       draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);

       /* draw message */
       if(strlen(msg) > g->overtmplen)
               msg[g->overtmplen] = 0;
       string(screen, g->overtmp->r.min, display->black, ZP, font, msg);
}

void
clearcursor(Graph *g)
{
       int x;
       long prev;

       if(g->overtmp == nil)
               return;

       if(g->cursor > 0 && g->cursor < g->ndata){
               x = g->r.max.x - g->cursor;
               prev = 0;
               if(g->cursor > 0)
                       prev = g->data[g->cursor-1];
               drawdatum(g, x, prev, g->data[g->cursor], g->vmax);
               g->cursor = -1;
       }
}

void
drawcursor(Graph *g, int x)
{
       if(g->overtmp == nil)
               return;

       draw(screen, Rect(x, g->r.min.y, x+1, g->r.max.y), cols[g->colindex][2], nil, ZP);
}

void
update1(Graph *g, long v, long vmax, long mark)
{
       char buf[Gmsglen];

       /* put back screen value sans message */
       if(g->overflow || *g->msg){
               clearmsg(g);
               g->overflow = 0;
       }

       draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
       drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
       if(mark)
               drawmark(g, g->r.max.x-1);
       memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
       g->data[0] = v;
       if(v>vmax){
               g->overflow = 1;
               sprint(buf, "%ld", v);
               drawmsg(g, buf);
       } else if(*g->msg)
               drawmsg(g, g->msg);

       if(g->cursor >= 0){
               g->cursor++;
               if(g->cursor >= g->ndata){
                       g->cursor = -1;
                       if(*g->msg){
                               clearmsg(g);
                               *g->msg = 0;
                       }
               }
       }

}

void
pinglost(Machine *m, Req*)
{
       m->lostmsgs++;
}

void
pingreply(Machine *m, Req *r)
{
       ulong x;

       x = r->time/1000LL;
       m->rttsum += x;
       m->rcvdmsgs++;
       m->rttmsgs++;
}


void
pingclean(Machine *m, ushort seq, vlong now)
{
       Req **l, *r;
       vlong x, y;

       y = 10LL*1000000000LL;
       for(l = &m->list; *l; ){
               r = *l;
               x = now - r->time;
               if(x > y || r->seq == seq){
                       *l = r->next;
                       r->time = x;
                       if(r->seq != seq)
                               pinglost(m, r);
                       else
                               pingreply(m, r);
                       free(r);
               } else
                       l = &(r->next);
       }
}

void
pingsend(Machine *m)
{
       int i;
       uchar buf[128];
       char err[ERRMAX];
       Icmphdr *ip;
       Req *r;

       ip = (Icmphdr *)(buf + (m->version==4? IPV4HDR_LEN: IPV6HDR_LEN));
       memset(buf, 0, sizeof buf);
       r = malloc(sizeof *r);
       if(r == nil)
               return;

       for(i = ip->data-buf; i < MSGLEN; i++)
               buf[i] = i;
       ip->type = m->version==4? EchoRequest: EchoRequestV6;
       ip->code = 0;
       ip->seq[0] = m->seq;
       ip->seq[1] = m->seq>>8;
       r->seq = m->seq;
       r->time = nsec();
       lock(m);
       pingclean(m, -1, r->time);
       r->next = m->list;
       m->list = r;
       unlock(m);
       if(write(m->pingfd, buf, MSGLEN) < MSGLEN){
               errstr(err, sizeof err);
               if(strstr(err, "unreach")||strstr(err, "exceed"))
                       m->unreachable++;
       }
       m->seq++;
}

void
pingrcv(void *arg)
{
       int i, n;
       uchar buf[512];
       ushort x;
       Icmphdr *ip;
       Machine *m = arg;

       ip = (Icmphdr *)(buf + (m->version==4? IPV4HDR_LEN: IPV6HDR_LEN));
       for(;;){
               n = read(m->pingfd, buf, sizeof(buf));
               if(n <= 0)
                       break;
               if(n < MSGLEN)
                       continue;
               for(i = ip->data-buf; i < MSGLEN; i++)
                       if(buf[i] != (i&0xff))
                               break;
               if(i != MSGLEN)
                       continue;
               x = (ip->seq[1]<<8) | ip->seq[0];
               if(ip->type != (m->version==4? EchoReply: EchoReplyV6) || ip->code != 0)
                       continue;
               lock(m);
               pingclean(m, x, nsec());
               unlock(m);
       }
}

void
initmach(Machine *m, char *name)
{
       int cfd = -1;
       char *p;

       srand(time(0));
       p = strchr(name, '!');
       if(p){
               p++;
               m->name = estrdup(p+1);
       }else
               p = name;
       m->name = estrdup(p);
       m->nproc = 1;

       m->version = 4;
       if(strstr(name, "icmpv6!") != nil)
               m->version = 6;
again:
       m->pingfd = dial(netmkaddr(m->name, m->version==4? "icmp": "icmpv6", "1"), nil, nil, &cfd);
       if(m->pingfd < 0){
               if(m->version == 4){
                       m->version = 6;
                       goto again;
               }
               sysfatal("dialing %s: %r", m->name);
       }
       write(cfd, "ignoreadvice", 12);
       close(cfd);
       startproc(pingrcv, m);
}

long
rttscale(long x)
{
       if(x == 0)
               return 0;
       x = 10.0*log10(x) - 20.0;
       if(x < 0)
               x = 0;
       return x;
}

double
rttunscale(long x)
{
       double dx;

       x += 20;
       dx = x;
       return pow(10.0, dx/10.0);
}

void
rttval(Machine *m, long *v, long *vmax, long *mark)
{
       ulong x;

       if(m->rttmsgs == 0){
               x = m->lastrtt;
       } else {
               x = m->rttsum/m->rttmsgs;
               m->rttsum = m->rttmsgs = 0;
               m->lastrtt = x;
       }

       *v = rttscale(x);
       *vmax = Rttmax;
       *mark = 0;
}

void
lostval(Machine *m, long *v, long *vmax, long *mark)
{
       ulong x;

       if(m->rcvdmsgs+m->lostmsgs > 0)
               x = (m->lostavg>>1) + (((m->lostmsgs*100)/(m->lostmsgs + m->rcvdmsgs))>>1);
       else
               x = m->lostavg;
       m->lostavg = x;
       m->lostmsgs = m->rcvdmsgs = 0;

       if(m->unreachable){
               m->unreachable = 0;
               *mark = 100;
       } else
               *mark = 0;

       *v = x;
       *vmax = 100;
}

void
usage(void)
{
       fprint(2, "usage: %s machine [machine...]\n", argv0);
       exits("usage");
}

void
addgraph(int n)
{
       Graph *g, *ograph;
       int i, j;
       static int nadd;

       if(n > nelem(menu2str))
               abort();
       /* avoid two adjacent graphs of same color */
       if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
               nadd++;
       ograph = graph;
       graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
       for(i=0; i<nmach; i++)
               for(j=0; j<ngraph; j++)
                       graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
       free(ograph);
       ngraph++;
       for(i=0; i<nmach; i++){
               g = &graph[i*ngraph+(ngraph-1)];
               memset(g, 0, sizeof(Graph));
               g->label = menu2str[n]+Opwid;
               g->newvalue = newvaluefn[n];
               g->update = update1;    /* no other update functions yet */
               g->mach = &mach[i];
               g->colindex = nadd%Ncolor;
       }
       present[n] = 1;
       nadd++;
}

int
which2index(int which)
{
       int i, n;

       n = -1;
       for(i=0; i<ngraph; i++){
               if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
                       n = i;
                       break;
               }
       }
       if(n < 0){
               fprint(2, "%s: internal error can't drop graph\n", argv0);
               killall("error");
       }
       return n;
}

int
index2which(int index)
{
       int i, n;

       n = -1;
       for(i=0; i<Nmenu2; i++){
               if(strcmp(menu2str[i]+Opwid, graph[index].label) == 0){
                       n = i;
                       break;
               }
       }
       if(n < 0){
               fprint(2, "%s: internal error can't identify graph\n", argv0);
               killall("error");
       }
       return n;
}

void
dropgraph(int which)
{
       Graph *ograph;
       int i, j, n;

       if(which > nelem(menu2str))
               abort();
       /* convert n to index in graph table */
       n = which2index(which);
       ograph = graph;
       graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
       for(i=0; i<nmach; i++){
               for(j=0; j<n; j++)
                       graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
               free(ograph[i*ngraph+j].data);
               freeimage(ograph[i*ngraph+j].overtmp);
               for(j++; j<ngraph; j++)
                       graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
       }
       free(ograph);
       ngraph--;
       present[which] = 0;
}

void
addmachine(char *name)
{
       if(ngraph > 0){
               fprint(2, "%s: internal error: ngraph>0 in addmachine()\n", argv0);
               usage();
       }
       if(nmach == NMACH)
               sysfatal("too many machines");
       initmach(&mach[nmach++], name);
}


void
resize(void)
{
       int i, j, n, startx, starty, x, y, dx, dy, hashdx, ondata;
       Graph *g;
       Rectangle machr, r;
       long v, vmax, mark;
       char buf[128];

       draw(screen, screen->r, display->white, nil, ZP);

       /* label left edge */
       x = screen->r.min.x;
       y = screen->r.min.y + Labspace+font->height+Labspace;
       dy = (screen->r.max.y - y)/ngraph;
       dx = Labspace+stringwidth(font, "0")+Labspace;
       startx = x+dx+1;
       starty = y;
       for(i=0; i<ngraph; i++,y+=dy){
               draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
               draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
               label(Pt(x, y), dy, graph[i].label);
               draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
       }

       /* label right edge */
       dx = Labspace+stringwidth(font, "0.001")+Labspace;
       hashdx = dx;
       x = screen->r.max.x - dx;
       y = screen->r.min.y + Labspace+font->height+Labspace;
       for(i=0; i<ngraph; i++,y+=dy){
               draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
               draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
               hashmarks(Pt(x, y), dy, i);
               draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
       }

       /* label top edge */
       dx = (screen->r.max.x - dx - startx)/nmach;
       for(x=startx, i=0; i<nmach; i++,x+=dx){
               draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
               j = dx/stringwidth(font, "0");
               n = mach[i].nproc;
               if(n>1 && j>=1+3+(n>10)+(n>100)){       /* first char of name + (n) */
                       j -= 3+(n>10)+(n>100);
                       if(j <= 0)
                               j = 1;
                       snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].name, n);
               }else
                       snprint(buf, sizeof buf, "%.*s", j, mach[i].name);
               string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP,
                       font, buf);
       }
       /* draw last vertical line */
       draw(screen,
               Rect(screen->r.max.x-hashdx-1, starty-1, screen->r.max.x-hashdx, screen->r.max.y),
               display->black, nil, ZP);

       /* create graphs */
       for(i=0; i<nmach; i++){
               machr = Rect(startx+i*dx, starty, screen->r.max.x, screen->r.max.y);
               if(i < nmach-1)
                       machr.max.x = startx+(i+1)*dx - 1;
               else
                       machr.max.x = screen->r.max.x - hashdx - 1;
               y = starty;
               for(j=0; j<ngraph; j++, y+=dy){
                       g = &graph[i*ngraph+j];
                       /* allocate data */
                       ondata = g->ndata;
                       g->ndata = Dx(machr)+1; /* may be too many if label will be drawn here; so what? */
                       g->data = erealloc(g->data, g->ndata*sizeof(long));
                       if(g->ndata > ondata)
                               memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(long));
                       /* set geometry */
                       g->r = machr;
                       g->r.min.y = y;
                       g->r.max.y = y+dy - 1;
                       if(j == ngraph-1)
                               g->r.max.y = screen->r.max.y;
                       draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
                       g->overflow = 0;
                       *g->msg = 0;
                       freeimage(g->overtmp);
                       g->overtmp = nil;
                       g->overtmplen = 0;
                       r = g->r;
                       r.max.y = r.min.y+font->height;
                       n = (g->r.max.x - r.min.x)/stringwidth(font, "9");
                       if(n > 4){
                               if(n > Gmsglen)
                                       n = Gmsglen;
                               r.max.x = r.min.x+stringwidth(font, "9")*n;
                               g->overtmplen = n;
                               g->overtmp = allocimage(display, r, screen->chan, 0, -1);
                       }
                       g->newvalue(g->mach, &v, &vmax, &mark);
                       redraw(g, vmax);
               }
       }

       flushimage(display, 1);
}

void
eresized(int new)
{
       lockdisplay(display);
       if(new && getwindow(display, Refnone) < 0) {
               fprint(2, "%s: can't reattach to window\n", argv0);
               killall("reattach");
       }
       resize();
       unlockdisplay(display);
}

void
dobutton2(Mouse *m)
{
       int i;

       for(i=0; i<Nmenu2; i++)
               if(present[i])
                       memmove(menu2str[i], "drop ", Opwid);
               else
                       memmove(menu2str[i], "add  ", Opwid);
       i = emenuhit(3, m, &menu2);
       if(i >= 0){
               if(!present[i])
                       addgraph(i);
               else if(ngraph > 1)
                       dropgraph(i);
               resize();
       }
}

void
dobutton1(Mouse *m)
{
       int i, n, dx, dt;
       Graph *g;
       char *e;
       double f;

       for(i = 0; i < ngraph*nmach; i++){
               if(ptinrect(m->xy, graph[i].r))
                       break;
       }
       if(i == ngraph*nmach)
               return;

       g = &graph[i];
       if(g->overtmp == nil)
               return;

       /* clear any previous message and cursor */
       if(g->overflow || *g->msg){
               clearmsg(g);
               *g->msg = 0;
               clearcursor(g);
       }

       dx = g->r.max.x - m->xy.x;
       g->cursor = dx;
       dt = dx*pinginterval;
       e = &g->msg[sizeof(g->msg)];
       seprint(g->msg, e, "%s", ctime(starttime-dt/1000)+11);
       g->msg[8] = 0;
       n = 8;

       switch(index2which(i)){
       case Mrtt:
               f = rttunscale(g->data[dx]);
               seprint(g->msg+n, e, " %3.3g", f/1000000);
               break;
       case Mlost:
               seprint(g->msg+n, e, " %ld%%", g->data[dx]);
               break;
       }

       drawmsg(g, g->msg);
       drawcursor(g, m->xy.x);
}

void
mouseproc(void*)
{
       Mouse mouse;

       for(;;){
               mouse = emouse();
               if(mouse.buttons == 4){
                       lockdisplay(display);
                       dobutton2(&mouse);
                       unlockdisplay(display);
               } else if(mouse.buttons == 1){
                       lockdisplay(display);
                       dobutton1(&mouse);
                       unlockdisplay(display);
               }
       }
}

void
startproc(void (*f)(void*), void *arg)
{
       int pid;

       switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
       case -1:
               fprint(2, "%s: fork failed: %r\n", argv0);
               killall("fork failed");
       case 0:
               f(arg);
               killall("process died");
               exits(nil);
       }
       pids[npid++] = pid;
}

void
main(int argc, char *argv[])
{
       int i, j;
       long v, vmax, mark;
       char flags[10], *f, *p;

       fmtinstall('V', eipfmt);

       f = flags;
       pinginterval = 5000;            /* 5 seconds */
       ARGBEGIN{
       case 'i':
               p = ARGF();
               if(p == nil)
                       usage();
               pinginterval = atoi(p);
               break;
       default:
               if(f - flags >= sizeof(flags)-1)
                       usage();
               *f++ = ARGC();
               break;
       }ARGEND
       *f = 0;

       pids[npid++] = getpid();

       for(i=0; i<argc; i++)
               addmachine(argv[i]);

       for(f = flags; *f; f++)
               switch(*f){
               case 'l':
                       addgraph(Mlost);
                       break;
               case 'r':
                       addgraph(Mrtt);
                       break;
               }

       if(nmach == 0)
               usage();

       if(ngraph == 0)
               addgraph(Mrtt);

       for(i=0; i<nmach; i++)
               for(j=0; j<ngraph; j++)
                       graph[i*ngraph+j].mach = &mach[i];

       if(initdraw(nil, nil, argv0) < 0){
               fprint(2, "%s: initdraw failed: %r\n", argv0);
               exits("initdraw");
       }
       display->locking = 1;   /* tell library we're using the display lock */
       colinit();
       einit(Emouse);
       startproc(mouseproc, 0);

       resize();

       starttime = time(0);

       unlockdisplay(display); /* display is still locked from initdraw() */
       for(j = 0; ; j++){
               lockdisplay(display);
               if(j == nmach){
                       parity = 1-parity;
                       j = 0;
                       for(i=0; i<nmach*ngraph; i++){
                               graph[i].newvalue(graph[i].mach, &v, &vmax, &mark);
                               graph[i].update(&graph[i], v, vmax, mark);
                       }
                       starttime = time(0);
               }
               flushimage(display, 1);
               unlockdisplay(display);
               pingsend(&mach[j%nmach]);
               sleep(pinginterval/nmach);
       }
}