summaryrefslogtreecommitdiffstats
path: root/mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h')
-rw-r--r--mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h736
1 files changed, 736 insertions, 0 deletions
diff --git a/mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h b/mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h
new file mode 100644
index 0000000000..174dd36bca
--- /dev/null
+++ b/mozglue/baseprofiler/public/BaseProfilerMarkersDetail.h
@@ -0,0 +1,736 @@
+/* -*- 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 BaseProfilerMarkersDetail_h
+#define BaseProfilerMarkersDetail_h
+
+#ifndef BaseProfilerMarkers_h
+# error "This header should only be #included by BaseProfilerMarkers.h"
+#endif
+
+#include "mozilla/BaseProfilerMarkersPrerequisites.h"
+
+// ~~ HERE BE DRAGONS ~~
+//
+// Everything below is internal implementation detail, you shouldn't need to
+// look at it unless working on the profiler code.
+
+#include "mozilla/BaseProfileJSONWriter.h"
+#include "mozilla/ProfileBufferEntryKinds.h"
+
+#include <limits>
+#include <tuple>
+
+namespace mozilla::baseprofiler {
+// Implemented in platform.cpp
+MFBT_API ProfileChunkedBuffer& profiler_get_core_buffer();
+} // namespace mozilla::baseprofiler
+
+namespace mozilla::base_profiler_markers_detail {
+
+struct Streaming {
+ // A `MarkerDataDeserializer` is a free function that can read a serialized
+ // payload from an `EntryReader` and streams it as JSON object properties.
+ using MarkerDataDeserializer = void (*)(ProfileBufferEntryReader&,
+ baseprofiler::SpliceableJSONWriter&);
+
+ // A `MarkerTypeNameFunction` is a free function that returns the name of the
+ // marker type.
+ using MarkerTypeNameFunction = Span<const char> (*)();
+
+ // A `MarkerSchemaFunction` is a free function that returns a
+ // `MarkerSchema`, which contains all the information needed to stream
+ // the display schema associated with a marker type.
+ using MarkerSchemaFunction = MarkerSchema (*)();
+
+ struct MarkerTypeFunctions {
+ MarkerDataDeserializer mMarkerDataDeserializer = nullptr;
+ MarkerTypeNameFunction mMarkerTypeNameFunction = nullptr;
+ MarkerSchemaFunction mMarkerSchemaFunction = nullptr;
+ };
+
+ // A `DeserializerTag` will be added before the payload, to help select the
+ // correct deserializer when reading back the payload.
+ using DeserializerTag = uint8_t;
+
+ // Store a deserializer (and other marker-type-specific functions) and get its
+ // `DeserializerTag`.
+ // This is intended to be only used once per deserializer when a new marker
+ // type is used for the first time, so it should be called to initialize a
+ // `static const` tag that will be re-used by all markers of the corresponding
+ // payload type -- see use below.
+ MFBT_API static DeserializerTag TagForMarkerTypeFunctions(
+ MarkerDataDeserializer aDeserializer,
+ MarkerTypeNameFunction aMarkerTypeNameFunction,
+ MarkerSchemaFunction aMarkerSchemaFunction);
+
+ // Get the `MarkerDataDeserializer` for a given `DeserializerTag`.
+ MFBT_API static MarkerDataDeserializer DeserializerForTag(
+ DeserializerTag aTag);
+
+ // Retrieve all MarkerTypeFunctions's.
+ // While this object lives, no other operations can happen on this list.
+ class LockedMarkerTypeFunctionsList {
+ public:
+ MFBT_API LockedMarkerTypeFunctionsList();
+ MFBT_API ~LockedMarkerTypeFunctionsList();
+
+ LockedMarkerTypeFunctionsList(const LockedMarkerTypeFunctionsList&) =
+ delete;
+ LockedMarkerTypeFunctionsList& operator=(
+ const LockedMarkerTypeFunctionsList&) = delete;
+
+ auto begin() const { return mMarkerTypeFunctionsSpan.begin(); }
+ auto end() const { return mMarkerTypeFunctionsSpan.end(); }
+
+ private:
+ Span<const MarkerTypeFunctions> mMarkerTypeFunctionsSpan;
+ };
+};
+
+// This helper will examine a marker type's `StreamJSONMarkerData` function, see
+// specialization below.
+template <typename T>
+struct StreamFunctionTypeHelper;
+
+// Helper specialization that takes the expected
+// `StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter&, ...)` function and
+// provide information about the `...` parameters.
+template <typename R, typename... As>
+struct StreamFunctionTypeHelper<R(baseprofiler::SpliceableJSONWriter&, As...)> {
+ constexpr static size_t scArity = sizeof...(As);
+ using TupleType =
+ std::tuple<std::remove_cv_t<std::remove_reference_t<As>>...>;
+
+ // Serialization function that takes the exact same parameter types
+ // (const-ref'd) as `StreamJSONMarkerData`. This has to be inside the helper
+ // because only here can we access the raw parameter pack `As...`.
+ // And because we're using the same argument types through
+ // references-to-const, permitted implicit conversions can happen.
+ static ProfileBufferBlockIndex Serialize(
+ ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName,
+ const MarkerCategory& aCategory, MarkerOptions&& aOptions,
+ Streaming::DeserializerTag aDeserializerTag, const As&... aAs) {
+ // Note that options are first after the entry kind, because they contain
+ // the thread id, which is handled first to filter markers by threads.
+ return aBuffer.PutObjects(ProfileBufferEntryKind::Marker, aOptions, aName,
+ aCategory, aDeserializerTag,
+ MarkerPayloadType::Cpp, aAs...);
+ }
+};
+
+// Helper for a marker type.
+// A marker type is defined in a `struct` with some expected static member
+// functions. See example in BaseProfilerMarkers.h.
+template <typename MarkerType>
+struct MarkerTypeSerialization {
+ // Definitions to access the expected
+ // `StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter&, ...)` function
+ // and its parameters.
+ using StreamFunctionType =
+ StreamFunctionTypeHelper<decltype(MarkerType::StreamJSONMarkerData)>;
+ constexpr static size_t scStreamFunctionParameterCount =
+ StreamFunctionType::scArity;
+ using StreamFunctionUserParametersTuple =
+ typename StreamFunctionType::TupleType;
+ template <size_t i>
+ using StreamFunctionParameter =
+ std::tuple_element_t<i, StreamFunctionUserParametersTuple>;
+
+ template <typename... Ts>
+ static ProfileBufferBlockIndex Serialize(ProfileChunkedBuffer& aBuffer,
+ const ProfilerString8View& aName,
+ const MarkerCategory& aCategory,
+ MarkerOptions&& aOptions,
+ const Ts&... aTs) {
+ static_assert(!std::is_same_v<MarkerType,
+ ::mozilla::baseprofiler::markers::NoPayload>,
+ "NoPayload should have been handled in the caller.");
+ // Register marker type functions, and get the tag for this deserializer.
+ // Note that the tag is stored in a function-static object, and this
+ // function is static in a templated struct, so there should only be one tag
+ // per MarkerType.
+ // Making the tag class-static may have been more efficient (to avoid a
+ // thread-safe init check at every call), but random global static
+ // initialization order would make it more complex to coordinate with
+ // `Streaming::TagForMarkerTypeFunctions()`, and also would add a (small)
+ // cost for everybody, even the majority of users not using the profiler.
+ static const Streaming::DeserializerTag tag =
+ Streaming::TagForMarkerTypeFunctions(Deserialize,
+ MarkerType::MarkerTypeName,
+ MarkerType::MarkerTypeDisplay);
+ return StreamFunctionType::Serialize(aBuffer, aName, aCategory,
+ std::move(aOptions), tag, aTs...);
+ }
+
+ private:
+ // This templated function will recursively deserialize each argument expected
+ // by `MarkerType::StreamJSONMarkerData()` on the stack, and call it at the
+ // end. E.g., for `StreamJSONMarkerData(int, char)`:
+ // - DeserializeArguments<0>(aER, aWriter) reads an int and calls:
+ // - DeserializeArguments<1>(aER, aWriter, const int&) reads a char and calls:
+ // - MarkerType::StreamJSONMarkerData(aWriter, const int&, const char&).
+ // Prototyping on godbolt showed that clang and gcc can flatten these
+ // recursive calls into one function with successive reads followed by the one
+ // stream call; tested up to 40 arguments: https://godbolt.org/z/5KeeM4
+ template <size_t i = 0, typename... Args>
+ static void DeserializeArguments(ProfileBufferEntryReader& aEntryReader,
+ baseprofiler::SpliceableJSONWriter& aWriter,
+ const Args&... aArgs) {
+ static_assert(sizeof...(Args) == i,
+ "We should have collected `i` arguments so far");
+ if constexpr (i < scStreamFunctionParameterCount) {
+ // Deserialize the i-th argument on this stack.
+ auto argument = aEntryReader.ReadObject<StreamFunctionParameter<i>>();
+ // Add our local argument to the next recursive call.
+ DeserializeArguments<i + 1>(aEntryReader, aWriter, aArgs..., argument);
+ } else {
+ // We've read all the arguments, finally call the `StreamJSONMarkerData`
+ // function, which should write the appropriate JSON elements for this
+ // marker type. Note that the MarkerType-specific "type" element is
+ // already written.
+ MarkerType::StreamJSONMarkerData(aWriter, aArgs...);
+ }
+ }
+
+ public:
+ static void Deserialize(ProfileBufferEntryReader& aEntryReader,
+ baseprofiler::SpliceableJSONWriter& aWriter) {
+ aWriter.StringProperty("type", MarkerType::MarkerTypeName());
+ DeserializeArguments(aEntryReader, aWriter);
+ }
+};
+
+template <>
+struct MarkerTypeSerialization<::mozilla::baseprofiler::markers::NoPayload> {
+ // Nothing! NoPayload has special handling avoiding payload work.
+};
+
+template <typename MarkerType, typename... Ts>
+static ProfileBufferBlockIndex AddMarkerWithOptionalStackToBuffer(
+ ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName,
+ const MarkerCategory& aCategory, MarkerOptions&& aOptions,
+ const Ts&... aTs) {
+ if constexpr (std::is_same_v<MarkerType,
+ ::mozilla::baseprofiler::markers::NoPayload>) {
+ static_assert(sizeof...(Ts) == 0,
+ "NoPayload does not accept any payload arguments.");
+ // Special case for NoPayload where there is a stack or inner window id:
+ // Because these options would be stored in the payload 'data' object, but
+ // there is no such object for NoPayload, we convert the marker to another
+ // type (without user fields in the 'data' object), so that the stack and/or
+ // inner window id are not lost.
+ // TODO: Remove this when bug 1646714 lands.
+ if (aOptions.Stack().GetChunkedBuffer() ||
+ !aOptions.InnerWindowId().IsUnspecified()) {
+ struct NoPayloadUserData {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("NoPayloadUserData");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter) {
+ // No user payload.
+ }
+ static mozilla::MarkerSchema MarkerTypeDisplay() {
+ using MS = mozilla::MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ // No user data to display.
+ return schema;
+ }
+ };
+ return MarkerTypeSerialization<NoPayloadUserData>::Serialize(
+ aBuffer, aName, aCategory, std::move(aOptions));
+ }
+
+ // Note that options are first after the entry kind, because they contain
+ // the thread id, which is handled first to filter markers by threads.
+ return aBuffer.PutObjects(
+ ProfileBufferEntryKind::Marker, std::move(aOptions), aName, aCategory,
+ base_profiler_markers_detail::Streaming::DeserializerTag(0));
+ } else {
+ return MarkerTypeSerialization<MarkerType>::Serialize(
+ aBuffer, aName, aCategory, std::move(aOptions), aTs...);
+ }
+}
+
+// Pointer to a function that can capture a backtrace into the provided
+// `ProfileChunkedBuffer`, and returns true when successful.
+using BacktraceCaptureFunction = bool (*)(ProfileChunkedBuffer&,
+ StackCaptureOptions);
+
+// Use a pre-allocated and cleared chunked buffer in the main thread's
+// `AddMarkerToBuffer()`.
+// Null if not the main thread, or if profilers are not active.
+MFBT_API ProfileChunkedBuffer* GetClearedBufferForMainThreadAddMarker();
+// Called by the profiler(s) when starting/stopping. Safe to nest.
+MFBT_API void EnsureBufferForMainThreadAddMarker();
+MFBT_API void ReleaseBufferForMainThreadAddMarker();
+
+// Add a marker with the given name, options, and arguments to the given buffer.
+// Because this may be called from either Base or Gecko Profiler functions, the
+// appropriate backtrace-capturing function must also be provided.
+template <typename MarkerType, typename... Ts>
+ProfileBufferBlockIndex AddMarkerToBuffer(
+ ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName,
+ const MarkerCategory& aCategory, MarkerOptions&& aOptions,
+ BacktraceCaptureFunction aBacktraceCaptureFunction, const Ts&... aTs) {
+ if (aOptions.ThreadId().IsUnspecified()) {
+ // If yet unspecified, set thread to this thread where the marker is added.
+ aOptions.Set(MarkerThreadId::CurrentThread());
+ }
+
+ if (aOptions.IsTimingUnspecified()) {
+ // If yet unspecified, set timing to this instant of adding the marker.
+ aOptions.Set(MarkerTiming::InstantNow());
+ }
+
+ StackCaptureOptions captureOptions = aOptions.Stack().CaptureOptions();
+ if (captureOptions != StackCaptureOptions::NoStack) {
+ // A capture was requested, let's attempt to do it here&now. This avoids a
+ // lot of allocations that would be necessary if capturing a backtrace
+ // separately.
+ // TODO reduce internal profiler stack levels, see bug 1659872.
+ auto CaptureStackAndAddMarker = [&](ProfileChunkedBuffer& aChunkedBuffer) {
+ aOptions.StackRef().UseRequestedBacktrace(
+ aBacktraceCaptureFunction(aChunkedBuffer, captureOptions)
+ ? &aChunkedBuffer
+ : nullptr);
+ // This call must be made from here, while chunkedBuffer is in scope.
+ return AddMarkerWithOptionalStackToBuffer<MarkerType>(
+ aBuffer, aName, aCategory, std::move(aOptions), aTs...);
+ };
+
+ if (ProfileChunkedBuffer* buffer = GetClearedBufferForMainThreadAddMarker();
+ buffer) {
+ // Use a pre-allocated buffer for the main thread (because it's the most
+ // used thread, and most sensitive to overhead), so it's only allocated
+ // once. It could be null if this is not the main thread, or no profilers
+ // are currently active.
+ return CaptureStackAndAddMarker(*buffer);
+ }
+ // TODO use a local on-stack byte buffer to remove last allocation.
+ ProfileBufferChunkManagerSingle chunkManager(
+ ProfileBufferChunkManager::scExpectedMaximumStackSize);
+ ProfileChunkedBuffer chunkedBuffer(
+ ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager);
+ return CaptureStackAndAddMarker(chunkedBuffer);
+ }
+
+ return AddMarkerWithOptionalStackToBuffer<MarkerType>(
+ aBuffer, aName, aCategory, std::move(aOptions), aTs...);
+}
+
+// Assuming aEntryReader points right after the entry type (being Marker), this
+// reads the remainder of the marker and outputs it.
+// - GetWriterForThreadCallback, called first, after the thread id is read:
+// (ThreadId) -> SpliceableJSONWriter* or null
+// If null, nothing will be output, but aEntryReader will still be read fully.
+// - StackCallback, only called if GetWriterForThreadCallback didn't return
+// null, and if the marker contains a stack:
+// (ProfileChunkedBuffer&) -> void
+// - RustMarkerCallback, only called if GetWriterForThreadCallback didn't return
+// null, and if the marker contains a Rust payload:
+// (DeserializerTag) -> void
+template <typename GetWriterForThreadCallback, typename StackCallback,
+ typename RustMarkerCallback>
+void DeserializeAfterKindAndStream(
+ ProfileBufferEntryReader& aEntryReader,
+ GetWriterForThreadCallback&& aGetWriterForThreadCallback,
+ StackCallback&& aStackCallback, RustMarkerCallback&& aRustMarkerCallback) {
+ // Each entry is made up of the following:
+ // ProfileBufferEntry::Kind::Marker, <- already read by caller
+ // options, <- next location in entries
+ // name,
+ // payload
+ const MarkerOptions options = aEntryReader.ReadObject<MarkerOptions>();
+
+ baseprofiler::SpliceableJSONWriter* writer =
+ std::forward<GetWriterForThreadCallback>(aGetWriterForThreadCallback)(
+ options.ThreadId().ThreadId());
+ if (!writer) {
+ // No writer associated with this thread id, drop it.
+ aEntryReader.SetRemainingBytes(0);
+ return;
+ }
+
+ // Write the information to JSON with the following schema:
+ // [name, startTime, endTime, phase, category, data]
+ writer->StartArrayElement();
+ {
+ writer->UniqueStringElement(aEntryReader.ReadObject<ProfilerString8View>());
+
+ const double startTime = options.Timing().GetStartTime();
+ writer->TimeDoubleMsElement(startTime);
+
+ const double endTime = options.Timing().GetEndTime();
+ writer->TimeDoubleMsElement(endTime);
+
+ writer->IntElement(static_cast<int64_t>(options.Timing().MarkerPhase()));
+
+ MarkerCategory category = aEntryReader.ReadObject<MarkerCategory>();
+ writer->IntElement(static_cast<int64_t>(category.GetCategory()));
+
+ if (const auto tag =
+ aEntryReader.ReadObject<mozilla::base_profiler_markers_detail::
+ Streaming::DeserializerTag>();
+ tag != 0) {
+ writer->StartObjectElement();
+ {
+ // Stream "common props".
+
+ // TODO: Move this to top-level tuple, when frontend supports it.
+ if (!options.InnerWindowId().IsUnspecified()) {
+ // Here, we are converting uint64_t to double. Both Browsing Context
+ // and Inner Window IDs are created using
+ // `nsContentUtils::GenerateProcessSpecificId`, which is specifically
+ // designed to only use 53 of the 64 bits to be lossless when passed
+ // into and out of JS as a double.
+ writer->DoubleProperty(
+ "innerWindowID",
+ static_cast<double>(options.InnerWindowId().Id()));
+ }
+
+ // TODO: Move this to top-level tuple, when frontend supports it.
+ if (ProfileChunkedBuffer* chunkedBuffer =
+ options.Stack().GetChunkedBuffer();
+ chunkedBuffer) {
+ writer->StartObjectProperty("stack");
+ { std::forward<StackCallback>(aStackCallback)(*chunkedBuffer); }
+ writer->EndObject();
+ }
+
+ auto payloadType = static_cast<mozilla::MarkerPayloadType>(
+ aEntryReader
+ .ReadObject<mozilla::MarkerPayloadTypeUnderlyingType>());
+
+ // Stream the payload, including the type.
+ switch (payloadType) {
+ case mozilla::MarkerPayloadType::Cpp: {
+ mozilla::base_profiler_markers_detail::Streaming::
+ MarkerDataDeserializer deserializer =
+ mozilla::base_profiler_markers_detail::Streaming::
+ DeserializerForTag(tag);
+ MOZ_RELEASE_ASSERT(deserializer);
+ deserializer(aEntryReader, *writer);
+ MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u);
+ break;
+ }
+ case mozilla::MarkerPayloadType::Rust:
+ std::forward<RustMarkerCallback>(aRustMarkerCallback)(tag);
+ MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown payload type.");
+ break;
+ }
+ }
+ writer->EndObject();
+ }
+ }
+ writer->EndArray();
+ MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u);
+}
+
+} // namespace mozilla::base_profiler_markers_detail
+
+namespace mozilla {
+
+// ----------------------------------------------------------------------------
+// Serializer, Deserializer: ProfilerStringView<CHAR>
+
+// The serialization starts with a ULEB128 number that encodes both whether the
+// ProfilerStringView is literal (Least Significant Bit = 0) or not (LSB = 1),
+// plus the string length (excluding null terminator) in bytes, shifted left by
+// 1 bit. Following that number:
+// - If literal, the string pointer value.
+// - If non-literal, the contents as bytes (excluding null terminator if any).
+template <typename CHAR>
+struct ProfileBufferEntryWriter::Serializer<ProfilerStringView<CHAR>> {
+ static Length Bytes(const ProfilerStringView<CHAR>& aString) {
+ MOZ_RELEASE_ASSERT(
+ aString.Length() < std::numeric_limits<Length>::max() / 2,
+ "Double the string length doesn't fit in Length type");
+ const Length stringLength = static_cast<Length>(aString.Length());
+ if (aString.IsLiteral()) {
+ // Literal -> Length shifted left and LSB=0, then pointer.
+ return ULEB128Size(stringLength << 1 | 0u) +
+ static_cast<ProfileChunkedBuffer::Length>(sizeof(const CHAR*));
+ }
+ // Non-literal -> Length shifted left and LSB=1, then string size in bytes.
+ return ULEB128Size((stringLength << 1) | 1u) + stringLength * sizeof(CHAR);
+ }
+
+ static void Write(ProfileBufferEntryWriter& aEW,
+ const ProfilerStringView<CHAR>& aString) {
+ MOZ_RELEASE_ASSERT(
+ aString.Length() < std::numeric_limits<Length>::max() / 2,
+ "Double the string length doesn't fit in Length type");
+ const Span<const CHAR> span = aString;
+ if (aString.IsLiteral()) {
+ // Literal -> Length shifted left and LSB=0, then pointer.
+ aEW.WriteULEB128(span.Length() << 1 | 0u);
+ aEW.WriteObject(WrapProfileBufferRawPointer(span.Elements()));
+ return;
+ }
+ // Non-literal -> Length shifted left and LSB=1, then string size in bytes.
+ aEW.WriteULEB128(span.Length() << 1 | 1u);
+ aEW.WriteBytes(span.Elements(), span.LengthBytes());
+ }
+};
+
+template <typename CHAR>
+struct ProfileBufferEntryReader::Deserializer<ProfilerStringView<CHAR>> {
+ static void ReadInto(ProfileBufferEntryReader& aER,
+ ProfilerStringView<CHAR>& aString) {
+ aString = Read(aER);
+ }
+
+ static ProfilerStringView<CHAR> Read(ProfileBufferEntryReader& aER) {
+ const Length lengthAndIsLiteral = aER.ReadULEB128<Length>();
+ const Length stringLength = lengthAndIsLiteral >> 1;
+ if ((lengthAndIsLiteral & 1u) == 0u) {
+ // LSB==0 -> Literal string, read the string pointer.
+ return ProfilerStringView<CHAR>(
+ aER.ReadObject<const CHAR*>(), stringLength,
+ ProfilerStringView<CHAR>::Ownership::Literal);
+ }
+ // LSB==1 -> Not a literal string.
+ ProfileBufferEntryReader::DoubleSpanOfConstBytes spans =
+ aER.ReadSpans(stringLength * sizeof(CHAR));
+ if (MOZ_LIKELY(spans.IsSingleSpan()) &&
+ reinterpret_cast<uintptr_t>(spans.mFirstOrOnly.Elements()) %
+ alignof(CHAR) ==
+ 0u) {
+ // Only a single span, correctly aligned for the CHAR type, we can just
+ // refer to it directly, assuming that this ProfilerStringView will not
+ // outlive the chunk.
+ return ProfilerStringView<CHAR>(
+ reinterpret_cast<const CHAR*>(spans.mFirstOrOnly.Elements()),
+ stringLength, ProfilerStringView<CHAR>::Ownership::Reference);
+ } else {
+ // Two spans, we need to concatenate them; or one span, but misaligned.
+ // Allocate a buffer to store the string (plus terminal, for safety), and
+ // give it to the ProfilerStringView; Note that this is a secret use of
+ // ProfilerStringView, which is intended to only be used between
+ // deserialization and JSON streaming.
+ CHAR* buffer = new CHAR[stringLength + 1];
+ spans.CopyBytesTo(buffer);
+ buffer[stringLength] = CHAR(0);
+ return ProfilerStringView<CHAR>(
+ buffer, stringLength,
+ ProfilerStringView<CHAR>::Ownership::OwnedThroughStringView);
+ }
+ }
+};
+
+// Serializer, Deserializer: MarkerCategory
+
+// The serialization contains both category numbers encoded as ULEB128.
+template <>
+struct ProfileBufferEntryWriter::Serializer<MarkerCategory> {
+ static Length Bytes(const MarkerCategory& aCategory) {
+ return ULEB128Size(static_cast<uint32_t>(aCategory.CategoryPair()));
+ }
+
+ static void Write(ProfileBufferEntryWriter& aEW,
+ const MarkerCategory& aCategory) {
+ aEW.WriteULEB128(static_cast<uint32_t>(aCategory.CategoryPair()));
+ }
+};
+
+template <>
+struct ProfileBufferEntryReader::Deserializer<MarkerCategory> {
+ static void ReadInto(ProfileBufferEntryReader& aER,
+ MarkerCategory& aCategory) {
+ aCategory = Read(aER);
+ }
+
+ static MarkerCategory Read(ProfileBufferEntryReader& aER) {
+ return MarkerCategory(static_cast<baseprofiler::ProfilingCategoryPair>(
+ aER.ReadULEB128<uint32_t>()));
+ }
+};
+
+// ----------------------------------------------------------------------------
+// Serializer, Deserializer: MarkerTiming
+
+// The serialization starts with the marker phase, followed by one or two
+// timestamps as needed.
+template <>
+struct ProfileBufferEntryWriter::Serializer<MarkerTiming> {
+ static Length Bytes(const MarkerTiming& aTiming) {
+ MOZ_ASSERT(!aTiming.IsUnspecified());
+ const auto phase = aTiming.MarkerPhase();
+ switch (phase) {
+ case MarkerTiming::Phase::Instant:
+ return SumBytes(phase, aTiming.StartTime());
+ case MarkerTiming::Phase::Interval:
+ return SumBytes(phase, aTiming.StartTime(), aTiming.EndTime());
+ case MarkerTiming::Phase::IntervalStart:
+ return SumBytes(phase, aTiming.StartTime());
+ case MarkerTiming::Phase::IntervalEnd:
+ return SumBytes(phase, aTiming.EndTime());
+ default:
+ MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant ||
+ phase == MarkerTiming::Phase::Interval ||
+ phase == MarkerTiming::Phase::IntervalStart ||
+ phase == MarkerTiming::Phase::IntervalEnd);
+ return 0; // Only to avoid build errors.
+ }
+ }
+
+ static void Write(ProfileBufferEntryWriter& aEW,
+ const MarkerTiming& aTiming) {
+ MOZ_ASSERT(!aTiming.IsUnspecified());
+ const auto phase = aTiming.MarkerPhase();
+ switch (phase) {
+ case MarkerTiming::Phase::Instant:
+ aEW.WriteObjects(phase, aTiming.StartTime());
+ return;
+ case MarkerTiming::Phase::Interval:
+ aEW.WriteObjects(phase, aTiming.StartTime(), aTiming.EndTime());
+ return;
+ case MarkerTiming::Phase::IntervalStart:
+ aEW.WriteObjects(phase, aTiming.StartTime());
+ return;
+ case MarkerTiming::Phase::IntervalEnd:
+ aEW.WriteObjects(phase, aTiming.EndTime());
+ return;
+ default:
+ MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant ||
+ phase == MarkerTiming::Phase::Interval ||
+ phase == MarkerTiming::Phase::IntervalStart ||
+ phase == MarkerTiming::Phase::IntervalEnd);
+ return;
+ }
+ }
+};
+
+template <>
+struct ProfileBufferEntryReader::Deserializer<MarkerTiming> {
+ static void ReadInto(ProfileBufferEntryReader& aER, MarkerTiming& aTiming) {
+ aTiming.mPhase = aER.ReadObject<MarkerTiming::Phase>();
+ switch (aTiming.mPhase) {
+ case MarkerTiming::Phase::Instant:
+ aTiming.mStartTime = aER.ReadObject<TimeStamp>();
+ aTiming.mEndTime = TimeStamp{};
+ break;
+ case MarkerTiming::Phase::Interval:
+ aTiming.mStartTime = aER.ReadObject<TimeStamp>();
+ aTiming.mEndTime = aER.ReadObject<TimeStamp>();
+ break;
+ case MarkerTiming::Phase::IntervalStart:
+ aTiming.mStartTime = aER.ReadObject<TimeStamp>();
+ aTiming.mEndTime = TimeStamp{};
+ break;
+ case MarkerTiming::Phase::IntervalEnd:
+ aTiming.mStartTime = TimeStamp{};
+ aTiming.mEndTime = aER.ReadObject<TimeStamp>();
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(aTiming.mPhase == MarkerTiming::Phase::Instant ||
+ aTiming.mPhase == MarkerTiming::Phase::Interval ||
+ aTiming.mPhase ==
+ MarkerTiming::Phase::IntervalStart ||
+ aTiming.mPhase == MarkerTiming::Phase::IntervalEnd);
+ break;
+ }
+ }
+
+ static MarkerTiming Read(ProfileBufferEntryReader& aER) {
+ TimeStamp start;
+ TimeStamp end;
+ auto phase = aER.ReadObject<MarkerTiming::Phase>();
+ switch (phase) {
+ case MarkerTiming::Phase::Instant:
+ start = aER.ReadObject<TimeStamp>();
+ break;
+ case MarkerTiming::Phase::Interval:
+ start = aER.ReadObject<TimeStamp>();
+ end = aER.ReadObject<TimeStamp>();
+ break;
+ case MarkerTiming::Phase::IntervalStart:
+ start = aER.ReadObject<TimeStamp>();
+ break;
+ case MarkerTiming::Phase::IntervalEnd:
+ end = aER.ReadObject<TimeStamp>();
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant ||
+ phase == MarkerTiming::Phase::Interval ||
+ phase == MarkerTiming::Phase::IntervalStart ||
+ phase == MarkerTiming::Phase::IntervalEnd);
+ break;
+ }
+ return MarkerTiming(start, end, phase);
+ }
+};
+
+// ----------------------------------------------------------------------------
+// Serializer, Deserializer: MarkerStack
+
+// The serialization only contains the `ProfileChunkedBuffer` from the
+// backtrace; if there is no backtrace or if it's empty, this will implicitly
+// store a nullptr (see
+// `ProfileBufferEntryWriter::Serializer<ProfilerChunkedBuffer*>`).
+template <>
+struct ProfileBufferEntryWriter::Serializer<MarkerStack> {
+ static Length Bytes(const MarkerStack& aStack) {
+ return SumBytes(aStack.GetChunkedBuffer());
+ }
+
+ static void Write(ProfileBufferEntryWriter& aEW, const MarkerStack& aStack) {
+ aEW.WriteObject(aStack.GetChunkedBuffer());
+ }
+};
+
+template <>
+struct ProfileBufferEntryReader::Deserializer<MarkerStack> {
+ static void ReadInto(ProfileBufferEntryReader& aER, MarkerStack& aStack) {
+ aStack = Read(aER);
+ }
+
+ static MarkerStack Read(ProfileBufferEntryReader& aER) {
+ return MarkerStack(aER.ReadObject<UniquePtr<ProfileChunkedBuffer>>());
+ }
+};
+
+// ----------------------------------------------------------------------------
+// Serializer, Deserializer: MarkerOptions
+
+// The serialization contains all members (either trivially-copyable, or they
+// provide their specialization above).
+template <>
+struct ProfileBufferEntryWriter::Serializer<MarkerOptions> {
+ static Length Bytes(const MarkerOptions& aOptions) {
+ return SumBytes(aOptions.ThreadId(), aOptions.Timing(), aOptions.Stack(),
+ aOptions.InnerWindowId());
+ }
+
+ static void Write(ProfileBufferEntryWriter& aEW,
+ const MarkerOptions& aOptions) {
+ aEW.WriteObjects(aOptions.ThreadId(), aOptions.Timing(), aOptions.Stack(),
+ aOptions.InnerWindowId());
+ }
+};
+
+template <>
+struct ProfileBufferEntryReader::Deserializer<MarkerOptions> {
+ static void ReadInto(ProfileBufferEntryReader& aER, MarkerOptions& aOptions) {
+ aER.ReadIntoObjects(aOptions.mThreadId, aOptions.mTiming, aOptions.mStack,
+ aOptions.mInnerWindowId);
+ }
+
+ static MarkerOptions Read(ProfileBufferEntryReader& aER) {
+ MarkerOptions options;
+ ReadInto(aER, options);
+ return options;
+ }
+};
+
+} // namespace mozilla
+
+#endif // BaseProfilerMarkersDetail_h