mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 04:09:50 +00:00
Bug 1632750 - Make ProfileBufferChunkManagerWithLocalLimit a ProfileBufferControlledChunkManager - r=canaltinova
Differential Revision: https://phabricator.services.mozilla.com/D72363
This commit is contained in:
parent
2cf1bef331
commit
3292ac76d9
@ -10,6 +10,7 @@
|
||||
#include "BaseProfiler.h"
|
||||
#include "mozilla/BaseProfilerDetail.h"
|
||||
#include "mozilla/ProfileBufferChunkManager.h"
|
||||
#include "mozilla/ProfileBufferControlledChunkManager.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -27,7 +28,8 @@ namespace mozilla {
|
||||
// assuming that the user is doing the right thing and releasing chunks ASAP,
|
||||
// and that the memory limit is reasonably large.
|
||||
class ProfileBufferChunkManagerWithLocalLimit final
|
||||
: public ProfileBufferChunkManager {
|
||||
: public ProfileBufferChunkManager,
|
||||
public ProfileBufferControlledChunkManager {
|
||||
public:
|
||||
using Length = ProfileBufferChunk::Length;
|
||||
|
||||
@ -39,6 +41,13 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
: mMaxTotalBytes(aMaxTotalBytes),
|
||||
mChunkMinBufferBytes(aChunkMinBufferBytes) {}
|
||||
|
||||
~ProfileBufferChunkManagerWithLocalLimit() {
|
||||
if (mUpdateCallback) {
|
||||
// Signal the end of this callback.
|
||||
std::move(mUpdateCallback)(Update(nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t MaxTotalSize() const final {
|
||||
// `mMaxTotalBytes` is `const` so there is no need to lock the mutex.
|
||||
return mMaxTotalBytes;
|
||||
@ -94,11 +103,20 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
void ReleaseChunks(UniquePtr<ProfileBufferChunk> aChunks) final {
|
||||
baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mUser, "Not registered yet");
|
||||
// Keep a pointer to the first newly-released chunk, so we can use it to
|
||||
// prepare an update (after `aChunks` is moved-from).
|
||||
const ProfileBufferChunk* const newlyReleasedChunks = aChunks.get();
|
||||
// Compute the size of all provided chunks.
|
||||
size_t bytes = 0;
|
||||
for (const ProfileBufferChunk* chunk = aChunks.get(); chunk;
|
||||
for (const ProfileBufferChunk* chunk = newlyReleasedChunks; chunk;
|
||||
chunk = chunk->GetNext()) {
|
||||
bytes += chunk->BufferBytes();
|
||||
MOZ_ASSERT(!chunk->ChunkHeader().mDoneTimeStamp.IsNull(),
|
||||
"All released chunks should have a 'Done' timestamp");
|
||||
MOZ_ASSERT(
|
||||
!chunk->GetNext() || (chunk->ChunkHeader().mDoneTimeStamp <
|
||||
chunk->GetNext()->ChunkHeader().mDoneTimeStamp),
|
||||
"Released chunk groups must have increasing timestamps");
|
||||
}
|
||||
// Transfer the chunks size from the unreleased bucket to the released one.
|
||||
mUnreleasedBufferBytes -= bytes;
|
||||
@ -110,9 +128,17 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
} else {
|
||||
// Add to the end of the released chunks list (oldest first, most recent
|
||||
// last.)
|
||||
MOZ_ASSERT(mReleasedChunks->Last()->ChunkHeader().mDoneTimeStamp <
|
||||
aChunks->ChunkHeader().mDoneTimeStamp,
|
||||
"Chunks must be released in increasing timestamps");
|
||||
mReleasedBufferBytes += bytes;
|
||||
mReleasedChunks->SetLast(std::move(aChunks));
|
||||
}
|
||||
|
||||
if (mUpdateCallback) {
|
||||
mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
|
||||
mReleasedChunks.get(), newlyReleasedChunks));
|
||||
}
|
||||
}
|
||||
|
||||
void SetChunkDestroyedCallback(
|
||||
@ -127,6 +153,9 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mUser, "Not registered yet");
|
||||
mReleasedBufferBytes = 0;
|
||||
if (mUpdateCallback) {
|
||||
mUpdateCallback(Update(mUnreleasedBufferBytes, 0, nullptr, nullptr));
|
||||
}
|
||||
return std::move(mReleasedChunks);
|
||||
}
|
||||
|
||||
@ -134,6 +163,10 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mUser, "Not registered yet");
|
||||
mUnreleasedBufferBytes = 0;
|
||||
if (mUpdateCallback) {
|
||||
mUpdateCallback(
|
||||
Update(0, mReleasedBufferBytes, mReleasedChunks.get(), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t SizeOfExcludingThis(
|
||||
@ -149,6 +182,44 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf, lock);
|
||||
}
|
||||
|
||||
void SetUpdateCallback(UpdateCallback&& aUpdateCallback) final {
|
||||
baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
|
||||
if (mUpdateCallback) {
|
||||
// Signal the end of the previous callback.
|
||||
std::move(mUpdateCallback)(Update(nullptr));
|
||||
}
|
||||
mUpdateCallback = std::move(aUpdateCallback);
|
||||
if (mUpdateCallback) {
|
||||
mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
|
||||
mReleasedChunks.get(), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyChunksAtOrBefore(TimeStamp aDoneTimeStamp) final {
|
||||
MOZ_ASSERT(!aDoneTimeStamp.IsNull());
|
||||
baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
|
||||
for (;;) {
|
||||
if (!mReleasedChunks) {
|
||||
// We don't own any released chunks (anymore), we're done.
|
||||
break;
|
||||
}
|
||||
if (mReleasedChunks->ChunkHeader().mDoneTimeStamp > aDoneTimeStamp) {
|
||||
// The current chunk is strictly after the given timestamp, we're done.
|
||||
break;
|
||||
}
|
||||
// We've found a chunk at or before the timestamp. Move the released chunk
|
||||
// pointer to the next one.
|
||||
UniquePtr<ProfileBufferChunk> oldest =
|
||||
std::exchange(mReleasedChunks, mReleasedChunks->ReleaseNext());
|
||||
mReleasedBufferBytes -= oldest->ChunkBytes();
|
||||
if (mChunkDestroyedCallback) {
|
||||
// Inform the user that we're going to destroy this chunk.
|
||||
mChunkDestroyedCallback(*oldest);
|
||||
}
|
||||
// Stop using `oldest`, this will effectively destroy it.
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
const ProfileBufferChunk* PeekExtantReleasedChunksAndLock() final {
|
||||
mMutex.Lock();
|
||||
@ -205,6 +276,11 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
if (chunk) {
|
||||
// We do have a chunk (recycled or new), record its size as "unreleased".
|
||||
mUnreleasedBufferBytes += chunk->BufferBytes();
|
||||
|
||||
if (mUpdateCallback) {
|
||||
mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
|
||||
mReleasedChunks.get(), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
@ -251,6 +327,8 @@ class ProfileBufferChunkManagerWithLocalLimit final
|
||||
// Callback set from `RequestChunk()`, until it is serviced in
|
||||
// `FulfillChunkRequests()`. There can only be one request in flight.
|
||||
std::function<void(UniquePtr<ProfileBufferChunk>)> mChunkReceiver;
|
||||
|
||||
UpdateCallback mUpdateCallback;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -1088,6 +1088,213 @@ static void TestControlledChunkManagerUpdate() {
|
||||
printf("TestControlledChunkManagerUpdate done\n");
|
||||
}
|
||||
|
||||
static void TestControlledChunkManagerWithLocalLimit() {
|
||||
printf("TestControlledChunkManagerWithLocalLimit...\n");
|
||||
|
||||
// Construct a ProfileBufferChunkManagerWithLocalLimit with chunk of minimum
|
||||
// size >=100, up to 1000 bytes.
|
||||
constexpr ProfileBufferChunk::Length MaxTotalBytes = 1000;
|
||||
constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 100;
|
||||
ProfileBufferChunkManagerWithLocalLimit cmll{MaxTotalBytes,
|
||||
ChunkMinBufferBytes};
|
||||
|
||||
// Reference to chunk manager base class.
|
||||
ProfileBufferChunkManager& cm = cmll;
|
||||
|
||||
// Reference to controlled chunk manager base class.
|
||||
ProfileBufferControlledChunkManager& ccm = cmll;
|
||||
|
||||
#ifdef DEBUG
|
||||
const char* chunkManagerRegisterer =
|
||||
"TestControlledChunkManagerWithLocalLimit";
|
||||
cm.RegisteredWith(chunkManagerRegisterer);
|
||||
#endif // DEBUG
|
||||
|
||||
MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == MaxTotalBytes,
|
||||
"Max total size should be exactly as given");
|
||||
|
||||
unsigned destroyedChunks = 0;
|
||||
unsigned destroyedBytes = 0;
|
||||
cm.SetChunkDestroyedCallback([&](const ProfileBufferChunk& aChunks) {
|
||||
for (const ProfileBufferChunk* chunk = &aChunks; chunk;
|
||||
chunk = chunk->GetNext()) {
|
||||
destroyedChunks += 1;
|
||||
destroyedBytes += chunk->BufferBytes();
|
||||
}
|
||||
});
|
||||
|
||||
using Update = ProfileBufferControlledChunkManager::Update;
|
||||
unsigned updateCount = 0;
|
||||
ProfileBufferControlledChunkManager::Update update;
|
||||
MOZ_RELEASE_ASSERT(update.IsNotUpdate());
|
||||
auto updateCallback = [&](Update&& aUpdate) {
|
||||
++updateCount;
|
||||
update.Fold(std::move(aUpdate));
|
||||
};
|
||||
ccm.SetUpdateCallback(updateCallback);
|
||||
MOZ_RELEASE_ASSERT(updateCount == 1,
|
||||
"SetUpdateCallback should have triggered an update");
|
||||
MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {})));
|
||||
updateCount = 0;
|
||||
update.Clear();
|
||||
|
||||
UniquePtr<ProfileBufferChunk> extantReleasedChunks =
|
||||
cm.GetExtantReleasedChunks();
|
||||
MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
|
||||
MOZ_RELEASE_ASSERT(updateCount == 1,
|
||||
"GetExtantReleasedChunks should have triggered an update");
|
||||
MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {})));
|
||||
updateCount = 0;
|
||||
update.Clear();
|
||||
|
||||
// First request.
|
||||
UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk();
|
||||
MOZ_RELEASE_ASSERT(!!chunk,
|
||||
"First chunk immediate request should always work");
|
||||
const auto chunkActualBufferBytes = chunk->BufferBytes();
|
||||
// Keep address, for later checks.
|
||||
const uintptr_t chunk1Address = reinterpret_cast<uintptr_t>(chunk.get());
|
||||
MOZ_RELEASE_ASSERT(updateCount == 1,
|
||||
"GetChunk should have triggered an update");
|
||||
MOZ_RELEASE_ASSERT(
|
||||
IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {})));
|
||||
updateCount = 0;
|
||||
update.Clear();
|
||||
|
||||
extantReleasedChunks = cm.GetExtantReleasedChunks();
|
||||
MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
|
||||
MOZ_RELEASE_ASSERT(updateCount == 1,
|
||||
"GetExtantReleasedChunks should have triggered an update");
|
||||
MOZ_RELEASE_ASSERT(
|
||||
IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {})));
|
||||
updateCount = 0;
|
||||
update.Clear();
|
||||
|
||||
// For this test, we need to be able to get at least 2 chunks without hitting
|
||||
// the limit. (If this failed, it wouldn't necessary be a problem with
|
||||
// ProfileBufferChunkManagerWithLocalLimit, fiddle with constants at the top
|
||||
// of this test.)
|
||||
MOZ_RELEASE_ASSERT(chunkActualBufferBytes < 2 * MaxTotalBytes);
|
||||
|
||||
ProfileBufferChunk::Length previousUnreleasedBytes = chunk->BufferBytes();
|
||||
ProfileBufferChunk::Length previousReleasedBytes = 0;
|
||||
TimeStamp previousOldestDoneTimeStamp;
|
||||
|
||||
unsigned chunk1ReuseCount = 0;
|
||||
|
||||
// We will do enough loops to go through the maximum size a number of times.
|
||||
const unsigned Rollovers = 3;
|
||||
const unsigned Loops = Rollovers * MaxTotalBytes / chunkActualBufferBytes;
|
||||
for (unsigned i = 0; i < Loops; ++i) {
|
||||
// Add some data to the chunk.
|
||||
const ProfileBufferIndex index =
|
||||
ProfileBufferIndex(chunkActualBufferBytes) * i + 1;
|
||||
chunk->SetRangeStart(index);
|
||||
Unused << chunk->ReserveInitialBlockAsTail(1);
|
||||
Unused << chunk->ReserveBlock(2);
|
||||
|
||||
// Request a new chunk.
|
||||
UniquePtr<ProfileBufferChunk> newChunk;
|
||||
cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
|
||||
newChunk = std::move(aChunk);
|
||||
});
|
||||
MOZ_RELEASE_ASSERT(updateCount == 0,
|
||||
"RequestChunk() shouldn't have triggered an update");
|
||||
cm.FulfillChunkRequests();
|
||||
MOZ_RELEASE_ASSERT(!!newChunk, "Chunk request should always work");
|
||||
MOZ_RELEASE_ASSERT(newChunk->BufferBytes() == chunkActualBufferBytes,
|
||||
"Unexpected chunk size");
|
||||
MOZ_RELEASE_ASSERT(!newChunk->GetNext(), "There should only be one chunk");
|
||||
|
||||
MOZ_RELEASE_ASSERT(updateCount == 1,
|
||||
"FulfillChunkRequests() after a request should have "
|
||||
"triggered an update");
|
||||
MOZ_RELEASE_ASSERT(!update.IsFinal());
|
||||
MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
|
||||
MOZ_RELEASE_ASSERT(update.UnreleasedBytes() ==
|
||||
previousUnreleasedBytes + newChunk->BufferBytes());
|
||||
previousUnreleasedBytes = update.UnreleasedBytes();
|
||||
MOZ_RELEASE_ASSERT(update.ReleasedBytes() <= previousReleasedBytes);
|
||||
previousReleasedBytes = update.ReleasedBytes();
|
||||
MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() ||
|
||||
update.OldestDoneTimeStamp() >=
|
||||
previousOldestDoneTimeStamp);
|
||||
previousOldestDoneTimeStamp = update.OldestDoneTimeStamp();
|
||||
MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty());
|
||||
updateCount = 0;
|
||||
update.Clear();
|
||||
|
||||
// Make sure the "Done" timestamp below cannot be the same as from the
|
||||
// previous loop.
|
||||
const TimeStamp now = TimeStamp::NowUnfuzzed();
|
||||
while (TimeStamp::NowUnfuzzed() == now) {
|
||||
::SleepMilli(1);
|
||||
}
|
||||
|
||||
// Mark previous chunk done and release it.
|
||||
chunk->MarkDone();
|
||||
const auto doneTimeStamp = chunk->ChunkHeader().mDoneTimeStamp;
|
||||
const auto bufferBytes = chunk->BufferBytes();
|
||||
cm.ReleaseChunks(std::move(chunk));
|
||||
|
||||
MOZ_RELEASE_ASSERT(updateCount == 1,
|
||||
"ReleaseChunks() should have triggered an update");
|
||||
MOZ_RELEASE_ASSERT(!update.IsFinal());
|
||||
MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
|
||||
MOZ_RELEASE_ASSERT(update.UnreleasedBytes() ==
|
||||
previousUnreleasedBytes - bufferBytes);
|
||||
previousUnreleasedBytes = update.UnreleasedBytes();
|
||||
MOZ_RELEASE_ASSERT(update.ReleasedBytes() ==
|
||||
previousReleasedBytes + bufferBytes);
|
||||
previousReleasedBytes = update.ReleasedBytes();
|
||||
MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() ||
|
||||
update.OldestDoneTimeStamp() >=
|
||||
previousOldestDoneTimeStamp);
|
||||
previousOldestDoneTimeStamp = update.OldestDoneTimeStamp();
|
||||
MOZ_RELEASE_ASSERT(update.OldestDoneTimeStamp() <= doneTimeStamp);
|
||||
MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().size() == 1);
|
||||
MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mDoneTimeStamp ==
|
||||
doneTimeStamp);
|
||||
MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mBufferBytes ==
|
||||
bufferBytes);
|
||||
updateCount = 0;
|
||||
update.Clear();
|
||||
|
||||
// And cycle to the new chunk.
|
||||
chunk = std::move(newChunk);
|
||||
|
||||
if (reinterpret_cast<uintptr_t>(chunk.get()) == chunk1Address) {
|
||||
++chunk1ReuseCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Enough testing! Clean-up.
|
||||
Unused << chunk->ReserveInitialBlockAsTail(0);
|
||||
chunk->MarkDone();
|
||||
cm.ForgetUnreleasedChunks();
|
||||
MOZ_RELEASE_ASSERT(
|
||||
updateCount == 1,
|
||||
"ForgetUnreleasedChunks() should have triggered an update");
|
||||
MOZ_RELEASE_ASSERT(!update.IsFinal());
|
||||
MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
|
||||
MOZ_RELEASE_ASSERT(update.UnreleasedBytes() == 0);
|
||||
MOZ_RELEASE_ASSERT(update.ReleasedBytes() == previousReleasedBytes);
|
||||
MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty() == 1);
|
||||
updateCount = 0;
|
||||
update.Clear();
|
||||
|
||||
ccm.SetUpdateCallback({});
|
||||
MOZ_RELEASE_ASSERT(updateCount == 1,
|
||||
"SetUpdateCallback({}) should have triggered an update");
|
||||
MOZ_RELEASE_ASSERT(update.IsFinal());
|
||||
|
||||
#ifdef DEBUG
|
||||
cm.DeregisteredFrom(chunkManagerRegisterer);
|
||||
#endif // DEBUG
|
||||
|
||||
printf("TestControlledChunkManagerWithLocalLimit done\n");
|
||||
}
|
||||
|
||||
static void TestChunkedBuffer() {
|
||||
printf("TestChunkedBuffer...\n");
|
||||
|
||||
@ -2781,6 +2988,7 @@ void TestProfilerDependencies() {
|
||||
TestChunkManagerSingle();
|
||||
TestChunkManagerWithLocalLimit();
|
||||
TestControlledChunkManagerUpdate();
|
||||
TestControlledChunkManagerWithLocalLimit();
|
||||
TestChunkedBuffer();
|
||||
TestChunkedBufferSingle();
|
||||
TestModuloBuffer();
|
||||
|
Loading…
x
Reference in New Issue
Block a user