1#!/usr/local/bin/perl 2# 3# Parse event stream and convert individual events into a summary 4# record for the process. 5# 6# Git.exe generates one or more "event" records for each API method, 7# such as "start <argv>" and "exit <code>", during the life of the git 8# process. Additionally, the input may contain interleaved events 9# from multiple concurrent git processes and/or multiple threads from 10# within a git process. 11# 12# Accumulate events for each process (based on its unique SID) in a 13# dictionary and emit process summary records. 14# 15# Convert some of the variable fields (such as elapsed time) into 16# placeholders (or omit them) to make HEREDOC comparisons easier in 17# the test scripts. 18# 19# We may also omit fields not (currently) useful for testing purposes. 20 21use strict; 22use warnings; 23use JSON::PP; 24use Data::Dumper; 25use Getopt::Long; 26 27# The version of the trace2 event target format that we understand. 28# This is reported in the 'version' event in the 'evt' field. 29# It comes from the GIT_TRACE2_EVENT_VERSION macro in trace2/tr2_tgt_event.c 30my $evt_version = '1'; 31 32my $show_children = 1; 33my $show_exec = 1; 34my $show_threads = 1; 35 36# A hack to generate test HEREDOC data for pasting into the test script. 37# Usage: 38# cd "t/trash directory.t0212-trace2-event" 39# $TT trace ... >trace.event 40# VV=$(../../git.exe version | sed -e 's/^git version //') 41# perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc 42# Then paste heredoc into your new test. 43 44my $gen_heredoc = 0; 45my $gen_version = ''; 46 47GetOptions("children!" => \$show_children, 48 "exec!" => \$show_exec, 49 "threads!" => \$show_threads, 50 "HEREDOC!" => \$gen_heredoc, 51 "VERSION=s" => \$gen_version ) 52 or die("Error in command line arguments\n"); 53 54 55# SIDs contains timestamps and PIDs of the process and its parents. 56# This makes it difficult to match up in a HEREDOC in the test script. 57# Build a map from actual SIDs to predictable constant values and yet 58# keep the parent/child relationships. For example: 59# {..., "sid":"1539706952458276-8652", ...} 60# {..., "sid":"1539706952458276-8652/1539706952649493-15452", ...} 61# becomes: 62# {..., "sid":"_SID1_", ...} 63# {..., "sid":"_SID1_/_SID2_", ...} 64my $sid_map; 65my $sid_count = 0; 66 67my $processes; 68 69while (<>) { 70 my $line = decode_json( $_ ); 71 72 my $sid = ""; 73 my $sid_sep = ""; 74 75 my $raw_sid = $line->{'sid'}; 76 my @raw_sid_parts = split /\//, $raw_sid; 77 foreach my $raw_sid_k (@raw_sid_parts) { 78 if (!exists $sid_map->{$raw_sid_k}) { 79 $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_'; 80 $sid_count++; 81 } 82 $sid = $sid . $sid_sep . $sid_map->{$raw_sid_k}; 83 $sid_sep = '/'; 84 } 85 86 my $event = $line->{'event'}; 87 88 if ($event eq 'version') { 89 $processes->{$sid}->{'version'} = $line->{'exe'}; 90 if ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) { 91 # If we are generating data FOR the test script, replace 92 # the reported git.exe version with a reference to an 93 # environment variable. When our output is pasted into 94 # the test script, it will then be expanded in future 95 # test runs to the THEN current version of git.exe. 96 # We assume that the test script uses env var $V. 97 $processes->{$sid}->{'version'} = "\$V"; 98 } 99 } 100 101 elsif ($event eq 'start') { 102 $processes->{$sid}->{'argv'} = $line->{'argv'}; 103 $processes->{$sid}->{'argv'}[0] = "_EXE_"; 104 } 105 106 elsif ($event eq 'exit') { 107 $processes->{$sid}->{'exit_code'} = $line->{'code'}; 108 } 109 110 elsif ($event eq 'atexit') { 111 $processes->{$sid}->{'exit_code'} = $line->{'code'}; 112 } 113 114 elsif ($event eq 'error') { 115 # For HEREDOC purposes, use the error message format string if 116 # available, rather than the formatted message (which probably 117 # has an absolute pathname). 118 if (exists $line->{'fmt'}) { 119 push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} ); 120 } 121 elsif (exists $line->{'msg'}) { 122 push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} ); 123 } 124 } 125 126 elsif ($event eq 'cmd_path') { 127 ## $processes->{$sid}->{'path'} = $line->{'path'}; 128 # 129 # Like in the 'start' event, we need to replace the value of 130 # argv[0] with a token for HEREDOC purposes. However, the 131 # event is only emitted when RUNTIME_PREFIX is defined, so 132 # just omit it for testing purposes. 133 # $processes->{$sid}->{'path'} = "_EXE_"; 134 } 135 elsif ($event eq 'cmd_ancestry') { 136 # 'cmd_ancestry' is platform-specific and not implemented everywhere, so 137 # just skip it for testing purposes. 138 } 139 elsif ($event eq 'cmd_name') { 140 $processes->{$sid}->{'name'} = $line->{'name'}; 141 $processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'}; 142 } 143 144 elsif ($event eq 'alias') { 145 $processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'}; 146 $processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'}; 147 } 148 149 elsif ($event eq 'def_param') { 150 my $kv; 151 $kv->{'param'} = $line->{'param'}; 152 $kv->{'value'} = $line->{'value'}; 153 push( @{$processes->{$sid}->{'params'}}, $kv ); 154 } 155 156 elsif ($event eq 'child_start') { 157 if ($show_children == 1) { 158 $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'}; 159 $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'}; 160 $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = "_EXE_"; 161 $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0; 162 } 163 } 164 165 elsif ($event eq 'child_exit') { 166 if ($show_children == 1) { 167 $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'}; 168 } 169 } 170 171 # TODO decide what information we want to test from thread events. 172 173 elsif ($event eq 'thread_start') { 174 if ($show_threads == 1) { 175 } 176 } 177 178 elsif ($event eq 'thread_exit') { 179 if ($show_threads == 1) { 180 } 181 } 182 183 # TODO decide what information we want to test from exec events. 184 185 elsif ($event eq 'exec') { 186 if ($show_exec == 1) { 187 } 188 } 189 190 elsif ($event eq 'exec_result') { 191 if ($show_exec == 1) { 192 } 193 } 194 195 elsif ($event eq 'def_param') { 196 # Accumulate parameter key/value pairs by key rather than in an array 197 # so that we get overwrite (last one wins) effects. 198 $processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'}; 199 } 200 201 elsif ($event eq 'def_repo') { 202 # $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'}; 203 $processes->{$sid}->{'repos'}->{$line->{'repo'}} = "_WORKTREE_"; 204 } 205 206 # A series of potentially nested and threaded region and data events 207 # is fundamentally incompatibile with the type of summary record we 208 # are building in this script. Since they are intended for 209 # perf-trace-like analysis rather than a result summary, we ignore 210 # most of them here. 211 212 # elsif ($event eq 'region_enter') { 213 # } 214 # elsif ($event eq 'region_leave') { 215 # } 216 217 elsif ($event eq 'data') { 218 my $cat = $line->{'category'}; 219 if ($cat eq 'test_category') { 220 221 my $key = $line->{'key'}; 222 my $value = $line->{'value'}; 223 $processes->{$sid}->{'data'}->{$cat}->{$key} = $value; 224 } 225 } 226 227 # This trace2 target does not emit 'printf' events. 228 # 229 # elsif ($event eq 'printf') { 230 # } 231} 232 233# Dump the resulting hash into something that we can compare against 234# in the test script. These options make Dumper output look a little 235# bit like JSON. Also convert variable references of the form "$VAR*" 236# so that the matching HEREDOC doesn't need to escape it. 237 238$Data::Dumper::Sortkeys = 1; 239$Data::Dumper::Indent = 1; 240$Data::Dumper::Purity = 1; 241$Data::Dumper::Pair = ':'; 242 243my $out = Dumper($processes); 244$out =~ s/'/"/g; 245$out =~ s/\$VAR/VAR/g; 246 247# Finally, if we're running this script to generate (manually confirmed) 248# data to add to the test script, guard the indentation. 249 250if ($gen_heredoc == 1) { 251 $out =~ s/^/\t\|/gms; 252} 253 254print $out; 255