Bug 1476757 - Add methods to change the capacity of the ProfileBuffer. r=njn
authorMarkus Stange <mstange@themasta.com>
Fri, 12 Oct 2018 13:39:47 +0000
changeset 490763 ef82ba4b7f22f673870bc71ad4564bf0ef228b06
parent 490762 1924a400dfa461fc3ec3e47fd290838d78ed38a1
child 490764 3428510869a9b347639d3f79506d7225be92c191
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersnjn
bugs1476757
milestone64.0a1
Bug 1476757 - Add methods to change the capacity of the ProfileBuffer. r=njn Depends on D8218 Differential Revision: https://phabricator.services.mozilla.com/D6264
tools/profiler/core/ProfileBuffer.cpp
tools/profiler/core/ProfileBuffer.h
--- a/tools/profiler/core/ProfileBuffer.cpp
+++ b/tools/profiler/core/ProfileBuffer.cpp
@@ -11,38 +11,128 @@
 #include "ProfilerMarker.h"
 #include "jsfriendapi.h"
 #include "nsScriptSecurityManager.h"
 #include "nsJSPrincipals.h"
 
 using namespace mozilla;
 
 ProfileBuffer::ProfileBuffer(uint32_t aCapacity)
-  : mEntryIndexMask(0)
+  : mEntries(nullptr)
+  , mEntryIndexMask(0)
   , mRangeStart(0)
   , mRangeEnd(0)
   , mCapacity(0)
 {
-  // Round aCapacity up to the nearest power of two, so that we can index
-  // mEntries with a simple mask and don't need to do a slow modulo operation.
-  const uint32_t UINT32_MAX_POWER_OF_TWO = 1 << 31;
-  MOZ_RELEASE_ASSERT(aCapacity <= UINT32_MAX_POWER_OF_TWO,
-                     "aCapacity is larger than what we support");
-  mCapacity = RoundUpPow2(aCapacity);
-  mEntryIndexMask = mCapacity - 1;
-  mEntries = MakeUnique<ProfileBufferEntry[]>(mCapacity);
+  bool succeeded = SetMinCapacity(aCapacity);
+  MOZ_RELEASE_ASSERT(succeeded, "Couldn't allocate initial ProfileBuffer storage");
 }
 
 ProfileBuffer::~ProfileBuffer()
 {
   while (mStoredMarkers.peek()) {
     delete mStoredMarkers.popHead();
   }
 }
 
