/
profiler.d
303 lines (250 loc) · 7.41 KB
/
profiler.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/**
Trace Event Format profiling.
Copyright: Guillaume Piolat 2022.
License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Authors: Guillaume Piolat
*/
module dplug.gui.profiler;
// TODO: clock should be monotonic per-thread
import core.stdc.stdio;
import dplug.core.math;
import dplug.core.sync;
import dplug.core.thread;
import dplug.core.string;
import dplug.core.nogc;
import dplug.core.vec;
version(Windows)
{
import core.sys.windows.windef;
import core.sys.windows.winbase;
}
nothrow @nogc:
/// Allows to generate a Trace Event Format profile JSON.
interface IProfiler
{
nothrow @nogc:
/// All functions for this interface can be called from many threads at once.
/// However, from the same thread, there is an ordering.
/// - `begin/end` pairs must be balanced, per-thread
/// - `begin/end` pairs can be nested, per-thread
/// - `category` must come outside of a `begin`/`end` pair
/// Set current category for pending begin and instant events, for the current thread.
/// categoryZ must be a zero-terminated slice (zero not counted in length).
/// This is thread-safe and can be called from multiple threads.
/// Returns: itself.
IProfiler category(const(char)[] categoryZ);
/// Add an instant event to the trace.
/// This is thread-safe and can be called from multiple threads.
/// Returns: itself.
IProfiler instant(const(char)[] categoryZ);
/// This Begin/End Event will be added to the queue.
/// Events can be added from whatever thread. But within the same threads, the begin/end events
/// are nested and must be balanced.
/// nameZ must be be a zero-terminated slice (zero not counted in length).
/// Returns: itself.
IProfiler begin(const(char)[] nameZ);
///ditto
IProfiler end();
/// Return a borrowed array of bytes for saving.
/// Lifetime is tied to lifetime of the interface object.
/// After `toBytes` is called, no recording function above can be called.
const(ubyte)[] toBytes();
}
/// Create an `IProfiler`.
IProfiler createProfiler()
{
version(Dplug_ProfileUI)
{
return mallocNew!TraceProfiler();
}
else
{
return mallocNew!NullProfiler();
}
}
/// Destroy an `IProfiler` created with `createTraceProfiler`.
void destroyProfiler(IProfiler profiler)
{
destroyFree(profiler);
}
class NullProfiler : IProfiler
{
override IProfiler category(const(char)[] categoryZ)
{
return this;
}
override IProfiler instant(const(char)[] categoryZ)
{
return this;
}
override IProfiler begin(const(char)[] nameZ)
{
return this;
}
override IProfiler end()
{
return this;
}
override const(ubyte)[] toBytes()
{
return [];
}
}
version(Dplug_ProfileUI):
/// Allows to generate a Trace Event Format profile JSON.
class TraceProfiler : IProfiler
{
public:
nothrow:
@nogc:
this()
{
_clock.initialize();
_mutex = makeMutex;
}
override IProfiler category(const(char)[] categoryZ)
{
ensureThreadContext();
threadInfo.lastCategoryZ = categoryZ;
return this;
}
override IProfiler instant(const(char)[] nameZ)
{
ensureThreadContext();
long us = _clock.getTickUs();
addEvent(nameZ, threadInfo.lastCategoryZ, "i", us);
return this;
}
override IProfiler begin(const(char)[] nameZ)
{
ensureThreadContext();
long us = _clock.getTickUs();
addEvent(nameZ, threadInfo.lastCategoryZ, "B", us);
return this;
}
override IProfiler end()
{
// no ensureThreadContext, since by API can't begin with .end
long us = _clock.getTickUs();
addEventNoname("E", us);
return this;
}
override const(ubyte)[] toBytes()
{
finalize();
return cast(const(ubyte)[])_concatenated.asSlice();
}
private:
bool _finalized = false;
Clock _clock;
static ThreadContext threadInfo; // this is TLS
Vec!(String*) _allBuffers; // list of all thread-local buffers, this access is checked
String _concatenated;
UncheckedMutex _mutex; // in below mutex.
void finalize()
{
if (_finalized)
return;
_concatenated.makeEmpty();
_concatenated ~= "[";
_mutex.lock();
foreach(ref bufptr; _allBuffers[])
_concatenated ~= *bufptr;
_mutex.unlock();
_concatenated ~= "]";
_finalized = true;
}
void addEvent(const(char)[] nameZ,
const(char)[] categoryZ,
const(char)[] typeZ,
long us)
{
if (!threadInfo.firstEventForThisThread)
{
threadInfo.buffer ~= ",\n";
}
threadInfo.firstEventForThisThread = false;
char[256] buf;
size_t tid = getCurrentThreadId();
snprintf(buf.ptr, 256, `{ "name": "%s", "cat": "%s", "ph": "%s", "pid": 0, "tid": %zu, "ts": %lld }`,
nameZ.ptr, categoryZ.ptr, typeZ.ptr, tid, us);
threadInfo.buffer.appendZeroTerminatedString(buf.ptr);
}
void addEventNoname(const(char)[] typeZ, long us)
{
if (!threadInfo.firstEventForThisThread)
{
threadInfo.buffer ~= ",\n";
}
threadInfo.firstEventForThisThread = false;
char[256] buf;
size_t tid = getCurrentThreadId();
snprintf(buf.ptr, 256, `{ "ph": "%s", "pid": 0, "tid": %zu, "ts": %lld }`,
typeZ.ptr, tid, us);
threadInfo.buffer.appendZeroTerminatedString(buf.ptr);
}
// All thread-local requirements for the profiling to be thread-local.
static struct ThreadContext
{
bool threadWasSeenAlready = false;
Vec!long timeStack; // stack of "begin" time values
String buffer; // thread-local buffer
const(char)[] lastCategoryZ;
bool firstEventForThisThread = true;
}
void ensureThreadContext()
{
// have we seen this thread yet? If not, initialize thread locals
if (!threadInfo.threadWasSeenAlready)
{
threadInfo.threadWasSeenAlready = true;
threadInfo.lastCategoryZ = "none";
threadInfo.buffer.makeEmpty;
threadInfo.firstEventForThisThread = true;
// register buffer
_mutex.lock();
_allBuffers.pushBack(&threadInfo.buffer);
_mutex.unlock();
}
}
}
private:
struct Clock
{
nothrow @nogc:
void initialize()
{
version(Windows)
{
QueryPerformanceFrequency(&_qpcFrequency);
}
}
/// Get us timestamp.
/// Must be thread-safe.
// It doesn't handle wrap-around superbly.
long getTickUs() nothrow @nogc
{
version(Windows)
{
LARGE_INTEGER lint;
QueryPerformanceCounter(&lint);
double seconds = lint.QuadPart / cast(double)(_qpcFrequency.QuadPart);
long us = cast(long)(seconds * 1_000_000);
return us;
}
else
{
import core.time;
return convClockFreq(MonoTime.currTime.ticks, MonoTime.ticksPerSecond, 1_000_000);
}
}
private:
version(Windows)
{
LARGE_INTEGER _qpcFrequency;
}
}
version(Dplug_profileUI)
{
pragma(msg, "You probably meant Dplug_ProfileUI, not Dplug_profileUI. Correct your dub.json");
}