Bug 1429904 - Add JITFrameInfo. r=njn
authorMarkus Stange <mstange@themasta.com>
Wed, 28 Feb 2018 00:13:51 -0500
changeset 458687 3284f277d5323ab2ea5775588b9e37d5558655f1
parent 458686 b323ee89d4c7cc81c5d7b7a4d1e3292f074f976d
child 458688 0d15c7f905365c952ec5c42ef7e0f4315276ec8a
push id8808
push userarchaeopteryx@coole-files.de
push dateFri, 02 Mar 2018 22:13:05 +0000
treeherdermozilla-beta@7475508d19db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1429904
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1429904 - Add JITFrameInfo. r=njn MozReview-Commit-ID: DashxIKyzYZ
tools/profiler/core/ProfileBuffer.h
tools/profiler/core/ProfileBufferEntry.cpp
tools/profiler/core/ProfileBufferEntry.h
tools/profiler/tasktracer/GeckoTaskTracer.cpp
--- 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"