| #include "stdinc.h" |
| #include "dat.h" |
| #include "fns.h" |
| #include "xml.h" |
| |
| typedef struct HttpObj HttpObj; |
| extern QLock memdrawlock; |
| |
| enum |
| { |
| ObjNameSize = 64, |
| MaxObjs = 64 |
| }; |
| |
| struct HttpObj |
| { |
| char name[ObjNameSize]; |
| int (*f)(HConnect*); |
| }; |
| |
| static HttpObj objs[MaxObjs]; |
| |
| static char *webroot; |
| |
| static void listenproc(void*); |
| static int estats(HConnect *c); |
| static int dindex(HConnect *c); |
| static int xindex(HConnect *c); |
| static int xlog(HConnect *c); |
| static int sindex(HConnect *c); |
| static int hempty(HConnect *c); |
| static int hlcacheempty(HConnect *c); |
| static int hdcacheempty(HConnect *c); |
| static int hicacheempty(HConnect *c); |
| static int hicachekick(HConnect *c); |
| static int hdcachekick(HConnect *c); |
| static int hicacheflush(HConnect *c); |
| static int hdcacheflush(HConnect *c); |
| static int httpdobj(char *name, int (*f)(HConnect*)); |
| static int xgraph(HConnect *c); |
| static int xset(HConnect *c); |
| static int fromwebdir(HConnect *c); |
| |
| int |
| httpdinit(char *address, char *dir) |
| { |
| fmtinstall('D', hdatefmt); |
| /* fmtinstall('H', httpfmt); */ |
| fmtinstall('U', hurlfmt); |
| |
| if(address == nil) |
| address = "tcp!*!http"; |
| webroot = dir; |
| |
| httpdobj("/stats", estats); |
| httpdobj("/index", dindex); |
| httpdobj("/storage", sindex); |
| httpdobj("/xindex", xindex); |
| httpdobj("/flushicache", hicacheflush); |
| httpdobj("/flushdcache", hdcacheflush); |
| httpdobj("/kickicache", hicachekick); |
| httpdobj("/kickdcache", hdcachekick); |
| httpdobj("/graph", xgraph); |
| httpdobj("/set", xset); |
| httpdobj("/log", xlog); |
| httpdobj("/empty", hempty); |
| httpdobj("/emptyicache", hicacheempty); |
| httpdobj("/emptylumpcache", hlcacheempty); |
| httpdobj("/emptydcache", hdcacheempty); |
| httpdobj("/disk", hdisk); |
| httpdobj("/debug", hdebug); |
| httpdobj("/proc/", hproc); |
| |
| if(vtproc(listenproc, address) < 0) |
| return -1; |
| return 0; |
| } |
| |
| static int |
| httpdobj(char *name, int (*f)(HConnect*)) |
| { |
| int i; |
| |
| if(name == nil || strlen(name) >= ObjNameSize) |
| return -1; |
| for(i = 0; i < MaxObjs; i++){ |
| if(objs[i].name[0] == '\0'){ |
| strcpy(objs[i].name, name); |
| objs[i].f = f; |
| return 0; |
| } |
| if(strcmp(objs[i].name, name) == 0) |
| return -1; |
| } |
| return -1; |
| } |
| |
| static HConnect* |
| mkconnect(void) |
| { |
| HConnect *c; |
| |
| c = mallocz(sizeof(HConnect), 1); |
| if(c == nil) |
| sysfatal("out of memory"); |
| c->replog = nil; |
| c->hpos = c->header; |
| c->hstop = c->header; |
| return c; |
| } |
| |
| void httpproc(void*); |
| |
| static void |
| listenproc(void *vaddress) |
| { |
| HConnect *c; |
| char *address, ndir[NETPATHLEN], dir[NETPATHLEN]; |
| int ctl, nctl, data; |
| |
| address = vaddress; |
| ctl = announce(address, dir); |
| if(ctl < 0){ |
| fprint(2, "venti: httpd can't announce on %s: %r\n", address); |
| return; |
| } |
| |
| if(0) print("announce ctl %d dir %s\n", ctl, dir); |
| for(;;){ |
| /* |
| * wait for a call (or an error) |
| */ |
| nctl = listen(dir, ndir); |
| if(0) print("httpd listen %d %s...\n", nctl, ndir); |
| if(nctl < 0){ |
| fprint(2, "venti: httpd can't listen on %s: %r\n", address); |
| return; |
| } |
| |
| data = accept(ctl, ndir); |
| if(0) print("httpd accept %d...\n", data); |
| if(data < 0){ |
| fprint(2, "venti: httpd accept: %r\n"); |
| close(nctl); |
| continue; |
| } |
| if(0) print("httpd close nctl %d\n", nctl); |
| close(nctl); |
| c = mkconnect(); |
| hinit(&c->hin, data, Hread); |
| hinit(&c->hout, data, Hwrite); |
| vtproc(httpproc, c); |
| } |
| } |
| |
| void |
| httpproc(void *v) |
| { |
| HConnect *c; |
| int ok, i, n; |
| |
| c = v; |
| |
| for(;;){ |
| /* |
| * No timeout because the signal appears to hit every |
| * proc, not just us. |
| */ |
| if(hparsereq(c, 0) < 0) |
| break; |
| |
| for(i = 0; i < MaxObjs && objs[i].name[0]; i++){ |
| n = strlen(objs[i].name); |
| if((objs[i].name[n-1] == '/' && strncmp(c->req.uri, objs[i].name, n) == 0) |
| || (objs[i].name[n-1] != '/' && strcmp(c->req.uri, objs[i].name) == 0)){ |
| ok = (*objs[i].f)(c); |
| goto found; |
| } |
| } |
| ok = fromwebdir(c); |
| found: |
| hflush(&c->hout); |
| if(c->head.closeit) |
| ok = -1; |
| hreqcleanup(c); |
| |
| if(ok < 0) |
| break; |
| } |
| hreqcleanup(c); |
| close(c->hin.fd); |
| free(c); |
| } |
| |
| char* |
| hargstr(HConnect *c, char *name, char *def) |
| { |
| HSPairs *p; |
| |
| for(p=c->req.searchpairs; p; p=p->next) |
| if(strcmp(p->s, name) == 0) |
| return p->t; |
| return def; |
| } |
| |
| vlong |
| hargint(HConnect *c, char *name, vlong def) |
| { |
| char *a; |
| |
| if((a = hargstr(c, name, nil)) == nil) |
| return def; |
| return atoll(a); |
| } |
| |
| static int |
| percent(ulong v, ulong total) |
| { |
| if(total == 0) |
| total = 1; |
| if(v < 1000*1000) |
| return (v * 100) / total; |
| total /= 100; |
| if(total == 0) |
| total = 1; |
| return v / total; |
| } |
| |
| static int |
| preq(HConnect *c) |
| { |
| if(hparseheaders(c, 0) < 0) |
| return -1; |
| if(strcmp(c->req.meth, "GET") != 0 |
| && strcmp(c->req.meth, "HEAD") != 0) |
| return hunallowed(c, "GET, HEAD"); |
| if(c->head.expectother || c->head.expectcont) |
| return hfail(c, HExpectFail, nil); |
| return 0; |
| } |
| |
| int |
| hsettype(HConnect *c, char *type) |
| { |
| Hio *hout; |
| int r; |
| |
| r = preq(c); |
| if(r < 0) |
| return r; |
| |
| hout = &c->hout; |
| if(c->req.vermaj){ |
| hokheaders(c); |
| hprint(hout, "Content-type: %s\r\n", type); |
| if(http11(c)) |
| hprint(hout, "Transfer-Encoding: chunked\r\n"); |
| hprint(hout, "\r\n"); |
| } |
| |
| if(http11(c)) |
| hxferenc(hout, 1); |
| else |
| c->head.closeit = 1; |
| return 0; |
| } |
| |
| int |
| hsethtml(HConnect *c) |
| { |
| return hsettype(c, "text/html; charset=utf-8"); |
| } |
| |
| int |
| hsettext(HConnect *c) |
| { |
| return hsettype(c, "text/plain; charset=utf-8"); |
| } |
| |
| static int |
| herror(HConnect *c) |
| { |
| int n; |
| Hio *hout; |
| |
| hout = &c->hout; |
| n = snprint(c->xferbuf, HBufSize, "<html><head><title>Error</title></head>\n<body><h1>Error</h1>\n<pre>%r</pre>\n</body></html>"); |
| hprint(hout, "%s %s\r\n", hversion, "400 Bad Request"); |
| hprint(hout, "Date: %D\r\n", time(nil)); |
| hprint(hout, "Server: Venti\r\n"); |
| hprint(hout, "Content-Type: text/html\r\n"); |
| hprint(hout, "Content-Length: %d\r\n", n); |
| if(c->head.closeit) |
| hprint(hout, "Connection: close\r\n"); |
| else if(!http11(c)) |
| hprint(hout, "Connection: Keep-Alive\r\n"); |
| hprint(hout, "\r\n"); |
| |
| if(c->req.meth == nil || strcmp(c->req.meth, "HEAD") != 0) |
| hwrite(hout, c->xferbuf, n); |
| |
| return hflush(hout); |
| } |
| |
| int |
| hnotfound(HConnect *c) |
| { |
| int r; |
| |
| r = preq(c); |
| if(r < 0) |
| return r; |
| return hfail(c, HNotFound, c->req.uri); |
| } |
| |
| struct { |
| char *ext; |
| char *type; |
| } exttab[] = { |
| ".html", "text/html", |
| ".txt", "text/plain", |
| ".xml", "text/xml", |
| ".png", "image/png", |
| ".gif", "image/gif", |
| 0 |
| }; |
| |
| static int |
| fromwebdir(HConnect *c) |
| { |
| char buf[4096], *p, *ext, *type; |
| int i, fd, n, defaulted; |
| Dir *d; |
| |
| if(webroot == nil || strstr(c->req.uri, "..")) |
| return hnotfound(c); |
| snprint(buf, sizeof buf-20, "%s/%s", webroot, c->req.uri+1); |
| defaulted = 0; |
| reopen: |
| if((fd = open(buf, OREAD)) < 0) |
| return hnotfound(c); |
| d = dirfstat(fd); |
| if(d == nil){ |
| close(fd); |
| return hnotfound(c); |
| } |
| if(d->mode&DMDIR){ |
| if(!defaulted){ |
| defaulted = 1; |
| strcat(buf, "/index.html"); |
| free(d); |
| close(fd); |
| goto reopen; |
| } |
| free(d); |
| return hnotfound(c); |
| } |
| free(d); |
| p = buf+strlen(buf); |
| type = "application/octet-stream"; |
| for(i=0; exttab[i].ext; i++){ |
| ext = exttab[i].ext; |
| if(p-strlen(ext) >= buf && strcmp(p-strlen(ext), ext) == 0){ |
| type = exttab[i].type; |
| break; |
| } |
| } |
| if(hsettype(c, type) < 0){ |
| close(fd); |
| return 0; |
| } |
| while((n = read(fd, buf, sizeof buf)) > 0) |
| if(hwrite(&c->hout, buf, n) < 0) |
| break; |
| close(fd); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| static struct |
| { |
| char *name; |
| int *p; |
| } namedints[] = |
| { |
| "compress", &compressblocks, |
| "devnull", &writestodevnull, |
| "logging", &ventilogging, |
| "stats", &collectstats, |
| "icachesleeptime", &icachesleeptime, |
| "minicachesleeptime", &minicachesleeptime, |
| "arenasumsleeptime", &arenasumsleeptime, |
| "l0quantum", &l0quantum, |
| "l1quantum", &l1quantum, |
| "manualscheduling", &manualscheduling, |
| "ignorebloom", &ignorebloom, |
| "syncwrites", &syncwrites, |
| "icacheprefetch", &icacheprefetch, |
| "bootstrap", &bootstrap, |
| 0 |
| }; |
| |
| static int |
| xset(HConnect *c) |
| { |
| int i, old; |
| char *name, *value; |
| |
| if(hsettext(c) < 0) |
| return -1; |
| |
| if((name = hargstr(c, "name", nil)) == nil || name[0] == 0){ |
| for(i=0; namedints[i].name; i++) |
| hprint(&c->hout, "%s = %d\n", namedints[i].name, *namedints[i].p); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| for(i=0; namedints[i].name; i++) |
| if(strcmp(name, namedints[i].name) == 0) |
| break; |
| if(!namedints[i].name){ |
| hprint(&c->hout, "%s not found\n", name); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| if((value = hargstr(c, "value", nil)) == nil || value[0] == 0){ |
| hprint(&c->hout, "%s = %d\n", namedints[i].name, *namedints[i].p); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| old = *namedints[i].p; |
| *namedints[i].p = atoll(value); |
| hprint(&c->hout, "%s = %d (was %d)\n", name, *namedints[i].p, old); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| static int |
| estats(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| |
| |
| hout = &c->hout; |
| /* |
| hprint(hout, "lump writes=%,ld\n", stats.lumpwrites); |
| hprint(hout, "lump reads=%,ld\n", stats.lumpreads); |
| hprint(hout, "lump cache read hits=%,ld\n", stats.lumphit); |
| hprint(hout, "lump cache read misses=%,ld\n", stats.lumpmiss); |
| |
| hprint(hout, "clump disk writes=%,ld\n", stats.clumpwrites); |
| hprint(hout, "clump disk bytes written=%,lld\n", stats.clumpbwrites); |
| hprint(hout, "clump disk bytes compressed=%,lld\n", stats.clumpbcomp); |
| hprint(hout, "clump disk reads=%,ld\n", stats.clumpreads); |
| hprint(hout, "clump disk bytes read=%,lld\n", stats.clumpbreads); |
| hprint(hout, "clump disk bytes uncompressed=%,lld\n", stats.clumpbuncomp); |
| |
| hprint(hout, "clump directory disk writes=%,ld\n", stats.ciwrites); |
| hprint(hout, "clump directory disk reads=%,ld\n", stats.cireads); |
| |
| hprint(hout, "index disk writes=%,ld\n", stats.indexwrites); |
| hprint(hout, "index disk reads=%,ld\n", stats.indexreads); |
| hprint(hout, "index disk bloom filter hits=%,ld %d%% falsemisses=%,ld %d%%\n", |
| stats.indexbloomhits, |
| percent(stats.indexbloomhits, stats.indexreads), |
| stats.indexbloomfalsemisses, |
| percent(stats.indexbloomfalsemisses, stats.indexreads)); |
| hprint(hout, "bloom filter bits=%,ld of %,ld %d%%\n", |
| stats.bloomones, stats.bloombits, percent(stats.bloomones, stats.bloombits)); |
| hprint(hout, "index disk reads for modify=%,ld\n", stats.indexwreads); |
| hprint(hout, "index disk reads for allocation=%,ld\n", stats.indexareads); |
| hprint(hout, "index block splits=%,ld\n", stats.indexsplits); |
| |
| hprint(hout, "index cache lookups=%,ld\n", stats.iclookups); |
| hprint(hout, "index cache hits=%,ld %d%%\n", stats.ichits, |
| percent(stats.ichits, stats.iclookups)); |
| hprint(hout, "index cache fills=%,ld %d%%\n", stats.icfills, |
| percent(stats.icfills, stats.iclookups)); |
| hprint(hout, "index cache inserts=%,ld\n", stats.icinserts); |
| |
| hprint(hout, "disk cache hits=%,ld\n", stats.pchit); |
| hprint(hout, "disk cache misses=%,ld\n", stats.pcmiss); |
| hprint(hout, "disk cache reads=%,ld\n", stats.pcreads); |
| hprint(hout, "disk cache bytes read=%,lld\n", stats.pcbreads); |
| |
| hprint(hout, "disk cache writes=%,ld\n", stats.dirtydblocks); |
| hprint(hout, "disk cache writes absorbed=%,ld %d%%\n", stats.absorbedwrites, |
| percent(stats.absorbedwrites, stats.dirtydblocks)); |
| |
| hprint(hout, "disk cache flushes=%,ld\n", stats.dcacheflushes); |
| hprint(hout, "disk cache flush writes=%,ld (%,ld per flush)\n", |
| stats.dcacheflushwrites, |
| stats.dcacheflushwrites/(stats.dcacheflushes ? stats.dcacheflushes : 1)); |
| |
| hprint(hout, "disk writes=%,ld\n", stats.diskwrites); |
| hprint(hout, "disk bytes written=%,lld\n", stats.diskbwrites); |
| hprint(hout, "disk reads=%,ld\n", stats.diskreads); |
| hprint(hout, "disk bytes read=%,lld\n", stats.diskbreads); |
| */ |
| |
| hflush(hout); |
| return 0; |
| } |
| |
| static int |
| sindex(HConnect *c) |
| { |
| Hio *hout; |
| Index *ix; |
| Arena *arena; |
| vlong clumps, cclumps, uncsize, used, size; |
| int i, r, active; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| ix = mainindex; |
| |
| hprint(hout, "index=%s\n", ix->name); |
| |
| active = 0; |
| clumps = 0; |
| cclumps = 0; |
| uncsize = 0; |
| used = 0; |
| size = 0; |
| for(i = 0; i < ix->narenas; i++){ |
| arena = ix->arenas[i]; |
| if(arena != nil && arena->memstats.clumps != 0){ |
| active++; |
| clumps += arena->memstats.clumps; |
| cclumps += arena->memstats.cclumps; |
| uncsize += arena->memstats.uncsize; |
| used += arena->memstats.used; |
| } |
| size += arena->size; |
| } |
| hprint(hout, "total arenas=%,d active=%,d\n", ix->narenas, active); |
| hprint(hout, "total space=%,lld used=%,lld\n", size, used + clumps * ClumpInfoSize); |
| hprint(hout, "clumps=%,lld compressed clumps=%,lld data=%,lld compressed data=%,lld\n", |
| clumps, cclumps, uncsize, used - clumps * ClumpSize); |
| hflush(hout); |
| return 0; |
| } |
| |
| static void |
| darena(Hio *hout, Arena *arena) |
| { |
| hprint(hout, "arena='%s' on %s at [%lld,%lld)\n\tversion=%d created=%d modified=%d", |
| arena->name, arena->part->name, arena->base, arena->base + arena->size + 2 * arena->blocksize, |
| arena->version, arena->ctime, arena->wtime); |
| if(arena->memstats.sealed) |
| hprint(hout, " mem=sealed"); |
| if(arena->diskstats.sealed) |
| hprint(hout, " disk=sealed"); |
| if(arena->inqueue) |
| hprint(hout, " inqueue"); |
| hprint(hout, "\n"); |
| if(scorecmp(zeroscore, arena->score) != 0) |
| hprint(hout, "\tscore=%V\n", arena->score); |
| |
| hprint(hout, "\twritten: clumps=%d compressed clumps=%d data=%,lld compressed data=%,lld storage=%,lld\n", |
| arena->memstats.clumps, arena->memstats.cclumps, arena->memstats.uncsize, |
| arena->memstats.used - arena->memstats.clumps * ClumpSize, |
| arena->memstats.used + arena->memstats.clumps * ClumpInfoSize); |
| hprint(hout, "\tindexed: clumps=%d compressed clumps=%d data=%,lld compressed data=%,lld storage=%,lld\n", |
| arena->diskstats.clumps, arena->diskstats.cclumps, arena->diskstats.uncsize, |
| arena->diskstats.used - arena->diskstats.clumps * ClumpSize, |
| arena->diskstats.used + arena->diskstats.clumps * ClumpInfoSize); |
| } |
| |
| static int |
| hempty(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| emptylumpcache(); |
| emptydcache(); |
| emptyicache(); |
| hprint(hout, "emptied all caches\n"); |
| hflush(hout); |
| return 0; |
| } |
| |
| static int |
| hlcacheempty(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| emptylumpcache(); |
| hprint(hout, "emptied lumpcache\n"); |
| hflush(hout); |
| return 0; |
| } |
| |
| static int |
| hicacheempty(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| emptyicache(); |
| hprint(hout, "emptied icache\n"); |
| hflush(hout); |
| return 0; |
| } |
| |
| static int |
| hdcacheempty(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| emptydcache(); |
| hprint(hout, "emptied dcache\n"); |
| hflush(hout); |
| return 0; |
| } |
| static int |
| hicachekick(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| kickicache(); |
| hprint(hout, "kicked icache\n"); |
| hflush(hout); |
| return 0; |
| } |
| |
| static int |
| hdcachekick(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| kickdcache(); |
| hprint(hout, "kicked dcache\n"); |
| hflush(hout); |
| return 0; |
| } |
| static int |
| hicacheflush(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| flushicache(); |
| hprint(hout, "flushed icache\n"); |
| hflush(hout); |
| return 0; |
| } |
| |
| static int |
| hdcacheflush(HConnect *c) |
| { |
| Hio *hout; |
| int r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| flushdcache(); |
| hprint(hout, "flushed dcache\n"); |
| hflush(hout); |
| return 0; |
| } |
| |
| static int |
| dindex(HConnect *c) |
| { |
| Hio *hout; |
| Index *ix; |
| int i, r; |
| |
| r = hsettext(c); |
| if(r < 0) |
| return r; |
| hout = &c->hout; |
| |
| |
| ix = mainindex; |
| hprint(hout, "index=%s version=%d blocksize=%d tabsize=%d\n", |
| ix->name, ix->version, ix->blocksize, ix->tabsize); |
| hprint(hout, "\tbuckets=%d div=%d\n", ix->buckets, ix->div); |
| for(i = 0; i < ix->nsects; i++) |
| hprint(hout, "\tsect=%s for buckets [%lld,%lld) buckmax=%d\n", ix->smap[i].name, ix->smap[i].start, ix->smap[i].stop, ix->sects[i]->buckmax); |
| for(i = 0; i < ix->narenas; i++){ |
| if(ix->arenas[i] != nil && ix->arenas[i]->memstats.clumps != 0){ |
| hprint(hout, "arena=%s at index [%lld,%lld)\n\t", ix->amap[i].name, ix->amap[i].start, ix->amap[i].stop); |
| darena(hout, ix->arenas[i]); |
| } |
| } |
| hflush(hout); |
| return 0; |
| } |
| |
| typedef struct Arg Arg; |
| struct Arg |
| { |
| int index; |
| int index2; |
| }; |
| |
| static long |
| rawgraph(Stats *s, Stats *t, void *va) |
| { |
| Arg *a; |
| |
| USED(s); |
| a = va; |
| return t->n[a->index]; |
| } |
| |
| static long |
| diffgraph(Stats *s, Stats *t, void *va) |
| { |
| Arg *a; |
| |
| a = va; |
| return t->n[a->index] - s->n[a->index]; |
| } |
| |
| static long |
| pctgraph(Stats *s, Stats *t, void *va) |
| { |
| Arg *a; |
| |
| USED(s); |
| a = va; |
| return percent(t->n[a->index], t->n[a->index2]); |
| } |
| |
| static long |
| pctdiffgraph(Stats *s, Stats *t, void *va) |
| { |
| Arg *a; |
| |
| a = va; |
| return percent(t->n[a->index]-s->n[a->index], t->n[a->index2]-s->n[a->index2]); |
| } |
| |
| static long |
| xdiv(long a, long b) |
| { |
| if(b == 0) |
| b++; |
| return a/b; |
| } |
| |
| static long |
| divdiffgraph(Stats *s, Stats *t, void *va) |
| { |
| Arg *a; |
| |
| a = va; |
| return xdiv(t->n[a->index] - s->n[a->index], t->n[a->index2] - s->n[a->index2]); |
| } |
| |
| static long |
| netbw(Stats *s) |
| { |
| ulong *n; |
| |
| n = s->n; |
| return n[StatRpcReadBytes]+n[StatRpcWriteBytes]; /* not exactly right */ |
| } |
| |
| static long |
| diskbw(Stats *s) |
| { |
| ulong *n; |
| |
| n = s->n; |
| return n[StatApartReadBytes]+n[StatApartWriteBytes] |
| + n[StatIsectReadBytes]+n[StatIsectWriteBytes] |
| + n[StatSumReadBytes]; |
| } |
| |
| static long |
| iobw(Stats *s) |
| { |
| return netbw(s)+diskbw(s); |
| } |
| |
| static long |
| diskgraph(Stats *s, Stats *t, void *va) |
| { |
| USED(va); |
| return diskbw(t)-diskbw(s); |
| } |
| |
| static long |
| netgraph(Stats *s, Stats *t, void *va) |
| { |
| USED(va); |
| return netbw(t)-netbw(s); |
| } |
| |
| static long |
| iograph(Stats *s, Stats *t, void *va) |
| { |
| USED(va); |
| return iobw(t)-iobw(s); |
| } |
| |
| |
| static char* graphname[] = |
| { |
| "rpctotal", |
| "rpcread", |
| "rpcreadok", |
| "rpcreadfail", |
| "rpcreadbyte", |
| "rpcreadtime", |
| "rpcreadcached", |
| "rpcreadcachedtime", |
| "rpcreaduncached", |
| "rpcreaduncachedtime", |
| "rpcwrite", |
| "rpcwritenew", |
| "rpcwriteold", |
| "rpcwritefail", |
| "rpcwritebyte", |
| "rpcwritetime", |
| "rpcwritenewtime", |
| "rpcwriteoldtime", |
| |
| "lcachehit", |
| "lcachemiss", |
| "lcachelookup", |
| "lcachewrite", |
| "lcachesize", |
| "lcachestall", |
| "lcachelookuptime", |
| |
| "dcachehit", |
| "dcachemiss", |
| "dcachelookup", |
| "dcacheread", |
| "dcachewrite", |
| "dcachedirty", |
| "dcachesize", |
| "dcacheflush", |
| "dcachestall", |
| "dcachelookuptime", |
| |
| "dblockstall", |
| "lumpstall", |
| |
| "icachehit", |
| "icachemiss", |
| "icacheread", |
| "icachewrite", |
| "icachefill", |
| "icacheprefetch", |
| "icachedirty", |
| "icachesize", |
| "icacheflush", |
| "icachestall", |
| "icachelookuptime", |
| "icachelookup", |
| "scachehit", |
| "scacheprefetch", |
| |
| "bloomhit", |
| "bloommiss", |
| "bloomfalsemiss", |
| "bloomlookup", |
| "bloomones", |
| "bloombits", |
| |
| "apartread", |
| "apartreadbyte", |
| "apartwrite", |
| "apartwritebyte", |
| |
| "isectread", |
| "isectreadbyte", |
| "isectwrite", |
| "isectwritebyte", |
| |
| "sumread", |
| "sumreadbyte", |
| |
| "cigload", |
| "cigloadtime", |
| }; |
| |
| static int |
| findname(char *s) |
| { |
| int i; |
| |
| for(i=0; i<nelem(graphname); i++) |
| if(strcmp(graphname[i], s) == 0) |
| return i; |
| return -1; |
| } |
| |
| static void |
| dotextbin(Hio *io, Graph *g) |
| { |
| int i, nbin; |
| Statbin *b, bin[2000]; /* 32 kB, but whack is worse */ |
| |
| needstack(8192); /* double check that bin didn't kill us */ |
| nbin = 100; |
| binstats(g->fn, g->arg, g->t0, g->t1, bin, nbin); |
| |
| hprint(io, "stats\n\n"); |
| for(i=0; i<nbin; i++){ |
| b = &bin[i]; |
| hprint(io, "%d: nsamp=%d min=%d max=%d avg=%d\n", |
| i, b->nsamp, b->min, b->max, b->avg); |
| } |
| } |
| |
| static int |
| xgraph(HConnect *c) |
| { |
| char *name; |
| Hio *hout; |
| Memimage *m; |
| int dotext; |
| Graph g; |
| Arg arg; |
| char *graph, *a; |
| |
| name = hargstr(c, "arg", ""); |
| if((arg.index = findname(name)) == -1 && strcmp(name, "*") != 0){ |
| werrstr("unknown name %s", name); |
| goto error; |
| } |
| a = hargstr(c, "arg2", ""); |
| if(a[0] && (arg.index2 = findname(a)) == -1){ |
| werrstr("unknown name %s", a); |
| goto error; |
| } |
| |
| g.arg = &arg; |
| g.t0 = hargint(c, "t0", -120); |
| g.t1 = hargint(c, "t1", 0); |
| g.min = hargint(c, "min", -1); |
| g.max = hargint(c, "max", -1); |
| g.wid = hargint(c, "wid", -1); |
| g.ht = hargint(c, "ht", -1); |
| dotext = hargstr(c, "text", "")[0] != 0; |
| g.fill = hargint(c, "fill", -1); |
| |
| graph = hargstr(c, "graph", "raw"); |
| if(strcmp(graph, "raw") == 0) |
| g.fn = rawgraph; |
| else if(strcmp(graph, "diskbw") == 0) |
| g.fn = diskgraph; |
| else if(strcmp(graph, "iobw") == 0) |
| g.fn = iograph; |
| else if(strcmp(graph, "netbw") == 0) |
| g.fn = netgraph; |
| else if(strcmp(graph, "diff") == 0) |
| g.fn = diffgraph; |
| else if(strcmp(graph, "pct") == 0) |
| g.fn = pctgraph; |
| else if(strcmp(graph, "pctdiff") == 0) |
| g.fn = pctdiffgraph; |
| else if(strcmp(graph, "divdiff") == 0) |
| g.fn = divdiffgraph; |
| else{ |
| werrstr("unknown graph %s", graph); |
| goto error; |
| } |
| |
| if(dotext){ |
| hsettype(c, "text/plain"); |
| dotextbin(&c->hout, &g); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| m = statgraph(&g); |
| if(m == nil) |
| goto error; |
| |
| if(hsettype(c, "image/png") < 0) |
| return -1; |
| hout = &c->hout; |
| writepng(hout, m); |
| qlock(&memdrawlock); |
| freememimage(m); |
| qunlock(&memdrawlock); |
| hflush(hout); |
| return 0; |
| |
| error: |
| return herror(c); |
| } |
| |
| static int |
| xloglist(HConnect *c) |
| { |
| if(hsettype(c, "text/html") < 0) |
| return -1; |
| vtloghlist(&c->hout); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| static int |
| xlog(HConnect *c) |
| { |
| char *name; |
| VtLog *l; |
| |
| name = hargstr(c, "log", ""); |
| if(!name[0]) |
| return xloglist(c); |
| l = vtlogopen(name, 0); |
| if(l == nil) |
| return hnotfound(c); |
| if(hsettype(c, "text/html") < 0){ |
| vtlogclose(l); |
| return -1; |
| } |
| vtloghdump(&c->hout, l); |
| vtlogclose(l); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| static int |
| xindex(HConnect *c) |
| { |
| if(hsettype(c, "text/xml") < 0) |
| return -1; |
| xmlindex(&c->hout, mainindex, "index", 0); |
| hflush(&c->hout); |
| return 0; |
| } |
| |
| void |
| xmlindent(Hio *hout, int indent) |
| { |
| int i; |
| |
| for(i = 0; i < indent; i++) |
| hputc(hout, '\t'); |
| } |
| |
| void |
| xmlaname(Hio *hout, char *v, char *tag) |
| { |
| hprint(hout, " %s=\"%s\"", tag, v); |
| } |
| |
| void |
| xmlscore(Hio *hout, u8int *v, char *tag) |
| { |
| if(scorecmp(zeroscore, v) == 0) |
| return; |
| hprint(hout, " %s=\"%V\"", tag, v); |
| } |
| |
| void |
| xmlsealed(Hio *hout, int v, char *tag) |
| { |
| if(!v) |
| return; |
| hprint(hout, " %s=\"yes\"", tag); |
| } |
| |
| void |
| xmlu32int(Hio *hout, u32int v, char *tag) |
| { |
| hprint(hout, " %s=\"%ud\"", tag, v); |
| } |
| |
| void |
| xmlu64int(Hio *hout, u64int v, char *tag) |
| { |
| hprint(hout, " %s=\"%llud\"", tag, v); |
| } |
| |
| void |
| vtloghdump(Hio *h, VtLog *l) |
| { |
| int i; |
| VtLogChunk *c; |
| char *name; |
| |
| name = l ? l->name : "<nil>"; |
| |
| hprint(h, "<html><head>\n"); |
| hprint(h, "<title>Venti Server Log: %s</title>\n", name); |
| hprint(h, "</head><body>\n"); |
| hprint(h, "<b>Venti Server Log: %s</b>\n<p>\n", name); |
| |
| if(l){ |
| c = l->w; |
| for(i=0; i<l->nchunk; i++){ |
| if(++c == l->chunk+l->nchunk) |
| c = l->chunk; |
| hwrite(h, c->p, c->wp-c->p); |
| } |
| } |
| hprint(h, "</body></html>\n"); |
| } |
| |
| static int |
| strpcmp(const void *va, const void *vb) |
| { |
| return strcmp(*(char**)va, *(char**)vb); |
| } |
| |
| void |
| vtloghlist(Hio *h) |
| { |
| char **p; |
| int i, n; |
| |
| hprint(h, "<html><head>\n"); |
| hprint(h, "<title>Venti Server Logs</title>\n"); |
| hprint(h, "</head><body>\n"); |
| hprint(h, "<b>Venti Server Logs</b>\n<p>\n"); |
| |
| p = vtlognames(&n); |
| qsort(p, n, sizeof(p[0]), strpcmp); |
| for(i=0; i<n; i++) |
| hprint(h, "<a href=\"/log?log=%s\">%s</a><br>\n", p[i], p[i]); |
| vtfree(p); |
| hprint(h, "</body></html>\n"); |
| } |