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:
Gerald Squelart 2019-08-20 21:33:52 +00:00
parent 87c8e35964
commit f908211695
2 changed files with 244 additions and 3 deletions

View File

@ -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

View File

@ -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");
}