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