-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProfiler.h
458 lines (386 loc) · 15.2 KB
/
Profiler.h
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/// \brief A simple profiler that generates json that can be loaded into chrome://tracing
/// It is implemented lock free and allocation free (during profiling) with no platform specific code.
///
/// See: http://www.gamasutra.com/view/news/176420/Indepth_Using_Chrometracing_to_view_your_inline_profiling_data.php
/// https://aras-p.info/blog/2017/01/23/Chrome-Tracing-as-Profiler-Frontend/
/// https://github.com/mainroach/sandbox/tree/master/chrome-event-trace
/// https://github.com/catapult-project/catapult
/// https://src.chromium.org/viewvc/chrome/trunk/src/base/debug/trace_event.h?view=markup
/// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit
///
/// To enable, define TAREN_PROFILE_ENABLE in the project builds that need profiling, then in one .cpp file
/// define TAREN_PROFILER_IMPLEMENTATION before including this file.
/// // i.e. it should look like this:
/// #define TAREN_PROFILER_IMPLEMENTATION
/// #include "Profiler.h"
///
/// Usage:
/// PROFILE_BEGIN(); // Enables profiling
/// PROFILE_TAG_BEGIN("TagName"); // Starts a tag
/// PROFILE_TAG_END(); // Ends a tag
///
/// PROFILE_SCOPE("TagName); // Begin / End tag scope
///
/// PROFILE_TAG_VALUE("TagName", 123); // Add an instant tag with a value
/// PROFILE_END(string) or PROFILE_ENDFILEJSON("filename") // Writes tags to a string or a file
///
/// Default tags must be a string literal or it will fail to compile. If you need a dynamic string,
/// there is a limited scratch buffer that is used with the COPY / FORMAT / PRINTF variants of the tag types.
/// eg. PROFILE_TAG_PRINTF_BEGIN("Value %d", 1234);
/// PROFILE_TAG_FORMAT_BEGIN("Value {}", 1234);
/// PROFILE_TAG_COPY_BEGIN(dynamicString.c_str());
///
/// Thread safety:
/// The tag calls are thread safe, but the PROFILE_BEGIN() / PROFILE_END() are not. If you need to call these concurrently, protect with a mutex.
///
/// Resource limits:
/// The profiler has some hard coded limits that can be overridden by specifying some project #defines:
/// TAREN_PROFILER_TAG_MAX_COUNT - How many tags to support in a capture
/// TAREN_PROFILER_TAG_NAME_BUFFER_SIZE - Size of the buffer that caches dynamic tag names
/// TAREN_PROFILER_FORMAT_COUNT - Max size of a dynamic tag
#pragma once
// TODO: Test thread safety in End() code
#ifdef TAREN_PROFILE_ENABLE
#ifndef TAREN_PROFILER_FORMAT_COUNT
#define TAREN_PROFILER_FORMAT_COUNT 30
#endif //!TAREN_PROFILER_TAG_COUNT
#define PROFILE_FORMAT_INTERNAL(...) char buf[TAREN_PROFILER_FORMAT_COUNT]; const auto out = std::format_to_n(buf, TAREN_PROFILER_FORMAT_COUNT - 1, __VA_ARGS__); *out.out = '\0'
#define PROFILE_PRINTF_INTERNAL(...) char buf[TAREN_PROFILER_FORMAT_COUNT]; std::snprintf(buf, TAREN_PROFILER_FORMAT_COUNT, __VA_ARGS__)
#define PROFILE_BEGIN(...) taren_profiler::Begin(__VA_ARGS__)
#define PROFILE_END(...) taren_profiler::End(__VA_ARGS__)
#define PROFILE_ENDFILEJSON(...) taren_profiler::EndFileJson(__VA_ARGS__)
#define PROFILE_TAG_BEGIN(str) static_assert(str[0] != 0, "Only literal strings - Use PROFILE_TAGCOPY_BEGIN"); taren_profiler::ProfileTag(taren_profiler::TagType::Begin, str)
#define PROFILE_TAG_COPY_BEGIN(str) taren_profiler::ProfileTag(taren_profiler::TagType::Begin, str, true)
#define PROFILE_TAG_FORMAT_BEGIN(...) if(taren_profiler::IsProfiling()) { PROFILE_FORMAT_INTERNAL(__VA_ARGS__); PROFILE_TAG_COPY_BEGIN(buf); }
#define PROFILE_TAG_PRINTF_BEGIN(...) if(taren_profiler::IsProfiling()) { PROFILE_PRINTF_INTERNAL(__VA_ARGS__); PROFILE_TAG_COPY_BEGIN(buf); }
#define PROFILE_TAG_END() taren_profiler::ProfileTag(taren_profiler::TagType::End, nullptr)
#define PROFILE_SCOPE_INTERNAL2(X,Y) X ## Y
#define PROFILE_SCOPE_INTERNAL(a,b) PROFILE_SCOPE_INTERNAL2(a,b)
#define PROFILE_SCOPE(str) PROFILE_TAG_BEGIN(str); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__)
#define PROFILE_SCOPE_COPY(str) PROFILE_TAG_COPY_BEGIN(str); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__)
#define PROFILE_SCOPE_FORMAT(...) PROFILE_TAG_FORMAT_BEGIN(__VA_ARGS__); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__)
#define PROFILE_SCOPE_PRINTF(...) PROFILE_TAG_PRINTF_BEGIN(__VA_ARGS__); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__)
#define PROFILE_TAG_VALUE(str, value) static_assert(str[0] != 0, "Only literal strings - Use PROFILE_TAG_VALUE_COPY"); taren_profiler::ProfileTag(taren_profiler::TagType::Value, str, false, value)
#define PROFILE_TAG_VALUE_COPY(str, value) taren_profiler::ProfileTag(taren_profiler::TagType::Value, str, true, value)
#define PROFILE_TAG_VALUE_FORMAT(value, ...) if(taren_profiler::IsProfiling()) { PROFILE_FORMAT_INTERNAL(__VA_ARGS__); PROFILE_TAG_VALUE_COPY(buf, value); }
#define PROFILE_TAG_VALUE_PRINTF(value, ...) if(taren_profiler::IsProfiling()) { PROFILE_PRINTF_INTERNAL(__VA_ARGS__); PROFILE_TAG_VALUE_COPY(buf, value); }
#else // !TAREN_PROFILE_ENABLE
#define PROFILE_BEGIN(...)
#define PROFILE_END(...)
#define PROFILE_ENDFILEJSON(...)
#define PROFILE_TAG_BEGIN(...)
#define PROFILE_TAG_COPY_BEGIN(...)
#define PROFILE_TAG_FORMAT_BEGIN(...)
#define PROFILE_TAG_PRINTF_BEGIN(...)
#define PROFILE_TAG_END()
#define PROFILE_SCOPE(...)
#define PROFILE_SCOPE_COPY(...)
#define PROFILE_SCOPE_FORMAT(...)
#define PROFILE_SCOPE_PRINTF(...)
#define PROFILE_TAG_VALUE(...)
#define PROFILE_TAG_VALUE_COPY(...)
#define PROFILE_TAG_VALUE_FORMAT(...)
#define PROFILE_TAG_VALUE_PRINTF(...)
#endif // !TAREN_PROFILE_ENABLE
#ifdef TAREN_PROFILE_ENABLE
#include <string>
#include <ostream>
#if (__cplusplus >= 202002L)
#include <format>
#endif
namespace taren_profiler
{
enum class TagType
{
Begin,
End,
Value,
};
/// \brief Get if the profiler is currently running
/// \return Returns true if profiling is current running
bool IsProfiling();
/// \brief Start profiling recording
/// \return Returns true if profiling was started
bool Begin();
/// \brief Ends the profiling
/// \param o_outStream The stream to write the json to
/// \param o_outString The string to write the json to
/// \return Returns true on success
bool End(std::ostream& o_outStream);
bool End(std::string& o_outString);
/// \brief Ends the profiling and writes the json results to a file
/// \param i_fileName The file name to write to.
/// \param i_appendDateExtension If true, the current date/time and the extension .json is appended to the filename before opening.
/// \return Returns true on success
bool EndFileJson(const char* i_fileName, bool i_appendDateExtension = true);
/// \brief Set a profiling tag
/// \param i_type The type of tag
/// \param i_str The tag name, must be a literal string or i_copyTag set to true
/// \param i_copyStr If the i_str value should be copied internally
/// \param i_value The value to supply with the tag
void ProfileTag(TagType i_type, const char* i_str, bool i_copyStr = false, int32_t i_value = 0);
struct ProfileScope
{
~ProfileScope() { ProfileTag(TagType::End, nullptr); }
};
}
#ifdef TAREN_PROFILER_IMPLEMENTATION
#ifndef TAREN_PROFILER_TAG_MAX_COUNT
#define TAREN_PROFILER_TAG_MAX_COUNT 10000000
#endif //!TAREN_PROFILER_TAG_COUNT
#ifndef TAREN_PROFILER_TAG_NAME_BUFFER_SIZE
#define TAREN_PROFILER_TAG_NAME_BUFFER_SIZE 1000000
#endif //!TAREN_PROFILER_TAG_NAME_BUFFER_SIZE
#include <chrono>
#include <thread>
#include <atomic>
#include <ctime>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <fstream>
namespace
{
using clock = std::chrono::high_resolution_clock;
struct ProfileRecord
{
clock::time_point m_time; // The time of the profile data
const char* m_tag = nullptr; // The tag used in profiling - if empty is an end event
std::thread::id m_threadID; // The id of the thread
taren_profiler::TagType m_type; // The tag type
int32_t m_value = 0; // Misc value used with the tag
};
std::atomic_bool g_enabled = false; // If profiling is enabled
clock::time_point g_startTime; // The start time of the profile
std::atomic_uint32_t g_slotCount = 0; // The current slot counter
std::atomic_uint32_t g_recordCount = 0; // The current record count
ProfileRecord g_records[TAREN_PROFILER_TAG_MAX_COUNT]; // The profiling records
std::atomic_uint32_t g_copyBufferSize = 0; // The current copy buffer usage count
char g_copyBuffer[TAREN_PROFILER_TAG_NAME_BUFFER_SIZE]; // The buffer to store copied tag names
const char* CopyStr(const char* i_str)
{
if (i_str == nullptr)
{
return nullptr;
}
// Allocate space to copy into
uint32_t len = (uint32_t)strlen(i_str) + 1;
uint32_t startOffset = g_copyBufferSize.fetch_add(len);
if ((startOffset + len) <= TAREN_PROFILER_TAG_NAME_BUFFER_SIZE)
{
char* outBuffer = &g_copyBuffer[startOffset];
memcpy(outBuffer, i_str, len);
return outBuffer;
}
else
{
g_copyBufferSize -= len; // Undo the add to make room for a smaller tag
}
return "OutOfTagBufferSpace";
}
}
namespace taren_profiler
{
void ProfileTag(TagType i_type, const char* i_str, bool i_copyStr, int32_t i_value)
{
if (!g_enabled)
{
return;
}
// Get the slot to put the record
uint32_t recordIndex = g_slotCount.fetch_add(1);
if (recordIndex < TAREN_PROFILER_TAG_MAX_COUNT)
{
ProfileRecord& newData = g_records[recordIndex];
newData.m_type = i_type;
newData.m_threadID = std::this_thread::get_id();
newData.m_tag = i_copyStr ? CopyStr(i_str) : i_str;
newData.m_value = i_value;
newData.m_time = clock::now(); // Assign the time as the last possible thing
g_recordCount++; // Flag that the record is complete
}
else
{
g_slotCount--; // Only hit if exceeded the record count or end of profiling, reverse the add
}
}
bool IsProfiling()
{
return g_enabled;
}
bool Begin()
{
if (g_enabled)
{
return false;
}
// Clear all data (may have been some extra in buffers from previous enable)
g_recordCount = 0;
g_slotCount = 0;
g_copyBufferSize = 0;
g_startTime = clock::now();
g_enabled = true;
return true;
}
static void CleanJsonStr(std::string& io_str)
{
size_t startPos = 0;
while ((startPos = io_str.find_first_of("\\\"", startPos)) != std::string::npos)
{
io_str.insert(startPos, 1, '\\');
startPos += 2;
}
}
bool End(std::ostream& o_outStream)
{
if (!g_enabled)
{
return false;
}
g_enabled = false;
struct Tags
{
int32_t m_index = -1; // The index of the thread
std::vector<const char*> m_tags; // The tag stack
};
// Init this thread as the primary thread
std::unordered_map<std::thread::id, Tags> threadStack;
threadStack[std::this_thread::get_id()].m_index = 0;
int32_t threadCounter = 1;
std::string cleanTag;
o_outStream << "{\"traceEvents\":[\n";
// Flag that records should no longer be written by setting the slot count to TAREN_PROFILER_TAG_COUNT
uint32_t slotCount = g_slotCount;
do
{
// Check if already at the limit (can exceed the limit for a short duration)
if (slotCount >= TAREN_PROFILER_TAG_MAX_COUNT)
{
slotCount = TAREN_PROFILER_TAG_MAX_COUNT;
break;
}
} while (!g_slotCount.compare_exchange_weak(slotCount, (uint32_t)TAREN_PROFILER_TAG_MAX_COUNT));
// Wait for all threads to finish writing tags
uint32_t recordCount = g_recordCount;
while (recordCount != slotCount)
{
std::this_thread::yield();
recordCount = g_recordCount;
}
for (size_t i = 0; i < recordCount; i++)
{
const ProfileRecord& entry = g_records[i];
// Assign a unique index to each thread
Tags& stack = threadStack[entry.m_threadID];
if (stack.m_index < 0)
{
stack.m_index = threadCounter;
threadCounter++;
}
// Get the name tags
const char* tag = entry.m_tag;
if (tag == nullptr)
{
tag = "Unknown";
}
const char* typeTag = "B";
if (entry.m_type == TagType::Begin)
{
stack.m_tags.push_back(tag);
}
else if (entry.m_type == TagType::End)
{
typeTag = "E";
if (stack.m_tags.size() > 0)
{
tag = stack.m_tags.back();
stack.m_tags.pop_back();
}
}
else if (entry.m_type == TagType::Value)
{
typeTag = "O";
}
// Markup invalid json characters
if (strchr(tag, '"') != nullptr ||
strchr(tag, '\\') != nullptr)
{
cleanTag = tag;
CleanJsonStr(cleanTag);
tag = cleanTag.c_str();
}
// Get the microsecond count
long long msCount = std::chrono::duration_cast<std::chrono::microseconds>(entry.m_time - g_startTime).count();
if (i != 0)
{
o_outStream << ",\n";
}
// Format the string (Note using process ID for threads as that gives a better formatting in the output tool for value tags)
o_outStream <<
"{\"name\":\"" << tag << "\",\"ph\":\"" << typeTag << "\",\"ts\":" << msCount << ",\"pid\":" << stack.m_index << ",\"cat\":\"\",\"tid\":0,";
if (entry.m_type == TagType::Value)
{
o_outStream << "\"id\":\"" << tag << "\", \"args\":{\"snapshot\":{\"Value\": " << entry.m_value << "}}}";
}
else
{
o_outStream << "\"args\":{}}";
}
}
// Write thread "names"
if (recordCount > 0)
{
for (auto& t : threadStack)
{
// Sort thread listing by the time that they appear in the profile (tool sorts by name)
char indexSpaceString[64];
std::snprintf(indexSpaceString, sizeof(indexSpaceString), "%02d", t.second.m_index);
// Ensure a clean json string (undefined what thread::id is)
std::ostringstream ss;
ss << t.first;
std::string threadName = ss.str();
CleanJsonStr(threadName);
o_outStream << // (Note using process ID for threads as that gives a better formatting in the output tool for value tags)
",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"tid\":0,\"pid\":" << t.second.m_index <<
",\"args\":{\"name\":\"Thread" << indexSpaceString << "_" << threadName << "\"}}";
}
}
o_outStream << "\n]\n}\n";
return true;
}
bool End(std::string& o_outString)
{
std::ostringstream ss;
bool retval = End(ss);
o_outString = ss.str();
return retval;
}
bool EndFileJson(const char* i_fileName, bool i_appendDateExtension)
{
std::ofstream file;
if (i_appendDateExtension)
{
// Create a filename with the current date in it with extension
std::time_t t = std::time(nullptr);
tm timeBuf;
localtime_s(&timeBuf, &t);
char extStr[120];
if (std::strftime(extStr, sizeof(extStr), "_%Y%m%d-%H%M%S.json", &timeBuf) == 0)
{
return false;
}
file.open(std::string(i_fileName) + extStr);
}
else
{
file.open(i_fileName);
}
if (!file.is_open())
{
return false;
}
return End(file);
}
}
#endif // TAREN_PROFILER_IMPLEMENTATION
#endif // TAREN_PROFILE_ENABLE