Fixed double buffering for QT widget
[mozilla-central.git] / tools / memory / bloattable.pl
blob6d4ca02c9b429afc8033c41fbf83b6e97eecb078
1 #!/usr/bin/perl -w
3 # ***** BEGIN LICENSE BLOCK *****
4 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 # The contents of this file are subject to the Mozilla Public License Version
7 # 1.1 (the "License"); you may not use this file except in compliance with
8 # the License. You may obtain a copy of the License at
9 # http://www.mozilla.org/MPL/
11 # Software distributed under the License is distributed on an "AS IS" basis,
12 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 # for the specific language governing rights and limitations under the
14 # License.
16 # The Original Code is Waldemar's Perl Utilities.
18 # The Initial Developer of the Original Code is
19 # Netscape Communications Corporation.
20 # Portions created by the Initial Developer are Copyright (C) 2000
21 # the Initial Developer. All Rights Reserved.
23 # Contributor(s):
24 # Waldemar Horwat <waldemar@acm.org>
26 # Alternatively, the contents of this file may be used under the terms of
27 # either the GNU General Public License Version 2 or later (the "GPL"), or
28 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 # in which case the provisions of the GPL or the LGPL are applicable instead
30 # of those above. If you wish to allow use of your version of this file only
31 # under the terms of either the GPL or the LGPL, and not to allow others to
32 # use your version of this file under the terms of the MPL, indicate your
33 # decision by deleting the provisions above and replace them with the notice
34 # and other provisions required by the GPL or the LGPL. If you do not delete
35 # the provisions above, a recipient may use your version of this file under
36 # the terms of any one of the MPL, the GPL or the LGPL.
38 # ***** END LICENSE BLOCK *****
40 # bloattable [-debug] [-source] [-byte n|-obj n|-ref n] <file1> <file2> ... <filen> > <html-file>
42 # file1, file2, ... filen should be successive BloatView files generated from the same run.
43 # Summarize them in an HTML table. Output the HTML to the standard output.
45 # If -debug is set, create a slightly larger html file which is more suitable for debugging this script.
46 # If -source is set, create an html file that prints the html source as the output
47 # If -byte n, -obj n, or -ref n is given, make the page default to showing byte, object, or reference statistics,
48 # respectively, and sort by the nth column (n is zero-based, so the first column has n==0).
50 # See http://lxr.mozilla.org/mozilla/source/xpcom/doc/MemoryTools.html
52 use 5.004;
53 use strict;
54 use diagnostics;
55 use File::Basename;
56 use Getopt::Long;
58 # The generated HTML is almost entirely generated by a script. Only the <HTML>, <HEAD>, and <BODY> elements are explicit
59 # because a <SCRIPT> element cannot officially be a direct descendant of an <HTML> element.
60 # The script itself is almost all generated by an eval of a large string. This allows the script to reproduce itself
61 # when making a new page using document.write's. Re-sorting the page causes it to regenerate itself in this way.
65 # Return the file's modification date.
66 sub fileModDate($) {
67 my ($pathName) = @_;
68 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) =
69 stat $pathName or die "Can't stat '$pathName'";
70 return $mtime;
74 sub fileCoreName($) {
75 my ($pathName) = @_;
76 my $fileName = basename($pathName, "");
77 $fileName =~ s/\..*//;
78 return $fileName;
82 # Convert a raw string into a single-quoted JavaScript string.
83 sub singleQuoteString($) {
84 local ($_) = @_;
85 s/\\/\\\\/g;
86 s/'/\\'/g;
87 s/\n/\\n/g;
88 s/<\//<\\\//g;
89 return "'$_'";
93 # Convert a raw string into a double-quoted JavaScript string.
94 sub doubleQuoteString($) {
95 local ($_) = @_;
96 s/\\/\\\\/g;
97 s/"/\\"/g;
98 s/\n/\\n/g;
99 s/<\//<\\\//g;
100 return "\"$_\"";
104 # Quote special HTML characters in the string.
105 sub quoteHTML($) {
106 local ($_) = @_;
107 s/\&/&amp;/g;
108 s/</&lt;/g;
109 s/>/&gt;/g;
110 s/ /&nbsp;/g;
111 s/\n/<BR>\n/g;
112 return $_;
116 # Write the generated page to the standard output.
117 # The script source code is read from this file past the __END__ marker
118 # @$scriptData is the JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
119 # @$persistentScriptData is the same as @scriptData, but persists when the page reloads itself.
120 # If $debug is true, generate the script directly instead of having it eval itself.
121 # If $source is true, generate a script that displays the page's source instead of the page itself.
122 sub generate(\@\@$$$$) {
123 my ($scriptData, $persistentScriptData, $debug, $source, $showMode, $sortColumn) = @_;
125 my @scriptSource = <DATA>;
126 chomp @scriptSource;
127 print <<'EOS';
128 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
129 <HTML>
130 <HEAD>
131 <SCRIPT type="text/javascript">
134 foreach (@$scriptData) {print "$_\n";}
135 print "\n";
137 print "var srcArray = [\n";
138 my @quotedScriptSource = map {
139 my $line = $_;
140 $line =~ s/^\s+//g;
141 # $line =~ s/^\/\/SOURCE\s+//g if $source;
142 $line =~ s/^\/\/.*//g;
143 $line =~ s/\s+$//g;
144 $line eq "" ? () : $line
145 } @$persistentScriptData, @scriptSource;
146 my $lastQuotedLine = pop @quotedScriptSource;
147 foreach (@quotedScriptSource) {print doubleQuoteString($_), ",\n";}
148 print doubleQuoteString($lastQuotedLine), "];\n\n";
150 if ($debug) {
151 push @quotedScriptSource, $lastQuotedLine;
152 foreach (@quotedScriptSource) {
153 s/<\//<\\\//g; # This fails if a regexp ends with a '<'. Oh well....
154 print "$_\n";
156 print "\n";
157 } else {
158 print "eval(srcArray.join(\"\\n\"));\n\n";
160 print "showMode = $showMode;\n";
161 print "sortColumn = $sortColumn;\n";
162 if ($source) {
163 print <<'EOS';
164 function writeQuotedHTML(s) {
165 document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n'));
168 var quotingDocument = {
169 write: function () {
170 for (var i = 0; i < arguments.length; i++)
171 writeQuotedHTML(arguments[i]);
173 writeln: function () {
174 for (var i = 0; i < arguments.length; i++)
175 writeQuotedHTML(arguments[i]);
176 document.writeln('<BR>');
180 } else {
181 print "showHead(document);\n";
183 print "</SCRIPT>\n";
184 print "</HEAD>\n\n";
185 print "<BODY>\n";
186 if ($source) {
187 print "<P><TT>";
188 print quoteHTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
189 print quoteHTML "<HTML>\n";
190 print quoteHTML "<HEAD>\n";
191 print "<SCRIPT type=\"text/javascript\">showHead(quotingDocument);</SCRIPT>\n";
192 print quoteHTML "</HEAD>\n\n";
193 print quoteHTML "<BODY>\n";
194 print "<SCRIPT type=\"text/javascript\">showBody(quotingDocument);</SCRIPT>\n";
195 print quoteHTML "</BODY>\n";
196 print quoteHTML "</HTML>\n";
197 print "</TT></P>\n";
198 } else {
199 print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n";
201 print "</BODY>\n";
202 print "</HTML>\n";
207 # Read the bloat file into hash table $h. The hash table is indexed by class names;
208 # each entry is a list with the following elements:
209 # bytesAlloc Total number of bytes allocated
210 # bytesNet Total number of bytes allocated but not deallocated
211 # objectsAlloc Total number of objects allocated
212 # objectsNet Total number of objects allocated but not deallocated
213 # refsAlloc Total number of references AddRef'd
214 # refsNet Total number of references AddRef'd but not Released
215 # Except for TOTAL, all hash table entries refer to mutually exclusive data.
216 # $sizes is a hash table indexed by class names. Each entry of that table contains the class's instance size.
217 sub readBloatFile($\%\%) {
218 my ($file, $h, $sizes) = @_;
219 local $_; # Needed for 'while (<FILE>)' below.
221 my $readSomething = 0;
222 open FILE, $file;
223 while (<FILE>) {
224 if (my ($name, $size, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet) =
225 /^\s*(?:\d+)\s+([\w:]+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(-?\d+)\s*\([^()]*\)\s*(\d+)\s+(-?\d+)\s*\([^()]*\)\s*$/) {
226 my $bytesAlloc;
227 if ($name eq "TOTAL") {
228 $size = "undefined";
229 $bytesAlloc = "undefined";
230 } else {
231 $bytesAlloc = $objectsAlloc * $size;
232 if ($bytesNet != $objectsNet * $size) {
233 print STDERR "In '$file', class $name bytesNet != objectsNet * size: $bytesNet != $objectsNet * $size\n";
236 print STDERR "Duplicate entry $name in '$file'\n" if $$h{$name};
237 $$h{$name} = [$bytesAlloc, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet];
239 my $oldSize = $$sizes{$name};
240 print STDERR "Mismatch of sizes of class $name: $oldSize and $size\n" if defined($oldSize) && $size ne $oldSize;
241 $$sizes{$name} = $size;
242 $readSomething = 1;
243 } elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) {
244 print STDERR "Unable to parse '$file' line: $_";
247 close FILE;
248 print STDERR "No data in '$file'\n" unless $readSomething;
249 return $h;
253 my %sizes; # <class-name> => <instance-size>
254 my %tables; # <file-name> => <bloat-table>; see readBloatFile for format of <bloat-table>
256 # Generate the JavaScript source code for the row named $c. $l can contain the initial entries of the row.
257 sub genTableRowSource($$) {
258 my ($l, $c) = @_;
259 my $lastE;
260 foreach (@ARGV) {
261 my $e = $tables{$_}{$c};
262 if (defined($lastE) && !defined($e)) {
263 $e = [0,0,0,0,0,0];
264 print STDERR "Class $c is defined in an earlier file but not in '$_'\n";
266 if (defined $e) {
267 if (defined $lastE) {
268 for (my $i = 0; $i <= $#$e; $i++) {
269 my $n = $$e[$i];
270 $l .= ($n eq "undefined" ? "undefined" : $n - $$lastE[$i]) . ",";
272 $l .= " ";
273 } else {
274 $l .= join(",", @$e) . ", ";
276 $lastE = $e;
277 } else {
278 $l .= "0,0,0,0,0,0, ";
281 $l .= join(",", @$lastE);
282 return "[$l]";
287 my $debug;
288 my $source;
289 my $showMode;
290 my $sortColumn;
291 my @modeOptions;
293 GetOptions("debug" => \$debug, "source" => \$source, "byte=i" => \$modeOptions[0], "obj=i" => \$modeOptions[1], "ref=i" => \$modeOptions[2]);
294 for (my $i = 0; $i != 3; $i++) {
295 my $modeOption = $modeOptions[$i];
296 if ($modeOption) {
297 die "Only one of -byte, -obj, or -ref may be given" if defined $showMode;
298 my $nFileColumns = scalar(@ARGV) + 1;
299 die "-byte, -obj, or -ref column number out of range" if $modeOption < 0 || $modeOption >= 2 + 2*$nFileColumns;
300 $showMode = $i;
301 if ($modeOption >= 2) {
302 $modeOption -= 2;
303 $sortColumn = 2 + $showMode*2;
304 if ($modeOption >= $nFileColumns) {
305 $sortColumn++;
306 $modeOption -= $nFileColumns;
308 $sortColumn += $modeOption*6;
309 } else {
310 $sortColumn = $modeOption;
314 unless (defined $showMode) {
315 $showMode = 0;
316 $sortColumn = 0;
319 # Read all of the bloat files.
320 foreach (@ARGV) {
321 unless ($tables{$_}) {
322 my $f = $_;
323 my %table;
325 readBloatFile $_, %table, %sizes;
326 $tables{$_} = \%table;
329 die "No input" unless %sizes;
331 my @scriptData; # JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
332 my @persistentScriptData; # Same as @scriptData, but persists the page reloads itself.
334 # Print a list of bloat file names.
335 push @persistentScriptData, "var nFiles = " . scalar(@ARGV) . ";";
336 push @persistentScriptData, "var fileTags = [" . join(", ", map {singleQuoteString substr(fileCoreName($_), -10)} @ARGV) . "];";
337 push @persistentScriptData, "var fileNames = [" . join(", ", map {singleQuoteString $_} @ARGV) . "];";
338 push @persistentScriptData, "var fileDates = [" . join(", ", map {singleQuoteString localtime fileModDate $_} @ARGV) . "];";
340 # Print the bloat tables.
341 push @persistentScriptData, "var totals = " . genTableRowSource('"TOTAL", undefined, ', "TOTAL") . ";";
342 push @scriptData, "var classTables = [";
343 delete $sizes{"TOTAL"};
344 my @classes = sort(keys %sizes);
345 for (my $i = 0; $i <= $#classes; $i++) {
346 my $c = $classes[$i];
347 push @scriptData, genTableRowSource(doubleQuoteString($c).", ".$sizes{$c}.", ", $c) . ($i == $#classes ? "];" : ",");
350 generate(@scriptData, @persistentScriptData, $debug, $source, $showMode, $sortColumn);
354 # The source of the eval'd JavaScript follows.
355 # Comments starting with // that are alone on a line are stripped by the Perl script.
356 __END__
358 // showMode: 0=bytes, 1=objects, 2=references
359 var showMode;
360 var modeName;
361 var modeNameUpper;
363 var sortColumn;
365 // Sort according to the sortColumn. Column 0 is sorted alphabetically in ascending order.
366 // All other columns are sorted numerically in descending order, with column 0 used for a secondary sort.
367 // Undefined is always listed last.
368 function sortCompare(x, y) {
369 if (sortColumn) {
370 var xc = x[sortColumn];
371 var yc = y[sortColumn];
372 if (xc < yc || xc === undefined && yc !== undefined) return 1;
373 if (yc < xc || yc === undefined && xc !== undefined) return -1;
376 var x0 = x[0];
377 var y0 = y[0];
378 if (x0 > y0 || x0 === undefined && y0 !== undefined) return 1;
379 if (y0 > x0 || y0 === undefined && x0 !== undefined) return -1;
380 return 0;
384 // Quote special HTML characters in the string.
385 function quoteHTML(s) {
386 s = s.replace(/&/g, '&amp;');
387 // Can't use /</g because HTML interprets '</g' as ending the script!
388 s = s.replace(/\x3C/g, '&lt;');
389 s = s.replace(/>/g, '&gt;');
390 s = s.replace(/ /g, '&nbsp;');
391 return s;
395 function writeFileTable(d) {
396 d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
397 d.writeln('<TR>\n<TH>Name</TH>\n<TH>File</TH>\n<TH>Date</TH>\n</TR>');
398 for (var i = 0; i < nFiles; i++)
399 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>');
400 d.writeln('</TABLE>');
404 function writeReloadLink(d, column, s, rowspan) {
405 d.write(rowspan == 1 ? '<TH>' : '<TH rowspan='+rowspan+'>');
406 if (column != sortColumn)
407 d.write('<A href="javascript:reloadSelf('+column+','+showMode+')">');
408 d.write(s);
409 if (column != sortColumn)
410 d.write('</A>');
411 d.writeln('</TH>');
414 function writeClassTableRow(d, row, base, modeName) {
415 if (modeName) {
416 d.writeln('<TR>\n<TH>'+modeName+'</TH>');
417 } else {
418 d.writeln('<TR>\n<TD><A href="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD>');
419 var v = row[1];
420 d.writeln('<TD class=num>'+(v === undefined ? '' : v)+'</TD>');
422 for (var i = 0; i != 2; i++) {
423 var c = base + i;
424 for (var j = 0; j <= nFiles; j++) {
425 v = row[c];
426 var style = 'num';
427 if (j != nFiles)
428 if (v > 0) {
429 style = 'pos';
430 v = '+'+v;
431 } else
432 style = 'neg';
433 d.writeln('<TD class='+style+'>'+(v === undefined ? '' : v)+'</TD>');
434 c += 6;
437 d.writeln('</TR>');
440 function writeClassTable(d) {
441 var base = 2 + showMode*2;
443 // Make a copy because a sort is destructive.
444 var table = classTables.concat();
445 table.sort(sortCompare);
447 d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
449 d.writeln('<TR>');
450 writeReloadLink(d, 0, 'Class Name', 2);
451 writeReloadLink(d, 1, 'Instance<BR>Size', 2);
452 d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated</TH>');
453 d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated but not freed</TH>\n</TR>');
454 d.writeln('<TR>');
455 for (var i = 0; i != 2; i++) {
456 var c = base + i;
457 for (var j = 0; j <= nFiles; j++) {
458 writeReloadLink(d, c, j == nFiles ? 'Total' : quoteHTML(fileTags[j]), 1);
459 c += 6;
462 d.writeln('</TR>');
464 writeClassTableRow(d, totals, base, 0);
465 for (var r = 0; r < table.length; r++)
466 writeClassTableRow(d, table[r], base, 0);
468 d.writeln('</TABLE>');
472 var modeNames = ["byte", "object", "reference"];
473 var modeNamesUpper = ["Byte", "Object", "Reference"];
474 var styleSheet = '<STYLE type="TEXT/CSS">\n'+
475 'BODY {background-color: #FFFFFF; color: #000000}\n'+
476 '.num {text-align: right}\n'+
477 '.pos {text-align: right; color: #CC0000}\n'+
478 '.neg {text-align: right; color: #009900}\n'+
479 '</STYLE>';
482 function showHead(d) {
483 modeName = modeNames[showMode];
484 modeNameUpper = modeNamesUpper[showMode];
485 d.writeln('<TITLE>'+modeNameUpper+' Bloats</TITLE>');
486 d.writeln(styleSheet);
489 function showBody(d) {
490 d.writeln('<H1>'+modeNameUpper+' Bloats</H1>');
491 writeFileTable(d);
492 d.write('<FORM>');
493 for (var i = 0; i != 3; i++)
494 if (i != showMode) {
495 var newSortColumn = sortColumn;
496 if (sortColumn >= 2)
497 newSortColumn = sortColumn + (i-showMode)*2;
498 d.write('<INPUT type="button" value="Show '+modeNamesUpper[i]+'s" onClick="reloadSelf('+newSortColumn+','+i+')">');
500 d.writeln('</FORM>');
501 d.writeln('<P>The numbers do not include <CODE>malloc</CODE>\'d data such as string contents.</P>');
502 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>');
503 writeClassTable(d);
507 function showRowDetail(rowName) {
508 var row;
509 var i;
511 if (rowName == "TOTAL")
512 row = totals;
513 else {
514 for (i = 0; i < classTables.length; i++)
515 if (rowName == classTables[i][0]) {
516 row = classTables[i];
517 break;
520 if (row) {
521 var w = window.open("", "ClassTableRowDetails");
522 var d = w.document;
523 d.open();
524 d.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
525 d.writeln('<HTML>\n<HEAD>\n<TITLE>'+quoteHTML(rowName)+' bloat details</TITLE>');
526 d.writeln(styleSheet);
527 d.writeln('</HEAD>\n\n<BODY>');
528 d.writeln('<H2>'+quoteHTML(rowName)+'</H2>');
529 if (row[1] !== undefined)
530 d.writeln('<P>Each instance has '+row[1]+' bytes.</P>');
532 d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
533 d.writeln('<TR>\n<TH></TH>\n<TH colspan='+(nFiles+1)+'>Allocated</TH>');
534 d.writeln('<TH colspan='+(nFiles+1)+'>Allocated but not freed</TH>\n</TR>');
535 d.writeln('<TR>\n<TH></TH>');
536 for (i = 0; i != 2; i++)
537 for (var j = 0; j <= nFiles; j++)
538 d.writeln('<TH>'+(j == nFiles ? 'Total' : quoteHTML(fileTags[j]))+'</TH>');
539 d.writeln('</TR>');
541 for (i = 0; i != 3; i++)
542 writeClassTableRow(d, row, 2+i*2, modeNamesUpper[i]+'s');
544 d.writeln('</TABLE>\n</BODY>\n</HTML>');
545 d.close();
547 return undefined;
551 function stringSource(s) {
552 s = s.replace(/\\/g, '\\\\');
553 s = s.replace(/"/g, '\\"');
554 s = s.replace(/<\//g, '<\\/');
555 return '"'+s+'"';
558 function reloadSelf(n,m) {
559 // Need to cache these because globals go away on document.open().
560 var sa = srcArray;
561 var ss = stringSource;
562 var ct = classTables;
563 var i;
565 document.open();
566 // Uncomment this and comment the document.open() line above to see the reloaded page's source.
567 //var w = window.open("", "NewDoc");
568 //var d = w.document;
569 //var document = new Object;
570 //document.write = function () {
571 // for (var i = 0; i < arguments.length; i++) {
572 // var s = arguments[i].toString();
573 // s = s.replace(/&/g, '&amp;');
574 // s = s.replace(/\x3C/g, '&lt;');
575 // s = s.replace(/>/g, '&gt;');
576 // s = s.replace(/ /g, '&nbsp;');
577 // d.write(s);
578 // }
579 //};
580 //document.writeln = function () {
581 // for (var i = 0; i < arguments.length; i++) {
582 // var s = arguments[i].toString();
583 // s = s.replace(/&/g, '&amp;');
584 // s = s.replace(/\x3C/g, '&lt;');
585 // s = s.replace(/>/g, '&gt;');
586 // s = s.replace(/ /g, '&nbsp;');
587 // d.write(s);
588 // }
589 // d.writeln('<BR>');
590 //};
592 document.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
593 document.writeln('<HTML>\n<HEAD>\n<SCRIPT type="text/javascript">');
595 // Manually copy non-persistent script data
596 if (!ct.length)
597 document.writeln('var classTables = [];');
598 else {
599 document.writeln('var classTables = [');
600 for (i = 0; i < ct.length; i++) {
601 var row = ct[i];
602 document.write('[' + ss(row[0]));
603 for (var j = 1; j < row.length; j++)
604 document.write(',' + row[j]);
605 document.writeln(']' + (i == ct.length-1 ? '];' : ','));
609 document.writeln('var srcArray = [');
610 for (i = 0; i < sa.length; i++) {
611 document.write(ss(sa[i]));
612 if (i != sa.length-1)
613 document.writeln(',');
615 document.writeln('];');
616 document.writeln('eval(srcArray.join("\\n"));');
617 document.writeln('showMode = '+m+';');
618 document.writeln('sortColumn = '+n+';');
619 document.writeln('showHead(document);');
620 document.writeln('</SCRIPT>\n</HEAD>\n\n<BODY>\n<SCRIPT type="text/javascript">showBody(document);</SCRIPT>\n</BODY>\n</HTML>');
621 document.close();
622 return undefined;