summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core/ProfileEntry.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/core/ProfileEntry.cpp')
-rw-r--r--tools/profiler/core/ProfileEntry.cpp881
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 000000000..22d53a6f3
--- /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
+////////////////////////////////////////////////////////////////////////