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