Bug 1695120: Remove unused BlocksRingBuffer r=profiler-reviewers,canaltinova

Differential Revision: https://phabricator.services.mozilla.com/D194428
This commit is contained in:
Neel Chauhan 2023-12-06 14:08:02 +00:00
parent 911a588250
commit ff6cf2d0d7
5 changed files with 1 additions and 1942 deletions

View File

@ -2299,7 +2299,7 @@ void SamplerThread::Run() {
// Not *no*-stack-sampling means we do want stack sampling.
const bool stackSampling = !ProfilerFeature::HasNoStackSampling(features);
// Use local BlocksRingBuffer&ProfileBuffer to capture the stack.
// Use local ProfileBuffer to capture the stack.
// (This is to avoid touching the CorePS::CoreBuffer lock while
// a thread is suspended, because that thread could be working with
// the CorePS::CoreBuffer as well.)

View File

@ -92,7 +92,6 @@ EXPORTS.mozilla += [
"public/BaseProfilerRAIIMacro.h",
"public/BaseProfilerState.h",
"public/BaseProfilerUtils.h",
"public/BlocksRingBuffer.h",
"public/FailureLatch.h",
"public/leb128iterator.h",
"public/ModuloBuffer.h",

View File

@ -1,999 +0,0 @@
/* -*- 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 BlocksRingBuffer_h
#define BlocksRingBuffer_h
#include "mozilla/BaseProfilerDetail.h"
#include "mozilla/ModuloBuffer.h"
#include "mozilla/ProfileBufferIndex.h"
#include "mozilla/ScopeExit.h"
#include <functional>
#include <string>
#include <tuple>
#include <utility>
namespace mozilla {
// Thread-safe Ring buffer that can store blocks of different sizes during
// defined sessions.
// Each *block* contains an *entry* and the entry size:
// [ entry_size | entry ] [ entry_size | entry ] ...
// *In-session* is a period of time during which `BlocksRingBuffer` allows
// reading and writing. *Out-of-session*, the `BlocksRingBuffer` object is
// still valid, but contains no data, and gracefully denies accesses.
//
// To write an entry, the buffer reserves a block of sufficient size (to contain
// user data of predetermined size), writes the entry size, and lets the caller
// fill the entry contents using ModuloBuffer::Iterator APIs and a few entry-
// specific APIs. E.g.:
// ```
// BlockRingsBuffer brb(PowerOfTwo<BlockRingsBuffer::Length>(1024));
// brb.ReserveAndPut([]() { return sizeof(123); },
// [&](ProfileBufferEntryWriter& aEW) {
// aEW.WriteObject(123);
// });
// ```
// Other `Put...` functions may be used as shortcuts for simple entries.
// The objects given to the caller's callbacks should only be used inside the
// callbacks and not stored elsewhere, because they keep their own references to
// the BlocksRingBuffer and therefore should not live longer.
// Different type of objects may be serialized into an entry, see `Serializer`
// for more information.
//
// When reading data, the buffer iterates over blocks (it knows how to read the
// entry size, and therefore move to the next block), and lets the caller read
// the entry inside of each block. E.g.:
// ```
// brb.Read([](BlocksRingBuffer::Reader aR) {}
// for (ProfileBufferEntryReader aER : aR) {
// /* Use ProfileBufferEntryReader functions to read serialized objects. */
// int n = aER.ReadObject<int>();
// }
// });
// ```
// Different type of objects may be deserialized from an entry, see
// `Deserializer` for more information.
//
// The caller may retrieve the `ProfileBufferBlockIndex` corresponding to an
// entry (`ProfileBufferBlockIndex` is an opaque type preventing the user from
// modifying it). That index may later be used to get back to that particular
// entry if it still exists.
class BlocksRingBuffer {
public:
// Using ModuloBuffer as underlying circular byte buffer.
using Buffer = ModuloBuffer<uint32_t, ProfileBufferIndex>;
using Byte = Buffer::Byte;
// Length type for total buffer (as PowerOfTwo<Length>) and each entry.
using Length = uint32_t;
enum class ThreadSafety { WithoutMutex, WithMutex };
// Default constructor starts out-of-session (nothing to read or write).
explicit BlocksRingBuffer(ThreadSafety aThreadSafety)
: mMutex(aThreadSafety != ThreadSafety::WithoutMutex) {}
// Create a buffer of the given length.
explicit BlocksRingBuffer(ThreadSafety aThreadSafety,
PowerOfTwo<Length> aLength)
: mMutex(aThreadSafety != ThreadSafety::WithoutMutex),
mMaybeUnderlyingBuffer(Some(UnderlyingBuffer(aLength))) {}
// Take ownership of an existing buffer.
BlocksRingBuffer(ThreadSafety aThreadSafety,
UniquePtr<Buffer::Byte[]> aExistingBuffer,
PowerOfTwo<Length> aLength)
: mMutex(aThreadSafety != ThreadSafety::WithoutMutex),
mMaybeUnderlyingBuffer(
Some(UnderlyingBuffer(std::move(aExistingBuffer), aLength))) {}
// Use an externally-owned buffer.
BlocksRingBuffer(ThreadSafety aThreadSafety, Buffer::Byte* aExternalBuffer,
PowerOfTwo<Length> aLength)
: mMutex(aThreadSafety != ThreadSafety::WithoutMutex),
mMaybeUnderlyingBuffer(
Some(UnderlyingBuffer(aExternalBuffer, aLength))) {}
// Destructor doesn't need to do anything special. (Clearing entries would
// only update indices and stats, which won't be accessible after the object
// is destroyed anyway.)
~BlocksRingBuffer() = default;
// Remove underlying buffer, if any.
void Reset() {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
ResetUnderlyingBuffer();
}
// Create a buffer of the given length.
void Set(PowerOfTwo<Length> aLength) {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
ResetUnderlyingBuffer();
mMaybeUnderlyingBuffer.emplace(aLength);
}
// Take ownership of an existing buffer.
void Set(UniquePtr<Buffer::Byte[]> aExistingBuffer,
PowerOfTwo<Length> aLength) {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
ResetUnderlyingBuffer();
mMaybeUnderlyingBuffer.emplace(std::move(aExistingBuffer), aLength);
}
// Use an externally-owned buffer.
void Set(Buffer::Byte* aExternalBuffer, PowerOfTwo<Length> aLength) {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
ResetUnderlyingBuffer();
mMaybeUnderlyingBuffer.emplace(aExternalBuffer, aLength);
}
// This cannot change during the lifetime of this buffer, so there's no need
// to lock.
bool IsThreadSafe() const { return mMutex.IsActivated(); }
[[nodiscard]] bool IsInSession() const {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
return !!mMaybeUnderlyingBuffer;
}
// Lock the buffer mutex and run the provided callback.
// This can be useful when the caller needs to explicitly lock down this
// buffer, but not do anything else with it.
template <typename Callback>
auto LockAndRun(Callback&& aCallback) const {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
return std::forward<Callback>(aCallback)();
}
// Buffer length in bytes.
Maybe<PowerOfTwo<Length>> BufferLength() const {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
return mMaybeUnderlyingBuffer.map([](const UnderlyingBuffer& aBuffer) {
return aBuffer.mBuffer.BufferLength();
});
;
}
// Size of external resources.
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
if (!mMaybeUnderlyingBuffer) {
return 0;
}
return mMaybeUnderlyingBuffer->mBuffer.SizeOfExcludingThis(aMallocSizeOf);
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
// Snapshot of the buffer state.
struct State {
// Index to the first block.
ProfileBufferBlockIndex mRangeStart;
// Index past the last block. Equals mRangeStart if empty.
ProfileBufferBlockIndex mRangeEnd;
// Number of blocks that have been pushed into this buffer.
uint64_t mPushedBlockCount = 0;
// Number of blocks that have been removed from this buffer.
// Note: Live entries = pushed - cleared.
uint64_t mClearedBlockCount = 0;
};
// Get a snapshot of the current state.
// When out-of-session, mFirstReadIndex==mNextWriteIndex, and
// mPushedBlockCount==mClearedBlockCount==0.
// Note that these may change right after this thread-safe call, so they
// should only be used for statistical purposes.
State GetState() const {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
return {
mFirstReadIndex, mNextWriteIndex,
mMaybeUnderlyingBuffer ? mMaybeUnderlyingBuffer->mPushedBlockCount : 0,
mMaybeUnderlyingBuffer ? mMaybeUnderlyingBuffer->mClearedBlockCount
: 0};
}
class Reader;
// Class that can iterate through blocks and provide
// `ProfileBufferEntryReader`s.
// Created through `Reader`, lives within a lock guard lifetime.
class BlockIterator {
public:
#ifdef DEBUG
~BlockIterator() {
// No BlockIterator should live outside of a mutexed call.
mRing->mMutex.AssertCurrentThreadOwns();
}
#endif // DEBUG
// Comparison with other iterator, mostly used in range-for loops.
bool operator==(const BlockIterator aRhs) const {
MOZ_ASSERT(mRing == aRhs.mRing);
return mBlockIndex == aRhs.mBlockIndex;
}
bool operator!=(const BlockIterator aRhs) const {
MOZ_ASSERT(mRing == aRhs.mRing);
return mBlockIndex != aRhs.mBlockIndex;
}
// Advance to next BlockIterator.
BlockIterator& operator++() {
mBlockIndex = NextBlockIndex();
return *this;
}
// Dereferencing creates a `ProfileBufferEntryReader` for the entry inside
// this block.
ProfileBufferEntryReader operator*() const {
return mRing->ReaderInBlockAt(mBlockIndex);
}
// True if this iterator is just past the last entry.
bool IsAtEnd() const {
MOZ_ASSERT(mBlockIndex <= BufferRangeEnd());
return mBlockIndex == BufferRangeEnd();
}
// Can be used as reference to come back to this entry with `ReadAt()`.
ProfileBufferBlockIndex CurrentBlockIndex() const { return mBlockIndex; }
// Index past the end of this block, which is the start of the next block.
ProfileBufferBlockIndex NextBlockIndex() const {
MOZ_ASSERT(!IsAtEnd());
const Length entrySize =
mRing->ReaderInBlockAt(mBlockIndex).RemainingBytes();
return ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
mBlockIndex.ConvertToProfileBufferIndex() + ULEB128Size(entrySize) +
entrySize);
}
// Index of the first block in the whole buffer.
ProfileBufferBlockIndex BufferRangeStart() const {
return mRing->mFirstReadIndex;
}
// Index past the last block in the whole buffer.
ProfileBufferBlockIndex BufferRangeEnd() const {
return mRing->mNextWriteIndex;
}
private:
// Only a Reader can instantiate a BlockIterator.
friend class Reader;
BlockIterator(const BlocksRingBuffer& aRing,
ProfileBufferBlockIndex aBlockIndex)
: mRing(WrapNotNull(&aRing)), mBlockIndex(aBlockIndex) {
// No BlockIterator should live outside of a mutexed call.
mRing->mMutex.AssertCurrentThreadOwns();
}
// Using a non-null pointer instead of a reference, to allow copying.
// This BlockIterator should only live inside one of the thread-safe
// BlocksRingBuffer functions, for this reference to stay valid.
NotNull<const BlocksRingBuffer*> mRing;
ProfileBufferBlockIndex mBlockIndex;
};
// Class that can create `BlockIterator`s (e.g., for range-for), or just
// iterate through entries; lives within a lock guard lifetime.
class MOZ_RAII Reader {
public:
Reader(const Reader&) = delete;
Reader& operator=(const Reader&) = delete;
Reader(Reader&&) = delete;
Reader& operator=(Reader&&) = delete;
#ifdef DEBUG
~Reader() {
// No Reader should live outside of a mutexed call.
mRing.mMutex.AssertCurrentThreadOwns();
}
#endif // DEBUG
// Index of the first block in the whole buffer.
ProfileBufferBlockIndex BufferRangeStart() const {
return mRing.mFirstReadIndex;
}
// Index past the last block in the whole buffer.
ProfileBufferBlockIndex BufferRangeEnd() const {
return mRing.mNextWriteIndex;
}
// Iterators to the first and past-the-last blocks.
// Compatible with range-for (see `ForEach` below as example).
BlockIterator begin() const {
return BlockIterator(mRing, BufferRangeStart());
}
// Note that a `BlockIterator` at the `end()` should not be dereferenced, as
// there is no actual block there!
BlockIterator end() const { return BlockIterator(mRing, BufferRangeEnd()); }
// Get a `BlockIterator` at the given `ProfileBufferBlockIndex`, clamped to
// the stored range. Note that a `BlockIterator` at the `end()` should not
// be dereferenced, as there is no actual block there!
BlockIterator At(ProfileBufferBlockIndex aBlockIndex) const {
if (aBlockIndex < BufferRangeStart()) {
// Anything before the range (including null ProfileBufferBlockIndex) is
// clamped at the beginning.
return begin();
}
// Otherwise we at least expect the index to be valid (pointing exactly at
// a live block, or just past the end.)
mRing.AssertBlockIndexIsValidOrEnd(aBlockIndex);
return BlockIterator(mRing, aBlockIndex);
}
// Run `aCallback(ProfileBufferEntryReader&)` on each entry from first to
// last. Callback should not store `ProfileBufferEntryReader`, as it may
// become invalid after this thread-safe call.
template <typename Callback>
void ForEach(Callback&& aCallback) const {
for (ProfileBufferEntryReader reader : *this) {
aCallback(reader);
}
}
private:
friend class BlocksRingBuffer;
explicit Reader(const BlocksRingBuffer& aRing) : mRing(aRing) {
// No Reader should live outside of a mutexed call.
mRing.mMutex.AssertCurrentThreadOwns();
}
// This Reader should only live inside one of the thread-safe
// BlocksRingBuffer functions, for this reference to stay valid.
const BlocksRingBuffer& mRing;
};
// Call `aCallback(BlocksRingBuffer::Reader*)` (nullptr when out-of-session),
// and return whatever `aCallback` returns. Callback should not store
// `Reader`, because it may become invalid after this call.
template <typename Callback>
auto Read(Callback&& aCallback) const {
{
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
if (MOZ_LIKELY(mMaybeUnderlyingBuffer)) {
Reader reader(*this);
return std::forward<Callback>(aCallback)(&reader);
}
}
return std::forward<Callback>(aCallback)(nullptr);
}
// Call `aCallback(ProfileBufferEntryReader&)` on each item.
// Callback should not store `ProfileBufferEntryReader`, because it may become
// invalid after this call.
template <typename Callback>
void ReadEach(Callback&& aCallback) const {
Read([&](Reader* aReader) {
if (MOZ_LIKELY(aReader)) {
aReader->ForEach(aCallback);
}
});
}
// Call `aCallback(Maybe<ProfileBufferEntryReader>&&)` on the entry at
// the given ProfileBufferBlockIndex; The `Maybe` will be `Nothing` if
// out-of-session, or if that entry doesn't exist anymore, or if we've reached
// just past the last entry. Return whatever `aCallback` returns. Callback
// should not store `ProfileBufferEntryReader`, because it may become invalid
// after this call.
template <typename Callback>
auto ReadAt(ProfileBufferBlockIndex aBlockIndex, Callback&& aCallback) const {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
MOZ_ASSERT(aBlockIndex <= mNextWriteIndex);
Maybe<ProfileBufferEntryReader> maybeEntryReader;
if (MOZ_LIKELY(mMaybeUnderlyingBuffer) && aBlockIndex >= mFirstReadIndex &&
aBlockIndex < mNextWriteIndex) {
AssertBlockIndexIsValid(aBlockIndex);
maybeEntryReader.emplace(ReaderInBlockAt(aBlockIndex));
}
return std::forward<Callback>(aCallback)(std::move(maybeEntryReader));
}
// Main function to write entries.
// Reserve `aCallbackBytes()` bytes, call `aCallback()` with a pointer to an
// on-stack temporary ProfileBufferEntryWriter (nullptr when out-of-session),
// and return whatever `aCallback` returns. Callback should not store
// `ProfileBufferEntryWriter`, because it may become invalid after this
// thread-safe call. Note: `aCallbackBytes` is a callback instead of a simple
// value, to delay this potentially-expensive computation until after we're
// checked that we're in-session; use `Put(Length, Callback)` below if you
// know the size already.
template <typename CallbackBytes, typename Callback>
auto ReserveAndPut(CallbackBytes aCallbackBytes, Callback&& aCallback) {
Maybe<ProfileBufferEntryWriter> maybeEntryWriter;
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
if (MOZ_LIKELY(mMaybeUnderlyingBuffer)) {
const Length entryBytes = std::forward<CallbackBytes>(aCallbackBytes)();
MOZ_RELEASE_ASSERT(entryBytes > 0);
const Length bufferBytes =
mMaybeUnderlyingBuffer->mBuffer.BufferLength().Value();
MOZ_RELEASE_ASSERT(entryBytes <= bufferBytes - ULEB128Size(entryBytes),
"Entry would wrap and overwrite itself");
// Compute block size from the requested entry size.
const Length blockBytes = ULEB128Size(entryBytes) + entryBytes;
// We will put this new block at the end of the current buffer.
const ProfileBufferIndex blockIndex =
mNextWriteIndex.ConvertToProfileBufferIndex();
// Compute the end of this new block.
const ProfileBufferIndex blockEnd = blockIndex + blockBytes;
while (blockEnd >
mFirstReadIndex.ConvertToProfileBufferIndex() + bufferBytes) {
// About to trample on an old block.
ProfileBufferEntryReader reader = ReaderInBlockAt(mFirstReadIndex);
mMaybeUnderlyingBuffer->mClearedBlockCount += 1;
// Move the buffer reading start past this cleared block.
mFirstReadIndex = ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
mFirstReadIndex.ConvertToProfileBufferIndex() +
ULEB128Size(reader.RemainingBytes()) + reader.RemainingBytes());
}
// Store the new end of buffer.
mNextWriteIndex =
ProfileBufferBlockIndex::CreateFromProfileBufferIndex(blockEnd);
mMaybeUnderlyingBuffer->mPushedBlockCount += 1;
// Finally, let aCallback write into the entry.
mMaybeUnderlyingBuffer->mBuffer.EntryWriterFromTo(maybeEntryWriter,
blockIndex, blockEnd);
MOZ_ASSERT(maybeEntryWriter.isSome(),
"Non-empty entry should always create an EntryWriter");
maybeEntryWriter->WriteULEB128(entryBytes);
MOZ_ASSERT(maybeEntryWriter->RemainingBytes() == entryBytes);
}
#ifdef DEBUG
auto checkAllWritten = MakeScopeExit([&]() {
MOZ_ASSERT(!maybeEntryWriter || maybeEntryWriter->RemainingBytes() == 0);
});
#endif // DEBUG
return std::forward<Callback>(aCallback)(maybeEntryWriter);
}
// Add a new entry of known size, call `aCallback` with a pointer to a
// temporary ProfileBufferEntryWriter (can be null when out-of-session), and
// return whatever `aCallback` returns. Callback should not store the
// `ProfileBufferEntryWriter`, as it may become invalid after this thread-safe
// call.
template <typename Callback>
auto Put(Length aBytes, Callback&& aCallback) {
return ReserveAndPut([aBytes]() { return aBytes; },
std::forward<Callback>(aCallback));
}
// Add a new entry copied from the given buffer, return block index.
ProfileBufferBlockIndex PutFrom(const void* aSrc, Length aBytes) {
return ReserveAndPut([aBytes]() { return aBytes; },
[&](Maybe<ProfileBufferEntryWriter>& aEntryWriter) {
if (MOZ_UNLIKELY(aEntryWriter.isNothing())) {
// Out-of-session, return "empty" index.
return ProfileBufferBlockIndex{};
}
aEntryWriter->WriteBytes(aSrc, aBytes);
return aEntryWriter->CurrentBlockIndex();
});
}
// Add a new single entry with *all* given object (using a Serializer for
// each), return block index.
template <typename... Ts>
ProfileBufferBlockIndex PutObjects(const Ts&... aTs) {
static_assert(sizeof...(Ts) > 0,
"PutObjects must be given at least one object.");
return ReserveAndPut(
[&]() { return ProfileBufferEntryWriter::SumBytes(aTs...); },
[&](Maybe<ProfileBufferEntryWriter>& aEntryWriter) {
if (MOZ_UNLIKELY(aEntryWriter.isNothing())) {
// Out-of-session, return "empty" index.
return ProfileBufferBlockIndex{};
}
aEntryWriter->WriteObjects(aTs...);
return aEntryWriter->CurrentBlockIndex();
});
}
// Add a new entry copied from the given object, return block index.
template <typename T>
ProfileBufferBlockIndex PutObject(const T& aOb) {
return PutObjects(aOb);
}
// Append the contents of another BlocksRingBuffer to this one.
ProfileBufferBlockIndex AppendContents(const BlocksRingBuffer& aSrc) {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
if (MOZ_UNLIKELY(!mMaybeUnderlyingBuffer)) {
// We are out-of-session, could not append contents.
return ProfileBufferBlockIndex{};
}
baseprofiler::detail::BaseProfilerMaybeAutoLock srcLock(aSrc.mMutex);
if (MOZ_UNLIKELY(!aSrc.mMaybeUnderlyingBuffer)) {
// The other BRB is out-of-session, nothing to copy, we're done.
return ProfileBufferBlockIndex{};
}
const ProfileBufferIndex srcStartIndex =
aSrc.mFirstReadIndex.ConvertToProfileBufferIndex();
const ProfileBufferIndex srcEndIndex =
aSrc.mNextWriteIndex.ConvertToProfileBufferIndex();
const Length bytesToCopy = static_cast<Length>(srcEndIndex - srcStartIndex);
if (MOZ_UNLIKELY(bytesToCopy == 0)) {
// The other BRB is empty, nothing to copy, we're done.
return ProfileBufferBlockIndex{};
}
const Length bufferBytes =
mMaybeUnderlyingBuffer->mBuffer.BufferLength().Value();
MOZ_RELEASE_ASSERT(bytesToCopy <= bufferBytes,
"Entry would wrap and overwrite itself");
// We will put all copied blocks at the end of the current buffer.
const ProfileBufferIndex dstStartIndex =
mNextWriteIndex.ConvertToProfileBufferIndex();
// Compute where the copy will end...
const ProfileBufferIndex dstEndIndex = dstStartIndex + bytesToCopy;
while (dstEndIndex >
mFirstReadIndex.ConvertToProfileBufferIndex() + bufferBytes) {
// About to trample on an old block.
ProfileBufferEntryReader reader = ReaderInBlockAt(mFirstReadIndex);
mMaybeUnderlyingBuffer->mClearedBlockCount += 1;
// Move the buffer reading start past this cleared block.
mFirstReadIndex = ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
mFirstReadIndex.ConvertToProfileBufferIndex() +
ULEB128Size(reader.RemainingBytes()) + reader.RemainingBytes());
}
// Store the new end of buffer.
mNextWriteIndex =
ProfileBufferBlockIndex::CreateFromProfileBufferIndex(dstEndIndex);
// Update our pushed count with the number of live blocks we are copying.
mMaybeUnderlyingBuffer->mPushedBlockCount +=
aSrc.mMaybeUnderlyingBuffer->mPushedBlockCount -
aSrc.mMaybeUnderlyingBuffer->mClearedBlockCount;
auto reader = aSrc.mMaybeUnderlyingBuffer->mBuffer.EntryReaderFromTo(
srcStartIndex, srcEndIndex, nullptr, nullptr);
auto writer = mMaybeUnderlyingBuffer->mBuffer.EntryWriterFromTo(
dstStartIndex, dstEndIndex);
writer.WriteFromReader(reader, bytesToCopy);
return ProfileBufferBlockIndex::CreateFromProfileBufferIndex(dstStartIndex);
}
// Clear all entries: Move read index to the end so that these entries cannot
// be read anymore.
void Clear() {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
ClearAllEntries();
}
// Clear all entries strictly before aBlockIndex, and move read index to the
// end so that these entries cannot be read anymore.
void ClearBefore(ProfileBufferBlockIndex aBlockIndex) {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
if (!mMaybeUnderlyingBuffer) {
return;
}
// Don't accept a not-yet-written index. One-past-the-end is ok.
MOZ_ASSERT(aBlockIndex <= mNextWriteIndex);
if (aBlockIndex <= mFirstReadIndex) {
// Already cleared.
return;
}
if (aBlockIndex == mNextWriteIndex) {
// Right past the end, just clear everything.
ClearAllEntries();
return;
}
// Otherwise we need to clear a subset of entries.
AssertBlockIndexIsValid(aBlockIndex);
// Just count skipped entries.
Reader reader(*this);
BlockIterator it = reader.begin();
for (; it.CurrentBlockIndex() < aBlockIndex; ++it) {
MOZ_ASSERT(it.CurrentBlockIndex() < reader.end().CurrentBlockIndex());
mMaybeUnderlyingBuffer->mClearedBlockCount += 1;
}
MOZ_ASSERT(it.CurrentBlockIndex() == aBlockIndex);
// Move read index to given index, so there's effectively no more entries
// before.
mFirstReadIndex = aBlockIndex;
}
#ifdef DEBUG
void Dump() const {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex);
if (!mMaybeUnderlyingBuffer) {
printf("empty BlocksRingBuffer\n");
return;
}
using ULL = unsigned long long;
printf("start=%llu (%llu) end=%llu (%llu) - ",
ULL(mFirstReadIndex.ConvertToProfileBufferIndex()),
ULL(mFirstReadIndex.ConvertToProfileBufferIndex() &
(mMaybeUnderlyingBuffer->mBuffer.BufferLength().Value() - 1)),
ULL(mNextWriteIndex.ConvertToProfileBufferIndex()),
ULL(mNextWriteIndex.ConvertToProfileBufferIndex() &
(mMaybeUnderlyingBuffer->mBuffer.BufferLength().Value() - 1)));
mMaybeUnderlyingBuffer->mBuffer.Dump();
}
#endif // DEBUG
private:
// In DEBUG mode, assert that `aBlockIndex` is a valid index for a live block.
// (Not just in range, but points exactly at the start of a block.)
// Slow, so avoid it for internal checks; this is more to check what callers
// provide us.
void AssertBlockIndexIsValid(ProfileBufferBlockIndex aBlockIndex) const {
#ifdef DEBUG
mMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(aBlockIndex >= mFirstReadIndex);
MOZ_ASSERT(aBlockIndex < mNextWriteIndex);
// Quick check (default), or slow check (change '1' to '0') below:
# if 1
// Quick check that this looks like a valid block start.
// Read the entry size at the start of the block.
const Length entryBytes = ReaderInBlockAt(aBlockIndex).RemainingBytes();
MOZ_ASSERT(entryBytes > 0, "Empty entries are not allowed");
MOZ_ASSERT(
entryBytes < mMaybeUnderlyingBuffer->mBuffer.BufferLength().Value() -
ULEB128Size(entryBytes),
"Entry would wrap and overwrite itself");
// The end of the block should be inside the live buffer range.
MOZ_ASSERT(aBlockIndex.ConvertToProfileBufferIndex() +
ULEB128Size(entryBytes) + entryBytes <=
mNextWriteIndex.ConvertToProfileBufferIndex());
# else
// Slow check that the index is really the start of the block.
// This kills performances, as it reads from the first index until
// aBlockIndex. Only use to debug issues locally.
Reader reader(*this);
BlockIterator it = reader.begin();
for (; it.CurrentBlockIndex() < aBlockIndex; ++it) {
MOZ_ASSERT(it.CurrentBlockIndex() < reader.end().CurrentBlockIndex());
}
MOZ_ASSERT(it.CurrentBlockIndex() == aBlockIndex);
# endif
#endif // DEBUG
}
// In DEBUG mode, assert that `aBlockIndex` is a valid index for a live block,
// or is just past-the-end. (Not just in range, but points exactly at the
// start of a block.) Slow, so avoid it for internal checks; this is more to
// check what callers provide us.
void AssertBlockIndexIsValidOrEnd(ProfileBufferBlockIndex aBlockIndex) const {
#ifdef DEBUG
mMutex.AssertCurrentThreadOwns();
if (aBlockIndex == mNextWriteIndex) {
return;
}
AssertBlockIndexIsValid(aBlockIndex);
#endif // DEBUG
}
// Create a reader for the block starting at aBlockIndex.
ProfileBufferEntryReader ReaderInBlockAt(
ProfileBufferBlockIndex aBlockIndex) const {
mMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mMaybeUnderlyingBuffer.isSome());
MOZ_ASSERT(aBlockIndex >= mFirstReadIndex);
MOZ_ASSERT(aBlockIndex < mNextWriteIndex);
// Create a reader from the given index until the end of the buffer.
ProfileBufferEntryReader reader =
mMaybeUnderlyingBuffer->mBuffer.EntryReaderFromTo(
aBlockIndex.ConvertToProfileBufferIndex(),
mNextWriteIndex.ConvertToProfileBufferIndex(), nullptr, nullptr);
// Read the block size at the beginning.
const Length entryBytes = reader.ReadULEB128<Length>();
// Make sure we don't overshoot the buffer.
MOZ_RELEASE_ASSERT(entryBytes <= reader.RemainingBytes());
ProfileBufferIndex nextBlockIndex =
aBlockIndex.ConvertToProfileBufferIndex() + ULEB128Size(entryBytes) +
entryBytes;
// And reduce the reader to the entry area. Only provide a next-block-index
// if it's not at the end of the buffer (i.e., there's an actual block
// there).
reader = mMaybeUnderlyingBuffer->mBuffer.EntryReaderFromTo(
aBlockIndex.ConvertToProfileBufferIndex() + ULEB128Size(entryBytes),
nextBlockIndex, aBlockIndex,
(nextBlockIndex < mNextWriteIndex.ConvertToProfileBufferIndex())
? ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
nextBlockIndex)
: ProfileBufferBlockIndex{});
return reader;
}
ProfileBufferEntryReader FullBufferReader() const {
mMutex.AssertCurrentThreadOwns();
if (!mMaybeUnderlyingBuffer) {
return {};
}
return mMaybeUnderlyingBuffer->mBuffer.EntryReaderFromTo(
mFirstReadIndex.ConvertToProfileBufferIndex(),
mNextWriteIndex.ConvertToProfileBufferIndex(), nullptr, nullptr);
}
// Clear all entries: Move read index to the end so that these entries cannot
// be read anymore.
void ClearAllEntries() {
mMutex.AssertCurrentThreadOwns();
if (!mMaybeUnderlyingBuffer) {
return;
}
// Mark all entries pushed so far as cleared.
mMaybeUnderlyingBuffer->mClearedBlockCount =
mMaybeUnderlyingBuffer->mPushedBlockCount;
// Move read index to write index, so there's effectively no more entries
// that can be read. (Not setting both to 0, in case user is keeping
// `ProfileBufferBlockIndex`'es to old entries.)
mFirstReadIndex = mNextWriteIndex;
}
// If there is an underlying buffer, clear all entries, and discard the
// buffer. This BlocksRingBuffer will now gracefully reject all API calls, and
// is in a state where a new underlying buffer may be set.
void ResetUnderlyingBuffer() {
mMutex.AssertCurrentThreadOwns();
if (!mMaybeUnderlyingBuffer) {
return;
}
ClearAllEntries();
mMaybeUnderlyingBuffer.reset();
}
// Used to de/serialize a BlocksRingBuffer (e.g., containing a backtrace).
friend ProfileBufferEntryWriter::Serializer<BlocksRingBuffer>;
friend ProfileBufferEntryReader::Deserializer<BlocksRingBuffer>;
friend ProfileBufferEntryWriter::Serializer<UniquePtr<BlocksRingBuffer>>;
friend ProfileBufferEntryReader::Deserializer<UniquePtr<BlocksRingBuffer>>;
// Mutex guarding the following members.
mutable baseprofiler::detail::BaseProfilerMaybeMutex mMutex;
struct UnderlyingBuffer {
// Create a buffer of the given length.
explicit UnderlyingBuffer(PowerOfTwo<Length> aLength) : mBuffer(aLength) {
MOZ_ASSERT(aLength.Value() > ULEB128MaxSize<Length>(),
"Buffer should be able to contain more than a block size");
}
// Take ownership of an existing buffer.
UnderlyingBuffer(UniquePtr<Buffer::Byte[]> aExistingBuffer,
PowerOfTwo<Length> aLength)
: mBuffer(std::move(aExistingBuffer), aLength) {
MOZ_ASSERT(aLength.Value() > ULEB128MaxSize<Length>(),
"Buffer should be able to contain more than a block size");
}
// Use an externally-owned buffer.
UnderlyingBuffer(Buffer::Byte* aExternalBuffer, PowerOfTwo<Length> aLength)
: mBuffer(aExternalBuffer, aLength) {
MOZ_ASSERT(aLength.Value() > ULEB128MaxSize<Length>(),
"Buffer should be able to contain more than a block size");
}
// Only allow move-construction.
UnderlyingBuffer(UnderlyingBuffer&&) = default;
// Copies and move-assignment are explictly disallowed.
UnderlyingBuffer(const UnderlyingBuffer&) = delete;
UnderlyingBuffer& operator=(const UnderlyingBuffer&) = delete;
UnderlyingBuffer& operator=(UnderlyingBuffer&&) = delete;
// Underlying circular byte buffer.
Buffer mBuffer;
// Statistics.
uint64_t mPushedBlockCount = 0;
uint64_t mClearedBlockCount = 0;
};
// Underlying buffer, with stats.
// Only valid during in-session period.
Maybe<UnderlyingBuffer> mMaybeUnderlyingBuffer;
// Index to the first block to be read (or cleared). Initialized to 1 because
// 0 is reserved for the "empty" ProfileBufferBlockIndex value. Kept between
// sessions, so that stored indices from one session will be gracefully denied
// in future sessions.
ProfileBufferBlockIndex mFirstReadIndex =
ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
ProfileBufferIndex(1));
// Index where the next new block should be allocated. Initialized to 1
// because 0 is reserved for the "empty" ProfileBufferBlockIndex value. Kept
// between sessions, so that stored indices from one session will be
// gracefully denied in future sessions.
ProfileBufferBlockIndex mNextWriteIndex =
ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
ProfileBufferIndex(1));
};
// ----------------------------------------------------------------------------
// BlocksRingBuffer serialization
// 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 ProfileBufferEntryWriter::Serializer<BlocksRingBuffer> {
static Length Bytes(const BlocksRingBuffer& aBuffer) {
baseprofiler::detail::BaseProfilerMaybeAutoLock 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 = aBuffer.mFirstReadIndex.ConvertToProfileBufferIndex();
const auto end = aBuffer.mNextWriteIndex.ConvertToProfileBufferIndex();
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(ProfileBufferEntryWriter& aEW,
const BlocksRingBuffer& aBuffer) {
baseprofiler::detail::BaseProfilerMaybeAutoLock lock(aBuffer.mMutex);
if (aBuffer.mMaybeUnderlyingBuffer.isNothing()) {
// Out-of-session, only store a length of 0.
aEW.WriteULEB128<Length>(0);
return;
}
const auto start = aBuffer.mFirstReadIndex.ConvertToProfileBufferIndex();
const auto end = aBuffer.mNextWriteIndex.ConvertToProfileBufferIndex();
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.
auto reader = aBuffer.FullBufferReader();
aEW.WriteFromReader(reader, reader.RemainingBytes());
// 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 ProfileBufferEntryReader::Deserializer<BlocksRingBuffer> {
static void ReadInto(ProfileBufferEntryReader& 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<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<Length>(len));
MOZ_ASSERT(aBuffer.BufferLength()->Value() >= len);
}
// Read start and end indices.
const auto start = aER.ReadObject<ProfileBufferIndex>();
aBuffer.mFirstReadIndex =
ProfileBufferBlockIndex::CreateFromProfileBufferIndex(start);
const auto end = aER.ReadObject<ProfileBufferIndex>();
aBuffer.mNextWriteIndex =
ProfileBufferBlockIndex::CreateFromProfileBufferIndex(end);
MOZ_ASSERT(end - start == len);
// Copy bytes into the buffer.
auto writer =
aBuffer.mMaybeUnderlyingBuffer->mBuffer.EntryWriterFromTo(start, end);
writer.WriteFromReader(aER, end - start);
MOZ_ASSERT(writer.RemainingBytes() == 0);
// 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(ProfileBufferEntryReader& 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 ProfileBufferEntryWriter::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(ProfileBufferEntryWriter& 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 ProfileBufferEntryReader::Deserializer<UniquePtr<BlocksRingBuffer>> {
static void ReadInto(ProfileBufferEntryReader& aER,
UniquePtr<BlocksRingBuffer>& aBuffer) {
aBuffer = Read(aER);
}
static UniquePtr<BlocksRingBuffer> Read(ProfileBufferEntryReader& aER) {
UniquePtr<BlocksRingBuffer> bufferUPtr;
// Keep a copy of the reader before reading the length, so we can restart
// from here below.
ProfileBufferEntryReader readerBeforeLen = aER;
// Read the stored buffer length.
const auto len = aER.ReadULEB128<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 without mutex.
bufferUPtr = MakeUnique<BlocksRingBuffer>(
BlocksRingBuffer::ThreadSafety::WithoutMutex);
// Rewind the reader before the length and deserialize the contents, using
// the non-UniquePtr Deserializer.
aER = readerBeforeLen;
aER.ReadIntoObject(*bufferUPtr);
return bufferUPtr;
}
};
} // namespace mozilla
#endif // BlocksRingBuffer_h

