tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 0b34e3e412936107678e91977f1a953d75db1256
parent 0d00dcebfc31aec0a6b157700029528ba2806b97
Author: Nazım Can Altınova <canaltinova@gmail.com>
Date:   Tue, 21 Oct 2025 21:15:35 +0000

Bug 1959977 - Collect sourceId of JS interpreter and JS tracer frames r=iain,mstange,profiler-reviewers

This patch collects the sourceIds of JS interpreter and JS execution
tracer frames inside the buffer. Then it serializes them as an index
into source table in frameTable location field, in a form like:
funcName (https://example.com/main.js:12:34)[<sourceIndex>]

This is made this way to make sure that we don't break the older Firefox
versions, so frontend will always treat `[sourceIndex]` as optional.

Differential Revision: https://phabricator.services.mozilla.com/D259254

Diffstat:
Mjs/public/ProfilingFrameIterator.h | 1+
Mjs/public/ProfilingStack.h | 20+++++++++++++++++---
Mjs/src/jit/JSJitFrameIter.cpp | 4+++-
Mjs/src/jit/JSJitFrameIter.h | 2+-
Mjs/src/vm/GeckoProfiler.cpp | 7++++++-
Mjs/src/vm/Stack.cpp | 7+++++--
Mmozglue/baseprofiler/core/ProfileBuffer.cpp | 10+++++++---
Mmozglue/baseprofiler/core/ProfileBuffer.h | 1+
Mmozglue/baseprofiler/core/ProfileBufferEntry.cpp | 5+++++
Mmozglue/baseprofiler/core/ProfileBufferEntry.h | 1+
Mmozglue/baseprofiler/public/ProfileBufferEntryKinds.h | 1+
Mtools/profiler/core/ProfileBuffer.cpp | 13+++++++++----
Mtools/profiler/core/ProfileBuffer.h | 3++-
Mtools/profiler/core/ProfileBufferEntry.cpp | 41+++++++++++++++++++++++++++++++++--------
Mtools/profiler/core/ProfileBufferEntry.h | 34++++++++++++++++++++++++++++++----
Mtools/profiler/core/ProfiledThreadData.cpp | 17+++++++++++------
Mtools/profiler/core/ProfiledThreadData.h | 18++++++++++++------
Mtools/profiler/core/platform.cpp | 7++++---
Mtools/profiler/tests/browser/browser_test_feature_jstracing_objtestutils.js | 25++++++++++++++++---------
19 files changed, 165 insertions(+), 52 deletions(-)

diff --git a/js/public/ProfilingFrameIterator.h b/js/public/ProfilingFrameIterator.h @@ -161,6 +161,7 @@ class MOZ_NON_PARAM JS_PUBLIC_API ProfilingFrameIterator { const char* label; JSScript* interpreterScript; uint64_t realmID; + uint32_t sourceId; public: void* returnAddress() const { diff --git a/js/public/ProfilingStack.h b/js/public/ProfilingStack.h @@ -141,6 +141,12 @@ class ProfilingStackFrame { // ProfilingStackFrame object. mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> realmID_; + // ID of the script source for JS stack frames. + // Must not be used on non-JS frames; it'll contain either the default 0, + // or a leftover value from a previous JS stack frame that was using this + // ProfilingStackFrame object. + mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> sourceId_; + // The bytecode offset for JS stack frames. // Must not be used on non-JS frames; it'll contain either the default 0, // or a leftover value from a previous JS stack frame that was using this @@ -163,6 +169,8 @@ class ProfilingStackFrame { pcOffsetIfJS_ = offsetIfJS; uint64_t realmID = other.realmID_; realmID_ = realmID; + uint32_t sourceId = other.sourceId_; + sourceId_ = sourceId; uint32_t flagsAndCategory = other.flagsAndCategoryPair_; flagsAndCategoryPair_ = flagsAndCategory; return *this; @@ -290,6 +298,7 @@ class ProfilingStackFrame { flagsAndCategoryPair_ = uint32_t(Flags::IS_LABEL_FRAME) | (uint32_t(aCategoryPair) << uint32_t(Flags::FLAGS_BITCOUNT)) | aFlags; + sourceId_ = 0; MOZ_ASSERT(isLabelFrame()); } @@ -306,12 +315,14 @@ class ProfilingStackFrame { template <JS::ProfilingCategoryPair Category, uint32_t ExtraFlags = 0> void initJsFrame(const char* aLabel, const char* aDynamicString, - JSScript* aScript, jsbytecode* aPc, uint64_t aRealmID) { + JSScript* aScript, jsbytecode* aPc, uint64_t aRealmID, + uint32_t aSourceId) { label_ = aLabel; dynamicString_ = aDynamicString; spOrScript = aScript; pcOffsetIfJS_ = pcToOffset(aScript, aPc); realmID_ = aRealmID; + sourceId_ = aSourceId; flagsAndCategoryPair_ = (uint32_t(Category) << uint32_t(Flags::FLAGS_BITCOUNT)) | uint32_t(Flags::IS_JS_FRAME) | ExtraFlags; @@ -351,6 +362,8 @@ class ProfilingStackFrame { void trace(JSTracer* trc); + JS_PUBLIC_API uint32_t sourceId() const; + // The offset of a pc into a script's code can actually be 0, so to // signify a nullptr pc, use a -1 index. This is checked against in // pc() and setPC() to set/get the right pc. @@ -456,7 +469,8 @@ class JS_PUBLIC_API ProfilingStack final { } void pushJsFrame(const char* label, const char* dynamicString, - JSScript* script, jsbytecode* pc, uint64_t aRealmID) { + JSScript* script, jsbytecode* pc, uint64_t aRealmID, + uint32_t aSourceId = 0) { // This thread is the only one that ever changes the value of // stackPointer. Only load the atomic once. uint32_t oldStackPointer = stackPointer; @@ -466,7 +480,7 @@ class JS_PUBLIC_API ProfilingStack final { } frames[oldStackPointer] .initJsFrame<JS::ProfilingCategoryPair::JS_Interpreter>( - label, dynamicString, script, pc, aRealmID); + label, dynamicString, script, pc, aRealmID, aSourceId); // This must happen at the end, see the comment in pushLabelFrame. stackPointer = stackPointer + 1; diff --git a/js/src/jit/JSJitFrameIter.cpp b/js/src/jit/JSJitFrameIter.cpp @@ -598,7 +598,8 @@ const char* JSJitProfilingFrameIterator::baselineInterpreterLabel() const { } void JSJitProfilingFrameIterator::baselineInterpreterScriptPC( - JSScript** script, jsbytecode** pc, uint64_t* realmID) const { + JSScript** script, jsbytecode** pc, uint64_t* realmID, + uint32_t* sourceId) const { MOZ_ASSERT(type_ == FrameType::BaselineJS); BaselineFrame* blFrame = (BaselineFrame*)(fp_ - BaselineFrame::Size()); *script = frameScript(); @@ -612,6 +613,7 @@ void JSJitProfilingFrameIterator::baselineInterpreterScriptPC( } *realmID = (*script)->realm()->creationOptions().profilerRealmID(); + *sourceId = (*script)->scriptSource()->id(); } } diff --git a/js/src/jit/JSJitFrameIter.h b/js/src/jit/JSJitFrameIter.h @@ -272,7 +272,7 @@ class JSJitProfilingFrameIterator { const char* baselineInterpreterLabel() const; void baselineInterpreterScriptPC(JSScript** script, jsbytecode** pc, - uint64_t* realmID) const; + uint64_t* realmID, uint32_t* sourceId) const; void* fp() const { MOZ_ASSERT(!done()); diff --git a/js/src/vm/GeckoProfiler.cpp b/js/src/vm/GeckoProfiler.cpp @@ -281,7 +281,8 @@ bool GeckoProfilerThread::enter(JSContext* cx, JSScript* script) { profilingStack_->pushJsFrame( "", dynamicString, script, script->code(), - script->realm()->creationOptions().profilerRealmID()); + script->realm()->creationOptions().profilerRealmID(), + script->scriptSource()->id()); return true; } @@ -632,6 +633,10 @@ void ProfilingStackFrame::setPC(jsbytecode* pc) { pcOffsetIfJS_ = pcToOffset(script, pc); } +JS_PUBLIC_API uint32_t ProfilingStackFrame::sourceId() const { + return sourceId_; +} + JS_PUBLIC_API void js::SetContextProfilingStack( JSContext* cx, ProfilingStack* profilingStack) { cx->geckoProfiler().setProfilingStack( diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp @@ -667,6 +667,7 @@ JS::ProfilingFrameIterator::getPhysicalFrameAndEntry( frame.interpreterScript = nullptr; // TODO: get the realm ID of wasm frames. Bug 1596235. frame.realmID = 0; + frame.sourceId = 0; return mozilla::Some(frame); } @@ -718,8 +719,9 @@ JS::ProfilingFrameIterator::getPhysicalFrameAndEntry( frame.stackAddress = stackAddr; if ((*entry)->isBaselineInterpreter()) { frame.label = jsJitIter().baselineInterpreterLabel(); - jsJitIter().baselineInterpreterScriptPC( - &frame.interpreterScript, &frame.interpreterPC_, &frame.realmID); + jsJitIter().baselineInterpreterScriptPC(&frame.interpreterScript, + &frame.interpreterPC_, + &frame.realmID, &frame.sourceId); MOZ_ASSERT(frame.interpreterScript); MOZ_ASSERT(frame.interpreterPC_); } else { @@ -727,6 +729,7 @@ JS::ProfilingFrameIterator::getPhysicalFrameAndEntry( frame.returnAddress_ = returnAddr; frame.label = nullptr; frame.realmID = 0; + frame.sourceId = 0; } frame.activation = activation_; frame.endStackAddress = endStackAddress_; diff --git a/mozglue/baseprofiler/core/ProfileBuffer.cpp b/mozglue/baseprofiler/core/ProfileBuffer.cpp @@ -58,8 +58,8 @@ uint64_t ProfileBuffer::AddThreadIdEntry(BaseProfilerThreadId aThreadId) { void ProfileBuffer::CollectCodeLocation( const char* aLabel, const char* aStr, uint32_t aFrameFlags, - uint64_t aInnerWindowID, const Maybe<uint32_t>& aLineNumber, - const Maybe<uint32_t>& aColumnNumber, + uint64_t aInnerWindowID, uint32_t aSourceId, + const Maybe<uint32_t>& aLineNumber, const Maybe<uint32_t>& aColumnNumber, const Maybe<ProfilingCategoryPair>& aCategoryPair) { AddEntry(ProfileBufferEntry::Label(aLabel)); AddEntry(ProfileBufferEntry::FrameFlags(uint64_t(aFrameFlags))); @@ -105,6 +105,10 @@ void ProfileBuffer::CollectCodeLocation( AddEntry(ProfileBufferEntry::InnerWindowID(aInnerWindowID)); } + if (aSourceId) { + AddEntry(ProfileBufferEntry::SourceId(aSourceId)); + } + if (aLineNumber) { AddEntry(ProfileBufferEntry::LineNumber(*aLineNumber)); } @@ -209,7 +213,7 @@ void ProfileBufferCollector::CollectProfilingStackFrame( MOZ_ASSERT(aFrame.isLabelFrame()); mBuf.CollectCodeLocation(label, dynamicString, aFrame.flags(), - aFrame.realmID(), line, column, + aFrame.realmID(), 0, line, column, Some(aFrame.categoryPair())); } diff --git a/mozglue/baseprofiler/core/ProfileBuffer.h b/mozglue/baseprofiler/core/ProfileBuffer.h @@ -42,6 +42,7 @@ class ProfileBuffer final { void CollectCodeLocation(const char* aLabel, const char* aStr, uint32_t aFrameFlags, uint64_t aInnerWindowID, + uint32_t aSourceId, const Maybe<uint32_t>& aLineNumber, const Maybe<uint32_t>& aColumnNumber, const Maybe<ProfilingCategoryPair>& aCategoryPair); diff --git a/mozglue/baseprofiler/core/ProfileBufferEntry.cpp b/mozglue/baseprofiler/core/ProfileBufferEntry.cpp @@ -63,6 +63,11 @@ ProfileBufferEntry::ProfileBufferEntry(Kind aKind, uint64_t aUint64) memcpy(mStorage, &aUint64, sizeof(aUint64)); } +ProfileBufferEntry::ProfileBufferEntry(Kind aKind, uint32_t aUint32) + : mKind(aKind) { + memcpy(mStorage, &aUint32, sizeof(aUint32)); +} + ProfileBufferEntry::ProfileBufferEntry(Kind aKind, BaseProfilerThreadId aThreadId) : mKind(aKind) { diff --git a/mozglue/baseprofiler/core/ProfileBufferEntry.h b/mozglue/baseprofiler/core/ProfileBufferEntry.h @@ -42,6 +42,7 @@ class ProfileBufferEntry { ProfileBufferEntry(Kind aKind, double aDouble); ProfileBufferEntry(Kind aKind, int64_t aInt64); ProfileBufferEntry(Kind aKind, uint64_t aUint64); + ProfileBufferEntry(Kind aKind, uint32_t aUint32); ProfileBufferEntry(Kind aKind, int aInt); ProfileBufferEntry(Kind aKind, BaseProfilerThreadId aThreadId); diff --git a/mozglue/baseprofiler/public/ProfileBufferEntryKinds.h b/mozglue/baseprofiler/public/ProfileBufferEntryKinds.h @@ -29,6 +29,7 @@ static constexpr size_t ProfileBufferEntryNumChars = 8; MACRO(DynamicStringFragment, char*, ProfileBufferEntryNumChars) \ MACRO(JitReturnAddr, void*, sizeof(void*)) \ MACRO(InnerWindowID, uint64_t, sizeof(uint64_t)) \ + MACRO(SourceId, uint32_t, sizeof(uint32_t)) \ MACRO(LineNumber, int, sizeof(int)) \ MACRO(ColumnNumber, int, sizeof(int)) \ MACRO(NativeLeafAddr, void*, sizeof(void*)) \ diff --git a/tools/profiler/core/ProfileBuffer.cpp b/tools/profiler/core/ProfileBuffer.cpp @@ -59,8 +59,8 @@ uint64_t ProfileBuffer::AddThreadIdEntry(ProfilerThreadId aThreadId) { void ProfileBuffer::CollectCodeLocation( const char* aLabel, const char* aStr, uint32_t aFrameFlags, - uint64_t aInnerWindowID, const Maybe<uint32_t>& aLineNumber, - const Maybe<uint32_t>& aColumnNumber, + uint64_t aInnerWindowID, uint32_t aSourceId, + const Maybe<uint32_t>& aLineNumber, const Maybe<uint32_t>& aColumnNumber, const Maybe<JS::ProfilingCategoryPair>& aCategoryPair) { AddEntry(ProfileBufferEntry::Label(aLabel)); AddEntry(ProfileBufferEntry::FrameFlags(uint64_t(aFrameFlags))); @@ -106,6 +106,10 @@ void ProfileBuffer::CollectCodeLocation( AddEntry(ProfileBufferEntry::InnerWindowID(aInnerWindowID)); } + if (aSourceId) { + AddEntry(ProfileBufferEntry::SourceId(aSourceId)); + } + if (aLineNumber) { AddEntry(ProfileBufferEntry::LineNumber(*aLineNumber)); } @@ -195,7 +199,7 @@ void ProfileBufferCollector::CollectJitReturnAddr(void* aAddr) { void ProfileBufferCollector::CollectWasmFrame( JS::ProfilingCategoryPair aCategory, const char* aLabel) { - mBuf.CollectCodeLocation("", aLabel, 0, 0, Nothing(), Nothing(), + mBuf.CollectCodeLocation("", aLabel, 0, 0, 0, Nothing(), Nothing(), Some(aCategory)); } @@ -210,6 +214,7 @@ void ProfileBufferCollector::CollectProfilingStackFrame( const char* dynamicString = aFrame.dynamicString(); Maybe<uint32_t> line; Maybe<uint32_t> column; + uint32_t sourceId = aFrame.sourceId(); if (aFrame.isJsFrame()) { // There are two kinds of JS frames that get pushed onto the ProfilingStack. @@ -240,6 +245,6 @@ void ProfileBufferCollector::CollectProfilingStackFrame( } mBuf.CollectCodeLocation(label, dynamicString, aFrame.flags(), - aFrame.realmID(), line, column, + aFrame.realmID(), sourceId, line, column, Some(aFrame.categoryPair())); } diff --git a/tools/profiler/core/ProfileBuffer.h b/tools/profiler/core/ProfileBuffer.h @@ -46,7 +46,8 @@ class ProfileBuffer final { void CollectCodeLocation( const char* aLabel, const char* aStr, uint32_t aFrameFlags, - uint64_t aInnerWindowID, const mozilla::Maybe<uint32_t>& aLineNumber, + uint64_t aInnerWindowID, uint32_t aSourceId, + const mozilla::Maybe<uint32_t>& aLineNumber, const mozilla::Maybe<uint32_t>& aColumnNumber, const mozilla::Maybe<JS::ProfilingCategoryPair>& aCategoryPair); diff --git a/tools/profiler/core/ProfileBufferEntry.cpp b/tools/profiler/core/ProfileBufferEntry.cpp @@ -75,6 +75,11 @@ ProfileBufferEntry::ProfileBufferEntry(Kind aKind, uint64_t aUint64) memcpy(mStorage, &aUint64, sizeof(aUint64)); } +ProfileBufferEntry::ProfileBufferEntry(Kind aKind, uint32_t aUint32) + : mKind(aKind) { + memcpy(mStorage, &aUint32, sizeof(aUint32)); +} + ProfileBufferEntry::ProfileBufferEntry(Kind aKind, ProfilerThreadId aThreadId) : mKind(aKind) { static_assert(std::is_trivially_copyable_v<ProfilerThreadId>); @@ -118,6 +123,12 @@ uint64_t ProfileBufferEntry::GetUint64() const { return result; } +uint32_t ProfileBufferEntry::GetUint32() const { + uint32_t result; + memcpy(&result, mStorage, sizeof(result)); + return result; +} + ProfilerThreadId ProfileBufferEntry::GetThreadId() const { ProfilerThreadId result; static_assert(std::is_trivially_copyable_v<ProfilerThreadId>); @@ -321,7 +332,8 @@ bool UniqueStacks::FrameKey::NormalFrameData::operator==( return mLocation == aOther.mLocation && mRelevantForJS == aOther.mRelevantForJS && mBaselineInterp == aOther.mBaselineInterp && - mInnerWindowID == aOther.mInnerWindowID && mLine == aOther.mLine && + mInnerWindowID == aOther.mInnerWindowID && + mSourceId == aOther.mSourceId && mLine == aOther.mLine && mColumn == aOther.mColumn && mCategoryPair == aOther.mCategoryPair; } @@ -337,14 +349,17 @@ bool UniqueStacks::FrameKey::JITFrameData::operator==( // strings at the same indices. UniqueStacks::UniqueStacks( FailureLatch& aFailureLatch, JITFrameInfo&& aJITFrameInfo, - ProfilerCodeAddressService* aCodeAddressService /* = nullptr */) + ProfilerCodeAddressService* aCodeAddressService /* = nullptr */, + const nsTHashMap<SourceId, IndexIntoSourceTable>* + aSourceIdToIndexMap /* = nullptr */) : mUniqueStrings(std::move(aJITFrameInfo) .MoveUniqueStringsWithNewFailureLatch(aFailureLatch)), mCodeAddressService(aCodeAddressService), mFrameTableWriter(aFailureLatch), mStackTableWriter(aFailureLatch), mJITInfoRanges(std::move(aJITFrameInfo) - .MoveRangesWithNewFailureLatch(aFailureLatch)) { + .MoveRangesWithNewFailureLatch(aFailureLatch)), + mSourceIdToIndexMap(aSourceIdToIndexMap) { if (!mUniqueStrings) { SetFailure("Did not get mUniqueStrings from JITFrameInfo"); return; @@ -504,7 +519,8 @@ void UniqueStacks::StreamNonJITFrame(const FrameKey& aFrame) { AutoArraySchemaWithStringsWriter writer(mFrameTableWriter, *mUniqueStrings); const NormalFrameData& data = aFrame.mData.as<NormalFrameData>(); - writer.StringElement(LOCATION, data.mLocation); + writer.StringElement(LOCATION, + data.GetLocationWithSourceIndex(mSourceIdToIndexMap)); writer.BoolElement(RELEVANT_FOR_JS, data.mRelevantForJS); // It's okay to convert uint64_t to double here because DOM always creates IDs @@ -1143,7 +1159,10 @@ void ProfileBuffer::MaybeStreamExecutionTraceToJSON( } UniqueStacks::FrameKey newFrame(nsCString(name.get()), true, false, - event.functionEvent.realmID, Nothing{}, + event.functionEvent.realmID, + // Even though it says scriptId, this is + // actually sourceId. See bug 1980369. + event.functionEvent.scriptId, Nothing{}, Nothing{}, Some(categoryPair)); maybeStack = uniqueStacks.AppendFrame(stack, newFrame); if (!maybeStack) { @@ -1160,7 +1179,7 @@ void ProfileBuffer::MaybeStreamExecutionTraceToJSON( } else if (event.kind == JS::ExecutionTrace::EventKind::LabelEnter) { UniqueStacks::FrameKey newFrame( nsCString(&trace.stringBuffer[event.labelEvent.label]), true, false, - 0, Nothing{}, Nothing{}, Some(JS::ProfilingCategoryPair::DOM)); + 0, 0, Nothing{}, Nothing{}, Some(JS::ProfilingCategoryPair::DOM)); maybeStack = uniqueStacks.AppendFrame(stack, newFrame); if (!maybeStack) { writer.SetFailure("AppendFrame failure"); @@ -1409,6 +1428,12 @@ ProfilerThreadId ProfileBuffer::DoStreamSamplesAndMarkersToJSON( e.Next(); } + uint32_t sourceId = 0; + if (e.Has() && e.Get().IsSourceId()) { + sourceId = uint64_t(e.Get().GetUint32()); + e.Next(); + } + Maybe<unsigned> line; if (e.Has() && e.Get().IsLineNumber()) { line = Some(unsigned(e.Get().GetInt())); @@ -1431,8 +1456,8 @@ ProfilerThreadId ProfileBuffer::DoStreamSamplesAndMarkersToJSON( maybeStack = uniqueStacks.AppendFrame( stack, UniqueStacks::FrameKey(std::move(frameLabel), relevantForJS, - isBaselineInterp, innerWindowID, line, - column, categoryPair)); + isBaselineInterp, innerWindowID, + sourceId, line, column, categoryPair)); if (!maybeStack) { writer.SetFailure("AppendFrame failure"); return; diff --git a/tools/profiler/core/ProfileBufferEntry.h b/tools/profiler/core/ProfileBufferEntry.h @@ -25,6 +25,8 @@ #include "mozilla/Variant.h" #include "mozilla/Vector.h" #include "nsString.h" +#include "nsStringFwd.h" +#include "nsTHashMap.h" class ProfilerCodeAddressService; struct JSContext; @@ -52,6 +54,7 @@ class ProfileBufferEntry { ProfileBufferEntry(Kind aKind, double aDouble); ProfileBufferEntry(Kind aKind, int64_t aInt64); ProfileBufferEntry(Kind aKind, uint64_t aUint64); + ProfileBufferEntry(Kind aKind, uint32_t aUint32); ProfileBufferEntry(Kind aKind, int aInt); ProfileBufferEntry(Kind aKind, ProfilerThreadId aThreadId); @@ -87,6 +90,7 @@ class ProfileBufferEntry { int GetInt() const; int64_t GetInt64() const; uint64_t GetUint64() const; + uint32_t GetUint32() const; ProfilerThreadId GetThreadId() const; void CopyCharsInto(char (&aOutArray)[kNumChars]) const; }; @@ -218,15 +222,16 @@ class UniqueStacks final : public mozilla::FailureLatch { public: struct FrameKey { explicit FrameKey(const char* aLocation) - : mData(NormalFrameData{nsCString(aLocation), false, false, 0, + : mData(NormalFrameData{nsCString(aLocation), false, false, 0, 0, mozilla::Nothing(), mozilla::Nothing()}) {} FrameKey(nsCString&& aLocation, bool aRelevantForJS, bool aBaselineInterp, - uint64_t aInnerWindowID, const mozilla::Maybe<unsigned>& aLine, + uint64_t aInnerWindowID, uint32_t aSourceId, + const mozilla::Maybe<unsigned>& aLine, const mozilla::Maybe<unsigned>& aColumn, const mozilla::Maybe<JS::ProfilingCategoryPair>& aCategoryPair) : mData(NormalFrameData{aLocation, aRelevantForJS, aBaselineInterp, - aInnerWindowID, aLine, aColumn, + aInnerWindowID, aSourceId, aLine, aColumn, aCategoryPair}) {} FrameKey(void* aJITAddress, uint32_t aJITDepth, uint32_t aRangeIndex) @@ -242,10 +247,26 @@ class UniqueStacks final : public mozilla::FailureLatch { struct NormalFrameData { bool operator==(const NormalFrameData& aOther) const; + nsCString GetLocationWithSourceIndex( + const nsTHashMap<SourceId, IndexIntoSourceTable>* aSourceIdToIndexMap) + const { + nsCString result = mLocation; + if (mSourceId && aSourceIdToIndexMap) { + auto index = aSourceIdToIndexMap->MaybeGet(mSourceId); + if (index) { + result.AppendLiteral("["); + result.AppendInt(*index); + result.AppendLiteral("]"); + } + } + return result; + } + nsCString mLocation; bool mRelevantForJS; bool mBaselineInterp; uint64_t mInnerWindowID; + uint32_t mSourceId; mozilla::Maybe<unsigned> mLine; mozilla::Maybe<unsigned> mColumn; mozilla::Maybe<JS::ProfilingCategoryPair> mCategoryPair; @@ -275,6 +296,7 @@ class UniqueStacks final : public mozilla::FailureLatch { hash = mozilla::AddToHash(hash, data.mRelevantForJS); hash = mozilla::AddToHash(hash, data.mBaselineInterp); hash = mozilla::AddToHash(hash, data.mInnerWindowID); + hash = mozilla::AddToHash(hash, data.mSourceId); if (data.mLine.isSome()) { hash = mozilla::AddToHash(hash, *data.mLine); } @@ -346,7 +368,9 @@ class UniqueStacks final : public mozilla::FailureLatch { UniqueStacks(mozilla::FailureLatch& aFailureLatch, JITFrameInfo&& aJITFrameInfo, - ProfilerCodeAddressService* aCodeAddressService = nullptr); + ProfilerCodeAddressService* aCodeAddressService = nullptr, + const nsTHashMap<SourceId, IndexIntoSourceTable>* + aSourceIdToIndexMap = nullptr); // Return a StackKey for aFrame as the stack's root frame (no prefix). [[nodiscard]] mozilla::Maybe<StackKey> BeginStack(const FrameKey& aFrame); @@ -401,6 +425,8 @@ class UniqueStacks final : public mozilla::FailureLatch { mozilla::HashMap<StackKey, uint32_t, StackKeyHasher> mStackToIndexMap; mozilla::Vector<JITFrameInfoForBufferRange> mJITInfoRanges; + + const nsTHashMap<SourceId, IndexIntoSourceTable>* mSourceIdToIndexMap; }; // diff --git a/tools/profiler/core/ProfiledThreadData.cpp b/tools/profiler/core/ProfiledThreadData.cpp @@ -104,7 +104,8 @@ mozilla::NotNull<mozilla::UniquePtr<UniqueStacks>> ProfiledThreadData::PrepareUniqueStacks( const ProfileBuffer& aBuffer, JSContext* aCx, mozilla::FailureLatch& aFailureLatch, ProfilerCodeAddressService* aService, - mozilla::ProgressLogger aProgressLogger) { + mozilla::ProgressLogger aProgressLogger, + const nsTHashMap<SourceId, IndexIntoSourceTable>* aSourceIdToIndexMap) { if (mJITFrameInfoForPreviousJSContexts && mJITFrameInfoForPreviousJSContexts->HasExpired( aBuffer.BufferRangeStart())) { @@ -133,7 +134,7 @@ ProfiledThreadData::PrepareUniqueStacks( } return mozilla::MakeNotNull<mozilla::UniquePtr<UniqueStacks>>( - aFailureLatch, std::move(jitFrameInfo), aService); + aFailureLatch, std::move(jitFrameInfo), aService, aSourceIdToIndexMap); } void ProfiledThreadData::StreamJSON( @@ -393,7 +394,8 @@ ThreadStreamingContext::ThreadStreamingContext( ProfiledThreadData& aProfiledThreadData, const ProfileBuffer& aBuffer, JSContext* aCx, mozilla::FailureLatch& aFailureLatch, ProfilerCodeAddressService* aService, - mozilla::ProgressLogger aProgressLogger) + mozilla::ProgressLogger aProgressLogger, + const nsTHashMap<SourceId, IndexIntoSourceTable>* aSourceIdToIndexMap) : mProfiledThreadData(aProfiledThreadData), mJSContext(aCx), mSamplesDataWriter(aFailureLatch), @@ -403,7 +405,8 @@ ThreadStreamingContext::ThreadStreamingContext( aBuffer, aCx, aFailureLatch, aService, aProgressLogger.CreateSubLoggerFromTo( 0_pc, "Preparing thread streaming context unique stacks...", - 99_pc, "Prepared thread streaming context Unique stacks"))) { + 99_pc, "Prepared thread streaming context Unique stacks"), + aSourceIdToIndexMap)) { if (aFailureLatch.Failed()) { return; } @@ -454,7 +457,8 @@ ProcessStreamingContext::~ProcessStreamingContext() { void ProcessStreamingContext::AddThreadStreamingContext( ProfiledThreadData& aProfiledThreadData, const ProfileBuffer& aBuffer, JSContext* aCx, ProfilerCodeAddressService* aService, - mozilla::ProgressLogger aProgressLogger) { + mozilla::ProgressLogger aProgressLogger, + const nsTHashMap<SourceId, IndexIntoSourceTable>* aSourceIdToIndexMap) { if (mFailureLatch.Failed()) { return; } @@ -466,5 +470,6 @@ void ProcessStreamingContext::AddThreadStreamingContext( aProfiledThreadData, aBuffer, aCx, mFailureLatch, aService, aProgressLogger.CreateSubLoggerFromTo( 1_pc, "Prepared streaming thread id", 100_pc, - "Added thread streaming context")); + "Added thread streaming context"), + aSourceIdToIndexMap); } diff --git a/tools/profiler/core/ProfiledThreadData.h b/tools/profiler/core/ProfiledThreadData.h @@ -77,7 +77,9 @@ class ProfiledThreadData final { const ProfileBuffer& aBuffer, JSContext* aCx, mozilla::FailureLatch& aFailureLatch, ProfilerCodeAddressService* aService, - mozilla::ProgressLogger aProgressLogger); + mozilla::ProgressLogger aProgressLogger, + const nsTHashMap<SourceId, IndexIntoSourceTable>* aSourceIdToIndexMap = + nullptr); void StreamJSON(const ProfileBuffer& aBuffer, JSContext* aCx, SpliceableJSONWriter& aWriter, const nsACString& aProcessName, @@ -166,7 +168,9 @@ struct ThreadStreamingContext { const ProfileBuffer& aBuffer, JSContext* aCx, mozilla::FailureLatch& aFailureLatch, ProfilerCodeAddressService* aService, - mozilla::ProgressLogger aProgressLogger); + mozilla::ProgressLogger aProgressLogger, + const nsTHashMap<SourceId, IndexIntoSourceTable>* + aSourceIdToIndexMap = nullptr); void FinalizeWriter(); }; @@ -184,10 +188,12 @@ class ProcessStreamingContext final : public mozilla::FailureLatch { // Add the streaming context corresponding to each profiled thread. This // should be called exactly the number of times specified in the constructor. - void AddThreadStreamingContext(ProfiledThreadData& aProfiledThreadData, - const ProfileBuffer& aBuffer, JSContext* aCx, - ProfilerCodeAddressService* aService, - mozilla::ProgressLogger aProgressLogger); + void AddThreadStreamingContext( + ProfiledThreadData& aProfiledThreadData, const ProfileBuffer& aBuffer, + JSContext* aCx, ProfilerCodeAddressService* aService, + mozilla::ProgressLogger aProgressLogger, + const nsTHashMap<SourceId, IndexIntoSourceTable>* aSourceIdToIndexMap = + nullptr); // Retrieve the ThreadStreamingContext for a given thread id. // Returns null if that thread id doesn't correspond to any profiled thread. diff --git a/tools/profiler/core/platform.cpp b/tools/profiler/core/platform.cpp @@ -2486,7 +2486,7 @@ static void MergeStacks( uint32_t(js::ProfilingStackFrame::Flags::IS_BLINTERP_FRAME); stackFrame.initJsFrame<JS::ProfilingCategoryPair::JS_BaselineInterpret, ExtraFlags>("", jsFrame.label, script, pc, - jsFrame.realmID); + jsFrame.realmID, jsFrame.sourceId); aCollector.CollectProfilingStackFrame(stackFrame); } else { MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion || @@ -3671,7 +3671,7 @@ static void CollectJavaThreadProfileData( nsCString frameNameString = frameName->ToCString(); auto categoryPair = InferJavaCategory(frameNameString); - aProfileBuffer.CollectCodeLocation("", frameNameString.get(), 0, 0, + aProfileBuffer.CollectCodeLocation("", frameNameString.get(), 0, 0, 0, Nothing(), Nothing(), Some(categoryPair)); } @@ -3909,7 +3909,8 @@ locked_profiler_stream_json_for_this_process( MOZ_RELEASE_ASSERT(thread.mProfiledThreadData); processStreamingContext.AddThreadStreamingContext( *thread.mProfiledThreadData, buffer, thread.mJSContext, aService, - std::move(progressLogger)); + std::move(progressLogger), + sourceIdToIndexMap.isSome() ? sourceIdToIndexMap.ptr() : nullptr); if (aWriter.Failed()) { return Err(ProfilerError::JsonGenerationFailed); } diff --git a/tools/profiler/tests/browser/browser_test_feature_jstracing_objtestutils.js b/tools/profiler/tests/browser/browser_test_feature_jstracing_objtestutils.js @@ -83,11 +83,15 @@ add_task(async function test_profile_feature_jstracing_objtestutils() { // Then lookup for the matching frame, based on the string index const { frameTable } = contentThread; const FRAME_LOCATION_SLOT = frameTable.schema.location; - const functionDummyFrameIndex = frameTable.data.findIndex( - frame => frame[FRAME_LOCATION_SLOT] == functionDummyStringIndex - ); + const functionDummyFrameIndexes = []; + frameTable.data.forEach((frame, idx) => { + if (frame[FRAME_LOCATION_SLOT] === functionDummyStringIndex) { + functionDummyFrameIndexes.push(idx); + } + }); + Assert.greater( - functionDummyFrameIndex, + functionDummyFrameIndexes.length, 0, "Found frame for 'dummy' function call" ); @@ -96,14 +100,17 @@ add_task(async function test_profile_feature_jstracing_objtestutils() { // Each symbol's frame is visible in a stack, and the stack tree is valid const { stackTable } = contentThread; const STACK_FRAME_SLOT = stackTable.schema.frame; - const functionDummyStack = stackTable.data.findIndex( - stack => stack[STACK_FRAME_SLOT] == functionDummyFrameIndex - ); + const functionDummyStacks = []; + stackTable.data.forEach((stack, idx) => { + if (functionDummyFrameIndexes.includes(stack[STACK_FRAME_SLOT])) { + functionDummyStacks.push(idx); + } + }); const { samples } = contentThread; const SAMPLE_STACK_SLOT = contentThread.samples.schema.stack; - const functionDummySamples = samples.data.filter( - sample => sample[SAMPLE_STACK_SLOT] == functionDummyStack + const functionDummySamples = samples.data.filter(sample => + functionDummyStacks.includes(sample[SAMPLE_STACK_SLOT]) ); const actualValues = [];