| #!/usr/bin/perl | 
 | # | 
 | # Parse event stream and convert individual events into a summary | 
 | # record for the process. | 
 | # | 
 | # Git.exe generates one or more "event" records for each API method, | 
 | # such as "start <argv>" and "exit <code>", during the life of the git | 
 | # process.  Additionally, the input may contain interleaved events | 
 | # from multiple concurrent git processes and/or multiple threads from | 
 | # within a git process. | 
 | # | 
 | # Accumulate events for each process (based on its unique SID) in a | 
 | # dictionary and emit process summary records. | 
 | # | 
 | # Convert some of the variable fields (such as elapsed time) into | 
 | # placeholders (or omit them) to make HEREDOC comparisons easier in | 
 | # the test scripts. | 
 | # | 
 | # We may also omit fields not (currently) useful for testing purposes. | 
 |  | 
 | use strict; | 
 | use warnings; | 
 | use JSON::PP; | 
 | use Data::Dumper; | 
 | use Getopt::Long; | 
 |  | 
 | # The version of the trace2 event target format that we understand. | 
 | # This is reported in the 'version' event in the 'evt' field. | 
 | # It comes from the GIT_TRACE2_EVENT_VERSION macro in trace2/tr2_tgt_event.c | 
 | my $evt_version = '1'; | 
 |  | 
 | my $show_children = 1; | 
 | my $show_exec     = 1; | 
 | my $show_threads  = 1; | 
 |  | 
 | # A hack to generate test HEREDOC data for pasting into the test script. | 
 | # Usage: | 
 | #    cd "t/trash directory.t0212-trace2-event" | 
 | #    $TT trace ... >trace.event | 
 | #    VV=$(../../git.exe version | sed -e 's/^git version //') | 
 | #    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc | 
 | # Then paste heredoc into your new test. | 
 |  | 
 | my $gen_heredoc = 0; | 
 | my $gen_version = ''; | 
 |  | 
 | GetOptions("children!" => \$show_children, | 
 | 	   "exec!"     => \$show_exec, | 
 | 	   "threads!"  => \$show_threads, | 
 | 	   "HEREDOC!"  => \$gen_heredoc, | 
 | 	   "VERSION=s" => \$gen_version    ) | 
 |     or die("Error in command line arguments\n"); | 
 |  | 
 |  | 
 | # SIDs contains timestamps and PIDs of the process and its parents. | 
 | # This makes it difficult to match up in a HEREDOC in the test script. | 
 | # Build a map from actual SIDs to predictable constant values and yet | 
 | # keep the parent/child relationships.  For example: | 
 | # {..., "sid":"1539706952458276-8652", ...} | 
 | # {..., "sid":"1539706952458276-8652/1539706952649493-15452", ...} | 
 | # becomes: | 
 | # {..., "sid":"_SID1_", ...} | 
 | # {..., "sid":"_SID1_/_SID2_", ...} | 
 | my $sid_map; | 
 | my $sid_count = 0; | 
 |  | 
 | my $processes; | 
 |  | 
 | while (<>) { | 
 |     my $line = decode_json( $_ ); | 
 |  | 
 |     my $sid = ""; | 
 |     my $sid_sep = ""; | 
 |  | 
 |     my $raw_sid = $line->{'sid'}; | 
 |     my @raw_sid_parts = split /\//, $raw_sid; | 
 |     foreach my $raw_sid_k (@raw_sid_parts) { | 
 | 	if (!exists $sid_map->{$raw_sid_k}) { | 
 | 	    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_'; | 
 | 	    $sid_count++; | 
 | 	} | 
 | 	$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k}; | 
 | 	$sid_sep = '/'; | 
 |     } | 
 |      | 
 |     my $event = $line->{'event'}; | 
 |  | 
 |     if ($event eq 'version') { | 
 | 	$processes->{$sid}->{'version'} = $line->{'exe'}; | 
 | 	if ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) { | 
 | 	    # If we are generating data FOR the test script, replace | 
 | 	    # the reported git.exe version with a reference to an | 
 | 	    # environment variable.  When our output is pasted into | 
 | 	    # the test script, it will then be expanded in future | 
 | 	    # test runs to the THEN current version of git.exe. | 
 | 	    # We assume that the test script uses env var $V. | 
 | 	    $processes->{$sid}->{'version'} = "\$V"; | 
 | 	} | 
 |     } | 
 |  | 
 |     elsif ($event eq 'start') { | 
 | 	$processes->{$sid}->{'argv'} = $line->{'argv'}; | 
 | 	$processes->{$sid}->{'argv'}[0] = "_EXE_"; | 
 |     } | 
 |  | 
 |     elsif ($event eq 'exit') { | 
 | 	$processes->{$sid}->{'exit_code'} = $line->{'code'}; | 
 |     } | 
 |  | 
 |     elsif ($event eq 'atexit') { | 
 | 	$processes->{$sid}->{'exit_code'} = $line->{'code'}; | 
 |     } | 
 |  | 
 |     elsif ($event eq 'error') { | 
 | 	# For HEREDOC purposes, use the error message format string if | 
 | 	# available, rather than the formatted message (which probably | 
 | 	# has an absolute pathname). | 
 | 	if (exists $line->{'fmt'}) { | 
 | 	    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} ); | 
 | 	} | 
 | 	elsif (exists $line->{'msg'}) { | 
 | 	    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} ); | 
 | 	} | 
 |     } | 
 |  | 
 |     elsif ($event eq 'cmd_path') { | 
 | 	## $processes->{$sid}->{'path'} = $line->{'path'}; | 
 | 	# | 
 | 	# Like in the 'start' event, we need to replace the value of | 
 | 	# argv[0] with a token for HEREDOC purposes.  However, the | 
 | 	# event is only emitted when RUNTIME_PREFIX is defined, so | 
 | 	# just omit it for testing purposes. | 
 | 	# $processes->{$sid}->{'path'} = "_EXE_"; | 
 |     } | 
 |      | 
 |     elsif ($event eq 'cmd_name') { | 
 | 	$processes->{$sid}->{'name'} = $line->{'name'}; | 
 | 	$processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'}; | 
 |     } | 
 |  | 
 |     elsif ($event eq 'alias') { | 
 | 	$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'}; | 
 | 	$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'}; | 
 |     } | 
 |  | 
 |     elsif ($event eq 'def_param') { | 
 | 	my $kv; | 
 | 	$kv->{'param'} = $line->{'param'}; | 
 | 	$kv->{'value'} = $line->{'value'}; | 
 | 	push( @{$processes->{$sid}->{'params'}}, $kv ); | 
 |     } | 
 |  | 
 |     elsif ($event eq 'child_start') { | 
 | 	if ($show_children == 1) { | 
 | 	    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'}; | 
 | 	    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'}; | 
 | 	    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = "_EXE_"; | 
 | 	    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0; | 
 | 	} | 
 |     } | 
 |  | 
 |     elsif ($event eq 'child_exit') { | 
 | 	if ($show_children == 1) { | 
 | 	    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'}; | 
 | 	} | 
 |     } | 
 |  | 
 |     # TODO decide what information we want to test from thread events. | 
 |  | 
 |     elsif ($event eq 'thread_start') { | 
 | 	if ($show_threads == 1) { | 
 | 	} | 
 |     } | 
 |  | 
 |     elsif ($event eq 'thread_exit') { | 
 | 	if ($show_threads == 1) { | 
 | 	} | 
 |     } | 
 |  | 
 |     # TODO decide what information we want to test from exec events. | 
 |  | 
 |     elsif ($event eq 'exec') { | 
 | 	if ($show_exec == 1) { | 
 | 	} | 
 |     } | 
 |  | 
 |     elsif ($event eq 'exec_result') { | 
 | 	if ($show_exec == 1) { | 
 | 	} | 
 |     } | 
 |  | 
 |     elsif ($event eq 'def_param') { | 
 | 	# Accumulate parameter key/value pairs by key rather than in an array | 
 | 	# so that we get overwrite (last one wins) effects. | 
 | 	$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'}; | 
 |     } | 
 |  | 
 |     elsif ($event eq 'def_repo') { | 
 | 	# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'}; | 
 | 	$processes->{$sid}->{'repos'}->{$line->{'repo'}} = "_WORKTREE_"; | 
 |     } | 
 |  | 
 |     # A series of potentially nested and threaded region and data events | 
 |     # is fundamentally incompatibile with the type of summary record we | 
 |     # are building in this script.  Since they are intended for | 
 |     # perf-trace-like analysis rather than a result summary, we ignore | 
 |     # most of them here. | 
 |  | 
 |     # elsif ($event eq 'region_enter') { | 
 |     # } | 
 |     # elsif ($event eq 'region_leave') { | 
 |     # } | 
 |  | 
 |     elsif ($event eq 'data') { | 
 | 	my $cat = $line->{'category'}; | 
 | 	if ($cat eq 'test_category') { | 
 | 	     | 
 | 	    my $key = $line->{'key'}; | 
 | 	    my $value = $line->{'value'}; | 
 | 	    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value; | 
 | 	} | 
 |     } | 
 |  | 
 |     # This trace2 target does not emit 'printf' events. | 
 |     # | 
 |     # elsif ($event eq 'printf') { | 
 |     # } | 
 | } | 
 |  | 
 | # Dump the resulting hash into something that we can compare against | 
 | # in the test script.  These options make Dumper output look a little | 
 | # bit like JSON.  Also convert variable references of the form "$VAR*" | 
 | # so that the matching HEREDOC doesn't need to escape it. | 
 |  | 
 | $Data::Dumper::Sortkeys = 1; | 
 | $Data::Dumper::Indent = 1; | 
 | $Data::Dumper::Purity = 1; | 
 | $Data::Dumper::Pair = ':'; | 
 |  | 
 | my $out = Dumper($processes); | 
 | $out =~ s/'/"/g; | 
 | $out =~ s/\$VAR/VAR/g; | 
 |  | 
 | # Finally, if we're running this script to generate (manually confirmed) | 
 | # data to add to the test script, guard the indentation. | 
 |  | 
 | if ($gen_heredoc == 1) { | 
 |     $out =~ s/^/\t\|/gms; | 
 | } | 
 |  | 
 | print $out; |