|
1 #!/usr/bin/perl -w |
|
2 # |
|
3 # This Source Code Form is subject to the terms of the Mozilla Public |
|
4 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
6 |
|
7 # bloattable [-debug] [-source] [-byte n|-obj n|-ref n] <file1> <file2> ... <filen> > <html-file> |
|
8 # |
|
9 # file1, file2, ... filen should be successive BloatView files generated from the same run. |
|
10 # Summarize them in an HTML table. Output the HTML to the standard output. |
|
11 # |
|
12 # If -debug is set, create a slightly larger html file which is more suitable for debugging this script. |
|
13 # If -source is set, create an html file that prints the html source as the output |
|
14 # If -byte n, -obj n, or -ref n is given, make the page default to showing byte, object, or reference statistics, |
|
15 # respectively, and sort by the nth column (n is zero-based, so the first column has n==0). |
|
16 # |
|
17 # See http://lxr.mozilla.org/mozilla/source/xpcom/doc/MemoryTools.html |
|
18 |
|
19 use 5.004; |
|
20 use strict; |
|
21 use diagnostics; |
|
22 use File::Basename; |
|
23 use Getopt::Long; |
|
24 |
|
25 # The generated HTML is almost entirely generated by a script. Only the <HTML>, <HEAD>, and <BODY> elements are explicit |
|
26 # because a <SCRIPT> element cannot officially be a direct descendant of an <HTML> element. |
|
27 # The script itself is almost all generated by an eval of a large string. This allows the script to reproduce itself |
|
28 # when making a new page using document.write's. Re-sorting the page causes it to regenerate itself in this way. |
|
29 |
|
30 |
|
31 |
|
32 # Return the file's modification date. |
|
33 sub fileModDate($) { |
|
34 my ($pathName) = @_; |
|
35 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = |
|
36 stat $pathName or die "Can't stat '$pathName'"; |
|
37 return $mtime; |
|
38 } |
|
39 |
|
40 |
|
41 sub fileCoreName($) { |
|
42 my ($pathName) = @_; |
|
43 my $fileName = basename($pathName, ""); |
|
44 $fileName =~ s/\..*//; |
|
45 return $fileName; |
|
46 } |
|
47 |
|
48 |
|
49 # Convert a raw string into a single-quoted JavaScript string. |
|
50 sub singleQuoteString($) { |
|
51 local ($_) = @_; |
|
52 s/\\/\\\\/g; |
|
53 s/'/\\'/g; |
|
54 s/\n/\\n/g; |
|
55 s/<\//<\\\//g; |
|
56 return "'$_'"; |
|
57 } |
|
58 |
|
59 |
|
60 # Convert a raw string into a double-quoted JavaScript string. |
|
61 sub doubleQuoteString($) { |
|
62 local ($_) = @_; |
|
63 s/\\/\\\\/g; |
|
64 s/"/\\"/g; |
|
65 s/\n/\\n/g; |
|
66 s/<\//<\\\//g; |
|
67 return "\"$_\""; |
|
68 } |
|
69 |
|
70 |
|
71 # Quote special HTML characters in the string. |
|
72 sub quoteHTML($) { |
|
73 local ($_) = @_; |
|
74 s/\&/&/g; |
|
75 s/</</g; |
|
76 s/>/>/g; |
|
77 s/ / /g; |
|
78 s/\n/<BR>\n/g; |
|
79 return $_; |
|
80 } |
|
81 |
|
82 |
|
83 # Write the generated page to the standard output. |
|
84 # The script source code is read from this file past the __END__ marker |
|
85 # @$scriptData is the JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript. |
|
86 # @$persistentScriptData is the same as @scriptData, but persists when the page reloads itself. |
|
87 # If $debug is true, generate the script directly instead of having it eval itself. |
|
88 # If $source is true, generate a script that displays the page's source instead of the page itself. |
|
89 sub generate(\@\@$$$$) { |
|
90 my ($scriptData, $persistentScriptData, $debug, $source, $showMode, $sortColumn) = @_; |
|
91 |
|
92 my @scriptSource = <DATA>; |
|
93 chomp @scriptSource; |
|
94 print <<'EOS'; |
|
95 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> |
|
96 <HTML> |
|
97 <HEAD> |
|
98 <SCRIPT type="text/javascript"> |
|
99 EOS |
|
100 |
|
101 foreach (@$scriptData) {print "$_\n";} |
|
102 print "\n"; |
|
103 |
|
104 print "var srcArray = [\n"; |
|
105 my @quotedScriptSource = map { |
|
106 my $line = $_; |
|
107 $line =~ s/^\s+//g; |
|
108 # $line =~ s/^\/\/SOURCE\s+//g if $source; |
|
109 $line =~ s/^\/\/.*//g; |
|
110 $line =~ s/\s+$//g; |
|
111 $line eq "" ? () : $line |
|
112 } @$persistentScriptData, @scriptSource; |
|
113 my $lastQuotedLine = pop @quotedScriptSource; |
|
114 foreach (@quotedScriptSource) {print doubleQuoteString($_), ",\n";} |
|
115 print doubleQuoteString($lastQuotedLine), "];\n\n"; |
|
116 |
|
117 if ($debug) { |
|
118 push @quotedScriptSource, $lastQuotedLine; |
|
119 foreach (@quotedScriptSource) { |
|
120 s/<\//<\\\//g; # This fails if a regexp ends with a '<'. Oh well.... |
|
121 print "$_\n"; |
|
122 } |
|
123 print "\n"; |
|
124 } else { |
|
125 print "eval(srcArray.join(\"\\n\"));\n\n"; |
|
126 } |
|
127 print "showMode = $showMode;\n"; |
|
128 print "sortColumn = $sortColumn;\n"; |
|
129 if ($source) { |
|
130 print <<'EOS'; |
|
131 function writeQuotedHTML(s) { |
|
132 document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n')); |
|
133 } |
|
134 |
|
135 var quotingDocument = { |
|
136 write: function () { |
|
137 for (var i = 0; i < arguments.length; i++) |
|
138 writeQuotedHTML(arguments[i]); |
|
139 }, |
|
140 writeln: function () { |
|
141 for (var i = 0; i < arguments.length; i++) |
|
142 writeQuotedHTML(arguments[i]); |
|
143 document.writeln('<BR>'); |
|
144 } |
|
145 }; |
|
146 EOS |
|
147 } else { |
|
148 print "showHead(document);\n"; |
|
149 } |
|
150 print "</SCRIPT>\n"; |
|
151 print "</HEAD>\n\n"; |
|
152 print "<BODY>\n"; |
|
153 if ($source) { |
|
154 print "<P><TT>"; |
|
155 print quoteHTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n"; |
|
156 print quoteHTML "<HTML>\n"; |
|
157 print quoteHTML "<HEAD>\n"; |
|
158 print "<SCRIPT type=\"text/javascript\">showHead(quotingDocument);</SCRIPT>\n"; |
|
159 print quoteHTML "</HEAD>\n\n"; |
|
160 print quoteHTML "<BODY>\n"; |
|
161 print "<SCRIPT type=\"text/javascript\">showBody(quotingDocument);</SCRIPT>\n"; |
|
162 print quoteHTML "</BODY>\n"; |
|
163 print quoteHTML "</HTML>\n"; |
|
164 print "</TT></P>\n"; |
|
165 } else { |
|
166 print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n"; |
|
167 } |
|
168 print "</BODY>\n"; |
|
169 print "</HTML>\n"; |
|
170 } |
|
171 |
|
172 |
|
173 |
|
174 # Read the bloat file into hash table $h. The hash table is indexed by class names; |
|
175 # each entry is a list with the following elements: |
|
176 # bytesAlloc Total number of bytes allocated |
|
177 # bytesNet Total number of bytes allocated but not deallocated |
|
178 # objectsAlloc Total number of objects allocated |
|
179 # objectsNet Total number of objects allocated but not deallocated |
|
180 # refsAlloc Total number of references AddRef'd |
|
181 # refsNet Total number of references AddRef'd but not Released |
|
182 # Except for TOTAL, all hash table entries refer to mutually exclusive data. |
|
183 # $sizes is a hash table indexed by class names. Each entry of that table contains the class's instance size. |
|
184 sub readBloatFile($\%\%) { |
|
185 my ($file, $h, $sizes) = @_; |
|
186 local $_; # Needed for 'while (<FILE>)' below. |
|
187 |
|
188 my $readSomething = 0; |
|
189 open FILE, $file; |
|
190 while (<FILE>) { |
|
191 if (my ($name, $size, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet) = |
|
192 /^\s*(?:\d+)\s+([\w:]+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(-?\d+)\s*\([^()]*\)\s*(\d+)\s+(-?\d+)\s*\([^()]*\)\s*$/) { |
|
193 my $bytesAlloc; |
|
194 if ($name eq "TOTAL") { |
|
195 $size = "undefined"; |
|
196 $bytesAlloc = "undefined"; |
|
197 } else { |
|
198 $bytesAlloc = $objectsAlloc * $size; |
|
199 if ($bytesNet != $objectsNet * $size) { |
|
200 print STDERR "In '$file', class $name bytesNet != objectsNet * size: $bytesNet != $objectsNet * $size\n"; |
|
201 } |
|
202 } |
|
203 print STDERR "Duplicate entry $name in '$file'\n" if $$h{$name}; |
|
204 $$h{$name} = [$bytesAlloc, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet]; |
|
205 |
|
206 my $oldSize = $$sizes{$name}; |
|
207 print STDERR "Mismatch of sizes of class $name: $oldSize and $size\n" if defined($oldSize) && $size ne $oldSize; |
|
208 $$sizes{$name} = $size; |
|
209 $readSomething = 1; |
|
210 } elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) { |
|
211 print STDERR "Unable to parse '$file' line: $_"; |
|
212 } |
|
213 } |
|
214 close FILE; |
|
215 print STDERR "No data in '$file'\n" unless $readSomething; |
|
216 return $h; |
|
217 } |
|
218 |
|
219 |
|
220 my %sizes; # <class-name> => <instance-size> |
|
221 my %tables; # <file-name> => <bloat-table>; see readBloatFile for format of <bloat-table> |
|
222 |
|
223 # Generate the JavaScript source code for the row named $c. $l can contain the initial entries of the row. |
|
224 sub genTableRowSource($$) { |
|
225 my ($l, $c) = @_; |
|
226 my $lastE; |
|
227 foreach (@ARGV) { |
|
228 my $e = $tables{$_}{$c}; |
|
229 if (defined($lastE) && !defined($e)) { |
|
230 $e = [0,0,0,0,0,0]; |
|
231 print STDERR "Class $c is defined in an earlier file but not in '$_'\n"; |
|
232 } |
|
233 if (defined $e) { |
|
234 if (defined $lastE) { |
|
235 for (my $i = 0; $i <= $#$e; $i++) { |
|
236 my $n = $$e[$i]; |
|
237 $l .= ($n eq "undefined" ? "undefined" : $n - $$lastE[$i]) . ","; |
|
238 } |
|
239 $l .= " "; |
|
240 } else { |
|
241 $l .= join(",", @$e) . ", "; |
|
242 } |
|
243 $lastE = $e; |
|
244 } else { |
|
245 $l .= "0,0,0,0,0,0, "; |
|
246 } |
|
247 } |
|
248 $l .= join(",", @$lastE); |
|
249 return "[$l]"; |
|
250 } |
|
251 |
|
252 |
|
253 |
|
254 my $debug; |
|
255 my $source; |
|
256 my $showMode; |
|
257 my $sortColumn; |
|
258 my @modeOptions; |
|
259 |
|
260 GetOptions("debug" => \$debug, "source" => \$source, "byte=i" => \$modeOptions[0], "obj=i" => \$modeOptions[1], "ref=i" => \$modeOptions[2]); |
|
261 for (my $i = 0; $i != 3; $i++) { |
|
262 my $modeOption = $modeOptions[$i]; |
|
263 if ($modeOption) { |
|
264 die "Only one of -byte, -obj, or -ref may be given" if defined $showMode; |
|
265 my $nFileColumns = scalar(@ARGV) + 1; |
|
266 die "-byte, -obj, or -ref column number out of range" if $modeOption < 0 || $modeOption >= 2 + 2*$nFileColumns; |
|
267 $showMode = $i; |
|
268 if ($modeOption >= 2) { |
|
269 $modeOption -= 2; |
|
270 $sortColumn = 2 + $showMode*2; |
|
271 if ($modeOption >= $nFileColumns) { |
|
272 $sortColumn++; |
|
273 $modeOption -= $nFileColumns; |
|
274 } |
|
275 $sortColumn += $modeOption*6; |
|
276 } else { |
|
277 $sortColumn = $modeOption; |
|
278 } |
|
279 } |
|
280 } |
|
281 unless (defined $showMode) { |
|
282 $showMode = 0; |
|
283 $sortColumn = 0; |
|
284 } |
|
285 |
|
286 # Read all of the bloat files. |
|
287 foreach (@ARGV) { |
|
288 unless ($tables{$_}) { |
|
289 my $f = $_; |
|
290 my %table; |
|
291 |
|
292 readBloatFile $_, %table, %sizes; |
|
293 $tables{$_} = \%table; |
|
294 } |
|
295 } |
|
296 die "No input" unless %sizes; |
|
297 |
|
298 my @scriptData; # JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript. |
|
299 my @persistentScriptData; # Same as @scriptData, but persists the page reloads itself. |
|
300 |
|
301 # Print a list of bloat file names. |
|
302 push @persistentScriptData, "var nFiles = " . scalar(@ARGV) . ";"; |
|
303 push @persistentScriptData, "var fileTags = [" . join(", ", map {singleQuoteString substr(fileCoreName($_), -10)} @ARGV) . "];"; |
|
304 push @persistentScriptData, "var fileNames = [" . join(", ", map {singleQuoteString $_} @ARGV) . "];"; |
|
305 push @persistentScriptData, "var fileDates = [" . join(", ", map {singleQuoteString localtime fileModDate $_} @ARGV) . "];"; |
|
306 |
|
307 # Print the bloat tables. |
|
308 push @persistentScriptData, "var totals = " . genTableRowSource('"TOTAL", undefined, ', "TOTAL") . ";"; |
|
309 push @scriptData, "var classTables = ["; |
|
310 delete $sizes{"TOTAL"}; |
|
311 my @classes = sort(keys %sizes); |
|
312 for (my $i = 0; $i <= $#classes; $i++) { |
|
313 my $c = $classes[$i]; |
|
314 push @scriptData, genTableRowSource(doubleQuoteString($c).", ".$sizes{$c}.", ", $c) . ($i == $#classes ? "];" : ","); |
|
315 } |
|
316 |
|
317 generate(@scriptData, @persistentScriptData, $debug, $source, $showMode, $sortColumn); |
|
318 1; |
|
319 |
|
320 |
|
321 # The source of the eval'd JavaScript follows. |
|
322 # Comments starting with // that are alone on a line are stripped by the Perl script. |
|
323 __END__ |
|
324 |
|
325 // showMode: 0=bytes, 1=objects, 2=references |
|
326 var showMode; |
|
327 var modeName; |
|
328 var modeNameUpper; |
|
329 |
|
330 var sortColumn; |
|
331 |
|
332 // Sort according to the sortColumn. Column 0 is sorted alphabetically in ascending order. |
|
333 // All other columns are sorted numerically in descending order, with column 0 used for a secondary sort. |
|
334 // Undefined is always listed last. |
|
335 function sortCompare(x, y) { |
|
336 if (sortColumn) { |
|
337 var xc = x[sortColumn]; |
|
338 var yc = y[sortColumn]; |
|
339 if (xc < yc || xc === undefined && yc !== undefined) return 1; |
|
340 if (yc < xc || yc === undefined && xc !== undefined) return -1; |
|
341 } |
|
342 |
|
343 var x0 = x[0]; |
|
344 var y0 = y[0]; |
|
345 if (x0 > y0 || x0 === undefined && y0 !== undefined) return 1; |
|
346 if (y0 > x0 || y0 === undefined && x0 !== undefined) return -1; |
|
347 return 0; |
|
348 } |
|
349 |
|
350 |
|
351 // Quote special HTML characters in the string. |
|
352 function quoteHTML(s) { |
|
353 s = s.replace(/&/g, '&'); |
|
354 // Can't use /</g because HTML interprets '</g' as ending the script! |
|
355 s = s.replace(/\x3C/g, '<'); |
|
356 s = s.replace(/>/g, '>'); |
|
357 s = s.replace(/ /g, ' '); |
|
358 return s; |
|
359 } |
|
360 |
|
361 |
|
362 function writeFileTable(d) { |
|
363 d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>'); |
|
364 d.writeln('<TR>\n<TH>Name</TH>\n<TH>File</TH>\n<TH>Date</TH>\n</TR>'); |
|
365 for (var i = 0; i < nFiles; i++) |
|
366 d.writeln('<TR>\n<TD>'+quoteHTML(fileTags[i])+'</TD>\n<TD><TT>'+quoteHTML(fileNames[i])+'</TT></TD>\n<TD>'+quoteHTML(fileDates[i])+'</TD>\n</TR>'); |
|
367 d.writeln('</TABLE>'); |
|
368 } |
|
369 |
|
370 |
|
371 function writeReloadLink(d, column, s, rowspan) { |
|
372 d.write(rowspan == 1 ? '<TH>' : '<TH rowspan='+rowspan+'>'); |
|
373 if (column != sortColumn) |
|
374 d.write('<A href="javascript:reloadSelf('+column+','+showMode+')">'); |
|
375 d.write(s); |
|
376 if (column != sortColumn) |
|
377 d.write('</A>'); |
|
378 d.writeln('</TH>'); |
|
379 } |
|
380 |
|
381 function writeClassTableRow(d, row, base, modeName) { |
|
382 if (modeName) { |
|
383 d.writeln('<TR>\n<TH>'+modeName+'</TH>'); |
|
384 } else { |
|
385 d.writeln('<TR>\n<TD><A href="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD>'); |
|
386 var v = row[1]; |
|
387 d.writeln('<TD class=num>'+(v === undefined ? '' : v)+'</TD>'); |
|
388 } |
|
389 for (var i = 0; i != 2; i++) { |
|
390 var c = base + i; |
|
391 for (var j = 0; j <= nFiles; j++) { |
|
392 v = row[c]; |
|
393 var style = 'num'; |
|
394 if (j != nFiles) |
|
395 if (v > 0) { |
|
396 style = 'pos'; |
|
397 v = '+'+v; |
|
398 } else |
|
399 style = 'neg'; |
|
400 d.writeln('<TD class='+style+'>'+(v === undefined ? '' : v)+'</TD>'); |
|
401 c += 6; |
|
402 } |
|
403 } |
|
404 d.writeln('</TR>'); |
|
405 } |
|
406 |
|
407 function writeClassTable(d) { |
|
408 var base = 2 + showMode*2; |
|
409 |
|
410 // Make a copy because a sort is destructive. |
|
411 var table = classTables.concat(); |
|
412 table.sort(sortCompare); |
|
413 |
|
414 d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>'); |
|
415 |
|
416 d.writeln('<TR>'); |
|
417 writeReloadLink(d, 0, 'Class Name', 2); |
|
418 writeReloadLink(d, 1, 'Instance<BR>Size', 2); |
|
419 d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated</TH>'); |
|
420 d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated but not freed</TH>\n</TR>'); |
|
421 d.writeln('<TR>'); |
|
422 for (var i = 0; i != 2; i++) { |
|
423 var c = base + i; |
|
424 for (var j = 0; j <= nFiles; j++) { |
|
425 writeReloadLink(d, c, j == nFiles ? 'Total' : quoteHTML(fileTags[j]), 1); |
|
426 c += 6; |
|
427 } |
|
428 } |
|
429 d.writeln('</TR>'); |
|
430 |
|
431 writeClassTableRow(d, totals, base, 0); |
|
432 for (var r = 0; r < table.length; r++) |
|
433 writeClassTableRow(d, table[r], base, 0); |
|
434 |
|
435 d.writeln('</TABLE>'); |
|
436 } |
|
437 |
|
438 |
|
439 var modeNames = ["byte", "object", "reference"]; |
|
440 var modeNamesUpper = ["Byte", "Object", "Reference"]; |
|
441 var styleSheet = '<STYLE type="TEXT/CSS">\n'+ |
|
442 'BODY {background-color: #FFFFFF; color: #000000}\n'+ |
|
443 '.num {text-align: right}\n'+ |
|
444 '.pos {text-align: right; color: #CC0000}\n'+ |
|
445 '.neg {text-align: right; color: #009900}\n'+ |
|
446 '</STYLE>'; |
|
447 |
|
448 |
|
449 function showHead(d) { |
|
450 modeName = modeNames[showMode]; |
|
451 modeNameUpper = modeNamesUpper[showMode]; |
|
452 d.writeln('<TITLE>'+modeNameUpper+' Bloats</TITLE>'); |
|
453 d.writeln(styleSheet); |
|
454 } |
|
455 |
|
456 function showBody(d) { |
|
457 d.writeln('<H1>'+modeNameUpper+' Bloats</H1>'); |
|
458 writeFileTable(d); |
|
459 d.write('<FORM>'); |
|
460 for (var i = 0; i != 3; i++) |
|
461 if (i != showMode) { |
|
462 var newSortColumn = sortColumn; |
|
463 if (sortColumn >= 2) |
|
464 newSortColumn = sortColumn + (i-showMode)*2; |
|
465 d.write('<INPUT type="button" value="Show '+modeNamesUpper[i]+'s" onClick="reloadSelf('+newSortColumn+','+i+')">'); |
|
466 } |
|
467 d.writeln('</FORM>'); |
|
468 d.writeln('<P>The numbers do not include <CODE>malloc</CODE>\'d data such as string contents.</P>'); |
|
469 d.writeln('<P>Click on a column heading to sort by that column. Click on a class name to see details for that class.</P>'); |
|
470 writeClassTable(d); |
|
471 } |
|
472 |
|
473 |
|
474 function showRowDetail(rowName) { |
|
475 var row; |
|
476 var i; |
|
477 |
|
478 if (rowName == "TOTAL") |
|
479 row = totals; |
|
480 else { |
|
481 for (i = 0; i < classTables.length; i++) |
|
482 if (rowName == classTables[i][0]) { |
|
483 row = classTables[i]; |
|
484 break; |
|
485 } |
|
486 } |
|
487 if (row) { |
|
488 var w = window.open("", "ClassTableRowDetails"); |
|
489 var d = w.document; |
|
490 d.open(); |
|
491 d.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">'); |
|
492 d.writeln('<HTML>\n<HEAD>\n<TITLE>'+quoteHTML(rowName)+' bloat details</TITLE>'); |
|
493 d.writeln(styleSheet); |
|
494 d.writeln('</HEAD>\n\n<BODY>'); |
|
495 d.writeln('<H2>'+quoteHTML(rowName)+'</H2>'); |
|
496 if (row[1] !== undefined) |
|
497 d.writeln('<P>Each instance has '+row[1]+' bytes.</P>'); |
|
498 |
|
499 d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>'); |
|
500 d.writeln('<TR>\n<TH></TH>\n<TH colspan='+(nFiles+1)+'>Allocated</TH>'); |
|
501 d.writeln('<TH colspan='+(nFiles+1)+'>Allocated but not freed</TH>\n</TR>'); |
|
502 d.writeln('<TR>\n<TH></TH>'); |
|
503 for (i = 0; i != 2; i++) |
|
504 for (var j = 0; j <= nFiles; j++) |
|
505 d.writeln('<TH>'+(j == nFiles ? 'Total' : quoteHTML(fileTags[j]))+'</TH>'); |
|
506 d.writeln('</TR>'); |
|
507 |
|
508 for (i = 0; i != 3; i++) |
|
509 writeClassTableRow(d, row, 2+i*2, modeNamesUpper[i]+'s'); |
|
510 |
|
511 d.writeln('</TABLE>\n</BODY>\n</HTML>'); |
|
512 d.close(); |
|
513 } |
|
514 return undefined; |
|
515 } |
|
516 |
|
517 |
|
518 function stringSource(s) { |
|
519 s = s.replace(/\\/g, '\\\\'); |
|
520 s = s.replace(/"/g, '\\"'); |
|
521 s = s.replace(/<\//g, '<\\/'); |
|
522 return '"'+s+'"'; |
|
523 } |
|
524 |
|
525 function reloadSelf(n,m) { |
|
526 // Need to cache these because globals go away on document.open(). |
|
527 var sa = srcArray; |
|
528 var ss = stringSource; |
|
529 var ct = classTables; |
|
530 var i; |
|
531 |
|
532 document.open(); |
|
533 // Uncomment this and comment the document.open() line above to see the reloaded page's source. |
|
534 //var w = window.open("", "NewDoc"); |
|
535 //var d = w.document; |
|
536 //var document = new Object; |
|
537 //document.write = function () { |
|
538 // for (var i = 0; i < arguments.length; i++) { |
|
539 // var s = arguments[i].toString(); |
|
540 // s = s.replace(/&/g, '&'); |
|
541 // s = s.replace(/\x3C/g, '<'); |
|
542 // s = s.replace(/>/g, '>'); |
|
543 // s = s.replace(/ /g, ' '); |
|
544 // d.write(s); |
|
545 // } |
|
546 //}; |
|
547 //document.writeln = function () { |
|
548 // for (var i = 0; i < arguments.length; i++) { |
|
549 // var s = arguments[i].toString(); |
|
550 // s = s.replace(/&/g, '&'); |
|
551 // s = s.replace(/\x3C/g, '<'); |
|
552 // s = s.replace(/>/g, '>'); |
|
553 // s = s.replace(/ /g, ' '); |
|
554 // d.write(s); |
|
555 // } |
|
556 // d.writeln('<BR>'); |
|
557 //}; |
|
558 |
|
559 document.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">'); |
|
560 document.writeln('<HTML>\n<HEAD>\n<SCRIPT type="text/javascript">'); |
|
561 |
|
562 // Manually copy non-persistent script data |
|
563 if (!ct.length) |
|
564 document.writeln('var classTables = [];'); |
|
565 else { |
|
566 document.writeln('var classTables = ['); |
|
567 for (i = 0; i < ct.length; i++) { |
|
568 var row = ct[i]; |
|
569 document.write('[' + ss(row[0])); |
|
570 for (var j = 1; j < row.length; j++) |
|
571 document.write(',' + row[j]); |
|
572 document.writeln(']' + (i == ct.length-1 ? '];' : ',')); |
|
573 } |
|
574 } |
|
575 |
|
576 document.writeln('var srcArray = ['); |
|
577 for (i = 0; i < sa.length; i++) { |
|
578 document.write(ss(sa[i])); |
|
579 if (i != sa.length-1) |
|
580 document.writeln(','); |
|
581 } |
|
582 document.writeln('];'); |
|
583 document.writeln('eval(srcArray.join("\\n"));'); |
|
584 document.writeln('showMode = '+m+';'); |
|
585 document.writeln('sortColumn = '+n+';'); |
|
586 document.writeln('showHead(document);'); |
|
587 document.writeln('</SCRIPT>\n</HEAD>\n\n<BODY>\n<SCRIPT type="text/javascript">showBody(document);</SCRIPT>\n</BODY>\n</HTML>'); |
|
588 document.close(); |
|
589 return undefined; |
|
590 } |