#include <u.h>
#include <tos.h>
#include <libc.h>
#include <thread.h>
#include <ip.h>
#include <bio.h>
#include <draw.h>
#include <mouse.h>
#include <cursor.h>
#include <keyboard.h>
#include "trace.h"

#define	GBIT8(p)	((p)[0])
#define	GBIT16(p)	((p)[0]|((p)[1]<<8))
#define	GBIT32(p)	((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
#define	GBIT64(p)	((u32int)((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24)) |\
				((vlong)((p)[4]|((p)[5]<<8)|((p)[6]<<16)|((p)[7]<<24)) << 32))

#define	PBIT8(p,v)	(p)[0]=(v)
#define	PBIT16(p,v)	(p)[0]=(v);(p)[1]=(v)>>8
#define	PBIT32(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24
#define	PBIT64(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24;\
			(p)[4]=(v)>>32;(p)[5]=(v)>>40;(p)[6]=(v)>>48;(p)[7]=(v)>>56

#define	BIT8SZ		1
#define	BIT16SZ		2
#define	BIT32SZ		4
#define	BIT64SZ		8

#pragma	varargck	type	"t"		uvlong
#pragma	varargck	type	"t"		vlong
#pragma	varargck	type	"U"		uvlong

#define NS(x)	((vlong)x)
#define US(x)	(NS(x) * 1000ULL)
#define MS(x)	(US(x) * 1000ULL)
#define S(x)	(MS(x) * 1000ULL)

#define numblocks(a, b)	(((a) + (b) - 1) / (b))
#define roundup(a, b)	(numblocks((a), (b)) * (b))

enum {
	OneRound = MS(1)/2LL,
	MilliRound = US(1)/2LL,
};

typedef struct Event	Event;
typedef struct Task	Task;
struct Event {
	Traceevent;
	vlong	etime;	/* length of block to draw */
};

struct Task {
	int	pid;
	char	*name;
	int	nevents;	
	Event	*events;
	vlong	tstart;
	vlong	total;
	vlong	runtime;
	vlong	runmax;
	vlong	runthis;
	long	runs;
	ulong	tevents[Nevent];
};

enum {
	Nevents = 1024,
	Ncolor = 6,
	K = 1024,
};

vlong	now, prevts;

int	newwin;
int	Width = 1000;		
int	Height = 100;		// Per task
int	topmargin = 8;
int	bottommargin = 4;
int	lineht = 12;
int	wctlfd;
int	nevents;
Traceevent *eventbuf;
Event	*event;

void drawtrace(void);
int schedparse(char*, char*, char*);
int timeconv(Fmt*);
static void tracefile(int);

char *schedstatename[] = {
	[SAdmit] =	"Admit",
	[SSleep] =	"Sleep",
	[SDead] =	"Dead",
	[SDeadline] =	"Deadline",
	[SEdf] =	"Edf",
	[SExpel] =	"Expel",
	[SReady] =	"Ready",
	[SRelease] =	"Release",
	[SRun] =	"Run",
	[SSlice] =	"Slice",
	[SInts] =	"Ints",
	[SInte] =	"Inte",
	[SUser] = 	"User",
	[SYield] =	"Yield",
	[SLock] =	"Lock",
};

struct {
	vlong	scale;
	vlong	bigtics;
	vlong	littletics;
	int	sleep;
} scales[] = {
	{	US(500),	US(100),	US(50),		  0},
	{	US(1000),	US(500),	US(100),	  0},
	{	US(2000),	US(1000),	US(200),	  0},
	{	US(5000),	US(1000),	US(500),	  0},
	{	MS(10),		MS(5),		MS(1),		 20},
	{	MS(20),		MS(10),		MS(2),		 20},
	{	MS(50),		MS(10),		MS(5),		 20},
	{	MS(100),	MS(50),		MS(10),		 20},	/* starting scaleno */
	{	MS(200),	MS(100),	MS(20),		 20},
	{	MS(500),	MS(100),	MS(50),		 50},
	{	MS(1000),	MS(500),	MS(100),	100},
	{	MS(2000),	MS(1000),	MS(200),	100},
	{	MS(5000),	MS(1000),	MS(500),	100},
	{	S(10),		S(50),		S(1),		100},
	{	S(20),		S(10),		S(2),		100},
	{	S(50),		S(10),		S(5),		100},
	{	S(100),		S(50),		S(10),		100},
	{	S(200),		S(100),		S(20),		100},
	{	S(500),		S(100),		S(50),		100},
	{	S(1000),	S(500),		S(100),		100},
};

