#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include #include #include "runt.h" static void cpxec(Prog *); static void memprof(int, Heap*, ulong); extern Inst* pc2dispc(Inst*, Module*); static int interval = 100; /* Sampling interval in milliseconds */ enum { HSIZE = 32, }; #define HASH(m) ((m)%HSIZE) /* cope with multiple profilers some day */ typedef struct Record Record; struct Record { int id; char* name; char* path; Inst* base; int size; /*Module* m; */ ulong mtime; Qid qid; Record* hash; Record* link; ulong bucket[1]; }; struct { Lock l; vlong time; Record* hash[HSIZE]; Record* list; } profile; typedef struct Pmod Pmod; struct Pmod { char* name; Pmod* link; } *pmods; #define QSHIFT 4 #define QID(q) ((ulong)(q).path&0xf) #define QPID(pid) ((pid)<link) if(r->id == id) break; return r; } static void addpmod(char *m) { Pmod *p = malloc(sizeof(Pmod)); if(p == nil) return; p->name = malloc(strlen(m)+1); if(p->name == nil){ free(p); return; } strcpy(p->name, m); p->link = pmods; pmods = p; } static void freepmods(void) { Pmod *p, *np; for(p = pmods; p != nil; p = np){ free(p->name); np = p->link; free(p); } pmods = nil; } static int inpmods(char *m) { Pmod *p; for(p = pmods; p != nil; p = p->link) if(strcmp(p->name, m) == 0) return 1; return 0; } static void freeprof(void) { int i; Record *r, *nr; ids = 0; profiler = Pnil; freepmods(); for(r = profile.list; r != nil; r = nr){ free(r->path); nr = r->link; free(r); } profile.list = nil; profile.time = 0; for(i = 0; i < HSIZE; i++) profile.hash[i] = nil; } static int profgen(Chan *c, char *name, Dirtab *d, int nd, int s, Dir *dp) { Qid qid; Record *r; ulong path, perm, len; Dirtab *tab; USED(name); USED(d); USED(nd); if(s == DEVDOTDOT) { mkqid(&qid, Qdir, 0, QTDIR); devdir(c, qid, "#P", 0, eve, 0555, dp); return 1; } if(c->qid.path == Qdir && c->qid.type & QTDIR) { acquire(); if(s-- == 0){ tab = &profdir[Qctl]; mkqid(&qid, PATH(c->qid)|tab->qid.path, c->qid.vers, QTFILE); devdir(c, qid, tab->name, tab->length, eve, tab->perm, dp); release(); return 1; } r = profile.list; while(s-- && r != nil) r = r->link; if(r == nil) { release(); return -1; } sprint(up->genbuf, "%.8lux", (ulong)r->id); mkqid(&qid, (r->id<id, QTDIR); devdir(c, qid, up->genbuf, 0, eve, DMDIR|0555, dp); release(); return 1; } if(s >= nelem(profdir)-1) error(Enonexist); /* was return -1; */ tab = &profdir[s]; path = PATH(c->qid); acquire(); r = getrec(PID(c->qid)); if(r == nil) { release(); error(Enonexist); /* was return -1; */ } perm = tab->perm; len = tab->length; mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE); devdir(c, qid, tab->name, len, eve, perm, dp); release(); return 1; } static Chan* profattach(char *spec) { return devattach('P', spec); } static Walkqid* profwalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, 0, 0, profgen); } static int profstat(Chan *c, uchar *db, int n) { return devstat(c, db, n, 0, 0, profgen); } static Chan* profopen(Chan *c, int omode) { int qid; Record *r; if(c->qid.type & QTDIR) { if(omode != OREAD) error(Eisdir); c->mode = openmode(omode); c->flag |= COPEN; c->offset = 0; return c; } if(omode&OTRUNC) error(Eperm); qid = QID(c->qid); if(qid == Qctl || qid == Qpctl){ if (omode != OWRITE) error(Eperm); } else{ if(omode != OREAD) error(Eperm); } if(qid != Qctl){ acquire(); r = getrec(PID(c->qid)); release(); if(r == nil) error(Ethread); } c->offset = 0; c->flag |= COPEN; c->mode = openmode(omode); if(QID(c->qid) == Qhist) c->aux = nil; return c; } static int profwstat(Chan *c, uchar *dp, int n) { Dir d; Record *r; if(strcmp(up->env->user, eve)) error(Eperm); if(c->qid.type & QTDIR) error(Eperm); acquire(); r = getrec(PID(c->qid)); release(); if(r == nil) error(Ethread); n = convM2D(dp, n, &d, nil); if(n == 0) error(Eshortstat); d.mode &= 0777; /* TO DO: copy to c->aux->perm, once that exists */ return n; } static void profclose(Chan *c) { USED(c); } static long profread(Chan *c, void *va, long n, vlong offset) { int i; Record *r; char *a = va; if(c->qid.type & QTDIR) return devdirread(c, a, n, 0, 0, profgen); acquire(); r = getrec(PID(c->qid)); release(); if(r == nil) error(Ethread); switch(QID(c->qid)){ case Qname: return readstr(offset, va, n, r->name); case Qpath: return readstr(offset, va, n, r->path); case Qhist: i = (int)c->aux; while(i < r->size && r->bucket[i] == 0) i++; if(i >= r->size) return 0; c->aux = (void*)(i+1); if(n < 20) error(Etoosmall); return sprint(a, "%d %lud", i, r->bucket[i]); case Qctl: error(Eperm); } return 0; } static long profwrite(Chan *c, void *va, long n, vlong offset) { int i; char *a = va; char buf[128], *fields[128]; USED(va); USED(n); USED(offset); if(c->qid.type & QTDIR) error(Eisdir); switch(QID(c->qid)){ case Qctl: if(n > sizeof(buf)-1) n = sizeof(buf)-1; memmove(buf, a, n); buf[n] = 0; i = getfields(buf, fields, nelem(fields), 1, " \t\n"); if(i > 0 && strcmp(fields[0], "module") == 0){ freepmods(); while(--i > 0) addpmod(fields[i]); return n; } if(i == 1){ if(strcmp(fields[0], "start") == 0){ if(profiler == Pnil) { profiler = Psam; if(!samplefn){ samplefn = 1; kproc("prof", sampler, 0, 0); } } } else if(strcmp(fields[0], "startmp") == 0){ if(profiler == Pnil){ profiler = Pmem; heapmonitor = memprof; } } else if(strcmp(fields[0], "stop") == 0) profiler = Pnil; else if(strcmp(fields[0], "end") == 0){ profiler = Pnil; freeprof(); interval = 100; } else error(Ebadarg); } else if (i == 2){ if(strcmp(fields[0], "interval") == 0) interval = strtoul(fields[1], nil, 0); else if(strcmp(fields[0], "startcp") == 0){ Prog *p; acquire(); p = progpid(strtoul(fields[1], nil, 0)); if(p == nil){ release(); return -1; } if(profiler == Pnil){ profiler = Pcov; p->xec = cpxec; } release(); } else error(Ebadarg); } else error(Ebadarg); return n; default: error(Eperm); } return 0; } static Record* newmodule(Module *m, int vm, int scale, int origin) { int dsize; Record *r, **l; if(!vm) acquire(); if((m->compiled && m->pctab == nil) || m->prog == nil) { if(!vm) release(); return nil; } /* print("newmodule %x %s %s %d %d %d\n", m, m->name, m->path, m->mtime, m->qid.path, m->qid.vers); */ if(m->compiled) dsize = m->nprog * sizeof(r->bucket[0]); else dsize = (msize(m->prog)/sizeof(Inst)) * sizeof(r->bucket[0]); dsize *= scale; dsize += origin; r = malloc(sizeof(Record)+dsize); if(r == nil) { if(!vm) release(); return nil; } r->id = ++ids; if(ids == (1<<8)-1) ids = 0; kstrdup(&r->name, m->name); kstrdup(&r->path, m->path); r->base = m->prog; r->size = dsize/sizeof(r->bucket[0]); // r->m = m; r->mtime = m->mtime; r->qid.path = m->qid.path; r->qid.vers = m->qid.vers; memset(r->bucket, 0, dsize); r->link = profile.list; profile.list = r; l = &profile.hash[HASH(m->mtime)]; r->hash = *l; *l = r; if(!vm) release(); return r; } static Record* mlook(Module *m, int vm, int scale, int origin) { Record *r; for(r = profile.hash[HASH(m->mtime)]; r; r = r->hash){ /* if(r->m == m){ /* bug - r->m could be old exited module */ if(r->mtime == m->mtime && r->qid.path == m->qid.path && r->qid.vers == m->qid.vers && strcmp(r->name, m->name) == 0 && strcmp(r->path, m->path) == 0){ r->base = m->prog; return r; } } if(pmods == nil || inpmods(m->name) || inpmods(m->path)){ if(0 && profiler == Pmem) heapmonitor = nil; r = newmodule(m, vm, scale, origin); if(0 && profiler == Pmem) heapmonitor = memprof; return r; } return nil; } static void sampler(void* a) { int i; Module *m; Record *r; Inst *p; USED(a); for(;;) { tsleep(&up->sleep, return0, nil, interval); if(profiler != Psam) break; lock(&profile.l); profile.time += interval; if(R.M == H || (m = R.M->m) == nil){ unlock(&profile.l); continue; } p = R.PC; r = mlook(m, 0, 1, 0); if(r == nil){ unlock(&profile.l); continue; } if(m->compiled && m->pctab != nil) p = pc2dispc(p, m); if((i = p-r->base) >= 0 && i < r->size) r->bucket[i]++; unlock(&profile.l); } samplefn = 0; pexit("", 0); } /* * coverage profiling */ static void cpxec(Prog *p) { int op, i; Module *m; Record *r; Prog *n; 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{ m = R.M->m; r = profiler == Pcov ? mlook(m, 1, 1, 0) : nil; do{ dec[R.PC->add](); op = R.PC->op; if(r != nil){ i = R.PC-r->base; if(i >= 0 && i < r->size) r->bucket[i]++; } R.PC++; optab[op](); if(op == ISPAWN || op == IMSPAWN){ n = delruntail(Pdebug); // any state will do n->xec = cpxec; addrun(n); } if(m != R.M->m){ m = R.M->m; r = profiler == Pcov ? mlook(m, 1, 1, 0) : nil; } }while(--R.IC != 0); } p->R = R; } /* memory profiling */ enum{ Halloc, Hfree, Hgcfree, }; #define MPAD sizeof(double) static void memprof(int c, Heap *h, ulong n) { int i, j, k; ulong kk, *b; Module *m; Record *r; Inst *p; /* print("%d %x %uld\n", c, h, n); */ USED(h); if(profiler != Pmem){ heapmonitor = nil; return; } lock(&profile.l); m = nil; if(c != Hgcfree && (R.M == H || (m = R.M->m) == nil)){ unlock(&profile.l); return; } j = n; if(c == 0 || c == 4){ /* heap or main allocation */ p = R.PC; if(m->compiled && m->pctab != nil) p = pc2dispc(p, m); if((r = mlook(m, 1, 2, 2)) == nil){ unlock(&profile.l); return; } i = p-r->base; k = (r->id<<24) | i; if(c == 0){ h->hprof = k; k = sizeof(Heap); } else{ *(ulong*)h = k; k = MPAD; } /* 31 is pool quanta - dependency on alloc.c */ j = ((j+k+BHDRSIZE+31)&~31) - (k+BHDRSIZE); } else{ /* c == 1 is ref count free */ /* c == 2 is gc free */ /* c == 3 is main free */ if(c == 3) k = *(ulong*)h; else k = h->hprof; if((r = getrec(k>>24)) == nil){ unlock(&profile.l); return; } i = k&0xffffff; if(c == 3) j = msize(h)-MPAD; else j = hmsize(h)-sizeof(Heap); j = -j; } i = 2*(i+1); b = r->bucket; if(i >= 0 && i < r->size){ if(0){ if(c == 1){ b[0] -= j; b[i] -= j; } else if(c == 2){ b[1] -= j; b[i+1] -= j; } } else{ b[0] += j; if((int)b[0] < 0) b[0] = 0; b[i] += j; if((int)b[i] < 0) b[i] = 0; if(j > 0){ if((kk = b[0]) > b[1]) b[1] = kk; if((kk = b[i]) > b[i+1]) b[i+1] = kk; } } } unlock(&profile.l); } Dev profdevtab = { 'P', "prof", devreset, devinit, devshutdown, profattach, profwalk, profstat, profopen, devcreate, profclose, profread, devbread, profwrite, devbwrite, devremove, profwstat };