#include	"dat.h"
#include	"fns.h"
#include	"error.h"
#include	"interp.h"

enum
{
	Qdir,
	Qctl,
	Qstate,
	Qsum,
	Qevent,
	Qprof,
	Qheap,
	Qgc
};

static
Dirtab memdir[] =
{
	".",			{Qdir, 0, QTDIR},	0,	DMDIR|0555,
	"memctl",		{Qctl},	0,			0666,
	"memstate",	{Qstate},	0,			0444,
	"memsum",	{Qsum},	0,			0444,
	"memevent",	{Qevent},	0,			0444,
	"memprof",	{Qprof},	0,			0444,
	"memheap",	{Qheap},	0,			0444,
	"memgc",		{Qgc},	0,			0444,
};

enum
{
	/* these are the top two bits of size */
	Pflags=	3<<30,
	  Pfree=	0<<30,
	  Palloc=	1<<30,
	  Paend=	2<<30,
	  Pimmutable=	3<<30,

	Npool = 3,
	Nevent = 10000,
	Nstate = 12000
};

/*
 * pool snapshots
 */
typedef struct Pstate Pstate;
struct Pstate
{
	ulong	base;
	ulong	size;
};

static struct
{
	Pstate	state[3+Nstate];
	Pstate*	lim;
	Pstate*	ptr;
	int	summary;
} poolstate[Npool];
static Ref	stateopen;

/*
 * pool/heap allocation events
 */
typedef struct Pevent Pevent;
struct Pevent
{
	int	pool;
	ulong	pc;
	ulong	base;
	ulong	size;
};

static struct
{
	Lock	l;
	Ref	inuse;
	Rendez	r;
	int	open;
	Pevent	events[Nevent];
	int	rd;
	int	wr;
	int	full;
	int	want;
	ulong	lost;
} poolevents;

/*
 * allocation profiles
 */
typedef struct Pprof Pprof;
typedef struct Pbucket Pbucket;

struct Pbucket
{
	ulong	val;
	ulong	pool;
	ulong	count;
	ulong	size;
	Pbucket*	next;
};

static struct {
	Ref	inuse;	/* only one of these things */
	Lock	l;
	Pbucket	buckets[1000];
	Pbucket	snap[1000];
	int	used;
	int	snapped;
	Pbucket*	hash[128];
	ulong	lost;
} memprof;

extern	void	(*memmonitor)(int, ulong, ulong, ulong);
extern	ulong	gcnruns;
extern	ulong	gcsweeps;
extern	ulong	gcbroken;
extern	ulong	gchalted;
extern	ulong	gcepochs;
extern	uvlong	gcdestroys;
extern	uvlong	gcinspects;
extern	uvlong	gcbusy;
extern	uvlong	gcidle;
extern	uvlong	gcidlepass;
extern	uvlong	gcpartial;


static void
mprofreset(void)
{
	lock(&memprof.l);	/* need ilock in kernel */
	memset(memprof.hash, 0, sizeof(memprof.hash));
	memprof.used = 0;
	memprof.lost = 0;
	unlock(&memprof.l);
}

static void
mprofmonitor(int pool, ulong pc, ulong base, ulong size)
{
	Pbucket **h0, **h, *p;

	if((pool&7) == 1)
		return;	/* ignore heap */
	USED(base);
	h0 = &memprof.hash[((pc>>16)^(pc>>4))&(nelem(memprof.hash)-1)];
	lock(&memprof.l);
	for(h = h0; (p = *h) != nil; h = &p->next)
		if(p->val == pc && p->pool == pool){
			p->count++;
			p->size += size;
			*h = p->next;
			p->next = *h0;
			*h0 = p;
			unlock(&memprof.l);
			return;
		}
	if(memprof.used >= nelem(memprof.buckets)){
		memprof.lost++;
		unlock(&memprof.l);
		return;
	}
	p = &memprof.buckets[memprof.used++];
	p->val = pc;
	p->pool = pool;
	p->count = 1;
	p->size = size;
	p->next = *h0;
	*h0 = p;
	unlock(&memprof.l);
}

