shithub: riscv

ref: 88ccea37f65b87312292b2b1a107c39927c6a2c3
dir: /sys/src/cmd/venti/srv/httpd.c/

View raw version
#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");
}