Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
// This header contains basic definitions required to create marker types, and
// to add markers to the profiler buffers.
//
// In most cases, #include "mozilla/BaseProfilerMarkers.h" instead, or
// #include "mozilla/BaseProfilerMarkerTypes.h" for common marker types.
#ifndef BaseProfilerMarkersPrerequisites_h
#define BaseProfilerMarkersPrerequisites_h
namespace mozilla {
enum class StackCaptureOptions {
NoStack, // No stack captured.
Full, // Capture a full stack, including label frames, JS frames and
// native frames.
NonNative, // Capture a stack without native frames for reduced overhead.
};
}
#include "BaseProfileJSONWriter.h"
#include "BaseProfilingCategory.h"
#include "mozilla/Maybe.h"
#include "mozilla/ProfileChunkedBuffer.h"
#include "mozilla/BaseProfilerState.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Variant.h"
#include <initializer_list>
#include <string_view>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace mozilla {
// Return a NotNull<const CHAR*> pointing at the literal empty string `""`.
template <typename CHAR>
constexpr const CHAR* LiteralEmptyStringPointer() {
static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>,
"Only char and char16_t are supported in Firefox");
if constexpr (std::is_same_v<CHAR, char>) {
return "";
}
if constexpr (std::is_same_v<CHAR, char16_t>) {
return u"";
}
}
// Return a string_view<CHAR> pointing at the literal empty string.
template <typename CHAR>
constexpr std::basic_string_view<CHAR> LiteralEmptyStringView() {
static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>,
"Only char and char16_t are supported in Firefox");
// Use `operator""sv()` from <string_view>.
using namespace std::literals::string_view_literals;
if constexpr (std::is_same_v<CHAR, char>) {
return ""sv;
}
if constexpr (std::is_same_v<CHAR, char16_t>) {
return u""sv;
}
}
// General string view, optimized for short on-stack life before serialization,
// and between deserialization and JSON-streaming.
template <typename CHAR>
class MOZ_STACK_CLASS ProfilerStringView {
public:
// Default constructor points at "" (literal empty string).
constexpr ProfilerStringView() = default;
// Don't allow copy.
ProfilerStringView(const ProfilerStringView&) = delete;
ProfilerStringView& operator=(const ProfilerStringView&) = delete;
// Allow move. For consistency the moved-from string is always reset to "".
constexpr ProfilerStringView(ProfilerStringView&& aOther)
: mStringView(std::move(aOther.mStringView)),
mOwnership(aOther.mOwnership) {
if (mOwnership == Ownership::OwnedThroughStringView) {
// We now own the buffer, make the other point at the literal "".
aOther.mStringView = LiteralEmptyStringView<CHAR>();
aOther.mOwnership = Ownership::Literal;
}
}
constexpr ProfilerStringView& operator=(ProfilerStringView&& aOther) {
mStringView = std::move(aOther.mStringView);
mOwnership = aOther.mOwnership;
if (mOwnership == Ownership::OwnedThroughStringView) {
// We now own the buffer, make the other point at the literal "".
aOther.mStringView = LiteralEmptyStringView<CHAR>();
aOther.mOwnership = Ownership::Literal;
}
return *this;
}
~ProfilerStringView() {
if (MOZ_UNLIKELY(mOwnership == Ownership::OwnedThroughStringView)) {
// We own the buffer pointed at by mStringView, destroy it.
// This is only used between deserialization and streaming.
delete mStringView.data();
}
}
// Implicit construction from nullptr, points at "" (literal empty string).
constexpr MOZ_IMPLICIT ProfilerStringView(decltype(nullptr)) {}
// Implicit constructor from a literal string.
template <size_t Np1>
constexpr MOZ_IMPLICIT ProfilerStringView(const CHAR (&aLiteralString)[Np1])
: ProfilerStringView(aLiteralString, Np1 - 1, Ownership::Literal) {}
// Constructor from a non-literal string.
constexpr ProfilerStringView(const CHAR* aString, size_t aLength)
: ProfilerStringView(aString, aLength, Ownership::Reference) {}
// Implicit constructor from a string_view.
constexpr MOZ_IMPLICIT ProfilerStringView(
const std::basic_string_view<CHAR>& aStringView)
: ProfilerStringView(aStringView.data(), aStringView.length(),
Ownership::Reference) {}
// Implicit constructor from an expiring string_view. We assume that the
// pointed-at string will outlive this ProfilerStringView.
constexpr MOZ_IMPLICIT ProfilerStringView(
std::basic_string_view<CHAR>&& aStringView)
: ProfilerStringView(aStringView.data(), aStringView.length(),
Ownership::Reference) {}
// Implicit constructor from std::string.
constexpr MOZ_IMPLICIT ProfilerStringView(
const std::basic_string<CHAR>& aString)
: ProfilerStringView(aString.data(), aString.length(),
Ownership::Reference) {}
// Construction from a raw pointer to a null-terminated string.
// This is a named class-static function to make it more obvious where work is
// being done (to determine the string length), and encourage users to instead
// provide a length, if already known.
// TODO: Find callers and convert them to constructor instead if possible.
static constexpr ProfilerStringView WrapNullTerminatedString(
const CHAR* aString) {
return ProfilerStringView(
aString, aString ? std::char_traits<CHAR>::length(aString) : 0,
Ownership::Reference);
}
// Implicit constructor for an object with member functions `Data()`
// `Length()`, and `IsLiteral()`, common in xpcom strings.
template <
typename String,
typename DataReturnType = decltype(std::declval<const String>().Data()),
typename LengthReturnType =
decltype(std::declval<const String>().Length()),
typename IsLiteralReturnType =
decltype(std::declval<const String>().IsLiteral()),
typename =
std::enable_if_t<std::is_convertible_v<DataReturnType, const CHAR*> &&
std::is_integral_v<LengthReturnType> &&
std::is_same_v<IsLiteralReturnType, bool>>>
constexpr MOZ_IMPLICIT ProfilerStringView(const String& aString)
: ProfilerStringView(
static_cast<const CHAR*>(aString.Data()), aString.Length(),
aString.IsLiteral() ? Ownership::Literal : Ownership::Reference) {}
[[nodiscard]] constexpr const std::basic_string_view<CHAR>& StringView()
const {
return mStringView;
}
[[nodiscard]] constexpr size_t Length() const { return mStringView.length(); }
[[nodiscard]] constexpr bool IsLiteral() const {
return mOwnership == Ownership::Literal;
}
[[nodiscard]] constexpr bool IsReference() const {
return mOwnership == Ownership::Reference;
}
// No `IsOwned...()` because it's a secret, only used internally!
[[nodiscard]] Span<const CHAR> AsSpan() const {
return Span<const CHAR>(mStringView.data(), mStringView.length());
}
[[nodiscard]] operator Span<const CHAR>() const { return AsSpan(); }
private:
enum class Ownership { Literal, Reference, OwnedThroughStringView };
// Allow deserializer to store anything here.
friend ProfileBufferEntryReader::Deserializer<ProfilerStringView>;
constexpr ProfilerStringView(const CHAR* aString, size_t aLength,
Ownership aOwnership)
: mStringView(aString ? std::basic_string_view<CHAR>(aString, aLength)
: LiteralEmptyStringView<CHAR>()),
mOwnership(aString ? aOwnership : Ownership::Literal) {}
// String view to an outside string (literal or reference).
// We may actually own the pointed-at buffer, but it is only used internally
// between deserialization and JSON streaming.
std::basic_string_view<CHAR> mStringView = LiteralEmptyStringView<CHAR>();
Ownership mOwnership = Ownership::Literal;
};
using ProfilerString8View = ProfilerStringView<char>;
using ProfilerString16View = ProfilerStringView<char16_t>;
// This compulsory marker parameter contains the required category information.
class MarkerCategory {
public:
// Constructor from category pair (includes both super- and sub-categories).
constexpr explicit MarkerCategory(
baseprofiler::ProfilingCategoryPair aCategoryPair)
: mCategoryPair(aCategoryPair) {}
// Returns the stored category pair.
constexpr baseprofiler::ProfilingCategoryPair CategoryPair() const {
return mCategoryPair;
}
// Returns the super-category from the stored category pair.
baseprofiler::ProfilingCategory GetCategory() const {
return GetProfilingCategoryPairInfo(mCategoryPair).mCategory;
}
private:
baseprofiler::ProfilingCategoryPair mCategoryPair =
baseprofiler::ProfilingCategoryPair::OTHER;
};
namespace baseprofiler::category {
// Each category pair name constructs a MarkerCategory.
// E.g.: mozilla::baseprofiler::category::OTHER_Profiling
// Profiler macros will take the category name alone without namespace.
// E.g.: `PROFILER_MARKER_UNTYPED("name", OTHER_Profiling)`
#define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color)
#define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) \
static constexpr MarkerCategory name{ProfilingCategoryPair::name};
#define CATEGORY_ENUM_END_CATEGORY
MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY,
CATEGORY_ENUM_SUBCATEGORY,
CATEGORY_ENUM_END_CATEGORY)
#undef CATEGORY_ENUM_BEGIN_CATEGORY
#undef CATEGORY_ENUM_SUBCATEGORY
#undef CATEGORY_ENUM_END_CATEGORY
// Import `MarkerCategory` into this namespace. This will allow using this type
// dynamically in macros that prepend `::mozilla::baseprofiler::category::` to
// the given category, e.g.:
// `PROFILER_MARKER_UNTYPED("name", MarkerCategory(...))`
using MarkerCategory = ::mozilla::MarkerCategory;
} // namespace baseprofiler::category
// The classes below are all embedded in a `MarkerOptions` object.
class MarkerOptions;
// This marker option captures a given thread id.
// If left unspecified (by default construction) during the add-marker call, the
// current thread id will be used then.
class MarkerThreadId {
public:
// Default constructor, keeps the thread id unspecified.
constexpr MarkerThreadId() = default;
// Constructor from a given thread id.
constexpr explicit MarkerThreadId(
baseprofiler::BaseProfilerThreadId aThreadId)
: mThreadId(aThreadId) {}
// Use the current thread's id.
static MarkerThreadId CurrentThread() {
return MarkerThreadId(baseprofiler::profiler_current_thread_id());
}
// Use the main thread's id. This can be useful to record a marker from a
// possibly-unregistered thread, and display it in the main thread track.
static MarkerThreadId MainThread() {
return MarkerThreadId(baseprofiler::profiler_main_thread_id());
}
[[nodiscard]] constexpr baseprofiler::BaseProfilerThreadId ThreadId() const {
return mThreadId;
}
[[nodiscard]] constexpr bool IsUnspecified() const {
return !mThreadId.IsSpecified();
}
private:
baseprofiler::BaseProfilerThreadId mThreadId;
};
// This marker option contains marker timing information.
// This class encapsulates the logic for correctly storing a marker based on its
// Use the static methods to create the MarkerTiming. This is a transient object
// that is being used to enforce the constraints of the combinations of the
// data.
class MarkerTiming {
public:
// The following static methods are used to create the MarkerTiming based on
// the type that it is.
static MarkerTiming InstantAt(const TimeStamp& aTime) {
MOZ_ASSERT(!aTime.IsNull(), "Time is null for an instant marker.");
return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::Instant};
}
static MarkerTiming InstantNow() { return InstantAt(TimeStamp::Now()); }
static MarkerTiming Interval(const TimeStamp& aStartTime,
const TimeStamp& aEndTime) {
MOZ_ASSERT(!aStartTime.IsNull(),
"Start time is null for an interval marker.");
MOZ_ASSERT(!aEndTime.IsNull(), "End time is null for an interval marker.");
return MarkerTiming{aStartTime, aEndTime, MarkerTiming::Phase::Interval};
}
static MarkerTiming IntervalUntilNowFrom(const TimeStamp& aStartTime) {
return Interval(aStartTime, TimeStamp::Now());
}
static MarkerTiming IntervalStart(const TimeStamp& aTime = TimeStamp::Now()) {
MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval start marker.");
return MarkerTiming{aTime, TimeStamp{}, MarkerTiming::Phase::IntervalStart};
}
static MarkerTiming IntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) {
MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker.");
return MarkerTiming{TimeStamp{}, aTime, MarkerTiming::Phase::IntervalEnd};
}
// Set the interval end in this timing.
// If there was already a start time, this makes it a full interval.
void SetIntervalEnd(const TimeStamp& aTime = TimeStamp::Now()) {
MOZ_ASSERT(!aTime.IsNull(), "Time is null for an interval end marker.");
mEndTime = aTime;
mPhase = mStartTime.IsNull() ? Phase::IntervalEnd : Phase::Interval;
}
[[nodiscard]] const TimeStamp& StartTime() const { return mStartTime; }
[[nodiscard]] const TimeStamp& EndTime() const { return mEndTime; }
// The phase differentiates Instant markers from Interval markers.
// Interval markers can either carry both timestamps on a single marker,
// or they can be split into individual Start and End markers, which are
// associated with each other via the marker name.
//
// The numeric representation of this enum value is also exposed in the
// ETW trace event's Phase field.
enum class Phase : uint8_t {
Instant = 0,
Interval = 1,
IntervalStart = 2,
IntervalEnd = 3,
};
[[nodiscard]] Phase MarkerPhase() const {
MOZ_ASSERT(!IsUnspecified());
return mPhase;
}
// The following getter methods are used to put the value into the buffer for
// storage.
[[nodiscard]] double GetStartTime() const {
MOZ_ASSERT(!IsUnspecified());
// If mStartTime is null (e.g., for IntervalEnd), this will output 0.0 as
// expected.
return MarkerTiming::timeStampToDouble(mStartTime);
}
[[nodiscard]] double GetEndTime() const {
MOZ_ASSERT(!IsUnspecified());
// If mEndTime is null (e.g., for Instant or IntervalStart), this will
// output 0.0 as expected.
return MarkerTiming::timeStampToDouble(mEndTime);
}
[[nodiscard]] uint8_t GetPhase() const {
MOZ_ASSERT(!IsUnspecified());
return static_cast<uint8_t>(mPhase);
}
// This is a constructor for Rust FFI bindings. It must not be used outside of
// this! Please see the other static constructors above.
static void UnsafeConstruct(MarkerTiming* aMarkerTiming,
const TimeStamp& aStartTime,
const TimeStamp& aEndTime, Phase aPhase) {
new (aMarkerTiming) MarkerTiming{aStartTime, aEndTime, aPhase};
}
private:
friend ProfileBufferEntryWriter::Serializer<MarkerTiming>;
friend ProfileBufferEntryReader::Deserializer<MarkerTiming>;
friend MarkerOptions;
// Default timing leaves it internally "unspecified", serialization getters
// and add-marker functions will default to `InstantNow()`.
constexpr MarkerTiming() = default;
// This should only be used by internal profiler code.
[[nodiscard]] bool IsUnspecified() const {
return mStartTime.IsNull() && mEndTime.IsNull();
}
// Full constructor, used by static factory functions.
constexpr MarkerTiming(const TimeStamp& aStartTime, const TimeStamp& aEndTime,
Phase aPhase)
: mStartTime(aStartTime), mEndTime(aEndTime), mPhase(aPhase) {}
static double timeStampToDouble(const TimeStamp& time) {
if (time.IsNull()) {
// The Phase lets us know not to use this value.
return 0;
}
return (time - TimeStamp::ProcessCreation()).ToMilliseconds();
}
TimeStamp mStartTime;
TimeStamp mEndTime;
Phase mPhase = Phase::Instant;
};
// This marker option allows three cases:
// - By default, no stacks are captured.
// - The caller can request a stack capture, and the add-marker code will take
// care of it in the most efficient way.
// - The caller can still provide an existing backtrace, for cases where a
// marker reports something that happened elsewhere.
class MarkerStack {
public:
// Default constructor, no capture.
constexpr MarkerStack() = default;
// Disallow copy.
MarkerStack(const MarkerStack&) = delete;
MarkerStack& operator=(const MarkerStack&) = delete;
// Allow move.
MarkerStack(MarkerStack&& aOther)
: mCaptureOptions(aOther.mCaptureOptions),
mOptionalChunkedBufferStorage(
std::move(aOther.mOptionalChunkedBufferStorage)),
mChunkedBuffer(aOther.mChunkedBuffer) {
AssertInvariants();
aOther.Clear();
}
MarkerStack& operator=(MarkerStack&& aOther) {
mCaptureOptions = aOther.mCaptureOptions;
mOptionalChunkedBufferStorage =
std::move(aOther.mOptionalChunkedBufferStorage);
mChunkedBuffer = aOther.mChunkedBuffer;
AssertInvariants();
aOther.Clear();
return *this;
}
// Take ownership of a backtrace. If null or empty, equivalent to NoStack().
explicit MarkerStack(UniquePtr<ProfileChunkedBuffer>&& aExternalChunkedBuffer)
: mOptionalChunkedBufferStorage(
(!aExternalChunkedBuffer || aExternalChunkedBuffer->IsEmpty())
? nullptr
: std::move(aExternalChunkedBuffer)),
mChunkedBuffer(mOptionalChunkedBufferStorage.get()) {
AssertInvariants();
}
// Use an existing backtrace stored elsewhere, which the user must guarantee
// is alive during the add-marker call. If empty, equivalent to NoStack().
explicit MarkerStack(ProfileChunkedBuffer& aExternalChunkedBuffer)
: mChunkedBuffer(aExternalChunkedBuffer.IsEmpty()
? nullptr
: &aExternalChunkedBuffer) {
AssertInvariants();
}
// Don't capture a stack in this marker.
static MarkerStack NoStack() {
return MarkerStack(StackCaptureOptions::NoStack);
}
// Capture a stack when adding this marker.
static MarkerStack Capture(
StackCaptureOptions aCaptureOptions = StackCaptureOptions::Full) {
// Actual capture will be handled inside profiler_add_marker.
return MarkerStack(aCaptureOptions);
}
// Optionally capture a stack, useful for avoiding long-winded ternaries.
static MarkerStack MaybeCapture(bool aDoCapture) {
return aDoCapture ? Capture() : NoStack();
}
// Use an existing backtrace stored elsewhere, which the user must guarantee
// is alive during the add-marker call. If empty, equivalent to NoStack().
static MarkerStack UseBacktrace(
ProfileChunkedBuffer& aExternalChunkedBuffer) {
return MarkerStack(aExternalChunkedBuffer);
}
// Take ownership of a backtrace previously captured with
// `profiler_capture_backtrace()`. If null, equivalent to NoStack().
static MarkerStack TakeBacktrace(
UniquePtr<ProfileChunkedBuffer>&& aExternalChunkedBuffer) {
return MarkerStack(std::move(aExternalChunkedBuffer));
}
// Construct with the given capture options.
static MarkerStack WithCaptureOptions(StackCaptureOptions aCaptureOptions) {
return MarkerStack(aCaptureOptions);
}
[[nodiscard]] StackCaptureOptions CaptureOptions() const {
return mCaptureOptions;
}
ProfileChunkedBuffer* GetChunkedBuffer() const { return mChunkedBuffer; }
// Use backtrace after a request. If null, equivalent to NoStack().
void UseRequestedBacktrace(ProfileChunkedBuffer* aExternalChunkedBuffer) {
MOZ_RELEASE_ASSERT(mCaptureOptions != StackCaptureOptions::NoStack);
mCaptureOptions = StackCaptureOptions::NoStack;
if (aExternalChunkedBuffer && !aExternalChunkedBuffer->IsEmpty()) {
// We only need to use the provided buffer if it is not empty.
mChunkedBuffer = aExternalChunkedBuffer;
}
AssertInvariants();
}
void Clear() {
mCaptureOptions = StackCaptureOptions::NoStack;
mOptionalChunkedBufferStorage.reset();
mChunkedBuffer = nullptr;
AssertInvariants();
}
private:
explicit MarkerStack(StackCaptureOptions aCaptureOptions)
: mCaptureOptions(aCaptureOptions) {
AssertInvariants();
}
// This should be called after every constructor and non-const function.
void AssertInvariants() const {
#ifdef DEBUG
if (mCaptureOptions != StackCaptureOptions::NoStack) {
MOZ_ASSERT(!mOptionalChunkedBufferStorage,
"We should not hold a buffer when capture is requested");
MOZ_ASSERT(!mChunkedBuffer,
"We should not point at a buffer when capture is requested");
} else {
if (mOptionalChunkedBufferStorage) {
MOZ_ASSERT(mChunkedBuffer == mOptionalChunkedBufferStorage.get(),
"Non-null mOptionalChunkedBufferStorage must be pointed-at "
"by mChunkedBuffer");
}
if (mChunkedBuffer) {
MOZ_ASSERT(!mChunkedBuffer->IsEmpty(),
"Non-null mChunkedBuffer must not be empty");
}
}
#endif // DEBUG
}
StackCaptureOptions mCaptureOptions = StackCaptureOptions::NoStack;
// Optional storage for the backtrace, in case it was captured before the
// add-marker call.
UniquePtr<ProfileChunkedBuffer> mOptionalChunkedBufferStorage;
// If not null, this points to the backtrace. It may point to a backtrace
// temporarily stored on the stack, or to mOptionalChunkedBufferStorage.
ProfileChunkedBuffer* mChunkedBuffer = nullptr;
};
// This marker option captures a given inner window id.
class MarkerInnerWindowId {
public:
// Default constructor, it leaves the id unspecified.
constexpr MarkerInnerWindowId() = default;
// Constructor with a specified inner window id.
constexpr explicit MarkerInnerWindowId(uint64_t i) : mInnerWindowId(i) {}
// Constructor with either specified inner window id or Nothing.
constexpr explicit MarkerInnerWindowId(const Maybe<uint64_t>& i)
: mInnerWindowId(i.valueOr(scNoId)) {}
// Explicit option with unspecified id.
constexpr static MarkerInnerWindowId NoId() { return MarkerInnerWindowId{}; }
[[nodiscard]] bool IsUnspecified() const { return mInnerWindowId == scNoId; }
[[nodiscard]] constexpr uint64_t Id() const { return mInnerWindowId; }
private:
static constexpr uint64_t scNoId = 0;
uint64_t mInnerWindowId = scNoId;
};
// This class combines each of the possible marker options above.
class MarkerOptions {
public:
// Constructor from individual options (including none).
// Implicit to allow `{}` and one option type as-is.
// Options that are not provided here are defaulted. In particular, timing
// defaults to `MarkerTiming::InstantNow()` when the marker is recorded.
template <typename... Options>
MOZ_IMPLICIT MarkerOptions(Options&&... aOptions) {
(Set(std::forward<Options>(aOptions)), ...);
}
// Disallow copy.
MarkerOptions(const MarkerOptions&) = delete;
MarkerOptions& operator=(const MarkerOptions&) = delete;
// Allow move.
MarkerOptions(MarkerOptions&&) = default;
MarkerOptions& operator=(MarkerOptions&&) = default;
// The embedded `MarkerTiming` hasn't been specified yet.
[[nodiscard]] bool IsTimingUnspecified() const {
return mTiming.IsUnspecified();
}
// Each option may be added in a chain by e.g.:
// `options.Set(MarkerThreadId(123)).Set(MarkerTiming::IntervalEnd())`.
// When passed to an add-marker function, it must be an rvalue, either created
// on the spot, or `std::move`d from storage, e.g.:
// `PROFILER_MARKER_UNTYPED("...", std::move(options).Set(...))`;
//
// Options can be read by their name (without "Marker"), e.g.: `o.ThreadId()`.
// Add "Ref" for a non-const reference, e.g.: `o.ThreadIdRef() = ...;`
#define FUNCTIONS_ON_MEMBER(NAME) \
MarkerOptions& Set(Marker##NAME&& a##NAME)& { \
m##NAME = std::move(a##NAME); \
return *this; \
} \
\
MarkerOptions&& Set(Marker##NAME&& a##NAME)&& { \
m##NAME = std::move(a##NAME); \
return std::move(*this); \
} \
\
const Marker##NAME& NAME() const { return m##NAME; } \
\
Marker##NAME& NAME##Ref() { return m##NAME; }
FUNCTIONS_ON_MEMBER(ThreadId);
FUNCTIONS_ON_MEMBER(Timing);
FUNCTIONS_ON_MEMBER(Stack);
FUNCTIONS_ON_MEMBER(InnerWindowId);
#undef FUNCTIONS_ON_MEMBER
private:
friend ProfileBufferEntryReader::Deserializer<MarkerOptions>;
MarkerThreadId mThreadId;
MarkerTiming mTiming;
MarkerStack mStack;
MarkerInnerWindowId mInnerWindowId;
};
} // namespace mozilla
namespace mozilla::baseprofiler::markers {
// Default marker payload types, with no extra information, not even a marker
// type and payload. This is intended for label-only markers.
struct NoPayload final {};
} // namespace mozilla::baseprofiler::markers
namespace mozilla {
class JSONWriter;
// This class collects all the information necessary to stream the JSON schema
// that informs the front-end how to display a type of markers.
// It will be created and populated in `MarkerTypeDisplay()` functions in each
// marker type definition, see Add/Set functions.
class MarkerSchema {
public:
// This is used to describe a C++ type that is expected to be specified to
// the marker and used in PayloadField. This type is the expected input type
// to the marker data.
enum class InputType {
Uint64,
Uint32,
Uint8,
Boolean,
CString,
String,
TimeStamp,
TimeDuration
};
enum class Location : unsigned {
MarkerChart,
MarkerTable,
// This adds markers to the main marker timeline in the header.
TimelineOverview,
// In the timeline, this is a section that breaks out markers that are
// related to memory. When memory counters are enabled, this is its own
// track, otherwise it is displayed with the main thread.
TimelineMemory,
// This adds markers to the IPC timeline area in the header.
TimelineIPC,
// This adds markers to the FileIO timeline area in the header.
TimelineFileIO,
// TODO - This is not supported yet.
StackChart
};
// Used as constructor parameter, to explicitly specify that the location (and
// other display options) are handled as a special case in the front-end.
// In this case, *no* schema will be output for this type.
struct SpecialFrontendLocation {};
enum class Format {
// ----------------------------------------------------
// String types.
// Show the URL, and handle PII sanitization
Url,
// Show the file path, and handle PII sanitization.
FilePath,
// Important, do not put URL or file path information here, as it will not
// be sanitized. Please be careful with including other types of PII here as
// well.
// e.g. "Label: Some String"
String,
// Show a string from a UniqueStringArray given an index in the profile.
// e.g. 1, given string table ["hello", "world"] will show "world"
UniqueString,
// ----------------------------------------------------
// Numeric types
// For time data that represents a duration of time.
// e.g. "Label: 5s, 5ms, 5μs"
Duration,
// Data that happened at a specific time, relative to the start of the
// profile. e.g. "Label: 15.5s, 20.5ms, 30.5μs"
Time,
// The following are alternatives to display a time only in a specific unit
// of time.
Seconds, // "Label: 5s"
Milliseconds, // "Label: 5ms"
Microseconds, // "Label: 5μs"
Nanoseconds, // "Label: 5ns"
// e.g. "Label: 5.55mb, 5 bytes, 312.5kb"
Bytes,
// This should be a value between 0 and 1.
// "Label: 50%"
Percentage,
// The integer should be used for generic representations of numbers.
// Do not use it for time information.
// "Label: 52, 5,323, 1,234,567"
Integer,
// The decimal should be used for generic representations of numbers.
// Do not use it for time information.
// "Label: 52.23, 0.0054, 123,456.78"
Decimal
};
// This represents groups of markers which MarkerTypes can expose to indicate
// what group they belong to (multiple groups are allowed combined in bitwise
// or). This is currently only used for ETW filtering. In the long run this
// should be generalized to gecko markers.
enum class ETWMarkerGroup : uint64_t {
Generic = 1,
UserMarkers = 1 << 1,
Memory = 1 << 2,
Scheduling = 1 << 3,
Text = 1 << 4,
Tracing = 1 << 5
};
// Flags which describe additional information for a PayloadField.
enum class PayloadFlags : uint32_t { None = 0, Searchable = 1 };
// This is one field of payload to be used for additional marker data.
struct PayloadField {
// Key identifying the marker.
const char* Key;
// Input type, this represents the data type specified.
InputType InputTy;
// Label, additional description.
const char* Label = nullptr;
// Format as written to the JSON.
Format Fmt = Format::String;
// Optional PayloadFlags.
PayloadFlags Flags = PayloadFlags::None;
};
enum class Searchable { NotSearchable, Searchable };
enum class GraphType { Line, Bar, FilledLine };
enum class GraphColor {
Blue,
Green,
Grey,
Ink,
Magenta,
Orange,
Purple,
Red,
Teal,
Yellow
};
// Marker schema, with a non-empty list of locations where markers should be
// shown.
// Tech note: Even though `aLocations` are templated arguments, they are
// assigned to an `enum class` object, so they can only be of that enum type.
template <typename... Locations>
explicit MarkerSchema(Location aLocation, Locations... aLocations)
: mLocations{aLocation, aLocations...} {}
// Alternative constructor for MarkerSchema.
explicit MarkerSchema(const mozilla::MarkerSchema::Location* aLocations,
size_t aLength)
: mLocations(aLocations, aLocations + aLength) {}
// Marker schema for types that have special frontend handling.
// Nothing else should be set in this case.
// Implicit to allow quick return from MarkerTypeDisplay functions.
MOZ_IMPLICIT MarkerSchema(SpecialFrontendLocation) {}
// Caller must specify location(s) or SpecialFrontendLocation above.
MarkerSchema() = delete;
// Optional labels in the marker chart, the chart tooltip, and the marker
// table. If not provided, the marker "name" will be used. The given string
// can contain element keys in braces to include data elements streamed by
// `StreamJSONMarkerData()`. E.g.: "This is {text}"
#define LABEL_SETTER(name) \
MarkerSchema& Set##name(std::string a##name) { \
m##name = std::move(a##name); \
return *this; \
}
LABEL_SETTER(ChartLabel)
LABEL_SETTER(TooltipLabel)
LABEL_SETTER(TableLabel)
#undef LABEL_SETTER
MarkerSchema& SetAllLabels(std::string aText) {
// Here we set the same text in each label.
// TODO: Move to a single "label" field once the front-end allows it.
SetChartLabel(aText);
SetTooltipLabel(aText);
SetTableLabel(std::move(aText));
return *this;
}
// Each data element that is streamed by `StreamJSONMarkerData()` can be
// displayed as indicated by using one of the `Add...` function below.
// Each `Add...` will add a line in the full marker description. Parameters:
// - `aKey`: Element property name as streamed by `StreamJSONMarkerData()`.
// - `aLabel`: Optional prefix. Defaults to the key name.
// - `aFormat`: How to format the data element value, see `Format` above.
// - `aSearchable`: Optional, indicates if the value is used in searches,
// defaults to false.
MarkerSchema& AddKeyFormat(std::string aKey, Format aFormat) {
mData.emplace_back(mozilla::VariantType<DynamicData>{},
DynamicData{std::move(aKey), mozilla::Nothing{}, aFormat,
mozilla::Nothing{}});
return *this;
}
MarkerSchema& AddKeyLabelFormat(std::string aKey, std::string aLabel,
Format aFormat) {
mData.emplace_back(
mozilla::VariantType<DynamicData>{},
DynamicData{std::move(aKey), mozilla::Some(std::move(aLabel)), aFormat,
mozilla::Nothing{}});
return *this;
}
MarkerSchema& AddKeyFormatSearchable(std::string aKey, Format aFormat,
Searchable aSearchable) {
mData.emplace_back(mozilla::VariantType<DynamicData>{},
DynamicData{std::move(aKey), mozilla::Nothing{}, aFormat,
mozilla::Some(aSearchable)});
return *this;
}
MarkerSchema& AddKeyLabelFormatSearchable(std::string aKey,
std::string aLabel, Format aFormat,
Searchable aSearchable) {
mData.emplace_back(
mozilla::VariantType<DynamicData>{},
DynamicData{std::move(aKey), mozilla::Some(std::move(aLabel)), aFormat,
mozilla::Some(aSearchable)});
return *this;
}
// The display may also include static rows.
MarkerSchema& AddStaticLabelValue(std::string aLabel, std::string aValue) {
mData.emplace_back(mozilla::VariantType<StaticData>{},
StaticData{std::move(aLabel), std::move(aValue)});
return *this;
}
// Markers can be shown as timeline tracks.
MarkerSchema& AddChart(std::string aKey, GraphType aType) {
mGraphs.emplace_back(GraphData{std::move(aKey), aType, mozilla::Nothing{}});
return *this;
}
MarkerSchema& AddChartColor(std::string aKey, GraphType aType,
GraphColor aColor) {
mGraphs.emplace_back(
GraphData{std::move(aKey), aType, mozilla::Some(aColor)});
return *this;
}
// Internal streaming function.
MFBT_API void Stream(JSONWriter& aWriter, const Span<const char>& aName) &&;
private:
MFBT_API static Span<const char> LocationToStringSpan(Location aLocation);
MFBT_API static Span<const char> FormatToStringSpan(Format aFormat);
MFBT_API static Span<const char> GraphTypeToStringSpan(GraphType aType);
MFBT_API static Span<const char> GraphColorToStringSpan(GraphColor aColor);
// List of marker display locations. Empty for SpecialFrontendLocation.
std::vector<Location> mLocations;
// Labels for different places.
std::string mChartLabel;
std::string mTooltipLabel;
std::string mTableLabel;
// Main display, made of zero or more rows of key+label+format or label+value.
private:
struct DynamicData {
std::string mKey;
mozilla::Maybe<std::string> mLabel;
Format mFormat;
mozilla::Maybe<Searchable> mSearchable;
};
struct StaticData {
std::string mLabel;
std::string mValue;
};
using DataRow = mozilla::Variant<DynamicData, StaticData>;
using DataRowVector = std::vector<DataRow>;
DataRowVector mData;
struct GraphData {
std::string mKey;
GraphType mType;
mozilla::Maybe<GraphColor> mColor;
};
std::vector<GraphData> mGraphs;
};
namespace detail {
// GCC doesn't allow this to live inside the class.
template <typename PayloadType>
static void StreamPayload(baseprofiler::SpliceableJSONWriter& aWriter,
const Span<const char> aKey,
const PayloadType& aPayload) {
aWriter.StringProperty(aKey, aPayload);
}
template <typename PayloadType>
inline void StreamPayload(baseprofiler::SpliceableJSONWriter& aWriter,
const Span<const char> aKey,
const Maybe<PayloadType>& aPayload) {
if (aPayload.isSome()) {
StreamPayload(aWriter, aKey, *aPayload);
} else {
aWriter.NullProperty(aKey);
}
}
template <>
inline void StreamPayload<bool>(baseprofiler::SpliceableJSONWriter& aWriter,
const Span<const char> aKey,
const bool& aPayload) {
aWriter.BoolProperty(aKey, aPayload);
}
template <>
inline void StreamPayload<ProfilerString8View>(
baseprofiler::SpliceableJSONWriter& aWriter, const Span<const char> aKey,
const ProfilerString8View& aPayload) {
aWriter.StringProperty(aKey, aPayload);
}
} // namespace detail
// This helper class is used by MarkerTypes that want to support the general
// MarkerType object schema. When using this the markers will also transmit
// their payload to the ETW tracer as well as requiring less inline code.
// This is a curiously recurring template, the template argument is the child
// class itself.
template <typename T>
struct BaseMarkerType {
static constexpr const char* AllLabels = nullptr;
static constexpr const char* ChartLabel = nullptr;
static constexpr const char* TableLabel = nullptr;
static constexpr const char* TooltipLabel = nullptr;
// This indicates whether this marker type wants the names passed to the
// individual marker calls stores along with the marker.
static constexpr bool StoreName = false;
static constexpr MarkerSchema::ETWMarkerGroup Group =
MarkerSchema::ETWMarkerGroup::Generic;
static MarkerSchema MarkerTypeDisplay() {
using MS = MarkerSchema;
MS schema{T::Locations, std::size(T::Locations)};
if (T::AllLabels) {
schema.SetAllLabels(T::AllLabels);
}
if (T::ChartLabel) {
schema.SetChartLabel(T::ChartLabel);
}
if (T::TableLabel) {
schema.SetTableLabel(T::TableLabel);
}
if (T::TooltipLabel) {
schema.SetTooltipLabel(T::TooltipLabel);
}
for (const MS::PayloadField field : T::PayloadFields) {
if (field.Label) {
if (uint32_t(field.Flags) & uint32_t(MS::PayloadFlags::Searchable)) {
schema.AddKeyLabelFormatSearchable(field.Key, field.Label, field.Fmt,
MS::Searchable::Searchable);
} else {
schema.AddKeyLabelFormat(field.Key, field.Label, field.Fmt);
}
} else {
if (uint32_t(field.Flags) & uint32_t(MS::PayloadFlags::Searchable)) {
schema.AddKeyFormatSearchable(field.Key, field.Fmt,
MS::Searchable::Searchable);
} else {
schema.AddKeyFormat(field.Key, field.Fmt);
}
}
}
if (T::Description) {
schema.AddStaticLabelValue("Description", T::Description);
}
return schema;
}
static constexpr Span<const char> MarkerTypeName() {
return MakeStringSpan(T::Name);
}
// This is called by the child class since the child class version of this
// function is used to infer the argument types by the profile buffer and
// allows the child to do any special data conversion it needs to do.
// Optionally the child can opt not to use this at all and write the data
// out itself.
template <typename... PayloadArguments>
static void StreamJSONMarkerDataImpl(
baseprofiler::SpliceableJSONWriter& aWriter,
const PayloadArguments&... aPayloadArguments) {
size_t i = 0;
(detail::StreamPayload(aWriter, MakeStringSpan(T::PayloadFields[i++].Key),
aPayloadArguments),
...);
}
};
} // namespace mozilla
#endif // BaseProfilerMarkersPrerequisites_h