mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-11 16:32:59 +00:00
Bug 1575141 - BlocksRingBuffer::{S,Des}erializer<BlocksRingBuffer> - r=gregtatum
Backtraces (that are kept in some marker payloads) are stored in a small ProfileBuffer, we will need to store that data, which will happen to be inside a BlockRingBuffer, so BlockRingBuffer needs to be able to (de)serialize itself! This is done by storing the contents in the active buffer range, and some extra data, to later reconstruct a BlocksRingBuffer that looks like the original. Depends on D42496 Differential Revision: https://phabricator.services.mozilla.com/D42634 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
87c8e35964
commit
f908211695
@ -160,8 +160,8 @@ class BlocksRingBuffer {
|
||||
}
|
||||
|
||||
private:
|
||||
// Only BlocksRingBuffer internal functions can convert between `BlockIndex`
|
||||
// and `Index`.
|
||||
// Only BlocksRingBuffer internal functions and serializers can convert
|
||||
// between `BlockIndex` and `Index`.
|
||||
friend class BlocksRingBuffer;
|
||||
explicit BlockIndex(Index aBlockIndex) : mBlockIndex(aBlockIndex) {}
|
||||
explicit operator Index() const { return mBlockIndex; }
|
||||
@ -1071,6 +1071,12 @@ class BlocksRingBuffer {
|
||||
mMaybeUnderlyingBuffer.reset();
|
||||
}
|
||||
|
||||
// Used to de/serialize a BlocksRingBuffer (e.g., containing a backtrace).
|
||||
friend struct Serializer<BlocksRingBuffer>;
|
||||
friend struct Deserializer<BlocksRingBuffer>;
|
||||
friend struct Serializer<UniquePtr<BlocksRingBuffer>>;
|
||||
friend struct Deserializer<UniquePtr<BlocksRingBuffer>>;
|
||||
|
||||
// Mutex guarding the following members.
|
||||
mutable baseprofiler::detail::BaseProfilerMutex mMutex;
|
||||
|
||||
@ -1843,6 +1849,172 @@ struct BlocksRingBuffer::Deserializer<Variant<Ts...>> {
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// BlocksRingBuffer
|
||||
|
||||
// A BlocksRingBuffer can hide another one!
|
||||
// This will be used to store marker backtraces; They can be read back into a
|
||||
// UniquePtr<BlocksRingBuffer>.
|
||||
// Format: len (ULEB128) | start | end | buffer (len bytes) | pushed | cleared
|
||||
// len==0 marks an out-of-session buffer, or empty buffer.
|
||||
template <>
|
||||
struct BlocksRingBuffer::Serializer<BlocksRingBuffer> {
|
||||
static Length Bytes(const BlocksRingBuffer& aBuffer) {
|
||||
baseprofiler::detail::BaseProfilerAutoLock lock(aBuffer.mMutex);
|
||||
if (aBuffer.mMaybeUnderlyingBuffer.isNothing()) {
|
||||
// Out-of-session, we only need 1 byte to store a length of 0.
|
||||
return ULEB128Size<Length>(0);
|
||||
}
|
||||
const auto start = Index(aBuffer.mFirstReadIndex);
|
||||
const auto end = Index(aBuffer.mNextWriteIndex);
|
||||
const auto len = end - start;
|
||||
if (len == 0) {
|
||||
// In-session but empty, also store a length of 0.
|
||||
return ULEB128Size<Length>(0);
|
||||
}
|
||||
return ULEB128Size(len) + sizeof(start) + sizeof(end) + len +
|
||||
sizeof(aBuffer.mMaybeUnderlyingBuffer->mPushedBlockCount) +
|
||||
sizeof(aBuffer.mMaybeUnderlyingBuffer->mClearedBlockCount);
|
||||
}
|
||||
|
||||
static void Write(EntryWriter& aEW, const BlocksRingBuffer& aBuffer) {
|
||||
baseprofiler::detail::BaseProfilerAutoLock lock(aBuffer.mMutex);
|
||||
if (aBuffer.mMaybeUnderlyingBuffer.isNothing()) {
|
||||
// Out-of-session, only store a length of 0.
|
||||
aEW.WriteULEB128<Length>(0);
|
||||
return;
|
||||
}
|
||||
const auto start = Index(aBuffer.mFirstReadIndex);
|
||||
const auto end = Index(aBuffer.mNextWriteIndex);
|
||||
MOZ_ASSERT(end - start <= std::numeric_limits<Length>::max());
|
||||
const auto len = static_cast<Length>(end - start);
|
||||
if (len == 0) {
|
||||
// In-session but empty, only store a length of 0.
|
||||
aEW.WriteULEB128<Length>(0);
|
||||
return;
|
||||
}
|
||||
// In-session.
|
||||
// Store buffer length, start and end indices.
|
||||
aEW.WriteULEB128<Length>(len);
|
||||
aEW.WriteObject(start);
|
||||
aEW.WriteObject(end);
|
||||
// Write all the bytes. TODO: Optimize with memcpy's?
|
||||
const auto readerEnd =
|
||||
aBuffer.mMaybeUnderlyingBuffer->mBuffer.ReaderAt(end);
|
||||
for (auto reader = aBuffer.mMaybeUnderlyingBuffer->mBuffer.ReaderAt(start);
|
||||
reader != readerEnd; ++reader) {
|
||||
aEW.WriteObject(*reader);
|
||||
}
|
||||
// And write stats.
|
||||
aEW.WriteObject(aBuffer.mMaybeUnderlyingBuffer->mPushedBlockCount);
|
||||
aEW.WriteObject(aBuffer.mMaybeUnderlyingBuffer->mClearedBlockCount);
|
||||
}
|
||||
};
|
||||
|
||||
// A serialized BlocksRingBuffer can be read into an empty buffer (either
|
||||
// out-of-session, or in-session with enough room).
|
||||
template <>
|
||||
struct BlocksRingBuffer::Deserializer<BlocksRingBuffer> {
|
||||
static void ReadInto(EntryReader& aER, BlocksRingBuffer& aBuffer) {
|
||||
// Expect an empty buffer, as we're going to overwrite it.
|
||||
MOZ_ASSERT(aBuffer.GetState().mRangeStart == aBuffer.GetState().mRangeEnd);
|
||||
// Read the stored buffer length.
|
||||
const auto len = aER.ReadULEB128<BlocksRingBuffer::Length>();
|
||||
if (len == 0) {
|
||||
// 0-length means an "uninteresting" buffer, just return now.
|
||||
return;
|
||||
}
|
||||
// We have a non-empty buffer to read.
|
||||
if (aBuffer.BufferLength().isSome()) {
|
||||
// Output buffer is in-session (i.e., it already has a memory buffer
|
||||
// attached). Make sure the caller allocated enough space.
|
||||
MOZ_RELEASE_ASSERT(aBuffer.BufferLength()->Value() >= len);
|
||||
} else {
|
||||
// Output buffer is out-of-session, attach a new memory buffer.
|
||||
aBuffer.Set(PowerOfTwo<BlocksRingBuffer::Length>(len));
|
||||
MOZ_ASSERT(aBuffer.BufferLength()->Value() >= len);
|
||||
}
|
||||
// Read start and end indices.
|
||||
const auto start = aER.ReadObject<BlocksRingBuffer::Index>();
|
||||
aBuffer.mFirstReadIndex = BlocksRingBuffer::BlockIndex(start);
|
||||
const auto end = aER.ReadObject<BlocksRingBuffer::Index>();
|
||||
aBuffer.mNextWriteIndex = BlocksRingBuffer::BlockIndex(end);
|
||||
MOZ_ASSERT(end - start == len);
|
||||
// Copy bytes into the buffer.
|
||||
const auto writerEnd =
|
||||
aBuffer.mMaybeUnderlyingBuffer->mBuffer.WriterAt(end);
|
||||
for (auto writer = aBuffer.mMaybeUnderlyingBuffer->mBuffer.WriterAt(start);
|
||||
writer != writerEnd; ++writer, ++aER) {
|
||||
*writer = *aER;
|
||||
}
|
||||
// Finally copy stats.
|
||||
aBuffer.mMaybeUnderlyingBuffer->mPushedBlockCount = aER.ReadObject<decltype(
|
||||
aBuffer.mMaybeUnderlyingBuffer->mPushedBlockCount)>();
|
||||
aBuffer.mMaybeUnderlyingBuffer->mClearedBlockCount =
|
||||
aER.ReadObject<decltype(
|
||||
aBuffer.mMaybeUnderlyingBuffer->mClearedBlockCount)>();
|
||||
}
|
||||
|
||||
// We cannot output a BlocksRingBuffer object (not copyable), use `ReadInto()`
|
||||
// or `aER.ReadObject<UniquePtr<BlocksRinbBuffer>>()` instead.
|
||||
static BlocksRingBuffer Read(BlocksRingBuffer::EntryReader& aER) = delete;
|
||||
};
|
||||
|
||||
// A BlocksRingBuffer is usually refererenced through a UniquePtr, for
|
||||
// convenience we support (de)serializing that UniquePtr directly.
|
||||
// This is compatible with the non-UniquePtr serialization above, with a null
|
||||
// pointer being treated like an out-of-session or empty buffer; and any of
|
||||
// these would be deserialized into a null pointer.
|
||||
template <>
|
||||
struct BlocksRingBuffer::Serializer<UniquePtr<BlocksRingBuffer>> {
|
||||
static Length Bytes(const UniquePtr<BlocksRingBuffer>& aBufferUPtr) {
|
||||
if (!aBufferUPtr) {
|
||||
// Null pointer, treat it like an empty buffer, i.e., write length of 0.
|
||||
return ULEB128Size<Length>(0);
|
||||
}
|
||||
// Otherwise write the pointed-at BlocksRingBuffer (which could be
|
||||
// out-of-session or empty.)
|
||||
return SumBytes(*aBufferUPtr);
|
||||
}
|
||||
|
||||
static void Write(EntryWriter& aEW,
|
||||
const UniquePtr<BlocksRingBuffer>& aBufferUPtr) {
|
||||
if (!aBufferUPtr) {
|
||||
// Null pointer, treat it like an empty buffer, i.e., write length of 0.
|
||||
aEW.WriteULEB128<Length>(0);
|
||||
return;
|
||||
}
|
||||
// Otherwise write the pointed-at BlocksRingBuffer (which could be
|
||||
// out-of-session or empty.)
|
||||
aEW.WriteObject(*aBufferUPtr);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct BlocksRingBuffer::Deserializer<UniquePtr<BlocksRingBuffer>> {
|
||||
static void ReadInto(EntryReader& aER, UniquePtr<BlocksRingBuffer>& aBuffer) {
|
||||
aBuffer = Read(aER);
|
||||
}
|
||||
|
||||
static UniquePtr<BlocksRingBuffer> Read(BlocksRingBuffer::EntryReader& aER) {
|
||||
UniquePtr<BlocksRingBuffer> bufferUPtr;
|
||||
// Read the stored buffer length.
|
||||
const auto len = aER.ReadULEB128<BlocksRingBuffer::Length>();
|
||||
if (len == 0) {
|
||||
// 0-length means an "uninteresting" buffer, just return nullptr.
|
||||
return bufferUPtr;
|
||||
}
|
||||
// We have a non-empty buffer.
|
||||
// allocate an empty BlocksRingBuffer.
|
||||
bufferUPtr = MakeUnique<BlocksRingBuffer>();
|
||||
// Rewind the reader before the length and deserialize the contents, using
|
||||
// the non-UniquePtr Deserializer.
|
||||
aER -= ULEB128Size(len);
|
||||
aER.ReadIntoObject(*bufferUPtr);
|
||||
return bufferUPtr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // BlocksRingBuffer_h
|
||||
|
@ -1271,6 +1271,61 @@ void TestBlocksRingBufferSerialization() {
|
||||
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v2);
|
||||
});
|
||||
|
||||
// 2nd BlocksRingBuffer to contain the 1st one. It has be be more than twice
|
||||
// the size.
|
||||
constexpr uint32_t MBSize2 = MBSize * 4;
|
||||
uint8_t buffer2[MBSize2 * 3];
|
||||
for (size_t i = 0; i < MBSize2 * 3; ++i) {
|
||||
buffer2[i] = uint8_t('B' + i);
|
||||
}
|
||||
BlocksRingBuffer rb2(&buffer2[MBSize2], MakePowerOfTwo32<MBSize2>());
|
||||
rb2.PutObject(rb);
|
||||
|
||||
// 3rd BlocksRingBuffer deserialized from the 2nd one.
|
||||
uint8_t buffer3[MBSize * 3];
|
||||
for (size_t i = 0; i < MBSize * 3; ++i) {
|
||||
buffer3[i] = uint8_t('C' + i);
|
||||
}
|
||||
BlocksRingBuffer rb3(&buffer3[MBSize], MakePowerOfTwo32<MBSize>());
|
||||
rb2.ReadEach(
|
||||
[&](BlocksRingBuffer::EntryReader& aER) { aER.ReadIntoObject(rb3); });
|
||||
|
||||
// And a 4th heap-allocated one.
|
||||
UniquePtr<BlocksRingBuffer> rb4up;
|
||||
rb2.ReadEach([&](BlocksRingBuffer::EntryReader& aER) {
|
||||
rb4up = aER.ReadObject<UniquePtr<BlocksRingBuffer>>();
|
||||
});
|
||||
MOZ_RELEASE_ASSERT(!!rb4up);
|
||||
|
||||
// Clear 1st and 2nd BlocksRingBuffers, to ensure we have made a deep copy
|
||||
// into the 3rd&4th ones.
|
||||
rb.Clear();
|
||||
rb2.Clear();
|
||||
|
||||
// And now the 3rd one should have the same contents as the 1st one had.
|
||||
rb3.ReadEach([&](BlocksRingBuffer::EntryReader& aER) {
|
||||
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v0);
|
||||
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v1);
|
||||
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v2);
|
||||
});
|
||||
|
||||
// And 4th.
|
||||
rb4up->ReadEach([&](BlocksRingBuffer::EntryReader& aER) {
|
||||
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v0);
|
||||
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v1);
|
||||
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v2);
|
||||
});
|
||||
|
||||
// In fact, the 3rd and 4th ones should have the same state, because they were
|
||||
// created the same way.
|
||||
MOZ_RELEASE_ASSERT(rb3.GetState().mRangeStart ==
|
||||
rb4up->GetState().mRangeStart);
|
||||
MOZ_RELEASE_ASSERT(rb3.GetState().mRangeEnd == rb4up->GetState().mRangeEnd);
|
||||
MOZ_RELEASE_ASSERT(rb3.GetState().mPushedBlockCount ==
|
||||
rb4up->GetState().mPushedBlockCount);
|
||||
MOZ_RELEASE_ASSERT(rb3.GetState().mClearedBlockCount ==
|
||||
rb4up->GetState().mClearedBlockCount);
|
||||
|
||||
// Check that only the provided stack-based sub-buffer was modified.
|
||||
uint32_t changed = 0;
|
||||
for (size_t i = MBSize; i < MBSize * 2; ++i) {
|
||||
@ -1279,7 +1334,7 @@ void TestBlocksRingBufferSerialization() {
|
||||
// Expect at least 75% changes.
|
||||
MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
|
||||
|
||||
// Everything around the sub-buffer should be unchanged.
|
||||
// Everything around the sub-buffers should be unchanged.
|
||||
for (size_t i = 0; i < MBSize; ++i) {
|
||||
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
|
||||
}
|
||||
@ -1287,6 +1342,20 @@ void TestBlocksRingBufferSerialization() {
|
||||
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < MBSize2; ++i) {
|
||||
MOZ_RELEASE_ASSERT(buffer2[i] == uint8_t('B' + i));
|
||||
}
|
||||
for (size_t i = MBSize2 * 2; i < MBSize2 * 3; ++i) {
|
||||
MOZ_RELEASE_ASSERT(buffer2[i] == uint8_t('B' + i));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < MBSize; ++i) {
|
||||
MOZ_RELEASE_ASSERT(buffer3[i] == uint8_t('C' + i));
|
||||
}
|
||||
for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
|
||||
MOZ_RELEASE_ASSERT(buffer3[i] == uint8_t('C' + i));
|
||||
}
|
||||
|
||||
printf("TestBlocksRingBufferSerialization done\n");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user