Bug 1646266 - Marker Deserialization - r=gregtatum

`DeserializeAfterKindAndStream()` is the main function that extracts all the marker data (past the already-read entry kind), and streams it to JSON using the user-provided `Stream(JSONWriter&, ...)` function in the appropriate marker type definition.

It currently requires two external functions to stream the name and the optional backtrace, because these are different between the two profilers. This may change in the future.

(Deserialization is implemented before serialization, because the `Deserialize()` function is needed during serialization to get a marker type tag.)

Differential Revision: https://phabricator.services.mozilla.com/D87254
This commit is contained in:
Gerald Squelart 2020-09-02 04:01:48 +00:00
parent f304c42c99
commit 0000b4f25a
3 changed files with 246 additions and 68 deletions

View File

@ -15,8 +15,10 @@
#include "BaseProfiler.h"
#include "BaseProfilerMarkerPayload.h"
#include "mozilla/BaseProfilerMarkers.h"
#include "platform.h"
#include "ProfileBuffer.h"
#include "ProfilerBacktrace.h"
namespace mozilla {
namespace baseprofiler {
@ -812,33 +814,61 @@ void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter,
MOZ_ASSERT(static_cast<ProfileBufferEntry::KindUnderlyingType>(type) <
static_cast<ProfileBufferEntry::KindUnderlyingType>(
ProfileBufferEntry::Kind::MODERN_LIMIT));
if (type == ProfileBufferEntry::Kind::MarkerData &&
aER.ReadObject<int>() == aThreadId) {
// Schema:
// [name, time, category, data]
aWriter.StartArrayElement();
{
std::string name = aER.ReadObject<std::string>();
const ProfilingCategoryPairInfo& info = GetProfilingCategoryPairInfo(
static_cast<ProfilingCategoryPair>(aER.ReadObject<uint32_t>()));
auto payload = aER.ReadObject<UniquePtr<ProfilerMarkerPayload>>();
double time = aER.ReadObject<double>();
MOZ_ASSERT(aER.RemainingBytes() == 0);
aUniqueStacks.mUniqueStrings->WriteElement(aWriter, name.c_str());
aWriter.DoubleElement(time);
aWriter.IntElement(unsigned(info.mCategory));
if (payload) {
aWriter.StartObjectElement(SpliceableJSONWriter::SingleLineStyle);
{ payload->StreamPayload(aWriter, aProcessStartTime, aUniqueStacks); }
aWriter.EndObject();
// Code should *break* from the switch if the entry was not fully read.
// Code should *return* from the switch if the entry was fully read.
switch (type) {
case ProfileBufferEntry::Kind::MarkerData:
if (aER.ReadObject<int>() != aThreadId) {
break;
}
}
aWriter.EndArray();
} else {
aER.SetRemainingBytes(0);
// Schema:
// [name, time, category, data]
aWriter.StartArrayElement();
{
std::string name = aER.ReadObject<std::string>();
const ProfilingCategoryPairInfo& info = GetProfilingCategoryPairInfo(
static_cast<ProfilingCategoryPair>(aER.ReadObject<uint32_t>()));
auto payload = aER.ReadObject<UniquePtr<ProfilerMarkerPayload>>();
double time = aER.ReadObject<double>();
MOZ_ASSERT(aER.RemainingBytes() == 0);
aUniqueStacks.mUniqueStrings->WriteElement(aWriter, name.c_str());
aWriter.DoubleElement(time);
aWriter.IntElement(unsigned(info.mCategory));
if (payload) {
aWriter.StartObjectElement(SpliceableJSONWriter::SingleLineStyle);
{
payload->StreamPayload(aWriter, aProcessStartTime, aUniqueStacks);
}
aWriter.EndObject();
}
}
aWriter.EndArray();
return;
case ProfileBufferEntry::Kind::Marker:
if (::mozilla::base_profiler_markers_detail::
DeserializeAfterKindAndStream(
aER, aWriter, aThreadId,
[&](const mozilla::ProfilerString8View& aName) {
aUniqueStacks.mUniqueStrings->WriteElement(
aWriter, aName.String().c_str());
},
[&](ProfileChunkedBuffer& aChunkedBuffer) {
ProfilerBacktrace backtrace("", aThreadId,
&aChunkedBuffer);
backtrace.StreamJSON(
aWriter, TimeStamp::ProcessCreation(), aUniqueStacks);
})) {
return;
}
break;
default:
break;
}
aER.SetRemainingBytes(0);
});
}

View File

@ -21,6 +21,7 @@
// look at it unless working on the profiler code.
# include "mozilla/JSONWriter.h"
# include "mozilla/ProfileBufferEntryKinds.h"
# include <limits>
# include <tuple>
@ -91,6 +92,42 @@ struct MarkerTypeSerialization {
template <size_t i>
using StreamFunctionParameter =
std::tuple_element_t<i, StreamFunctionUserParametersTuple>;
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,
JSONWriter& 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,
JSONWriter& aWriter) {
aWriter.StringProperty("type", MarkerType::MarkerTypeName());
DeserializeArguments(aEntryReader, aWriter);
}
};
template <>
@ -98,6 +135,82 @@ struct MarkerTypeSerialization<::mozilla::baseprofiler::markers::NoPayload> {
// Nothing! NoPayload has special handling avoiding payload work.
};
template <typename NameCallback, typename StackCallback>
[[nodiscard]] bool DeserializeAfterKindAndStream(
ProfileBufferEntryReader& aEntryReader, JSONWriter& aWriter,
int aThreadIdOrZero, NameCallback&& aNameCallback,
StackCallback&& aStackCallback) {
// 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>();
if (aThreadIdOrZero != 0 &&
options.ThreadId().ThreadId() != aThreadIdOrZero) {
// A specific thread is being read, we're not in it.
return false;
}
// Write the information to JSON with the following schema:
// [name, startTime, endTime, phase, category, data]
aWriter.StartArrayElement();
{
std::forward<NameCallback>(aNameCallback)(
aEntryReader.ReadObject<mozilla::ProfilerString8View>());
const double startTime = options.Timing().GetStartTime();
aWriter.DoubleElement(startTime);
const double endTime = options.Timing().GetEndTime();
aWriter.DoubleElement(endTime);
aWriter.IntElement(static_cast<int64_t>(options.Timing().MarkerPhase()));
aWriter.IntElement(static_cast<int64_t>(options.Category().Category()));
if (const auto tag =
aEntryReader.ReadObject<mozilla::base_profiler_markers_detail::
Streaming::DeserializerTag>();
tag != 0) {
aWriter.StartObjectElement(JSONWriter::SingleLineStyle);
{
// 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.
aWriter.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) {
aWriter.StartObjectProperty("stack");
{ std::forward<StackCallback>(aStackCallback)(*chunkedBuffer); }
aWriter.EndObject();
}
// Stream the payload, including the type.
mozilla::base_profiler_markers_detail::Streaming::Deserializer
deserializer = mozilla::base_profiler_markers_detail::Streaming::
DeserializerForTag(tag);
MOZ_RELEASE_ASSERT(deserializer);
deserializer(aEntryReader, aWriter);
}
aWriter.EndObject();
}
}
aWriter.EndArray();
return true;
}
} // namespace mozilla::base_profiler_markers_detail
namespace mozilla {

View File

@ -6,8 +6,10 @@
#include "ProfileBufferEntry.h"
#include "mozilla/ProfilerMarkers.h"
#include "platform.h"
#include "ProfileBuffer.h"
#include "ProfilerBacktrace.h"
#include "ProfilerMarkerPayload.h"
#include "jsapi.h"
@ -672,6 +674,7 @@ class EntryGetter {
// */
// )
// | MarkerData
// | Marker
// | ( /* Counters */
// CounterId
// Time
@ -1181,51 +1184,83 @@ void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter,
MOZ_ASSERT(static_cast<ProfileBufferEntry::KindUnderlyingType>(type) <
static_cast<ProfileBufferEntry::KindUnderlyingType>(
ProfileBufferEntry::Kind::MODERN_LIMIT));
if (type == ProfileBufferEntry::Kind::MarkerData &&
aER.ReadObject<int>() == aThreadId) {
aWriter.StartArrayElement();
{
// Extract the information from the buffer:
// Each entry is made up of the following:
//
// [
// ProfileBufferEntry::Kind::MarkerData, <- already read
// threadId, <- already read
// name, <- next location in entries
// startTime,
// endTime,
// phase,
// categoryPair,
// payload,
// ]
auto name = aER.ReadObject<std::string>();
auto startTime = aER.ReadObject<double>();
auto endTime = aER.ReadObject<double>();
auto phase = aER.ReadObject<uint8_t>();
const JS::ProfilingCategoryPairInfo& info =
GetProfilingCategoryPairInfo(static_cast<JS::ProfilingCategoryPair>(
aER.ReadObject<uint32_t>()));
auto payload = aER.ReadObject<UniquePtr<ProfilerMarkerPayload>>();
MOZ_ASSERT(aER.RemainingBytes() == 0);
// Now write this information to JSON with the following schema:
// [name, startTime, endTime, phase, category, data]
aUniqueStacks.mUniqueStrings->WriteElement(aWriter, name.c_str());
aWriter.DoubleElement(startTime);
aWriter.DoubleElement(endTime);
aWriter.IntElement(phase);
aWriter.IntElement(unsigned(info.mCategory));
if (payload) {
aWriter.StartObjectElement(SpliceableJSONWriter::SingleLineStyle);
{ payload->StreamPayload(aWriter, aProcessStartTime, aUniqueStacks); }
aWriter.EndObject();
// Code should *return* from the switch if the entry was fully read.
// Code should *break* from the switch if the entry was not fully read (we
// then need to adjust the reader position to the end of the entry, as
// expected by the reader code.)
switch (type) {
case ProfileBufferEntry::Kind::MarkerData:
if (aER.ReadObject<int>() != aThreadId) {
break; // Entry not fully read.
}
}
aWriter.EndArray();
} else {
aER.SetRemainingBytes(0);
aWriter.StartArrayElement();
{
// Extract the information from the buffer:
// Each entry is made up of the following:
//
// [
// ProfileBufferEntry::Kind::MarkerData, <- already read
// threadId, <- already read
// name, <- next location in entries
// startTime,
// endTime,
// phase,
// categoryPair,
// payload
// ]
auto name = aER.ReadObject<std::string>();
auto startTime = aER.ReadObject<double>();
auto endTime = aER.ReadObject<double>();
auto phase = aER.ReadObject<uint8_t>();
const JS::ProfilingCategoryPairInfo& info =
GetProfilingCategoryPairInfo(
static_cast<JS::ProfilingCategoryPair>(
aER.ReadObject<uint32_t>()));
auto payload = aER.ReadObject<UniquePtr<ProfilerMarkerPayload>>();
MOZ_ASSERT(aER.RemainingBytes() == 0);
// Now write this information to JSON with the following schema:
// [name, startTime, endTime, phase, category, data]
aUniqueStacks.mUniqueStrings->WriteElement(aWriter, name.c_str());
aWriter.DoubleElement(startTime);
aWriter.DoubleElement(endTime);
aWriter.IntElement(phase);
aWriter.IntElement(unsigned(info.mCategory));
if (payload) {
aWriter.StartObjectElement(SpliceableJSONWriter::SingleLineStyle);
{
payload->StreamPayload(aWriter, aProcessStartTime, aUniqueStacks);
}
aWriter.EndObject();
}
}
aWriter.EndArray();
return; // Entry fully read.
case ProfileBufferEntry::Kind::Marker:
if (mozilla::base_profiler_markers_detail::
DeserializeAfterKindAndStream(
aER, aWriter, aThreadId,
[&](const mozilla::ProfilerString8View& aName) {
aUniqueStacks.mUniqueStrings->WriteElement(
aWriter, aName.String().c_str());
},
[&](ProfileChunkedBuffer& aChunkedBuffer) {
ProfilerBacktrace backtrace("", aThreadId,
&aChunkedBuffer);
backtrace.StreamJSON(aWriter, aProcessStartTime,
aUniqueStacks);
})) {
return; // Entry fully read.
}
break; // Entry not fully read.
default:
break; // Entry not fully read.
}
aER.SetRemainingBytes(0);
});
}