Skip to content

Commit 8107162

Browse files
jeffhostetlergitster
authored andcommitted
trace2: add global counter mechanism
Add global counters mechanism to Trace2. The Trace2 counters mechanism adds the ability to create a set of global counter variables and an API to increment them efficiently. Counters can optionally report per-thread usage in addition to the sum across all threads. Counter events are emitted to the Trace2 logs when a thread exits and at process exit. Counters are an alternative to `data` and `data_json` events. Counters are useful when you want to measure something across the life of the process, when you don't want per-measurement events for performance reasons, when the data does not fit conveniently within a region, or when your control flow does not easily let you write the final total. For example, you might use this to report the number of calls to unzip() or the number of de-delta steps during a checkout. Signed-off-by: Jeff Hostetler <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8ad5756 commit 8107162

File tree

13 files changed

+517
-7
lines changed

13 files changed

+517
-7
lines changed

Documentation/technical/api-trace2.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,37 @@ The "value" field may be an integer or a string.
805805
}
806806
------------
807807

808+
`"th_counter"`::
809+
This event logs the value of a counter variable in a thread.
810+
This event is generated when a thread exits for counters that
811+
requested per-thread events.
812+
+
813+
------------
814+
{
815+
"event":"th_counter",
816+
...
817+
"category":"my_category",
818+
"name":"my_counter",
819+
"count":23
820+
}
821+
------------
822+
823+
`"counter"`::
824+
This event logs the value of a counter variable across all threads.
825+
This event is generated when the process exits. The total value
826+
reported here is the sum across all threads.
827+
+
828+
------------
829+
{
830+
"event":"counter",
831+
...
832+
"category":"my_category",
833+
"name":"my_counter",
834+
"count":23
835+
}
836+
------------
837+
838+
808839
== Example Trace2 API Usage
809840

810841
Here is a hypothetical usage of the Trace2 API showing the intended

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,7 @@ LIB_OBJS += trace.o
10941094
LIB_OBJS += trace2.o
10951095
LIB_OBJS += trace2/tr2_cfg.o
10961096
LIB_OBJS += trace2/tr2_cmd_name.o
1097+
LIB_OBJS += trace2/tr2_ctr.o
10971098
LIB_OBJS += trace2/tr2_dst.o
10981099
LIB_OBJS += trace2/tr2_sid.o
10991100
LIB_OBJS += trace2/tr2_sysenv.o

t/helper/test-trace2.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,92 @@ static int ut_101timer(int argc, const char **argv)
323323
return 0;
324324
}
325325

326+
/*
327+
* Single-threaded counter test. Add several values to the TEST1 counter.
328+
* The test script can verify that the final sum is reported in the "counter"
329+
* event.
330+
*/
331+
static int ut_200counter(int argc, const char **argv)
332+
{
333+
const char *usage_error =
334+
"expect <v1> [<v2> [...]]";
335+
int value;
336+
int k;
337+
338+
if (argc < 1)
339+
die("%s", usage_error);
340+
341+
for (k = 0; k < argc; k++) {
342+
if (get_i(&value, argv[k]))
343+
die("invalid value[%s] -- %s",
344+
argv[k], usage_error);
345+
trace2_counter_add(TRACE2_COUNTER_ID_TEST1, value);
346+
}
347+
348+
return 0;
349+
}
350+
351+
/*
352+
* Multi-threaded counter test. Create seveal threads that each increment
353+
* the TEST2 global counter. The test script can verify that an individual
354+
* "th_counter" event is generated with a partial sum for each thread and
355+
* that a final aggregate "counter" event is generated.
356+
*/
357+
358+
struct ut_201_data {
359+
int v1;
360+
int v2;
361+
};
362+
363+
static void *ut_201counter_thread_proc(void *_ut_201_data)
364+
{
365+
struct ut_201_data *data = _ut_201_data;
366+
367+
trace2_thread_start("ut_201");
368+
369+
trace2_counter_add(TRACE2_COUNTER_ID_TEST2, data->v1);
370+
trace2_counter_add(TRACE2_COUNTER_ID_TEST2, data->v2);
371+
372+
trace2_thread_exit();
373+
return NULL;
374+
}
375+
376+
static int ut_201counter(int argc, const char **argv)
377+
{
378+
const char *usage_error =
379+
"expect <v1> <v2> <threads>";
380+
381+
struct ut_201_data data = { 0, 0 };
382+
int nr_threads = 0;
383+
int k;
384+
pthread_t *pids = NULL;
385+
386+
if (argc != 3)
387+
die("%s", usage_error);
388+
if (get_i(&data.v1, argv[0]))
389+
die("%s", usage_error);
390+
if (get_i(&data.v2, argv[1]))
391+
die("%s", usage_error);
392+
if (get_i(&nr_threads, argv[2]))
393+
die("%s", usage_error);
394+
395+
CALLOC_ARRAY(pids, nr_threads);
396+
397+
for (k = 0; k < nr_threads; k++) {
398+
if (pthread_create(&pids[k], NULL, ut_201counter_thread_proc, &data))
399+
die("failed to create thread[%d]", k);
400+
}
401+
402+
for (k = 0; k < nr_threads; k++) {
403+
if (pthread_join(pids[k], NULL))
404+
die("failed to join thread[%d]", k);
405+
}
406+
407+
free(pids);
408+
409+
return 0;
410+
}
411+
326412
/*
327413
* Usage:
328414
* test-tool trace2 <ut_name_1> <ut_usage_1>
@@ -346,6 +432,9 @@ static struct unit_test ut_table[] = {
346432

347433
{ ut_100timer, "100timer", "<count> <ms_delay>" },
348434
{ ut_101timer, "101timer", "<count> <ms_delay> <threads>" },
435+
436+
{ ut_200counter, "200counter", "<v1> [<v2> [<v3> [...]]]" },
437+
{ ut_201counter, "201counter", "<v1> <v2> <threads>" },
349438
};
350439
/* clang-format on */
351440

