Import TBBT (NFS trace replay).
[bluesky.git] / TBBT / trace_init / nfsscan.pl
1 #!/usr/bin/perl -w\r
2 #\r
3 # Copyright (c) 2002-2003\r
4 #      The President and Fellows of Harvard College.\r
5 #\r
6 # Redistribution and use in source and binary forms, with or without\r
7 # modification, are permitted provided that the following conditions\r
8 # are met:\r
9 # 1. Redistributions of source code must retain the above copyright\r
10 #    notice, this list of conditions and the following disclaimer.\r
11 # 2. Redistributions in binary form must reproduce the above copyright\r
12 #    notice, this list of conditions and the following disclaimer in the\r
13 #    documentation and/or other materials provided with the distribution.\r
14 # 3. Neither the name of the University nor the names of its contributors\r
15 #    may be used to endorse or promote products derived from this software\r
16 #    without specific prior written permission.\r
17 #\r
18 # THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND\r
19 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
20 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
21 # ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE\r
22 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\r
23 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\r
24 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\r
25 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
26 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\r
27 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\r
28 # SUCH DAMAGE.\r
29 #\r
30 # $Id: nfsscan,v 1.18 2003/07/28 14:27:16 ellard Exp $\r
31 \r
32 $ProgDir = $0;\r
33 $ProgDir =~ /(^.*)\//;\r
34 $ProgDir = $1;\r
35 if (!$ProgDir) {\r
36         $ProgDir = ".";\r
37 }\r
38 \r
39 require "$ProgDir/nfsdump.pl";\r
40 require "$ProgDir/userUtils.pl";\r
41 require "$ProgDir/hier.pl";\r
42 require "$ProgDir/counts.pl";\r
43 require "$ProgDir/latency.pl";\r
44 require "$ProgDir/key.pl";\r
45 require "$ProgDir/common.pl";\r
46 \r
47 use Getopt::Std;\r
48 \r
49 $INTERVAL       = 5 * 60;               # in seconds (5 minutes)\r
50 \r
51 %KeysSeen       = ();\r
52 \r
53 @ADD_USERS      = ();\r
54 @DEL_USERS      = ();\r
55 @ADD_GROUPS     = ();\r
56 @DEL_GROUPS     = ();\r
57 @ADD_CLIENTS    = ();\r
58 @DEL_CLIENTS    = ();\r
59 \r
60 $DO_COUNTS      = 1;\r
61 $DO_LATENCY     = 0;\r
62 $DO_FILES       = 0;\r
63 $DO_PATHS       = 0;\r
64 $DO_SQUEEZE     = 0;\r
65 \r
66 $FH_TYPE        = 'unknown';\r
67 \r
68 $END_TIME       = -1;\r
69 $START_TIME     = -1;\r
70 $NOW            = -1;\r
71 $UseClient      = 0;\r
72 $UseFH          = 0;\r
73 $UseUID         = 0;\r
74 $UseGID         = 0;\r
75 $OMIT_ZEROS     = 0;\r
76 \r
77 $OutFileBaseName        = undef;\r
78 \r
79 $nextPruneTime          = -1;\r
80 $PRUNE_INTERVAL         = 1 * 60;       # One minute.\r
81 \r
82 # &&&\r
83 # Is this really the right default set of operations?\r
84 \r
85 $DEF_OPLIST     = 'read,write,lookup,getattr,access,create,remove';\r
86 @OPLIST         = ('TOTAL', 'INTERESTING', \r
87                         split (/,/, $DEF_OPLIST));\r
88 %OPARRAY        = ();\r
89 \r
90 $Usage =<< ".";\r
91 \r
92 Usage: $0 [options] [trace1 [trace2 ...]]\r
93 \r
94 If no trace files are specified, then the trace is read from stdin.\r
95 \r
96 Command line options:\r
97 \r
98 -h              Print usage message and exit.\r
99 \r
100 -B [CFUG]       Compute per-Client, per-File, per-User, or per-Group info.\r
101 \r
102 -c c1[,c2]*     Include only activity performed by the specified clients.\r
103 \r
104 -C c1[,c2]*     Exclude activity performed by the specified clients.\r
105 \r
106 -d              Compute per-directory statistics.  This implicitly\r
107                 enables -BF so that per-file info is computed.\r
108 \r
109 -f              Do file info tracking.  This implicitly enables -BF so\r
110                 that per-File info is computed.\r
111 \r
112 -F fhtype       Specify the file handle type used by the server.\r
113                 (advfs or netapp)\r
114 \r
115 -g g1[,g2]*     Include only activity performed by the specified groups.\r
116 \r
117 -G g1[,g2]*     Exclude activity performed by the specified groups.\r
118 \r
119 -l              Record average operation latency.\r
120 \r
121 -o basename     Write output to files starting with the specified\r
122                 basename.  The "Count" table goes to basename.cnt,\r
123                 "Latency" to basename.lat, and "File" to basename.fil.\r
124                 The default is to write all output to stdout.\r
125 \r
126 -O op[,op]*     Specify the list of "interesting" operations.\r
127                 The default list is:\r
128 \r
129                 read,write,lookup,getattr,access,create,remove\r
130 \r
131                 If the first op starts with +, then the specified list\r
132                 of ops is appended to the default list.  The special\r
133                 pseudo-ops readM and writeM represent the number of\r
134                 bytes read and written, expressed in MB.\r
135 \r
136 -t interval     Time interval for cummulative statistics (such as\r
137                 operation count).  The default is $INTERVAL seconds. \r
138                 If set to 0, then the entire trace is processed.  By\r
139                 default, time is specified in seconds, but if the last\r
140                 character of the interval is any of s, m, h, or d,\r
141                 then the interval is interpreted as seconds, minutes,\r
142                 hours, or days.\r
143 \r
144 -u u1[,u2]*     Include only activity performed by the specified users.\r
145 \r
146 -U u1[,u2]*     Exclude activity performed by the specified users.\r
147 \r
148 -Z              Omit count and latency lines that have a zero total\r
149                 operation count.\r
150 .\r
151 \r
152 \r
153 main ();\r
154 \r
155 sub main {\r
156 \r
157         parseArgs ();\r
158 \r
159         if ($DO_COUNTS) {\r
160                 counts::printTitle (*OUT_COUNTS);\r
161         }\r
162 \r
163         if ($DO_LATENCY) {\r
164                 latency::printTitle (*OUT_LATENCY);\r
165         }\r
166 \r
167         counts::resetOpCounts ();\r
168 \r
169         my $cmdbuf = 'rm -f noattrdirdiscard noattrdir-root';\r
170         system($cmdbuf);\r
171 \r
172         readTrace ();\r
173 }\r
174 \r
175 sub parseArgs {\r
176 \r
177         my $cmdline = "$0 " . join (' ', @ARGV);\r
178 \r
179         my $Options = "B:dfF:g:G:hlO:o:t:u:U:SR:Z";\r
180         if (! getopts ($Options)) {\r
181                 print STDERR "$0: Incorrect usage.\n";\r
182                 print STDERR $Usage;\r
183         exit (1);\r
184         }\r
185         if (defined $opt_h) {\r
186                 print $Usage;\r
187                 exit (0);\r
188         }\r
189 \r
190         #RFS: neednot input arguments\r
191         $opt_o = "test";\r
192         $opt_f = 1;\r
193         $opt_t = 0;\r
194         #$opt_F = 'RFSNN'; # advfs or netapp\r
195 \r
196         if (defined $opt_B) {\r
197                 $UseClient = ($opt_B =~ /C/);\r
198                 $UseFH = ($opt_B =~ /F/);\r
199                 $UseUID = ($opt_B =~ /U/);\r
200                 $UseGID = ($opt_B =~ /G/);\r
201         }\r
202 \r
203         if (defined $opt_o) {\r
204                 $OutFileBaseName = $opt_o;\r
205         }\r
206 \r
207         if (defined $opt_O) {\r
208                 if ($opt_O =~ /^\+(.*)/) {\r
209                         @OPLIST = (@OPLIST, split (/,/, $1));\r
210                 }\r
211                 else {\r
212                         @OPLIST = ('TOTAL', 'INTERESTING', split (/,/, $opt_O));\r
213                 }\r
214                 # Error checking?\r
215         }\r
216 \r
217         if (defined $opt_l) {\r
218                 $DO_LATENCY = 1;\r
219         }\r
220 \r
221         if (defined $opt_t) {\r
222                 if ($INTERVAL =~ /([0-9]*)([smhd])/) {\r
223                         my $n = $1;\r
224                         my $unit = $2;\r
225 \r
226                         if ($unit eq 's') {\r
227                                 $INTERVAL = $opt_t;\r
228                         }\r
229                         elsif ($unit eq 'm') {\r
230                                 $INTERVAL = $opt_t * 60;\r
231                         }\r
232                         elsif ($unit eq 'h') {\r
233                                 $INTERVAL = $opt_t * 60 * 60;\r
234                         }\r
235                         elsif ($unit eq 'd') {\r
236                                 $INTERVAL = $opt_t * 24 * 60 * 60;\r
237                         }\r
238                 }\r
239                 else {\r
240                         $INTERVAL = $opt_t;\r
241                 }\r
242         }\r
243 \r
244         $DO_PATHS = (defined $opt_d);\r
245         $DO_FILES = (defined $opt_f);\r
246         $DO_SQUEEZE = (defined $opt_S);\r
247         $OMIT_ZEROS = (defined $opt_Z);\r
248 \r
249         $TIME_ROUNDING = (defined $opt_R) ? $opt_R : 0;\r
250 \r
251         if (defined $opt_F) {\r
252                 $FH_TYPE = $opt_F;\r
253         }\r
254 \r
255         if (defined $opt_c) {\r
256                 @ADD_CLIENTS = split (/,/, $opt_c);\r
257         }\r
258         if (defined $opt_C) {\r
259                 @DEL_CLIENTS = split (/,/, $opt_c);\r
260         }\r
261 \r
262         if (defined $opt_g) {\r
263                 @ADD_GROUPS = groups2gids (split (/,/, $opt_g));\r
264         }\r
265         if (defined $opt_G) {\r
266                 @DEL_GROUPS = groups2gids (split (/,/, $opt_G));\r
267         }\r
268 \r
269         if (defined $opt_u) {\r
270                 @ADD_USERS = logins2uids (split (/,/, $opt_u));\r
271         }\r
272         if (defined $opt_U) {\r
273                 @DEL_USERS = logins2uids (split (/,/, $opt_U));\r
274         }\r
275 \r
276 \r
277         # Now that we know what options the user asked for, initialize\r
278         # things accordingly.\r
279 \r
280         if ($DO_PATHS || $DO_FILES) {\r
281                 $UseFH = 1;\r
282         }\r
283 \r
284         if ($DO_LATENCY) {\r
285                 latency::init (@OPLIST);\r
286         }\r
287 \r
288         if ($DO_COUNTS) {\r
289                 counts::init (@OPLIST);\r
290         }\r
291 \r
292         if (defined $OutFileBaseName) {\r
293                 if ($DO_COUNTS) {\r
294                         open (OUT_COUNTS, ">$OutFileBaseName.cnt") ||\r
295                                 die "Can't create $OutFileBaseName.cnt.";\r
296                         print OUT_COUNTS "#cmdline $cmdline\n";\r
297                 }\r
298                 if ($DO_LATENCY) {\r
299                         open (OUT_LATENCY, ">$OutFileBaseName.lat") ||\r
300                                 die "Can't create $OutFileBaseName.lat.";\r
301                         print OUT_LATENCY "#cmdline $cmdline\n";\r
302                 }\r
303                 if ($DO_FILES) {\r
304                         open (OUT_FILES, ">$OutFileBaseName.fil") ||\r
305                                 die "Can't create $OutFileBaseName.fil.";\r
306                         print OUT_FILES "#cmdline $cmdline\n";\r
307                 }\r
308                 if ($DO_PATHS) {\r
309                         open (OUT_PATHS, ">$OutFileBaseName.pat") ||\r
310                                 die "Can't create $OutFileBaseName.pat.";\r
311                         print OUT_PATHS "#cmdline $cmdline\n";\r
312                 }\r
313         }\r
314         else {\r
315                 *OUT_COUNTS = STDOUT;\r
316                 *OUT_LATENCY = STDOUT;\r
317                 *OUT_FILES = STDOUT;\r
318                 *OUT_PATHS = STDOUT;\r
319 \r
320                 print STDOUT "#cmdline $cmdline\n";\r
321         }\r
322 \r
323         foreach my $op ( @OPLIST ) {\r
324                 $OPARRAY{$op} = 1;\r
325         }\r
326 \r
327         return ;\r
328 }\r
329 \r
330 sub readTrace {\r
331         my (@args) = @_;\r
332 \r
333         while (my $line = <>) {\r
334 \r
335                 $hier::rfsLineNum++;\r
336                 if ( ($hier::rfsLineNum % 1000) eq 0) {\r
337                         print STDERR "$hier::rfsLineNum\n";\r
338                 }\r
339 \r
340 \r
341                 if ($line =~ /SHORT\ PACKET/) {\r
342                         next;\r
343                 }\r
344 \r
345                 my ($proto, $op, $xid, $client, $now, $response) =\r
346                                 nfsd::nfsDumpParseLineHeader ($line);\r
347                 $NOW = $now;\r
348 \r
349                 # NOTE:  This next bit of logic requires a little\r
350                 # extra attention.  We want to discard lines as\r
351                 # quickly as we can if they're not "interesting". \r
352                 # However, different lines are interesting in\r
353                 # different contexts, so the order of the tests and\r
354                 # the manner in which they are interspersed with\r
355                 # subroutine calls to pluck info from the lines is\r
356                 # very important.\r
357 \r
358                 # Check whether it is a line that we should prune and\r
359                 # ignore, because of the filters.\r
360                 \r
361                 next if (($op eq 'C3' || $op eq 'C2') &&\r
362                                 ! pruneCall ($line, $client));\r
363 \r
364                 if ($DO_PATHS || $DO_FILES) {\r
365                         hier::processLine ($line,\r
366                                         $proto, $op, $xid, $client,\r
367                                         $now, $response, $FH_TYPE);\r
368                 }\r
369 \r
370                 my $key = key::makeKey ($line, $proto, $op,\r
371                                 $xid, $client, $now,\r
372                                 $UseClient, $UseFH, $UseUID, $UseGID,\r
373                                 $FH_TYPE);\r
374                 if (! defined $key) {\r
375                         next ;\r
376                 }\r
377                 $KeysSeen{$key} = 1;\r
378 \r
379                 # Count everything towards the total, but only\r
380                 # do the rest of the processing for things\r
381                 # that are "interesting".\r
382 \r
383                 if ($proto eq 'C3' || $proto eq 'C2') {\r
384                         $counts::OpCounts{"$key,TOTAL"}++;\r
385                         $counts::KeysSeen{$key} = 1;\r
386 \r
387                         next if (! exists $OPARRAY{$op});\r
388 \r
389                         $counts::OpCounts{"$key,$op"}++;\r
390                         $counts::OpCounts{"$key,INTERESTING"}++;\r
391                 }\r
392 \r
393                 if ($op eq 'read' && exists $OPARRAY{'readM'}) {\r
394                         doReadSize ($line, $proto, $op, $key, $client, $xid, $response, $now);\r
395                 }\r
396 \r
397                 if ($op eq 'write' && exists $OPARRAY{'writeM'}) {\r
398                         doWriteSize ($line, $proto, $op, $key, $client, $xid, $response, $now);\r
399                 }\r
400 \r
401                 if ($DO_LATENCY) {\r
402                         latency::update ($key, $proto, $op,\r
403                                         $xid, $client, $now);\r
404                 }\r
405 \r
406                 if ($END_TIME < 0) {\r
407                         $START_TIME = findStartTime ($NOW, $TIME_ROUNDING);\r
408                         $END_TIME = $START_TIME + $INTERVAL;\r
409                 }\r
410 \r
411                 # Note that this is a loop, because if the interval is\r
412                 # short enough, or the system is very idle (or there's\r
413                 # a filter in place that makes it look idle), entire\r
414                 # intervals can go by without anything happening at\r
415                 # all.  Some tools can get confused if intervals are\r
416                 # missing from the table, so we emit them anyway.\r
417 \r
418                 while (($INTERVAL > 0) && ($NOW >= $END_TIME)) {\r
419                         printAll ($START_TIME);\r
420 \r
421                         counts::resetOpCounts ();\r
422                         latency::resetOpCounts ();\r
423 \r
424                         $START_TIME += $INTERVAL;\r
425                         $END_TIME = $START_TIME + $INTERVAL;\r
426                 }\r
427 \r
428                 if ($now > $nextPruneTime) {\r
429                         key::prunePending ($now - $PRUNE_INTERVAL);\r
430                         latency::prunePending ($now - $PRUNE_INTERVAL);\r
431 \r
432                         prunePending ($now - $PRUNE_INTERVAL);\r
433 \r
434                         $nextPruneTime = $now + $PRUNE_INTERVAL;\r
435                 }\r
436         }\r
437 \r
438         # Squeeze out the last little bit, if there's anything that we\r
439         # counted but did not emit.  If DO_SQUEEZE is true, then\r
440         # always do this.  Otherwise, only squeeze out the results of\r
441         # the last interval if the interval is "almost" complete (ie\r
442         # within 10 seconds of the end).\r
443 \r
444         if (($NOW > $START_TIME) && ($DO_SQUEEZE || (($END_TIME - $NOW) < 10))) {\r
445                 printAll ($START_TIME);\r
446                 counts::resetOpCounts ();\r
447         }\r
448 \r
449         print "#T endtime = $NOW\n";\r
450 \r
451 }\r
452 \r
453 sub printAll {\r
454         my ($start_time) = @_;\r
455 \r
456         if ($DO_COUNTS) {\r
457                 counts::printOps ($start_time, *OUT_COUNTS);\r
458         }\r
459 \r
460         if ($DO_LATENCY) {\r
461                 latency::printOps ($start_time, *OUT_LATENCY);\r
462         }\r
463 \r
464         if ($DO_FILES) {\r
465                 hier::printAll ($start_time, *OUT_FILES);\r
466         }\r
467 \r
468         if ($DO_PATHS) {\r
469                 printPaths ($start_time, *OUT_PATHS);\r
470         }\r
471 }\r
472 \r
473 sub pruneCall {\r
474         my ($line, $client) = @_;\r
475 \r
476         if (@ADD_USERS > 0 || @DEL_USERS > 0) {\r
477                 my $c_uid = nfsd::nfsDumpParseLineField ($line, 'euid');\r
478                 if (! defined ($c_uid)) {\r
479                         return 0;\r
480                 }\r
481                 $c_uid = hex ($c_uid);\r
482 \r
483                 if (@ADD_USERS && !grep (/^$c_uid$/, @ADD_USERS)) {\r
484                         return 0;\r
485                 }\r
486                 if (@DEL_USERS && grep (/^$c_uid$/, @DEL_USERS)) {\r
487                         return 0;\r
488                 }\r
489         }\r
490 \r
491         if (@ADD_GROUPS > 0 || @DEL_GROUPS > 0) {\r
492                 my $c_gid = nfsd::nfsDumpParseLineField ($line, 'egid');\r
493                 if (! defined ($c_gid)) {\r
494                         return 0;\r
495                 }\r
496                 $c_gid = hex ($c_gid);\r
497 \r
498                 if (@ADD_GROUPS && !grep (/^$c_gid$/, @ADD_GROUPS)) {\r
499                         return 0;\r
500                 }\r
501                 if (@DEL_GROUPS && grep (/^$c_gid$/, @DEL_GROUPS)) {\r
502                         return 0;\r
503                 }\r
504         }\r
505 \r
506         if (@ADD_CLIENTS > 0 || @DEL_CLIENTS > 0) {\r
507                 if (@ADD_CLIENTS && !grep (/^$client$/, @ADD_CLIENTS)) {\r
508                         return 0;\r
509                 }\r
510                 if (@DEL_CLIENTS && grep (/^$client$/, @DEL_CLIENTS)) {\r
511                         return 0;\r
512                 }\r
513         }\r
514 \r
515         return 1;\r
516 }\r
517 \r
518 %PathOpCounts   = ();\r
519 %PathsSeen      = ();\r
520 \r
521 sub buildDirPath {\r
522         my ($fh, $key) = @_;\r
523         my $pfh;\r
524         my $cnt;\r
525 \r
526         foreach my $op ( @OPLIST ) {\r
527                 if (exists $counts::OpCounts{"$key,$op"}) {\r
528                         $cnt = $counts::OpCounts{"$key,$op"};\r
529                 }\r
530                 else {\r
531                         $cnt = 0;\r
532                 }\r
533                 $PathOpCounts{"$fh,$op"} = $cnt;\r
534 \r
535                 $PathsSeen{$fh} = 1;\r
536 \r
537                 $pfh = $fh;\r
538                 my $len = 0;\r
539                 while (defined ($pfh = $hier::fh2Parent{$pfh})) {\r
540 \r
541                         if ($len++ > 20) {\r
542                                 print "Really long path ($fh)\n";\r
543                                 last;\r
544                         }\r
545 \r
546                         if (exists $PathOpCounts{"$pfh,$op"}) {\r
547                                 $PathOpCounts{"$pfh,$op"} += $cnt;\r
548                         }\r
549                         else {\r
550                                 $PathOpCounts{"$pfh,$op"} = $cnt;\r
551                         }\r
552                         $PathsSeen{$pfh} = 1;\r
553                 }\r
554         }\r
555 \r
556         return ;\r
557 }\r
558 \r
559 sub printPaths {\r
560         my ($start_time, $out) = @_;\r
561 \r
562         my $str = "#D time Dir/File dircnt path fh";\r
563         foreach my $op ( @OPLIST ) {\r
564                 $str .= " $op";\r
565         }\r
566         $str .= "\n";\r
567 \r
568         print $out $str;\r
569 \r
570         undef %PathsSeen;\r
571 \r
572         foreach my $key ( keys %KeysSeen ) {\r
573                 my ($client_id, $fh, $euid, $egid) = split (/,/, $key);\r
574 \r
575                 buildDirPath ($fh, $key);\r
576         }\r
577 \r
578         foreach my $fh ( keys %PathsSeen ) {\r
579                 my ($path, $cnt) = hier::findPath ($fh);\r
580 \r
581                 if ($cnt == 0) {\r
582                         $path = ".";\r
583                 }\r
584 \r
585                 my $type = (exists $hier::fhIsDir{$fh} && $hier::fhIsDir{$fh}==2) ? 'D' : 'F';\r
586 \r
587                 my $str = "$cnt $type $path $fh ";\r
588 \r
589                 foreach my $op ( @OPLIST ) {\r
590                         my $cnt;\r
591 \r
592                         if (exists $PathOpCounts{"$fh,$op"}) {\r
593                                 $cnt = $PathOpCounts{"$fh,$op"};\r
594                         }\r
595                         else {\r
596                                 print "Missing $fh $op\n";\r
597                                 $cnt = 0;\r
598                         }\r
599 \r
600                         $str .= " $cnt";\r
601 \r
602                         $PathOpCounts{"$fh,$op"} = 0;   # &&& reset\r
603                 }\r
604 \r
605                 print $out "D $start_time $str\n";\r
606         }\r
607 }\r
608 \r
609 %uxid2key       = ();\r
610 %uxid2time      = ();\r
611 \r
612 sub doReadSize {\r
613         my ($line, $proto, $op, $key, $client, $xid, $response, $time) = @_;\r
614 \r
615         my $uxid = "$client-$xid";\r
616 \r
617         if ($proto eq 'C3' || $proto eq 'C2') {\r
618                 $uxid2time{$uxid} = $time;\r
619                 $uxid2key{$uxid} = $key;\r
620         }\r
621         else {\r
622                 if (! exists $uxid2key{$uxid}) {\r
623                         return ;\r
624                 }\r
625                 if ($response ne 'OK') {\r
626                         return ;\r
627                 }\r
628 \r
629                 $key = $uxid2key{$uxid};\r
630                 my $count = nfsd::nfsDumpParseLineField ($line, 'count');\r
631                 $count = hex ($count);\r
632 \r
633                 delete $uxid2key{$uxid};\r
634                 delete $uxid2time{$uxid};\r
635 \r
636                 $counts::OpCounts{"$key,readM"} += $count;\r
637         }\r
638 }\r
639 \r
640 # Note that we always just assume that writes succeed, because on most\r
641 # systems they virtually always do.  If you're tracing a system where\r
642 # your users are constantly filling up the disk or exceeding their\r
643 # quotas, then you will need to fix this.\r
644 \r
645 sub doWriteSize {\r
646         my ($line, $proto, $op, $key, $client, $xid, $response, $time) = @_;\r
647 \r
648         if ($proto eq 'C3' || $proto eq 'C2') {\r
649 \r
650                 my $tag = ($proto eq 'C3') ? 'count' : 'tcount';\r
651 \r
652                 my $count = nfsd::nfsDumpParseLineField ($line, $tag);\r
653 \r
654                 if (! $count) {\r
655                         printf "WEIRD count $line\n";\r
656                 }\r
657 \r
658                 $count = hex ($count);\r
659 \r
660                 $counts::OpCounts{"$key,writeM"} += $count;\r
661         }\r
662 }\r
663 \r
664 \r
665 # Purge all the pending XID records dated earlier than $when (which is\r
666 # typically at least $PRUNE_INTERVAL seconds ago).  This is important\r
667 # because otherwise missing XID records can pile up, eating a lot of\r
668 # memory. \r
669   \r
670 sub prunePending {\r
671         my ($when) = @_;\r
672 \r
673         foreach my $uxid ( keys %uxid2time ) {\r
674                 if ($uxid2time{$uxid} < $when) {\r
675                         delete $uxid2key{$uxid};\r
676                         delete $uxid2time{$uxid};\r
677                 }\r
678         }\r
679 \r
680         return ;\r
681 }\r
682 \r