+bool
+ProfileBuffer::SetMinCapacity(uint32_t aMinCapacity)
+{
+  // Round aMinCapacity up to the nearest power of two, so that we can index
+  // mEntries with a simple mask and don't need to do a slow modulo operation.
+  const uint32_t UINT32_MAX_POWER_OF_TWO = 1 << 31;
+  MOZ_RELEASE_ASSERT(aMinCapacity <= UINT32_MAX_POWER_OF_TWO,
+                     "aMinCapacity is larger than what we support");
+  return SetCapacityPow2(RoundUpPow2(aMinCapacity));
+}
+
+static uint64_t
+RoundDownToMultipleOfPow2(uint64_t aNumber, uint64_t aMultiplier)
+{
+  return aNumber & ~(aMultiplier - 1);
+}
+
+bool
+ProfileBuffer::SetCapacityPow2(uint32_t aNewCapacity)
+{
+  MOZ_RELEASE_ASSERT(aNewCapacity != 0, "can't set ProfileBuffer capacity to zero");
+  MOZ_RELEASE_ASSERT(IsPowerOfTwo(aNewCapacity), "aNewCapacity needs to be a power of two");
+
+  if (aNewCapacity == mCapacity) {
+    return true;
+  }
+
+  MOZ_RELEASE_ASSERT(Length() <= aNewCapacity, "can't make the capacity smaller than the used size");
+
+  auto newEntries = MakeUniqueFallible<ProfileBufferEntry[]>(aNewCapacity);
+  if (!newEntries) {
+    return false;
+  }
+
+  uint32_t newIndexMask = aNewCapacity - 1;
+
+  if (mCapacity != 0 && mRangeStart != mRangeEnd) {
+    // Copy existing data from mEntries into newEntries. Make sure that every
+    // entry preserves its position in buffer space.
+    // If the range wraps around in the old or in the new buffer, we need to
+    // copy the data in two chunks: [start, wrapIndex), [wrapIndex, end)
+    // If the range wraps in both the old and the new buffer, the wrap index
+    // will be the same in both buffers.
+    //
+    // If range doesn't wrap around:
+    //
+    //           +- wrapIndex
+    //           |+- mRangeStart
+    //           ||   +- mRangeEnd
+    //           vv   v
+    // ...-+-----+-----+-----+-----+-----+-----+-----+-----+-...
+    //     |     |[---]|     |     |     |     |     |     |
+    // ...-+-----+-----+-----+-----+-----+-----+-----+-----+-...
+    //     |      [---]            |                       |
+    // ...-+-----+-----+-----+-----+-----+-----+-----+-----+-...
+    //     ^^^^^^^ smaller capacity
+    //     ^^^^^^^^^^^^^^^^^^^^^^^^^ larger capacity
+    //
+    // If range wraps around:
+    //
+    //                         +- mRangeStart
+    //                         |   +- wrapIndex
+    //                         |   | +- mRangeEnd
+    //                         v   v v
+    // ...-+-----+-----+-----+-----+-----+-----+-----+-----+-...
+    //     |     |     |     | [---+-]   |     |     |     |
+    // ...-+-----+-----+-----+-----+-----+-----+-----+-----+-...
+    //     |                   [---+-]                     |
+    // ...-+-----+-----+-----+-----+-----+-----+-----+-----+-...
+    //     ^^^^^^^ smaller capacity
+    //     ^^^^^^^^^^^^^^^^^^^^^^^^^ larger capacity
+    uint64_t wrapIndex =
+      RoundDownToMultipleOfPow2(mRangeEnd, std::min(aNewCapacity, mCapacity));
+    if (wrapIndex <= mRangeStart) {
+      // There is no wrapping. Copy the entire range as one chunk.
+      PodCopy(&newEntries[mRangeStart & newIndexMask],
+              &mEntries[mRangeStart & mEntryIndexMask],
+              mRangeEnd - mRangeStart);
+    } else {
+      // Copy the range in two separate chunks.
+      PodCopy(&newEntries[mRangeStart & newIndexMask],
+              &mEntries[mRangeStart & mEntryIndexMask],
+              wrapIndex - mRangeStart);
+      PodCopy(&newEntries[wrapIndex & newIndexMask],
+              &mEntries[wrapIndex & mEntryIndexMask],
+              mRangeEnd - wrapIndex);
+    }
+  }
+
+  mCapacity = aNewCapacity;
+  mEntryIndexMask = newIndexMask;
+  mEntries = std::move(newEntries);
+  return true;
+}
+
 // Called from signal, call only reentrant functions
 void
 ProfileBuffer::AddEntry(const ProfileBufferEntry& aEntry)
 {
   GetEntry(mRangeEnd++) = aEntry;
 
   // The distance between mRangeStart and mRangeEnd must never exceed
   // mCapacity, so advance mRangeStart if necessary.
--- a/tools/profiler/core/ProfileBuffer.h
+++ b/tools/profiler/core/ProfileBuffer.h
@@ -8,19 +8,20 @@
 
 #include "platform.h"
 #include "ProfileBufferEntry.h"
 #include "ProfilerMarker.h"
 #include "ProfileJSONWriter.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/RefCounted.h"
 
-// A fixed-capacity circular buffer.
-// This class is used as a queue of entries which, after construction, never
-// allocates. This makes it safe to use in the profiler's "critical section".
+// A resizeable circular buffer.
+// This class is used as a queue of entries which, outside of construction and
+// calls to SetCapacity, never allocates. This makes it safe to use in the
+// profiler's "critical section".
 // Entries are appended at the end. Once the queue capacity has been reached,
 // adding a new entry will evict an old entry from the start of the queue.
 // Positions in the queue are represented as 64-bit unsigned integers which
 // only increase and never wrap around.
 // mRangeStart and mRangeEnd describe the range in that uint64_t space which is
 // covered by the queue contents.
 // Internally, the buffer uses a fixed-size storage and applies a modulo
 // operation when accessing entries in that storage buffer. "Evicting" an entry
@@ -31,16 +32,29 @@ class ProfileBuffer final
 public:
   // ProfileBuffer constructor
   // @param aCapacity The minimum capacity of the buffer. The actual buffer
   //                   capacity will be rounded up to the next power of two.
   explicit ProfileBuffer(uint32_t aCapacity);
 
   ~ProfileBuffer();
 
+  uint32_t Length() { return mRangeEnd - mRangeStart; }
+
+  // Set the buffer capacity to at least aMinCapacity. aMinCapacity must not be
+  // zero and at least Length(). Otherwise, it triggers abort. This method
+  // allocates. The allocation is fallible and the return value indicates success.
+  bool SetMinCapacity(uint32_t aMinCapacity);
+
+  // Set the buffer capacity to exactly aNewCapacity. aNewCapacity must be a
+  // power of two, non-zero, and at least Length(). Otherwise, it triggers abort.
+  // This method allocates. The allocation is fallible and the return value
+  // indicates success.
+  bool SetCapacityPow2(uint32_t aNewCapacity);
+
   // Add |aEntry| to the buffer, ignoring what kind of entry it is.
   void AddEntry(const ProfileBufferEntry& aEntry);
 
   // Add to the buffer a sample start (ThreadId) entry for aThreadId.
   // Returns the position of the entry.
   uint64_t AddThreadIdEntry(int aThreadId);
 
   void CollectCodeLocation(