ref: 1bfde841484fadb1c41f41a6279e7e070f61a31b
dir: /sys/src/cmd/ip/gping.c/
#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); } }