diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2014-08-02 02:30:19 +0200 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2014-08-02 02:30:19 +0200 |
commit | 23aaa0c59cb0bf3e82e587571ee70d2c5c9e410b (patch) | |
tree | 4d42902dd2793825cfb36f77bd1f2a436c4d8c05 /sys/src/cmd/iostats.c | |
parent | d14b6a0bf98306a6ec1b9181e998ef672db5a206 (diff) |
iostats: reimplement iostats as a 9p filter instead of duplicating exportfs
old iostats failed to work when builidng the kernel due to old bugs
that where already fixed in exportfs. instead of backporting the fixes,
reimplement iostats as a filter that sits between exportfs and the
process mount. from users perspective, theres no difference.
the result is much smaller and can handle everything that exportfs
can like /srv.
Diffstat (limited to 'sys/src/cmd/iostats.c')
-rw-r--r-- | sys/src/cmd/iostats.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/sys/src/cmd/iostats.c b/sys/src/cmd/iostats.c new file mode 100644 index 000000000..0807f5bb1 --- /dev/null +++ b/sys/src/cmd/iostats.c @@ -0,0 +1,534 @@ +/* + * iostats - Gather file system information + */ +#include <u.h> +#include <libc.h> +#include <auth.h> +#include <fcall.h> + +#define DEBUGFILE "iostats.out" +#define DONESTR "done" + +enum{ + Maxfile = 1000, /* number of Files we'll log */ + Maxrpc = 20000,/* number of RPCs we'll log */ +}; + +typedef struct File File; +typedef struct Fid Fid; +typedef struct Req Req; +typedef struct Rpc Rpc; +typedef struct Stats Stats; + +/* per file statistics */ +struct File +{ + Qid qid; + char *path; + + ulong nopen; + + ulong nread; + vlong bread; + + ulong nwrite; + vlong bwrite; +}; + +/* fid context */ +struct Fid +{ + int fid; + Qid qid; + char *path; + File *file; /* set on open/create */ + Fid *next; +}; + +/* a request */ +struct Req +{ + Req *next; + vlong t; + + Fcall f; + uchar buf[]; +}; + +/* per rpc statistics */ +struct Rpc +{ + char *name; + ulong count; + vlong time; + vlong lo; + vlong hi; + vlong bin; + vlong bout; +}; + +/* all the statistics */ +struct Stats +{ + vlong totread; + vlong totwrite; + ulong nrpc; + vlong nproto; + Rpc rpc[Maxrpc]; + File file[Maxfile]; +}; + +Stats stats[1]; + +int pfd[2]; +int efd[2]; +int done; + +Lock rqlock; +Req *rqhead; + +Fid *fidtab[1024]; + +void +catcher(void *a, char *msg) +{ + USED(a); + + if(strcmp(msg, DONESTR) == 0) { + done = 1; + noted(NCONT); + } + if(strcmp(msg, "exit") == 0) + exits("exit"); + + noted(NDFLT); +} + +void +update(Rpc *rpc, vlong t) +{ + vlong t2; + + t2 = nsec(); + t = t2 - t; + if(t < 0) + t = 0; + + rpc->time += t; + if(t < rpc->lo) + rpc->lo = t; + if(t > rpc->hi) + rpc->hi = t; +} + +Fid** +fidhash(int fid) +{ + return &fidtab[fid % nelem(fidtab)]; +} + +Fid* +getfid(int fid, int new) +{ + Fid *f, **ff; + + ff = fidhash(fid); + for(f = *ff; f != nil; f = f->next){ + if(f->fid == fid) + return f; + } + if(new){ + f = mallocz(sizeof(*f), 1); + f->fid = fid; + f->next = *ff; + *ff = f; + } + return f; +} + +void +setfid(Fid *f, char *path, Qid qid) +{ + if(path != f->path){ + free(f->path); + f->path = path; + } + f->qid = qid; + f->file = nil; +} + +void +rattach(Fcall *fin, Fcall *fout) +{ + setfid(getfid(fin->fid, 1), strdup("/"), fout->qid); +} + +void +rwalk(Fcall *fin, Fcall *fout) +{ + Fid *of, *f; + int i; + + if((of = getfid(fin->fid, 0)) == nil) + return; + f = getfid(fin->newfid, 1); + if(f != of) + setfid(f, strdup(of->path), of->qid); + for(i=0; i<fout->nwqid; i++) + setfid(f, cleanname(smprint("%s/%s", f->path, fin->wname[i])), fout->wqid[i]); +} + +void +ropen(Fcall *fin, Fcall *fout) +{ + File *fs; + Fid *f; + + if((f = getfid(fin->fid, 0)) == nil) + return; + if(fin->type == Tcreate) + setfid(f, cleanname(smprint("%s/%s", f->path, fin->name)), fout->qid); + else + setfid(f, f->path, fout->qid); + for(fs = stats->file; fs < &stats->file[Maxfile]; fs++){ + if(fs->nopen == 0){ + fs->path = strdup(f->path); + fs->qid = f->qid; + f->file = fs; + break; + } + if(fs->qid.path == f->qid.path && strcmp(fs->path, f->path) == 0){ + f->file = fs; + break; + } + } + if(f->file != nil) + f->file->nopen++; +} + +void +rclunk(Fcall *fin) +{ + Fid **ff, *f; + + for(ff = fidhash(fin->fid); (f = *ff) != nil; ff = &f->next){ + if(f->fid == fin->fid){ + *ff = f->next; + free(f->path); + free(f); + return; + } + } +} + +void +rio(Fcall *fin, Fcall *fout) +{ + Fid *f; + int count; + + count = fout->count; + if((f = getfid(fin->fid, 0)) == nil) + return; + switch(fout->type){ + case Rread: + if(f->file != nil){ + f->file->nread++; + f->file->bread += count; + } + stats->totread += count; + break; + case Rwrite: + if(f->file != nil){ + f->file->nwrite++; + f->file->bwrite += count; + } + stats->totwrite += count; + break; + } +} + +void +usage(void) +{ + fprint(2, "usage: iostats [-d] [-f debugfile] cmds [args ...]\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + Rpc *rpc; + ulong ttime; + char *dbfile; + char buf[64*1024]; + float brpsec, bwpsec, bppsec; + int cpid, fspid, rspid, dbg, n, mflag; + File *fs; + Req *r, **rr; + + dbg = 0; + mflag = MREPL; + dbfile = DEBUGFILE; + + ARGBEGIN{ + case 'd': + dbg++; + break; + case 'f': + dbfile = ARGF(); + break; + case 'C': + mflag |= MCACHE; + break; + default: + usage(); + }ARGEND + + USED(dbfile); + + if(argc == 0) + usage(); + + if(pipe(pfd) < 0) + sysfatal("pipe"); + + switch(cpid = fork()) { + case -1: + sysfatal("fork"); + case 0: + close(pfd[1]); + if(getwd(buf, sizeof(buf)) == 0) + sysfatal("no working directory"); + + rfork(RFENVG|RFNAMEG|RFNOTEG); + + if(mount(pfd[0], -1, "/", mflag, "") < 0) + sysfatal("mount /"); + + bind("#c/pid", "/dev/pid", MREPL); + bind("#c/ppid", "/dev/ppid", MREPL); + bind("#e", "/env", MREPL|MCREATE); + close(0); + close(1); + close(2); + open("/fd/0", OREAD); + open("/fd/1", OWRITE); + open("/fd/2", OWRITE); + if(chdir(buf) < 0) + sysfatal("chdir"); + exec(argv[0], argv); + exec(smprint("/bin/%s", argv[0]), argv); + sysfatal("exec: %r"); + default: + close(pfd[0]); + } + + switch(fspid = fork()) { + default: + while(cpid != waitpid()) + ; + postnote(PNPROC, fspid, DONESTR); + while(fspid != waitpid()) + ; + exits(0); + case -1: + sysfatal("fork"); + case 0: + notify(catcher); + break; + } + + if(pipe(efd) < 0) + sysfatal("pipe"); + + /* spawn exportfs */ + switch(fork()) { + default: + close(efd[0]); + break; + case -1: + sysfatal("fork"); + case 0: + dup(efd[0], 0); + close(efd[0]); + close(efd[1]); + if(dbg){ + execl("/bin/exportfs", "exportfs", "-df", dbfile, "-r", "/", nil); + } else { + execl("/bin/exportfs", "exportfs", "-r", "/", nil); + } + exits(0); + } + + fmtinstall('F', fcallfmt); + + stats->rpc[Tversion].name = "version"; + stats->rpc[Tauth].name = "auth"; + stats->rpc[Tflush].name = "flush"; + stats->rpc[Tattach].name = "attach"; + stats->rpc[Twalk].name = "walk"; + stats->rpc[Topen].name = "open"; + stats->rpc[Tcreate].name = "create"; + stats->rpc[Tclunk].name = "clunk"; + stats->rpc[Tread].name = "read"; + stats->rpc[Twrite].name = "write"; + stats->rpc[Tremove].name = "remove"; + stats->rpc[Tstat].name = "stat"; + stats->rpc[Twstat].name = "wstat"; + + for(n = 0; n < Maxrpc; n++) + stats->rpc[n].lo = 10000000000LL; + + switch(rspid = rfork(RFPROC|RFMEM)) { + case 0: + /* read response from exportfs and pass to mount */ + while(!done){ + uchar tmp[sizeof(buf)]; + Fcall f; + + n = read(efd[1], buf, sizeof(buf)); + if(n < 0) + break; + if(n == 0) + continue; + + /* convert response */ + memset(&f, 0, sizeof(f)); + memmove(tmp, buf, n); + if(convM2S(tmp, n, &f) != n) + sysfatal("convM2S: %r"); + + /* find request to this response */ + lock(&rqlock); + for(rr = &rqhead; (r = *rr) != nil; rr = &r->next){ + if(r->f.tag == f.tag){ + *rr = r->next; + r->next = nil; + break; + } + } + stats->nproto += n; + unlock(&rqlock); + + switch(f.type){ + case Ropen: + case Rcreate: + ropen(&r->f, &f); + break; + case Rclunk: + case Rremove: + rclunk(&r->f); + break; + case Rattach: + rattach(&r->f, &f); + break; + case Rwalk: + rwalk(&r->f, &f); + break; + case Rread: + case Rwrite: + rio(&r->f, &f); + break; + } + + rpc = &stats->rpc[r->f.type]; + update(rpc, r->t); + rpc->bout += n; + free(r); + + if(write(pfd[1], buf, n) != n) + break; + } + exits(0); + default: + /* read request from mount and pass to exportfs */ + while(!done){ + n = read(pfd[1], buf, sizeof(buf)); + if(n < 0) + break; + if(n == 0) + continue; + + r = mallocz(sizeof(*r) + n, 1); + memmove(r->buf, buf, n); + if(convM2S(r->buf, n, &r->f) != n) + sysfatal("convM2S: %r"); + + rpc = &stats->rpc[r->f.type]; + rpc->count++; + rpc->bin += n; + + lock(&rqlock); + stats->nrpc++; + stats->nproto += n; + r->next = rqhead; + rqhead = r; + unlock(&rqlock); + + r->t = nsec(); + + if(write(efd[1], buf, n) != n) + break; + } + } + + /* shutdown */ + done = 1; + postnote(PNPROC, rspid, DONESTR); + close(pfd[1]); + close(efd[1]); + + /* dump statistics */ + rpc = &stats->rpc[Tread]; + brpsec = (double)stats->totread / (((float)rpc->time/1e9)+.000001); + + rpc = &stats->rpc[Twrite]; + bwpsec = (double)stats->totwrite / (((float)rpc->time/1e9)+.000001); + + ttime = 0; + for(n = 0; n < Maxrpc; n++) { + rpc = &stats->rpc[n]; + if(rpc->count == 0) + continue; + ttime += rpc->time; + } + + bppsec = (double)stats->nproto / ((ttime/1e9)+.000001); + + fprint(2, "\nread %llud bytes, %g Kb/sec\n", stats->totread, brpsec/1024.0); + fprint(2, "write %llud bytes, %g Kb/sec\n", stats->totwrite, bwpsec/1024.0); + fprint(2, "protocol %llud bytes, %g Kb/sec\n", stats->nproto, bppsec/1024.0); + fprint(2, "rpc %lud count\n\n", stats->nrpc); + + fprint(2, "%-10s %5s %5s %5s %5s %5s T R\n", + "Message", "Count", "Low", "High", "Time", "Averg"); + + for(n = 0; n < Maxrpc; n++) { + rpc = &stats->rpc[n]; + if(rpc->count == 0) + continue; + fprint(2, "%-10s %5lud %5llud %5llud %5llud %5llud ms %8llud %8llud bytes\n", + rpc->name, + rpc->count, + rpc->lo/1000000, + rpc->hi/1000000, + rpc->time/1000000, + rpc->time/1000000/rpc->count, + rpc->bin, + rpc->bout); + } + + fprint(2, "\nOpens Reads (bytes) Writes (bytes) File\n"); + for(fs = stats->file; fs < &stats->file[Maxfile]; fs++){ + if(fs->nopen == 0) + break; + fprint(2, "%5lud %8lud %8llud %8lud %8llud %s\n", + fs->nopen, + fs->nread, fs->bread, + fs->nwrite, fs->bwrite, + fs->path); + } + + exits(0); +} |