2 # ======================================================================
3 # log2xml - This script translates cacao -verbosecall output into a
4 # more readable XML format. It also separates the output of
5 # different threads and is able to ignore commonly called
6 # (uninteresting) methods.
9 # cacao -verbose -verbosecall ... | log2xml.pl
11 # For each thread the script writes a file LOG_{threadid}.xml.
13 # You can use any XML editor to browse the logs. If you want to use
14 # vim, which works well, take a look at log2xml.vim in this
17 # You may want to edit the opt_ignore options below.
19 # Author : Edwin Steiner
23 # Revision 1.5 2005/04/15 09:33:34 edwin
24 # preserve indentation of log text
26 # Revision 1.4 2005/04/15 09:06:54 edwin
27 # output more valid xml
29 # Revision 1.3 2005/04/14 20:11:04 edwin
32 # Revision 1.2 2005/04/14 20:10:20 edwin
33 # disabled debug print, added vim boilerplate
35 # Revision 1.1 2005/04/14 19:44:00 edwin
36 # added log2xml.pl and log2xml.vim
38 # ======================================================================
43 my @opt_ignorelist = (
44 'java.lang.Character.toLowerCase(C)C',
45 'java.lang.Character.toUpperCase(C)C',
46 'java.lang.String.endsWith(Ljava/lang/String;)Z',
47 'java.lang.String.equalsIgnoreCase(Ljava/lang/String;)Z',
48 'java.lang.String.equals(Ljava/lang/Object;)Z',
49 'java.lang.String.indexOf(I)I',
50 'java.lang.String.indexOf(II)I',
51 'java.lang.String.indexOf(Ljava/lang/String;)I',
52 'java.lang.String.indexOf(Ljava/lang/String;I)I',
53 'java.lang.String.lastIndexOf(Ljava/lang/String;)I',
54 'java.lang.String.lastIndexOf(Ljava/lang/String;I)I',
55 'java.lang.String.regionMatches(ILjava/lang/String;II)Z',
56 'java.lang.String.regionMatches(ZILjava/lang/String;II)Z',
57 'java.lang.String.replace(CC)Ljava/lang/String;',
58 'java.lang.String.startsWith(Ljava/lang/String;)Z',
59 'java.lang.String.substring(II)Ljava/lang/String;',
60 'java.lang.String.toLowerCase()Ljava/lang/String;',
61 'java.util.HashMap.get(Ljava/lang/Object;)Ljava/lang/Object;',
62 'java.util.HashMap.put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;',
63 'java.util.Hashtable.clone()Ljava/lang/Object;',
64 'java.util.Hashtable.get(Ljava/lang/Object;)Ljava/lang/Object;',
65 'java.util.Hashtable.put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;',
66 'java.util.jar.JarFile$EntryInputStream.read([BII)I',
69 my @opt_ignore_regexps = (
70 qr/^java\.lang\.Character\./,
71 qr/^java\.lang\.String\./,
72 qr/^java\.util\.jar\./,
73 qr/^java\.util\.Locale\./,
74 qr/^java\.util\.zip\./,
79 my $opt_no_text_for_ignored = 0;
80 my $opt_fileprefix = 'LOG_';
87 my %ignorehash = map { $_=>1 } @opt_ignorelist;
89 # ======================================================================
91 # ======================================================================
93 # Quote the given text for putting it in an XML file.
105 my ($thread,$xml) = @_;
106 my $file = $files{$thread};
110 sub write_xml_call_info ( $$ )
112 my ($thread,$node) = @_;
113 my $file = $files{$thread};
114 print $file ' return="' . quote($node->{RETURN}) . '"' if defined($node->{RETURN});
115 print $file ' except="' . quote($node->{EXCEPTION}) . '"' if defined($node->{EXCEPTION});
117 print $file ' details="' . quote($node->{DETAILS}) . '"' if defined($node->{DETAILS});
121 sub write_xml_frame ( $$ )
123 my ($thread,$node) = @_;
125 my $file = $files{$thread};
126 print $file '<frame name="' . quote($node->{NAME}) . '"';
127 write_xml_call_info($thread,$node);
131 sub write_xml_enter ( $$ )
133 my ($thread,$node) = @_;
135 my $file = $files{$thread};
136 print $file '<call name="' . quote($node->{NAME}) . '"';
137 write_xml_call_info($thread,$node);
141 sub write_xml_leave ( $$ )
143 my ($thread,$node) = @_;
145 my $file = $files{$thread};
146 print $file '<leave';
147 write_xml_call_info($thread,$node);
148 print $file ' name="' . quote($node->{NAME}) . '"';
150 print $file "</call>\n";
153 sub write_xml_never_returns ( $$ )
155 my ($thread,$node) = @_;
157 my $file = $files{$thread};
158 print $file '<never_returns';
159 print $file ' name="' . quote($node->{NAME}) . '"';
161 print $file "</call>\n";
164 sub write_xml_node ( $$ )
166 my ($thread,$node) = @_;
168 write_xml_enter($thread,$node);
169 write_xml_body($thread,$node);
170 write_xml_leave($thread,$node);
173 sub write_xml_body ( $$ )
175 my ($thread,$node) = @_;
177 for my $x (@{$node->{BODY}}) {
179 write_xml_node($thread,$x);
182 my $file = $files{$thread};
188 sub write_xml_header ( $ )
191 print $file '<?xml version="1.0"?>'."\n";
192 print $file "<thread>\n";
195 sub write_xml_end ( $ )
198 print $file "</thread>\n";
201 # ======================================================================
203 # ======================================================================
205 # Return true if functions with this name are ignored.
210 return 1 if $ignorehash{$name};
212 for my $re (@opt_ignore_regexps) {
213 return 1 if $name =~ $re;
219 # ======================================================================
220 # STACK RECONSTRUCTION
221 # ======================================================================
225 return {NAME => 'root',
234 sub thread_register ( $ )
237 unless (exists $stacks{$thread}) {
238 $stacks{$thread} = [make_root];
239 my $filename = $opt_fileprefix . $thread . '.xml';
240 $files{$thread} = IO::File->new(">$filename");
241 $ignore_level{$thread} = 0;
242 write_xml_header($files{$thread});
249 return $stacks{$thread}->[-1] or die "stack underflow";
255 return pop @{$stacks{$thread}} or die "stack underflow";
258 sub stack_push ( $$ )
260 my ($thread,$value) = @_;
261 push @{$stacks{$thread}},$value;
264 sub stack_slot ( $$ )
266 my ($thread,$index) = @_;
267 return $stacks{$thread}->[$index] or die "invalid stack index";
270 sub stack_write ( $ )
274 for (@{$stacks{$thread}}) {
275 print STDERR "\t".quote($_->{NAME})."\n";
279 sub process_call ( $$$ )
281 my ($thread,$keyword,$rest) = @_;
283 my $top = stack_top($thread);
285 $rest =~ /(\S+?) \( ([^)]*) \) ([^ \t(]+) (.*) $/x or die "could not match call log: $rest";
286 my ($n,$args,$rettype,$details) = ($1,$2,$3,$4);
287 my $name = "$n($args)$rettype";
289 my $call = {NAME => $name,SHORTNAME => $n,DETAILS => $details,BODY => [],LINE => $.};
291 $call->{FIRST} = 1 if $keyword eq '1st_call';
293 stack_push($thread,$call);
295 if ($ignore_level{$thread} == 0) {
296 write_xml_enter($thread,$call);
298 if (is_ignored($name)) {
299 $ignore_level{$thread}++;
303 $ignore_level{$thread}++;
307 sub process_leave ( $$$ )
309 my ($thread,$rest,$exception) = @_;
311 my $top = stack_pop($thread);
314 $top->{EXCEPTION} = $exception;
317 $rest =~ /(\S+?) (\([^)]*\))? (->(.*))?$/x or die "could not match finished log: $rest";
318 my ($name,$return) = ($1,$4);
320 $name eq $top->{NAME} or die "warning: mismatched leave:\n"
321 ."\t(line $.) $name from\n"
322 ."\t(line ".$top->{LINE}.") ".$top->{NAME}."\n";
324 $top->{RETURN} = defined($return) ? $return : 'void';
327 --$ignore_level{$thread} if $ignore_level{$thread} > 0;
329 if ($ignore_level{$thread} == 0) {
330 write_xml_leave($thread,$top);
334 sub process_exception ( $$$ )
336 my ($thread,$exception,$rest) = @_;
339 my $top = stack_top($thread);
341 if ($rest =~ /(\S+?) \([^)]*\) \((0x[^)]+)\)/x) {
342 ($name,$entry) = ($1,$2);
344 elsif ($rest =~ /call_java_method/) {
345 $name = "call_java_method";
349 die "could not match exception: $exception in $rest";
352 $top->{ENTRYPOINT} = $entry unless defined($top->{ENTRYPOINT});
354 if ($name eq $top->{SHORTNAME} && $entry eq $top->{ENTRYPOINT}) {
355 if ($ignore_level{$thread} == 0) {
356 my $handled = '<exception name="'.$exception.'"/>'."\n";
357 write_xml($thread,$handled);
359 return; # exception handled locally
362 # unwind a stack frame
364 while ($name ne 'call_java_method' && $name ne stack_slot($thread,-2)->{SHORTNAME}) {
365 stack_write($thread);
366 print STDERR "exception : $exception\n";
367 print STDERR "method : $name\n";
368 print STDERR "entrypoint: $entry\n";
369 print STDERR "scope : ".$top->{SHORTNAME}."\n";
370 print STDERR "scopeentry: ".$top->{ENTRYPOINT}."\n";
371 print STDERR "outer : ".stack_slot($thread,-2)->{SHORTNAME}."\n";
372 print STDERR "outerentry: ".stack_slot($thread,-2)->{ENTRYPOINT}."\n";
373 print STDERR "unwinding stack...\n";
375 warn "heuristic unwind: $exception in $rest";
376 process_leave($thread,$rest,$exception);
379 process_leave($thread,$rest,$exception);
382 sub process_call_log ( $$$ )
384 my ($thread,$keyword,$rest) = @_;
386 if ($keyword eq 'called' || $keyword eq '1st_call') {
387 process_call($thread,$keyword,$rest);
389 elsif ($keyword eq 'finished') {
390 process_leave($thread,$rest,undef);
393 die "invalid log keyword: $keyword";
397 sub process_text ( $$ )
399 my ($thread,$text) = @_;
401 # print STDERR "$.: $text\n";
403 if ($opt_no_text_for_ignored && $ignore_level{$thread} > 0) {
407 my $top = stack_top($thread);
409 my $file = $files{$thread};
410 print $file quote($text)."\n";
413 # ======================================================================
415 # ======================================================================
423 if (/LOG: \[(\S+)\](\s*)(.*)/) {
424 my ($thread,$space,$log) = ($1,$2,$3);
425 thread_register($thread);
426 $lastthread = $thread;
427 if ($log =~ /(1st_call|called|finished):\s*(.*)/) {
428 process_call_log($thread,$1,$2);
430 elsif ($log =~ /Exception\s+(\S+)\s+thrown in\s+(.*)/) {
431 process_exception($thread,$1,$2);
434 process_text($thread,$space.$log);
438 unless (defined($lastthread)) {
439 $lastthread = '(nil)';
440 thread_register($lastthread);
442 process_text($lastthread,$_);
449 warn "warning: omitting the rest of the input";
452 for my $thread (keys %stacks) {
453 my $ign = $ignore_level{$thread};
455 warn "warning: ignore_level not reset to 0 at end of input";
456 write_xml($thread,"\n<!-- LOG ENDS IN IGNORED METHOD - STACKTRACE: -->\n\n");
458 my $top = $stacks{$thread}->[-$ign];
459 write_xml_frame($thread,$top);
462 pop @{$stacks{$thread}} for 1..$ignore_level{$thread};
464 while (scalar @{$stacks{$thread}} > 1) {
465 my $top = $stacks{$thread}->[-1];
466 write_xml_never_returns($thread,$top);
467 pop @{$stacks{$thread}};
469 write_xml_end($files{$thread});
470 $files{$thread}->close();
472 print STDERR "processed $. lines\n";
477 # vim: noet ts=4 sw=4 ai