Integrate perfetto with UserTiming API (#44702)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/44702

Based on bgirard initial changes in D53478653, this creates an initial integration of the Perfetto SDK with the User Timing API, allowing performance information to be logged from JS to Perfetto traces.

We only enable this for Android right now, but may be able to leverage this on other platforms too in the future.

The logic in `initializePerfetto` may need to moved to another common target (eg reactperflogger) once we want to make this usable in other components, but keeping it scoped to User Timing for now.

Changelog: [Internal]

Reviewed By: bgirard

Differential Revision: D57881823

fbshipit-source-id: 11ba09cbc01a102a72eee65ce6d6aeca508e864a
This commit is contained in:
Pieter De Baets
2024-06-03 08:55:48 -07:00
committed by Facebook GitHub Bot
parent 84ff977b37
commit 9d3bac6fd5
5 changed files with 140 additions and 4 deletions
@@ -5,16 +5,24 @@
* LICENSE file in the root directory of this source tree.
*/
#include "NativePerformance.h"
#include <memory>
#include <mutex>
#include <unordered_map>
#include <cxxreact/JSExecutor.h>
#include <cxxreact/ReactMarker.h>
#include <jsi/instrumentation.h>
#include <react/performance/timeline/PerformanceEntryReporter.h>
#include "NativePerformance.h"
#include "Plugins.h"
#ifdef WITH_PERFETTO
#include <perfetto.h>
#include <reactperflogger/ReactPerfettoCategories.h>
#endif
std::shared_ptr<facebook::react::TurboModule> NativePerformanceModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativePerformance>(
@@ -23,8 +31,79 @@ std::shared_ptr<facebook::react::TurboModule> NativePerformanceModuleProvider(
namespace facebook::react {
namespace {
#ifdef WITH_PERFETTO
// Offset for custom perfetto tracks
uint64_t trackId = 0x5F3759DF;
// Extract this once we start emitting perfetto markers from other modules
std::once_flag perfettoInit;
void initializePerfetto() {
std::call_once(perfettoInit, []() {
perfetto::TracingInitArgs args;
args.backends |= perfetto::kSystemBackend;
args.use_monotonic_clock = true;
perfetto::Tracing::Initialize(args);
perfetto::TrackEvent::Register();
});
}
const std::string TRACK_PREFIX = "Track:";
const std::string DEFAULT_TRACK_NAME = "Web Performance";
std::tuple<perfetto::Track, std::string_view> parsePerfettoTrack(
const std::string& name) {
// Until there's a standard way to pass through track information, parse it
// manually, e.g., "Track:Foo:Event name"
// https://github.com/w3c/user-timing/issues/109
std::optional<std::string> trackName;
std::string_view eventName(name);
if (name.starts_with(TRACK_PREFIX)) {
const auto trackNameDelimiter = name.find(':', TRACK_PREFIX.length());
if (trackNameDelimiter != std::string::npos) {
trackName = name.substr(
TRACK_PREFIX.length(), trackNameDelimiter - TRACK_PREFIX.length());
eventName = std::string_view(name).substr(trackNameDelimiter + 1);
}
}
auto& trackNameRef = trackName.has_value() ? *trackName : DEFAULT_TRACK_NAME;
static std::unordered_map<std::string, perfetto::Track> tracks;
auto it = tracks.find(trackNameRef);
if (it == tracks.end()) {
auto track = perfetto::Track(trackId++);
auto desc = track.Serialize();
desc.set_name(trackNameRef);
perfetto::TrackEvent::SetTrackDescriptor(track, desc);
tracks.emplace(trackNameRef, track);
return std::make_tuple(track, eventName);
} else {
return std::make_tuple(it->second, eventName);
}
}
// Perfetto's monotonic clock seems to match the std::chrono::steady_clock we
// use in JSExecutor::performanceNow on Android platforms, but if that
// assumption is incorrect we may need to manually offset perfetto timestamps.
uint64_t performanceNowToPerfettoTraceTime(double perfNowTime) {
if (perfNowTime == 0) {
return perfetto::TrackEvent::GetTraceTimeNs();
}
return static_cast<uint64_t>(perfNowTime * 1.e6);
}
#endif
} // namespace
NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
: NativePerformanceCxxSpec(std::move(jsInvoker)) {}
: NativePerformanceCxxSpec(std::move(jsInvoker)) {
#ifdef WITH_PERFETTO
initializePerfetto();
#endif
}
double NativePerformance::now(jsi::Runtime& /*rt*/) {
return JSExecutor::performanceNow();
@@ -34,6 +113,16 @@ void NativePerformance::mark(
jsi::Runtime& rt,
std::string name,
double startTime) {
#ifdef WITH_PERFETTO
if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) {
auto [track, eventName] = parsePerfettoTrack(name);
TRACE_EVENT_INSTANT(
"react-native",
perfetto::DynamicString(eventName.data(), eventName.size()),
track,
performanceNowToPerfettoTraceTime(startTime));
}
#endif
PerformanceEntryReporter::getInstance()->mark(name, startTime);
}
@@ -45,6 +134,21 @@ void NativePerformance::measure(
std::optional<double> duration,
std::optional<std::string> startMark,
std::optional<std::string> endMark) {
#ifdef WITH_PERFETTO
if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) {
// TODO T190600850 support startMark/endMark
if (!startMark && !endMark) {
auto [track, eventName] = parsePerfettoTrack(name);
TRACE_EVENT_BEGIN(
"react-native",
perfetto::DynamicString(eventName.data(), eventName.size()),
track,
performanceNowToPerfettoTraceTime(startTime));
TRACE_EVENT_END(
"react-native", track, performanceNowToPerfettoTraceTime(endTime));
}
}
#endif
PerformanceEntryReporter::getInstance()->measure(
name, startTime, endTime, duration, startMark, endMark);
}
@@ -11,7 +11,7 @@
namespace facebook::react {
std::shared_ptr<PerformanceEntryReporter>
std::shared_ptr<PerformanceEntryReporter>&
PerformanceEntryReporter::getInstance() {
static auto instance = std::make_shared<PerformanceEntryReporter>();
return instance;
@@ -89,7 +89,7 @@ class PerformanceEntryReporter {
// the same thread.
// TODO: Consider passing it as a parameter to the corresponding modules at
// creation time instead of having the singleton.
static std::shared_ptr<PerformanceEntryReporter> getInstance();
static std::shared_ptr<PerformanceEntryReporter>& getInstance();
struct PopPendingEntriesResult {
std::vector<PerformanceEntry> entries;
@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ReactPerfettoCategories.h"
#ifdef WITH_PERFETTO
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
#endif
@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifdef WITH_PERFETTO
#include <perfetto.h>
PERFETTO_DEFINE_CATEGORIES(
perfetto::Category("react-native")
.SetDescription("User timing events from React Native"));
#endif