libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
Trace.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2026
4 * Trace.cpp
5 *
6 * See Trace.h
7 ***************************************************************************/
8#include "Trace.h"
9
10#if LOGGING_ENABLED
11
12#include <stdarg.h>
13#include <stdint.h>
14#include <stdio.h>
15#include <string.h>
16#include <unistd.h>
17
18#include "Platform.h"
19#include "Thread.h"
20#include "Mutex.h"
21#include "Time.h"
22
23#define TRACE_MAX_THREADS 12
24#define TRACE_RING_SIZE 256 // phase changes remembered, across all threads
25#define TRACE_DUMP_EVENTS 64 // how many of those a stall report replays
26#define TRACE_SLOW_PHASE_MS 500 // a phase this long is reported when it ends
27#define TRACE_STALL_MS 3000 // a phase this long is reported while it is still going on
28#define TRACE_REPEAT_MS 5000 // ...and again this often
29#define WATCHDOG_STACKSIZE (16 * 1024)
30
33{
35 const char * name;
36 const char * volatile where;
37 volatile uint32_t since;
38};
39
42{
43 uint32_t when;
44 int slot;
45 const char * volatile where;
46};
47
48static TraceSlot slots[TRACE_MAX_THREADS];
49static volatile int slotCount = 0; // slots[0..slotCount) are filled in
50static TraceEvent ring[TRACE_RING_SIZE];
51static volatile uint32_t ringPos = 0; // total events ever added
52static void (*stateFn)(char *, size_t) = nullptr;
53static Thread watchdogThread;
54static Ticks epoch = 0;
55static bool epochSet = false;
56
57static Mutex & RegisterLock() { static Mutex m; return m; }
58
61static uint32_t NowMs()
62{
63 return epochSet ? SystemTime::diffMillisecs(epoch, SystemTime::now()) : 0;
64}
65
66static bool IsIdle(const char * where)
67{
68 return !where || strcmp(where, "idle") == 0;
69}
70
71static int FindSlotIndex()
72{
74 int count = slotCount;
75
76 for(int i = 0; i < count; i++)
77 if(slots[i].id == me)
78 return i;
79
80 return -1;
81}
82
83static void RingAdd(int slot, const char * where, uint32_t now)
84{
85 uint32_t index = __atomic_fetch_add(&ringPos, 1, __ATOMIC_RELAXED) % TRACE_RING_SIZE;
86 ring[index].when = now;
87 ring[index].slot = slot;
88 ring[index].where = where;
89}
90
91void TraceThread(const char * name)
92{
93 MutexLock guard(RegisterLock());
94
95 if(!epochSet)
96 {
97 epoch = SystemTime::now();
98 epochSet = true;
99 }
100
101 if(slotCount >= TRACE_MAX_THREADS)
102 return;
103
104 TraceSlot & slot = slots[slotCount];
105 slot.id = ThreadId::current();
106 slot.name = name;
107 slot.where = "idle";
108 slot.since = NowMs();
109 slotCount = slotCount + 1;
110}
111
112void TraceAt(const char * where)
113{
114 int index = FindSlotIndex();
115 if(index < 0)
116 return;
117
118 TraceSlot & slot = slots[index];
119 uint32_t now = NowMs();
120 const char * previous = slot.where;
121
122 if(previous == where) // eg. a per-frame heartbeat: just proves the thread is alive
123 {
124 slot.since = now;
125 return;
126 }
127
128 uint32_t ms = now - slot.since;
129
130 slot.where = where;
131 slot.since = now;
132 RingAdd(index, where, now);
133
134 if(ms >= TRACE_SLOW_PHASE_MS && !IsIdle(previous))
135 LOG_WARN("[trace] [%s] '%s' took %u ms (now '%s')", slot.name, previous, (unsigned)ms, where);
136}
137
138void TraceLog(const char * fmt, ...)
139{
140 char message[192];
141 va_list args;
142 va_start(args, fmt);
143 vsnprintf(message, sizeof(message), fmt, args);
144 va_end(args);
145
146 int index = FindSlotIndex();
147 LOG_INFO("[%s] %s", index >= 0 ? slots[index].name : "?", message);
148}
149
150void TraceSetStateFn(void (*fn)(char *, size_t))
151{
152 stateFn = fn;
153}
154
155static void DumpEverything(uint32_t now)
156{
157 int count = slotCount;
158
159 for(int i = 0; i < count; i++)
160 LOG_ERROR("[trace] [%s] '%s' for %u ms", slots[i].name, slots[i].where ? slots[i].where : "?", (unsigned)(now - slots[i].since));
161
162 if(stateFn)
163 {
164 char state[192] = { 0 };
165 stateFn(state, sizeof(state));
166 LOG_ERROR("[trace] state: %s", state);
167 }
168}
169
170static void DumpHistory(uint32_t now)
171{
172 uint32_t total = ringPos;
173 uint32_t events = total < TRACE_DUMP_EVENTS ? total : TRACE_DUMP_EVENTS;
174
175 LOG_ERROR("[trace] last %u phase changes, oldest first:", (unsigned)events);
176
177 for(uint32_t n = events; n > 0; n--)
178 {
179 const TraceEvent & event = ring[(total - n) % TRACE_RING_SIZE];
180 if(!event.where || event.slot < 0 || event.slot >= slotCount)
181 continue;
182
183 LOG_ERROR("[trace] -%5u ms [%s] %s", (unsigned)(now - event.when), slots[event.slot].name, event.where);
184 }
185}
186
187static void * watchdog(void *)
188{
189 uint32_t nextReport[TRACE_MAX_THREADS];
190 uint32_t reportedSince[TRACE_MAX_THREADS];
191 bool historyDumped = false;
192
193 for(int i = 0; i < TRACE_MAX_THREADS; i++)
194 {
195 nextReport[i] = TRACE_STALL_MS;
196 reportedSince[i] = 0;
197 }
198
199 while(!watchdogThread.stopRequested())
200 {
201 usleep(250000);
202
203 uint32_t now = NowMs();
204 int count = slotCount;
205 bool anyStalled = false;
206
207 for(int i = 0; i < count; i++)
208 {
209 uint32_t since = slots[i].since;
210 const char * where = slots[i].where;
211
212 if(since != reportedSince[i]) // moved on since the last report
213 {
214 reportedSince[i] = since;
215 nextReport[i] = TRACE_STALL_MS;
216 }
217
218 if(IsIdle(where))
219 continue;
220
221 uint32_t ms = now - since;
222 if(ms >= nextReport[i])
223 {
224 nextReport[i] = ms + TRACE_REPEAT_MS;
225 anyStalled = true;
226 LOG_ERROR("[trace] STALL [%s] in '%s' for %u ms", slots[i].name, where, (unsigned)ms);
227 }
228 }
229
230 if(anyStalled)
231 {
232 DumpEverything(now);
233
234 if(!historyDumped) // once - the history is what led up to the first stall
235 {
236 historyDumped = true;
237 DumpHistory(now);
238 }
239 }
240 }
241 return nullptr;
242}
243
244void TraceStartWatchdog()
245{
246 watchdogThread.start(watchdog, nullptr, WATCHDOG_STACKSIZE, ThreadPriority::Normal);
247}
248
249#endif // LOGGING_ENABLED
RAII lock guard - locks on construction, unlocks on destruction.
Definition Mutex.h:37
A simple mutual-exclusion lock.
Definition Mutex.h:14
static Ticks now()
Definition Time.h:28
static uint32_t diffMillisecs(Ticks start, Ticks end)
Definition Time.h:52
A lightweight, comparable identifier for a thread - including the app's own main/original thread,...
Definition Thread.h:113
static ThreadId current()
Definition Thread.cpp:129
A platform thread.
Definition Thread.h:40
One entry in the ring of recent phase changes.
Definition Trace.cpp:42
const char *volatile where
Phase it entered.
Definition Trace.cpp:45
int slot
Index into the slots array of the thread that changed phase.
Definition Trace.cpp:44
uint32_t when
NowMs()
Definition Trace.cpp:43
One traced thread: its name and the phase it is in right now.
Definition Trace.cpp:33
const char * name
Name given to TraceThread()
Definition Trace.cpp:35
volatile uint32_t since
NowMs() when it entered where
Definition Trace.cpp:37
ThreadId id
The traced thread.
Definition Trace.cpp:34
const char *volatile where
Current phase; a string literal, compared by pointer.
Definition Trace.cpp:36