michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include "platform.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "jsapi.h" michael@0: michael@0: // JSON michael@0: #include "JSStreamWriter.h" michael@0: michael@0: // Self michael@0: #include "ProfileEntry.h" michael@0: michael@0: #if _MSC_VER michael@0: #define snprintf _snprintf michael@0: #endif michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // BEGIN ProfileEntry michael@0: michael@0: ProfileEntry::ProfileEntry() michael@0: : mTagData(nullptr) michael@0: , mTagName(0) michael@0: { } michael@0: michael@0: // aTagData must not need release (i.e. be a string from the text segment) michael@0: ProfileEntry::ProfileEntry(char aTagName, const char *aTagData) michael@0: : mTagData(aTagData) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker) michael@0: : mTagMarker(aTagMarker) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr) michael@0: : mTagPtr(aTagPtr) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: ProfileEntry::ProfileEntry(char aTagName, float aTagFloat) michael@0: : mTagFloat(aTagFloat) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset) michael@0: : mTagOffset(aTagOffset) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress) michael@0: : mTagAddress(aTagAddress) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: ProfileEntry::ProfileEntry(char aTagName, int aTagLine) michael@0: : mTagLine(aTagLine) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: ProfileEntry::ProfileEntry(char aTagName, char aTagChar) michael@0: : mTagChar(aTagChar) michael@0: , mTagName(aTagName) michael@0: { } michael@0: michael@0: bool ProfileEntry::is_ent_hint(char hintChar) { michael@0: return mTagName == 'h' && mTagChar == hintChar; michael@0: } michael@0: michael@0: bool ProfileEntry::is_ent_hint() { michael@0: return mTagName == 'h'; michael@0: } michael@0: michael@0: bool ProfileEntry::is_ent(char tagChar) { michael@0: return mTagName == tagChar; michael@0: } michael@0: michael@0: void* ProfileEntry::get_tagPtr() { michael@0: // No consistency checking. Oh well. michael@0: return mTagPtr; michael@0: } michael@0: michael@0: void ProfileEntry::log() michael@0: { michael@0: // There is no compiler enforced mapping between tag chars michael@0: // and union variant fields, so the following was derived michael@0: // by looking through all the use points of TableTicker.cpp. michael@0: // mTagMarker (ProfilerMarker*) m michael@0: // mTagData (const char*) c,s michael@0: // mTagPtr (void*) d,l,L,B (immediate backtrace), S(start-of-stack) michael@0: // mTagLine (int) n,f michael@0: // mTagChar (char) h michael@0: // mTagFloat (double) r,t,p michael@0: switch (mTagName) { michael@0: case 'm': michael@0: LOGF("%c \"%s\"", mTagName, mTagMarker->GetMarkerName()); break; michael@0: case 'c': case 's': michael@0: LOGF("%c \"%s\"", mTagName, mTagData); break; michael@0: case 'd': case 'l': case 'L': case 'B': case 'S': michael@0: LOGF("%c %p", mTagName, mTagPtr); break; michael@0: case 'n': case 'f': michael@0: LOGF("%c %d", mTagName, mTagLine); break; michael@0: case 'h': michael@0: LOGF("%c \'%c\'", mTagName, mTagChar); break; michael@0: case 'r': case 't': case 'p': michael@0: LOGF("%c %f", mTagName, mTagFloat); break; michael@0: default: michael@0: LOGF("'%c' unknown_tag", mTagName); break; michael@0: } michael@0: } michael@0: michael@0: std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry) michael@0: { michael@0: if (entry.mTagName == 'r' || entry.mTagName == 't') { michael@0: stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n"; michael@0: } else if (entry.mTagName == 'l' || entry.mTagName == 'L') { michael@0: // Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms michael@0: // Additionally, stringstream seemed to be ignoring formatter flags. michael@0: char tagBuff[1024]; michael@0: unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr; michael@0: snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc); michael@0: stream << tagBuff; michael@0: } else if (entry.mTagName == 'd') { michael@0: // TODO implement 'd' tag for text profile michael@0: } else { michael@0: stream << entry.mTagName << "-" << entry.mTagData << "\n"; michael@0: } michael@0: return stream; michael@0: } michael@0: michael@0: // END ProfileEntry michael@0: //////////////////////////////////////////////////////////////////////// michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // BEGIN ThreadProfile michael@0: michael@0: #define DYNAMIC_MAX_STRING 512 michael@0: michael@0: ThreadProfile::ThreadProfile(const char* aName, int aEntrySize, michael@0: PseudoStack *aStack, Thread::tid_t aThreadId, michael@0: PlatformData* aPlatform, michael@0: bool aIsMainThread, void *aStackTop) michael@0: : mWritePos(0) michael@0: , mLastFlushPos(0) michael@0: , mReadPos(0) michael@0: , mEntrySize(aEntrySize) michael@0: , mPseudoStack(aStack) michael@0: , mMutex("ThreadProfile::mMutex") michael@0: , mName(strdup(aName)) michael@0: , mThreadId(aThreadId) michael@0: , mIsMainThread(aIsMainThread) michael@0: , mPlatformData(aPlatform) michael@0: , mGeneration(0) michael@0: , mPendingGenerationFlush(0) michael@0: , mStackTop(aStackTop) michael@0: { michael@0: mEntries = new ProfileEntry[mEntrySize]; michael@0: } michael@0: michael@0: ThreadProfile::~ThreadProfile() michael@0: { michael@0: free(mName); michael@0: delete[] mEntries; michael@0: } michael@0: michael@0: void ThreadProfile::addTag(ProfileEntry aTag) michael@0: { michael@0: // Called from signal, call only reentrant functions michael@0: mEntries[mWritePos] = aTag; michael@0: mWritePos = mWritePos + 1; michael@0: if (mWritePos >= mEntrySize) { michael@0: mPendingGenerationFlush++; michael@0: mWritePos = mWritePos % mEntrySize; michael@0: } michael@0: if (mWritePos == mReadPos) { michael@0: // Keep one slot open michael@0: mEntries[mReadPos] = ProfileEntry(); michael@0: mReadPos = (mReadPos + 1) % mEntrySize; michael@0: } michael@0: // we also need to move the flush pos to ensure we michael@0: // do not pass it michael@0: if (mWritePos == mLastFlushPos) { michael@0: mLastFlushPos = (mLastFlushPos + 1) % mEntrySize; michael@0: } michael@0: } michael@0: michael@0: // flush the new entries michael@0: void ThreadProfile::flush() michael@0: { michael@0: mLastFlushPos = mWritePos; michael@0: mGeneration += mPendingGenerationFlush; michael@0: mPendingGenerationFlush = 0; michael@0: } michael@0: michael@0: // discards all of the entries since the last flush() michael@0: // NOTE: that if mWritePos happens to wrap around past michael@0: // mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries michael@0: // michael@0: // r = mReadPos michael@0: // w = mWritePos michael@0: // f = mLastFlushPos michael@0: // michael@0: // r f w michael@0: // |-----------------------------| michael@0: // | abcdefghijklmnopq | -> 'abcdefghijklmnopq' michael@0: // |-----------------------------| michael@0: // michael@0: // michael@0: // mWritePos and mReadPos have passed mLastFlushPos michael@0: // f michael@0: // w r michael@0: // |-----------------------------| michael@0: // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| michael@0: // |-----------------------------| michael@0: // w michael@0: // r michael@0: // |-----------------------------| michael@0: // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> '' michael@0: // |-----------------------------| michael@0: // michael@0: // michael@0: // mWritePos will end up the same as mReadPos michael@0: // r michael@0: // w f michael@0: // |-----------------------------| michael@0: // |ABCDEFGHIJKLMklmnopqrstuvwxyz| michael@0: // |-----------------------------| michael@0: // r michael@0: // w michael@0: // |-----------------------------| michael@0: // |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> '' michael@0: // |-----------------------------| michael@0: // michael@0: // michael@0: // mWritePos has moved past mReadPos michael@0: // w r f michael@0: // |-----------------------------| michael@0: // |ABCDEFdefghijklmnopqrstuvwxyz| michael@0: // |-----------------------------| michael@0: // r w michael@0: // |-----------------------------| michael@0: // |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl' michael@0: // |-----------------------------| michael@0: michael@0: void ThreadProfile::erase() michael@0: { michael@0: mWritePos = mLastFlushPos; michael@0: mPendingGenerationFlush = 0; michael@0: } michael@0: michael@0: char* ThreadProfile::processDynamicTag(int readPos, michael@0: int* tagsConsumed, char* tagBuff) michael@0: { michael@0: int readAheadPos = (readPos + 1) % mEntrySize; michael@0: int tagBuffPos = 0; michael@0: michael@0: // Read the string stored in mTagData until the null character is seen michael@0: bool seenNullByte = false; michael@0: while (readAheadPos != mLastFlushPos && !seenNullByte) { michael@0: (*tagsConsumed)++; michael@0: ProfileEntry readAheadEntry = mEntries[readAheadPos]; michael@0: for (size_t pos = 0; pos < sizeof(void*); pos++) { michael@0: tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos]; michael@0: if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) { michael@0: seenNullByte = true; michael@0: break; michael@0: } michael@0: tagBuffPos++; michael@0: } michael@0: if (!seenNullByte) michael@0: readAheadPos = (readAheadPos + 1) % mEntrySize; michael@0: } michael@0: return tagBuff; michael@0: } michael@0: michael@0: void ThreadProfile::IterateTags(IterateTagsCallback aCallback) michael@0: { michael@0: MOZ_ASSERT(aCallback); michael@0: michael@0: int readPos = mReadPos; michael@0: while (readPos != mLastFlushPos) { michael@0: // Number of tag consumed michael@0: int incBy = 1; michael@0: const ProfileEntry& entry = mEntries[readPos]; michael@0: michael@0: // Read ahead to the next tag, if it's a 'd' tag process it now michael@0: const char* tagStringData = entry.mTagData; michael@0: int readAheadPos = (readPos + 1) % mEntrySize; michael@0: char tagBuff[DYNAMIC_MAX_STRING]; michael@0: // Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2 michael@0: tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; michael@0: michael@0: if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') { michael@0: tagStringData = processDynamicTag(readPos, &incBy, tagBuff); michael@0: } michael@0: michael@0: aCallback(entry, tagStringData); michael@0: michael@0: readPos = (readPos + incBy) % mEntrySize; michael@0: } michael@0: } michael@0: michael@0: void ThreadProfile::ToStreamAsJSON(std::ostream& stream) michael@0: { michael@0: JSStreamWriter b(stream); michael@0: StreamJSObject(b); michael@0: } michael@0: michael@0: void ThreadProfile::StreamJSObject(JSStreamWriter& b) michael@0: { michael@0: b.BeginObject(); michael@0: // Thread meta data michael@0: if (XRE_GetProcessType() == GeckoProcessType_Plugin) { michael@0: // TODO Add the proper plugin name michael@0: b.NameValue("name", "Plugin"); michael@0: } else { michael@0: b.NameValue("name", mName); michael@0: } michael@0: b.NameValue("tid", static_cast(mThreadId)); michael@0: michael@0: b.Name("samples"); michael@0: b.BeginArray(); michael@0: michael@0: bool sample = false; michael@0: int readPos = mReadPos; michael@0: while (readPos != mLastFlushPos) { michael@0: // Number of tag consumed michael@0: ProfileEntry entry = mEntries[readPos]; michael@0: michael@0: switch (entry.mTagName) { michael@0: case 'r': michael@0: { michael@0: if (sample) { michael@0: b.NameValue("responsiveness", entry.mTagFloat); michael@0: } michael@0: } michael@0: break; michael@0: case 'p': michael@0: { michael@0: if (sample) { michael@0: b.NameValue("power", entry.mTagFloat); michael@0: } michael@0: } michael@0: break; michael@0: case 'f': michael@0: { michael@0: if (sample) { michael@0: b.NameValue("frameNumber", entry.mTagLine); michael@0: } michael@0: } michael@0: break; michael@0: case 't': michael@0: { michael@0: if (sample) { michael@0: b.NameValue("time", entry.mTagFloat); michael@0: } michael@0: } michael@0: break; michael@0: case 's': michael@0: { michael@0: // end the previous sample if there was one michael@0: if (sample) { michael@0: b.EndObject(); michael@0: } michael@0: // begin the next sample michael@0: b.BeginObject(); michael@0: michael@0: sample = true; michael@0: michael@0: // Seek forward through the entire sample, looking for frames michael@0: // this is an easier approach to reason about than adding more michael@0: // control variables and cases to the loop that goes through the buffer once michael@0: b.Name("frames"); michael@0: b.BeginArray(); michael@0: michael@0: b.BeginObject(); michael@0: b.NameValue("location", "(root)"); michael@0: b.EndObject(); michael@0: michael@0: int framePos = (readPos + 1) % mEntrySize; michael@0: ProfileEntry frame = mEntries[framePos]; michael@0: while (framePos != mLastFlushPos && frame.mTagName != 's') { michael@0: int incBy = 1; michael@0: frame = mEntries[framePos]; michael@0: // Read ahead to the next tag, if it's a 'd' tag process it now michael@0: const char* tagStringData = frame.mTagData; michael@0: int readAheadPos = (framePos + 1) % mEntrySize; michael@0: char tagBuff[DYNAMIC_MAX_STRING]; michael@0: // Make sure the string is always null terminated if it fills up michael@0: // DYNAMIC_MAX_STRING-2 michael@0: tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; michael@0: michael@0: if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') { michael@0: tagStringData = processDynamicTag(framePos, &incBy, tagBuff); michael@0: } michael@0: michael@0: // Write one frame. It can have either michael@0: // 1. only location - 'l' containing a memory address michael@0: // 2. location and line number - 'c' followed by 'd's and an optional 'n' michael@0: if (frame.mTagName == 'l') { michael@0: b.BeginObject(); michael@0: // Bug 753041 michael@0: // We need a double cast here to tell GCC that we don't want to sign michael@0: // extend 32-bit addresses starting with 0xFXXXXXX. michael@0: unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr; michael@0: snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc); michael@0: b.NameValue("location", tagBuff); michael@0: b.EndObject(); michael@0: } else if (frame.mTagName == 'c') { michael@0: b.BeginObject(); michael@0: b.NameValue("location", tagStringData); michael@0: readAheadPos = (framePos + incBy) % mEntrySize; michael@0: if (readAheadPos != mLastFlushPos && michael@0: mEntries[readAheadPos].mTagName == 'n') { michael@0: b.NameValue("line", mEntries[readAheadPos].mTagLine); michael@0: incBy++; michael@0: } michael@0: b.EndObject(); michael@0: } michael@0: framePos = (framePos + incBy) % mEntrySize; michael@0: } michael@0: b.EndArray(); michael@0: } michael@0: break; michael@0: } michael@0: readPos = (readPos + 1) % mEntrySize; michael@0: } michael@0: if (sample) { michael@0: b.EndObject(); michael@0: } michael@0: b.EndArray(); michael@0: michael@0: b.Name("markers"); michael@0: b.BeginArray(); michael@0: readPos = mReadPos; michael@0: while (readPos != mLastFlushPos) { michael@0: ProfileEntry entry = mEntries[readPos]; michael@0: if (entry.mTagName == 'm') { michael@0: entry.getMarker()->StreamJSObject(b); michael@0: } michael@0: readPos = (readPos + 1) % mEntrySize; michael@0: } michael@0: b.EndArray(); michael@0: b.EndObject(); michael@0: } michael@0: michael@0: JSObject* ThreadProfile::ToJSObject(JSContext *aCx) michael@0: { michael@0: JS::RootedValue val(aCx); michael@0: std::stringstream ss; michael@0: { michael@0: // Define a scope to prevent a moving GC during ~JSStreamWriter from michael@0: // trashing the return value. michael@0: JSStreamWriter b(ss); michael@0: StreamJSObject(b); michael@0: NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str())); michael@0: JS_ParseJSON(aCx, static_cast(js_string.get()), js_string.Length(), &val); michael@0: } michael@0: return &val.toObject(); michael@0: } michael@0: michael@0: PseudoStack* ThreadProfile::GetPseudoStack() michael@0: { michael@0: return mPseudoStack; michael@0: } michael@0: michael@0: void ThreadProfile::BeginUnwind() michael@0: { michael@0: mMutex.Lock(); michael@0: } michael@0: michael@0: void ThreadProfile::EndUnwind() michael@0: { michael@0: mMutex.Unlock(); michael@0: } michael@0: michael@0: mozilla::Mutex* ThreadProfile::GetMutex() michael@0: { michael@0: return &mMutex; michael@0: } michael@0: michael@0: void ThreadProfile::DuplicateLastSample() { michael@0: // Scan the whole buffer (even unflushed parts) michael@0: // Adding mEntrySize makes the result of the modulus positive michael@0: // We search backwards from mWritePos-1 to mReadPos michael@0: for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize; michael@0: readPos != (mReadPos + mEntrySize - 1) % mEntrySize; michael@0: readPos = (readPos + mEntrySize - 1) % mEntrySize) { michael@0: if (mEntries[readPos].mTagName == 's') { michael@0: // Found the start of the last entry at position readPos michael@0: int copyEndIdx = mWritePos; michael@0: // Go through the whole entry and duplicate it michael@0: for (;readPos != copyEndIdx; readPos = (readPos + 1) % mEntrySize) { michael@0: switch (mEntries[readPos].mTagName) { michael@0: // Copy with new time michael@0: case 't': michael@0: addTag(ProfileEntry('t', static_cast((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()))); michael@0: break; michael@0: // Don't copy markers michael@0: case 'm': michael@0: break; michael@0: // Copy anything else we don't know about michael@0: // L, B, S, c, s, d, l, f, h, r, t, p michael@0: default: michael@0: addTag(mEntries[readPos]); michael@0: break; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile) michael@0: { michael@0: int readPos = profile.mReadPos; michael@0: while (readPos != profile.mLastFlushPos) { michael@0: stream << profile.mEntries[readPos]; michael@0: readPos = (readPos + 1) % profile.mEntrySize; michael@0: } michael@0: return stream; michael@0: } michael@0: michael@0: // END ThreadProfile michael@0: ////////////////////////////////////////////////////////////////////////