int ntasks, verbose, triggerproc, paused;
Task *tasks;
Image *cols[Ncolor][4];
Font *mediumfont, *tinyfont;
Image *grey, *red, *green, *blue, *bg, *fg;
char*profdev = "/proc/trace";

static void
usage(void)
{
	fprint(2, "Usage: %s [-f file [-g]] [-d profdev] [-w] [-v] [-t triggerproc] [processes]\n", argv0);
	exits(nil);
}

void
threadmain(int argc, char **argv)
{
	int fd, i, justfile, graph;
	char fname[80];

	fmtinstall('t', timeconv);
	justfile = graph = 0;
	ARGBEGIN {
	case 'f':
		justfile = 1;
		profdev = EARGF(usage());
		break;
	case 'g':
		graph = 1;
		break;
	case 'd':
		profdev = EARGF(usage());
		break;
	case 'v':
		verbose = 1;
		break;
	case 'w':
		newwin++;
		break;
	case 't':
		triggerproc = (int)strtol(EARGF(usage()), nil, 0);
		break;
	default:
		usage();
	}
	ARGEND;
	if(justfile){
		if(argc != 0)
		usage();
		tracefile(graph);
		exits(nil);
	}
	
	fname[sizeof fname - 1] = 0;
	for(i = 0; i < argc; i++){
		snprint(fname, sizeof fname - 2, "/proc/%s/ctl", 
					argv[i]);
		if((fd = open(fname, OWRITE)) < 0){
			fprint(2, "%s: cannot open %s: %r\n",
						argv[0], fname);
			continue;
		}

		if(fprint(fd, "trace 1") < 0)
			fprint(2, "%s: cannot enable tracing on %s: %r\n",
						argv[0], fname);
		close(fd);
	}

	drawtrace();
}

static
struct{
	int pid;
	int state;
}graphs[64];

static void
addtograph(Traceevent *t)
{
	int i, dead;

	dead = -1;
	for(i = 0; i < nelem(graphs); i++){
		if(graphs[i].pid == t->pid){
			/*
			 * dead procs might get some sleep/wakeup events, keep them dead.
			 */
			if(graphs[i].state == SDead)
				return;
			break;
		}
		if(graphs[i].state == SDead)
			dead = i;
		if(graphs[i].pid == 0 || i == nelem(graphs)-1){
			if(dead >= 0)
				i = dead;
			graphs[i].pid = t->pid;
			break;
		}
	}
	if(i == nelem(graphs))
		return;
	graphs[i].state = t->etype;
}

static void
printgraph(Biobuf *bout, int pid, int core, uvlong time, char *sname)
{
	int i;
	static char *schar[] = {
	[SAdmit] =	"!a",
	[SSleep] =	".s",
	[SDead] =	"xd",
	[SDeadline] =	"??",
	[SEdf] =	"??",
	[SExpel] =	"??",
	[SReady] =	"!r",
	[SRelease] =	"??",
	[SRun] =	"|R",
	[SSlice] =	"??",
	[SInts] =	"!i",
	[SInte] =	"|e",
	[SUser] = 	"|u",
	[SYield] =	"!y",
	[SLock] =	"!l",
	};

	Bprint(bout, "%20.20lld %02d %4d", time, core, pid);
	for(i = 0; i < nelem(graphs); i++){
		if(graphs[i].pid == 0)
			break;
		if(graphs[i].pid != pid && graphs[i].state == SDead)
			Bprint(bout, "\t ");
		else
			Bprint(bout, "\t%c", schar[graphs[i].state][0]);
		if(graphs[i].pid == pid)
			Bputc(bout, schar[graphs[i].state][1]);
	}
	Bprint(bout, "\t%s\n", sname);
}

