| #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]; |
| |
| if (t->recursion_count) { |
| /* |
| * The current thread is exiting with |
| * timer[tid] still running. |
| * |
| * Technically, this is a bug, but I'm going |
| * to ignore it. |
| * |
| * I don't think it is worth calling die() |
| * for. I don't think it is worth killing the |
| * process for this bookkeeping error. We |
| * might want to call warning(), but I'm going |
| * to wait on that. |
| * |
| * The downside here is that total_ns won't |
| * include the current open interval (now - |
| * start_ns). I can live with that. |
| */ |
| } |
| |
| 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); |
| } |