Bug 1429904 - Add JITFrameInfo. r?njn
MozReview-Commit-ID: DashxIKyzYZ
--- a/tools/profiler/core/ProfileBuffer.h
+++ b/tools/profiler/core/ProfileBuffer.h
@@ -45,16 +45,23 @@ public:
void CollectCodeLocation(
const char* aLabel, const char* aStr, int aLineNumber,
const mozilla::Maybe<js::ProfileEntry::Category>& aCategory);
// Maximum size of a frameKey string that we'll handle.
static const size_t kMaxFrameKeyLength = 512;
+ // Add JIT frame information to aJITFrameInfo for any JitReturnAddr entries
+ // that are currently in the buffer at or after aRangeStart, in samples
+ // for the given thread.
+ void AddJITInfoForRange(uint64_t aRangeStart,
+ int aThreadId, JSContext* aContext,
+ JITFrameInfo& aJITFrameInfo) const;
+
bool StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
double aSinceTime, JSContext* cx,
UniqueStacks& aUniqueStacks) const;
bool StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
const mozilla::TimeStamp& aProcessStartTime,
double aSinceTime,
UniqueStacks& aUniqueStacks) const;
void StreamPausedRangesToJSON(SpliceableJSONWriter& aWriter,
--- a/tools/profiler/core/ProfileBufferEntry.cpp
+++ b/tools/profiler/core/ProfileBufferEntry.cpp
@@ -288,16 +288,62 @@ UniqueStacks::BeginStack(const FrameKey&
UniqueStacks::StackKey
UniqueStacks::AppendFrame(const StackKey& aStack, const FrameKey& aFrame)
{
return StackKey(aStack, GetOrAddStackIndex(aStack), GetOrAddFrameIndex(aFrame));
}
uint32_t
+JITFrameInfoForBufferRange::JITFrameKey::Hash() const
+{
+ uint32_t hash = 0;
+ hash = AddToHash(hash, mCanonicalAddress);
+ hash = AddToHash(hash, mDepth);
+ return hash;
+}
+
+bool
+JITFrameInfoForBufferRange::JITFrameKey::operator==(const JITFrameKey& aOther) const
+{
+ return mCanonicalAddress == aOther.mCanonicalAddress &&
+ mDepth == aOther.mDepth;
+}
+
+template<class KeyClass, class T> void
+CopyClassHashtable(nsClassHashtable<KeyClass, T>& aDest,
+ const nsClassHashtable<KeyClass, T>& aSrc)
+{
+ for (auto iter = aSrc.ConstIter(); !iter.Done(); iter.Next()) {
+ const T& objRef = *iter.Data();
+ aDest.LookupOrAdd(iter.Key(), objRef);
+ }
+}
+
+JITFrameInfoForBufferRange
+JITFrameInfoForBufferRange::Clone() const
+{
+ nsClassHashtable<nsPtrHashKey<void>, nsTArray<JITFrameKey>> jitAddressToJITFramesMap;
+ nsClassHashtable<nsGenericHashKey<JITFrameKey>, nsCString> jitFrameToFrameJSONMap;
+ CopyClassHashtable(jitAddressToJITFramesMap, mJITAddressToJITFramesMap);
+ CopyClassHashtable(jitFrameToFrameJSONMap, mJITFrameToFrameJSONMap);
+ return JITFrameInfoForBufferRange{
+ mRangeStart, mRangeEnd,
+ Move(jitAddressToJITFramesMap), Move(jitFrameToFrameJSONMap) };
+}
+
+JITFrameInfo::JITFrameInfo(const JITFrameInfo& aOther)
+ : mUniqueStrings(MakeUnique<UniqueJSONStrings>(*aOther.mUniqueStrings))
+{
+ for (const JITFrameInfoForBufferRange& range : aOther.mRanges) {
+ mRanges.AppendElement(range.Clone());
+ }
+}
+
+uint32_t
UniqueStacks::JITAddress::Hash() const
{
uint32_t hash = 0;
hash = AddToHash(hash, mAddress);
hash = AddToHash(hash, mStreamingGen);
return hash;
}
@@ -555,29 +601,30 @@ StreamJITFrameOptimizations(SpliceableJS
unsigned line, column;
line = JS_PCToLineNumber(script, pc, &column);
aWriter.IntProperty("line", line);
aWriter.IntProperty("column", column);
}
aWriter.EndObject();
}
-void
-UniqueStacks::StreamJITFrame(JSContext* aContext,
- const JS::ProfiledFrameHandle& aJITFrame)
+static void
+StreamJITFrame(JSContext* aContext, SpliceableJSONWriter& aWriter,
+ UniqueJSONStrings& aUniqueStrings,
+ const JS::ProfiledFrameHandle& aJITFrame)
{
enum Schema : uint32_t {
LOCATION = 0,
IMPLEMENTATION = 1,
OPTIMIZATIONS = 2,
LINE = 3,
CATEGORY = 4
};
- AutoArraySchemaWriter writer(mFrameTableWriter, *mUniqueStrings);
+ AutoArraySchemaWriter writer(aWriter, aUniqueStrings);
writer.StringElement(LOCATION, aJITFrame.label());
JS::ProfilingFrameIterator::FrameKind frameKind = aJITFrame.frameKind();
MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion ||
frameKind == JS::ProfilingFrameIterator::Frame_Baseline);
writer.StringElement(IMPLEMENTATION,
frameKind == JS::ProfilingFrameIterator::Frame_Ion
@@ -588,16 +635,90 @@ UniqueStacks::StreamJITFrame(JSContext*
writer.FreeFormElement(OPTIMIZATIONS,
[&](SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings) {
StreamJITFrameOptimizations(aWriter, aUniqueStrings, aContext,
aJITFrame);
});
}
}
+struct CStringWriteFunc : public JSONWriteFunc
+{
+ nsACString& mBuffer; // The struct must not outlive this buffer
+ explicit CStringWriteFunc(nsACString& aBuffer) : mBuffer(aBuffer) {}
+
+ void Write(const char* aStr) override
+ {
+ mBuffer.Append(aStr);
+ }
+};
+
+static nsCString
+JSONForJITFrame(JSContext* aContext, const JS::ProfiledFrameHandle& aJITFrame,
+ UniqueJSONStrings& aUniqueStrings)
+{
+ nsCString json;
+ SpliceableJSONWriter writer(MakeUnique<CStringWriteFunc>(json));
+ StreamJITFrame(aContext, writer, aUniqueStrings, aJITFrame);
+ return json;
+}
+
+void
+JITFrameInfo::AddInfoForRange(uint64_t aRangeStart, uint64_t aRangeEnd,
+ JSContext* aCx,
+ const std::function<void(const std::function<void(void*)>&)>& aJITAddressProvider)
+{
+ if (aRangeStart == aRangeEnd) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(aRangeStart < aRangeEnd);
+
+ if (!mRanges.IsEmpty()) {
+ const JITFrameInfoForBufferRange& prevRange = mRanges.LastElement();
+ MOZ_RELEASE_ASSERT(prevRange.mRangeEnd <= aRangeStart,
+ "Ranges must be non-overlapping and added in-order.");
+ }
+
+ using JITFrameKey = JITFrameInfoForBufferRange::JITFrameKey;
+
+ nsClassHashtable<nsPtrHashKey<void>, nsTArray<JITFrameKey>> jitAddressToJITFrameMap;
+ nsClassHashtable<nsGenericHashKey<JITFrameKey>, nsCString> jitFrameToFrameJSONMap;
+
+ aJITAddressProvider([&](void* aJITAddress) {
+ // Make sure that we have cached data for aJITAddress.
+ if (!jitAddressToJITFrameMap.Contains(aJITAddress)) {
+ nsTArray<JITFrameKey>& jitFrameKeys =
+ *jitAddressToJITFrameMap.LookupOrAdd(aJITAddress);
+ for (JS::ProfiledFrameHandle handle : JS::GetProfiledFrames(aCx, aJITAddress)) {
+ uint32_t depth = jitFrameKeys.Length();
+ JITFrameKey jitFrameKey{ handle.canonicalAddress(), depth };
+ if (!jitFrameToFrameJSONMap.Contains(jitFrameKey)) {
+ nsCString& json = *jitFrameToFrameJSONMap.LookupOrAdd(jitFrameKey);
+ json = JSONForJITFrame(aCx, handle, *mUniqueStrings);
+ }
+ jitFrameKeys.AppendElement(jitFrameKey);
+ }
+ }
+ });
+
+ mRanges.AppendElement(JITFrameInfoForBufferRange{
+ aRangeStart, aRangeEnd,
+ Move(jitAddressToJITFrameMap), Move(jitFrameToFrameJSONMap)
+ });
+}
+
+// This method will go away in the next patch.
+void
+UniqueStacks::StreamJITFrame(JSContext* aContext,
+ const JS::ProfiledFrameHandle& aJITFrame)
+{
+ ::StreamJITFrame(aContext, mFrameTableWriter, *mUniqueStrings, aJITFrame);
+}
+
struct ProfileSample
{
uint32_t mStack;
double mTime;
Maybe<double> mResponsiveness;
Maybe<double> mRSS;
Maybe<double> mUSS;
};
@@ -963,16 +1084,59 @@ ProfileBuffer::StreamSamplesToJSON(Splic
WriteSample(aWriter, *aUniqueStacks.mUniqueStrings, sample);
haveSamples = true;
}
return haveSamples;
#undef ERROR_AND_CONTINUE
}
+void
+ProfileBuffer::AddJITInfoForRange(uint64_t aRangeStart,
+ int aThreadId, JSContext* aContext,
+ JITFrameInfo& aJITFrameInfo) const
+{
+ // We can only process JitReturnAddr entries if we have a JSContext.
+ MOZ_RELEASE_ASSERT(aContext);
+
+ aRangeStart = std::max(aRangeStart, mRangeStart);
+ aJITFrameInfo.AddInfoForRange(aRangeStart, mRangeEnd, aContext,
+ [&](const std::function<void(void*)>& aJITAddressConsumer) {
+ // Find all JitReturnAddr entries in the given range for the given thread,
+ // and call aJITAddressConsumer with those addresses.
+
+ EntryGetter e(*this, aRangeStart);
+ while (true) {
+ // Advance to the next ThreadId entry.
+ while (e.Has() && !e.Get().IsThreadId()) {
+ e.Next();
+ }
+ if (!e.Has()) {
+ break;
+ }
+
+ MOZ_ASSERT(e.Get().IsThreadId());
+ int threadId = e.Get().u.mInt;
+ e.Next();
+
+ // Ignore samples that are for a different thread.
+ if (threadId != aThreadId) {
+ continue;
+ }
+
+ while (e.Has() && !e.Get().IsThreadId()) {
+ if (e.Get().IsJitReturnAddr()) {
+ aJITAddressConsumer(e.Get().u.mPtr);
+ }
+ e.Next();
+ }
+ }
+ });
+}
+
// This method returns true if it wrote anything to the writer.
bool
ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter,
int aThreadId,
const TimeStamp& aProcessStartTime,
double aSinceTime,
UniqueStacks& aUniqueStacks) const
{
--- a/tools/profiler/core/ProfileBufferEntry.h
+++ b/tools/profiler/core/ProfileBufferEntry.h
@@ -21,16 +21,17 @@
#include "nsDataHashtable.h"
#include "mozilla/Maybe.h"
#include "mozilla/Vector.h"
#include "gtest/MozGtestFriend.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/UniquePtr.h"
#include "nsClassHashtable.h"
#include "mozilla/Variant.h"
+#include "nsTArray.h"
class ProfilerMarker;
#define FOR_EACH_PROFILE_BUFFER_ENTRY_KIND(macro) \
macro(Category, int) \
macro(CollectionStart, double) \
macro(CollectionEnd, double) \
macro(Label, const char*) \
@@ -137,16 +138,86 @@ public:
uint32_t GetOrAddIndex(const char* aStr);
private:
SpliceableChunkedJSONWriter mStringTableWriter;
nsDataHashtable<nsCStringHashKey, uint32_t> mStringToIndexMap;
};
+// Contains all the information about JIT frames that is needed to stream stack
+// frames for JitReturnAddr entries in the profiler buffer.
+// Every return address (void*) is mapped to one or more JITFrameKeys, and
+// every JITFrameKey is mapped to a JSON string for that frame.
+// mRangeStart and mRangeEnd describe the range in the buffer for which this
+// mapping is valid. Only JitReturnAddr entries within that buffer range can be
+// processed using this JITFrameInfoForBufferRange object.
+struct JITFrameInfoForBufferRange final
+{
+ JITFrameInfoForBufferRange Clone() const;
+
+ uint64_t mRangeStart;
+ uint64_t mRangeEnd; // mRangeEnd marks the first invalid index.
+
+ struct JITFrameKey
+ {
+ uint32_t Hash() const;
+ bool operator==(const JITFrameKey& aOther) const;
+ bool operator!=(const JITFrameKey& aOther) const { return !(*this == aOther); }
+
+ void* mCanonicalAddress;
+ uint32_t mDepth;
+ };
+ nsClassHashtable<nsPtrHashKey<void>, nsTArray<JITFrameKey>> mJITAddressToJITFramesMap;
+ nsClassHashtable<nsGenericHashKey<JITFrameKey>, nsCString> mJITFrameToFrameJSONMap;
+};
+
+// Contains JITFrameInfoForBufferRange objects for multiple profiler buffer ranges.
+struct JITFrameInfo final
+{
+ JITFrameInfo()
+ : mUniqueStrings(mozilla::MakeUnique<UniqueJSONStrings>())
+ {}
+
+ MOZ_IMPLICIT JITFrameInfo(const JITFrameInfo& aOther);
+
+ // Creates a new JITFrameInfoForBufferRange object in mRanges by looking up
+ // information about the provided JIT return addresses using aCx.
+ // Addresses are provided like this:
+ // The caller of AddInfoForRange supplies a function in aJITAddressProvider.
+ // This function will be called once, synchronously, with an
+ // aJITAddressConsumer argument, which is a function that needs to be called
+ // for every address. That function can be called multiple times for the same
+ // address.
+ void AddInfoForRange(uint64_t aRangeStart, uint64_t aRangeEnd, JSContext* aCx,
+ const std::function<void(const std::function<void(void*)>&)>& aJITAddressProvider);
+
+ // Returns whether the information stored in this object is still relevant
+ // for any entries in the buffer.
+ bool HasExpired(uint64_t aCurrentBufferRangeStart) const
+ {
+ if (mRanges.IsEmpty()) {
+ // No information means no relevant information. Allow this object to be
+ // discarded.
+ return true;
+ }
+ return mRanges.LastElement().mRangeEnd <= aCurrentBufferRangeStart;
+ }
+
+ // The array of ranges of JIT frame information, sorted by buffer position.
+ // Ranges are non-overlapping.
+ // The JSON of the cached frames can contain string indexes, which refer
+ // to strings in mUniqueStrings.
+ nsTArray<JITFrameInfoForBufferRange> mRanges;
+
+ // The string table which contains strings used in the frame JSON that's
+ // cached in mRanges.
+ mozilla::UniquePtr<UniqueJSONStrings> mUniqueStrings;
+};
+
class UniqueStacks
{
public:
// We de-duplicate information about JIT frames based on the return address
// of the frame. However, if the same UniqueStacks object is used to stream
// profiler buffer contents more than once, then in the time between the two
// stream attempts ("streaming generations"), JIT code can have been freed
// and reallocated in the same areas of memory. Consequently, during the next
--- a/tools/profiler/tasktracer/GeckoTaskTracer.cpp
+++ b/tools/profiler/tasktracer/GeckoTaskTracer.cpp
@@ -2,16 +2,17 @@
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "GeckoTaskTracer.h"
#include "GeckoTaskTracerImpl.h"
+#include "mozilla/DebugOnly.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "nsString.h"