static void
tracefile(int graph)
{
	int logfd;
	Traceevent t;
	Biobuf bout;
	uchar buf[BIT32SZ+BIT32SZ+BIT64SZ+BIT32SZ];
	uvlong t0;

	if((logfd = open(profdev, OREAD)) < 0)
		sysfatal("%s: open: %r", profdev);
	if(Binit(&bout, 1, OWRITE) < 0)
		sysfatal("stdout: Binit: %r");
	if(graph)
		Bprint(&bout, "#time               core pid  states\n");
	t0 = 0;
	while(read(logfd, buf, sizeof buf) == sizeof buf){
		t.pid = GBIT32(buf);
		t.etype = GBIT32(buf+BIT32SZ);
		t.time = GBIT64(buf+BIT32SZ+BIT32SZ);
		t.core = GBIT32(buf+BIT32SZ+BIT32SZ+BIT64SZ);
		if(t.pid == 0)
			continue;
		if(t.etype >= nelem(schedstatename) || schedstatename[t.etype] == nil){
			fprint(2, "unknown state %ud\n", t.etype);
			continue;
		}
		if(graph == 0)
			Bprint(&bout, "%ud\t%-10.10s\t%ulld\t%ud\n",
				t.pid, schedstatename[t.etype], t.time, t.core);
		else{
			addtograph(&t);
			if(t0 == 0)
				t0 = t.time;
			printgraph(&bout, t.pid, t.core, t.time-t0, schedstatename[t.etype]);
		}
	}
	Bterm(&bout);
	close(logfd);
}

static void
mkcol(int i, int c0, int c1, int c2)
{
	cols[i][0] = allocimagemix(display, c0, DWhite);
	cols[i][1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c1);
	cols[i][2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c2);
	cols[i][3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c0);
}

static void
colinit(void)
{
	mediumfont = openfont(display, "/lib/font/bit/lucidasans/unicode.10.font");
	if(mediumfont == nil)
		mediumfont = font;
	tinyfont = openfont(display, "/lib/font/bit/lucidasans/unicode.7.font");
	if(tinyfont == nil)
		tinyfont = font;
	topmargin = mediumfont->height+2;
	bottommargin = tinyfont->height+2;

	/* Peach */
	mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
	/* Aqua */
	mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
	/* Yellow */
	mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
	/* Green */
	mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
	/* Blue */
	mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
	/* Grey */
	cols[5][0] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xEEEEEEFF);
	cols[5][1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF);
	cols[5][2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x888888FF);
	cols[5][3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAAAAAAFF);
	grey = cols[5][2];
	red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFF0000FF);
	green = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x00FF00FF);
	blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x0000FFFF);
	bg = display->white;
	fg = display->black;
}

