1#include <log.h>
2#include <math/min_max.h>
3#include <thread/thread.h>
4#include <thread/thread_diag.h>
5
6struct thread_diag *thread_diag(struct thread *t) {
7 if (!t || !(thread_get_flags(t) & THREAD_FLAG_DIAG))
8 return NULL;
9
10 return t->private;
11}
12
13bool thread_diag_attach(struct thread *t, struct thread_diag *d) {
14 if (!t || !d)
15 return false;
16
17 if (t->private)
18 return false;
19
20 t->private = d;
21 thread_or_flags(t, flags: THREAD_FLAG_DIAG);
22 return true;
23}
24
25void thread_diag_detach(struct thread *t) {
26 if (!t || !(thread_get_flags(t) & THREAD_FLAG_DIAG))
27 return;
28
29 thread_and_flags(t, flags: ~THREAD_FLAG_DIAG);
30 t->private = NULL;
31}
32
33void thread_diag_record_arm(struct thread *t, const char *site, void *ra,
34 void *object, enum thread_state state,
35 enum thread_wait_type type, uint8_t reason) {
36 struct thread_diag *d = thread_diag(t);
37 if (!d)
38 return;
39
40 d->wait_arm_total++;
41
42 struct thread_wait_arm *newest =
43 d->wait_arm_count
44 ? &d->wait_trace[(d->wait_arm_count - 1) % THREAD_WAIT_TRACE_DEPTH]
45 : NULL;
46
47 if (newest && newest->object == object &&
48 newest->block_count == t->wait_block_count &&
49 newest->wait_type == (uint8_t) type &&
50 newest->state == (uint8_t) state && newest->reason == reason) {
51 newest->last_epoch = atomic_load_rlx(&t->wait_epoch);
52 newest->last_ms = time_get_ms();
53 newest->repeats++;
54 return;
55 }
56
57 struct thread_wait_arm *e =
58 &d->wait_trace[d->wait_arm_count % THREAD_WAIT_TRACE_DEPTH];
59
60 e->site = site;
61 e->ra = ra;
62 e->object = object;
63 e->first_epoch = atomic_load_rlx(&t->wait_epoch);
64 e->last_epoch = e->first_epoch;
65 e->repeats = 1;
66 e->last_ms = time_get_ms();
67 e->state = (uint8_t) state;
68 e->wait_type = (uint8_t) type;
69 e->reason = reason;
70 e->block_count = t->wait_block_count;
71
72 d->wait_arm_count++;
73}
74
75void thread_diag_apc_deliver_enter(struct thread *t, void *ra) {
76 struct thread_diag *d = thread_diag(t);
77 if (!d)
78 return;
79
80 d->apc_deliver_entries++;
81 d->apc_last_deliver_ra = ra;
82}
83
84void thread_diag_apc_deliver_batch(struct thread *t, uint64_t drained) {
85 struct thread_diag *d = thread_diag(t);
86 if (!d)
87 return;
88
89 if (drained > d->apc_deliver_max)
90 d->apc_deliver_max = drained;
91}
92
93static const char *thread_wait_type_str(uint8_t type) {
94 switch (type) {
95 case THREAD_WAIT_NONE: return "NONE";
96 case THREAD_WAIT_UNINTERRUPTIBLE: return "UNINT";
97 case THREAD_WAIT_INTERRUPTIBLE: return "INTR";
98 default: return "?";
99 }
100}
101
102void thread_dump_wait_trace(struct thread *t, const char *role, size_t idx,
103 uint64_t max_arms) {
104 struct thread_diag *d = thread_diag(t);
105 if (!d)
106 return;
107
108 uint64_t count = d->wait_arm_count;
109
110 log_msg(LOG_ERROR, " %s[%zu] arms=%lu (%lu distinct)", role, idx,
111 d->wait_arm_total, count);
112
113 log_msg(LOG_ERROR,
114 " %s[%zu] apc: entries=%lu max_burst=%lu deliver_ra=%p", role,
115 idx, d->apc_deliver_entries, d->apc_deliver_max,
116 d->apc_last_deliver_ra);
117
118 uint64_t depth = MIN(max_arms, THREAD_WAIT_TRACE_DEPTH);
119 uint64_t shown = MIN(count, depth);
120
121 for (uint64_t i = 0; i < shown; i++) {
122 uint64_t seq = count - 1 - i;
123 struct thread_wait_arm *e =
124 &d->wait_trace[seq % THREAD_WAIT_TRACE_DEPTH];
125
126 log_msg(LOG_ERROR, " %s[%zu] arm#%lu %s ra=%p x%lu", role, idx, seq,
127 e->site ? e->site : "?", e->ra, e->repeats);
128
129 log_msg(LOG_ERROR,
130 " %s[%zu] -> state=%d %s object=%p reason=%u "
131 "epoch=%lu..%lu last=%lums blocks=%u",
132 role, idx, (int) e->state, thread_wait_type_str(e->wait_type),
133 e->object, (unsigned) e->reason, e->first_epoch, e->last_epoch,
134 e->last_ms, e->block_count);
135 }
136}
137