summaryrefslogtreecommitdiffstats
path: root/mozglue/baseprofiler/public/ProfileBufferControlledChunkManager.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mozglue/baseprofiler/public/ProfileBufferControlledChunkManager.h203
1 files changed, 203 insertions, 0 deletions
diff --git a/mozglue/baseprofiler/public/ProfileBufferControlledChunkManager.h b/mozglue/baseprofiler/public/ProfileBufferControlledChunkManager.h
new file mode 100644
index 0000000000..45b39b163c
--- /dev/null
+++ b/mozglue/baseprofiler/public/ProfileBufferControlledChunkManager.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef ProfileBufferControlledChunkManager_h
+#define ProfileBufferControlledChunkManager_h
+
+#include "mozilla/ProfileBufferChunk.h"
+
+#include <functional>
+#include <vector>
+
+namespace mozilla {
+
+// A "Controlled" chunk manager will provide updates about chunks that it
+// creates, releases, and destroys; and it can destroy released chunks as
+// requested.
+class ProfileBufferControlledChunkManager {
+ public:
+ using Length = ProfileBufferChunk::Length;
+
+ virtual ~ProfileBufferControlledChunkManager() = default;
+
+ // Minimum amount of chunk metadata to be transferred between processes.
+ struct ChunkMetadata {
+ // Timestamp when chunk was marked "done", which is used to:
+ // - determine its age, so the oldest one will be destroyed first,
+ // - uniquely identify this chunk in this process. (The parent process is
+ // responsible for associating this timestamp to its process id.)
+ TimeStamp mDoneTimeStamp;
+ // Size of this chunk's buffer.
+ Length mBufferBytes;
+
+ ChunkMetadata(TimeStamp aDoneTimeStamp, Length aBufferBytes)
+ : mDoneTimeStamp(aDoneTimeStamp), mBufferBytes(aBufferBytes) {}
+ };
+
+ // Class collecting all information necessary to describe updates that
+ // happened in a chunk manager.
+ // An update can be folded into a previous update.
+ class Update {
+ public:
+ // Construct a "not-an-Update" object, which should only be used after a
+ // real update is folded into it.
+ Update() = default;
+
+ // Construct a "final" Update, which marks the end of all updates from a
+ // chunk manager.
+ explicit Update(decltype(nullptr)) : mUnreleasedBytes(FINAL) {}
+
+ // Construct an Update from the given data and released chunks.
+ // The chunk pointers may be null, and it doesn't matter if
+ // `aNewlyReleasedChunks` is already linked to `aExistingReleasedChunks` or
+ // not.
+ Update(size_t aUnreleasedBytes, size_t aReleasedBytes,
+ const ProfileBufferChunk* aExistingReleasedChunks,
+ const ProfileBufferChunk* aNewlyReleasedChunks)
+ : mUnreleasedBytes(aUnreleasedBytes),
+ mReleasedBytes(aReleasedBytes),
+ mOldestDoneTimeStamp(
+ aExistingReleasedChunks
+ ? aExistingReleasedChunks->ChunkHeader().mDoneTimeStamp
+ : TimeStamp{}) {
+ MOZ_RELEASE_ASSERT(
+ !IsNotUpdate(),
+ "Empty update should only be constructed with default constructor");
+ MOZ_RELEASE_ASSERT(
+ !IsFinal(),
+ "Final update should only be constructed with nullptr constructor");
+ for (const ProfileBufferChunk* chunk = aNewlyReleasedChunks; chunk;
+ chunk = chunk->GetNext()) {
+ mNewlyReleasedChunks.emplace_back(ChunkMetadata{
+ chunk->ChunkHeader().mDoneTimeStamp, chunk->BufferBytes()});
+ }
+ }
+
+ // Construct an Update from raw data.
+ // This may be used to re-construct an Update that was previously
+ // serialized.
+ Update(size_t aUnreleasedBytes, size_t aReleasedBytes,
+ TimeStamp aOldestDoneTimeStamp,
+ std::vector<ChunkMetadata>&& aNewlyReleasedChunks)
+ : mUnreleasedBytes(aUnreleasedBytes),
+ mReleasedBytes(aReleasedBytes),
+ mOldestDoneTimeStamp(aOldestDoneTimeStamp),
+ mNewlyReleasedChunks(std::move(aNewlyReleasedChunks)) {}
+
+ // Clear the Update completely and return it to a "not-an-Update" state.
+ void Clear() {
+ mUnreleasedBytes = NO_UPDATE;
+ mReleasedBytes = 0;
+ mOldestDoneTimeStamp = TimeStamp{};
+ mNewlyReleasedChunks.clear();
+ }
+
+ bool IsNotUpdate() const { return mUnreleasedBytes == NO_UPDATE; }
+
+ bool IsFinal() const { return mUnreleasedBytes == FINAL; }
+
+ size_t UnreleasedBytes() const {
+ MOZ_RELEASE_ASSERT(!IsNotUpdate(),
+ "Cannot access UnreleasedBytes from empty update");
+ MOZ_RELEASE_ASSERT(!IsFinal(),
+ "Cannot access UnreleasedBytes from final update");
+ return mUnreleasedBytes;
+ }
+
+ size_t ReleasedBytes() const {
+ MOZ_RELEASE_ASSERT(!IsNotUpdate(),
+ "Cannot access ReleasedBytes from empty update");
+ MOZ_RELEASE_ASSERT(!IsFinal(),
+ "Cannot access ReleasedBytes from final update");
+ return mReleasedBytes;
+ }
+
+ TimeStamp OldestDoneTimeStamp() const {
+ MOZ_RELEASE_ASSERT(!IsNotUpdate(),
+ "Cannot access OldestDoneTimeStamp from empty update");
+ MOZ_RELEASE_ASSERT(!IsFinal(),
+ "Cannot access OldestDoneTimeStamp from final update");
+ return mOldestDoneTimeStamp;
+ }
+
+ const std::vector<ChunkMetadata>& NewlyReleasedChunksRef() const {
+ MOZ_RELEASE_ASSERT(
+ !IsNotUpdate(),
+ "Cannot access NewlyReleasedChunksRef from empty update");
+ MOZ_RELEASE_ASSERT(
+ !IsFinal(), "Cannot access NewlyReleasedChunksRef from final update");
+ return mNewlyReleasedChunks;
+ }
+
+ // Fold a later update into this one.
+ void Fold(Update&& aNewUpdate) {
+ MOZ_ASSERT(
+ !IsFinal() || aNewUpdate.IsFinal(),
+ "There shouldn't be another non-final update after the final update");
+
+ if (IsNotUpdate() || aNewUpdate.IsFinal()) {
+ // We were empty, or the new update is the final update, we just switch
+ // to that new update.
+ *this = std::move(aNewUpdate);
+ return;
+ }
+
+ mUnreleasedBytes = aNewUpdate.mUnreleasedBytes;
+ mReleasedBytes = aNewUpdate.mReleasedBytes;
+ if (!aNewUpdate.mOldestDoneTimeStamp.IsNull()) {
+ MOZ_ASSERT(mOldestDoneTimeStamp.IsNull() ||
+ mOldestDoneTimeStamp <= aNewUpdate.mOldestDoneTimeStamp);
+ mOldestDoneTimeStamp = aNewUpdate.mOldestDoneTimeStamp;
+ auto it = mNewlyReleasedChunks.begin();
+ while (it != mNewlyReleasedChunks.end() &&
+ it->mDoneTimeStamp < mOldestDoneTimeStamp) {
+ it = mNewlyReleasedChunks.erase(it);
+ }
+ }
+ if (!aNewUpdate.mNewlyReleasedChunks.empty()) {
+ mNewlyReleasedChunks.reserve(mNewlyReleasedChunks.size() +
+ aNewUpdate.mNewlyReleasedChunks.size());
+ mNewlyReleasedChunks.insert(mNewlyReleasedChunks.end(),
+ aNewUpdate.mNewlyReleasedChunks.begin(),
+ aNewUpdate.mNewlyReleasedChunks.end());
+ }
+ }
+
+ private:
+ static const size_t NO_UPDATE = size_t(-1);
+ static const size_t FINAL = size_t(-2);
+
+ size_t mUnreleasedBytes = NO_UPDATE;
+ size_t mReleasedBytes = 0;
+ TimeStamp mOldestDoneTimeStamp;
+ std::vector<ChunkMetadata> mNewlyReleasedChunks;
+ };
+
+ using UpdateCallback = std::function<void(Update&&)>;
+
+ // This *may* be set (or reset) by an object that needs to know about all
+ // chunk updates that happen in this manager. The main use will be to
+ // coordinate the global memory usage of Firefox.
+ // If a non-empty callback is given, it will be immediately invoked with the
+ // current state.
+ // When the callback is about to be destroyed (by overwriting it here, or in
+ // the class destructor), it will be invoked one last time with an empty
+ // update.
+ // Note that the callback (even the first current-state callback) will be
+ // invoked from inside a locked scope, so it should *not* call other functions
+ // of the chunk manager. A side benefit of this locking is that it guarantees
+ // that no two invocations can overlap.
+ virtual void SetUpdateCallback(UpdateCallback&& aUpdateCallback) = 0;
+
+ // This is a request to destroy all chunks before the given timestamp.
+ // This timestamp should be one that was given in a previous UpdateCallback
+ // call. Obviously, only released chunks can be destroyed.
+ virtual void DestroyChunksAtOrBefore(TimeStamp aDoneTimeStamp) = 0;
+};
+
+} // namespace mozilla
+
+#endif // ProfileBufferControlledChunkManager_h