Skip to content

Commit 8ad5756

Browse files
jeffhostetlergitster
authored andcommitted
trace2: add stopwatch timers
Add stopwatch timer mechanism to Trace2. Timers are an alternative to Trace2 Regions. Regions are useful for measuring the time spent in various computation phases, such as the time to read the index, time to scan for unstaged files, time to scan for untracked files, and etc. However, regions are not appropriate in all places. For example, during a checkout, it would be very inefficient to use regions to measure the total time spent inflating objects from the ODB from across the entire lifetime of the process; a per-unzip() region would flood the output and significantly slow the command; and some form of post-processing would be requried to compute the time spent in unzip(). Timers can be used to measure a series of timer intervals and emit a single summary event (at thread and/or process exit). Signed-off-by: Jeff Hostetler <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 24a4c45 commit 8ad5756

File tree

15 files changed

+786
-0
lines changed

15 files changed

+786
-0
lines changed

Documentation/technical/api-trace2.txt

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,42 @@ The "value" field may be an integer or a string.
769769
}
770770
------------
771771

772+
`"th_timer"`::
773+
This event logs the amount of time that a stopwatch timer was
774+
running in the thread. This event is generated when a thread
775+
exits for timers that requested per-thread events.
776+
+
777+
------------
778+
{
779+
"event":"th_timer",
780+
...
781+
"category":"my_category",
782+
"name":"my_timer",
783+
"intervals":5, # number of time it was started/stopped
784+
"t_total":0.052741, # total time in seconds it was running
785+
"t_min":0.010061, # shortest interval
786+
"t_max":0.011648 # longest interval
787+
}
788+
------------
789+
790+
`"timer"`::
791+
This event logs the amount of time that a stopwatch timer was
792+
running aggregated across all threads. This event is generated
793+
when the process exits.
794+
+
795+
------------
796+
{
797+
"event":"timer",
798+
...
799+
"category":"my_category",
800+
"name":"my_timer",
801+
"intervals":5, # number of time it was started/stopped
802+
"t_total":0.052741, # total time in seconds it was running
803+
"t_min":0.010061, # shortest interval
804+
"t_max":0.011648 # longest interval
805+
}
806+
------------
807+
772808
== Example Trace2 API Usage
773809

774810
Here is a hypothetical usage of the Trace2 API showing the intended
@@ -1200,6 +1236,60 @@ d0 | main | data | r0 | 0.002126 | 0.002126 | fsy
12001236
d0 | main | exit | | 0.000470 | | | code:0
12011237
d0 | main | atexit | | 0.000477 | | | code:0
12021238
----------------
1239+
1240+
Stopwatch Timer Events::
1241+
1242+
Measure the time spent in a function call or span of code
1243+
that might be called from many places within the code
1244+
throughout the life of the process.
1245+
+
1246+
----------------
1247+
static void expensive_function(void)
1248+
{
1249+
trace2_timer_start(TRACE2_TIMER_ID_TEST1);
1250+
...
1251+
sleep_millisec(1000); // Do something expensive
1252+
...
1253+
trace2_timer_stop(TRACE2_TIMER_ID_TEST1);
1254+
}
1255+
1256+
static int ut_100timer(int argc, const char **argv)
1257+
{
1258+
...
1259+
1260+
expensive_function();
1261+
1262+
// Do something else 1...
1263+
1264+
expensive_function();
1265+
1266+
// Do something else 2...
1267+
1268+
expensive_function();
1269+
1270+
return 0;
1271+
}
1272+
----------------
1273+
+
1274+
In this example, we measure the total time spent in
1275+
`expensive_function()` regardless of when it is called
1276+
in the overall flow of the program.
1277+
+
1278+
----------------
1279+
$ export GIT_TRACE2_PERF_BRIEF=1
1280+
$ export GIT_TRACE2_PERF=~/log.perf
1281+
$ t/helper/test-tool trace2 100timer 3 1000
1282+
...
1283+
$ cat ~/log.perf
1284+
d0 | main | version | | | | | ...
1285+
d0 | main | start | | 0.001453 | | | t/helper/test-tool trace2 100timer 3 1000
1286+
d0 | main | cmd_name | | | | | trace2 (trace2)
1287+
d0 | main | exit | | 3.003667 | | | code:0
1288+
d0 | main | timer | | | | test | name:test1 intervals:3 total:3.001686 min:1.000254 max:1.000929
1289+
d0 | main | atexit | | 3.003796 | | | code:0
1290+
----------------
1291+
1292+
12031293
== Future Work
12041294

