|  | #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; | 
|  | } | 
|  |  |