static void
redraw(int scaleno)
{
	int n, i, j, x;
	char buf[256];
	Point p, q;
	Rectangle r, rtime;
	Task *t;
	vlong ts, oldestts, newestts, period, ppp, scale, s, ss;

#	define time2x(t)	((int)(((t) - oldestts) / ppp))

	scale = scales[scaleno].scale;
	period = scale + scales[scaleno].littletics;
	ppp = period / Width;	// period per pixel.

	/* Round `now' to a nice number */
	newestts = now - (now % scales[scaleno].bigtics) + 
			(scales[scaleno].littletics>>1);

	oldestts = newestts - period;

//print("newestts %t, period %t, %d-%d\n", newestts, period, time2x(oldestts), time2x(newestts));
	if (prevts < oldestts){
		oldestts = newestts - period;

		prevts = oldestts;
		draw(screen, screen->r, bg, nil, ZP);
	}else{
		/* just white out time */
		rtime = screen->r;
		rtime.min.x = rtime.max.x - stringwidth(mediumfont, "00000000000.000s");
		rtime.max.y = rtime.min.y + mediumfont->height;
		draw(screen, rtime, bg, nil, ZP);
	}
	p = screen->r.min;
	for (n = 0; n != ntasks; n++) {
		t = &tasks[n];
		/* p is upper left corner for this task */
		rtime = Rpt(p, addpt(p, Pt(500, mediumfont->height)));
		draw(screen, rtime, bg, nil, ZP);
		snprint(buf, sizeof(buf), "%d %s", t->pid, t->name);
		q = string(screen, p, fg, ZP, mediumfont, buf);
		s = now - t->tstart;
		if(t->tevents[SRelease])
			snprint(buf, sizeof(buf), " per %t — avg: %t max: %t",
				(uvlong)(s/t->tevents[SRelease]),
				(uvlong)(t->runtime/t->tevents[SRelease]),
				t->runmax);
		else if((s /=1000000000LL) != 0)
			snprint(buf, sizeof(buf), " per 1s — avg: %t total: %t",
				t->total/s,
				t->total);
		else
			snprint(buf, sizeof(buf), " total: %t", t->total);
		string(screen, q, fg, ZP, tinyfont, buf);
		p.y += Height;
	}
	x = time2x(prevts);

	p = screen->r.min;
	for (n = 0; n != ntasks; n++) {
		t = &tasks[n];

		/* p is upper left corner for this task */

		/* Move part already drawn */
		r = Rect(p.x, p.y + topmargin, p.x + x, p.y+Height);
		draw(screen, r, screen, nil, Pt(p.x + Width - x, p.y + topmargin));

		r.max.x = screen->r.max.x;
		r.min.x += x;
		draw(screen, r, bg, nil, ZP);

		line(screen, addpt(p, Pt(x, Height - lineht)), Pt(screen->r.max.x, p.y + Height - lineht),
			Endsquare, Endsquare, 0, cols[n % Ncolor][1], ZP);

		for (i = 0; i < t->nevents-1; i++)
			if (prevts < t->events[i + 1].time)
				break;
			
		if (i > 0) {
			memmove(t->events, t->events + i, (t->nevents - i) * sizeof(Event));
			t->nevents -= i;
		}

		for (i = 0; i != t->nevents; i++) {
			Event *e = &t->events[i], *_e;
			int sx, ex;

			switch (e->etype & 0xffff) {
			case SAdmit:
				if (e->time > prevts && e->time <= newestts) {
					sx = time2x(e->time);
					line(screen, addpt(p, Pt(sx, topmargin)), 
						addpt(p, Pt(sx, Height - bottommargin)), 
						Endarrow, Endsquare, 1, green, ZP);
				}
				break;
			case SExpel:
				if (e->time > prevts && e->time <= newestts) {
					sx = time2x(e->time);
					line(screen, addpt(p, Pt(sx, topmargin)), 
						addpt(p, Pt(sx, Height - bottommargin)), 
						Endsquare, Endarrow, 1, red, ZP);
				}
				break;
			case SRelease:
				if (e->time > prevts && e->time <= newestts) {
					sx = time2x(e->time);
					line(screen, addpt(p, Pt(sx, topmargin)), 
						addpt(p, Pt(sx, Height - bottommargin)), 
						Endarrow, Endsquare, 1, fg, ZP);
				}
				break;
			case SDeadline:
				if (e->time > prevts && e->time <= newestts) {
					sx = time2x(e->time);
					line(screen, addpt(p, Pt(sx, topmargin)), 
						addpt(p, Pt(sx, Height - bottommargin)), 
						Endsquare, Endarrow, 1, fg, ZP);
				}
				break;

			case SYield:
			case SUser:
				if (e->time > prevts && e->time <= newestts) {
					sx = time2x(e->time);
					line(screen, addpt(p, Pt(sx, topmargin)), 
						addpt(p, Pt(sx, Height - bottommargin)), 
						Endsquare, Endarrow, 0, 
						(e->etype == SYield)? green: blue, ZP);
				}
				break;
			case SSlice:
				if (e->time > prevts && e->time <= newestts) {
					sx = time2x(e->time);
					line(screen, addpt(p, Pt(sx, topmargin)), 
						addpt(p, Pt(sx, Height - bottommargin)), 
						Endsquare, Endarrow, 0, red, ZP);
				}
				break;

			case SRun:
			case SEdf:
				sx = time2x(e->time);
				ex = time2x(e->etime);
				if(ex == sx)
					ex++;

				r = Rect(sx, topmargin + 8, ex, Height - lineht);
				r = rectaddpt(r, p);

				draw(screen, r, cols[n % Ncolor][e->etype==SRun?1:3], nil, ZP);

				if(t->pid == triggerproc && ex < Width)
					paused ^= 1;

				for(j = 0; j < t->nevents; j++){
					_e = &t->events[j];
					switch(_e->etype & 0xffff){
					case SInts:
						if (_e->time > prevts && _e->time <= newestts){
							sx = time2x(_e->time);
							line(screen, addpt(p, Pt(sx, topmargin)), 
												addpt(p, Pt(sx, Height / 2 - bottommargin)), 	
												Endsquare, Endsquare, 0, 
												green, ZP);
						}
						break;
					case SInte:
						if (_e->time > prevts && _e->time <= newestts) {
							sx = time2x(_e->time);
							line(screen, addpt(p, Pt(sx, Height / 2 - bottommargin)), 
												addpt(p, Pt(sx, Height - bottommargin)), 
												Endsquare, Endsquare, 0, 
												blue, ZP);
						}
						break;
					}
				}
				break;
			}
		}
		p.y += Height;
	}

	ts = prevts + scales[scaleno].littletics - (prevts % scales[scaleno].littletics);
	x = time2x(ts);

	while(x < Width){
		p = screen->r.min;
		for(n = 0; n < ntasks; n++){
			int height, width;

			/* p is upper left corner for this task */
			if ((ts % scales[scaleno].scale) == 0){
				height = 10 * Height;
				width = 1;
			}else if ((ts % scales[scaleno].bigtics) == 0){
				height = 12 * Height;
				width = 0;
			}else{
				height = 13 * Height;
				width = 0;
			}
			height >>= 4;

			line(screen, addpt(p, Pt(x, height)), addpt(p, Pt(x, Height - lineht)),
				Endsquare, Endsquare, width, cols[n % Ncolor][2], ZP);

			p.y += Height;
		}
		ts += scales[scaleno].littletics;
		x = time2x(ts);
	}

	rtime = screen->r;
	rtime.min.y = rtime.max.y - tinyfont->height + 2;
	draw(screen, rtime, bg, nil, ZP);
	ts = oldestts + scales[scaleno].bigtics - (oldestts % scales[scaleno].bigtics);
	x = time2x(ts);
	ss = 0;
	while(x < Width){
		snprint(buf, sizeof(buf), "%t", ss);
		string(screen, addpt(p, Pt(x - stringwidth(tinyfont, buf)/2, - tinyfont->height - 1)), 
			fg, ZP, tinyfont, buf);
		ts += scales[scaleno].bigtics;
		ss += scales[scaleno].bigtics;
		x = time2x(ts);
	}

	snprint(buf, sizeof(buf), "%t", now);
	string(screen, Pt(screen->r.max.x - stringwidth(mediumfont, buf), screen->r.min.y), 
		fg, ZP, mediumfont, buf);
	
	flushimage(display, 1);
	prevts = newestts;
}

