summaryrefslogtreecommitdiff
path: root/appl/cmd/iostats.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/iostats.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/iostats.b')
-rw-r--r--appl/cmd/iostats.b635
1 files changed, 635 insertions, 0 deletions
diff --git a/appl/cmd/iostats.b b/appl/cmd/iostats.b
new file mode 100644
index 00000000..c70aadc4
--- /dev/null
+++ b/appl/cmd/iostats.b
@@ -0,0 +1,635 @@
+implement Iostats;
+
+#
+# iostats - gather file system access statistics
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import sys;
+
+include "draw.m";
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg, NOFID, NOTAG: import styx;
+
+include "workdir.m";
+ workdir: Workdir;
+
+include "sh.m";
+
+include "arg.m";
+
+Iostats: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Maxmsg: con 128*1024+Styx->IOHDRSZ;
+Ns2ms: con big 1000000;
+
+Rpc: adt
+{
+ name: string;
+ count: big;
+ time: big;
+ lo: big;
+ hi: big;
+ bin: big;
+ bout: big;
+};
+
+Stats: adt
+{
+ totread: big;
+ totwrite: big;
+ nrpc: int;
+ nproto: int;
+ rpc: array of ref Rpc; # Maxrpc
+};
+
+Fid: adt {
+ nr: int; # fid number
+ path: ref Path; # path used to open Fid
+ qid: Qid;
+ mode: int;
+ nread: big;
+ nwrite: big;
+ bread: big;
+ bwrite: big;
+ offset: big; # for directories
+};
+
+Path: adt {
+ parent: cyclic ref Path;
+ name: string;
+};
+
+Frec: adt
+{
+ op: ref Path; # first name?
+ qid: Qid;
+ nread: big;
+ nwrite: big;
+ bread: big;
+ bwrite: big;
+ opens: int;
+};
+
+Tag: adt {
+ m: ref Tmsg;
+ fid: ref Fid;
+ stime: big;
+ next: cyclic ref Tag;
+};
+
+NTAGHASH: con 1<<4; # power of 2
+NFIDHASH: con 1<<4; # power of 2
+
+tags := array[NTAGHASH] of ref Tag;
+fids := array[NFIDHASH] of list of ref Fid;
+dbg := 0;
+
+stats: Stats;
+frecs: list of ref Frec;
+
+replymap := array[tagof Rmsg.Stat+1] of {
+ tagof Rmsg.Version => tagof Tmsg.Version,
+ tagof Rmsg.Auth => tagof Tmsg.Auth,
+ tagof Rmsg.Attach => tagof Tmsg.Attach,
+ tagof Rmsg.Flush => tagof Tmsg.Flush,
+ tagof Rmsg.Clunk => tagof Tmsg.Clunk,
+ tagof Rmsg.Remove => tagof Tmsg.Remove,
+ tagof Rmsg.Wstat => tagof Tmsg.Wstat,
+ tagof Rmsg.Walk => tagof Tmsg.Walk,
+ tagof Rmsg.Create => tagof Tmsg.Create,
+ tagof Rmsg.Open => tagof Tmsg.Open,
+ tagof Rmsg.Read => tagof Tmsg.Read,
+ tagof Rmsg.Write => tagof Tmsg.Write,
+ tagof Rmsg.Stat => tagof Tmsg.Stat,
+ * => -1,
+};
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ workdir = load Workdir Workdir->PATH;
+ sh := load Sh Sh->PATH;
+ styx = load Styx Styx->PATH;
+ styx->init();
+
+ wd := workdir->init();
+
+ dbfile := "iostats.out";
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("iostats [-d] [-f debugfile] cmds [args ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => dbg++;
+ 'f' => dbfile = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKFD|Sys->FORKNS|Sys->NEWPGRP|Sys->FORKENV, nil);
+
+ if(dbg){
+ fd := sys->create(dbfile, Sys->OWRITE, 8r666);
+ if(fd == nil)
+ fatal(sys->sprint("can't create %q: %r", dbfile));
+ sys->dup(fd.fd, 2);
+ }
+
+ if(sys->chdir("/") < 0)
+ fatal(sys->sprint("chdir /: %r"));
+
+ stats.totread = big 0;
+ stats.totwrite = big 0;
+ stats.nrpc = 0;
+ stats.nproto = 0;
+ stats.rpc = array[tagof Tmsg.Wstat + 1] of ref Rpc;
+ stats.rpc[tagof Tmsg.Version] = mkrpc("version");
+ stats.rpc[tagof Tmsg.Auth] = mkrpc("auth");
+ stats.rpc[tagof Tmsg.Flush] = mkrpc("flush");
+ stats.rpc[tagof Tmsg.Attach] = mkrpc("attach");
+ stats.rpc[tagof Tmsg.Walk] = mkrpc("walk");
+ stats.rpc[tagof Tmsg.Open] = mkrpc("open");
+ stats.rpc[tagof Tmsg.Create] = mkrpc("create");
+ stats.rpc[tagof Tmsg.Clunk] = mkrpc("clunk");
+ stats.rpc[tagof Tmsg.Read] = mkrpc("read");
+ stats.rpc[tagof Tmsg.Write] = mkrpc("write");
+ stats.rpc[tagof Tmsg.Remove] = mkrpc("remove");
+ stats.rpc[tagof Tmsg.Stat] = mkrpc("stat");
+ stats.rpc[tagof Tmsg.Wstat] = mkrpc("wstat");
+
+ mpipe := array[2] of ref Sys->FD;
+ if(sys->pipe(mpipe) < 0)
+ fatal(sys->sprint("can't create pipe: %r"));
+ pids := chan of int;
+ cmddone := chan of int;
+ spawn cmd(sh, ctxt, args, wd, mpipe[0], pids, cmddone);
+ <-pids;
+ mpipe[0] = nil;
+ epipe := array[2] of ref Sys->FD;
+ if(sys->pipe(epipe) < 0)
+ fatal(sys->sprint("can't create pipe: %r"));
+ spawn export(epipe[1], pids);
+ <-pids;
+ epipe[1] = nil;
+ iodone := chan of int;
+ spawn iostats(epipe[0], mpipe[1], pids, iodone);
+ <-pids;
+ epipe[0] = mpipe[1] = nil;
+ <-cmddone;
+ <-iodone;
+ results();
+}
+
+cmd(sh: Sh, ctxt: ref Draw->Context, args: list of string, wdir: string, fsfd: ref Sys->FD, pids: chan of int, done: chan of int)
+{
+ {
+ pids <-= sys->pctl(Sys->FORKNS|Sys->FORKFD, nil);
+ if(sys->mount(fsfd, nil, "/", Sys->MREPL, "") < 0)
+ fatal(sys->sprint("can't mount /: %r"));
+ fsfd = nil;
+ sys->bind("#e", "/env", Sys->MREPL | Sys->MCREATE);
+ sys->bind("#d", "/fd", Sys->MREPL); # better than nothing
+ if(sys->chdir(wdir) < 0)
+ fatal(sys->sprint("can't chdir to %s: %r", wdir));
+ sh->run(ctxt, args);
+ }exception{
+ "fail:*" =>
+ ; # don't mention it
+ * =>
+ raise; # cause the fault
+ }
+ done <-= 1;
+}
+
+iostats(expfd: ref Sys->FD, mountfd: ref Sys->FD, pids: chan of int, done: chan of int)
+{
+ pids <-= sys->pctl(Sys->NEWFD|Sys->NEWPGRP, 1 :: 2 :: expfd.fd :: mountfd.fd :: nil);
+ timefd := sys->open("/dev/time", Sys->OREAD);
+ if(timefd == nil)
+ fatal(sys->sprint("can't open /dev/time: %r"));
+ tmsgs := chan of (int, ref Tmsg);
+ spawn Treader(mountfd, expfd, tmsgs);
+ (tpid, nil) := <-tmsgs;
+ rmsgs := chan of (int, ref Rmsg);
+ spawn Rreader(expfd, mountfd, rmsgs);
+ (rpid, nil) := <-rmsgs;
+ expfd = mountfd = nil;
+ stderr := sys->fildes(2);
+Run:
+ for(;;)alt{
+ (n, t) := <-tmsgs => # n.b.: received on tmsgs before it goes to server
+ if(t == nil || tagof t == tagof Tmsg.Readerror)
+ break Run; # TO DO?
+ if(dbg)
+ sys->fprint(stderr, "->%s\n", t.text());
+ tag := newtag(t, nsec(timefd));
+ stats.nrpc++;
+ stats.nproto += n;
+ rpc := stats.rpc[tagof t];
+ if(rpc == nil){
+ sys->fprint(stderr, "iostats: unexpected T-msg %d\n", tagof t);
+ continue;
+ }
+ rpc.count++;
+ rpc.bin += big n;
+ pick pt := t {
+ Auth =>
+ tag.fid = newfid(pt.afid);
+ Attach =>
+ tag.fid = newfid(pt.fid);
+ Walk =>
+ tag.fid = findfid(pt.fid);
+ Open =>
+ tag.fid = findfid(pt.fid);
+ Create =>
+ tag.fid = findfid(pt.fid);
+ Read =>
+ tag.fid = findfid(pt.fid);
+ Write =>
+ tag.fid = findfid(pt.fid);
+ pt.data = nil; # don't need to keep data
+ Clunk or
+ Stat or
+ Remove =>
+ tag.fid = findfid(pt.fid);
+ Wstat =>
+ tag.fid = findfid(pt.fid);
+ }
+ (n, r) := <-rmsgs =>
+ if(r == nil || tagof r == tagof Rmsg.Readerror){
+ break Run; # TO DO
+ }
+ if(dbg)
+ sys->fprint(stderr, "<-%s\n", r.text());
+ stats.nproto += n;
+ tag := findtag(r.tag, 1);
+ if(tag == nil)
+ continue; # client or server error TO DO: account for flush
+ if(tagof r < len replymap && (tt := replymap[tagof r]) >= 0 && (rpc := stats.rpc[tt]) != nil){
+ update(rpc, nsec(timefd)-tag.stime);
+ rpc.bout += big n;
+ }
+ fid := tag.fid;
+ pick pr := r {
+ Error =>
+ pick m := tag.m {
+ Auth =>
+ if(fid != nil){
+ if(fid.nread != big 0 || fid.nwrite != big 0)
+ fidreport(fid);
+ freefid(fid);
+ }
+ }
+ Version =>
+ # could pick up message size
+ # flush fids/tags
+ tags = array[len tags] of ref Tag;
+ fids = array[len fids] of list of ref Fid;
+ Auth =>
+ # afid from fid.t, qaid from auth
+ if(fid != nil){
+ fid.qid = pr.aqid;
+ fid.path = ref Path(nil, "#auth");
+ }
+ Attach =>
+ if(fid != nil){
+ fid.qid = pr.qid;
+ fid.path = ref Path(nil, "/");
+ }
+ Walk =>
+ pick m := tag.m {
+ Walk =>
+ if(len pr.qids != len m.names)
+ break; # walk failed, no change
+ if(fid == nil)
+ break;
+ if(m.newfid != m.fid){
+ nf := newfid(m.newfid);
+ nf.path = fid.path;
+ fid = nf; # walk new fid
+ }
+ for(i := 0; i < len m.names; i++){
+ fid.qid = pr.qids[i];
+ if(m.names[i] == ".."){
+ if(fid.path.parent != nil)
+ fid.path = fid.path.parent;
+ }else
+ fid.path = ref Path(fid.path, m.names[i]);
+ }
+ }
+ Open or
+ Create =>
+ if(fid != nil)
+ fid.qid = pr.qid;
+ Read =>
+ fid.nread++;
+ nr := big len pr.data;
+ fid.bread += nr;
+ stats.totread += nr;
+ Write =>
+ # count
+ fid.nwrite++;
+ fid.bwrite += big pr.count;
+ stats.totwrite += big pr.count;
+ Flush =>
+ pick m := tag.m {
+ Flush =>
+ findtag(m.oldtag, 1); # discard if there
+ }
+ Clunk or
+ Remove =>
+ if(fid != nil){
+ if(fid.nread != big 0 || fid.nwrite != big 0)
+ fidreport(fid);
+ freefid(fid);
+ }
+ }
+ }
+ kill(rpid, "kill");
+ kill(tpid, "kill");
+ done <-= 1;
+}
+
+results()
+{
+ stderr := sys->fildes(2);
+ rpc := stats.rpc[tagof Tmsg.Read];
+ brpsec := real stats.totread / ((real rpc.time/1.0e9)+.000001);
+
+ rpc = stats.rpc[tagof Tmsg.Write];
+ bwpsec := real stats.totwrite / ((real rpc.time/1.0e9)+.000001);
+
+ ttime := big 0;
+ for(n := 0; n < len stats.rpc; n++){
+ rpc = stats.rpc[n];
+ if(rpc == nil || rpc.count == big 0)
+ continue;
+ ttime += rpc.time;
+ }
+
+ bppsec := real stats.nproto / ((real ttime/1.0e9)+.000001);
+
+ sys->fprint(stderr, "\nread %bud bytes, %g Kb/sec\n", stats.totread, brpsec/1024.0);
+ sys->fprint(stderr, "write %bud bytes, %g Kb/sec\n", stats.totwrite, bwpsec/1024.0);
+ sys->fprint(stderr, "protocol %ud bytes, %g Kb/sec\n", stats.nproto, bppsec/1024.0);
+ sys->fprint(stderr, "rpc %ud count\n\n", stats.nrpc);
+
+ sys->fprint(stderr, "%-10s %5s %5s %5s %5s %5s T R\n",
+ "Message", "Count", "Low", "High", "Time", " Avg");
+
+ for(n = 0; n < len stats.rpc; n++){
+ rpc = stats.rpc[n];
+ if(rpc == nil || rpc.count == big 0)
+ continue;
+ sys->fprint(stderr, "%-10s %5bud %5bud %5bud %5bud %5bud ms %8bud %8bud bytes\n",
+ rpc.name,
+ rpc.count,
+ rpc.lo/Ns2ms,
+ rpc.hi/Ns2ms,
+ rpc.time/Ns2ms,
+ rpc.time/Ns2ms/rpc.count,
+ rpc.bin,
+ rpc.bout);
+ }
+
+ # unclunked fids
+ for(n = 0; n < NFIDHASH; n++)
+ for(fl := fids[n]; fl != nil; fl = tl fl){
+ fid := hd fl;
+ if(fid.nread != big 0 || fid.nwrite != big 0)
+ fidreport(fid);
+ }
+ if(frecs == nil)
+ exit;
+
+ sys->fprint(stderr, "\nOpens Reads (bytes) Writes (bytes) File\n");
+ for(frl := frecs; frl != nil; frl = tl frl){
+ fr := hd frl;
+ case s := makepath(fr.op) {
+ "/fd/0" => s = "(stdin)";
+ "/fd/1" => s = "(stdout)";
+ "/fd/2" => s = "(stderr)";
+ "" => s = "/.";
+ }
+ sys->fprint(stderr, "%5ud %8bud %8bud %8bud %8bud %s\n", fr.opens, fr.nread, fr.bread,
+ fr.nwrite, fr.bwrite, s);
+ }
+}
+
+Treader(fd: ref Sys->FD, ofd: ref Sys->FD, out: chan of (int, ref Tmsg))
+{
+ out <-= (sys->pctl(0, nil), nil);
+ fd = sys->fildes(fd.fd);
+ ofd = sys->fildes(ofd.fd);
+ for(;;){
+ (a, err) := styx->readmsg(fd, Maxmsg);
+ if(err != nil){
+ out <-= (0, ref Tmsg.Readerror(0, err));
+ break;
+ }
+ if(a == nil){
+ out <-= (0, nil);
+ break;
+ }
+ (nil, m) := Tmsg.unpack(a);
+ if(m == nil){
+ out <-= (0, ref Tmsg.Readerror(0, "bad Styx T-message format"));
+ break;
+ }
+ out <-= (len a, m);
+ sys->write(ofd, a, len a); # TO DO: errors
+ }
+}
+
+Rreader(fd: ref Sys->FD, ofd: ref Sys->FD, out: chan of (int, ref Rmsg))
+{
+ out <-= (sys->pctl(0, nil), nil);
+ fd = sys->fildes(fd.fd);
+ ofd = sys->fildes(ofd.fd);
+ for(;;){
+ (a, err) := styx->readmsg(fd, Maxmsg);
+ if(err != nil){
+ out <-= (0, ref Rmsg.Readerror(0, err));
+ break;
+ }
+ if(a == nil){
+ out <-= (0, nil);
+ break;
+ }
+ (nil, m) := Rmsg.unpack(a);
+ if(m == nil){
+ out <-= (0, ref Rmsg.Readerror(0, "bad Styx R-message format"));
+ break;
+ }
+ out <-= (len a, m);
+ sys->write(ofd, a, len a); # TO DO: errors
+ }
+}
+
+reply(fd: ref Sys->FD, m: ref Rmsg)
+{
+ d := m.pack();
+ sys->write(fd, d, len d);
+}
+
+mkrpc(s: string): ref Rpc
+{
+ return ref Rpc(s, big 0, big 0, big 1 << 40, big 0, big 0, big 0);
+}
+
+newfid(nr: int): ref Fid
+{
+ h := nr%NFIDHASH;
+ for(fl := fids[h]; fl != nil; fl = tl fl)
+ if((hd fl).nr == nr)
+ return hd fl; # shouldn't happen: faulty client
+ fid := ref Fid;
+ fid.nr = nr;
+ fid.nread = big 0;
+ fid.nwrite = big 0;
+ fid.bread = big 0;
+ fid.bwrite = big 0;
+ fid.qid = Qid(big 0, 0, -1);
+ fids[h] = fid :: fids[h];
+ return fid;
+}
+
+findfid(nr: int): ref Fid
+{
+ for(fl := fids[nr%NFIDHASH]; fl != nil; fl = tl fl)
+ if((hd fl).nr == nr)
+ return hd fl;
+ return nil;
+}
+
+freefid(fid: ref Fid)
+{
+ h := fid.nr%NFIDHASH;
+ nl: list of ref Fid;
+ for(fl := fids[h]; fl != nil; fl = tl fl)
+ if((hd fl).nr != fid.nr)
+ nl = hd fl :: nl;
+ fids[h] = nl;
+}
+
+makepath(p: ref Path): string
+{
+ nl: list of string;
+ for(; p != nil; p = p.parent)
+ if(p.name != "/")
+ nl = p.name :: nl;
+ s := "";
+ for(; nl != nil; nl = tl nl)
+ if(s != nil)
+ s += "/" + hd nl;
+ else
+ s = hd nl;
+ return "/"+s;
+}
+
+fatal(s: string)
+{
+ sys->fprint(sys->fildes(2), "iostats: %s: %r\n", s);
+ raise "fatal:error";
+}
+
+nsec(fd: ref Sys->FD): big
+{
+ buf := array[100] of byte;
+ n := sys->pread(fd, buf, len buf, big 0);
+ if(n <= 0)
+ return big 0;
+ return big string buf[0:n];
+}
+
+fidreport(f: ref Fid)
+{
+ for(fl := frecs; fl != nil; fl = tl fl){
+ fr := hd fl;
+ if(eqqid(f.qid, fr.qid)){
+ # could put f.path in list of paths if aliases were interesting
+ fr.nread += f.nread;
+ fr.nwrite += f.nwrite;
+ fr.bread += f.bread;
+ fr.bwrite += f.bwrite;
+ fr.opens++;
+ return;
+ }
+ }
+
+ fr := ref Frec;
+ fr.op = f.path;
+ fr.qid = f.qid;
+ fr.nread = f.nread;
+ fr.nwrite = f.nwrite;
+ fr.bread = f.bread;
+ fr.bwrite = f.bwrite;
+ fr.opens = 1;
+ frecs = fr :: frecs;
+}
+
+update(rpc: ref Rpc, t: big)
+{
+ if(t < big 0)
+ t = big 0;
+
+ rpc.time += t;
+ if(t < rpc.lo)
+ rpc.lo = t;
+ if(t > rpc.hi)
+ rpc.hi = t;
+}
+
+newtag(m: ref Tmsg, t: big): ref Tag
+{
+ slot := m.tag & (NTAGHASH - 1);
+ tag := ref Tag(m, nil, t, tags[slot]);
+ tags[slot] = tag;
+ return tag;
+}
+
+findtag(tag: int, destroy: int): ref Tag
+{
+ slot := tag & (NTAGHASH - 1);
+ prev: ref Tag;
+ for(t := tags[slot]; t != nil; t = t.next){
+ if(t.m.tag == tag)
+ break;
+ prev = t;
+ }
+ if(t == nil || !destroy)
+ return t;
+ if(prev == nil)
+ tags[slot] = t.next;
+ else
+ prev.next = t.next;
+ return t;
+}
+
+eqqid(a, b: Qid): int
+{
+ return a.path == b.path && a.qtype == b.qtype;
+}
+
+export(fd: ref Sys->FD, pid: chan of int)
+{
+ pid <-= sys->pctl(Sys->NEWFD|Sys->FORKNS, fd.fd::0::1::2::nil);
+ sys->export(fd, "/", Sys->EXPWAIT);
+}
+
+kill(pid: int, what: string)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "%s", what);
+}