547 lines
21 KiB
C++
547 lines
21 KiB
C++
/* -*- 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 ProfileBufferChunk_h
|
|
#define ProfileBufferChunk_h
|
|
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/ProfileBufferIndex.h"
|
|
#include "mozilla/Span.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
|
|
#if defined(MOZ_MEMORY)
|
|
# include "mozmemory.h"
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <type_traits>
|
|
|
|
#ifdef DEBUG
|
|
# include <cstdio>
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
|
|
// Represents a single chunk of memory, with a link to the next chunk (or null).
|
|
//
|
|
// A chunk is made of an internal header (which contains a public part) followed
|
|
// by user-accessible bytes.
|
|
//
|
|
// +---------------+---------+----------------------------------------------+
|
|
// | public Header | private | memory containing user blocks |
|
|
// +---------------+---------+----------------------------------------------+
|
|
// <---------------BufferBytes()------------------>
|
|
// <------------------------------ChunkBytes()------------------------------>
|
|
//
|
|
// The chunk can reserve "blocks", but doesn't know the internal contents of
|
|
// each block, it only knows where the first one starts, and where the last one
|
|
// ends (which is where the next one will begin, if not already out of range).
|
|
// It is up to the user to add structure to each block so that they can be
|
|
// distinguished when later read.
|
|
//
|
|
// +---------------+---------+----------------------------------------------+
|
|
// | public Header | private | [1st block]...[last full block] |
|
|
// +---------------+---------+----------------------------------------------+
|
|
// ChunkHeader().mOffsetFirstBlock ^ ^
|
|
// ChunkHeader().mOffsetPastLastBlock --'
|
|
//
|
|
// It is possible to attempt to reserve more than the remaining space, in which
|
|
// case only what is available is returned. The caller is responsible for using
|
|
// another chunk, reserving a block "tail" in it, and using both parts to
|
|
// constitute a full block. (This initial tail may be empty in some chunks.)
|
|
//
|
|
// +---------------+---------+----------------------------------------------+
|
|
// | public Header | private | tail][1st block]...[last full block][head... |
|
|
// +---------------+---------+----------------------------------------------+
|
|
// ChunkHeader().mOffsetFirstBlock ^ ^
|
|
// ChunkHeader().mOffsetPastLastBlock --'
|
|
//
|
|
// Each Chunk has an internal state (checked in DEBUG builds) that directs how
|
|
// to use it during creation, initialization, use, end of life, recycling, and
|
|
// destruction. See `State` below for details.
|
|
// In particular:
|
|
// - `ReserveInitialBlockAsTail()` must be called before the first `Reserve()`
|
|
// after construction or recycling, even with a size of 0 (no actual tail),
|
|
// - `MarkDone()` and `MarkRecycled()` must be called as appropriate.
|
|
class ProfileBufferChunk {
|
|
public:
|
|
using Byte = uint8_t;
|
|
using Length = uint32_t;
|
|
|
|
using SpanOfBytes = Span<Byte>;
|
|
|
|
// Hint about the size of the metadata (public and private headers).
|
|
// `Create()` below takes the minimum *buffer* size, so the minimum total
|
|
// Chunk size is at least `SizeofChunkMetadata() + aMinBufferBytes`.
|
|
[[nodiscard]] static constexpr Length SizeofChunkMetadata() {
|
|
return static_cast<Length>(sizeof(InternalHeader));
|
|
}
|
|
|
|
// Allocate space for a chunk with a given minimum size, and construct it.
|
|
// The actual size may be higher, to match the actual space taken in the
|
|
// memory pool.
|
|
[[nodiscard]] static UniquePtr<ProfileBufferChunk> Create(
|
|
Length aMinBufferBytes) {
|
|
// We need at least one byte, to cover the always-present `mBuffer` byte.
|
|
aMinBufferBytes = std::max(aMinBufferBytes, Length(1));
|
|
// Trivial struct with the same alignment as `ProfileBufferChunk`, and size
|
|
// equal to that alignment, because typically the sizeof of an object is
|
|
// a multiple of its alignment.
|
|
struct alignas(alignof(InternalHeader)) ChunkStruct {
|
|
Byte c[alignof(InternalHeader)];
|
|
};
|
|
static_assert(std::is_trivial_v<ChunkStruct>,
|
|
"ChunkStruct must be trivial to avoid any construction");
|
|
// Allocate an array of that struct, enough to contain the expected
|
|
// `ProfileBufferChunk` (with its header+buffer).
|
|
size_t count = (sizeof(InternalHeader) + aMinBufferBytes +
|
|
(alignof(InternalHeader) - 1)) /
|
|
alignof(InternalHeader);
|
|
#if defined(MOZ_MEMORY)
|
|
// Potentially expand the array to use more of the effective allocation.
|
|
count = (malloc_good_size(count * sizeof(ChunkStruct)) +
|
|
(sizeof(ChunkStruct) - 1)) /
|
|
sizeof(ChunkStruct);
|
|
#endif
|
|
auto chunkStorage = MakeUnique<ChunkStruct[]>(count);
|
|
MOZ_ASSERT(reinterpret_cast<uintptr_t>(chunkStorage.get()) %
|
|
alignof(InternalHeader) ==
|
|
0);
|
|
// After the allocation, compute the actual chunk size (including header).
|
|
const size_t chunkBytes = count * sizeof(ChunkStruct);
|
|
MOZ_ASSERT(chunkBytes >= sizeof(ProfileBufferChunk),
|
|
"Not enough space to construct a ProfileBufferChunk");
|
|
MOZ_ASSERT(chunkBytes <=
|
|
static_cast<size_t>(std::numeric_limits<Length>::max()));
|
|
// Compute the size of the user-accessible buffer inside the chunk.
|
|
const Length bufferBytes =
|
|
static_cast<Length>(chunkBytes - sizeof(InternalHeader));
|
|
MOZ_ASSERT(bufferBytes >= aMinBufferBytes,
|
|
"Not enough space for minimum buffer size");
|
|
// Construct the header at the beginning of the allocated array, with the
|
|
// known buffer size.
|
|
new (chunkStorage.get()) ProfileBufferChunk(bufferBytes);
|
|
// We now have a proper `ProfileBufferChunk` object, create the appropriate
|
|
// UniquePtr for it.
|
|
UniquePtr<ProfileBufferChunk> chunk{
|
|
reinterpret_cast<ProfileBufferChunk*>(chunkStorage.release())};
|
|
MOZ_ASSERT(
|
|
size_t(reinterpret_cast<const char*>(
|
|
&chunk.get()->BufferSpan()[bufferBytes - 1]) -
|
|
reinterpret_cast<const char*>(chunk.get())) == chunkBytes - 1,
|
|
"Buffer span spills out of chunk allocation");
|
|
return chunk;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
~ProfileBufferChunk() {
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::InUse);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Full);
|
|
MOZ_ASSERT(mInternalHeader.mState == InternalHeader::State::Created ||
|
|
mInternalHeader.mState == InternalHeader::State::Done ||
|
|
mInternalHeader.mState == InternalHeader::State::Recycled);
|
|
}
|
|
#endif
|
|
|
|
// Must be called with the first block tail (may be empty), which will be
|
|
// skipped if the reader starts with this ProfileBufferChunk.
|
|
[[nodiscard]] SpanOfBytes ReserveInitialBlockAsTail(Length aTailSize) {
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::InUse);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Full);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Done);
|
|
MOZ_ASSERT(mInternalHeader.mState == InternalHeader::State::Created ||
|
|
mInternalHeader.mState == InternalHeader::State::Recycled);
|
|
mInternalHeader.mState = InternalHeader::State::InUse;
|
|
#endif
|
|
mInternalHeader.mHeader.mOffsetFirstBlock = aTailSize;
|
|
mInternalHeader.mHeader.mOffsetPastLastBlock = aTailSize;
|
|
mInternalHeader.mHeader.mStartTimeStamp = TimeStamp::Now();
|
|
return SpanOfBytes(&mBuffer, aTailSize);
|
|
}
|
|
|
|
struct ReserveReturn {
|
|
SpanOfBytes mSpan;
|
|
ProfileBufferBlockIndex mBlockRangeIndex;
|
|
};
|
|
|
|
// Reserve a block of up to `aBlockSize` bytes, and return a Span to it, and
|
|
// its starting index. The actual size may be smaller, if the block cannot fit
|
|
// in the remaining space.
|
|
[[nodiscard]] ReserveReturn ReserveBlock(Length aBlockSize) {
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Created);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Full);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Done);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Recycled);
|
|
MOZ_ASSERT(mInternalHeader.mState == InternalHeader::State::InUse);
|
|
MOZ_ASSERT(RangeStart() != 0,
|
|
"Expected valid range start before first Reserve()");
|
|
const Length blockOffset = mInternalHeader.mHeader.mOffsetPastLastBlock;
|
|
Length reservedSize = aBlockSize;
|
|
if (MOZ_UNLIKELY(aBlockSize >= RemainingBytes())) {
|
|
reservedSize = RemainingBytes();
|
|
#ifdef DEBUG
|
|
mInternalHeader.mState = InternalHeader::State::Full;
|
|
#endif
|
|
}
|
|
mInternalHeader.mHeader.mOffsetPastLastBlock += reservedSize;
|
|
mInternalHeader.mHeader.mBlockCount += 1;
|
|
return {SpanOfBytes(&mBuffer + blockOffset, reservedSize),
|
|
ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
|
|
mInternalHeader.mHeader.mRangeStart + blockOffset)};
|
|
}
|
|
|
|
// When a chunk will not be used to store more blocks (because it is full, or
|
|
// because the profiler will not add more data), it should be marked "done".
|
|
// Access to its content is still allowed.
|
|
void MarkDone() {
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Created);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Done);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Recycled);
|
|
MOZ_ASSERT(mInternalHeader.mState == InternalHeader::State::InUse ||
|
|
mInternalHeader.mState == InternalHeader::State::Full);
|
|
mInternalHeader.mState = InternalHeader::State::Done;
|
|
#endif
|
|
mInternalHeader.mHeader.mDoneTimeStamp = TimeStamp::Now();
|
|
}
|
|
|
|
// A "Done" chunk may be recycled, to avoid allocating a new one.
|
|
void MarkRecycled() {
|
|
#ifdef DEBUG
|
|
// We also allow Created and already-Recycled chunks to be recycled, this
|
|
// way it's easier to recycle chunks when their state is not easily
|
|
// trackable.
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::InUse);
|
|
MOZ_ASSERT(mInternalHeader.mState != InternalHeader::State::Full);
|
|
MOZ_ASSERT(mInternalHeader.mState == InternalHeader::State::Created ||
|
|
mInternalHeader.mState == InternalHeader::State::Done ||
|
|
mInternalHeader.mState == InternalHeader::State::Recycled);
|
|
mInternalHeader.mState = InternalHeader::State::Recycled;
|
|
#endif
|
|
// Reset all header fields, in case this recycled chunk gets read.
|
|
mInternalHeader.mHeader.Reset();
|
|
}
|
|
|
|
// Public header, meant to uniquely identify a chunk, it may be shared with
|
|
// other processes to coordinate global memory handling.
|
|
struct Header {
|
|
explicit Header(Length aBufferBytes) : mBufferBytes(aBufferBytes) {}
|
|
|
|
// Reset all members to their as-new values (apart from the buffer size,
|
|
// which cannot change), ready for re-use.
|
|
void Reset() {
|
|
mOffsetFirstBlock = 0;
|
|
mOffsetPastLastBlock = 0;
|
|
mStartTimeStamp = TimeStamp{};
|
|
mDoneTimeStamp = TimeStamp{};
|
|
mBlockCount = 0;
|
|
mRangeStart = 0;
|
|
mProcessId = 0;
|
|
}
|
|
|
|
// Note: Part of the ordering of members below is to avoid unnecessary
|
|
// padding.
|
|
|
|
// Members managed by the ProfileBufferChunk.
|
|
|
|
// Offset of the first block (past the initial tail block, which may be 0).
|
|
Length mOffsetFirstBlock = 0;
|
|
// Offset past the last byte of the last reserved block
|
|
// It may be past mBufferBytes when last block continues in the next
|
|
// ProfileBufferChunk. It may be before mBufferBytes if ProfileBufferChunk
|
|
// is marked "Done" before the end is reached.
|
|
Length mOffsetPastLastBlock = 0;
|
|
// Timestamp when the buffer becomes in-use, ready to record data.
|
|
TimeStamp mStartTimeStamp;
|
|
// Timestamp when the buffer is "Done" (which happens when the last block is
|
|
// written). This will be used to find and discard the oldest
|
|
// ProfileBufferChunk.
|
|
TimeStamp mDoneTimeStamp;
|
|
// Number of bytes in the buffer, set once at construction time.
|
|
const Length mBufferBytes;
|
|
// Number of reserved blocks (including final one even if partial, but
|
|
// excluding initial tail).
|
|
Length mBlockCount = 0;
|
|
|
|
// Meta-data set by the user.
|
|
|
|
// Index of the first byte of this ProfileBufferChunk, relative to all
|
|
// Chunks for this process. Index 0 is reserved as nullptr-like index,
|
|
// mRangeStart should be set to a non-0 value before the first `Reserve()`.
|
|
ProfileBufferIndex mRangeStart = 0;
|
|
// Process writing to this ProfileBufferChunk.
|
|
int mProcessId = 0;
|
|
|
|
// A bit of spare space (necessary here because of the alignment due to
|
|
// other members), may be later repurposed for extra data.
|
|
const int mPADDING = 0;
|
|
};
|
|
|
|
[[nodiscard]] const Header& ChunkHeader() const {
|
|
return mInternalHeader.mHeader;
|
|
}
|
|
|
|
[[nodiscard]] Length BufferBytes() const {
|
|
return ChunkHeader().mBufferBytes;
|
|
}
|
|
|
|
// Total size of the chunk (buffer + header).
|
|
[[nodiscard]] Length ChunkBytes() const {
|
|
return static_cast<Length>(sizeof(InternalHeader)) + BufferBytes();
|
|
}
|
|
|
|
// Size of external resources, in this case all the following chunks.
|
|
[[nodiscard]] size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
|
|
const ProfileBufferChunk* const next = GetNext();
|
|
return next ? next->SizeOfIncludingThis(aMallocSizeOf) : 0;
|
|
}
|
|
|
|
// Size of this chunk and all following ones.
|
|
[[nodiscard]] size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
|
// Just in case `aMallocSizeOf` falls back on just `sizeof`, make sure we
|
|
// account for at least the actual Chunk requested allocation size.
|
|
return std::max<size_t>(aMallocSizeOf(this), ChunkBytes()) +
|
|
SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
[[nodiscard]] Length RemainingBytes() const {
|
|
return BufferBytes() - OffsetPastLastBlock();
|
|
}
|
|
|
|
[[nodiscard]] Length OffsetFirstBlock() const {
|
|
return ChunkHeader().mOffsetFirstBlock;
|
|
}
|
|
|
|
[[nodiscard]] Length OffsetPastLastBlock() const {
|
|
return ChunkHeader().mOffsetPastLastBlock;
|
|
}
|
|
|
|
[[nodiscard]] Length BlockCount() const { return ChunkHeader().mBlockCount; }
|
|
|
|
[[nodiscard]] int ProcessId() const { return ChunkHeader().mProcessId; }
|
|
|
|
void SetProcessId(int aProcessId) {
|
|
mInternalHeader.mHeader.mProcessId = aProcessId;
|
|
}
|
|
|
|
// Global range index at the start of this Chunk.
|
|
[[nodiscard]] ProfileBufferIndex RangeStart() const {
|
|
return ChunkHeader().mRangeStart;
|
|
}
|
|
|
|
void SetRangeStart(ProfileBufferIndex aRangeStart) {
|
|
mInternalHeader.mHeader.mRangeStart = aRangeStart;
|
|
}
|
|
|
|
// Get a read-only Span to the buffer. It is up to the caller to decypher the
|
|
// contents, based on known offsets and the internal block structure.
|
|
[[nodiscard]] Span<const Byte> BufferSpan() const {
|
|
return Span<const Byte>(&mBuffer, BufferBytes());
|
|
}
|
|
|
|
[[nodiscard]] Byte ByteAt(Length aOffset) const {
|
|
MOZ_ASSERT(aOffset < OffsetPastLastBlock());
|
|
return *(&mBuffer + aOffset);
|
|
}
|
|
|
|
[[nodiscard]] ProfileBufferChunk* GetNext() {
|
|
return mInternalHeader.mNext.get();
|
|
}
|
|
[[nodiscard]] const ProfileBufferChunk* GetNext() const {
|
|
return mInternalHeader.mNext.get();
|
|
}
|
|
|
|
[[nodiscard]] UniquePtr<ProfileBufferChunk> ReleaseNext() {
|
|
return std::move(mInternalHeader.mNext);
|
|
}
|
|
|
|
void InsertNext(UniquePtr<ProfileBufferChunk>&& aChunk) {
|
|
if (!aChunk) {
|
|
return;
|
|
}
|
|
aChunk->SetLast(ReleaseNext());
|
|
mInternalHeader.mNext = std::move(aChunk);
|
|
}
|
|
|
|
// Find the last chunk in this chain (it may be `this`).
|
|
[[nodiscard]] ProfileBufferChunk* Last() {
|
|
ProfileBufferChunk* chunk = this;
|
|
for (;;) {
|
|
ProfileBufferChunk* next = chunk->GetNext();
|
|
if (!next) {
|
|
return chunk;
|
|
}
|
|
chunk = next;
|
|
}
|
|
}
|
|
[[nodiscard]] const ProfileBufferChunk* Last() const {
|
|
const ProfileBufferChunk* chunk = this;
|
|
for (;;) {
|
|
const ProfileBufferChunk* next = chunk->GetNext();
|
|
if (!next) {
|
|
return chunk;
|
|
}
|
|
chunk = next;
|
|
}
|
|
}
|
|
|
|
void SetLast(UniquePtr<ProfileBufferChunk>&& aChunk) {
|
|
if (!aChunk) {
|
|
return;
|
|
}
|
|
Last()->mInternalHeader.mNext = std::move(aChunk);
|
|
}
|
|
|
|
// Join two possibly-null chunk lists.
|
|
[[nodiscard]] static UniquePtr<ProfileBufferChunk> Join(
|
|
UniquePtr<ProfileBufferChunk>&& aFirst,
|
|
UniquePtr<ProfileBufferChunk>&& aLast) {
|
|
if (aFirst) {
|
|
aFirst->SetLast(std::move(aLast));
|
|
return std::move(aFirst);
|
|
}
|
|
return std::move(aLast);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void Dump(std::FILE* aFile = stdout) const {
|
|
fprintf(aFile,
|
|
"Chunk[%p] chunkSize=%u bufferSize=%u state=%s rangeStart=%u "
|
|
"firstBlockOffset=%u offsetPastLastBlock=%u blockCount=%u",
|
|
this, unsigned(ChunkBytes()), unsigned(BufferBytes()),
|
|
mInternalHeader.StateString(), unsigned(RangeStart()),
|
|
unsigned(OffsetFirstBlock()), unsigned(OffsetPastLastBlock()),
|
|
unsigned(BlockCount()));
|
|
const auto len = OffsetPastLastBlock();
|
|
constexpr unsigned columns = 16;
|
|
unsigned char ascii[columns + 1];
|
|
ascii[columns] = '\0';
|
|
for (Length i = 0; i < len; ++i) {
|
|
if (i % columns == 0) {
|
|
fprintf(aFile, "\n %4u=0x%03x:", unsigned(i), unsigned(i));
|
|
for (unsigned a = 0; a < columns; ++a) {
|
|
ascii[a] = ' ';
|
|
}
|
|
}
|
|
unsigned char sep = ' ';
|
|
if (i == OffsetFirstBlock()) {
|
|
if (i == OffsetPastLastBlock()) {
|
|
sep = '#';
|
|
} else {
|
|
sep = '[';
|
|
}
|
|
} else if (i == OffsetPastLastBlock()) {
|
|
sep = ']';
|
|
}
|
|
unsigned char c = *(&mBuffer + i);
|
|
fprintf(aFile, "%c%02x", sep, c);
|
|
|
|
if (i == len - 1) {
|
|
if (i + 1 == OffsetPastLastBlock()) {
|
|
// Special case when last block ends right at the end.
|
|
fprintf(aFile, "]");
|
|
} else {
|
|
fprintf(aFile, " ");
|
|
}
|
|
} else if (i % columns == columns - 1) {
|
|
fprintf(aFile, " ");
|
|
}
|
|
|
|
ascii[i % columns] = (c >= ' ' && c <= '~') ? c : '.';
|
|
|
|
if (i % columns == columns - 1) {
|
|
fprintf(aFile, " %s", ascii);
|
|
}
|
|
}
|
|
|
|
if (len % columns < columns - 1) {
|
|
for (Length i = len % columns; i < columns; ++i) {
|
|
fprintf(aFile, " ");
|
|
}
|
|
fprintf(aFile, " %s", ascii);
|
|
}
|
|
|
|
fprintf(aFile, "\n");
|
|
}
|
|
#endif // DEBUG
|
|
|
|
private:
|
|
// ProfileBufferChunk constructor. Use static `Create()` to allocate and
|
|
// construct a ProfileBufferChunk.
|
|
explicit ProfileBufferChunk(Length aBufferBytes)
|
|
: mInternalHeader(aBufferBytes) {}
|
|
|
|
// This internal header starts with the public `Header`, and adds some data
|
|
// only necessary for local handling.
|
|
// This encapsulation is also necessary to perform placement-new in
|
|
// `Create()`.
|
|
struct InternalHeader {
|
|
explicit InternalHeader(Length aBufferBytes) : mHeader(aBufferBytes) {}
|
|
|
|
Header mHeader;
|
|
UniquePtr<ProfileBufferChunk> mNext;
|
|
|
|
#ifdef DEBUG
|
|
enum class State {
|
|
Created, // Self-set. Just constructed, waiting for initial block tail.
|
|
InUse, // Ready to accept blocks.
|
|
Full, // Self-set. Blocks reach the end (or further).
|
|
Done, // Blocks won't be added anymore.
|
|
Recycled // Still full of data, but expecting an initial block tail.
|
|
};
|
|
|
|
State mState = State::Created;
|
|
// Transition table: (X=unexpected)
|
|
// Method \ State Created InUse Full Done Recycled
|
|
// ReserveInitialBlockAsTail InUse X X X InUse
|
|
// Reserve X InUse/Full X X X
|
|
// MarkDone X Done Done X X
|
|
// MarkRecycled X X X Recycled X
|
|
// destructor ok X X ok ok
|
|
|
|
const char* StateString() const {
|
|
switch (mState) {
|
|
case State::Created:
|
|
return "Created";
|
|
case State::InUse:
|
|
return "InUse";
|
|
case State::Full:
|
|
return "Full";
|
|
case State::Done:
|
|
return "Done";
|
|
case State::Recycled:
|
|
return "Recycled";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
#else // DEBUG
|
|
const char* StateString() const { return "(non-DEBUG)"; }
|
|
#endif
|
|
};
|
|
|
|
InternalHeader mInternalHeader;
|
|
|
|
// KEEP THIS LAST!
|
|
// First byte of the buffer. Note that ProfileBufferChunk::Create allocates a
|
|
// bigger block, such that `mBuffer` is the first of `mBufferBytes` available
|
|
// bytes.
|
|
// The initialization is not strictly needed, because bytes should only be
|
|
// read after they have been written and `mOffsetPastLastBlock` has been
|
|
// updated. However:
|
|
// - Reviewbot complains that it's not initialized.
|
|
// - It's cheap to initialize one byte.
|
|
// - In the worst case (reading does happen), zero is not a valid entry size
|
|
// and should get caught in entry readers.
|
|
Byte mBuffer = '\0';
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // ProfileBufferChunk_h
|