Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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,
        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");
        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 : "&lt;nil&gt;";

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