]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/iostats.c
421f531a90fe7d5bc353a45ce37fac6babd33c31
[plan9front.git] / sys / src / cmd / iostats.c
1 /*
2  * iostats - Gather file system information
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <auth.h>
7 #include <fcall.h>
8
9 #define DEBUGFILE       "iostats.out"
10 #define DONESTR         "done"
11
12 enum{
13         Maxfile         = 1000, /* number of Files we'll log */
14 };
15
16 typedef struct File File;
17 typedef struct Fid Fid;
18 typedef struct Req Req;
19 typedef struct Rpc Rpc;
20 typedef struct Stats Stats;
21
22 /* per file statistics */
23 struct File
24 {
25         Qid     qid;
26         char    *path;
27
28         ulong   nopen;
29
30         ulong   nread;
31         vlong   bread;
32
33         ulong   nwrite;
34         vlong   bwrite;
35 };
36
37 /* fid context */
38 struct Fid
39 {
40         int     fid;
41         Qid     qid;
42         char    *path;
43         File    *file;  /* set on open/create */
44         Fid     *next;
45 };
46
47 /* a request */
48 struct Req
49 {
50         Req     *next;
51         vlong   t;
52
53         Fcall   f;
54         uchar   buf[];
55 };
56
57 /* per rpc statistics */
58 struct Rpc
59 {
60         char    *name;
61         ulong   count;
62         vlong   time;
63         vlong   lo;
64         vlong   hi;
65         vlong   bin;
66         vlong   bout;
67 };
68
69 /* all the statistics */
70 struct Stats
71 {
72         vlong   totread;
73         vlong   totwrite;
74         ulong   nrpc;
75         vlong   nproto;
76         Rpc     rpc[Tmax];
77         File    file[Maxfile];
78 };
79
80 Stats   stats[1];
81
82 int pfd[2];
83 int efd[2];
84 int done;
85
86 Lock rqlock;
87 Req *rqhead;
88
89 Fid *fidtab[1024];
90
91 void
92 catcher(void *a, char *msg)
93 {
94         USED(a);
95
96         if(strcmp(msg, DONESTR) == 0) {
97                 done = 1;
98                 noted(NCONT);
99         }
100         if(strcmp(msg, "exit") == 0)
101                 exits("exit");
102
103         noted(NDFLT);
104 }
105
106 void
107 update(Rpc *rpc, vlong t)
108 {
109         vlong t2;
110
111         t2 = nsec();
112         t = t2 - t;
113         if(t < 0)
114                 t = 0;
115
116         rpc->time += t;
117         if(t < rpc->lo)
118                 rpc->lo = t;
119         if(t > rpc->hi)
120                 rpc->hi = t;
121 }
122
123 Fid**
124 fidhash(int fid)
125 {
126         return &fidtab[fid % nelem(fidtab)];
127 }
128
129 Fid*
130 getfid(int fid, int new)
131 {
132         Fid *f, **ff;
133
134         ff = fidhash(fid);
135         for(f = *ff; f != nil; f = f->next){
136                 if(f->fid == fid)
137                         return f;
138         }
139         if(new){
140                 f = mallocz(sizeof(*f), 1);
141                 f->fid = fid;
142                 f->next = *ff;
143                 *ff = f;
144         }
145         return f;
146 }
147
148 void
149 setfid(Fid *f, char *path, Qid qid)
150 {
151         if(path != f->path){
152                 free(f->path);
153                 f->path = path;
154         }
155         f->qid = qid;
156         f->file = nil;
157 }
158
159 void
160 rattach(Fcall *fin, Fcall *fout)
161 {
162         setfid(getfid(fin->fid, 1), strdup("/"), fout->qid);
163 }
164
165 void
166 rwalk(Fcall *fin, Fcall *fout)
167 {
168         Fid *of, *f;
169         int i;
170
171         if((of = getfid(fin->fid, 0)) == nil)
172                 return;
173         f = getfid(fin->newfid, 1);
174         if(f != of)
175                 setfid(f, strdup(of->path), of->qid);
176         for(i=0; i<fout->nwqid; i++)
177                 setfid(f, cleanname(smprint("%s/%s", f->path, fin->wname[i])), fout->wqid[i]);
178 }
179
180 void
181 ropen(Fcall *fin, Fcall *fout)
182 {
183         File *fs;
184         Fid *f;
185
186         if((f = getfid(fin->fid, 0)) == nil)
187                 return;
188         if(fin->type == Tcreate)
189                 setfid(f, cleanname(smprint("%s/%s", f->path, fin->name)), fout->qid);
190         else
191                 setfid(f, f->path, fout->qid);
192         for(fs = stats->file; fs < &stats->file[Maxfile]; fs++){
193                 if(fs->nopen == 0){
194                         fs->path = strdup(f->path);
195                         fs->qid = f->qid;
196                         f->file = fs;
197                         break;
198                 }
199                 if(fs->qid.path == f->qid.path && strcmp(fs->path, f->path) == 0){
200                         f->file = fs;
201                         break;
202                 }
203         }
204         if(f->file != nil)
205                 f->file->nopen++;
206 }
207
208 void
209 rclunk(Fcall *fin)
210 {
211         Fid **ff, *f;
212
213         for(ff = fidhash(fin->fid); (f = *ff) != nil; ff = &f->next){
214                 if(f->fid == fin->fid){
215                         *ff = f->next;
216                         free(f->path);
217                         free(f);
218                         return;
219                 }
220         }
221 }
222
223 void
224 rio(Fcall *fin, Fcall *fout)
225 {
226         Fid *f;
227         int count;
228
229         count = fout->count;
230         if((f = getfid(fin->fid, 0)) == nil)
231                 return;
232         switch(fout->type){
233         case Rread:
234                 if(f->file != nil){
235                         f->file->nread++;
236                         f->file->bread += count;
237                 }
238                 stats->totread += count;
239                 break;
240         case Rwrite:
241                 if(f->file != nil){
242                         f->file->nwrite++;
243                         f->file->bwrite += count;
244                 }
245                 stats->totwrite += count;
246                 break;
247         }
248 }
249
250 void
251 usage(void)
252 {
253         fprint(2, "usage: iostats [-dC] [-f debugfile] cmds [args ...]\n");
254         exits("usage");
255 }
256
257 void
258 main(int argc, char **argv)
259 {
260         Rpc *rpc;
261         ulong ttime;
262         char *dbfile;
263         char buf[64*1024];
264         float brpsec, bwpsec, bppsec;
265         int cpid, fspid, expid, rspid, dbg, n, mflag;
266         char *fds[3];
267         Waitmsg *w;
268         File *fs;
269         Req *r;
270
271         dbg = 0;
272         mflag = MREPL;
273         dbfile = DEBUGFILE;
274
275         ARGBEGIN{
276         case 'd':
277                 dbg++;
278                 break;
279         case 'f':
280                 dbfile = ARGF();
281                 break;
282         case 'C':
283                 mflag |= MCACHE;
284                 break;
285         default:
286                 usage();
287         }ARGEND
288
289         USED(dbfile);
290
291         if(argc == 0)
292                 usage();
293
294         if(pipe(pfd) < 0)
295                 sysfatal("pipe: %r");
296
297         /* dup std fds to be inherited to exportfs */
298         fds[0] = smprint("/fd/%d", dup(0, -1));
299         fds[1] = smprint("/fd/%d", dup(1, -1));
300         fds[2] = smprint("/fd/%d", dup(2, -1));
301
302         switch(cpid = fork()) {
303         case -1:
304                 sysfatal("fork: %r");
305         case 0:
306                 close(pfd[1]);
307                 close(atoi(strrchr(fds[0], '/')+1));
308                 close(atoi(strrchr(fds[1], '/')+1));
309                 close(atoi(strrchr(fds[2], '/')+1));
310
311                 if(getwd(buf, sizeof(buf)) == 0)
312                         sysfatal("no working directory: %r");
313
314                 rfork(RFENVG|RFNAMEG);
315
316                 if(mount(pfd[0], -1, "/", mflag, "") == -1)
317                         sysfatal("mount /: %r");
318
319                 /* replace std fds with the exported ones */
320                 close(0); open(fds[0], OREAD);
321                 close(1); open(fds[1], OWRITE);
322                 close(2); open(fds[2], OWRITE);
323
324                 bind("#c/pid", "/dev/pid", MREPL);
325                 bind("#c/ppid", "/dev/ppid", MREPL);
326                 bind("#e", "/env", MREPL|MCREATE);
327                 bind("#d", "/fd", MREPL);
328
329                 if(chdir(buf) < 0)
330                         sysfatal("chdir: %r");
331
332                 exec(*argv, argv);
333                 if(**argv != '/' && strncmp(*argv, "./", 2) != 0 && strncmp(*argv, "../", 3) != 0)
334                         exec(smprint("/bin/%s", *argv), argv);
335                 sysfatal("exec: %r");
336         default:
337                 close(pfd[0]);
338         }
339
340         /* isolate us from interrupts */
341         rfork(RFNOTEG);
342         if(pipe(efd) < 0)
343                 sysfatal("pipe: %r");
344
345         /* spawn exportfs */
346         switch(expid = fork()) {
347         default:
348                 close(efd[0]);
349                 close(atoi(strrchr(fds[0], '/')+1));
350                 close(atoi(strrchr(fds[1], '/')+1));
351                 close(atoi(strrchr(fds[2], '/')+1));
352                 break;
353         case -1:
354                 sysfatal("fork: %r");
355         case 0:
356                 dup(efd[0], 0);
357                 dup(efd[0], 1);
358                 close(efd[0]);
359                 close(efd[1]);
360                 close(pfd[1]);
361                 if(dbg){
362                         execl("/bin/exportfs", "exportfs", "-df", dbfile, "-r", "/", nil);
363                 } else {
364                         execl("/bin/exportfs", "exportfs", "-r", "/", nil);
365                 }
366                 sysfatal("exec: %r");
367         }
368
369         switch(fspid = fork()) {
370         default:
371                 close(pfd[1]);
372                 close(efd[1]);
373
374                 buf[0] = '\0';
375                 while((w = wait()) != nil && (cpid != -1 || fspid != -1 || expid != -1)){
376                         if(w->pid == fspid)
377                                 fspid = -1;
378                         else if(w->pid == expid)
379                                 expid = -1;
380                         else if(w->pid == cpid){
381                                 cpid = -1;
382                                 strcpy(buf, w->msg);
383                                 if(fspid != -1)
384                                         postnote(PNPROC, fspid, DONESTR);
385                         }
386                         if(buf[0] == '\0')
387                                 strcpy(buf, w->msg);
388                         free(w);
389                 }
390                 exits(buf);
391         case -1:
392                 sysfatal("fork: %r");
393         case 0:
394                 notify(catcher);
395                 break;
396         }
397
398         stats->rpc[Tversion].name = "version";
399         stats->rpc[Tauth].name = "auth";
400         stats->rpc[Tflush].name = "flush";
401         stats->rpc[Tattach].name = "attach";
402         stats->rpc[Twalk].name = "walk";
403         stats->rpc[Topen].name = "open";
404         stats->rpc[Tcreate].name = "create";
405         stats->rpc[Tclunk].name = "clunk";
406         stats->rpc[Tread].name = "read";
407         stats->rpc[Twrite].name = "write";
408         stats->rpc[Tremove].name = "remove";
409         stats->rpc[Tstat].name = "stat";
410         stats->rpc[Twstat].name = "wstat";
411
412         for(n = 0; n < nelem(stats->rpc); n++)
413                 stats->rpc[n].lo = 10000000000LL;
414
415         switch(rspid = rfork(RFPROC|RFMEM)) {
416         case 0:
417                 /* read response from exportfs and pass to mount */
418                 while(!done){
419                         uchar tmp[sizeof(buf)];
420                         Fcall f;
421                         Req **rr;
422
423                         n = read(efd[1], buf, sizeof(buf));
424                         if(n < 0)
425                                 break;
426                         if(n == 0)
427                                 continue;
428
429                         /* convert response */
430                         memset(&f, 0, sizeof(f));
431                         memmove(tmp, buf, n);
432                         if(convM2S(tmp, n, &f) != n)
433                                 sysfatal("convM2S: %r");
434
435                         /* find request to this response */
436                         lock(&rqlock);
437                         for(rr = &rqhead; (r = *rr) != nil; rr = &r->next){
438                                 if(r->f.tag == f.tag){
439                                         *rr = r->next;
440                                         r->next = nil;
441                                         break;
442                                 }
443                         }
444                         stats->nproto += n;
445                         unlock(&rqlock);
446
447                         switch(f.type){
448                         case Ropen:
449                         case Rcreate:
450                                 ropen(&r->f, &f);
451                                 break;
452                         case Rclunk:
453                         case Rremove:
454                                 rclunk(&r->f);
455                                 break;
456                         case Rattach:
457                                 rattach(&r->f, &f);
458                                 break;
459                         case Rwalk:
460                                 rwalk(&r->f, &f);
461                                 break;
462                         case Rread:
463                         case Rwrite:
464                                 rio(&r->f, &f);
465                                 break;
466                         }
467
468                         rpc = &stats->rpc[r->f.type];
469                         update(rpc, r->t);
470                         rpc->bout += n;
471                         free(r);
472
473                         if(write(pfd[1], buf, n) != n)
474                                 break;
475                 }
476                 exits(nil);
477         default:
478                 /* read request from mount and pass to exportfs */
479                 while(!done){
480                         n = read(pfd[1], buf, sizeof(buf));
481                         if(n < 0)
482                                 break;
483                         if(n == 0)
484                                 continue;
485
486                         r = mallocz(sizeof(*r) + n, 1);
487                         memmove(r->buf, buf, n);
488                         if(convM2S(r->buf, n, &r->f) != n)
489                                 sysfatal("convM2S: %r");
490
491                         rpc = &stats->rpc[r->f.type];
492                         rpc->count++;
493                         rpc->bin += n;
494
495                         lock(&rqlock);
496                         stats->nrpc++;
497                         stats->nproto += n;
498                         r->next = rqhead;
499                         rqhead = r;
500                         unlock(&rqlock);
501
502                         r->t = nsec();
503
504                         if(write(efd[1], buf, n) != n)
505                                 break;
506                 }
507         }
508
509         /* shutdown */
510         done = 1;
511         postnote(PNPROC, rspid, DONESTR);
512         close(pfd[1]);
513         close(efd[1]);
514
515         /* dump statistics */
516         rpc = &stats->rpc[Tread];
517         brpsec = (double)stats->totread / (((float)rpc->time/1e9)+.000001);
518
519         rpc = &stats->rpc[Twrite];
520         bwpsec = (double)stats->totwrite / (((float)rpc->time/1e9)+.000001);
521
522         ttime = 0;
523         for(n = 0; n < nelem(stats->rpc); n++) {
524                 rpc = &stats->rpc[n];
525                 if(rpc->count == 0)
526                         continue;
527                 ttime += rpc->time;
528         }
529
530         bppsec = (double)stats->nproto / ((ttime/1e9)+.000001);
531
532         fprint(2, "\nread      %llud bytes, %g Kb/sec\n", stats->totread, brpsec/1024.0);
533         fprint(2, "write     %llud bytes, %g Kb/sec\n", stats->totwrite, bwpsec/1024.0);
534         fprint(2, "protocol  %llud bytes, %g Kb/sec\n", stats->nproto, bppsec/1024.0);
535         fprint(2, "rpc       %lud count\n\n", stats->nrpc);
536
537         fprint(2, "%-10s %5s %5s %5s %5s %5s          T       R\n", 
538               "Message", "Count", "Low", "High", "Time", "Averg");
539
540         for(n = 0; n < nelem(stats->rpc); n++) {
541                 rpc = &stats->rpc[n];
542                 if(rpc->count == 0)
543                         continue;
544                 fprint(2, "%-10s %5lud %5llud %5llud %5llud %5llud ms %8llud %8llud bytes\n", 
545                         rpc->name, 
546                         rpc->count,
547                         rpc->lo/1000000,
548                         rpc->hi/1000000,
549                         rpc->time/1000000,
550                         rpc->time/1000000/rpc->count,
551                         rpc->bin,
552                         rpc->bout);
553         }
554
555         fprint(2, "\nOpens    Reads  (bytes)   Writes  (bytes) File\n");
556         for(fs = stats->file; fs < &stats->file[Maxfile]; fs++){
557                 if(fs->nopen == 0)
558                         break;
559
560                 if(strcmp(fs->path, fds[0]) == 0)
561                         fs->path = "stdin";
562                 else if(strcmp(fs->path, fds[1]) == 0)
563                         fs->path = "stdout";
564                 else if(strcmp(fs->path, fds[2]) == 0)
565                         fs->path = "stderr";
566
567                 fprint(2, "%5lud %8lud %8llud %8lud %8llud %s\n",
568                         fs->nopen,
569                         fs->nread, fs->bread,
570                         fs->nwrite, fs->bwrite,
571                         fs->path);
572         }
573
574         exits(nil);
575 }