Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Profiler2.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2011 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "precompiled.h"
24 
25 #include "Profiler2.h"
26 
28 #include "ps/CLogger.h"
29 #include "ps/CStr.h"
30 #include "ps/Profiler2GPU.h"
32 
33 #include <iomanip>
34 
36 
37 // A human-recognisable pattern (for debugging) followed by random bytes (for uniqueness)
38 const u8 CProfiler2::RESYNC_MAGIC[8] = {0x11, 0x22, 0x33, 0x44, 0xf4, 0x93, 0xbe, 0x15};
39 
41  m_Initialised(false), m_FrameNumber(0), m_MgContext(NULL), m_GPU(NULL)
42 {
43 }
44 
46 {
47  if (m_Initialised)
48  Shutdown();
49 }
50 
51 /**
52  * Mongoose callback. Run in an arbitrary thread (possibly concurrently with other requests).
53  */
54 static void* MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
55 {
56  CProfiler2* profiler = (CProfiler2*)request_info->user_data;
57  ENSURE(profiler);
58 
59  void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling
60 
61  const char* header200 =
62  "HTTP/1.1 200 OK\r\n"
63  "Access-Control-Allow-Origin: *\r\n" // TODO: not great for security
64  "Content-Type: text/plain; charset=utf-8\r\n\r\n";
65 
66  const char* header404 =
67  "HTTP/1.1 404 Not Found\r\n"
68  "Content-Type: text/plain; charset=utf-8\r\n\r\n"
69  "Unrecognised URI";
70 
71  const char* header400 =
72  "HTTP/1.1 400 Bad Request\r\n"
73  "Content-Type: text/plain; charset=utf-8\r\n\r\n"
74  "Invalid request";
75 
76  switch (event)
77  {
78  case MG_NEW_REQUEST:
79  {
80  std::stringstream stream;
81 
82  std::string uri = request_info->uri;
83  if (uri == "/overview")
84  {
85  profiler->ConstructJSONOverview(stream);
86  }
87  else if (uri == "/query")
88  {
89  if (!request_info->query_string)
90  {
91  mg_printf(conn, "%s (no query string)", header400);
92  return handled;
93  }
94 
95  // Identify the requested thread
96  char buf[256];
97  int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), "thread", buf, ARRAY_SIZE(buf));
98  if (len < 0)
99  {
100  mg_printf(conn, "%s (no 'thread')", header400);
101  return handled;
102  }
103  std::string thread(buf);
104 
105  const char* err = profiler->ConstructJSONResponse(stream, thread);
106  if (err)
107  {
108  mg_printf(conn, "%s (%s)", header400, err);
109  return handled;
110  }
111  }
112  else
113  {
114  mg_printf(conn, "%s", header404);
115  return handled;
116  }
117 
118  mg_printf(conn, "%s", header200);
119  std::string str = stream.str();
120  mg_write(conn, str.c_str(), str.length());
121  return handled;
122  }
123 
124  case MG_HTTP_ERROR:
125  return NULL;
126 
127  case MG_EVENT_LOG:
128  // Called by Mongoose's cry()
129  LOGERROR(L"Mongoose error: %hs", request_info->log_message);
130  return NULL;
131 
132  case MG_INIT_SSL:
133  return NULL;
134 
135  default:
136  debug_warn(L"Invalid Mongoose event type");
137  return NULL;
138  }
139 };
140 
142 {
145  ENSURE(err == 0);
146  m_Initialised = true;
147 
148  RegisterCurrentThread("main");
149 }
150 
152 {
153  ENSURE(!m_GPU);
154  m_GPU = new CProfiler2GPU(*this);
155 }
156 
158 {
160 
161  // Ignore multiple enablings
162  if (m_MgContext)
163  return;
164 
165  const char *options[] = {
166  "listening_ports", "127.0.0.1:8000", // bind to localhost for security
167  "num_threads", "6", // enough for the browser's parallel connection limit
168  NULL
169  };
170  m_MgContext = mg_start(MgCallback, this, options);
172 }
173 
175 {
177  if (!m_GPU)
178  InitialiseGPU();
179 }
180 
182 {
184 }
185 
187 {
189 
190  ENSURE(!m_GPU); // must shutdown GPU before profiler
191 
192  if (m_MgContext)
193  {
195  m_MgContext = NULL;
196  }
197 
198  // TODO: free non-NULL keys, instead of leaking them
199 
200  int err = pthread_key_delete(m_TLS);
201  ENSURE(err == 0);
202  m_Initialised = false;
203 }
204 
206 {
207  if (m_GPU)
208  m_GPU->FrameStart();
209 }
210 
212 {
213  if (m_GPU)
214  m_GPU->FrameEnd();
215 }
216 
218 {
219  if (m_GPU)
220  m_GPU->RegionEnter(id);
221 }
222 
224 {
225  if (m_GPU)
226  m_GPU->RegionLeave(id);
227 }
228 
229 /**
230  * Called by pthreads when a registered thread is destroyed.
231  */
232 void CProfiler2::TLSDtor(void* data)
233 {
234  ThreadStorage* storage = (ThreadStorage*)data;
235 
236  storage->GetProfiler().RemoveThreadStorage(storage);
237 
238  delete (ThreadStorage*)data;
239 }
240 
241 void CProfiler2::RegisterCurrentThread(const std::string& name)
242 {
244 
245  ENSURE(pthread_getspecific(m_TLS) == NULL); // mustn't register a thread more than once
246 
247  ThreadStorage* storage = new ThreadStorage(*this, name);
248  int err = pthread_setspecific(m_TLS, storage);
249  ENSURE(err == 0);
250 
252  RecordEvent("thread start");
253 
254  AddThreadStorage(storage);
255 }
256 
258 {
259  CScopeLock lock(m_Mutex);
260  m_Threads.push_back(storage);
261 }
262 
264 {
265  CScopeLock lock(m_Mutex);
266  m_Threads.erase(std::find(m_Threads.begin(), m_Threads.end(), storage));
267 }
268 
269 CProfiler2::ThreadStorage::ThreadStorage(CProfiler2& profiler, const std::string& name) :
270  m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time())
271 {
272  m_Buffer = new u8[BUFFER_SIZE];
273  memset(m_Buffer, ITEM_NOP, BUFFER_SIZE);
274 }
275 
277 {
278  delete[] m_Buffer;
279 }
280 
282 {
283  // Called from an arbitrary thread (not the one writing to the buffer).
284  //
285  // See comments on m_BufferPos0 etc.
286 
287  shared_ptr<u8> buffer(new u8[BUFFER_SIZE], ArrayDeleter());
288 
289  u32 pos1 = m_BufferPos1;
290  COMPILER_FENCE; // must read m_BufferPos1 before m_Buffer
291 
292  memcpy(buffer.get(), m_Buffer, BUFFER_SIZE);
293 
294  COMPILER_FENCE; // must read m_BufferPos0 after m_Buffer
295  u32 pos0 = m_BufferPos0;
296 
297  // The range [pos1, pos0) modulo BUFFER_SIZE is invalid, so concatenate the rest of the buffer
298 
299  if (pos1 <= pos0) // invalid range is in the middle of the buffer
300  return std::string(buffer.get()+pos0, buffer.get()+BUFFER_SIZE) + std::string(buffer.get(), buffer.get()+pos1);
301  else // invalid wrap is wrapped around the end/start buffer
302  return std::string(buffer.get()+pos0, buffer.get()+pos1);
303 }
304 
305 void CProfiler2::ThreadStorage::RecordAttribute(const char* fmt, va_list argp)
306 {
307  char buffer[MAX_ATTRIBUTE_LENGTH + 4] = {0}; // first 4 bytes are used for storing length
308  int len = vsnprintf(buffer + 4, MAX_ATTRIBUTE_LENGTH - 1, fmt, argp); // subtract 1 from length to make MSVC vsnprintf safe
309  // (Don't use vsprintf_s because it treats overflow as fatal)
310 
311  // Terminate the string if the printing was truncated
312  if (len < 0 || len >= (int)MAX_ATTRIBUTE_LENGTH - 1)
313  {
314  strncpy(buffer + 4 + MAX_ATTRIBUTE_LENGTH - 4, "...", 4);
315  len = MAX_ATTRIBUTE_LENGTH - 1; // excluding null terminator
316  }
317 
318  // Store the length in the buffer
319  memcpy(buffer, &len, sizeof(len));
320 
321  Write(ITEM_ATTRIBUTE, buffer, 4 + len);
322 }
323 
324 
325 void CProfiler2::ConstructJSONOverview(std::ostream& stream)
326 {
327  TIMER(L"profile2 overview");
328 
329  CScopeLock lock(m_Mutex);
330 
331  stream << "{\"threads\":[";
332  for (size_t i = 0; i < m_Threads.size(); ++i)
333  {
334  if (i != 0)
335  stream << ",";
336  stream << "{\"name\":\"" << CStr(m_Threads[i]->GetName()).EscapeToPrintableASCII() << "\"}";
337  }
338  stream << "]}";
339 }
340 
341 /**
342  * Given a buffer and a visitor class (with functions OnEvent, OnEnter, OnLeave, OnAttribute),
343  * calls the visitor for every item in the buffer.
344  */
345 template<typename V>
346 void RunBufferVisitor(const std::string& buffer, V& visitor)
347 {
348  TIMER(L"profile2 visitor");
349 
350  // The buffer doesn't necessarily start at the beginning of an item
351  // (we just grabbed it from some arbitrary point in the middle),
352  // so scan forwards until we find a sync marker.
353  // (This is probably pretty inefficient.)
354 
355  u32 realStart = (u32)-1; // the start point decided by the scan algorithm
356 
357  for (u32 start = 0; start + 1 + sizeof(CProfiler2::RESYNC_MAGIC) <= buffer.length(); ++start)
358  {
359  if (buffer[start] == CProfiler2::ITEM_SYNC
360  && memcmp(buffer.c_str() + start + 1, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0)
361  {
362  realStart = start;
363  break;
364  }
365  }
366 
367  ENSURE(realStart != (u32)-1); // we should have found a sync point somewhere in the buffer
368 
369  u32 pos = realStart; // the position as we step through the buffer
370 
371  double lastTime = -1;
372  // set to non-negative by EVENT_SYNC; we ignore all items before that
373  // since we can't compute their absolute times
374 
375  while (pos < buffer.length())
376  {
377  u8 type = buffer[pos];
378  ++pos;
379 
380  switch (type)
381  {
383  {
384  // ignore
385  break;
386  }
388  {
389  u8 magic[sizeof(CProfiler2::RESYNC_MAGIC)];
390  double t;
391  memcpy(magic, buffer.c_str()+pos, ARRAY_SIZE(magic));
392  ENSURE(memcmp(magic, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0);
393  pos += sizeof(CProfiler2::RESYNC_MAGIC);
394  memcpy(&t, buffer.c_str()+pos, sizeof(t));
395  pos += sizeof(t);
396  lastTime = t;
397  visitor.OnSync(lastTime);
398  break;
399  }
401  {
403  memcpy(&item, buffer.c_str()+pos, sizeof(item));
404  pos += sizeof(item);
405  if (lastTime >= 0)
406  {
407  lastTime = lastTime + (double)item.dt;
408  visitor.OnEvent(lastTime, item.id);
409  }
410  break;
411  }
413  {
415  memcpy(&item, buffer.c_str()+pos, sizeof(item));
416  pos += sizeof(item);
417  if (lastTime >= 0)
418  {
419  lastTime = lastTime + (double)item.dt;
420  visitor.OnEnter(lastTime, item.id);
421  }
422  break;
423  }
425  {
427  memcpy(&item, buffer.c_str()+pos, sizeof(item));
428  pos += sizeof(item);
429  if (lastTime >= 0)
430  {
431  lastTime = lastTime + (double)item.dt;
432  visitor.OnLeave(lastTime, item.id);
433  }
434  break;
435  }
437  {
438  u32 len;
439  memcpy(&len, buffer.c_str()+pos, sizeof(len));
441  pos += sizeof(len);
442  std::string attribute(buffer.c_str()+pos, buffer.c_str()+pos+len);
443  pos += len;
444  if (lastTime >= 0)
445  {
446  visitor.OnAttribute(attribute);
447  }
448  break;
449  }
450  default:
451  debug_warn(L"Invalid profiler item when parsing buffer");
452  return;
453  }
454  }
455 };
456 
457 /**
458  * Visitor class that dumps events as JSON.
459  * TODO: this is pretty inefficient (in implementation and in output format).
460  */
462 {
464 public:
465  BufferVisitor_Dump(std::ostream& stream) : m_Stream(stream)
466  {
467  }
468 
469  void OnSync(double UNUSED(time))
470  {
471  // Split the array of items into an array of array (arbitrarily splitting
472  // around the sync points) to avoid array-too-large errors in JSON decoders
473  m_Stream << "null], [\n";
474  }
475 
476  void OnEvent(double time, const char* id)
477  {
478  m_Stream << "[1," << std::fixed << std::setprecision(9) << time;
479  m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
480  }
481 
482  void OnEnter(double time, const char* id)
483  {
484  m_Stream << "[2," << std::fixed << std::setprecision(9) << time;
485  m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
486  }
487 
488  void OnLeave(double time, const char* id)
489  {
490  m_Stream << "[3," << std::fixed << std::setprecision(9) << time;
491  m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
492  }
493 
494  void OnAttribute(const std::string& attr)
495  {
496  m_Stream << "[4,\"" << CStr(attr).EscapeToPrintableASCII() << "\"],\n";
497  }
498 
499  std::ostream& m_Stream;
500 };
501 
502 const char* CProfiler2::ConstructJSONResponse(std::ostream& stream, const std::string& thread)
503 {
504  TIMER(L"profile2 query");
505 
506  std::string buffer;
507 
508  {
509  TIMER(L"profile2 get buffer");
510 
511  CScopeLock lock(m_Mutex); // lock against changes to m_Threads or deletions of ThreadStorage
512 
513  ThreadStorage* storage = NULL;
514  for (size_t i = 0; i < m_Threads.size(); ++i)
515  {
516  if (m_Threads[i]->GetName() == thread)
517  {
518  storage = m_Threads[i];
519  break;
520  }
521  }
522 
523  if (!storage)
524  return "cannot find named thread";
525 
526  stream << "{\"events\":[\n";
527 
528  stream << "[\n";
529  buffer = storage->GetBuffer();
530  }
531 
532  BufferVisitor_Dump visitor(stream);
533  RunBufferVisitor(buffer, visitor);
534 
535  stream << "null]\n]}";
536 
537  return NULL;
538 }
539 
541 {
542  OsPath path = psLogDir()/"profile2.jsonp";
543  std::ofstream stream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
544  ENSURE(stream.good());
545 
546  std::vector<ThreadStorage*> threads;
547 
548  {
549  CScopeLock lock(m_Mutex);
550  threads = m_Threads;
551  }
552 
553  stream << "profileDataCB({\"threads\": [\n";
554  for (size_t i = 0; i < threads.size(); ++i)
555  {
556  if (i != 0)
557  stream << ",\n";
558  stream << "{\"name\":\"" << CStr(threads[i]->GetName()).EscapeToPrintableASCII() << "\",\n";
559  stream << "\"data\": ";
560  ConstructJSONResponse(stream, threads[i]->GetName());
561  stream << "\n}";
562  }
563  stream << "\n]});\n";
564 }
#define NONCOPYABLE(className)
#define u8
Definition: types.h:39
bool m_Initialised
Definition: Profiler2.h:387
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
#define COMPILER_FENCE
prevent the compiler from reordering loads or stores across this point.
CFixed_15_16 fixed
Default fixed-point type used by the engine.
Definition: Fixed.h:339
const OsPath & psLogDir()
Definition: Pyrogenesis.cpp:96
#define LOGERROR
Definition: CLogger.h:35
void AddThreadStorage(ThreadStorage *storage)
Definition: Profiler2.cpp:257
ThreadStorage(CProfiler2 &profiler, const std::string &name)
Definition: Profiler2.cpp:269
char * uri
Definition: mongoose.h:38
void OnLeave(double time, const char *id)
Definition: Profiler2.cpp:488
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
Locks a CMutex over this object&#39;s lifetime.
Definition: ThreadUtil.h:73
CMutex m_Mutex
Definition: Profiler2.h:397
void RunBufferVisitor(const std::string &buffer, V &visitor)
Given a buffer and a visitor class (with functions OnEvent, OnEnter, OnLeave, OnAttribute), calls the visitor for every item in the buffer.
Definition: Profiler2.cpp:346
char * log_message
Definition: mongoose.h:42
void EnableHTTP()
Call in main thread to enable the HTTP server.
Definition: Profiler2.cpp:157
int pthread_key_create(pthread_key_t *key, void(*dtor)(void *))
Definition: wpthread.cpp:146
#define ARRAY_SIZE(name)
void RecordGPURegionEnter(const char *id)
Definition: Profiler2.cpp:217
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
CProfiler2 g_Profiler2
Definition: Profiler2.cpp:35
BufferVisitor_Dump(std::ostream &stream)
Definition: Profiler2.cpp:465
Definition: path.h:75
std::string GetBuffer()
Returns a copy of a subset of the thread&#39;s buffer.
Definition: Profiler2.cpp:281
static const u8 RESYNC_MAGIC[8]
An arbitrary number to help resyncing with the item stream when parsing.
Definition: Profiler2.h:112
void OnEnter(double time, const char *id)
Definition: Profiler2.cpp:482
void OnAttribute(const std::string &attr)
Definition: Profiler2.cpp:494
New profiler (complementing the older CProfileManager)
void RegisterCurrentThread(const std::string &name)
Call in any thread to enable the profiler in that thread.
Definition: Profiler2.cpp:241
void SaveToFile()
Call in any thread to save a JSONP representation of the buffers for all threads, to a file named pro...
Definition: Profiler2.cpp:540
int mg_get_var(const char *buf, size_t buf_len, const char *name, char *dst, size_t dst_len)
Definition: mongoose.cpp:1497
void RecordAttribute(const char *fmt, va_list argp) VPRINTF_ARGS(2)
Definition: Profiler2.cpp:305
#define SAFE_DELETE(p)
delete memory ensuing from new and set the pointer to zero (thus making double-frees safe / a no-op) ...
mg_event
Definition: mongoose.h:55
void ShutdownGPU()
Call in main thread to shut down the GPU profiling support, before shutting down OpenGL.
Definition: Profiler2.cpp:181
static const size_t BUFFER_SIZE
Definition: Profiler2.h:126
int pthread_key_delete(pthread_key_t key)
Definition: wpthread.cpp:173
void RecordGPURegionLeave(const char *id)
Definition: Profiler2.cpp:223
void Initialise()
Call in main thread to set up the profiler, before calling any other profiler functions.
Definition: Profiler2.cpp:141
char * query_string
Definition: mongoose.h:40
CProfiler2 & GetProfiler()
Definition: Profiler2.h:175
Visitor class that dumps events as JSON.
Definition: Profiler2.cpp:461
double timer_Time()
Definition: timer.cpp:98
std::ostream & m_Stream
Definition: Profiler2.cpp:499
void * pthread_getspecific(pthread_key_t key)
Definition: wpthread.cpp:184
#define TIMER(description)
Measures the time taken to execute code up until end of the current scope; displays it via debug_prin...
Definition: timer.h:108
int mg_printf(struct mg_connection *conn, const char *fmt,...)
Definition: mongoose.cpp:1450
static void * MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
Mongoose callback.
Definition: Profiler2.cpp:54
void OnSync(double time)
Definition: Profiler2.cpp:469
void Shutdown()
Call in main thread to shut everything down.
Definition: Profiler2.cpp:186
#define u32
Definition: types.h:41
mg_context * m_MgContext
Definition: Profiler2.h:391
int mg_write(struct mg_connection *conn, const void *buf, size_t len)
Definition: mongoose.cpp:1445
int pthread_setspecific(pthread_key_t key, const void *value)
Definition: wpthread.cpp:210
Used by CProfiler2 for GPU profiling support.
Definition: Profiler2GPU.h:31
void RecordGPUFrameStart()
Definition: Profiler2.cpp:205
CProfiler2GPU * m_GPU
Definition: Profiler2.h:395
void EnableGPU()
Call in main thread to enable the GPU profiling support, after OpenGL has been initialised.
Definition: Profiler2.cpp:174
pthread_key_t m_TLS
Definition: Profiler2.h:393
void RecordEvent(const char *id)
Definition: Profiler2.h:310
void * user_data
Definition: mongoose.h:36
void RemoveThreadStorage(ThreadStorage *storage)
Definition: Profiler2.cpp:263
void RegionLeave(const char *id)
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
void RegionEnter(const char *id)
static const size_t MAX_ATTRIBUTE_LENGTH
Definition: Profiler2.h:109
struct mg_context * mg_start(mg_callback_t user_callback, void *user_data, const char **options)
Definition: mongoose.cpp:4219
void InitialiseGPU()
Definition: Profiler2.cpp:151
void RecordGPUFrameEnd()
Definition: Profiler2.cpp:211
void OnEvent(double time, const char *id)
Definition: Profiler2.cpp:476
void ConstructJSONOverview(std::ostream &stream)
Call in any thread to produce a JSON representation of the general state of the application.
Definition: Profiler2.cpp:325
void Write(u64 reg, u64 value)
Definition: msr.cpp:136
void RecordSyncMarker()
Non-main threads should call this occasionally, especially if it&#39;s been a long time since their last ...
Definition: Profiler2.h:296
An item with a relative time and an ID string pointer.
Definition: Profiler2.h:117
void mg_stop(struct mg_context *ctx)
Definition: mongoose.cpp:4205
std::vector< ThreadStorage * > m_Threads
Definition: Profiler2.h:398
static void TLSDtor(void *data)
Called by pthreads when a registered thread is destroyed.
Definition: Profiler2.cpp:232
Class instantiated in every registered thread.
Definition: Profiler2.h:131
static std::string OsString(const OsPath &path)
Definition: os_path.h:42
const char * ConstructJSONResponse(std::ostream &stream, const std::string &thread)
Call in any thread to produce a JSON representation of the buffer for a given thread.
Definition: Profiler2.cpp:502