Task*
newtask(ulong pid)
{
	Task *t;
	char buf[64], *p;
	int fd,n;

	tasks = realloc(tasks, (ntasks + 1) * sizeof(Task));
	assert(tasks);

	t = &tasks[ntasks++];
	memset(t, 0, sizeof(Task));
	t->events = nil;
	snprint(buf, sizeof buf, "/proc/%ld/status", pid);
	t->name = nil;
	fd = open(buf, OREAD);
	if (fd >= 0){
		n = read(fd, buf, sizeof buf);
		if(n > 0){
			p = buf + sizeof buf - 1;
			*p = 0;
			p = strchr(buf, ' ');
			if (p) *p = 0;
			t->name = strdup(buf);
		}else
			print("%s: %r\n", buf);
		close(fd);
	}else
		print("%s: %r\n", buf);
	t->pid = pid;
	prevts = 0;
	if (newwin){
		fprint(wctlfd, "resize -dx %d -dy %d\n",
			Width + 20, (ntasks * Height) + 5);
	}else
		Height = ntasks ? Dy(screen->r)/ntasks : Dy(screen->r);
	return t;
}

void
doevent(Task *t, Traceevent *ep)
{
	int i, n;
	Event *event;
	vlong runt;

	t->tevents[ep->etype & 0xffff]++;
	n = t->nevents++;
	t->events = realloc(t->events, t->nevents*sizeof(Event));
	assert(t->events);
	event = &t->events[n];
	memmove(event, ep, sizeof(Traceevent));
	event->etime = 0;

	switch(event->etype & 0xffff){
	case SRelease:
		if (t->runthis > t->runmax)
			t->runmax = t->runthis;
		t->runthis = 0;
		break;

	case SSleep:
	case SYield:
	case SReady:
	case SSlice:
		for(i = n-1; i >= 0; i--)
			if (t->events[i].etype == SRun || 
				t->events[i].etype == SEdf)
				break;
		if(i < 0 || t->events[i].etime != 0)
			break;
		runt = event->time - t->events[i].time;
		if(runt > 0){
			t->events[i].etime = event->time;
			t->runtime += runt;
			t->total += runt;
			t->runthis += runt;
			t->runs++;
		}
		break;
	case SDead:
print("task died %d %t %s\n", event->pid, event->time, schedstatename[event->etype & 0xffff]);
		free(t->events);
		free(t->name);
		ntasks--;
		memmove(t, t+1, sizeof(Task)*(&tasks[ntasks]-t));
		if (newwin)
			fprint(wctlfd, "resize -dx %d -dy %d\n",
				Width + 20, (ntasks * Height) + 5);
		else
			Height = ntasks ? Dy(screen->r)/ntasks : Dy(screen->r);
		prevts = 0;
	}
}