12051295
=== Relationship to the Existing Trace Api (api-trace.txt)

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,7 @@ LIB_OBJS += trace2/tr2_tgt_event.o
11021102
LIB_OBJS += trace2/tr2_tgt_normal.o
11031103
LIB_OBJS += trace2/tr2_tgt_perf.o
11041104
LIB_OBJS += trace2/tr2_tls.o
1105+
LIB_OBJS += trace2/tr2_tmr.o
11051106
LIB_OBJS += trailer.o
11061107
LIB_OBJS += transport-helper.o
11071108
LIB_OBJS += transport.o

t/helper/test-trace2.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,101 @@ static int ut_010bug_BUG(int argc, const char **argv)
228228
BUG("a %s message", "BUG");
229229
}
230230

231+
/*
232+
* Single-threaded timer test. Create several intervals using the
233+
* TEST1 timer. The test script can verify that an aggregate Trace2
234+
* "timer" event is emitted indicating that we started+stopped the
235+
* timer the requested number of times.
236+
*/
237+
static int ut_100timer(int argc, const char **argv)
238+
{
239+
const char *usage_error =
240+
"expect <count> <ms_delay>";
241+
242+
int count = 0;
243+
int delay = 0;
244+
int k;
245+
246+
if (argc != 2)
247+
die("%s", usage_error);
248+
if (get_i(&count, argv[0]))
249+
die("%s", usage_error);
250+
if (get_i(&delay, argv[1]))
251+
die("%s", usage_error);
252+
253+
for (k = 0; k < count; k++) {
254+
trace2_timer_start(TRACE2_TIMER_ID_TEST1);
255+
sleep_millisec(delay);
256+
trace2_timer_stop(TRACE2_TIMER_ID_TEST1);
257+
}
258+
259+
return 0;
260+
}
261+
262+
struct ut_101_data {
263+
int count;
264+
int delay;
265+
};
266+
267+
static void *ut_101timer_thread_proc(void *_ut_101_data)
268+
{
269+
struct ut_101_data *data = _ut_101_data;
270+
int k;
271+
272+
trace2_thread_start("ut_101");
273+
274+
for (k = 0; k < data->count; k++) {
275+
trace2_timer_start(TRACE2_TIMER_ID_TEST2);
276+
sleep_millisec(data->delay);
277+
trace2_timer_stop(TRACE2_TIMER_ID_TEST2);
278+
}
279+
280+
trace2_thread_exit();
281+
return NULL;
282+
}
283+
284+
/*
285+
* Multi-threaded timer test. Create several threads that each create
286+
* several intervals using the TEST2 timer. The test script can verify
287+
* that an individual Trace2 "th_timer" events for each thread and an
288+
* aggregate "timer" event are generated.
289+
*/
290+
static int ut_101timer(int argc, const char **argv)
291+
{
292+
const char *usage_error =
293+
"expect <count> <ms_delay> <threads>";
294+
295+
struct ut_101_data data = { 0, 0 };
296+
int nr_threads = 0;
297+
int k;
298+
pthread_t *pids = NULL;
299+
300+
if (argc != 3)
301+
die("%s", usage_error);
302+
if (get_i(&data.count, argv[0]))
303+
die("%s", usage_error);
304+
if (get_i(&data.delay, argv[1]))
305+
die("%s", usage_error);
306+
if (get_i(&nr_threads, argv[2]))
307+
die("%s", usage_error);
308+
309+
CALLOC_ARRAY(pids, nr_threads);
310+
311+
for (k = 0; k < nr_threads; k++) {
312+
if (pthread_create(&pids[k], NULL, ut_101timer_thread_proc, &data))
313+
die("failed to create thread[%d]", k);
314+
}
315+
316+
for (k = 0; k < nr_threads; k++) {
317+
if (pthread_join(pids[k], NULL))
318+
die("failed to join thread[%d]", k);
319+
}
320+
321+
free(pids);
322+
323+
return 0;
324+
}
325+
231326
/*
232327
* Usage:
233328
* test-tool trace2 <ut_name_1> <ut_usage_1>
@@ -248,6 +343,9 @@ static struct unit_test ut_table[] = {
248343
{ ut_008bug, "008bug", "" },
249344
{ ut_009bug_BUG, "009bug_BUG","" },
250345
{ ut_010bug_BUG, "010bug_BUG","" },
346+
347+
{ ut_100timer, "100timer", "<count> <ms_delay>" },
348+
{ ut_101timer, "101timer", "<count> <ms_delay> <threads>" },
251349
};
252350
/* clang-format on */
253351