View File

@ -18,7 +18,6 @@
#ifdef MOZ_GECKO_PROFILER
# include "mozilla/BaseProfilerMarkerTypes.h"
# include "mozilla/BlocksRingBuffer.h"
# include "mozilla/leb128iterator.h"
# include "mozilla/ModuloBuffer.h"
# include "mozilla/mozalloc.h"
@ -3605,898 +3604,6 @@ void TestModuloBuffer() {
printf("TestModuloBuffer done\n");
}
void TestBlocksRingBufferAPI() {
printf("TestBlocksRingBufferAPI...\n");
// Create a 16-byte buffer, enough to store up to 3 entries (1 byte size + 4
// bytes uint64_t).
constexpr uint32_t MBSize = 16;
uint8_t buffer[MBSize * 3];
for (size_t i = 0; i < MBSize * 3; ++i) {
buffer[i] = uint8_t('A' + i);
}
// Start a temporary block to constrain buffer lifetime.
{
BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex,
&buffer[MBSize], MakePowerOfTwo32<MBSize>());
# define VERIFY_START_END_PUSHED_CLEARED(aStart, aEnd, aPushed, aCleared) \
{ \
BlocksRingBuffer::State state = rb.GetState(); \
MOZ_RELEASE_ASSERT(state.mRangeStart.ConvertToProfileBufferIndex() == \
(aStart)); \
MOZ_RELEASE_ASSERT(state.mRangeEnd.ConvertToProfileBufferIndex() == \
(aEnd)); \
MOZ_RELEASE_ASSERT(state.mPushedBlockCount == (aPushed)); \
MOZ_RELEASE_ASSERT(state.mClearedBlockCount == (aCleared)); \
}
// All entries will contain one 32-bit number. The resulting blocks will
// have the following structure:
// - 1 byte for the LEB128 size of 4
// - 4 bytes for the number.
// E.g., if we have entries with `123` and `456`:
// .-- Index 0 reserved for empty ProfileBufferBlockIndex, nothing there.
// | .-- first readable block at index 1
// | |.-- first block at index 1
// | ||.-- 1 byte for the entry size, which is `4` (32 bits)
// | ||| .-- entry starts at index 2, contains 32-bit int
// | ||| | .-- entry and block finish *after* index 5 (so 6)
// | ||| | | .-- second block starts at index 6
// | ||| | | | etc.
// | ||| | | | .-- End readable blocks: 11
// v vvv v v V v
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// - S[4 | int(123) ] [4 | int(456) ]E
// Empty buffer to start with.
// Start&end indices still at 1 (0 is reserved for the default
// ProfileBufferBlockIndex{} that cannot point at a valid entry), nothing
// cleared.
VERIFY_START_END_PUSHED_CLEARED(1, 1, 0, 0);
// Default ProfileBufferBlockIndex.
ProfileBufferBlockIndex bi0;
if (bi0) {
MOZ_RELEASE_ASSERT(false,
"if (ProfileBufferBlockIndex{}) should fail test");
}
if (!bi0) {
} else {
MOZ_RELEASE_ASSERT(false,
"if (!ProfileBufferBlockIndex{}) should succeed test");
}
MOZ_RELEASE_ASSERT(!bi0);
MOZ_RELEASE_ASSERT(bi0 == bi0);
MOZ_RELEASE_ASSERT(bi0 <= bi0);
MOZ_RELEASE_ASSERT(bi0 >= bi0);
MOZ_RELEASE_ASSERT(!(bi0 != bi0));
MOZ_RELEASE_ASSERT(!(bi0 < bi0));
MOZ_RELEASE_ASSERT(!(bi0 > bi0));
// Default ProfileBufferBlockIndex can be used, but returns no valid entry.
rb.ReadAt(bi0, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
});
// Push `1` directly.
MOZ_RELEASE_ASSERT(
rb.PutObject(uint32_t(1)).ConvertToProfileBufferIndex() == 1);
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// - S[4 | int(1) ]E
VERIFY_START_END_PUSHED_CLEARED(1, 6, 1, 0);
// Push `2` through ReserveAndPut, check output ProfileBufferBlockIndex.
auto bi2 = rb.ReserveAndPut([]() { return sizeof(uint32_t); },
[](Maybe<ProfileBufferEntryWriter>& aEW) {
MOZ_RELEASE_ASSERT(aEW.isSome());
aEW->WriteObject(uint32_t(2));
return aEW->CurrentBlockIndex();
});
static_assert(std::is_same<decltype(bi2), ProfileBufferBlockIndex>::value,
"All index-returning functions should return a "
"ProfileBufferBlockIndex");
MOZ_RELEASE_ASSERT(bi2.ConvertToProfileBufferIndex() == 6);
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// - S[4 | int(1) ] [4 | int(2) ]E
VERIFY_START_END_PUSHED_CLEARED(1, 11, 2, 0);
// Check single entry at bi2, store next block index.
auto i2Next =
rb.ReadAt(bi2, [bi2](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isSome());
MOZ_RELEASE_ASSERT(aMaybeReader->CurrentBlockIndex() == bi2);
MOZ_RELEASE_ASSERT(aMaybeReader->NextBlockIndex() == nullptr);
size_t entrySize = aMaybeReader->RemainingBytes();
MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject<uint32_t>() == 2);
// The next block index is after this block, which is made of the
// entry size (coded as ULEB128) followed by the entry itself.
return bi2.ConvertToProfileBufferIndex() + ULEB128Size(entrySize) +
entrySize;
});
auto bi2Next = rb.GetState().mRangeEnd;
MOZ_RELEASE_ASSERT(bi2Next.ConvertToProfileBufferIndex() == i2Next);
// bi2Next is at the end, nothing to read.
rb.ReadAt(bi2Next, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
});
// ProfileBufferBlockIndex tests.
if (bi2) {
} else {
MOZ_RELEASE_ASSERT(
false,
"if (non-default-ProfileBufferBlockIndex) should succeed test");
}
if (!bi2) {
MOZ_RELEASE_ASSERT(
false, "if (!non-default-ProfileBufferBlockIndex) should fail test");
}
MOZ_RELEASE_ASSERT(!!bi2);
MOZ_RELEASE_ASSERT(bi2 == bi2);
MOZ_RELEASE_ASSERT(bi2 <= bi2);
MOZ_RELEASE_ASSERT(bi2 >= bi2);
MOZ_RELEASE_ASSERT(!(bi2 != bi2));
MOZ_RELEASE_ASSERT(!(bi2 < bi2));
MOZ_RELEASE_ASSERT(!(bi2 > bi2));
MOZ_RELEASE_ASSERT(bi0 != bi2);
MOZ_RELEASE_ASSERT(bi0 < bi2);
MOZ_RELEASE_ASSERT(bi0 <= bi2);
MOZ_RELEASE_ASSERT(!(bi0 == bi2));
MOZ_RELEASE_ASSERT(!(bi0 > bi2));
MOZ_RELEASE_ASSERT(!(bi0 >= bi2));
MOZ_RELEASE_ASSERT(bi2 != bi0);
MOZ_RELEASE_ASSERT(bi2 > bi0);
MOZ_RELEASE_ASSERT(bi2 >= bi0);
MOZ_RELEASE_ASSERT(!(bi2 == bi0));
MOZ_RELEASE_ASSERT(!(bi2 < bi0));
MOZ_RELEASE_ASSERT(!(bi2 <= bi0));
MOZ_RELEASE_ASSERT(bi2 != bi2Next);
MOZ_RELEASE_ASSERT(bi2 < bi2Next);
MOZ_RELEASE_ASSERT(bi2 <= bi2Next);
MOZ_RELEASE_ASSERT(!(bi2 == bi2Next));
MOZ_RELEASE_ASSERT(!(bi2 > bi2Next));
MOZ_RELEASE_ASSERT(!(bi2 >= bi2Next));
MOZ_RELEASE_ASSERT(bi2Next != bi2);
MOZ_RELEASE_ASSERT(bi2Next > bi2);
MOZ_RELEASE_ASSERT(bi2Next >= bi2);
MOZ_RELEASE_ASSERT(!(bi2Next == bi2));
MOZ_RELEASE_ASSERT(!(bi2Next < bi2));
MOZ_RELEASE_ASSERT(!(bi2Next <= bi2));
// Push `3` through Put, check writer output
// is returned to the initial caller.
auto put3 =
rb.Put(sizeof(uint32_t), [&](Maybe<ProfileBufferEntryWriter>& aEW) {
MOZ_RELEASE_ASSERT(aEW.isSome());
aEW->WriteObject(uint32_t(3));
MOZ_RELEASE_ASSERT(aEW->CurrentBlockIndex() == bi2Next);
return float(aEW->CurrentBlockIndex().ConvertToProfileBufferIndex());
});
static_assert(std::is_same<decltype(put3), float>::value,
"Expect float as returned by callback.");
MOZ_RELEASE_ASSERT(put3 == 11.0);
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
// - S[4 | int(1) ] [4 | int(2) ] [4 | int(3) ]E
VERIFY_START_END_PUSHED_CLEARED(1, 16, 3, 0);
// Re-Read single entry at bi2, it should now have a next entry.
rb.ReadAt(bi2, [&](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isSome());
MOZ_RELEASE_ASSERT(aMaybeReader->CurrentBlockIndex() == bi2);
MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject<uint32_t>() == 2);
MOZ_RELEASE_ASSERT(aMaybeReader->NextBlockIndex() == bi2Next);
});
// Check that we have `1` to `3`.
uint32_t count = 0;
rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
});
MOZ_RELEASE_ASSERT(count == 3);
// Push `4`, store its ProfileBufferBlockIndex for later.
// This will wrap around, and clear the first entry.
ProfileBufferBlockIndex bi4 = rb.PutObject(uint32_t(4));
// Before:
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
// - S[4 | int(1) ] [4 | int(2) ] [4 | int(3) ]E
// 1. First entry cleared:
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
// - ? ? ? ? ? S[4 | int(2) ] [4 | int(3) ]E
// 2. New entry starts at 15 and wraps around: (shown on separate line)
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (16)
// - ? ? ? ? ? S[4 | int(2) ] [4 | int(3) ]
// 16 17 18 19 20 21 ...
// [4 | int(4) ]E
// (collapsed)
// 16 17 18 19 20 21 6 7 8 9 10 11 12 13 14 15 (16)
// [4 | int(4) ]E ? S[4 | int(2) ] [4 | int(3) ]
VERIFY_START_END_PUSHED_CLEARED(6, 21, 4, 1);
// Check that we have `2` to `4`.
count = 1;
rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
});
MOZ_RELEASE_ASSERT(count == 4);
// Push 5 through Put, no returns.
// This will clear the second entry.
// Check that the EntryWriter can access bi4 but not bi2.
auto bi5 =
rb.Put(sizeof(uint32_t), [&](Maybe<ProfileBufferEntryWriter>& aEW) {
MOZ_RELEASE_ASSERT(aEW.isSome());
aEW->WriteObject(uint32_t(5));
return aEW->CurrentBlockIndex();
});
auto bi6 = rb.GetState().mRangeEnd;
// 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15 (16)
// [4 | int(4) ] [4 | int(5) ]E ? S[4 | int(3) ]
VERIFY_START_END_PUSHED_CLEARED(11, 26, 5, 2);
// Read single entry at bi2, should now gracefully fail.
rb.ReadAt(bi2, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
});
// Read single entry at bi5.
rb.ReadAt(bi5, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isSome());
MOZ_RELEASE_ASSERT(aMaybeReader->ReadObject<uint32_t>() == 5);
});
rb.Read([&](BlocksRingBuffer::Reader* aReader) {
MOZ_RELEASE_ASSERT(!!aReader);
// begin() and end() should be at the range edges (verified above).
MOZ_RELEASE_ASSERT(
aReader->begin().CurrentBlockIndex().ConvertToProfileBufferIndex() ==
11);
MOZ_RELEASE_ASSERT(
aReader->end().CurrentBlockIndex().ConvertToProfileBufferIndex() ==
26);
// Null ProfileBufferBlockIndex clamped to the beginning.
MOZ_RELEASE_ASSERT(aReader->At(bi0) == aReader->begin());
// Cleared block index clamped to the beginning.
MOZ_RELEASE_ASSERT(aReader->At(bi2) == aReader->begin());
// At(begin) same as begin().
MOZ_RELEASE_ASSERT(aReader->At(aReader->begin().CurrentBlockIndex()) ==
aReader->begin());
// bi5 at expected position.
MOZ_RELEASE_ASSERT(
aReader->At(bi5).CurrentBlockIndex().ConvertToProfileBufferIndex() ==
21);
// bi6 at expected position at the end.
MOZ_RELEASE_ASSERT(aReader->At(bi6) == aReader->end());
// At(end) same as end().
MOZ_RELEASE_ASSERT(aReader->At(aReader->end().CurrentBlockIndex()) ==
aReader->end());
});
// Check that we have `3` to `5`.
count = 2;
rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
});
MOZ_RELEASE_ASSERT(count == 5);
// Clear everything before `4`, this should clear `3`.
rb.ClearBefore(bi4);
// 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15
// S[4 | int(4) ] [4 | int(5) ]E ? ? ? ? ? ?
VERIFY_START_END_PUSHED_CLEARED(16, 26, 5, 3);
// Check that we have `4` to `5`.
count = 3;
rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == ++count);
});
MOZ_RELEASE_ASSERT(count == 5);
// Clear everything before `4` again, nothing to clear.
rb.ClearBefore(bi4);
VERIFY_START_END_PUSHED_CLEARED(16, 26, 5, 3);
// Clear everything, this should clear `4` and `5`, and bring the start
// index where the end index currently is.
rb.ClearBefore(bi6);
// 16 17 18 19 20 21 22 23 24 25 26 11 12 13 14 15
// ? ? ? ? ? ? ? ? ? ? SE? ? ? ? ? ?
VERIFY_START_END_PUSHED_CLEARED(26, 26, 5, 5);
// Check that we have nothing to read.
rb.ReadEach([&](auto&&) { MOZ_RELEASE_ASSERT(false); });
// Read single entry at bi5, should now gracefully fail.
rb.ReadAt(bi5, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
});
// Clear everything before now-cleared `4`, nothing to clear.
rb.ClearBefore(bi4);
VERIFY_START_END_PUSHED_CLEARED(26, 26, 5, 5);
// Push `6` directly.
MOZ_RELEASE_ASSERT(rb.PutObject(uint32_t(6)) == bi6);
// 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// ? ? ? ? ? ? ? ? ? ? S[4 | int(6) ]E ?
VERIFY_START_END_PUSHED_CLEARED(26, 31, 6, 5);
{
// Create a 2nd buffer and fill it with `7` and `8`.
uint8_t buffer2[MBSize];
BlocksRingBuffer rb2(BlocksRingBuffer::ThreadSafety::WithoutMutex,
buffer2, MakePowerOfTwo32<MBSize>());
rb2.PutObject(uint32_t(7));
rb2.PutObject(uint32_t(8));
// Main buffer shouldn't have changed.
VERIFY_START_END_PUSHED_CLEARED(26, 31, 6, 5);
// Append contents of rb2 to rb, this should end up being the same as
// pushing the two numbers.
rb.AppendContents(rb2);
// 32 33 34 35 36 37 38 39 40 41 26 27 28 29 30 31
// int(7) ] [4 | int(8) ]E ? S[4 | int(6) ] [4 |
VERIFY_START_END_PUSHED_CLEARED(26, 41, 8, 5);
// Append contents of rb2 to rb again, to verify that rb2 was not modified
// above. This should clear `6` and the first `7`.
rb.AppendContents(rb2);
// 48 49 50 51 36 37 38 39 40 41 42 43 44 45 46 47
// int(8) ]E ? S[4 | int(8) ] [4 | int(7) ] [4 |
VERIFY_START_END_PUSHED_CLEARED(36, 51, 10, 7);
// End of block where rb2 lives, to verify that it is not needed anymore
// for its copied values to survive in rb.
}
VERIFY_START_END_PUSHED_CLEARED(36, 51, 10, 7);
// bi6 should now have been cleared.
rb.ReadAt(bi6, [](Maybe<ProfileBufferEntryReader>&& aMaybeReader) {
MOZ_RELEASE_ASSERT(aMaybeReader.isNothing());
});
// Check that we have `8`, `7`, `8`.
count = 0;
uint32_t expected[3] = {8, 7, 8};
rb.ReadEach([&](ProfileBufferEntryReader& aReader) {
MOZ_RELEASE_ASSERT(count < 3);
MOZ_RELEASE_ASSERT(aReader.ReadObject<uint32_t>() == expected[count++]);
});
MOZ_RELEASE_ASSERT(count == 3);
// End of block where rb lives, BlocksRingBuffer destructor should call
// entry destructor for remaining entries.
}
// Check that only the provided stack-based sub-buffer was modified.
uint32_t changed = 0;
for (size_t i = MBSize; i < MBSize * 2; ++i) {
changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
}
// Expect at least 75% changes.
MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
// Everything around the sub-buffer should be unchanged.
for (size_t i = 0; i < MBSize; ++i) {
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
}
for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
}
printf("TestBlocksRingBufferAPI done\n");
}
void TestBlocksRingBufferUnderlyingBufferChanges() {
printf("TestBlocksRingBufferUnderlyingBufferChanges...\n");
// Out-of-session BlocksRingBuffer to start with.
BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex);
// Block index to read at. Initially "null", but may be changed below.
ProfileBufferBlockIndex bi;
// Test all rb APIs when rb is out-of-session and therefore doesn't have an
// underlying buffer.
auto testOutOfSession = [&]() {
MOZ_RELEASE_ASSERT(rb.BufferLength().isNothing());
BlocksRingBuffer::State state = rb.GetState();
// When out-of-session, range start and ends are the same, and there are no
// pushed&cleared blocks.
MOZ_RELEASE_ASSERT(state.mRangeStart == state.mRangeEnd);
MOZ_RELEASE_ASSERT(state.mPushedBlockCount == 0);
MOZ_RELEASE_ASSERT(state.mClearedBlockCount == 0);
// `Put()` functions run the callback with `Nothing`.
int32_t ran = 0;
rb.Put(1, [&](Maybe<ProfileBufferEntryWriter>& aMaybeEntryWriter) {
MOZ_RELEASE_ASSERT(aMaybeEntryWriter.isNothing());
++ran;
});
MOZ_RELEASE_ASSERT(ran == 1);
// `PutFrom` won't do anything, and returns the null
// ProfileBufferBlockIndex.
MOZ_RELEASE_ASSERT(rb.PutFrom(&ran, sizeof(ran)) ==
ProfileBufferBlockIndex{});
MOZ_RELEASE_ASSERT(rb.PutObject(ran) == ProfileBufferBlockIndex{});
// `Read()` functions run the callback with `Nothing`.
ran = 0;
rb.Read([&](BlocksRingBuffer::Reader* aReader) {
MOZ_RELEASE_ASSERT(!aReader);
++ran;
});
MOZ_RELEASE_ASSERT(ran == 1);
ran = 0;
rb.ReadAt(ProfileBufferBlockIndex{},
[&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing());
++ran;
});
MOZ_RELEASE_ASSERT(ran == 1);
ran = 0;
rb.ReadAt(bi, [&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing());
++ran;
});
MOZ_RELEASE_ASSERT(ran == 1);
// `ReadEach` shouldn't run the callback (nothing to read).
rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
};
// As `testOutOfSession()` attempts to modify the buffer, we run it twice to
// make sure one run doesn't influence the next one.
testOutOfSession();
testOutOfSession();
rb.ClearBefore(bi);
testOutOfSession();
testOutOfSession();
rb.Clear();
testOutOfSession();
testOutOfSession();
rb.Reset();
testOutOfSession();
testOutOfSession();
constexpr uint32_t MBSize = 32;
rb.Set(MakePowerOfTwo<BlocksRingBuffer::Length, MBSize>());
constexpr bool EMPTY = true;
constexpr bool NOT_EMPTY = false;
// Test all rb APIs when rb has an underlying buffer.
auto testInSession = [&](bool aExpectEmpty) {
MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
BlocksRingBuffer::State state = rb.GetState();
if (aExpectEmpty) {
MOZ_RELEASE_ASSERT(state.mRangeStart == state.mRangeEnd);
MOZ_RELEASE_ASSERT(state.mPushedBlockCount == 0);
MOZ_RELEASE_ASSERT(state.mClearedBlockCount == 0);
} else {
MOZ_RELEASE_ASSERT(state.mRangeStart < state.mRangeEnd);
MOZ_RELEASE_ASSERT(state.mPushedBlockCount > 0);
MOZ_RELEASE_ASSERT(state.mClearedBlockCount <= state.mPushedBlockCount);
}
int32_t ran = 0;
// The following three `Put...` will write three int32_t of value 1.
bi = rb.Put(sizeof(ran),
[&](Maybe<ProfileBufferEntryWriter>& aMaybeEntryWriter) {
MOZ_RELEASE_ASSERT(aMaybeEntryWriter.isSome());
++ran;
aMaybeEntryWriter->WriteObject(ran);
return aMaybeEntryWriter->CurrentBlockIndex();
});
MOZ_RELEASE_ASSERT(ran == 1);
MOZ_RELEASE_ASSERT(rb.PutFrom(&ran, sizeof(ran)) !=
ProfileBufferBlockIndex{});
MOZ_RELEASE_ASSERT(rb.PutObject(ran) != ProfileBufferBlockIndex{});
ran = 0;
rb.Read([&](BlocksRingBuffer::Reader* aReader) {
MOZ_RELEASE_ASSERT(!!aReader);
++ran;
});
MOZ_RELEASE_ASSERT(ran == 1);
ran = 0;
rb.ReadEach([&](ProfileBufferEntryReader& aEntryReader) {
MOZ_RELEASE_ASSERT(aEntryReader.RemainingBytes() == sizeof(ran));
MOZ_RELEASE_ASSERT(aEntryReader.ReadObject<decltype(ran)>() == 1);
++ran;
});
MOZ_RELEASE_ASSERT(ran >= 3);
ran = 0;
rb.ReadAt(ProfileBufferBlockIndex{},
[&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing());
++ran;
});
MOZ_RELEASE_ASSERT(ran == 1);
ran = 0;
rb.ReadAt(bi, [&](Maybe<ProfileBufferEntryReader>&& aMaybeEntryReader) {
MOZ_RELEASE_ASSERT(aMaybeEntryReader.isNothing() == !bi);
++ran;
});
MOZ_RELEASE_ASSERT(ran == 1);
};
testInSession(EMPTY);
testInSession(NOT_EMPTY);
rb.Set(MakePowerOfTwo<BlocksRingBuffer::Length, 32>());
MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
testInSession(EMPTY);
testInSession(NOT_EMPTY);
rb.Reset();
testOutOfSession();
testOutOfSession();
uint8_t buffer[MBSize * 3];
for (size_t i = 0; i < MBSize * 3; ++i) {
buffer[i] = uint8_t('A' + i);
}
rb.Set(&buffer[MBSize], MakePowerOfTwo<BlocksRingBuffer::Length, MBSize>());
MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
testInSession(EMPTY);
testInSession(NOT_EMPTY);
rb.Reset();
testOutOfSession();
testOutOfSession();
rb.Set(&buffer[MBSize], MakePowerOfTwo<BlocksRingBuffer::Length, MBSize>());
MOZ_RELEASE_ASSERT(rb.BufferLength().isSome());
rb.ReadEach([](auto&&) { MOZ_RELEASE_ASSERT(false); });
testInSession(EMPTY);
testInSession(NOT_EMPTY);
// Remove the current underlying buffer, this should clear all entries.
rb.Reset();
// Check that only the provided stack-based sub-buffer was modified.
uint32_t changed = 0;
for (size_t i = MBSize; i < MBSize * 2; ++i) {
changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
}
// Expect at least 75% changes.
MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
// Everything around the sub-buffer should be unchanged.
for (size_t i = 0; i < MBSize; ++i) {
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
}
for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
}
testOutOfSession();
testOutOfSession();
printf("TestBlocksRingBufferUnderlyingBufferChanges done\n");
}
void TestBlocksRingBufferThreading() {
printf("TestBlocksRingBufferThreading...\n");
constexpr uint32_t MBSize = 8192;
uint8_t buffer[MBSize * 3];
for (size_t i = 0; i < MBSize * 3; ++i) {
buffer[i] = uint8_t('A' + i);
}
BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex,
&buffer[MBSize], MakePowerOfTwo32<MBSize>());
// Start reader thread.
std::atomic<bool> stopReader{false};
std::thread reader([&]() {
for (;;) {
BlocksRingBuffer::State state = rb.GetState();
printf(
"Reader: range=%llu..%llu (%llu bytes) pushed=%llu cleared=%llu "
"(alive=%llu)\n",
static_cast<unsigned long long>(
state.mRangeStart.ConvertToProfileBufferIndex()),
static_cast<unsigned long long>(
state.mRangeEnd.ConvertToProfileBufferIndex()),
static_cast<unsigned long long>(
state.mRangeEnd.ConvertToProfileBufferIndex()) -
static_cast<unsigned long long>(
state.mRangeStart.ConvertToProfileBufferIndex()),
static_cast<unsigned long long>(state.mPushedBlockCount),
static_cast<unsigned long long>(state.mClearedBlockCount),
static_cast<unsigned long long>(state.mPushedBlockCount -
state.mClearedBlockCount));
if (stopReader) {
break;
}
::SleepMilli(1);
}
});
// Start writer threads.
constexpr int ThreadCount = 32;
std::thread threads[ThreadCount];
for (int threadNo = 0; threadNo < ThreadCount; ++threadNo) {
threads[threadNo] = std::thread(
[&](int aThreadNo) {
::SleepMilli(1);
constexpr int pushCount = 1024;
for (int push = 0; push < pushCount; ++push) {
// Reserve as many bytes as the thread number (but at least enough
// to store an int), and write an increasing int.
rb.Put(std::max(aThreadNo, int(sizeof(push))),
[&](Maybe<ProfileBufferEntryWriter>& aEW) {
MOZ_RELEASE_ASSERT(aEW.isSome());
aEW->WriteObject(aThreadNo * 1000000 + push);
*aEW += aEW->RemainingBytes();
});
}
},
threadNo);
}
// Wait for all writer threads to die.
for (auto&& thread : threads) {
thread.join();
}
// Stop reader thread.
stopReader = true;
reader.join();
// Check that only the provided stack-based sub-buffer was modified.
uint32_t changed = 0;
for (size_t i = MBSize; i < MBSize * 2; ++i) {
changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
}
// Expect at least 75% changes.
MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
// Everything around the sub-buffer should be unchanged.
for (size_t i = 0; i < MBSize; ++i) {
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
}
for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
MOZ_RELEASE_ASSERT(buffer[i] == uint8_t('A' + i));
}
printf("TestBlocksRingBufferThreading done\n");
}
void TestBlocksRingBufferSerialization() {
printf("TestBlocksRingBufferSerialization...\n");
constexpr uint32_t MBSize = 64;
uint8_t buffer[MBSize * 3];
for (size_t i = 0; i < MBSize * 3; ++i) {
buffer[i] = uint8_t('A' + i);
}
BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex,
&buffer[MBSize], MakePowerOfTwo32<MBSize>());
// Will expect literal string to always have the same address.
# define THE_ANSWER "The answer is "
const char* theAnswer = THE_ANSWER;
rb.PutObjects('0', WrapProfileBufferLiteralCStringPointer(THE_ANSWER), 42,
std::string(" but pi="), 3.14);
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
char c0;
const char* answer;
int integer;
std::string str;
double pi;
aER.ReadIntoObjects(c0, answer, integer, str, pi);
MOZ_RELEASE_ASSERT(c0 == '0');
MOZ_RELEASE_ASSERT(answer == theAnswer);
MOZ_RELEASE_ASSERT(integer == 42);
MOZ_RELEASE_ASSERT(str == " but pi=");
MOZ_RELEASE_ASSERT(pi == 3.14);
});
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
char c0 = aER.ReadObject<char>();
MOZ_RELEASE_ASSERT(c0 == '0');
const char* answer = aER.ReadObject<const char*>();
MOZ_RELEASE_ASSERT(answer == theAnswer);
int integer = aER.ReadObject<int>();
MOZ_RELEASE_ASSERT(integer == 42);
std::string str = aER.ReadObject<std::string>();
MOZ_RELEASE_ASSERT(str == " but pi=");
double pi = aER.ReadObject<double>();
MOZ_RELEASE_ASSERT(pi == 3.14);
});
rb.Clear();
// Write an int and store its ProfileBufferBlockIndex.
ProfileBufferBlockIndex blockIndex = rb.PutObject(123);
// It should be non-0.
MOZ_RELEASE_ASSERT(blockIndex != ProfileBufferBlockIndex{});
// Write that ProfileBufferBlockIndex.
rb.PutObject(blockIndex);
rb.Read([&](BlocksRingBuffer::Reader* aR) {
BlocksRingBuffer::BlockIterator it = aR->begin();
const BlocksRingBuffer::BlockIterator itEnd = aR->end();
MOZ_RELEASE_ASSERT(it != itEnd);
MOZ_RELEASE_ASSERT((*it).ReadObject<int>() == 123);
++it;
MOZ_RELEASE_ASSERT(it != itEnd);
MOZ_RELEASE_ASSERT((*it).ReadObject<ProfileBufferBlockIndex>() ==
blockIndex);
++it;
MOZ_RELEASE_ASSERT(it == itEnd);
});
rb.Clear();
rb.PutObjects(
std::make_tuple('0', WrapProfileBufferLiteralCStringPointer(THE_ANSWER),
42, std::string(" but pi="), 3.14));
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
MOZ_RELEASE_ASSERT(aER.ReadObject<char>() == '0');
MOZ_RELEASE_ASSERT(aER.ReadObject<const char*>() == theAnswer);
MOZ_RELEASE_ASSERT(aER.ReadObject<int>() == 42);
MOZ_RELEASE_ASSERT(aER.ReadObject<std::string>() == " but pi=");
MOZ_RELEASE_ASSERT(aER.ReadObject<double>() == 3.14);
});
rb.Clear();
rb.PutObjects(
std::make_tuple('0', WrapProfileBufferLiteralCStringPointer(THE_ANSWER),
42, std::string(" but pi="), 3.14));
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
MOZ_RELEASE_ASSERT(aER.ReadObject<char>() == '0');
MOZ_RELEASE_ASSERT(aER.ReadObject<const char*>() == theAnswer);
MOZ_RELEASE_ASSERT(aER.ReadObject<int>() == 42);
MOZ_RELEASE_ASSERT(aER.ReadObject<std::string>() == " but pi=");
MOZ_RELEASE_ASSERT(aER.ReadObject<double>() == 3.14);
});
rb.Clear();
{
UniqueFreePtr<char> ufps(strdup(THE_ANSWER));
rb.PutObjects(ufps);
}
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
auto ufps = aER.ReadObject<UniqueFreePtr<char>>();
MOZ_RELEASE_ASSERT(!!ufps);
MOZ_RELEASE_ASSERT(std::string(THE_ANSWER) == ufps.get());
});
rb.Clear();
int intArray[] = {1, 2, 3, 4, 5};
rb.PutObjects(Span(intArray));
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
int intArrayOut[sizeof(intArray) / sizeof(intArray[0])] = {0};
auto outSpan = Span(intArrayOut);
aER.ReadIntoObject(outSpan);
for (size_t i = 0; i < sizeof(intArray) / sizeof(intArray[0]); ++i) {
MOZ_RELEASE_ASSERT(intArrayOut[i] == intArray[i]);
}
});
rb.Clear();
rb.PutObjects(Maybe<int>(Nothing{}), Maybe<int>(Some(123)));
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
Maybe<int> mi0, mi1;
aER.ReadIntoObjects(mi0, mi1);
MOZ_RELEASE_ASSERT(mi0.isNothing());
MOZ_RELEASE_ASSERT(mi1.isSome());
MOZ_RELEASE_ASSERT(*mi1 == 123);
});
rb.Clear();
using V = Variant<int, double, int>;
V v0(VariantIndex<0>{}, 123);
V v1(3.14);
V v2(VariantIndex<2>{}, 456);
rb.PutObjects(v0, v1, v2);
rb.ReadEach([&](ProfileBufferEntryReader& aER) {
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v0);
MOZ_RELEASE_ASSERT(aER.ReadObject<V>() == v1);
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(BlocksRingBuffer::ThreadSafety::WithoutMutex,
&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(BlocksRingBuffer::ThreadSafety::WithoutMutex,
&buffer3[MBSize], MakePowerOfTwo32<MBSize>());
rb2.ReadEach([&](ProfileBufferEntryReader& aER) { aER.ReadIntoObject(rb3); });
// And a 4th heap-allocated one.
UniquePtr<BlocksRingBuffer> rb4up;
rb2.ReadEach([&](ProfileBufferEntryReader& 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([&](ProfileBufferEntryReader& 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([&](ProfileBufferEntryReader& 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) {
changed += (buffer[i] == uint8_t('A' + i)) ? 0 : 1;
}
// Expect at least 75% changes.
MOZ_RELEASE_ASSERT(changed >= MBSize * 6 / 8);
// 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));
}
for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
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");
}
void TestLiteralEmptyStringView() {
printf("TestLiteralEmptyStringView...\n");
@ -4764,10 +3871,6 @@ void TestProfilerDependencies() {
TestChunkedBuffer();
TestChunkedBufferSingle();
TestModuloBuffer();
TestBlocksRingBufferAPI();
TestBlocksRingBufferUnderlyingBufferChanges();
TestBlocksRingBufferThreading();
TestBlocksRingBufferSerialization();
TestLiteralEmptyStringView();
TestProfilerStringView<char>();
TestProfilerStringView<char16_t>();

View File

@ -48,7 +48,6 @@
# include "jsapi.h"
# include "json/json.h"
# include "mozilla/Atomics.h"
# include "mozilla/BlocksRingBuffer.h"
# include "mozilla/DataMutex.h"
# include "mozilla/ProfileBufferEntrySerializationGeckoExtensions.h"
# include "mozilla/ProfileJSONWriter.h"
@ -1208,49 +1207,6 @@ TEST(GeckoProfiler, ThreadRegistration_RegistrationEdgeCases)
#ifdef MOZ_GECKO_PROFILER
TEST(BaseProfiler, BlocksRingBuffer)
{
constexpr uint32_t MBSize = 256;
uint8_t buffer[MBSize * 3];
for (size_t i = 0; i < MBSize * 3; ++i) {
buffer[i] = uint8_t('A' + i);
}
BlocksRingBuffer rb(BlocksRingBuffer::ThreadSafety::WithMutex,
&buffer[MBSize], MakePowerOfTwo32<MBSize>());
{
nsCString cs("nsCString"_ns);
nsString s(u"nsString"_ns);
nsAutoCString acs("nsAutoCString"_ns);
nsAutoString as(u"nsAutoString"_ns);
nsAutoCStringN<8> acs8("nsAutoCStringN"_ns);
nsAutoStringN<8> as8(u"nsAutoStringN"_ns);
JS::UniqueChars jsuc = JS_smprintf("%s", "JS::UniqueChars");
rb.PutObjects(cs, s, acs, as, acs8, as8, jsuc);
}
rb.ReadEach([](ProfileBufferEntryReader& aER) {
ASSERT_EQ(aER.ReadObject<nsCString>(), "nsCString"_ns);
ASSERT_EQ(aER.ReadObject<nsString>(), u"nsString"_ns);
ASSERT_EQ(aER.ReadObject<nsAutoCString>(), "nsAutoCString"_ns);
ASSERT_EQ(aER.ReadObject<nsAutoString>(), u"nsAutoString"_ns);
ASSERT_EQ(aER.ReadObject<nsAutoCStringN<8>>(), "nsAutoCStringN"_ns);
ASSERT_EQ(aER.ReadObject<nsAutoStringN<8>>(), u"nsAutoStringN"_ns);
auto jsuc2 = aER.ReadObject<JS::UniqueChars>();
ASSERT_TRUE(!!jsuc2);
ASSERT_TRUE(strcmp(jsuc2.get(), "JS::UniqueChars") == 0);
});
// Everything around the sub-buffer should be unchanged.
for (size_t i = 0; i < MBSize; ++i) {
ASSERT_EQ(buffer[i], uint8_t('A' + i));
}
for (size_t i = MBSize * 2; i < MBSize * 3; ++i) {
ASSERT_EQ(buffer[i], uint8_t('A' + i));
}
}
// Common JSON checks.
// Check that the given JSON string include no JSON whitespace characters