diff options
Diffstat (limited to 'tools/profiler/core/ProfileEntry.cpp')
-rw-r--r-- | tools/profiler/core/ProfileEntry.cpp | 881 |
1 files changed, 881 insertions, 0 deletions
diff --git a/tools/profiler/core/ProfileEntry.cpp b/tools/profiler/core/ProfileEntry.cpp new file mode 100644 index 0000000000..22d53a6f30 --- /dev/null +++ b/tools/profiler/core/ProfileEntry.cpp @@ -0,0 +1,881 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <ostream> +#include "platform.h" +#include "mozilla/HashFunctions.h" + +#ifndef SPS_STANDALONE +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +// JS +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/TrackedOptimizationInfo.h" +#endif + +// Self +#include "ProfileEntry.h" + +using mozilla::MakeUnique; +using mozilla::UniquePtr; +using mozilla::Maybe; +using mozilla::Some; +using mozilla::Nothing; +using mozilla::JSONWriter; + + +//////////////////////////////////////////////////////////////////////// +// BEGIN ProfileEntry + +ProfileEntry::ProfileEntry() + : mTagData(nullptr) + , mTagName(0) +{ } + +// aTagData must not need release (i.e. be a string from the text segment) +ProfileEntry::ProfileEntry(char aTagName, const char *aTagData) + : mTagData(aTagData) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker) + : mTagMarker(aTagMarker) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr) + : mTagPtr(aTagPtr) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, double aTagDouble) + : mTagDouble(aTagDouble) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset) + : mTagOffset(aTagOffset) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress) + : mTagAddress(aTagAddress) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, int aTagInt) + : mTagInt(aTagInt) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, char aTagChar) + : mTagChar(aTagChar) + , mTagName(aTagName) +{ } + +bool ProfileEntry::is_ent_hint(char hintChar) { + return mTagName == 'h' && mTagChar == hintChar; +} + +bool ProfileEntry::is_ent_hint() { + return mTagName == 'h'; +} + +bool ProfileEntry::is_ent(char tagChar) { + return mTagName == tagChar; +} + +void* ProfileEntry::get_tagPtr() { + // No consistency checking. Oh well. + return mTagPtr; +} + +// END ProfileEntry +//////////////////////////////////////////////////////////////////////// + +class JSONSchemaWriter +{ + JSONWriter& mWriter; + uint32_t mIndex; + +public: + explicit JSONSchemaWriter(JSONWriter& aWriter) + : mWriter(aWriter) + , mIndex(0) + { + aWriter.StartObjectProperty("schema"); + } + + void WriteField(const char* aName) { + mWriter.IntProperty(aName, mIndex++); + } + + ~JSONSchemaWriter() { + mWriter.EndObject(); + } +}; + +#ifndef SPS_STANDALONE +class StreamOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp +{ + JSONWriter& mWriter; + UniqueJSONStrings& mUniqueStrings; + bool mStartedTypeList; + +public: + StreamOptimizationTypeInfoOp(JSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings) + : mWriter(aWriter) + , mUniqueStrings(aUniqueStrings) + , mStartedTypeList(false) + { } + + void readType(const char* keyedBy, const char* name, + const char* location, Maybe<unsigned> lineno) override { + if (!mStartedTypeList) { + mStartedTypeList = true; + mWriter.StartObjectElement(); + mWriter.StartArrayProperty("typeset"); + } + + mWriter.StartObjectElement(); + { + mUniqueStrings.WriteProperty(mWriter, "keyedBy", keyedBy); + if (name) { + mUniqueStrings.WriteProperty(mWriter, "name", name); + } + if (location) { + mUniqueStrings.WriteProperty(mWriter, "location", location); + } + if (lineno.isSome()) { + mWriter.IntProperty("line", *lineno); + } + } + mWriter.EndObject(); + } + + void operator()(JS::TrackedTypeSite site, const char* mirType) override { + if (mStartedTypeList) { + mWriter.EndArray(); + mStartedTypeList = false; + } else { + mWriter.StartObjectElement(); + } + + { + mUniqueStrings.WriteProperty(mWriter, "site", JS::TrackedTypeSiteString(site)); + mUniqueStrings.WriteProperty(mWriter, "mirType", mirType); + } + mWriter.EndObject(); + } +}; + +// As mentioned in ProfileEntry.h, the JSON format contains many arrays whose +// elements are laid out according to various schemas to help +// de-duplication. This RAII class helps write these arrays by keeping track of +// the last non-null element written and adding the appropriate number of null +// elements when writing new non-null elements. It also automatically opens and +// closes an array element on the given JSON writer. +// +// Example usage: +// +// // Define the schema of elements in this type of array: [FOO, BAR, BAZ] +// enum Schema : uint32_t { +// FOO = 0, +// BAR = 1, +// BAZ = 2 +// }; +// +// AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings); +// if (shouldWriteFoo) { +// writer.IntElement(FOO, getFoo()); +// } +// ... etc ... +class MOZ_RAII AutoArraySchemaWriter +{ + friend class AutoObjectWriter; + + SpliceableJSONWriter& mJSONWriter; + UniqueJSONStrings* mStrings; + uint32_t mNextFreeIndex; + +public: + AutoArraySchemaWriter(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aStrings) + : mJSONWriter(aWriter) + , mStrings(&aStrings) + , mNextFreeIndex(0) + { + mJSONWriter.StartArrayElement(); + } + + // If you don't have access to a UniqueStrings, you had better not try and + // write a string element down the line! + explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter) + : mJSONWriter(aWriter) + , mStrings(nullptr) + , mNextFreeIndex(0) + { + mJSONWriter.StartArrayElement(); + } + + ~AutoArraySchemaWriter() { + mJSONWriter.EndArray(); + } + + void FillUpTo(uint32_t aIndex) { + MOZ_ASSERT(aIndex >= mNextFreeIndex); + mJSONWriter.NullElements(aIndex - mNextFreeIndex); + mNextFreeIndex = aIndex + 1; + } + + void IntElement(uint32_t aIndex, uint32_t aValue) { + FillUpTo(aIndex); + mJSONWriter.IntElement(aValue); + } + + void DoubleElement(uint32_t aIndex, double aValue) { + FillUpTo(aIndex); + mJSONWriter.DoubleElement(aValue); + } + + void StringElement(uint32_t aIndex, const char* aValue) { + MOZ_RELEASE_ASSERT(mStrings); + FillUpTo(aIndex); + mStrings->WriteElement(mJSONWriter, aValue); + } +}; + +class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp +{ + SpliceableJSONWriter& mWriter; + UniqueJSONStrings& mUniqueStrings; + +public: + StreamOptimizationAttemptsOp(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings) + : mWriter(aWriter), + mUniqueStrings(aUniqueStrings) + { } + + void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override { + enum Schema : uint32_t { + STRATEGY = 0, + OUTCOME = 1 + }; + + AutoArraySchemaWriter writer(mWriter, mUniqueStrings); + writer.StringElement(STRATEGY, JS::TrackedStrategyString(strategy)); + writer.StringElement(OUTCOME, JS::TrackedOutcomeString(outcome)); + } +}; + +class StreamJSFramesOp : public JS::ForEachProfiledFrameOp +{ + void* mReturnAddress; + UniqueStacks::Stack& mStack; + unsigned mDepth; + +public: + StreamJSFramesOp(void* aReturnAddr, UniqueStacks::Stack& aStack) + : mReturnAddress(aReturnAddr) + , mStack(aStack) + , mDepth(0) + { } + + unsigned depth() const { + MOZ_ASSERT(mDepth > 0); + return mDepth; + } + + void operator()(const JS::ForEachProfiledFrameOp::FrameHandle& aFrameHandle) override { + UniqueStacks::OnStackFrameKey frameKey(mReturnAddress, mDepth, aFrameHandle); + mStack.AppendFrame(frameKey); + mDepth++; + } +}; +#endif + +uint32_t UniqueJSONStrings::GetOrAddIndex(const char* aStr) +{ + uint32_t index; + StringKey key(aStr); + + auto it = mStringToIndexMap.find(key); + + if (it != mStringToIndexMap.end()) { + return it->second; + } + index = mStringToIndexMap.size(); + mStringToIndexMap[key] = index; + mStringTableWriter.StringElement(aStr); + return index; +} + +bool UniqueStacks::FrameKey::operator==(const FrameKey& aOther) const +{ + return mLocation == aOther.mLocation && + mLine == aOther.mLine && + mCategory == aOther.mCategory && + mJITAddress == aOther.mJITAddress && + mJITDepth == aOther.mJITDepth; +} + +bool UniqueStacks::StackKey::operator==(const StackKey& aOther) const +{ + MOZ_ASSERT_IF(mPrefix == aOther.mPrefix, mPrefixHash == aOther.mPrefixHash); + return mPrefix == aOther.mPrefix && mFrame == aOther.mFrame; +} + +UniqueStacks::Stack::Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot) + : mUniqueStacks(aUniqueStacks) + , mStack(aUniqueStacks.GetOrAddFrameIndex(aRoot)) +{ +} + +void UniqueStacks::Stack::AppendFrame(const OnStackFrameKey& aFrame) +{ + // Compute the prefix hash and index before mutating mStack. + uint32_t prefixHash = mStack.Hash(); + uint32_t prefix = mUniqueStacks.GetOrAddStackIndex(mStack); + mStack.UpdateHash(prefixHash, prefix, mUniqueStacks.GetOrAddFrameIndex(aFrame)); +} + +uint32_t UniqueStacks::Stack::GetOrAddIndex() const +{ + return mUniqueStacks.GetOrAddStackIndex(mStack); +} + +uint32_t UniqueStacks::FrameKey::Hash() const +{ + uint32_t hash = 0; + if (!mLocation.IsEmpty()) { +#ifdef SPS_STANDALONE + hash = mozilla::HashString(mLocation.c_str()); +#else + hash = mozilla::HashString(mLocation.get()); +#endif + } + if (mLine.isSome()) { + hash = mozilla::AddToHash(hash, *mLine); + } + if (mCategory.isSome()) { + hash = mozilla::AddToHash(hash, *mCategory); + } + if (mJITAddress.isSome()) { + hash = mozilla::AddToHash(hash, *mJITAddress); + if (mJITDepth.isSome()) { + hash = mozilla::AddToHash(hash, *mJITDepth); + } + } + return hash; +} + +uint32_t UniqueStacks::StackKey::Hash() const +{ + if (mPrefix.isNothing()) { + return mozilla::HashGeneric(mFrame); + } + return mozilla::AddToHash(*mPrefixHash, mFrame); +} + +UniqueStacks::Stack UniqueStacks::BeginStack(const OnStackFrameKey& aRoot) +{ + return Stack(*this, aRoot); +} + +UniqueStacks::UniqueStacks(JSContext* aContext) + : mContext(aContext) + , mFrameCount(0) +{ + mFrameTableWriter.StartBareList(); + mStackTableWriter.StartBareList(); +} + +#ifdef SPS_STANDALONE +uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack) +{ + uint32_t index; + auto it = mStackToIndexMap.find(aStack); + + if (it != mStackToIndexMap.end()) { + return it->second; + } + + index = mStackToIndexMap.size(); + mStackToIndexMap[aStack] = index; + StreamStack(aStack); + return index; +} +#else +uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack) +{ + uint32_t index; + if (mStackToIndexMap.Get(aStack, &index)) { + MOZ_ASSERT(index < mStackToIndexMap.Count()); + return index; + } + + index = mStackToIndexMap.Count(); + mStackToIndexMap.Put(aStack, index); + StreamStack(aStack); + return index; +} +#endif + +#ifdef SPS_STANDALONE +uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame) +{ + uint32_t index; + auto it = mFrameToIndexMap.find(aFrame); + if (it != mFrameToIndexMap.end()) { + MOZ_ASSERT(it->second < mFrameCount); + return it->second; + } + + // A manual count is used instead of mFrameToIndexMap.Count() due to + // forwarding of canonical JIT frames above. + index = mFrameCount++; + mFrameToIndexMap[aFrame] = index; + StreamFrame(aFrame); + return index; +} +#else +uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame) +{ + uint32_t index; + if (mFrameToIndexMap.Get(aFrame, &index)) { + MOZ_ASSERT(index < mFrameCount); + return index; + } + + // If aFrame isn't canonical, forward it to the canonical frame's index. + if (aFrame.mJITFrameHandle) { + void* canonicalAddr = aFrame.mJITFrameHandle->canonicalAddress(); + if (canonicalAddr != *aFrame.mJITAddress) { + OnStackFrameKey canonicalKey(canonicalAddr, *aFrame.mJITDepth, *aFrame.mJITFrameHandle); + uint32_t canonicalIndex = GetOrAddFrameIndex(canonicalKey); + mFrameToIndexMap.Put(aFrame, canonicalIndex); + return canonicalIndex; + } + } + + // A manual count is used instead of mFrameToIndexMap.Count() due to + // forwarding of canonical JIT frames above. + index = mFrameCount++; + mFrameToIndexMap.Put(aFrame, index); + StreamFrame(aFrame); + return index; +} +#endif + +uint32_t UniqueStacks::LookupJITFrameDepth(void* aAddr) +{ + uint32_t depth; + + auto it = mJITFrameDepthMap.find(aAddr); + if (it != mJITFrameDepthMap.end()) { + depth = it->second; + MOZ_ASSERT(depth > 0); + return depth; + } + return 0; +} + +void UniqueStacks::AddJITFrameDepth(void* aAddr, unsigned depth) +{ + mJITFrameDepthMap[aAddr] = depth; +} + +void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter) +{ + mFrameTableWriter.EndBareList(); + aWriter.TakeAndSplice(mFrameTableWriter.WriteFunc()); +} + +void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter) +{ + mStackTableWriter.EndBareList(); + aWriter.TakeAndSplice(mStackTableWriter.WriteFunc()); +} + +void UniqueStacks::StreamStack(const StackKey& aStack) +{ + enum Schema : uint32_t { + PREFIX = 0, + FRAME = 1 + }; + + AutoArraySchemaWriter writer(mStackTableWriter, mUniqueStrings); + if (aStack.mPrefix.isSome()) { + writer.IntElement(PREFIX, *aStack.mPrefix); + } + writer.IntElement(FRAME, aStack.mFrame); +} + +void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame) +{ + enum Schema : uint32_t { + LOCATION = 0, + IMPLEMENTATION = 1, + OPTIMIZATIONS = 2, + LINE = 3, + CATEGORY = 4 + }; + + AutoArraySchemaWriter writer(mFrameTableWriter, mUniqueStrings); + +#ifndef SPS_STANDALONE + if (!aFrame.mJITFrameHandle) { +#else + { +#endif +#ifdef SPS_STANDALONE + writer.StringElement(LOCATION, aFrame.mLocation.c_str()); +#else + writer.StringElement(LOCATION, aFrame.mLocation.get()); +#endif + if (aFrame.mLine.isSome()) { + writer.IntElement(LINE, *aFrame.mLine); + } + if (aFrame.mCategory.isSome()) { + writer.IntElement(CATEGORY, *aFrame.mCategory); + } + } +#ifndef SPS_STANDALONE + else { + const JS::ForEachProfiledFrameOp::FrameHandle& jitFrame = *aFrame.mJITFrameHandle; + + writer.StringElement(LOCATION, jitFrame.label()); + + JS::ProfilingFrameIterator::FrameKind frameKind = jitFrame.frameKind(); + MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion || + frameKind == JS::ProfilingFrameIterator::Frame_Baseline); + writer.StringElement(IMPLEMENTATION, + frameKind == JS::ProfilingFrameIterator::Frame_Ion + ? "ion" + : "baseline"); + + if (jitFrame.hasTrackedOptimizations()) { + writer.FillUpTo(OPTIMIZATIONS); + mFrameTableWriter.StartObjectElement(); + { + mFrameTableWriter.StartArrayProperty("types"); + { + StreamOptimizationTypeInfoOp typeInfoOp(mFrameTableWriter, mUniqueStrings); + jitFrame.forEachOptimizationTypeInfo(typeInfoOp); + } + mFrameTableWriter.EndArray(); + + JS::Rooted<JSScript*> script(mContext); + jsbytecode* pc; + mFrameTableWriter.StartObjectProperty("attempts"); + { + { + JSONSchemaWriter schema(mFrameTableWriter); + schema.WriteField("strategy"); + schema.WriteField("outcome"); + } + + mFrameTableWriter.StartArrayProperty("data"); + { + StreamOptimizationAttemptsOp attemptOp(mFrameTableWriter, mUniqueStrings); + jitFrame.forEachOptimizationAttempt(attemptOp, script.address(), &pc); + } + mFrameTableWriter.EndArray(); + } + mFrameTableWriter.EndObject(); + + if (JSAtom* name = js::GetPropertyNameFromPC(script, pc)) { + char buf[512]; + JS_PutEscapedFlatString(buf, mozilla::ArrayLength(buf), js::AtomToFlatString(name), 0); + mUniqueStrings.WriteProperty(mFrameTableWriter, "propertyName", buf); + } + + unsigned line, column; + line = JS_PCToLineNumber(script, pc, &column); + mFrameTableWriter.IntProperty("line", line); + mFrameTableWriter.IntProperty("column", column); + } + mFrameTableWriter.EndObject(); + } + } +#endif +} + +struct ProfileSample +{ + uint32_t mStack; + Maybe<double> mTime; + Maybe<double> mResponsiveness; + Maybe<double> mRSS; + Maybe<double> mUSS; + Maybe<int> mFrameNumber; + Maybe<double> mPower; +}; + +static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample) +{ + enum Schema : uint32_t { + STACK = 0, + TIME = 1, + RESPONSIVENESS = 2, + RSS = 3, + USS = 4, + FRAME_NUMBER = 5, + POWER = 6 + }; + + AutoArraySchemaWriter writer(aWriter); + + writer.IntElement(STACK, aSample.mStack); + + if (aSample.mTime.isSome()) { + writer.DoubleElement(TIME, *aSample.mTime); + } + + if (aSample.mResponsiveness.isSome()) { + writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness); + } + + if (aSample.mRSS.isSome()) { + writer.DoubleElement(RSS, *aSample.mRSS); + } + + if (aSample.mUSS.isSome()) { + writer.DoubleElement(USS, *aSample.mUSS); + } + + if (aSample.mFrameNumber.isSome()) { + writer.IntElement(FRAME_NUMBER, *aSample.mFrameNumber); + } + + if (aSample.mPower.isSome()) { + writer.DoubleElement(POWER, *aSample.mPower); + } +} + +void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId, + double aSinceTime, JSContext* aContext, + UniqueStacks& aUniqueStacks) +{ + Maybe<ProfileSample> sample; + int readPos = mReadPos; + int currentThreadID = -1; + Maybe<double> currentTime; + UniquePtr<char[]> tagBuff = MakeUnique<char[]>(DYNAMIC_MAX_STRING); + + while (readPos != mWritePos) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + currentTime.reset(); + int readAheadPos = (readPos + 1) % mEntrySize; + if (readAheadPos != mWritePos) { + ProfileEntry readAheadEntry = mEntries[readAheadPos]; + if (readAheadEntry.mTagName == 't') { + currentTime = Some(readAheadEntry.mTagDouble); + } + } + } + if (currentThreadID == aThreadId && (currentTime.isNothing() || *currentTime >= aSinceTime)) { + switch (entry.mTagName) { + case 'r': + if (sample.isSome()) { + sample->mResponsiveness = Some(entry.mTagDouble); + } + break; + case 'p': + if (sample.isSome()) { + sample->mPower = Some(entry.mTagDouble); + } + break; + case 'R': + if (sample.isSome()) { + sample->mRSS = Some(entry.mTagDouble); + } + break; + case 'U': + if (sample.isSome()) { + sample->mUSS = Some(entry.mTagDouble); + } + break; + case 'f': + if (sample.isSome()) { + sample->mFrameNumber = Some(entry.mTagInt); + } + break; + case 's': + { + // end the previous sample if there was one + if (sample.isSome()) { + WriteSample(aWriter, *sample); + sample.reset(); + } + // begin the next sample + sample.emplace(); + sample->mTime = currentTime; + + // Seek forward through the entire sample, looking for frames + // this is an easier approach to reason about than adding more + // control variables and cases to the loop that goes through the buffer once + + UniqueStacks::Stack stack = + aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)")); + + int framePos = (readPos + 1) % mEntrySize; + ProfileEntry frame = mEntries[framePos]; + while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') { + int incBy = 1; + frame = mEntries[framePos]; + + // Read ahead to the next tag, if it's a 'd' tag process it now + const char* tagStringData = frame.mTagData; + int readAheadPos = (framePos + 1) % mEntrySize; + // Make sure the string is always null terminated if it fills up + // DYNAMIC_MAX_STRING-2 + tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; + + if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') { + tagStringData = processDynamicTag(framePos, &incBy, tagBuff.get()); + } + + // Write one frame. It can have either + // 1. only location - 'l' containing a memory address + // 2. location and line number - 'c' followed by 'd's, + // an optional 'n' and an optional 'y' + // 3. a JIT return address - 'j' containing native code address + if (frame.mTagName == 'l') { + // Bug 753041 + // We need a double cast here to tell GCC that we don't want to sign + // extend 32-bit addresses starting with 0xFXXXXXX. + unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr; + snprintf(tagBuff.get(), DYNAMIC_MAX_STRING, "%#llx", pc); + stack.AppendFrame(UniqueStacks::OnStackFrameKey(tagBuff.get())); + } else if (frame.mTagName == 'c') { + UniqueStacks::OnStackFrameKey frameKey(tagStringData); + readAheadPos = (framePos + incBy) % mEntrySize; + if (readAheadPos != mWritePos && + mEntries[readAheadPos].mTagName == 'n') { + frameKey.mLine = Some((unsigned) mEntries[readAheadPos].mTagInt); + incBy++; + } + readAheadPos = (framePos + incBy) % mEntrySize; + if (readAheadPos != mWritePos && + mEntries[readAheadPos].mTagName == 'y') { + frameKey.mCategory = Some((unsigned) mEntries[readAheadPos].mTagInt); + incBy++; + } + stack.AppendFrame(frameKey); +#ifndef SPS_STANDALONE + } else if (frame.mTagName == 'J') { + // A JIT frame may expand to multiple frames due to inlining. + void* pc = frame.mTagPtr; + unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc); + if (depth == 0) { + StreamJSFramesOp framesOp(pc, stack); + JS::ForEachProfiledFrame(aContext, pc, framesOp); + aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth()); + } else { + for (unsigned i = 0; i < depth; i++) { + UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i); + stack.AppendFrame(inlineFrameKey); + } + } +#endif + } + framePos = (framePos + incBy) % mEntrySize; + } + + sample->mStack = stack.GetOrAddIndex(); + break; + } + } + } + readPos = (readPos + 1) % mEntrySize; + } + if (sample.isSome()) { + WriteSample(aWriter, *sample); + } +} + +void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId, + double aSinceTime, UniqueStacks& aUniqueStacks) +{ + int readPos = mReadPos; + int currentThreadID = -1; + while (readPos != mWritePos) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + } else if (currentThreadID == aThreadId && entry.mTagName == 'm') { + const ProfilerMarker* marker = entry.getMarker(); + if (marker->GetTime() >= aSinceTime) { + entry.getMarker()->StreamJSON(aWriter, aUniqueStacks); + } + } + readPos = (readPos + 1) % mEntrySize; + } +} + +int ProfileBuffer::FindLastSampleOfThread(int aThreadId) +{ + // We search backwards from mWritePos-1 to mReadPos. + // Adding mEntrySize makes the result of the modulus positive. + for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize; + readPos != (mReadPos + mEntrySize - 1) % mEntrySize; + readPos = (readPos + mEntrySize - 1) % mEntrySize) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) { + return readPos; + } + } + + return -1; +} + +void ProfileBuffer::DuplicateLastSample(int aThreadId) +{ + int lastSampleStartPos = FindLastSampleOfThread(aThreadId); + if (lastSampleStartPos == -1) { + return; + } + + MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T'); + + addTag(mEntries[lastSampleStartPos]); + + // Go through the whole entry and duplicate it, until we find the next one. + for (int readPos = (lastSampleStartPos + 1) % mEntrySize; + readPos != mWritePos; + readPos = (readPos + 1) % mEntrySize) { + switch (mEntries[readPos].mTagName) { + case 'T': + // We're done. + return; + case 't': + // Copy with new time + addTag(ProfileEntry('t', (mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds())); + break; + case 'm': + // Don't copy markers + break; + // Copy anything else we don't know about + // L, B, S, c, s, d, l, f, h, r, t, p + default: + addTag(mEntries[readPos]); + break; + } + } +} + +// END ProfileBuffer +//////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////// +// BEGIN ThreadProfile + +// END ThreadProfile +//////////////////////////////////////////////////////////////////////// |