diff options
author | rsc <devnull@localhost> | 2004-04-23 05:14:58 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2004-04-23 05:14:58 +0000 |
commit | e21fee604e3b53fd8c57ac3817e6d829f62d6ee3 (patch) | |
tree | 80147a5d4d886cce392c7e18f5ecb9eaaeb2359d /src/cmd/draw/stats.c | |
parent | f0f4401f0cfc654646bdf21849627ebcbd5d82b5 (diff) | |
download | plan9port-e21fee604e3b53fd8c57ac3817e6d829f62d6ee3.tar.gz plan9port-e21fee604e3b53fd8c57ac3817e6d829f62d6ee3.tar.bz2 plan9port-e21fee604e3b53fd8c57ac3817e6d829f62d6ee3.zip |
move these here
Diffstat (limited to 'src/cmd/draw/stats.c')
-rw-r--r-- | src/cmd/draw/stats.c | 884 |
1 files changed, 884 insertions, 0 deletions
diff --git a/src/cmd/draw/stats.c b/src/cmd/draw/stats.c new file mode 100644 index 00000000..07d20ad4 --- /dev/null +++ b/src/cmd/draw/stats.c @@ -0,0 +1,884 @@ +#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 +{ + Vbattery, + Vcontext, + Vcpu, + Vether, + Vethererr, + Vetherin, + Vetherout, + Vfault, + Vfork, + Vidle, + Vintr, + Vload, + Vmem, + Vswap, + Vsys, + Vsyscall, + Vuser, + Nvalue, +}; + +char* +labels[Nvalue] = +{ + "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*, ulong, 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]; +}; + +char *menu2str[Nvalue+1]; +char xmenu2str[Nvalue+1][40]; + +Menu menu2 = {menu2str, nil}; +int present[Nvalue]; +Image *cols[Ncolor][3]; +Graph *graph; +Machine *mach; +Font *mediumfont; +char *mysysname; +char argchars[] = "bceEfiIlmnsw"; +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"); + exits(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) +{ + 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, ulong v, ulong vmax) +{ + char buf[32]; + int overflow; + + 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 [-O] [-S scale] [-LY] [-%s] [machine...]\n", argchars); + exits("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->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 machthread(void*); + +void +threadmain(int argc, char *argv[]) +{ + int i, j; + char *s; + ulong v, vmax, nargs; + char args[100]; + + nmach = 1; + mysysname = sysname(); + if(mysysname == nil){ + fprint(2, "stats: can't find sysname: %r\n"); + exits("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 'Y': + ylabels++; + break; + case 'O': + oldsystem = 1; + 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++) + threadcreate(machthread, &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 'b': + addgraph(Vbattery); + break; + case 'c': + addgraph(Vcontext); + 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(nil, nil, "stats") < 0) + sysfatal("initdraw: %r"); + colinit(); + if((mc = initmouse(nil, nil)) == 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); + + resize(); + unlockdisplay(display); + + 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); + threadsleep(sleeptime); + } +} + +void +machthread(void *v) +{ + char buf[256], *f[4], *p; + int i, n, t; + Machine *m; + + m = v; + t = 0; + for(;;){ + n = threadread(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] = "sysstat"; + 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, "sysstat", 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; +} + |