#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);
	}
}
