| 1 | #include "git-compat-util.h" |
| 2 | #include "trace2/tr2_tgt.h" |
| 3 | #include "trace2/tr2_tls.h" |
| 4 | #include "trace2/tr2_tmr.h" |
| 5 | #include "trace.h" |
| 6 | |
| 7 | #define MY_MAX(a, b) ((a) > (b) ? (a) : (b)) |
| 8 | #define MY_MIN(a, b) ((a) < (b) ? (a) : (b)) |
| 9 | |
| 10 | /* |
| 11 | * A global timer block to aggregate values from the partial sums from |
| 12 | * each thread. |
| 13 | */ |
| 14 | static struct tr2_timer_block final_timer_block; /* access under tr2tls_mutex */ |
| 15 | |
| 16 | /* |
| 17 | * Define metadata for each stopwatch timer. |
| 18 | * |
| 19 | * This array must match "enum trace2_timer_id" and the values |
| 20 | * in "struct tr2_timer_block.timer[*]". |
| 21 | */ |
| 22 | static struct tr2_timer_metadata tr2_timer_metadata[TRACE2_NUMBER_OF_TIMERS] = { |
| 23 | [TRACE2_TIMER_ID_TEST1] = { |
| 24 | .category = "test", |
| 25 | .name = "test1", |
| 26 | .want_per_thread_events = 0, |
| 27 | }, |
| 28 | [TRACE2_TIMER_ID_TEST2] = { |
| 29 | .category = "test", |
| 30 | .name = "test2", |
| 31 | .want_per_thread_events = 1, |
| 32 | }, |
| 33 | |
| 34 | /* Add additional metadata before here. */ |
| 35 | }; |
| 36 | |
| 37 | void tr2_start_timer(enum trace2_timer_id tid) |
| 38 | { |
| 39 | struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); |
| 40 | struct tr2_timer *t = &ctx->timer_block.timer[tid]; |
| 41 | |
| 42 | t->recursion_count++; |
| 43 | if (t->recursion_count > 1) |
| 44 | return; /* ignore recursive starts */ |
| 45 | |
| 46 | t->start_ns = getnanotime(); |
| 47 | } |
| 48 | |
| 49 | void tr2_stop_timer(enum trace2_timer_id tid) |
| 50 | { |
| 51 | struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); |
| 52 | struct tr2_timer *t = &ctx->timer_block.timer[tid]; |
| 53 | uint64_t ns_now; |
| 54 | uint64_t ns_interval; |
| 55 | |
| 56 | assert(t->recursion_count > 0); |
| 57 | |
| 58 | t->recursion_count--; |
| 59 | if (t->recursion_count) |
| 60 | return; /* still in recursive call(s) */ |
| 61 | |
| 62 | ns_now = getnanotime(); |
| 63 | ns_interval = ns_now - t->start_ns; |
| 64 | |
| 65 | t->total_ns += ns_interval; |
| 66 | |
| 67 | /* |
| 68 | * min_ns was initialized to zero (in the xcalloc()) rather |
| 69 | * than UINT_MAX when the block of timers was allocated, |
| 70 | * so we should always set both the min_ns and max_ns values |
| 71 | * the first time that the timer is used. |
| 72 | */ |
| 73 | if (!t->interval_count) { |
| 74 | t->min_ns = ns_interval; |
| 75 | t->max_ns = ns_interval; |
| 76 | } else { |
| 77 | t->min_ns = MY_MIN(ns_interval, t->min_ns); |
| 78 | t->max_ns = MY_MAX(ns_interval, t->max_ns); |
| 79 | } |
| 80 | |
| 81 | t->interval_count++; |
| 82 | |
| 83 | ctx->used_any_timer = 1; |
| 84 | if (tr2_timer_metadata[tid].want_per_thread_events) |
| 85 | ctx->used_any_per_thread_timer = 1; |
| 86 | } |
| 87 | |
| 88 | void tr2_update_final_timers(void) |
| 89 | { |
| 90 | struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); |
| 91 | enum trace2_timer_id tid; |
| 92 | |
| 93 | if (!ctx->used_any_timer) |
| 94 | return; |
| 95 | |
| 96 | /* |
| 97 | * Accessing `final_timer_block` requires holding `tr2tls_mutex`. |
| 98 | * We assume that our caller is holding the lock. |
| 99 | */ |
| 100 | |
| 101 | for (tid = 0; tid < TRACE2_NUMBER_OF_TIMERS; tid++) { |
| 102 | struct tr2_timer *t_final = &final_timer_block.timer[tid]; |
| 103 | struct tr2_timer *t = &ctx->timer_block.timer[tid]; |
| 104 | |
| 105 | /* |
| 106 | * `t->recursion_count` could technically be non-zero, which |
| 107 | * would constitute a bug. Reporting the bug would potentially |
| 108 | * cause an infinite recursion, though, so let's ignore it. |
| 109 | */ |
| 110 | |
| 111 | if (!t->interval_count) |
| 112 | continue; /* this timer was not used by this thread */ |
| 113 | |
| 114 | t_final->total_ns += t->total_ns; |
| 115 | |
| 116 | /* |
| 117 | * final_timer_block.timer[tid].min_ns was initialized to |
| 118 | * was initialized to zero rather than UINT_MAX, so we should |
| 119 | * always set both the min_ns and max_ns values the first time |
| 120 | * that we add a partial sum into it. |
| 121 | */ |
| 122 | if (!t_final->interval_count) { |
| 123 | t_final->min_ns = t->min_ns; |
| 124 | t_final->max_ns = t->max_ns; |
| 125 | } else { |
| 126 | t_final->min_ns = MY_MIN(t_final->min_ns, t->min_ns); |
| 127 | t_final->max_ns = MY_MAX(t_final->max_ns, t->max_ns); |
| 128 | } |
| 129 | |
| 130 | t_final->interval_count += t->interval_count; |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | void tr2_emit_per_thread_timers(tr2_tgt_evt_timer_t *fn_apply) |
| 135 | { |
| 136 | struct tr2tls_thread_ctx *ctx = tr2tls_get_self(); |
| 137 | enum trace2_timer_id tid; |
| 138 | |
| 139 | if (!ctx->used_any_per_thread_timer) |
| 140 | return; |
| 141 | |
| 142 | /* |
| 143 | * For each timer, if the timer wants per-thread events and |
| 144 | * this thread used it, emit it. |
| 145 | */ |
| 146 | for (tid = 0; tid < TRACE2_NUMBER_OF_TIMERS; tid++) |
| 147 | if (tr2_timer_metadata[tid].want_per_thread_events && |
| 148 | ctx->timer_block.timer[tid].interval_count) |
| 149 | fn_apply(&tr2_timer_metadata[tid], |
| 150 | &ctx->timer_block.timer[tid], |
| 151 | 0); |
| 152 | } |
| 153 | |
| 154 | void tr2_emit_final_timers(tr2_tgt_evt_timer_t *fn_apply) |
| 155 | { |
| 156 | enum trace2_timer_id tid; |
| 157 | |
| 158 | /* |
| 159 | * Accessing `final_timer_block` requires holding `tr2tls_mutex`. |
| 160 | * We assume that our caller is holding the lock. |
| 161 | */ |
| 162 | |
| 163 | for (tid = 0; tid < TRACE2_NUMBER_OF_TIMERS; tid++) |
| 164 | if (final_timer_block.timer[tid].interval_count) |
| 165 | fn_apply(&tr2_timer_metadata[tid], |
| 166 | &final_timer_block.timer[tid], |
| 167 | 1); |
| 168 | } |