mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
Bug 1695120: Remove unused BlocksRingBuffer r=profiler-reviewers,canaltinova
Differential Revision: https://phabricator.services.mozilla.com/D194428
This commit is contained in:
parent
911a588250
commit
ff6cf2d0d7
@ -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.)
|
||||
|
@ -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",
|
||||
|
@ -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
|
@ -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>();
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user