| 1 | #!/usr/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 | |
| 21 | use strict; |
| 22 | use warnings; |
| 23 | use JSON::PP; |
| 24 | use Data::Dumper; |
| 25 | use 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 |
| 30 | my $evt_version = '1'; |
| 31 | |
| 32 | my $show_children = 1; |
| 33 | my $show_exec = 1; |
| 34 | my $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 | |
| 44 | my $gen_heredoc = 0; |
| 45 | my $gen_version = ''; |
| 46 | |
| 47 | GetOptions("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_", ...} |
| 64 | my $sid_map; |
| 65 | my $sid_count = 0; |
| 66 | |
| 67 | my $processes; |
| 68 | |
| 69 | while (<>) { |
| 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 incompatible 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 | my $key = $line->{'key'}; |
| 220 | my $value = $line->{'value'}; |
| 221 | $processes->{$sid}->{'data'}->{$cat}->{$key} = $value; |
| 222 | } |
| 223 | |
| 224 | elsif ($event eq 'data_json') { |
| 225 | # NEEDSWORK: Ignore due to |
| 226 | # compat/win32/trace2_win32_process_info.c, which should log a |
| 227 | # "cmd_ancestry" event instead. |
| 228 | } |
| 229 | |
| 230 | else { |
| 231 | push @{$processes->{$sid}->{$event}} => $line->{value}; |
| 232 | } |
| 233 | |
| 234 | # This trace2 target does not emit 'printf' events. |
| 235 | # |
| 236 | # elsif ($event eq 'printf') { |
| 237 | # } |
| 238 | } |
| 239 | |
| 240 | # Dump the resulting hash into something that we can compare against |
| 241 | # in the test script. These options make Dumper output look a little |
| 242 | # bit like JSON. Also convert variable references of the form "$VAR*" |
| 243 | # so that the matching HEREDOC doesn't need to escape it. |
| 244 | |
| 245 | $Data::Dumper::Sortkeys = 1; |
| 246 | $Data::Dumper::Indent = 1; |
| 247 | $Data::Dumper::Purity = 1; |
| 248 | $Data::Dumper::Pair = ':'; |
| 249 | |
| 250 | my $out = Dumper($processes); |
| 251 | $out =~ s/'/"/g; |
| 252 | $out =~ s/\$VAR/VAR/g; |
| 253 | |
| 254 | # Finally, if we're running this script to generate (manually confirmed) |
| 255 | # data to add to the test script, guard the indentation. |
| 256 | |
| 257 | if ($gen_heredoc == 1) { |
| 258 | $out =~ s/^/\t\|/gms; |
| 259 | } |
| 260 | |
| 261 | print $out; |