#include #include #include #include #include #include #include #include #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 */ // int rtt; Req *next; }; struct Machine { Lock; char *name; int pingfd; int nproc; int rttmsgs; ulong rttsum; ulong lastrtt; int lostmsgs; int rcvdmsgs; ulong lostavg; int unreachable; ushort seq; Req *first; Req *last; Req *rcvd; char buf[1024]; char *bufp; char *ebufp; }; 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]; Font *mediumfont; 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; ibuf, sizeof m->buf); if(n <= 0){ close(*fd); *fd = -1; return 0; } m->bufp = m->buf; m->ebufp = m->buf+n; return 1; } 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+mediumfont->height-Ysqueeze > maxy) break; w = chartorune(r, s); s += w; w = runestringwidth(mediumfont, r); if(w > maxw) maxw = w; runestring(screen, p, display->black, ZP, mediumfont, r); p.y += mediumfont->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*mediumfont->height) string(screen, Pt(x, y-mediumfont->height/2), display->black, ZP, mediumfont, 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; ir); 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, mediumfont, 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, int) { Req **l, *r; vlong x, y; y = 10LL*1000000000LL; for(l = &m->first; *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); } } /* IPv4 only */ void pingsend(Machine *m) { int i; char buf[128], err[ERRMAX]; Icmphdr *ip; Req *r; ip = (Icmphdr *)(buf + IPV4HDR_LEN); memset(buf, 0, sizeof buf); r = malloc(sizeof *r); if(r == nil) return; for(i = 32; i < MSGLEN; i++) buf[i] = i; ip->type = EchoRequest; ip->code = 0; ip->seq[0] = m->seq; ip->seq[1] = m->seq>>8; r->seq = m->seq; r->next = nil; lock(m); pingclean(m, -1, nsec(), 0); if(m->first == nil) m->first = r; else m->last->next = r; m->last = r; r->time = nsec(); unlock(m); if(write(m->pingfd, buf, MSGLEN) < MSGLEN){ errstr(err, sizeof err); if(strstr(err, "unreach")||strstr(err, "exceed")) m->unreachable++; } m->seq++; } /* IPv4 only */ void pingrcv(void *arg) { int i, n, fd; uchar buf[512]; ushort x; vlong now; Icmphdr *ip; Ip4hdr *ip4; Machine *m = arg; ip4 = (Ip4hdr *)buf; ip = (Icmphdr *)(buf + IPV4HDR_LEN); fd = dup(m->pingfd, -1); for(;;){ n = read(fd, buf, sizeof(buf)); now = nsec(); if(n <= 0) continue; if(n < MSGLEN){ print("bad len %d/%d\n", n, MSGLEN); continue; } for(i = 32; i < MSGLEN; i++) if(buf[i] != (i&0xff)) continue; x = (ip->seq[1]<<8) | ip->seq[0]; if(ip->type != EchoReply || ip->code != 0) continue; lock(m); pingclean(m, x, now, ip4->ttl); unlock(m); } } void initmach(Machine *m, char *name) { 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->pingfd = dial(netmkaddr(m->name, "icmp", "1"), 0, 0, 0); if(m->pingfd < 0) sysfatal("dialing %s: %r", m->name); 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; } jmp_buf catchalarm; void alarmed(void *a, char *s) { if(strcmp(s, "alarm") == 0) notejmp(a, catchalarm, 1); noted(NDFLT); } 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; ilabel = 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 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 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+mediumfont->height+Labspace; dy = (screen->r.max.y - y)/ngraph; dx = Labspace+stringwidth(mediumfont, "0")+Labspace; startx = x+dx+1; starty = y; for(i=0; ir.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(mediumfont, "0.001")+Labspace; hashdx = dx; x = screen->r.max.x - dx; y = screen->r.min.y + Labspace+mediumfont->height+Labspace; for(i=0; ir.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; ir.max.y), display->black, nil, ZP); j = dx/stringwidth(mediumfont, "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, mediumfont, 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; ir.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; jndata; 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+mediumfont->height; n = (g->r.max.x - r.min.x)/stringwidth(mediumfont, "9"); if(n > 4){ if(n > Gmsglen) n = Gmsglen; r.max.x = r.min.x+stringwidth(mediumfont, "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= 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; for(i=0; ilocking = 1; /* tell library we're using the display lock */ 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