static void
_memmonitor(int pool, ulong pc, ulong base, ulong size)
{
	Pevent e;

	e.pool = pool;
	e.pc = pc;
	e.base = base;
	e.size = size;
	lock(&poolevents.l);
	if(!poolevents.full){
		poolevents.events[poolevents.wr] = e;
		if(++poolevents.wr == nelem(poolevents.events))
			poolevents.wr = 0;
		if(poolevents.wr == poolevents.rd)
			poolevents.full = 1;
	}else
		poolevents.lost++;
	if(poolevents.want){
		poolevents.want = 0;
		Wakeup(&poolevents.r);
	}
	unlock(&poolevents.l);
}

static int
ismemdata(void *v)
{
	USED(v);
	return poolevents.full || poolevents.rd != poolevents.wr;
}

static char*
memaudit(int pno, Bhdr *b)
{
	Pstate *p;

	if(pno >= Npool)
		return "too many pools for memaudit";
	if((p = poolstate[pno].ptr) == poolstate[pno].lim){
		if(b->magic == MAGIC_E)
			return nil;
		p = &poolstate[pno].state[1];
		if(b->magic == MAGIC_F)
			p++;
		p->base++;
		p->size += b->size;
		return nil;
	}
	poolstate[pno].ptr++;
	p->base = (ulong)b;
	p->size = b->size;
	switch(b->magic){
	case MAGIC_A:
		p->size |= Palloc;
		break;
	case MAGIC_F:
		p->size |= Pfree;
		break;
	case MAGIC_E:
		p->size = b->csize | Paend;
		break;
	case MAGIC_I:
		p->size |= Pimmutable;
		break;
	default:
		return "bad magic number in block";
	}
	return nil;
}

static void
mput4(uchar *m, ulong v)
{
	m[0] = v>>24;
	m[1] = v>>16;
	m[2] = v>>8;
	m[3] = v;
}

static Chan*
memattach(char *spec)
{
	return devattach('%', spec);
}

static Walkqid*
memwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, memdir, nelem(memdir), devgen);
}

static int
memstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, memdir, nelem(memdir), devgen);
}

static Chan*
memopen(Chan *c, int omode)
{
	if(memmonitor != nil && c->qid.path != Qgc)
		error(Einuse);
	c = devopen(c, omode, memdir, nelem(memdir), devgen);
	switch((ulong)c->qid.path){
	case Qevent:
		if(incref(&poolevents.inuse) != 1){
			decref(&poolevents.inuse);
			c->flag &= ~COPEN;
			error(Einuse);
		}
		poolevents.rd = poolevents.wr = 0;
		poolevents.full = 0;
		poolevents.want = 0;
		poolevents.lost = 0;
		memmonitor = _memmonitor;
		poolevents.open = 1;
		break;
	case Qstate:
		if(incref(&stateopen) != 1){
			decref(&stateopen);
			c->flag &= ~COPEN;
			error(Einuse);
		}
		break;
	case Qprof:
		if(incref(&memprof.inuse) != 1){
			decref(&memprof.inuse);
			c->flag &= ~COPEN;
			error(Einuse);
		}
		memmonitor = mprofmonitor;
		break;
	}
	return c;
}

static void
memclose(Chan *c)
{
	if((c->flag & COPEN) == 0)
		return;
	switch((ulong)c->qid.path) {
	case Qevent:
		memmonitor = nil;
		poolevents.open = 0;
		decref(&poolevents.inuse);
		break;
	case Qstate:
		decref(&stateopen);
		break;
	case Qprof:
		decref(&memprof.inuse);
		memmonitor = nil;
		break;
	}

}

