Bug 1429904 - Add JITFrameInfo. r?njn draft
authorMarkus Stange <mstange@themasta.com>
Wed, 28 Feb 2018 00:13:51 -0500
changeset 762109 c6b5aa2d7edd7d667d7a4563578ef7614a6d58e8
parent 762108 053e5491c5b0abd9a4f62982ddbc2ff6b17f085f
child 762110 3408bf0d74379d3554f353e4501e616930b100b0
push id101088
push userbmo:mstange@themasta.com
push dateThu, 01 Mar 2018 20:52:26 +0000
reviewersnjn
bugs1429904
milestone60.0a1
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"