Bug 1900846: Emit gecko profiler markers as perfetto track events if enabled. r=profiler-reviewers,canaltinova

If perfetto is enabled, then this patch enables emitting gecko profiler markers as perfetto track events.  It will always emit the marker name, category and timestamps at the very least.  It also adds support to the various payload types that are currently used, and those will be emitted as perfetto debug annotations.

Differential Revision: https://phabricator.services.mozilla.com/D214033
This commit is contained in:
Denis Palmeiro 2024-07-26 01:46:38 +00:00
parent 8ca09eb710
commit 069d9c4abb
5 changed files with 371 additions and 4 deletions

View File

@ -7,6 +7,18 @@
#include "mozilla/Perfetto.h" #include "mozilla/Perfetto.h"
#include <stdlib.h> #include <stdlib.h>
const char* ProfilerCategoryNames[] = {
#define CATEGORY_JSON_BEGIN_CATEGORY(name, labelAsString, color) #name,
#define CATEGORY_JSON_SUBCATEGORY(supercategory, name, labelAsString)
#define CATEGORY_JSON_END_CATEGORY
MOZ_PROFILING_CATEGORY_LIST(CATEGORY_JSON_BEGIN_CATEGORY,
CATEGORY_JSON_SUBCATEGORY,
CATEGORY_JSON_END_CATEGORY)
#undef CATEGORY_JSON_BEGIN_CATEGORY
#undef CATEGORY_JSON_SUBCATEGORY
#undef CATEGORY_JSON_END_CATEGORY
};
PERFETTO_TRACK_EVENT_STATIC_STORAGE(); PERFETTO_TRACK_EVENT_STATIC_STORAGE();
void InitPerfetto() { void InitPerfetto() {

View File

@ -8,8 +8,12 @@
#define mozilla_Perfetto_h #define mozilla_Perfetto_h
#ifdef MOZ_PERFETTO #ifdef MOZ_PERFETTO
# include "perfetto/perfetto.h" # include "mozilla/BaseProfilerMarkers.h"
# include "mozilla/Span.h"
# include "mozilla/TimeStamp.h" # include "mozilla/TimeStamp.h"
# include "nsString.h"
# include "nsPrintfCString.h"
# include "perfetto/perfetto.h"
// Initialization is called when a content process is created. // Initialization is called when a content process is created.
// This can be called multiple times. // This can be called multiple times.
@ -83,6 +87,7 @@ extern void InitPerfetto();
# define PERFETTO_TRACE_EVENT(...) TRACE_EVENT(__VA_ARGS__) # define PERFETTO_TRACE_EVENT(...) TRACE_EVENT(__VA_ARGS__)
# define PERFETTO_TRACE_EVENT_BEGIN(...) TRACE_EVENT_BEGIN(__VA_ARGS__) # define PERFETTO_TRACE_EVENT_BEGIN(...) TRACE_EVENT_BEGIN(__VA_ARGS__)
# define PERFETTO_TRACE_EVENT_END(...) TRACE_EVENT_END(__VA_ARGS__) # define PERFETTO_TRACE_EVENT_END(...) TRACE_EVENT_END(__VA_ARGS__)
# define PERFETTO_TRACE_EVENT_INSTANT(...) TRACE_EVENT_INSTANT(__VA_ARGS__)
namespace perfetto { namespace perfetto {
// Specialize custom timestamps for mozilla::TimeStamp. // Specialize custom timestamps for mozilla::TimeStamp.
@ -101,6 +106,306 @@ struct TraceTimestampTraits<mozilla::TimeStamp> {
PERFETTO_DEFINE_CATEGORIES(perfetto::Category("task"), PERFETTO_DEFINE_CATEGORIES(perfetto::Category("task"),
perfetto::Category("usertiming")); perfetto::Category("usertiming"));
template <typename T, typename = void>
struct MarkerHasPayloadFields : std::false_type {};
template <typename T>
struct MarkerHasPayloadFields<T, std::void_t<decltype(T::PayloadFields)>>
: std::true_type {};
using MS = mozilla::MarkerSchema;
// Primary template. Assert if a payload type has not been specialized so we
// don't miss payload information.
template <typename T, typename Enable = void>
struct AddDebugAnnotationImpl {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const T& aValue) {
static_assert(false,
"Unsupported payload type for perfetto debug annotations.");
}
};
// Do nothing for these types.
template <>
struct AddDebugAnnotationImpl<mozilla::Nothing> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const mozilla::Nothing& aValue) {}
};
template <>
struct AddDebugAnnotationImpl<std::nullptr_t> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const std::nullptr_t& aValue) {}
};
// Specialize mozilla::Maybe<>
template <typename T>
struct AddDebugAnnotationImpl<mozilla::Maybe<T>> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const mozilla::Maybe<T>& aValue) {
if (aValue.isNothing()) {
return;
}
AddDebugAnnotationImpl<T>::call(ctx, aKey, *aValue);
}
};
// Specialize integral types.
template <typename T>
struct AddDebugAnnotationImpl<T, std::enable_if_t<std::is_integral_v<T>>> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const T& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
if constexpr (std::is_same_v<T, bool>) {
arg->set_bool_value(static_cast<uint64_t>(aValue));
} else if constexpr (std::is_signed_v<T>) {
arg->set_int_value(static_cast<uint64_t>(aValue));
} else {
static_assert(std::is_unsigned_v<T>);
arg->set_uint_value(static_cast<uint64_t>(aValue));
}
}
};
// Specialize time durations.
template <>
struct AddDebugAnnotationImpl<
mozilla::BaseTimeDuration<mozilla::TimeDurationValueCalculator>> {
static void call(
perfetto::EventContext& ctx, const char* const aKey,
const mozilla::BaseTimeDuration<mozilla::TimeDurationValueCalculator>&
aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_uint_value(static_cast<uint64_t>(aValue.ToMilliseconds()));
}
};
// Specialize the various string representations.
template <>
struct AddDebugAnnotationImpl<mozilla::ProfilerString8View> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const mozilla::ProfilerString8View& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue.StringView().data());
}
};
template <size_t N>
struct AddDebugAnnotationImpl<nsAutoCStringN<N>> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsAutoCStringN<N>& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue.get());
}
};
template <>
struct AddDebugAnnotationImpl<nsCString> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsCString& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue.get());
}
};
template <>
struct AddDebugAnnotationImpl<nsAutoCString> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsAutoCString& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue.get());
}
};
template <>
struct AddDebugAnnotationImpl<nsTLiteralString<char>> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsTLiteralString<char>& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue.get());
}
};
template <>
struct AddDebugAnnotationImpl<nsPrintfCString> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsPrintfCString& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue.get());
}
};
template <>
struct AddDebugAnnotationImpl<nsTDependentString<char>> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsTDependentString<char>& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue.get());
}
};
template <>
struct AddDebugAnnotationImpl<nsACString> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsACString& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(nsAutoCString(aValue).get());
}
};
template <>
struct AddDebugAnnotationImpl<std::string> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const std::string& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue);
}
};
template <size_t N>
struct AddDebugAnnotationImpl<char[N]> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const char* aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(aValue);
}
};
template <>
struct AddDebugAnnotationImpl<mozilla::ProfilerString16View> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const mozilla::ProfilerString16View& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(NS_ConvertUTF16toUTF8(aValue).get());
}
};
template <>
struct AddDebugAnnotationImpl<nsAString> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsAString& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(NS_ConvertUTF16toUTF8(aValue).get());
}
};
template <>
struct AddDebugAnnotationImpl<const nsAString&> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsAString& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(NS_ConvertUTF16toUTF8(aValue).get());
}
};
template <>
struct AddDebugAnnotationImpl<nsString> {
static void call(perfetto::EventContext& ctx, const char* const aKey,
const nsString& aValue) {
auto* arg = ctx.event()->add_debug_annotations();
arg->set_name(aKey);
arg->set_string_value(NS_ConvertUTF16toUTF8(aValue).get());
}
};
// Main helper call that dispatches to proper specialization.
template <typename T>
void AddDebugAnnotation(perfetto::EventContext& ctx, const char* const aKey,
const T& aValue) {
AddDebugAnnotationImpl<T>::call(ctx, aKey, aValue);
}
extern const char* ProfilerCategoryNames[];
// Main entry point from the gecko profiler for each marker.
template <typename MarkerType, typename... PayloadArguments>
void EmitPerfettoTrackEvent(const mozilla::ProfilerString8View& aName,
const mozilla::MarkerCategory& aCategory,
const mozilla::MarkerOptions& aOptions,
MarkerType aMarkerType,
const PayloadArguments&... aPayloadArguments) {
mozilla::TimeStamp startTime, endTime;
mozilla::MarkerTiming::Phase phase;
if (aOptions.IsTimingUnspecified()) {
startTime = mozilla::TimeStamp::Now();
phase = mozilla::MarkerTiming::Phase::Instant;
} else {
startTime = aOptions.Timing().StartTime();
endTime = aOptions.Timing().EndTime();
phase = aOptions.Timing().MarkerPhase();
}
const char* nameStr = aName.StringView().data();
if (!nameStr) {
return;
}
// Create a dynamic category for the marker.
const char* categoryName =
ProfilerCategoryNames[static_cast<uint32_t>(aCategory.GetCategory())];
perfetto::DynamicCategory category{categoryName};
perfetto::DynamicString name{nameStr};
// If the Marker has payload fields, we can annotate them in the perfetto
// track event. Otherwise, we define an empty lambda which does nothing.
std::function<void(perfetto::EventContext)> annotateTrackEvent =
[&](perfetto::EventContext ctx) {};
if constexpr (MarkerHasPayloadFields<MarkerType>::value) {
annotateTrackEvent = [&](perfetto::EventContext ctx) {
size_t i = 0;
auto processArgument = [&](const auto& payloadArg) {
AddDebugAnnotation(ctx, MarkerType::PayloadFields[i++].Key, payloadArg);
};
(processArgument(aPayloadArguments), ...);
};
}
// Create a unique id for each marker so it has it's own track.
mozilla::HashNumber hash =
mozilla::HashStringKnownLength(nameStr, aName.StringView().length());
switch (phase) {
case mozilla::MarkerTiming::Phase::Interval: {
hash = mozilla::AddToHash(
hash, startTime.RawClockMonotonicNanosecondsSinceBoot());
hash = mozilla::AddToHash(
hash, endTime.RawClockMonotonicNanosecondsSinceBoot());
perfetto::Track track(hash);
PERFETTO_TRACE_EVENT_BEGIN(category, name, track, startTime);
PERFETTO_TRACE_EVENT_END(category, track, endTime, annotateTrackEvent);
} break;
case mozilla::MarkerTiming::Phase::Instant: {
PERFETTO_TRACE_EVENT_INSTANT(category, name, startTime);
} break;
case mozilla::MarkerTiming::Phase::IntervalStart: {
PERFETTO_TRACE_EVENT_BEGIN(category, name, perfetto::Track(hash),
startTime);
} break;
case mozilla::MarkerTiming::Phase::IntervalEnd: {
PERFETTO_TRACE_EVENT_END(category, perfetto::Track(hash), endTime,
annotateTrackEvent);
} break;
}
}
#else // !defined(MOZ_PERFETTO) #else // !defined(MOZ_PERFETTO)
# define PERFETTO_TRACE_EVENT(...) \ # define PERFETTO_TRACE_EVENT(...) \
do { \ do { \
@ -111,6 +416,9 @@ PERFETTO_DEFINE_CATEGORIES(perfetto::Category("task"),
# define PERFETTO_TRACE_EVENT_END(...) \ # define PERFETTO_TRACE_EVENT_END(...) \
do { \ do { \
} while (0) } while (0)
# define PERFETTO_TRACE_EVENT_INSTANT(...) \
do { \
} while (0)
inline void InitPerfetto() {} inline void InitPerfetto() {}
#endif // MOZ_PERFETTO #endif // MOZ_PERFETTO

View File

@ -47,6 +47,7 @@
#include "mozilla/Assertions.h" #include "mozilla/Assertions.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h" #include "mozilla/MozPromise.h"
#include "mozilla/Perfetto.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsDebug.h" #include "nsDebug.h"
#include "nsXPCOM.h" #include "nsXPCOM.h"
@ -790,6 +791,21 @@ static void* AsyncSignalControlThreadEntry(void* aArg) {
// - mProcessStartTime, because it's immutable; // - mProcessStartTime, because it's immutable;
class CorePS { class CorePS {
private: private:
#ifdef MOZ_PERFETTO
class PerfettoObserver : public perfetto::TrackEventSessionObserver {
public:
PerfettoObserver() { perfetto::TrackEvent::AddSessionObserver(this); }
~PerfettoObserver() { perfetto::TrackEvent::RemoveSessionObserver(this); }
void OnStart(const perfetto::DataSourceBase::StartArgs&) override {
mozilla::profiler::detail::RacyFeatures::SetPerfettoTracingActive();
}
void OnStop(const perfetto::DataSourceBase::StopArgs&) override {
mozilla::profiler::detail::RacyFeatures::SetPerfettoTracingInactive();
}
} perfettoObserver;
#endif
CorePS() CorePS()
: mProcessStartTime(TimeStamp::ProcessCreation()), : mProcessStartTime(TimeStamp::ProcessCreation()),
mMaybeBandwidthCounter(nullptr) mMaybeBandwidthCounter(nullptr)
@ -5773,6 +5789,7 @@ void profiler_init(void* aStackTop) {
VTUNE_INIT(); VTUNE_INIT();
ETW::Init(); ETW::Init();
InitPerfetto();
MOZ_RELEASE_ASSERT(!CorePS::Exists()); MOZ_RELEASE_ASSERT(!CorePS::Exists());

View File

@ -136,14 +136,15 @@ inline mozilla::ProfileBufferBlockIndex AddMarkerToBuffer(
// return true. // return true.
[[nodiscard]] inline bool profiler_thread_is_being_profiled_for_markers() { [[nodiscard]] inline bool profiler_thread_is_being_profiled_for_markers() {
return profiler_thread_is_being_profiled(ThreadProfilingFeatures::Markers) || return profiler_thread_is_being_profiled(ThreadProfilingFeatures::Markers) ||
profiler_is_etw_collecting_markers(); profiler_is_etw_collecting_markers() || profiler_is_perfetto_tracing();
;
} }
[[nodiscard]] inline bool profiler_thread_is_being_profiled_for_markers( [[nodiscard]] inline bool profiler_thread_is_being_profiled_for_markers(
const ProfilerThreadId& aThreadId) { const ProfilerThreadId& aThreadId) {
return profiler_thread_is_being_profiled(aThreadId, return profiler_thread_is_being_profiled(aThreadId,
ThreadProfilingFeatures::Markers) || ThreadProfilingFeatures::Markers) ||
profiler_is_etw_collecting_markers(); profiler_is_etw_collecting_markers() || profiler_is_perfetto_tracing();
} }
// Add a marker to the Gecko Profiler buffer. // Add a marker to the Gecko Profiler buffer.
@ -167,6 +168,14 @@ mozilla::ProfileBufferBlockIndex profiler_add_marker_impl(
ETW::EmitETWMarker(aName, aCategory, aOptions, aMarkerType, ETW::EmitETWMarker(aName, aCategory, aOptions, aMarkerType,
aPayloadArguments...); aPayloadArguments...);
# endif # endif
# ifdef MOZ_PERFETTO
if (profiler_is_perfetto_tracing()) {
EmitPerfettoTrackEvent(aName, aCategory, aOptions, aMarkerType,
aPayloadArguments...);
}
# endif
if (!profiler_thread_is_being_gecko_profiled_for_markers( if (!profiler_thread_is_being_gecko_profiled_for_markers(
aOptions.ThreadId().ThreadId())) { aOptions.ThreadId().ThreadId())) {
return {}; return {};

View File

@ -15,6 +15,7 @@
#include <mozilla/DefineEnum.h> #include <mozilla/DefineEnum.h>
#include <mozilla/EnumSet.h> #include <mozilla/EnumSet.h>
#include "mozilla/ProfilerUtils.h" #include "mozilla/ProfilerUtils.h"
#include "mozilla/Perfetto.h"
#include <functional> #include <functional>
@ -210,6 +211,7 @@ using ProfilingStateChangeCallback = std::function<void(ProfilingState)>;
[[nodiscard]] inline bool profiler_is_active_and_unpaused() { return false; } [[nodiscard]] inline bool profiler_is_active_and_unpaused() { return false; }
[[nodiscard]] inline bool profiler_is_collecting_markers() { return false; } [[nodiscard]] inline bool profiler_is_collecting_markers() { return false; }
[[nodiscard]] inline bool profiler_is_etw_collecting_markers() { return false; } [[nodiscard]] inline bool profiler_is_etw_collecting_markers() { return false; }
[[nodiscard]] inline bool profiler_is_perfetto_tracing() { return false; }
[[nodiscard]] inline bool profiler_feature_active(uint32_t aFeature) { [[nodiscard]] inline bool profiler_feature_active(uint32_t aFeature) {
return false; return false;
} }
@ -254,6 +256,14 @@ class RacyFeatures {
sActiveAndFeatures &= ~ETWCollectionEnabled; sActiveAndFeatures &= ~ETWCollectionEnabled;
} }
static void SetPerfettoTracingActive() {
sActiveAndFeatures |= PerfettoTracingEnabled;
}
static void SetPerfettoTracingInactive() {
sActiveAndFeatures &= ~PerfettoTracingEnabled;
}
static void SetInactive() { sActiveAndFeatures = 0; } static void SetInactive() { sActiveAndFeatures = 0; }
static void SetPaused() { sActiveAndFeatures |= Paused; } static void SetPaused() { sActiveAndFeatures |= Paused; }
@ -315,7 +325,8 @@ class RacyFeatures {
[[nodiscard]] static bool IsCollectingMarkers() { [[nodiscard]] static bool IsCollectingMarkers() {
uint32_t af = sActiveAndFeatures; // copy it first uint32_t af = sActiveAndFeatures; // copy it first
return ((af & Active) && !(af & Paused)) || (af & ETWCollectionEnabled); return ((af & Active) && !(af & Paused)) || (af & ETWCollectionEnabled) ||
(af & PerfettoTracingEnabled);
} }
[[nodiscard]] static bool IsETWCollecting() { [[nodiscard]] static bool IsETWCollecting() {
@ -323,11 +334,17 @@ class RacyFeatures {
return (af & ETWCollectionEnabled); return (af & ETWCollectionEnabled);
} }
[[nodiscard]] static bool IsPerfettoTracing() {
uint32_t af = sActiveAndFeatures; // copy it first
return (af & PerfettoTracingEnabled);
}
private: private:
static constexpr uint32_t Active = 1u << 31; static constexpr uint32_t Active = 1u << 31;
static constexpr uint32_t Paused = 1u << 30; static constexpr uint32_t Paused = 1u << 30;
static constexpr uint32_t SamplingPaused = 1u << 29; static constexpr uint32_t SamplingPaused = 1u << 29;
static constexpr uint32_t ETWCollectionEnabled = 1u << 28; static constexpr uint32_t ETWCollectionEnabled = 1u << 28;
static constexpr uint32_t PerfettoTracingEnabled = 1u << 27;
// Ensure Active/Paused don't overlap with any of the feature bits. // Ensure Active/Paused don't overlap with any of the feature bits.
# define NO_OVERLAP(n_, str_, Name_, desc_) \ # define NO_OVERLAP(n_, str_, Name_, desc_) \
@ -385,6 +402,10 @@ class RacyFeatures {
return mozilla::profiler::detail::RacyFeatures::IsETWCollecting(); return mozilla::profiler::detail::RacyFeatures::IsETWCollecting();
} }
[[nodiscard]] inline bool profiler_is_perfetto_tracing() {
return mozilla::profiler::detail::RacyFeatures::IsPerfettoTracing();
}
// Is the profiler active and paused? Returns false if the profiler is inactive. // Is the profiler active and paused? Returns false if the profiler is inactive.
[[nodiscard]] bool profiler_is_paused(); [[nodiscard]] bool profiler_is_paused();