t/t0211-trace2-perf.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,53 @@ test_expect_success 'using global config, perf stream, return code 0' '
173173
test_cmp expect actual
174174
'
175175

176+
# Exercise the stopwatch timers in a loop and confirm that we have
177+
# as many start/stop intervals as expected. We cannot really test the
178+
# actual (total, min, max) timer values, so we have to assume that they
179+
# are good, but we can verify the interval count.
180+
#
181+
# The timer "test/test1" should only emit a global summary "timer" event.
182+
# The timer "test/test2" should emit per-thread "th_timer" events and a
183+
# global summary "timer" event.
184+
185+
have_timer_event () {
186+
thread=$1 event=$2 category=$3 name=$4 intervals=$5 file=$6 &&
187+
188+
pattern="d0|${thread}|${event}||||${category}|name:${name} intervals:${intervals}" &&
189+
190+
grep "${pattern}" ${file}
191+
}
192+
193+
test_expect_success 'stopwatch timer test/test1' '
194+
test_when_finished "rm trace.perf actual" &&
195+
test_config_global trace2.perfBrief 1 &&
196+
test_config_global trace2.perfTarget "$(pwd)/trace.perf" &&
197+
198+
# Use the timer "test1" 5 times from "main".
199+
test-tool trace2 100timer 5 10 &&
200+
201+
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
202+
203+
have_timer_event "main" "timer" "test" "test1" 5 actual
204+
'
205+
206+
test_expect_success 'stopwatch timer test/test2' '
207+
test_when_finished "rm trace.perf actual" &&
208+
test_config_global trace2.perfBrief 1 &&
209+
test_config_global trace2.perfTarget "$(pwd)/trace.perf" &&
210+
211+
# Use the timer "test2" 5 times each in 3 threads.
212+
test-tool trace2 101timer 5 10 3 &&
213+
214+
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
215+
216+
# So we should have 3 per-thread events of 5 each.
217+
have_timer_event "th01:ut_101" "th_timer" "test" "test2" 5 actual &&
218+
have_timer_event "th02:ut_101" "th_timer" "test" "test2" 5 actual &&
219+
have_timer_event "th03:ut_101" "th_timer" "test" "test2" 5 actual &&
220+
221+
# And we should have 15 total uses.
222+
have_timer_event "main" "timer" "test" "test2" 15 actual
223+
'
224+
176225
test_done

t/t0211/scrub_perf.perl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@
6464
goto SKIP_LINE;
6565
}
6666
}
67+
elsif ($tokens[$col_event] =~ m/timer/) {
68+
# This also captures "th_timer" events
69+
$tokens[$col_rest] =~ s/ total:\d+\.\d*/ total:_T_TOTAL_/;
70+
$tokens[$col_rest] =~ s/ min:\d+\.\d*/ min:_T_MIN_/;
71+
$tokens[$col_rest] =~ s/ max:\d+\.\d*/ max:_T_MAX_/;
72+
}
6773

6874
# t_abs and t_rel are either blank or a float. Replace the float
6975
# with a constant for matching the HEREDOC in the test script.

0 commit comments

Comments
 (0)