text: #include "u.h"
  #include "../port/lib.h"
  #include "mem.h"
  #include "dat.h"
  #include "fns.h"
  #include "io.h"
  #include "../port/error.h"
  #include <interp.h>
  #include <isa.h>
  #include "../ip/kernel.h"
  
  /* Builtin module support */
  #include "bench.h"
  #include "benchmod.h"
  
  typedef enum { None, Calibrate, Base, Op, Intr, Dis, Gc, MS2T};
  static struct {
  	int		inuse;	/* reference count */
  	int		test;
  	void*	scratch;
  	char*	buf;
  	int		bufsz;
  	char*	wpos;
  	void		(*op)(void);
  	vlong	tickstart;
  } bench;
  
  ulong (*ts32)(void) = rdtsc32;
  void (*ts)(vlong *) = rdtsc;
  
  static void
  log(char *msg, ...)
  {
  	va_list ap;
  
  	va_start(ap, msg);
  	bench.wpos = doprint(bench.wpos, bench.buf+bench.bufsz, msg, ap);
  	va_end(ap);
  }
  
  void
  elog(char *msg, ...)
  {
  	va_list ap;
  
  	if(bench.buf == 0)
  		return;
  	va_start(ap, msg);
  	bench.wpos = doprint(bench.wpos, bench.buf+bench.bufsz, msg, ap);
  	va_end(ap);
  }
  
  static void
  clear(void)
  {
  	bench.wpos = bench.buf;
  }
  
  static long
  rep(void *to, long n, ulong offset)
  {
  	long left = bench.wpos - bench.buf - offset;
  	if(left < 0)
  		left = 0;
  	if(n > left)
  		n = left;
  	memmove(to, bench.buf+offset, n);
  	return n;
  }
  
  static long
  notest(int report, void *va, long n, ulong offset)
  {
  	USED(report, va, n, offset);
  	if(report)
  		return rep(va, n, offset);
  	return 0;
  }
  
  // Calibration
  static long MS2TS = 0;	// time stamps per millisec
  static long US2TS = 0;	// time stamps per microsecond
  
  static long
  cal(int report, void *va, long n, ulong offset)
  {
  	int tot, i, lim, low, max, pl, mdelay;
  	ulong t;
  	if(report)
  		return rep(va, n, offset);
  	clear();
  	setpri(PriRealtime);
  	lim = 1000;
  	low = 64000000;
  	max = 0;
  	tot = 0;
  	mdelay = 1000;
  	for(i=0; i<lim; i++){
  		do{
  			pl = splhi();
  			t = (*ts32)();
  			microdelay(mdelay);
  			t = (*ts32)() - t;
  			splx(pl);
  		} while(t < 0);
  		if(t < low)
  			low = t;
  		if(t > max)
  			max = t;
  		tot += t;
  	}
  	MS2TS = tot/lim;
  	US2TS = MS2TS/1000;
  	if(va)
  		log("loopconst=%lud mdelay=%lud lim=%lud tot=%lud low=%lud max=%lud\n", m->loopconst, mdelay, lim, tot, low, max);
  	setpri(PriNormal);
  	return n;
  }
  
  /*
   * ticks to format string
   */
  static char *
  ts2str(vlong ticks)
  {
  #define Nbuf 5
  	static char b[Nbuf][40];
  	static int n=Nbuf-1;
  	char *fmt, *unit;
  	double d;
  
  	n = (n+1)%Nbuf;
  	if(ticks > MS2TS*1000) {
  		fmt = "%.2f %s";
  		unit = "s";
  		d = ((double)ticks/MS2TS) * 1000.0;
  	} else if(ticks > MS2TS) {
  		fmt = "%.2f %s";
  		unit = "ms";
  		d = (double)ticks/MS2TS;
  	} else if(ticks > MS2TS/1000) {
  		fmt = "%.2f %s";
  		unit = "us";
  		d = ((double)ticks*1000)/MS2TS;
  	} else {
  		fmt = "%.f %s";
  		unit = "ns";
  		d = ((double)ticks*1000*1000)/MS2TS;
  	}
  	sprint(b[n], fmt, d, unit);
  	return b[n];
  }
  
  /*
   * ticks to microseconds
   */
  static double
  ts2us(vlong ticks)
  {
  	return ((double)ticks*1000)/MS2TS;
  }
  
  /*
   * microseconds timestamp
   */
  static vlong
  bus(int reset)
  {
  	vlong now;
  	if(US2TS == 0)
  		return 0;
  	if(reset) {
  		(*ts)(&bench.tickstart);
  		return 0;
  	}
  	(*ts)(&now);
  	return ((now-bench.tickstart))/US2TS;
  }
  
  // Base
  static long
  base(int report, void *va, long n, ulong offset)
  {
  	int tot, i, lim, low, max, pl;
  	ulong t;
  	char *bm;
  
  	if(report)
  		return rep(va, n, offset);
  	clear();
  	setpri(PriRealtime);
  	lim = 1000;
  	low = 64000000;
  	max = 0;
  	tot = 0;
  	for(i=0; i<lim; i++){
  		do {
  			pl = splhi();
  			t = (*ts32)();
  			// do nothing
  			t = (*ts32)() - t;
  			splx(pl);
  		} while(t < 0);
  		if(t < low)
  			low = t;
  		if(t > max)
  			max = t;
  		tot += t;
  	}
  	bm = ts2str(tot/lim);
  	log("%d %lud %lud %lud %lud (%s)\n", up->pid, lim, tot, low, max, bm);
  	setpri(PriNormal);
  	return n;
  }
  
  // Timeop
  
  typedef struct Psync Psync;
  
  enum {
  	Maxprocs=3,
  };
  
  struct Psync {
  	Rendez	r;
  	int	flag;
  	int	id;
  	int	awaken;
  };
  static Psync timesync[Maxprocs];
  static Ref nactive;
  static Ref nbusy;
  static RWlock sync;
  
  static void
  nilop(void)
  {
  }
  
  static int
  timev(void *a)
  {
  	return *(int*)a;
  }
  
  static void
  timeop0(void *ap)
  {
  	int tot, i, lim, low, max;
  	ulong t;
  	Psync *ps;
  	char *bm;
  
  	ps = ap;
  	setpri(PriRealtime);
  	incref(&nactive);
  	sleep(&ps->r, timev, &ps->flag);
  	rlock(&sync);
  	lim = 1000;
  	low = 64000000;
  	max = 0;
  	tot = 0;
  	for(i=0; i<lim; i++){
  		do{
  			t = (*ts32)();
  			(*bench.op)();
  			t = (*ts32)() - t;
  		}while(t < 0);
  		if(t < low)
  			low = t;
  		if(t > max)
  			max = t;
  		tot += t;
  	}
  	bm = ts2str(tot/lim);
  	log("%d %lud %lud %lud %lud (%s)\n", up->pid, lim, tot, low, max, bm);
  	runlock(&sync);
  	pexit("", 0);
  }
  
  static long
  timeop(int report, void *va, long n, ulong offset)
  {
  	int i, np, pl;
  
  	if(report)
  		return rep(va, n, offset);
  	clear();
  	bench.op = 0;
  	if(strncmp(va, "nil", 3) == 0)
  		bench.op = nilop;
  	else if(strncmp(va, "sched", 5) == 0)
  		bench.op = sched;
  	else
  		return 0;
  	for(np=1; np<=Maxprocs; np++) {
  		nactive.ref = 0;
  		wlock(&sync);
  		log("%d procs\n", np);
  		setpri(PriRealtime);
  		for(i=0; i<np; i++) {
  			timesync[i].id = i;
  			kproc("timeop", timeop0, &timesync[i], 0);
  		}
  		while(nactive.ref < np)
  			tsleep(&up->sleep, return0, 0, 20);
  		for(i=0; i<np; i++){
  			timesync[i].flag = 1;
  			wakeup(&timesync[i].r);
  		}
  		sched();
  		pl = splhi();
  		setpri(PriNormal);
  		wunlock(&sync);
  		// now they run
  		wlock(&sync);		// wait for last reader
  		wunlock(&sync);
  		splx(pl);
  	}
  	return n;
  }
  
  typedef struct Ictr Ictr;
  struct Ictr {
  	ulong	base;
  	ulong	sleep;
  	ulong	spllo;
  	ulong	intr;
  	ulong	isave;
  	ulong	arrive;
  	ulong	wakeup;
  	ulong	awake;
  };
  static Ictr counters[100], *curct;
  static int intrwant;
  static Rendez vous;
  int	spltbl;	/* set by spllo */
  int	intrtbl;	/* set by intrvec() */
  int	isavetbl;	/* set by intrvec() */
  
  static int ienable;
  
  
  static void
  intrwake(Ureg*, void*)
  {
  	if(ienable == 0)
  		return;
  	if(spltbl == 0)		// not used here
  		curct->spllo = curct->intr = curct->isave = (*ts32)();
  	else {
  		curct->spllo = spltbl;
  		curct->intr = intrtbl;
  		curct->isave = isavetbl;
  	}
  	curct->arrive = (*ts32)();
  	intrwant = 0;
  	wakeup(&vous);
  	curct->wakeup = (*ts32)();
  }
  
  /*
   * sleep calls intrtest with splhi (under lock):
   * provoke the interrupt now, so that it is guaranteed
   * not to happen until sleep has queued the process,
   * forcing wakeup to do something.
   */
  static int
  intrtest(void*)
  {
  	ienable = 1;		/* enable recording on interrupt */
  	curct->sleep = (*ts32)();
  	return intrwant==0;
  }
  
  static long
  intrtime(int report, void *va, long n, ulong offset)
  {
  	Ictr *ic;
  	long t;
  	int i;
  	char *bm;
  
  	if(report)
  		return rep(va, n, offset);
  	clear();
  
  	setpri(PriRealtime);
  	sched();
  	curct = counters;
  	ienable = 0;
  	intrenable(VectorCLOCK, intrwake, nil, BUSUNKNOWN);
  	for(i=0; i<nelem(counters); i++){
  		curct = &counters[i];
  		intrwant = 1;
  		curct->base = (*ts32)();
  		sleep(&vous, intrtest, nil);
  		curct->awake = (*ts32)();
  		sched();	/* just to slow it down between trials */
  	}
  	log("interrupt\n");
  	for(i=0; i</*20*/nelem(counters); i++){
  		ic = &counters[i];
  		t = ic->awake - ic->base;
  		bm = ts2str(ic->awake - ic->arrive);
  		ic->awake -= ic->wakeup;
  		ic->wakeup -= ic->arrive;
  		ic->arrive -= ic->isave;
  		ic->isave -= ic->intr;
  		ic->intr -= ic->spllo;
  		ic->spllo -= ic->sleep;
  		ic->sleep -= ic->base;
  		log("%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld (%s)\n", ic->sleep, ic->spllo, ic->intr, ic->isave, ic->arrive, ic->wakeup, ic->awake, t, bm);
  	}
  	setpri(PriNormal);
  	return n;
  }
  
  
  /* DIS operation timing */
  
  typedef struct  {
  	vlong	n;		/* count */
  	vlong	min;
  	vlong 	max;
  	vlong	sum;
  	vlong	sumsq;	/* sum of squares */
  } Stat;
  
  static void
  stat(enum { Reset, Inc } op, Stat *c, vlong val)
  {
  	switch(op) {
  	case	Reset:
  		c->n = 0;
  		c->sum = 0;
  		c->sumsq = 0;
  		c->min = 0;
  		c->max = 0;
  		break;
  	case Inc:
  		c->n++;
  		c->sum += val;
  		c->sumsq += val*val;
  		break;
  	}
  	if(val < c->min || c->n == 1)
  		c->min = val;
  	if(val > c->max || c->n == 1)
  		c->max = val;
  }
  
  static void
  statinc(Stat *d, Stat *s)
  {
  	d->n += s->n;
  	d->sum += s->sum;
  	d->sumsq += s->sumsq;
  	if(s->min < d->min || d->n == s->n)
  		d->min = s->min;
  	if(s->max > d->max || d->n == s->n)
  		d->max = s->max;
  }
  
  enum
  {
  	HSIZE	= 31,
  	MAXCOUNT	= 100000000L,
  };
  
  typedef struct {
  	int		op;
  	int		pc;
  	long		count;
  	Stat	t;		/* combined dec and execution time */
  } Istat;
  
  typedef struct Mstat Mstat;
  struct Mstat {
  	char	name[NAMELEN];
  	char*	path;
  	int		ninst;
  	Istat*	inst;
  	Inst*		base;
  	Mstat*	hash;
  	Mstat*	link;
  };
  
  struct
  {
  	Mstat*	hash[HSIZE];
  	Mstat*	list;
  } vmstat;
  
  extern struct			/* see ../../interp/tab.h:/keywds/ */
  {
  	char*	name;
  	int	op;
  	int	terminal;
  }keywds[];
  
  static char *
  opname(int op)
  {
  	char *name;
  
  	if(op < 0 || op >= MAXDIS)
  		return "Unknown";
  	return keywds[op].name;
  	if(name == 0)
  		name = "<Noname>";
  	return name;
  }
  
  static void
  mreset(void)
  {
  	Mstat *mp, *link;
  
  	for(mp=vmstat.list; mp; mp=link) {
  		link = mp->link;
  		free(mp->inst);
  		free(mp);
  	}
  	vmstat.list = 0;
  	memset(vmstat.hash, 0, HSIZE*sizeof(Mstat*));
  }
  
  static ulong
  hash(void *s, int len)
  {
  	ulong sum = 0;
  	uchar *a = s;
  
  	while(len-- > 0)
  		sum = (sum << 1) + *a++;
  	return sum%HSIZE;
  }
  
  static Mstat *
  mlookup(Module *mod)
  {
  	Mstat *m;
  	ulong h;
  
  	for(m=vmstat.hash[hash(mod->name, NAMELEN)]; m; m=m->hash)
  		if(strcmp(m->name, mod->name) == 0
  		&& strcmp(m->path, mod->path) == 0) {
  			return m;
  		}
  
  
  	m = malloc(sizeof(Mstat));
  	if(m == 0)
  		return 0;
  	memmove(m->name, mod->name, NAMELEN);
  	m->path = strdup(mod->path);
  	m->ninst = mod->nprog;
  	m->inst = malloc(m->ninst*sizeof(Istat));
  	if(m->path == 0 || m->inst == 0)
  		return 0;
  	m->base = mod->prog;
  	m->link = vmstat.list;
  	vmstat.list = m;
  	h = hash(m->name, NAMELEN);
  	m->hash = vmstat.hash[h];
  	vmstat.hash[h] = m;
  	return m;
  }
  
  /* interpreted code Dis timing */
  void
  bxec(Prog *p)
  {
  	int op, pc;
  	vlong t0, t;
  	Mstat*	ms;
  	Istat*	is;
  	Module *om;
  
  	R = p->R;
  	R.MP = R.M->MP;
  	R.IC = p->quanta;
  
  	if(p->kill != nil) {
  		char *m;
  		m = p->kill;
  		p->kill = nil;
  		error(m);
  	}
  
  	if(R.M->compiled)
  		comvec();
  	else {
  		om = 0;
  		ms = mlookup(R.M->m);
  		do {
  			op = R.PC->op;
  			pc = R.PC-R.M->prog;
  			if(om != R.M->m) {
  				om = R.M->m;
  				ms = mlookup(R.M->m);
  			}
  
  			(*ts)(&t0);
  			dec[R.PC->add]();
  			R.PC++;
  			optab[op]();
  			(*ts)(&t);
  			if(ms) {
  				is = &ms->inst[pc];
  				if(is->count < MAXCOUNT) {
  					if(is->count++ == 0) {
  						is->op = op;
  						is->pc = pc;
  					}
  					stat(Inc, &is->t, t-t0);
  				}
  			}
  			if(op==ISPAWN || op==IMSPAWN) {
  				Prog *new = delruntail(Pdebug);
  				new->xec = bxec;
  				addrun(new);
  			}
  		} while(--R.IC != 0);
  	}
  
  	p->R = R;
  }
  
  /* compiled code Dis timing */
  
  static struct {			/* compiled code timing */
  	int		set;
  	int 		op, pc;		/* Dis opcode and program counter */
  	vlong	t0, t;			/* time-in and time-out */
  	vlong	base;		/* cost of doing the timing */
  	Mstat	*ms;
  	Module	*om;
  	int		timing;		/* between "dis timer start" and stop */
  } C;
  
  enum { Nop = 0 };	/* opcode value for Dis NOP instruction */
  void
  dopostcomp(vlong t)
  {
  	Istat*	is;
  
  	C.t = t;
  	C.set = 0;
  	if(C.ms != 0) {
  		is = &C.ms->inst[C.pc];
  		if(C.op == Nop) {			/* NOP  calibration */
  			vlong newbase = C.t - C.t0;
  			if(C.base == 0 || newbase < C.base)
  				C.base = newbase;
  		}
  		if(is->count < MAXCOUNT) {
  			if(is->count++ == 0) {
  				is->op = C.op;
  				is->pc = C.pc;
  			}
  			stat(Inc, &is->t, C.t-C.t0/*-C.base*/);
  		}
  	}
  }
  
  void
  postcomp(void)
  {
  	vlong t;
  
  	(*ts)(&t);
  	if(C.timing == 0 || C.set == 0) {
  		return;
  	}
  	dopostcomp(t);
  }
  
  void
  precomp(void)
  {
  	vlong t;
  
  	(*ts)(&t);
  	if(C.timing == 0)
  		return;
  	if(C.set)
  		dopostcomp(t);
  	C.pc = *(ulong *)R.m;
  	C.op = *(ulong *)R.s;
  	if(C.om != R.M->m) {
  		C.om = R.M->m;
  		C.ms = mlookup(R.M->m);
  	}
  	C.set = 1;
  	(*ts)(&C.t0);
  }
  
  /* standard deviation */
  static vlong
  sdev(Stat *s)
  {
  	extern double sqrt(double);
  	vlong var;
  	var = s->sum;
  	var *= var/s->n;
  	var = (s->sumsq - var)/s->n;
  	return (vlong)sqrt((double)var);
  }
  
  /*
   * Use the sequence:
   * 1. "timer startclr" or "timer start", then,
   * 2. Any DIS operations, and,
   * 3. "timer stop", to stop timing.
   * 4. Read the results from the data file after:
   *	a) "timer report" to get module/pc level results, or
   *	b) "timer summary" to get opcode level results
   */
  static long
  distime(int report, void *va, long n, ulong offset)
  {
  	Prog *p;
  	Mstat *mp;
  	Istat *ip, *ep;
  
  	if(report)
  		return rep(va, n, offset);
  	clear();
  	acquire();
  	p = currun();
  	if(strncmp(va, "timer startclr", 14) == 0) {
  		mreset();
  		memset(&C, 0, sizeof(C));
  		C.timing = 1;
  		p->xec = bxec;
  	} else if(strncmp(va, "timer start", 11) == 0) {
  		p->xec = bxec;
  		C.timing = 1;
  	} else if(strncmp(va, "timer stop", 10) == 0) {
  		p->xec = xec;				/* bug: stop all xec threads */
  		C.timing = 0;
  	} else if(strncmp(va, "timer nilop", 11) == 0) {
  	} else if(strncmp(va, "timer report", 12) == 0)	/* by address */
  		for(mp=vmstat.list; mp; mp=mp->link) {
  			ep = mp->inst + mp->ninst;
  			for(ip=mp->inst; ip<ep; ip++)
  				if(ip->count > 0) {
  					char *mean = ts2str(ip->t.sum/ip->count);
  					char *min = ts2str(ip->t.min);
  					char *max = ts2str(ip->t.max);
  					char *std = ts2str(sdev(&ip->t));
  					log("%s %d %s %ld %s %s %s %s\n", mp->path, ip->pc, opname(ip->op), ip->count, mean, min, max, std);
  				}
  		}
  	else if(strncmp(va, "timer summary", 13) == 0) {	/* by opcode */
  		static Stat T[MAXDIS];
  		int i;
  
  		for(i=0; i<MAXDIS; i++)
  			stat(Reset, &T[i], 0);
  		for(mp=vmstat.list; mp; mp=mp->link) {
  			ep = mp->inst + mp->ninst;
  			for(ip=mp->inst; ip<ep; ip++)
  				if(ip->count > 0)
  					statinc(&T[ip->op], &ip->t);
  		}
  		for(i=0; i<MAXDIS; i++) {
  			Stat *t = &T[i];
  			char *mean = "0.00 ms";
  			char *min = "0.00 ms";
  			char *max = "0.00 ms";
  			char *std = "0.00 ms";
  			if(t->n > 0) {
  				mean = ts2str(t->sum/t->n);
  				min = ts2str(t->min);
  				max = ts2str(t->max);
  				std = ts2str(sdev(t));
  			}
  			log("%d %s %lld %s %s %s %s\n", i, opname(i), t->n, mean, min, max, std);
  		}
  	} else
  		n = 0;
  	R.IC = 1;
  	release();
  
  	return n;
  }
   
  /*
   * Garbage collection
   */
  static int nidle;
  
  int
  idlegc(void *p)
  {
  	int done;
  	Prog *head;
  	vlong t0, t1, tot;
  	USED(p);
  
  	head = progn(0);	/* isched.head */
  	done = gccolor + 3;
  	tot = 0;
  	while(gccolor < done && gcruns()) {
  		if(tready(nil))
  			break;
  		(*ts)(&t0);
  		rungc(head);
  		(*ts)(&t1);
  		t1 -= t0;
  		tot += t1;
  //		log(" %.2f",  ts2us(t1));
  	}
  	log(" %.2f",  ts2us(tot));
  	nidle--;
  	if(nidle == 0) {
  		log("\n");
  		return 1;
  	}
  	return 0;
  }
  
  static long
  gctime(int report, void *va, long n, ulong offset)
  {
  	int i;
  	vlong t0, t1;
  	Prog *head;
  
  	if(report)
  		return rep(va, n, offset);
  	clear();
  	acquire();
  	head = progn(0);	/* isched.head */
  /*
  	if(strncmp(va, "idle", 4) == 0) {
  		nidle = 100;
  		log("GCIDLE:1l:Observation:n:Time:us");
  		atidle(idlegc, 0);
  	} else if(strncmp(va, "stop", 4) == 0) {
  		atidledont(idlegc, 0);
  	} else 
  */
  	if(strncmp(va, "sched", 5) == 0) {
  		log("GCSCHED:1l:Observation:n:Time:us");
  		for(i=0; i<1000; i++) {
  			(*ts)(&t0);
  			rungc(head);
  			(*ts)(&t1);
  			log(" %.2f",  ts2us(t1-t0));
  			release();
  			acquire();
  		}
  		log("\n");
  	} else if(strncmp(va, "acquire", 7) == 0) {
  		log("GCACQUIRE:1l:Observation:n:Time:us");
  		for(i=0; i<1000; i++) {
  			(*ts)(&t0);
  			release();
  			acquire();
  			head = progn(0);	/* isched.head */
  			rungc(head);
  			release();
  			acquire();
  			(*ts)(&t1);
  			log(" %.2f",  ts2us(t1-t0));
  		}
  		log("\n");
  	}
  
  	release();
  
  	return n;
  }
  
  
  /* 
   * Request the number of time stamp ticks per millisecond
   */
  static long
  ms2ts(int report, void *va, long n, ulong offset)
  {
  	if(report)
  		return rep(va, n, offset);
  	log("%.ld\n", MS2TS);
  	return n;
  }
  
  /*
   * $Bench builtin support
   */
  void
  Bench_reset(void *)
  {
  	bus(1);
  }
  
  void
  Bench_microsec(void *fp)
  {
  	F_Bench_microsec *f;
  
  	f = fp;
  	*f->ret = bus(0);
  }
  
  void
  Bench_disablegc(void *)
  {
  	gclock();
  }
  
  void
  Bench_enablegc(void *)
  {
  	gcunlock();
  }
  
  
  #define fdchk(x)	((x) == (Bench_FD*)H ? -1 : (x)->fd)
  void
  Bench_read(void *fp)
  {
  	int n;
  	F_Bench_read *f;
  	extern ulong	clkrelinq;
  	vlong usrelease, uskread, usacquire, ussched;
  
  	f = fp;
  	n = f->n;
  	if(f->buf == (Array*)H) {
  		*f->ret = 0;
  		return;		
  	}
  	if(n > f->buf->len)
  		n = f->buf->len;
  
  	bus(1);
  	release();
  	usrelease = bus(0);
  	*f->ret = kread(fdchk(f->fd), f->buf->data, n);
  	uskread = bus(0);
  	acquire();
  	usacquire = bus(0);
  	sched();
  	ussched = bus(0);
  	log("%lld %lld %lld %lud %lld\n", usrelease, uskread, usacquire, m->ticks, ussched);
  }
  
  
  /*
   * driver support
   */
  long (*Test[])(int report, void *va, long n, ulong offset) = {
  	[None]		notest,
  	[Calibrate]	cal,
  	[Base]		base,
  	[Op]			timeop,
  	[Intr]			intrtime,
  	[Dis]			distime,
  	[Gc]			gctime,
  	[MS2T]		ms2ts,
  };
  
  enum {
  	Benchdirqid,
  	Benchdataqid,
  	Benchctlqid,
  	Benchusqid,
  	Nbenchtab=Benchusqid,
  	Benchmaxqid,
  };
  #define Data 0
  static Dirtab benchtab[Nbenchtab]={
  	"bdata",	{Benchdataqid},		0,	0444,
  	"bctl",	{Benchctlqid},		0,	0666,
  	"busec",	{Benchusqid},		0,	0666,
  };
  
  static void
  benchreset(void)
  {
  	builtinmod("$Bench", Benchmodtab);
  }
  
  static Chan*
  benchattach(char *spec)
  {
  	bench.inuse++;
  	if(bench.inuse == 1) {
  		bench.bufsz = 100*READSTR;
  		bench.buf = xalloc(bench.bufsz);
  		bench.wpos = bench.buf;
  		if(bench.buf == 0)
  			error(Enomem);
  		bench.test = None;
  		cal(0, 0, 0, 0);
  	}	
  	return devattach('x', spec);
  }
  
  void
  benchdetach(void)
  {
  	bench.inuse--;
  	if(bench.inuse == 0)
  		xfree(bench.buf);
  }
  
  static int
  benchwalk(Chan *c, char *name)
  {
  	return devwalk(c, name, benchtab, (long)Nbenchtab, devgen);
  }
  
  static Chan*
  benchopen(Chan *c, int omode)
  {
  	if(c->qid.path == CHDIR){
  		if(omode != OREAD)
  			error(Eperm);
  	}
  	c->mode = openmode(omode);
  	c->flag |= COPEN;
  	c->offset = 0;
  	return c;
  }
  
  static void
  benchstat(Chan *c, char *db)
  {
  	switch(c->qid.path & ~CHDIR){
  	case Benchdataqid:
  		benchtab[Data].length = bench.wpos - bench.buf;
  	}
  	devstat(c, db, benchtab, (long)Nbenchtab, devgen);
  }
  
  static void
  benchclose(Chan *c)
  {
  	USED(c);
  }
  
  static long	 
  benchread(Chan *c, void *buf, long n, ulong offset)
  {
  	vlong us;
  	char tmp[64];
  
  	switch(c->qid.path & ~CHDIR){
  	case Benchdirqid:
  		return devdirread(c, buf, n, benchtab, Nbenchtab, devgen);
  
  	case Benchdataqid:
  		return Test[bench.test](1, buf, n, offset);
  
  	case Benchusqid:
  		(*ts)(&us);
  		us /= US2TS;
  		snprint(tmp, sizeof(tmp), "%.lld", us);
  		return readstr(0, buf, n, tmp);
  	default:
  		n = 0;
  		break;
  	}
  	return n;
  }
  
  static long	 
  benchwrite(Chan *c, void *buf, long n, ulong offset)
  {
  	int argn = n;
  
  	switch((int)(c->qid.path&~CHDIR)){
  	case Benchctlqid:
  		bench.test = None;
  		memset((char *)bench.buf, 0, bench.bufsz);
  		bench.wpos = bench.buf;
  		if(strncmp(buf, "calibrate", 9) == 0)
  			bench.test = Calibrate;
  		else if(strncmp(buf, "base", 4) == 0)
  			bench.test = Base;
  		else if(strncmp(buf, "intr", 4) == 0)
  			bench.test = Intr;
  		else if(strncmp(buf, "op ", 3) == 0) {
  			bench.test = Op;
  			buf = (char *)buf + 3;
  			argn -= 3;
  		} else if(strncmp(buf, "dis ", 4) == 0) {
  			bench.test = Dis;
  			buf = (char *)buf + 4;
  			argn -= 4;
  		} else if(strncmp(buf, "gc ", 3) == 0) {
  			bench.test = Gc;
  			buf = (char *)buf + 3;
  			argn -= 3;
  		} else if(strncmp(buf, "ms2ts", 5) == 0)
  			bench.test = MS2T;
  		else
  			error(Ebadctl);
  		Test[bench.test](0, buf, argn, offset);
  		break;
  	case Benchusqid:
  		(*ts)(&bench.tickstart);
  		break;
  	default:
  		error(Ebadusefd);
  	}
  	return n;
  }
  
  Dev	benchdevtab = {
  	'x',
  	"bench",
  
  	benchreset,
  	devinit,
  	benchattach,
  	benchdetach,
  	devclone,
  	benchwalk,
  	benchstat,
  	benchopen,
  	devcreate,
  	benchclose,
  	benchread,
  	devbread,
  	benchwrite,
  	devbwrite,
  	devremove,
  	devwstat,
  
  };
  