| #include "git-compat-util.h" | |
| #include "trace2/tr2_tgt.h" | |
| #include "trace2/tr2_tls.h" | |
| #include "trace2/tr2_tmr.h" | |
| #include "trace.h" | |
| #define MY_MAX(a, b) ((a) > (b) ? (a) : (b)) | |
| #define MY_MIN(a, b) ((a) < (b) ? (a) : (b)) | |
| /* | |
| * A global timer block to aggregate values from the partial sums from | |
| * each thread. | |
| */ | |
| static struct tr2_timer_block final_timer_block; /* access under tr2tls_mutex */ | |
| /* | |
| * Define metadata for each stopwatch timer. | |
| * | |
| * This array must match "enum trace2_timer_id" and the values | |
| * in "struct tr2_timer_block.timer[*]". | |
| */ | |
| static struct tr2_timer_metadata tr2_timer_metadata[TRACE2_NUMBER_OF_TIMERS] = { | |
| [TRACE2_TIMER_ID_TEST1] = { | |
| .category = "test", | |
| .name = "test1", | |
| .want_per_thread_events = 0, | |
| }, | |
| [TRACE2_TIMER_ID_TEST2] = { | |
| .category = "test", | |
| .name = "test2", | |
| .want_per_thread_events = 1, | |
| }, | |
| /* Add additional metadata before here. */ | |
| }; | |
| void tr2_start_timer(enum trace2_timer_id tid) | |
| { | |
| struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); | |
| struct tr2_timer *t = &ctx->timer_block.timer[tid]; | |
| t->recursion_count++; | |
| if (t->recursion_count > 1) | |
| return; /* ignore recursive starts */ | |
| t->start_ns = getnanotime(); | |
| } | |
| void tr2_stop_timer(enum trace2_timer_id tid) | |
| { | |
| struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); | |
| struct tr2_timer *t = &ctx->timer_block.timer[tid]; | |
| uint64_t ns_now; | |
| uint64_t ns_interval; | |
| assert(t->recursion_count > 0); | |
| t->recursion_count--; | |
| if (t->recursion_count) | |
| return; /* still in recursive call(s) */ | |
| ns_now = getnanotime(); | |
| ns_interval = ns_now - t->start_ns; | |
| t->total_ns += ns_interval; | |
| /* | |
| * min_ns was initialized to zero (in the xcalloc()) rather | |
| * than UINT_MAX when the block of timers was allocated, | |
| * so we should always set both the min_ns and max_ns values | |
| * the first time that the timer is used. | |
| */ | |
| if (!t->interval_count) { | |
| t->min_ns = ns_interval; | |
| t->max_ns = ns_interval; | |
| } else { | |
| t->min_ns = MY_MIN(ns_interval, t->min_ns); | |
| t->max_ns = MY_MAX(ns_interval, t->max_ns); | |
| } | |
| t->interval_count++; | |
| ctx->used_any_timer = 1; | |
| if (tr2_timer_metadata[tid].want_per_thread_events) | |
| ctx->used_any_per_thread_timer = 1; | |
| } | |
| void tr2_update_final_timers(void) | |
| { | |
| struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); | |
| enum trace2_timer_id tid; | |
| if (!ctx->used_any_timer) | |
| return; | |
| /* | |
| * Accessing `final_timer_block` requires holding `tr2tls_mutex`. | |
| * We assume that our caller is holding the lock. | |
| */ | |
| for (tid = 0; tid < TRACE2_NUMBER_OF_TIMERS; tid++) { | |
| struct tr2_timer *t_final = &final_timer_block.timer[tid]; | |
| struct tr2_timer *t = &ctx->timer_block.timer[tid]; | |
| /* | |
| * `t->recursion_count` could technically be non-zero, which | |
| * would constitute a bug. Reporting the bug would potentially | |
| * cause an infinite recursion, though, so let's ignore it. | |
| */ | |
| if (!t->interval_count) | |
| continue; /* this timer was not used by this thread */ | |
| t_final->total_ns += t->total_ns; | |
| /* | |
| * final_timer_block.timer[tid].min_ns was initialized to | |
| * was initialized to zero rather than UINT_MAX, so we should | |
| * always set both the min_ns and max_ns values the first time | |
| * that we add a partial sum into it. | |
| */ | |
| if (!t_final->interval_count) { | |
| t_final->min_ns = t->min_ns; | |
| t_final->max_ns = t->max_ns; | |
| } else { | |
| t_final->min_ns = MY_MIN(t_final->min_ns, t->min_ns); | |
| t_final->max_ns = MY_MAX(t_final->max_ns, t->max_ns); | |
| } | |
| t_final->interval_count += t->interval_count; | |
| } | |
| } | |
| void tr2_emit_per_thread_timers(tr2_tgt_evt_timer_t *fn_apply) | |
| { | |
| struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); | |
| enum trace2_timer_id tid; | |
| if (!ctx->used_any_per_thread_timer) | |
| return; | |
| /* | |
| * For each timer, if the timer wants per-thread events and | |
| * this thread used it, emit it. | |
| */ | |
| for (tid = 0; tid < TRACE2_NUMBER_OF_TIMERS; tid++) | |
| if (tr2_timer_metadata[tid].want_per_thread_events && | |
| ctx->timer_block.timer[tid].interval_count) | |
| fn_apply(&tr2_timer_metadata[tid], | |
| &ctx->timer_block.timer[tid], | |
| 0); | |
| } | |
| void tr2_emit_final_timers(tr2_tgt_evt_timer_t *fn_apply) | |
| { | |
| enum trace2_timer_id tid; | |
| /* | |
| * Accessing `final_timer_block` requires holding `tr2tls_mutex`. | |
| * We assume that our caller is holding the lock. | |
| */ | |
| for (tid = 0; tid < TRACE2_NUMBER_OF_TIMERS; tid++) | |
| if (final_timer_block.timer[tid].interval_count) | |
| fn_apply(&tr2_timer_metadata[tid], | |
| &final_timer_block.timer[tid], | |
| 1); | |
| } |