tools/profiler/ProfileEntry.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include <ostream>
michael@0 7 #include <sstream>
michael@0 8 #include "platform.h"
michael@0 9 #include "nsThreadUtils.h"
michael@0 10 #include "nsXULAppAPI.h"
michael@0 11 #include "jsapi.h"
michael@0 12
michael@0 13 // JSON
michael@0 14 #include "JSStreamWriter.h"
michael@0 15
michael@0 16 // Self
michael@0 17 #include "ProfileEntry.h"
michael@0 18
michael@0 19 #if _MSC_VER
michael@0 20 #define snprintf _snprintf
michael@0 21 #endif
michael@0 22
michael@0 23 ////////////////////////////////////////////////////////////////////////
michael@0 24 // BEGIN ProfileEntry
michael@0 25
michael@0 26 ProfileEntry::ProfileEntry()
michael@0 27 : mTagData(nullptr)
michael@0 28 , mTagName(0)
michael@0 29 { }
michael@0 30
michael@0 31 // aTagData must not need release (i.e. be a string from the text segment)
michael@0 32 ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
michael@0 33 : mTagData(aTagData)
michael@0 34 , mTagName(aTagName)
michael@0 35 { }
michael@0 36
michael@0 37 ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
michael@0 38 : mTagMarker(aTagMarker)
michael@0 39 , mTagName(aTagName)
michael@0 40 { }
michael@0 41
michael@0 42 ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
michael@0 43 : mTagPtr(aTagPtr)
michael@0 44 , mTagName(aTagName)
michael@0 45 { }
michael@0 46
michael@0 47 ProfileEntry::ProfileEntry(char aTagName, float aTagFloat)
michael@0 48 : mTagFloat(aTagFloat)
michael@0 49 , mTagName(aTagName)
michael@0 50 { }
michael@0 51
michael@0 52 ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
michael@0 53 : mTagOffset(aTagOffset)
michael@0 54 , mTagName(aTagName)
michael@0 55 { }
michael@0 56
michael@0 57 ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
michael@0 58 : mTagAddress(aTagAddress)
michael@0 59 , mTagName(aTagName)
michael@0 60 { }
michael@0 61
michael@0 62 ProfileEntry::ProfileEntry(char aTagName, int aTagLine)
michael@0 63 : mTagLine(aTagLine)
michael@0 64 , mTagName(aTagName)
michael@0 65 { }
michael@0 66
michael@0 67 ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
michael@0 68 : mTagChar(aTagChar)
michael@0 69 , mTagName(aTagName)
michael@0 70 { }
michael@0 71
michael@0 72 bool ProfileEntry::is_ent_hint(char hintChar) {
michael@0 73 return mTagName == 'h' && mTagChar == hintChar;
michael@0 74 }
michael@0 75
michael@0 76 bool ProfileEntry::is_ent_hint() {
michael@0 77 return mTagName == 'h';
michael@0 78 }
michael@0 79
michael@0 80 bool ProfileEntry::is_ent(char tagChar) {
michael@0 81 return mTagName == tagChar;
michael@0 82 }
michael@0 83
michael@0 84 void* ProfileEntry::get_tagPtr() {
michael@0 85 // No consistency checking. Oh well.
michael@0 86 return mTagPtr;
michael@0 87 }
michael@0 88
michael@0 89 void ProfileEntry::log()
michael@0 90 {
michael@0 91 // There is no compiler enforced mapping between tag chars
michael@0 92 // and union variant fields, so the following was derived
michael@0 93 // by looking through all the use points of TableTicker.cpp.
michael@0 94 // mTagMarker (ProfilerMarker*) m
michael@0 95 // mTagData (const char*) c,s
michael@0 96 // mTagPtr (void*) d,l,L,B (immediate backtrace), S(start-of-stack)
michael@0 97 // mTagLine (int) n,f
michael@0 98 // mTagChar (char) h
michael@0 99 // mTagFloat (double) r,t,p
michael@0 100 switch (mTagName) {
michael@0 101 case 'm':
michael@0 102 LOGF("%c \"%s\"", mTagName, mTagMarker->GetMarkerName()); break;
michael@0 103 case 'c': case 's':
michael@0 104 LOGF("%c \"%s\"", mTagName, mTagData); break;
michael@0 105 case 'd': case 'l': case 'L': case 'B': case 'S':
michael@0 106 LOGF("%c %p", mTagName, mTagPtr); break;
michael@0 107 case 'n': case 'f':
michael@0 108 LOGF("%c %d", mTagName, mTagLine); break;
michael@0 109 case 'h':
michael@0 110 LOGF("%c \'%c\'", mTagName, mTagChar); break;
michael@0 111 case 'r': case 't': case 'p':
michael@0 112 LOGF("%c %f", mTagName, mTagFloat); break;
michael@0 113 default:
michael@0 114 LOGF("'%c' unknown_tag", mTagName); break;
michael@0 115 }
michael@0 116 }
michael@0 117
michael@0 118 std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry)
michael@0 119 {
michael@0 120 if (entry.mTagName == 'r' || entry.mTagName == 't') {
michael@0 121 stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
michael@0 122 } else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
michael@0 123 // Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
michael@0 124 // Additionally, stringstream seemed to be ignoring formatter flags.
michael@0 125 char tagBuff[1024];
michael@0 126 unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
michael@0 127 snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
michael@0 128 stream << tagBuff;
michael@0 129 } else if (entry.mTagName == 'd') {
michael@0 130 // TODO implement 'd' tag for text profile
michael@0 131 } else {
michael@0 132 stream << entry.mTagName << "-" << entry.mTagData << "\n";
michael@0 133 }
michael@0 134 return stream;
michael@0 135 }
michael@0 136
michael@0 137 // END ProfileEntry
michael@0 138 ////////////////////////////////////////////////////////////////////////
michael@0 139
michael@0 140
michael@0 141 ////////////////////////////////////////////////////////////////////////
michael@0 142 // BEGIN ThreadProfile
michael@0 143
michael@0 144 #define DYNAMIC_MAX_STRING 512
michael@0 145
michael@0 146 ThreadProfile::ThreadProfile(const char* aName, int aEntrySize,
michael@0 147 PseudoStack *aStack, Thread::tid_t aThreadId,
michael@0 148 PlatformData* aPlatform,
michael@0 149 bool aIsMainThread, void *aStackTop)
michael@0 150 : mWritePos(0)
michael@0 151 , mLastFlushPos(0)
michael@0 152 , mReadPos(0)
michael@0 153 , mEntrySize(aEntrySize)
michael@0 154 , mPseudoStack(aStack)
michael@0 155 , mMutex("ThreadProfile::mMutex")
michael@0 156 , mName(strdup(aName))
michael@0 157 , mThreadId(aThreadId)
michael@0 158 , mIsMainThread(aIsMainThread)
michael@0 159 , mPlatformData(aPlatform)
michael@0 160 , mGeneration(0)
michael@0 161 , mPendingGenerationFlush(0)
michael@0 162 , mStackTop(aStackTop)
michael@0 163 {
michael@0 164 mEntries = new ProfileEntry[mEntrySize];
michael@0 165 }
michael@0 166
michael@0 167 ThreadProfile::~ThreadProfile()
michael@0 168 {
michael@0 169 free(mName);
michael@0 170 delete[] mEntries;
michael@0 171 }
michael@0 172
michael@0 173 void ThreadProfile::addTag(ProfileEntry aTag)
michael@0 174 {
michael@0 175 // Called from signal, call only reentrant functions
michael@0 176 mEntries[mWritePos] = aTag;
michael@0 177 mWritePos = mWritePos + 1;
michael@0 178 if (mWritePos >= mEntrySize) {
michael@0 179 mPendingGenerationFlush++;
michael@0 180 mWritePos = mWritePos % mEntrySize;
michael@0 181 }
michael@0 182 if (mWritePos == mReadPos) {
michael@0 183 // Keep one slot open
michael@0 184 mEntries[mReadPos] = ProfileEntry();
michael@0 185 mReadPos = (mReadPos + 1) % mEntrySize;
michael@0 186 }
michael@0 187 // we also need to move the flush pos to ensure we
michael@0 188 // do not pass it
michael@0 189 if (mWritePos == mLastFlushPos) {
michael@0 190 mLastFlushPos = (mLastFlushPos + 1) % mEntrySize;
michael@0 191 }
michael@0 192 }
michael@0 193
michael@0 194 // flush the new entries
michael@0 195 void ThreadProfile::flush()
michael@0 196 {
michael@0 197 mLastFlushPos = mWritePos;
michael@0 198 mGeneration += mPendingGenerationFlush;
michael@0 199 mPendingGenerationFlush = 0;
michael@0 200 }
michael@0 201
michael@0 202 // discards all of the entries since the last flush()
michael@0 203 // NOTE: that if mWritePos happens to wrap around past
michael@0 204 // mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries
michael@0 205 //
michael@0 206 // r = mReadPos
michael@0 207 // w = mWritePos
michael@0 208 // f = mLastFlushPos
michael@0 209 //
michael@0 210 // r f w
michael@0 211 // |-----------------------------|
michael@0 212 // | abcdefghijklmnopq | -> 'abcdefghijklmnopq'
michael@0 213 // |-----------------------------|
michael@0 214 //
michael@0 215 //
michael@0 216 // mWritePos and mReadPos have passed mLastFlushPos
michael@0 217 // f
michael@0 218 // w r
michael@0 219 // |-----------------------------|
michael@0 220 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
michael@0 221 // |-----------------------------|
michael@0 222 // w
michael@0 223 // r
michael@0 224 // |-----------------------------|
michael@0 225 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
michael@0 226 // |-----------------------------|
michael@0 227 //
michael@0 228 //
michael@0 229 // mWritePos will end up the same as mReadPos
michael@0 230 // r
michael@0 231 // w f
michael@0 232 // |-----------------------------|
michael@0 233 // |ABCDEFGHIJKLMklmnopqrstuvwxyz|
michael@0 234 // |-----------------------------|
michael@0 235 // r
michael@0 236 // w
michael@0 237 // |-----------------------------|
michael@0 238 // |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> ''
michael@0 239 // |-----------------------------|
michael@0 240 //
michael@0 241 //
michael@0 242 // mWritePos has moved past mReadPos
michael@0 243 // w r f
michael@0 244 // |-----------------------------|
michael@0 245 // |ABCDEFdefghijklmnopqrstuvwxyz|
michael@0 246 // |-----------------------------|
michael@0 247 // r w
michael@0 248 // |-----------------------------|
michael@0 249 // |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
michael@0 250 // |-----------------------------|
michael@0 251
michael@0 252 void ThreadProfile::erase()
michael@0 253 {
michael@0 254 mWritePos = mLastFlushPos;
michael@0 255 mPendingGenerationFlush = 0;
michael@0 256 }
michael@0 257
michael@0 258 char* ThreadProfile::processDynamicTag(int readPos,
michael@0 259 int* tagsConsumed, char* tagBuff)
michael@0 260 {
michael@0 261 int readAheadPos = (readPos + 1) % mEntrySize;
michael@0 262 int tagBuffPos = 0;
michael@0 263
michael@0 264 // Read the string stored in mTagData until the null character is seen
michael@0 265 bool seenNullByte = false;
michael@0 266 while (readAheadPos != mLastFlushPos && !seenNullByte) {
michael@0 267 (*tagsConsumed)++;
michael@0 268 ProfileEntry readAheadEntry = mEntries[readAheadPos];
michael@0 269 for (size_t pos = 0; pos < sizeof(void*); pos++) {
michael@0 270 tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
michael@0 271 if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
michael@0 272 seenNullByte = true;
michael@0 273 break;
michael@0 274 }
michael@0 275 tagBuffPos++;
michael@0 276 }
michael@0 277 if (!seenNullByte)
michael@0 278 readAheadPos = (readAheadPos + 1) % mEntrySize;
michael@0 279 }
michael@0 280 return tagBuff;
michael@0 281 }
michael@0 282
michael@0 283 void ThreadProfile::IterateTags(IterateTagsCallback aCallback)
michael@0 284 {
michael@0 285 MOZ_ASSERT(aCallback);
michael@0 286
michael@0 287 int readPos = mReadPos;
michael@0 288 while (readPos != mLastFlushPos) {
michael@0 289 // Number of tag consumed
michael@0 290 int incBy = 1;
michael@0 291 const ProfileEntry& entry = mEntries[readPos];
michael@0 292
michael@0 293 // Read ahead to the next tag, if it's a 'd' tag process it now
michael@0 294 const char* tagStringData = entry.mTagData;
michael@0 295 int readAheadPos = (readPos + 1) % mEntrySize;
michael@0 296 char tagBuff[DYNAMIC_MAX_STRING];
michael@0 297 // Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
michael@0 298 tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
michael@0 299
michael@0 300 if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
michael@0 301 tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
michael@0 302 }
michael@0 303
michael@0 304 aCallback(entry, tagStringData);
michael@0 305
michael@0 306 readPos = (readPos + incBy) % mEntrySize;
michael@0 307 }
michael@0 308 }
michael@0 309
michael@0 310 void ThreadProfile::ToStreamAsJSON(std::ostream& stream)
michael@0 311 {
michael@0 312 JSStreamWriter b(stream);
michael@0 313 StreamJSObject(b);
michael@0 314 }
michael@0 315
michael@0 316 void ThreadProfile::StreamJSObject(JSStreamWriter& b)
michael@0 317 {
michael@0 318 b.BeginObject();
michael@0 319 // Thread meta data
michael@0 320 if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
michael@0 321 // TODO Add the proper plugin name
michael@0 322 b.NameValue("name", "Plugin");
michael@0 323 } else {
michael@0 324 b.NameValue("name", mName);
michael@0 325 }
michael@0 326 b.NameValue("tid", static_cast<int>(mThreadId));
michael@0 327
michael@0 328 b.Name("samples");
michael@0 329 b.BeginArray();
michael@0 330
michael@0 331 bool sample = false;
michael@0 332 int readPos = mReadPos;
michael@0 333 while (readPos != mLastFlushPos) {
michael@0 334 // Number of tag consumed
michael@0 335 ProfileEntry entry = mEntries[readPos];
michael@0 336
michael@0 337 switch (entry.mTagName) {
michael@0 338 case 'r':
michael@0 339 {
michael@0 340 if (sample) {
michael@0 341 b.NameValue("responsiveness", entry.mTagFloat);
michael@0 342 }
michael@0 343 }
michael@0 344 break;
michael@0 345 case 'p':
michael@0 346 {
michael@0 347 if (sample) {
michael@0 348 b.NameValue("power", entry.mTagFloat);
michael@0 349 }
michael@0 350 }
michael@0 351 break;
michael@0 352 case 'f':
michael@0 353 {
michael@0 354 if (sample) {
michael@0 355 b.NameValue("frameNumber", entry.mTagLine);
michael@0 356 }
michael@0 357 }
michael@0 358 break;
michael@0 359 case 't':
michael@0 360 {
michael@0 361 if (sample) {
michael@0 362 b.NameValue("time", entry.mTagFloat);
michael@0 363 }
michael@0 364 }
michael@0 365 break;
michael@0 366 case 's':
michael@0 367 {
michael@0 368 // end the previous sample if there was one
michael@0 369 if (sample) {
michael@0 370 b.EndObject();
michael@0 371 }
michael@0 372 // begin the next sample
michael@0 373 b.BeginObject();
michael@0 374
michael@0 375 sample = true;
michael@0 376
michael@0 377 // Seek forward through the entire sample, looking for frames
michael@0 378 // this is an easier approach to reason about than adding more
michael@0 379 // control variables and cases to the loop that goes through the buffer once
michael@0 380 b.Name("frames");
michael@0 381 b.BeginArray();
michael@0 382
michael@0 383 b.BeginObject();
michael@0 384 b.NameValue("location", "(root)");
michael@0 385 b.EndObject();
michael@0 386
michael@0 387 int framePos = (readPos + 1) % mEntrySize;
michael@0 388 ProfileEntry frame = mEntries[framePos];
michael@0 389 while (framePos != mLastFlushPos && frame.mTagName != 's') {
michael@0 390 int incBy = 1;
michael@0 391 frame = mEntries[framePos];
michael@0 392 // Read ahead to the next tag, if it's a 'd' tag process it now
michael@0 393 const char* tagStringData = frame.mTagData;
michael@0 394 int readAheadPos = (framePos + 1) % mEntrySize;
michael@0 395 char tagBuff[DYNAMIC_MAX_STRING];
michael@0 396 // Make sure the string is always null terminated if it fills up
michael@0 397 // DYNAMIC_MAX_STRING-2
michael@0 398 tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
michael@0 399
michael@0 400 if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
michael@0 401 tagStringData = processDynamicTag(framePos, &incBy, tagBuff);
michael@0 402 }
michael@0 403
michael@0 404 // Write one frame. It can have either
michael@0 405 // 1. only location - 'l' containing a memory address
michael@0 406 // 2. location and line number - 'c' followed by 'd's and an optional 'n'
michael@0 407 if (frame.mTagName == 'l') {
michael@0 408 b.BeginObject();
michael@0 409 // Bug 753041
michael@0 410 // We need a double cast here to tell GCC that we don't want to sign
michael@0 411 // extend 32-bit addresses starting with 0xFXXXXXX.
michael@0 412 unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
michael@0 413 snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
michael@0 414 b.NameValue("location", tagBuff);
michael@0 415 b.EndObject();
michael@0 416 } else if (frame.mTagName == 'c') {
michael@0 417 b.BeginObject();
michael@0 418 b.NameValue("location", tagStringData);
michael@0 419 readAheadPos = (framePos + incBy) % mEntrySize;
michael@0 420 if (readAheadPos != mLastFlushPos &&
michael@0 421 mEntries[readAheadPos].mTagName == 'n') {
michael@0 422 b.NameValue("line", mEntries[readAheadPos].mTagLine);
michael@0 423 incBy++;
michael@0 424 }
michael@0 425 b.EndObject();
michael@0 426 }
michael@0 427 framePos = (framePos + incBy) % mEntrySize;
michael@0 428 }
michael@0 429 b.EndArray();
michael@0 430 }
michael@0 431 break;
michael@0 432 }
michael@0 433 readPos = (readPos + 1) % mEntrySize;
michael@0 434 }
michael@0 435 if (sample) {
michael@0 436 b.EndObject();
michael@0 437 }
michael@0 438 b.EndArray();
michael@0 439
michael@0 440 b.Name("markers");
michael@0 441 b.BeginArray();
michael@0 442 readPos = mReadPos;
michael@0 443 while (readPos != mLastFlushPos) {
michael@0 444 ProfileEntry entry = mEntries[readPos];
michael@0 445 if (entry.mTagName == 'm') {
michael@0 446 entry.getMarker()->StreamJSObject(b);
michael@0 447 }
michael@0 448 readPos = (readPos + 1) % mEntrySize;
michael@0 449 }
michael@0 450 b.EndArray();
michael@0 451 b.EndObject();
michael@0 452 }
michael@0 453
michael@0 454 JSObject* ThreadProfile::ToJSObject(JSContext *aCx)
michael@0 455 {
michael@0 456 JS::RootedValue val(aCx);
michael@0 457 std::stringstream ss;
michael@0 458 {
michael@0 459 // Define a scope to prevent a moving GC during ~JSStreamWriter from
michael@0 460 // trashing the return value.
michael@0 461 JSStreamWriter b(ss);
michael@0 462 StreamJSObject(b);
michael@0 463 NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str()));
michael@0 464 JS_ParseJSON(aCx, static_cast<const jschar*>(js_string.get()), js_string.Length(), &val);
michael@0 465 }
michael@0 466 return &val.toObject();
michael@0 467 }
michael@0 468
michael@0 469 PseudoStack* ThreadProfile::GetPseudoStack()
michael@0 470 {
michael@0 471 return mPseudoStack;
michael@0 472 }
michael@0 473
michael@0 474 void ThreadProfile::BeginUnwind()
michael@0 475 {
michael@0 476 mMutex.Lock();
michael@0 477 }
michael@0 478
michael@0 479 void ThreadProfile::EndUnwind()
michael@0 480 {
michael@0 481 mMutex.Unlock();
michael@0 482 }
michael@0 483
michael@0 484 mozilla::Mutex* ThreadProfile::GetMutex()
michael@0 485 {
michael@0 486 return &mMutex;
michael@0 487 }
michael@0 488
michael@0 489 void ThreadProfile::DuplicateLastSample() {
michael@0 490 // Scan the whole buffer (even unflushed parts)
michael@0 491 // Adding mEntrySize makes the result of the modulus positive
michael@0 492 // We search backwards from mWritePos-1 to mReadPos
michael@0 493 for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize;
michael@0 494 readPos != (mReadPos + mEntrySize - 1) % mEntrySize;
michael@0 495 readPos = (readPos + mEntrySize - 1) % mEntrySize) {
michael@0 496 if (mEntries[readPos].mTagName == 's') {
michael@0 497 // Found the start of the last entry at position readPos
michael@0 498 int copyEndIdx = mWritePos;
michael@0 499 // Go through the whole entry and duplicate it
michael@0 500 for (;readPos != copyEndIdx; readPos = (readPos + 1) % mEntrySize) {
michael@0 501 switch (mEntries[readPos].mTagName) {
michael@0 502 // Copy with new time
michael@0 503 case 't':
michael@0 504 addTag(ProfileEntry('t', static_cast<float>((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds())));
michael@0 505 break;
michael@0 506 // Don't copy markers
michael@0 507 case 'm':
michael@0 508 break;
michael@0 509 // Copy anything else we don't know about
michael@0 510 // L, B, S, c, s, d, l, f, h, r, t, p
michael@0 511 default:
michael@0 512 addTag(mEntries[readPos]);
michael@0 513 break;
michael@0 514 }
michael@0 515 }
michael@0 516 break;
michael@0 517 }
michael@0 518 }
michael@0 519 }
michael@0 520
michael@0 521 std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile)
michael@0 522 {
michael@0 523 int readPos = profile.mReadPos;
michael@0 524 while (readPos != profile.mLastFlushPos) {
michael@0 525 stream << profile.mEntries[readPos];
michael@0 526 readPos = (readPos + 1) % profile.mEntrySize;
michael@0 527 }
michael@0 528 return stream;
michael@0 529 }
michael@0 530
michael@0 531 // END ThreadProfile
michael@0 532 ////////////////////////////////////////////////////////////////////////

mercurial