void
drawtrace(void)
{
	char *wsys, line[256];
	int wfd, logfd;
	Mousectl *mousectl;
	Keyboardctl *keyboardctl;
	int scaleno;
	Rune r;
	int i, n;
	Task *t;
	Traceevent *ep;

	eventbuf = malloc(Nevents*sizeof(Traceevent));
	assert(eventbuf);

	if((logfd = open(profdev, OREAD)) < 0)
		sysfatal("%s: Cannot open %s: %r", argv0, profdev);

	if(newwin){
		if((wsys = getenv("wsys")) == nil)
			sysfatal("%s: Cannot find windowing system: %r",
						argv0);
	
		if((wfd = open(wsys, ORDWR)) < 0)
			sysfatal("%s: Cannot open windowing system: %r",
						argv0);
	
		snprint(line, sizeof(line), "new -pid %d -dx %d -dy %d",
				getpid(), Width + 20, Height + 5);
		line[sizeof(line) - 1] = '\0';
		rfork(RFNAMEG);
	
		if(mount(wfd, -1, "/mnt/wsys", MREPL, line) < 0) 
			sysfatal("%s: Cannot mount %s under /mnt/wsys: %r",
						argv0, line);
	
		if(bind("/mnt/wsys", "/dev", MBEFORE) < 0) 
			sysfatal("%s: Cannot bind /mnt/wsys in /dev: %r",
						argv0);
	
	}
	if((wctlfd = open("/dev/wctl", OWRITE)) < 0)
		sysfatal("%s: Cannot open /dev/wctl: %r", argv0);
	if(initdraw(nil, nil, "trace") < 0)
		sysfatal("%s: initdraw failure: %r", argv0);

	Width = Dx(screen->r);
	Height = Dy(screen->r);

	if((mousectl = initmouse(nil, screen)) == nil)
		sysfatal("%s: cannot initialize mouse: %r", argv0);

	if((keyboardctl = initkeyboard(nil)) == nil)
		sysfatal("%s: cannot initialize keyboard: %r", argv0);

	colinit();

	paused = 0;
	scaleno = 7;	/* 100 milliseconds */
	now = nsec();
	for(;;) {
		Alt a[] = {
			{ mousectl->c,			nil,		CHANRCV		},
			{ mousectl->resizec,	nil,		CHANRCV		},
			{ keyboardctl->c,		&r,			CHANRCV		},
			{ nil,					nil,		CHANNOBLK	},
		};

		switch (alt(a)) {
		case 0:
			continue;

		case 1:
			if(getwindow(display, Refnone) < 0)
				sysfatal("drawrt: Cannot re-attach window");
			if(newwin){
				if(Dx(screen->r) != Width || 
					Dy(screen->r) != (ntasks * Height)){
					fprint(2, "resize: x: have %d, need %d; y: have %d, need %d\n",
							Dx(screen->r), Width + 8, Dy(screen->r), (ntasks * Height) + 8);
					fprint(wctlfd, "resize -dx %d -dy %d\n", 
							Width + 8, (ntasks * Height) + 8);
				}
			}
			else{
				Width = Dx(screen->r);
				Height = ntasks? Dy(screen->r)/ntasks: 
							Dy(screen->r);
			}
			break;

		case 2:

			switch(r){
			case 'r':
				for(i = 0; i < ntasks; i++){
					tasks[i].tstart = now;
					tasks[i].total = 0;
					tasks[i].runtime = 0;
					tasks[i].runmax = 0;
					tasks[i].runthis = 0;
					tasks[i].runs = 0;
					memset(tasks[i].tevents, 0, Nevent*sizeof(ulong));
					
				}
				break;

			case 'p':
				paused ^= 1;
				prevts = 0;
				break;

			case '-':
				if (scaleno < nelem(scales) - 1)
					scaleno++;
				prevts = 0;
				break;

			case '+':
				if (scaleno > 0)
					scaleno--;
				prevts = 0;
				break;

			case 'q':
				threadexitsall(nil);

			case 'v':
				verbose ^= 1;

			default:
				break;
			}
			break;
			
		case 3:
			now = nsec();
			while((n = read(logfd, eventbuf, Nevents*sizeof(Traceevent))) > 0){
				assert((n % sizeof(Traceevent)) == 0);
				nevents = n / sizeof(Traceevent);
				for (ep = eventbuf; ep < eventbuf + nevents; ep++){
					if ((ep->etype & 0xffff) >= Nevent){
						print("%ud %t Illegal event %ud\n",
							ep->pid, ep->time, ep->etype & 0xffff);
						continue;
					}
					if (verbose)
						print("%ud %t %s\n",
							ep->pid, ep->time, schedstatename[ep->etype & 0xffff]);

					for(i = 0; i < ntasks; i++)
						if(tasks[i].pid == ep->pid)
							break;

					if(i == ntasks){
						t = newtask(ep->pid);
						t->tstart = ep->time;
					}else
						t = &tasks[i];

					doevent(t, ep);
				}
			}
			if(!paused)
				redraw(scaleno);
		}
		sleep(scales[scaleno].sleep);
	}
}

int
timeconv(Fmt *f)
{
	char buf[128], *sign;
	vlong t;

	buf[0] = 0;
	switch(f->r) {
	case 'U':
		t = va_arg(f->args, vlong);
		break;
	case 't':		// vlong in nanoseconds
		t = va_arg(f->args, vlong);
		break;
	default:
		return fmtstrcpy(f, "(timeconv)");
	}
	if (t < 0) {
		sign = "-";
		t = -t;
	}else
		sign = "";
	if (t > S(1)){
		t += OneRound;
		sprint(buf, "%s%d.%.3ds", sign, (int)(t / S(1)), (int)(t % S(1))/1000000);
	}else if (t > MS(1)){
		t += MilliRound;
		sprint(buf, "%s%d.%.3dms", sign, (int)(t / MS(1)), (int)(t % MS(1))/1000);
	}else if (t > US(1))
		sprint(buf, "%s%d.%.3dµs", sign, (int)(t / US(1)), (int)(t % US(1)));
	else
		sprint(buf, "%s%dns", sign, (int)t);
	return fmtstrcpy(f, buf);
}