t/t0211-trace2-perf.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,50 @@ test_expect_success 'stopwatch timer test/test2' '
222222
have_timer_event "main" "timer" "test" "test2" 15 actual
223223
'
224224

225+
# Exercise the global counters and confirm that we get the expected values.
226+
#
227+
# The counter "test/test1" should only emit a global summary "counter" event.
228+
# The counter "test/test2" could emit per-thread "th_counter" events and a
229+
# global summary "counter" event.
230+
231+
have_counter_event () {
232+
thread=$1 event=$2 category=$3 name=$4 value=$5 file=$6 &&
233+
234+
pattern="d0|${thread}|${event}||||${category}|name:${name} value:${value}" &&
235+
236+
grep "${patern}" ${file}
237+
}
238+
239+
test_expect_success 'global counter test/test1' '
240+
test_when_finished "rm trace.perf actual" &&
241+
test_config_global trace2.perfBrief 1 &&
242+
test_config_global trace2.perfTarget "$(pwd)/trace.perf" &&
243+
244+
# Use the counter "test1" and add n integers.
245+
test-tool trace2 200counter 1 2 3 4 5 &&
246+
247+
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
248+
249+
have_counter_event "main" "counter" "test" "test1" 15 actual
250+
'
251+
252+
test_expect_success 'global counter test/test2' '
253+
test_when_finished "rm trace.perf actual" &&
254+
test_config_global trace2.perfBrief 1 &&
255+
test_config_global trace2.perfTarget "$(pwd)/trace.perf" &&
256+
257+
# Add 2 integers to the counter "test2" in each of 3 threads.
258+
test-tool trace2 201counter 7 13 3 &&
259+
260+
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
261+
262+
# So we should have 3 per-thread events of 5 each.
263+
have_counter_event "th01:ut_201" "th_counter" "test" "test2" 20 actual &&
264+
have_counter_event "th02:ut_201" "th_counter" "test" "test2" 20 actual &&
265+
have_counter_event "th03:ut_201" "th_counter" "test" "test2" 20 actual &&
266+
267+
# And we should have a single event with the total across all threads.
268+
have_counter_event "main" "counter" "test" "test2" 60 actual
269+
'
270+
225271
test_done

trace2.c

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "version.h"
99
#include "trace2/tr2_cfg.h"
1010
#include "trace2/tr2_cmd_name.h"
11+
#include "trace2/tr2_ctr.h"
1112
#include "trace2/tr2_dst.h"
1213
#include "trace2/tr2_sid.h"
1314
#include "trace2/tr2_sysenv.h"
@@ -101,6 +102,22 @@ static void tr2_tgt_emit_a_timer(const struct tr2_timer_metadata *meta,
101102
tgt_j->pfn_timer(meta, timer, is_final_data);
102103
}
103104

105+
/*
106+
* The signature of this function must match the pfn_counter
107+
* method in the targets.
108+
*/
109+
static void tr2_tgt_emit_a_counter(const struct tr2_counter_metadata *meta,
110+
const struct tr2_counter *counter,
111+
int is_final_data)
112+
{
113+
struct tr2_tgt *tgt_j;
114+
int j;
115+
116+
for_each_wanted_builtin (j, tgt_j)
117+
if (tgt_j->pfn_counter)
118+
tgt_j->pfn_counter(meta, counter, is_final_data);
119+
}
120+
104121
static int tr2main_exit_code;
105122

106123
/*
@@ -132,20 +149,26 @@ static void tr2main_atexit_handler(void)
132149
* Some timers want per-thread details. If the main thread
133150
* used one of those timers, emit the details now (before
134151
* we emit the aggregate timer values).
152+
*
153+
* Likewise for counters.
135154
*/
136155
tr2_emit_per_thread_timers(tr2_tgt_emit_a_timer);
156+
tr2_emit_per_thread_counters(tr2_tgt_emit_a_counter);
137157

