| #include <u.h> | 
 | #include <libc.h> | 
 | #include <ctype.h> | 
 | #include <auth.h> | 
 | #include <fcall.h> | 
 | #include <draw.h> | 
 | #include <thread.h> | 
 | #include <mouse.h> | 
 | #include <keyboard.h> | 
 |  | 
 | typedef struct Graph		Graph; | 
 | typedef struct Machine	Machine; | 
 |  | 
 | enum | 
 | { | 
 | 	Ncolor	= 6, | 
 | 	Ysqueeze	= 2,	/* vertical squeezing of label text */ | 
 | 	Labspace	= 2,	/* room around label */ | 
 | 	Dot		= 2,	/* height of dot */ | 
 | 	Opwid	= 5,	/* strlen("add  ") or strlen("drop ") */ | 
 | 	Nlab		= 3,	/* max number of labels on y axis */ | 
 | 	Lablen	= 16,	/* max length of label */ | 
 | 	Lx		= 4,	/* label tick length */ | 
 |  | 
 | 	STACK	= 8192, | 
 | 	XSTACK	= 32768 | 
 | }; | 
 |  | 
 | enum | 
 | { | 
 | 	V80211, | 
 | 	Vbattery, | 
 | 	Vcontext, | 
 | 	Vcpu, | 
 | 	Vether, | 
 | 	Vethererr, | 
 | 	Vetherin, | 
 | 	Vetherout, | 
 | 	Vfault, | 
 | 	Vfork, | 
 | 	Vidle, | 
 | 	Vintr, | 
 | 	Vload, | 
 | 	Vmem, | 
 | 	Vswap, | 
 | 	Vsys, | 
 | 	Vsyscall, | 
 | 	Vuser, | 
 | 	Nvalue | 
 | }; | 
 |  | 
 | char* | 
 | labels[Nvalue] =  | 
 | { | 
 | 	"802.11", | 
 | 	"battery", | 
 | 	"context", | 
 | 	"cpu", | 
 | 	"ether", | 
 | 	"ethererr", | 
 | 	"etherin", | 
 | 	"etherout", | 
 | 	"fault", | 
 | 	"fork", | 
 | 	"idle", | 
 | 	"intr", | 
 | 	"load", | 
 | 	"mem", | 
 | 	"swap", | 
 | 	"sys", | 
 | 	"syscall", | 
 | 	"user" | 
 | };	 | 
 |  | 
 | struct Graph | 
 | { | 
 | 	int		colindex; | 
 | 	Rectangle	r; | 
 | 	int		*data; | 
 | 	int		ndata; | 
 | 	char		*label; | 
 | 	int		value; | 
 | 	void		(*update)(Graph*, long, ulong); | 
 | 	Machine	*mach; | 
 | 	int		overflow; | 
 | 	Image	*overtmp; | 
 | 	ulong	vmax; | 
 | }; | 
 |  | 
 | struct Machine | 
 | { | 
 | 	char		*name; | 
 | 	int		fd; | 
 | 	int		pid; | 
 | 	int		dead; | 
 | 	int		absolute[Nvalue]; | 
 | 	ulong	last[Nvalue]; | 
 | 	ulong	val[Nvalue][2]; | 
 | 	ulong	load; | 
 | 	ulong	nload; | 
 | }; | 
 |  | 
 | char	*menu2str[Nvalue+1]; | 
 | char xmenu2str[Nvalue+1][40]; | 
 |  | 
 | Menu	menu2 = {menu2str, 0}; | 
 | int		present[Nvalue]; | 
 | Image	*cols[Ncolor][3]; | 
 | Graph	*graph; | 
 | Machine	*mach; | 
 | Font		*mediumfont; | 
 | char		*fontname; | 
 | char		*mysysname; | 
 | char		argchars[] = "8bcCeEfiIlmnsw"; | 
 | int		pids[1024]; | 
 | int 		parity;	/* toggled to avoid patterns in textured background */ | 
 | int		nmach; | 
 | int		ngraph;	/* totaly number is ngraph*nmach */ | 
 | double	scale = 1.0; | 
 | int		logscale = 0; | 
 | int		ylabels = 0; | 
 | int		oldsystem = 0; | 
 | int 		sleeptime = 1000; | 
 | int		changedvmax; | 
 |  | 
 | Mousectl *mc; | 
 | Keyboardctl *kc; | 
 |  | 
 | void | 
 | killall(char *s) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for(i=0; i<nmach; i++) | 
 | 		if(mach[i].pid) | 
 | 			postnote(PNPROC, mach[i].pid, "kill"); | 
 | 	threadexitsall(s); | 
 | } | 
 |  | 
 | void* | 
 | emalloc(ulong sz) | 
 | { | 
 | 	void *v; | 
 | 	v = malloc(sz); | 
 | 	if(v == nil) { | 
 | 		fprint(2, "stats: out of memory allocating %ld: %r\n", sz); | 
 | 		killall("mem"); | 
 | 	} | 
 | 	memset(v, 0, sz); | 
 | 	return v; | 
 | } | 
 |  | 
 | void* | 
 | erealloc(void *v, ulong sz) | 
 | { | 
 | 	v = realloc(v, sz); | 
 | 	if(v == nil) { | 
 | 		fprint(2, "stats: out of memory reallocating %ld: %r\n", sz); | 
 | 		killall("mem"); | 
 | 	} | 
 | 	return v; | 
 | } | 
 |  | 
 | char* | 
 | estrdup(char *s) | 
 | { | 
 | 	char *t; | 
 | 	if((t = strdup(s)) == nil) { | 
 | 		fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s); | 
 | 		killall("mem"); | 
 | 	} | 
 | 	return t; | 
 | } | 
 |  | 
 | 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), CMAP8, 1, c1); | 
 | 	cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2); | 
 | } | 
 |  | 
 | void | 
 | colinit(void) | 
 | { | 
 | 	if(fontname) | 
 | 		mediumfont = openfont(display, fontname); | 
 | 	if(mediumfont == nil) | 
 | 		mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font"); | 
 | 	if(mediumfont == nil) | 
 | 		mediumfont = font; | 
 |  | 
 | 	/* 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), CMAP8, 1, 0xEEEEEEFF); | 
 | 	cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF); | 
 | 	cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF); | 
 | } | 
 |  | 
 | void | 
 | label(Point p, int dy, char *text) | 
 | { | 
 | 	char *s; | 
 | 	Rune r[2]; | 
 | 	int w, maxw, maxy; | 
 |  | 
 | 	p.x += Labspace; | 
 | 	maxy = p.y+dy; | 
 | 	maxw = 0; | 
 | 	r[1] = '\0'; | 
 | 	for(s=text; *s; ){ | 
 | 		if(p.y+mediumfont->height-Ysqueeze > maxy) | 
 | 			break; | 
 | 		w = chartorune(r, s); | 
 | 		s += w; | 
 | 		w = runestringwidth(mediumfont, r); | 
 | 		if(w > maxw) | 
 | 			maxw = w; | 
 | 		runestring(screen, p, display->black, ZP, mediumfont, r); | 
 | 		p.y += mediumfont->height-Ysqueeze; | 
 | 	} | 
 | } | 
 |  | 
 | Point | 
 | paritypt(int x) | 
 | { | 
 | 	return Pt(x+parity, 0); | 
 | } | 
 |  | 
 | Point | 
 | datapoint(Graph *g, int x, ulong v, ulong vmax) | 
 | { | 
 | 	Point p; | 
 | 	double y; | 
 |  | 
 | 	p.x = x; | 
 | 	y = ((double)v)/(vmax*scale); | 
 | 	if(logscale){ | 
 | 		/* | 
 | 		 * Arrange scale to cover a factor of 1000. | 
 | 		 * vmax corresponds to the 100 mark. | 
 | 		 * 10*vmax is the top of the scale. | 
 | 		 */ | 
 | 		if(y <= 0.) | 
 | 			y = 0; | 
 | 		else{ | 
 | 			y = log10(y); | 
 | 			/* 1 now corresponds to the top; -2 to the bottom; rescale */ | 
 | 			y = (y+2.)/3.; | 
 | 		} | 
 | 	} | 
 | 	p.y = g->r.max.y - Dy(g->r)*y - Dot; | 
 | 	if(p.y < g->r.min.y) | 
 | 		p.y = g->r.min.y; | 
 | 	if(p.y > g->r.max.y-Dot) | 
 | 		p.y = g->r.max.y-Dot; | 
 | 	return p; | 
 | } | 
 |  | 
 | void | 
 | drawdatum(Graph *g, int x, ulong prev, ulong v, ulong vmax) | 
 | { | 
 | 	int c; | 
 | 	Point p, q; | 
 |  | 
 | 	c = g->colindex; | 
 | 	p = datapoint(g, x, v, vmax); | 
 | 	q = datapoint(g, x, prev, vmax); | 
 | 	if(p.y < q.y){ | 
 | 		draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x)); | 
 | 		draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP); | 
 | 		draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP); | 
 | 	}else{ | 
 | 		draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x)); | 
 | 		draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP); | 
 | 		draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP); | 
 | 	} | 
 |  | 
 | } | 
 |  | 
 | void | 
 | redraw(Graph *g, int vmax) | 
 | { | 
 | 	int i, c; | 
 |  | 
 | 	if(vmax != g->vmax){ | 
 | 		g->vmax = vmax; | 
 | 		changedvmax = 1; | 
 | 	} | 
 | 	c = g->colindex; | 
 | 	draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x)); | 
 | 	for(i=1; i<Dx(g->r); i++) | 
 | 		drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax); | 
 | 	drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax); | 
 | 	g->overflow = 0; | 
 | } | 
 |  | 
 | void | 
 | update1(Graph *g, long v, ulong vmax) | 
 | { | 
 | 	char buf[32]; | 
 | 	int overflow; | 
 |  | 
 | 	if(v < 0) | 
 | 		v = 0; | 
 | 	if(vmax != g->vmax){ | 
 | 		g->vmax = vmax; | 
 | 		changedvmax = 1; | 
 | 	} | 
 | 	if(g->overflow && g->overtmp!=nil) | 
 | 		draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min); | 
 | 	draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y)); | 
 | 	drawdatum(g, g->r.max.x-1, g->data[0], v, vmax); | 
 | 	memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0])); | 
 | 	g->data[0] = v; | 
 | 	g->overflow = 0; | 
 | 	if(logscale) | 
 | 		overflow = (v>10*vmax*scale); | 
 | 	else | 
 | 		overflow = (v>vmax*scale); | 
 | 	if(overflow && g->overtmp!=nil){ | 
 | 		g->overflow = 1; | 
 | 		draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min); | 
 | 		sprint(buf, "%ld", v); | 
 | 		string(screen, g->overtmp->r.min, display->black, ZP, mediumfont, buf); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | usage(void) | 
 | { | 
 | 	fprint(2, "usage: stats [-LY] [-F font] [-O] [-S scale] [-W winsize] [-%s] [machine...]\n", argchars); | 
 | 	threadexitsall("usage"); | 
 | } | 
 |  | 
 | void | 
 | addgraph(int n) | 
 | { | 
 | 	Graph *g, *ograph; | 
 | 	int i, j; | 
 | 	static int nadd; | 
 |  | 
 | 	if(n > Nvalue) | 
 | 		abort(); | 
 | 	/* avoid two adjacent graphs of same color */ | 
 | 	if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor) | 
 | 		nadd++; | 
 | 	ograph = graph; | 
 | 	graph = emalloc(nmach*(ngraph+1)*sizeof(Graph)); | 
 | 	for(i=0; i<nmach; i++) | 
 | 		for(j=0; j<ngraph; j++) | 
 | 			graph[i*(ngraph+1)+j] = ograph[i*ngraph+j]; | 
 | 	free(ograph); | 
 | 	ngraph++; | 
 | 	for(i=0; i<nmach; i++){ | 
 | 		g = &graph[i*ngraph+(ngraph-1)]; | 
 | 		memset(g, 0, sizeof(Graph)); | 
 | 		g->value = n; | 
 | 		g->label = menu2str[n]+Opwid; | 
 | 		g->update = update1;	/* no other update functions yet */ | 
 | 		g->mach = &mach[i]; | 
 | 		g->colindex = nadd%Ncolor; | 
 | 	} | 
 | 	present[n] = 1; | 
 | 	nadd++; | 
 | } | 
 |  | 
 | void | 
 | dropgraph(int which) | 
 | { | 
 | 	Graph *ograph; | 
 | 	int i, j, n; | 
 |  | 
 | 	if(which > nelem(menu2str)) | 
 | 		abort(); | 
 | 	/* convert n to index in graph table */ | 
 | 	n = -1; | 
 | 	for(i=0; i<ngraph; i++) | 
 | 		if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){ | 
 | 			n = i; | 
 | 			break; | 
 | 		} | 
 | 	if(n < 0){ | 
 | 		fprint(2, "stats: internal error can't drop graph\n"); | 
 | 		killall("error"); | 
 | 	} | 
 | 	ograph = graph; | 
 | 	graph = emalloc(nmach*(ngraph-1)*sizeof(Graph)); | 
 | 	for(i=0; i<nmach; i++){ | 
 | 		for(j=0; j<n; j++) | 
 | 			graph[i*(ngraph-1)+j] = ograph[i*ngraph+j]; | 
 | 		free(ograph[i*ngraph+j].data); | 
 | 		freeimage(ograph[i*ngraph+j].overtmp); | 
 | 		for(j++; j<ngraph; j++) | 
 | 			graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j]; | 
 | 	} | 
 | 	free(ograph); | 
 | 	ngraph--; | 
 | 	present[which] = 0; | 
 | } | 
 |  | 
 | int initmach(Machine*, char*); | 
 |  | 
 | int | 
 | addmachine(char *name) | 
 | { | 
 | 	if(ngraph > 0){ | 
 | 		fprint(2, "stats: internal error: ngraph>0 in addmachine()\n"); | 
 | 		usage(); | 
 | 	} | 
 | 	if(mach == nil) | 
 | 		nmach = 0;	/* a little dance to get us started with local machine by default */ | 
 | 	mach = erealloc(mach, (nmach+1)*sizeof(Machine)); | 
 | 	memset(mach+nmach, 0, sizeof(Machine)); | 
 | 	if (initmach(mach+nmach, name)){ | 
 | 		nmach++; | 
 | 		return 1; | 
 | 	} else | 
 | 		return 0; | 
 | } | 
 |  | 
 | void | 
 | newvalue(Machine *m, int i, ulong *v, ulong *vmax) | 
 | { | 
 | 	ulong now; | 
 |  | 
 | 	if(m->last[i] == 0) | 
 | 		m->last[i] = m->val[i][0]; | 
 | 		 | 
 | 	if(i == Vload){ | 
 | 		/* | 
 | 		 * Invert the ewma to obtain the 5s load statistics. | 
 | 		 * Ewma is load' = (1884/2048)*load + (164/2048)*last5s, so we do | 
 | 		 * last5s = (load' - (1884/2048)*load) / (164/2048). | 
 | 		 */ | 
 | 		if(++m->nload%5 == 0){ | 
 | 			now = m->val[i][0]; | 
 | 			m->load = (now - (((vlong)m->last[i]*1884)/2048)) * 2048 / 164; | 
 | 			m->last[i] = now; | 
 | 		} | 
 | 		*v = m->load; | 
 | 		*vmax = m->val[i][1]; | 
 | 	}else if(m->absolute[i]){ | 
 | 		*v = m->val[i][0]; | 
 | 		*vmax = m->val[i][1]; | 
 | 	}else{ | 
 | 		now = m->val[i][0]; | 
 | 		*v = (vlong)((now - m->last[i])*sleeptime)/1000; | 
 | 		m->last[i] = now; | 
 | 		*vmax = m->val[i][1]; | 
 | 	} | 
 | 	if(*vmax == 0) | 
 | 		*vmax = 1; | 
 | } | 
 |  | 
 | void | 
 | labelstrs(Graph *g, char strs[Nlab][Lablen], int *np) | 
 | { | 
 | 	int j; | 
 | 	ulong vmax; | 
 |  | 
 | 	vmax = g->vmax; | 
 | 	if(logscale){ | 
 | 		for(j=1; j<=2; j++) | 
 | 			sprint(strs[j-1], "%g", scale*pow(10., j)*(double)vmax/100.); | 
 | 		*np = 2; | 
 | 	}else{ | 
 | 		for(j=1; j<=3; j++) | 
 | 			sprint(strs[j-1], "%g", scale*(double)j*(double)vmax/4.0); | 
 | 		*np = 3; | 
 | 	} | 
 | } | 
 |  | 
 | int | 
 | labelwidth(void) | 
 | { | 
 | 	int i, j, n, w, maxw; | 
 | 	char strs[Nlab][Lablen]; | 
 |  | 
 | 	maxw = 0; | 
 | 	for(i=0; i<ngraph; i++){ | 
 | 		/* choose value for rightmost graph */ | 
 | 		labelstrs(&graph[ngraph*(nmach-1)+i], strs, &n); | 
 | 		for(j=0; j<n; j++){ | 
 | 			w = stringwidth(mediumfont, strs[j]); | 
 | 			if(w > maxw) | 
 | 				maxw = w; | 
 | 		} | 
 | 	} | 
 | 	return maxw; | 
 | } | 
 |  | 
 | void | 
 | resize(void) | 
 | { | 
 | 	int i, j, k, n, startx, starty, x, y, dx, dy, ly, ondata, maxx, wid, nlab; | 
 | 	Graph *g; | 
 | 	Rectangle machr, r; | 
 | 	ulong v, vmax; | 
 | 	char buf[128], labs[Nlab][Lablen]; | 
 |  | 
 | 	draw(screen, screen->r, display->white, nil, ZP); | 
 |  | 
 | 	/* label left edge */ | 
 | 	x = screen->r.min.x; | 
 | 	y = screen->r.min.y + Labspace+mediumfont->height+Labspace; | 
 | 	dy = (screen->r.max.y - y)/ngraph; | 
 | 	dx = Labspace+stringwidth(mediumfont, "0")+Labspace; | 
 | 	startx = x+dx+1; | 
 | 	starty = y; | 
 | 	for(i=0; i<ngraph; i++,y+=dy){ | 
 | 		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP); | 
 | 		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x)); | 
 | 		label(Pt(x, y), dy, graph[i].label); | 
 | 		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP); | 
 | 	} | 
 |  | 
 | 	/* label top edge */ | 
 | 	dx = (screen->r.max.x - startx)/nmach; | 
 | 	for(x=startx, i=0; i<nmach; i++,x+=dx){ | 
 | 		draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP); | 
 | 		j = dx/stringwidth(mediumfont, "0"); | 
 | 	/*	n = mach[i].nproc; */ | 
 | 		n = 1; | 
 | 		if(n>1 && j>=1+3+(n>10)+(n>100)){	/* first char of name + (n) */ | 
 | 			j -= 3+(n>10)+(n>100); | 
 | 			if(j <= 0) | 
 | 				j = 1; | 
 | 			snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].name, n); | 
 | 		}else | 
 | 			snprint(buf, sizeof buf, "%.*s", j, mach[i].name); | 
 | 		string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP, mediumfont, buf); | 
 | 	} | 
 |  | 
 | 	maxx = screen->r.max.x; | 
 |  | 
 | 	/* label right, if requested */ | 
 | 	if(ylabels && dy>Nlab*(mediumfont->height+1)){ | 
 | 		wid = labelwidth(); | 
 | 		if(wid < (maxx-startx)-30){ | 
 | 			/* else there's not enough room */ | 
 | 			maxx -= 1+Lx+wid; | 
 | 			draw(screen, Rect(maxx, starty, maxx+1, screen->r.max.y), display->black, nil, ZP); | 
 | 			y = starty; | 
 | 			for(j=0; j<ngraph; j++, y+=dy){ | 
 | 				/* choose value for rightmost graph */ | 
 | 				g = &graph[ngraph*(nmach-1)+j]; | 
 | 				labelstrs(g, labs, &nlab); | 
 | 				r = Rect(maxx+1, y, screen->r.max.x, y+dy-1); | 
 | 				if(j == ngraph-1) | 
 | 					r.max.y = screen->r.max.y; | 
 | 				draw(screen, r, cols[g->colindex][0], nil, paritypt(r.min.x)); | 
 | 				for(k=0; k<nlab; k++){ | 
 | 					ly = y + (dy*(nlab-k)/(nlab+1)); | 
 | 					draw(screen, Rect(maxx+1, ly, maxx+1+Lx, ly+1), display->black, nil, ZP); | 
 | 					ly -= mediumfont->height/2; | 
 | 					string(screen, Pt(maxx+1+Lx, ly), display->black, ZP, mediumfont, labs[k]); | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* create graphs */ | 
 | 	for(i=0; i<nmach; i++){ | 
 | 		machr = Rect(startx+i*dx, starty, maxx, screen->r.max.y); | 
 | 		if(i < nmach-1) | 
 | 			machr.max.x = startx+(i+1)*dx - 1; | 
 | 		y = starty; | 
 | 		for(j=0; j<ngraph; j++, y+=dy){ | 
 | 			g = &graph[i*ngraph+j]; | 
 | 			/* allocate data */ | 
 | 			ondata = g->ndata; | 
 | 			g->ndata = Dx(machr)+1;	/* may be too many if label will be drawn here; so what? */ | 
 | 			g->data = erealloc(g->data, g->ndata*sizeof(ulong)); | 
 | 			if(g->ndata > ondata) | 
 | 				memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(ulong)); | 
 | 			/* set geometry */ | 
 | 			g->r = machr; | 
 | 			g->r.min.y = y; | 
 | 			g->r.max.y = y+dy - 1; | 
 | 			if(j == ngraph-1) | 
 | 				g->r.max.y = screen->r.max.y; | 
 | 			draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x)); | 
 | 			g->overflow = 0; | 
 | 			r = g->r; | 
 | 			r.max.y = r.min.y+mediumfont->height; | 
 | 			r.max.x = r.min.x+stringwidth(mediumfont, "9999999"); | 
 | 			freeimage(g->overtmp); | 
 | 			g->overtmp = nil; | 
 | 			if(r.max.x <= g->r.max.x) | 
 | 				g->overtmp = allocimage(display, r, screen->chan, 0, -1); | 
 | 			newvalue(g->mach, g->value, &v, &vmax); | 
 | 			redraw(g, vmax); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	flushimage(display, 1); | 
 | } | 
 |  | 
 | void | 
 | eresized(int new) | 
 | { | 
 | 	lockdisplay(display); | 
 | 	if(new && getwindow(display, Refnone) < 0) { | 
 | 		fprint(2, "stats: can't reattach to window\n"); | 
 | 		killall("reattach"); | 
 | 	} | 
 | 	resize(); | 
 | 	unlockdisplay(display); | 
 | } | 
 |  | 
 | void | 
 | mousethread(void *v) | 
 | { | 
 | 	Mouse m; | 
 | 	int i; | 
 |  | 
 | 	USED(v); | 
 |  | 
 | 	while(readmouse(mc) == 0){ | 
 | 		m = mc->m; | 
 | 		if(m.buttons == 4){ | 
 | 			for(i=0; i<Nvalue; i++) | 
 | 				if(present[i]) | 
 | 					memmove(menu2str[i], "drop ", Opwid); | 
 | 				else | 
 | 					memmove(menu2str[i], "add  ", Opwid); | 
 | 			lockdisplay(display); | 
 | 			i = menuhit(3, mc, &menu2, nil); | 
 | 			if(i >= 0){ | 
 | 				if(!present[i]) | 
 | 					addgraph(i); | 
 | 				else if(ngraph > 1) | 
 | 					dropgraph(i); | 
 | 				resize(); | 
 | 			} | 
 | 			unlockdisplay(display); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | resizethread(void *v) | 
 | { | 
 | 	USED(v); | 
 |  | 
 | 	while(recv(mc->resizec, 0) == 1){ | 
 | 		lockdisplay(display); | 
 | 		if(getwindow(display, Refnone) < 0) | 
 | 			sysfatal("attach to window: %r"); | 
 | 		resize(); | 
 | 		unlockdisplay(display); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | keyboardthread(void *v) | 
 | { | 
 | 	Rune r; | 
 |  | 
 | 	while(recv(kc->c, &r) == 1) | 
 | 		if(r == 0x7F || r == 'q') | 
 | 			killall("quit"); | 
 | } | 
 |  | 
 | void machproc(void*); | 
 | void updateproc(void*); | 
 |  | 
 | void | 
 | threadmain(int argc, char *argv[]) | 
 | { | 
 | 	int i, j; | 
 | 	char *s; | 
 | 	ulong nargs; | 
 | 	char args[100]; | 
 |  | 
 | 	nmach = 1; | 
 | 	mysysname = sysname(); | 
 | 	if(mysysname == nil){ | 
 | 		fprint(2, "stats: can't find sysname: %r\n"); | 
 | 		threadexitsall("sysname"); | 
 | 	} | 
 |  | 
 | 	nargs = 0; | 
 | 	ARGBEGIN{ | 
 | 	case 'T': | 
 | 		s = ARGF(); | 
 | 		if(s == nil) | 
 | 			usage(); | 
 | 		i = atoi(s); | 
 | 		if(i > 0) | 
 | 			sleeptime = 1000*i; | 
 | 		break; | 
 | 	case 'S': | 
 | 		s = ARGF(); | 
 | 		if(s == nil) | 
 | 			usage(); | 
 | 		scale = atof(s); | 
 | 		if(scale <= 0.) | 
 | 			usage(); | 
 | 		break; | 
 | 	case 'L': | 
 | 		logscale++; | 
 | 		break; | 
 | 	case 'F': | 
 | 		fontname = EARGF(usage()); | 
 | 		break; | 
 | 	case 'Y': | 
 | 		ylabels++; | 
 | 		break; | 
 | 	case 'O': | 
 | 		oldsystem = 1; | 
 | 		break; | 
 | 	case 'W': | 
 | 		winsize = EARGF(usage()); | 
 | 		break; | 
 | 	default: | 
 | 		if(nargs>=sizeof args || strchr(argchars, ARGC())==nil) | 
 | 			usage(); | 
 | 		args[nargs++] = ARGC(); | 
 | 	}ARGEND | 
 |  | 
 | 	for(i=0; i<Nvalue; i++){ | 
 | 		menu2str[i] = xmenu2str[i]; | 
 | 		snprint(xmenu2str[i], sizeof xmenu2str[i], "add  %s", labels[i]); | 
 | 	} | 
 |  | 
 | 	if(argc == 0){ | 
 | 		mach = emalloc(nmach*sizeof(Machine)); | 
 | 		initmach(&mach[0], mysysname); | 
 | 	}else{ | 
 | 		for(i=j=0; i<argc; i++) | 
 | 			addmachine(argv[i]); | 
 | 	} | 
 |  | 
 | 	for(i=0; i<nmach; i++) | 
 | 		proccreate(machproc, &mach[i], STACK); | 
 |  | 
 | 	for(i=0; i<nargs; i++) | 
 | 	switch(args[i]){ | 
 | 	default: | 
 | 		fprint(2, "stats: internal error: unknown arg %c\n", args[i]); | 
 | 		usage(); | 
 | 	case '8': | 
 | 		addgraph(V80211); | 
 | 		break; | 
 | 	case 'b': | 
 | 		addgraph(Vbattery); | 
 | 		break; | 
 | 	case 'c': | 
 | 		addgraph(Vcontext); | 
 | 		break; | 
 | 	case 'C': | 
 | 		addgraph(Vcpu); | 
 | 		break; | 
 | 	case 'e': | 
 | 		addgraph(Vether); | 
 | 		break; | 
 | 	case 'E': | 
 | 		addgraph(Vetherin); | 
 | 		addgraph(Vetherout); | 
 | 		break; | 
 | 	case 'f': | 
 | 		addgraph(Vfault); | 
 | 		break; | 
 | 	case 'i': | 
 | 		addgraph(Vintr); | 
 | 		break; | 
 | 	case 'I': | 
 | 		addgraph(Vload); | 
 | 		addgraph(Vidle); | 
 | 		break; | 
 | 	case 'l': | 
 | 		addgraph(Vload); | 
 | 		break; | 
 | 	case 'm': | 
 | 		addgraph(Vmem); | 
 | 		break; | 
 | 	case 'n': | 
 | 		addgraph(Vetherin); | 
 | 		addgraph(Vetherout); | 
 | 		addgraph(Vethererr); | 
 | 		break; | 
 | 	case 's': | 
 | 		addgraph(Vsyscall); | 
 | 		break; | 
 | 	case 'w': | 
 | 		addgraph(Vswap); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	if(ngraph == 0) | 
 | 		addgraph(Vload); | 
 |  | 
 | 	for(i=0; i<nmach; i++) | 
 | 		for(j=0; j<ngraph; j++) | 
 | 			graph[i*ngraph+j].mach = &mach[i]; | 
 |  | 
 | 	if(initdraw(0, nil, "stats") < 0) | 
 | 		sysfatal("initdraw: %r"); | 
 | 	colinit(); | 
 | 	if((mc = initmouse(nil, screen)) == nil) | 
 | 		sysfatal("initmouse: %r"); | 
 | 	if((kc = initkeyboard(nil)) == nil) | 
 | 		sysfatal("initkeyboard: %r"); | 
 |  | 
 | 	display->locking = 1; | 
 | 	threadcreate(keyboardthread, nil, XSTACK); | 
 | 	threadcreate(mousethread, nil, XSTACK); | 
 | 	threadcreate(resizethread, nil, XSTACK); | 
 | 	proccreate(updateproc, nil, XSTACK); | 
 | 	resize(); | 
 | 	unlockdisplay(display); | 
 | } | 
 |  | 
 | void | 
 | updateproc(void *z) | 
 | { | 
 | 	int i; | 
 | 	ulong v, vmax; | 
 |  | 
 | 	USED(z); | 
 | 	for(;;){ | 
 | 		parity = 1-parity; | 
 | 		lockdisplay(display); | 
 | 		for(i=0; i<nmach*ngraph; i++){ | 
 | 			newvalue(graph[i].mach, graph[i].value, &v, &vmax); | 
 | 			graph[i].update(&graph[i], v, vmax); | 
 | 		} | 
 | 		if(changedvmax){ | 
 | 			changedvmax = 0; | 
 | 			resize(); | 
 | 		} | 
 | 		flushimage(display, 1); | 
 | 		unlockdisplay(display); | 
 | 		sleep(sleeptime); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | machproc(void *v) | 
 | { | 
 | 	char buf[256], *f[4], *p; | 
 | 	int i, n, t; | 
 | 	Machine *m; | 
 |  | 
 | 	m = v; | 
 | 	t = 0; | 
 | 	for(;;){ | 
 | 		n = read(m->fd, buf+t, sizeof buf-t); | 
 | 		m->dead = 0; | 
 | 		if(n <= 0) | 
 | 			break; | 
 | 		t += n; | 
 | 		while((p = memchr(buf, '\n', t)) != nil){ | 
 | 			*p++ = 0; | 
 | 			n = tokenize(buf, f, nelem(f)); | 
 | 			if(n >= 3){ | 
 | 				for(i=0; i<Nvalue; i++){ | 
 | 					if(strcmp(labels[i], f[0]) == 0){ | 
 | 						if(*f[1] == '='){ | 
 | 							m->absolute[i] = 1; | 
 | 							f[1]++; | 
 | 						} | 
 | 						m->val[i][0] = strtoul(f[1], 0, 0); | 
 | 						m->val[i][1] = strtoul(f[2], 0, 0); | 
 | 					} | 
 | 				} | 
 | 			} | 
 | 			t -= (p-buf); | 
 | 			memmove(buf, p, t); | 
 | 		} | 
 | 	} | 
 | 	if(m->fd){ | 
 | 		close(m->fd); | 
 | 		m->fd = -1; | 
 | 	} | 
 | 	if(m->pid){ | 
 | 		postnote(PNPROC, m->pid, "kill"); | 
 | 		m->pid = 0; | 
 | 	} | 
 | } | 
 |  | 
 | int | 
 | initmach(Machine *m, char *name) | 
 | { | 
 | 	char *args[5], *q; | 
 | 	int p[2], kfd[3], pid; | 
 |  | 
 | 	m->name = name; | 
 | 	if(strcmp(name, mysysname) == 0) | 
 | 		name = nil; | 
 |  | 
 | 	if(pipe(p) < 0) | 
 | 		sysfatal("pipe: %r"); | 
 |  | 
 | 	memset(args, 0, sizeof args); | 
 | 	args[0] = "auxstats"; | 
 | 	if(name){ | 
 | 		args[1] = name; | 
 | 		if((q = strchr(name, ':')) != nil){ | 
 | 			*q++ = 0; | 
 | 			args[2] = q; | 
 | 		} | 
 | 	} | 
 | 	kfd[0] = open("/dev/null", OREAD); | 
 | 	kfd[1] = p[1]; | 
 | 	kfd[2] = dup(2, -1); | 
 | 	if((pid = threadspawn(kfd, "auxstats", args)) < 0){ | 
 | 		fprint(2, "spawn: %r\n"); | 
 | 		close(kfd[0]); | 
 | 		close(p[0]); | 
 | 		close(p[1]); | 
 | 		return 0; | 
 | 	} | 
 | 	m->fd = p[0]; | 
 | 	m->pid = pid; | 
 | 	if((q = strchr(m->name, '.')) != nil) | 
 | 		*q = 0; | 
 | 	return 1; | 
 | } | 
 |  |