static long
memread(Chan *c, void *va, long count, vlong offset)
{
	uchar *m;
	char *e, *s;
	int i, summary;
	long n, nr;
	Pstate *p;
	Pevent pe;
	Pbucket *b;

	if(c->qid.type & QTDIR)
		return devdirread(c, va, count, memdir, nelem(memdir), devgen);

	summary = 0;
	switch((ulong)c->qid.path) {
	default:
		error(Egreg);
	case Qctl:
		return 0;
	case Qsum:
		summary = 1;
		/* fall through */
	case Qstate:
		if(offset == 0){
			for(i=0; i<Npool; i++){
				poolstate[i].ptr = &poolstate[i].state[3];
				poolstate[i].lim = poolstate[i].ptr + Nstate;
				memset(poolstate[i].state, 0, sizeof(poolstate[i].state));
				poolstate[i].summary = summary;
			}
			e = poolaudit(memaudit);
			if(e != nil){
				print("mem: %s\n", e);
				error(e);
			}
		}
		m = va;
		nr = offset/8;
		for(i=0; i<Npool && count >= 8; i++){
			n = poolstate[i].ptr - poolstate[i].state;
			poolstate[i].state[0].base = i;
			poolstate[i].state[0].size = n;
			if(nr >= n){
				nr -= n;
				continue;
			}
			n -= nr;
			p = &poolstate[i].state[nr];
			for(; --n >= 0 && (count -= 8) >= 0; m += 8, p++){
				mput4(m, p->base);
				mput4(m+4, p->size);
			}
		}
		return m-(uchar*)va;
	case Qevent:
		while(!ismemdata(nil)){
			poolevents.want = 1;
			Sleep(&poolevents.r, ismemdata, nil);
		}
		m = va;
		do{
			if((count -= 4*4) < 0)
				return m-(uchar*)va;
			pe = poolevents.events[poolevents.rd];
			mput4(m, pe.pool);
			mput4(m+4, pe.pc);
			mput4(m+8, pe.base);
			mput4(m+12, pe.size);
			m += 4*4;
			if(++poolevents.rd >= nelem(poolevents.events))
				poolevents.rd = 0;
		}while(poolevents.rd != poolevents.wr);
		poolevents.full = 0;
		return m-(uchar*)va;
	case Qprof:
		if(offset == 0){
			lock(&memprof.l);
			memmove(memprof.snap, memprof.buckets, memprof.used*sizeof(memprof.buckets[0]));
			memprof.snapped = memprof.used;
			unlock(&memprof.l);
		}
		m = va;
		for(i = offset/(4*4); i < memprof.snapped && (count -= 4*4) >= 0; i++){
			b = &memprof.snap[i];
			mput4(m, b->pool);
			mput4(m+4, b->val);
			mput4(m+8, b->count);
			mput4(m+12, b->size);
			m += 4*4;
		}
		return m-(uchar*)va;
	case Qgc:
		s = malloc(READSTR);
		if(s == nil)
			error(Enomem);
		if(waserror()){
			free(s);
			nexterror();
		}
		snprint(s, READSTR, "runs: %lud\nsweeps: %lud\nbchain: %lud\nhalted: %lud\nepochs: %lud\ndestroy: %llud\ninspects: %llud\nbusy: %llud\nidle: %llud\nidlepass: %llud\npartial: %llud\n",
			gcnruns, gcsweeps, gcbroken, gchalted, gcepochs, gcdestroys, gcinspects, gcbusy, gcidle, gcidlepass, gcpartial);
		count = readstr(offset, va, count, s);
		poperror();
		free(s);
		return count;
	}
}

static long
memwrite(Chan *c, void *va, long count, vlong offset)
{
	USED(offset);
	USED(count);
	USED(va);

	if(c->qid.type & QTDIR)
		error(Eperm);

	switch((ulong)c->qid.path) {
	default:
		error(Egreg);
	case Qctl:
		error(Ebadarg);
	case Qstate:
		error(Eperm);
	case Qprof:
		mprofreset();
		break;
	}
	return 0;
}

Dev memdevtab = {
	'%',
	"mem",

	devinit,
	memattach,
	memwalk,
	memstat,
	memopen,
	devcreate,
	memclose,
	memread,
	devbread,
	memwrite,
	devbwrite,
	devremove,
	devwstat
};