138158
/*
139-
* Add stopwatch timer data for the main thread to the final
140-
* totals. And then emit the final timer values.
159+
* Add stopwatch timer and counter data for the main thread to
160+
* the final totals. And then emit the final values.
141161
*
142162
* Technically, we shouldn't need to hold the lock to update
143-
* and output the final_timer_block (since all other threads
144-
* should be dead by now), but it doesn't hurt anything.
163+
* and output the final_timer_block and final_counter_block
164+
* (since all other threads should be dead by now), but it
165+
* doesn't hurt anything.
145166
*/
146167
tr2tls_lock();
147168
tr2_update_final_timers();
169+
tr2_update_final_counters();
148170
tr2_emit_final_timers(tr2_tgt_emit_a_timer);
171+
tr2_emit_final_counters(tr2_tgt_emit_a_counter);
149172
tr2tls_unlock();
150173

151174
for_each_wanted_builtin (j, tgt_j)
@@ -582,16 +605,20 @@ void trace2_thread_exit_fl(const char *file, int line)
582605
/*
583606
* Some timers want per-thread details. If this thread used
584607
* one of those timers, emit the details now.
608+
*
609+
* Likewise for counters.
585610
*/
586611
tr2_emit_per_thread_timers(tr2_tgt_emit_a_timer);
612+
tr2_emit_per_thread_counters(tr2_tgt_emit_a_counter);
587613

588614
/*
589-
* Add stopwatch timer data from the current (non-main) thread
590-
* to the final totals. (We'll accumulate data for the main
591-
* thread later during "atexit".)
615+
* Add stopwatch timer and counter data from the current
616+
* (non-main) thread to the final totals. (We'll accumulate
617+
* data for the main thread later during "atexit".)
592618
*/
593619
tr2tls_lock();
594620
tr2_update_final_timers();
621+
tr2_update_final_counters();
595622
tr2tls_unlock();
596623

597624
for_each_wanted_builtin (j, tgt_j)
@@ -870,6 +897,17 @@ void trace2_timer_stop(enum trace2_timer_id tid)
870897
tr2_stop_timer(tid);
871898
}
872899

900+
void trace2_counter_add(enum trace2_counter_id cid, uint64_t value)
901+
{
902+
if (!trace2_enabled)
903+
return;
904+
905+
if (cid < 0 || cid >= TRACE2_NUMBER_OF_COUNTERS)
906+
BUG("trace2_counter_add: invalid counter id: %d", cid);
907+
908+
tr2_counter_increment(cid, value);
909+
}
910+
873911
const char *trace2_session_id(void)
874912
{
875913
return tr2_sid_get();

trace2.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ struct json_writer;
5252
* [] trace2_data* -- emit region/thread/repo data messages.
5353
* [] trace2_printf* -- legacy trace[1] messages.
5454
* [] trace2_timer* -- stopwatch timers (messages are deferred).
55+
* [] trace2_counter* -- global counters (messages are deferred).
5556
*/
5657

5758
/*
@@ -528,6 +529,42 @@ enum trace2_timer_id {
528529
void trace2_timer_start(enum trace2_timer_id tid);
529530
void trace2_timer_stop(enum trace2_timer_id tid);
530531

532+
/*
533+
* Define the set of global counters.
534+
*
535+
* We can add more at any time, but they must be defined at compile
536+
* time (to avoid the need to dynamically allocate and synchronize
537+
* them between different threads).
538+
*
539+
* These must start at 0 and be contiguous (because we use them
540+
* elsewhere as array indexes).
541+
*
542+
* Any values added to this enum be also be added to the
543+
* `tr2_counter_metadata[]` in `trace2/tr2_tr2_ctr.c`.
544+
*/
545+
enum trace2_counter_id {
546+
/*
547+
* Define two counters for testing. See `t/helper/test-trace2.c`.
548+
* These can be used for ad hoc testing, but should not be used
549+
* for permanent analysis code.
550+
*/
551+
TRACE2_COUNTER_ID_TEST1 = 0, /* emits summary event only */
552+
TRACE2_COUNTER_ID_TEST2, /* emits summary and thread events */
553+
554+
/* Add additional counter definitions before here. */
555+
TRACE2_NUMBER_OF_COUNTERS
556+
};
557+
558+
/*
559+
* Increase the named global counter by value.
560+
*
561+
* Note that this adds `value` to the current thread's partial sum for
562+
* this counter (without locking) and that the complete sum is not
563+
* available until all threads have exited, so it does not return the
564+
* new value of the counter.
565+
*/
566+
void trace2_counter_add(enum trace2_counter_id cid, uint64_t value);
567+
531568
/*
532569
* Optional platform-specific code to dump information about the
533570
* current and any parent process(es). This is intended to allow

0 commit comments

Comments
 (0)