Skip to content

Commit

Permalink
Integrate perfetto with UserTiming API (#44702)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
javache authored and pull[bot] committed Aug 8, 2024
1 parent 1aaa544 commit 802b9f1
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand All @@ -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();
Expand All @@ -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);
}

Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 802b9f1

Please sign in to comment.