Skip to content

Commit 5a4cb3d

Browse files
Benoit Girardfacebook-github-bot
Benoit Girard
authored andcommitted
Add Perfetto Hermes Sampling Data Source (#44818)
Summary: Pull Request resolved: #44818 Introduce a new data source for Perfetto. This one turns on the Hermes sampler, and at the end we flush the state to Perfetto. This provides JS sampling data in Perfetto traces that can be used to easily spot JS performance problems not otherwise obvious. Reviewed By: javache Differential Revision: D57226087 fbshipit-source-id: 77c4a335bb462e73d74345eedc3fa634405bfd0f
1 parent 2b343ca commit 5a4cb3d

File tree

6 files changed

+236
-41
lines changed

6 files changed

+236
-41
lines changed

packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
#include "Plugins.h"
2020

2121
#ifdef WITH_PERFETTO
22-
#include <perfetto.h>
23-
#include <reactperflogger/ReactPerfettoCategories.h>
22+
#include <reactperflogger/ReactPerfetto.h>
2423
#endif
2524

2625
std::shared_ptr<facebook::react::TurboModule> NativePerformanceModuleProvider(
@@ -35,21 +34,6 @@ namespace {
3534

3635
#ifdef WITH_PERFETTO
3736

38-
// Offset for custom perfetto tracks
39-
uint64_t trackId = 0x5F3759DF;
40-
41-
// Extract this once we start emitting perfetto markers from other modules
42-
std::once_flag perfettoInit;
43-
void initializePerfetto() {
44-
std::call_once(perfettoInit, []() {
45-
perfetto::TracingInitArgs args;
46-
args.backends |= perfetto::kSystemBackend;
47-
args.use_monotonic_clock = true;
48-
perfetto::Tracing::Initialize(args);
49-
perfetto::TrackEvent::Register();
50-
});
51-
}
52-
5337
const std::string TRACK_PREFIX = "Track:";
5438
const std::string DEFAULT_TRACK_NAME = "Web Performance";
5539

@@ -70,28 +54,7 @@ std::tuple<perfetto::Track, std::string_view> parsePerfettoTrack(
7054
}
7155

7256
auto& trackNameRef = trackName.has_value() ? *trackName : DEFAULT_TRACK_NAME;
73-
static std::unordered_map<std::string, perfetto::Track> tracks;
74-
auto it = tracks.find(trackNameRef);
75-
if (it == tracks.end()) {
76-
auto track = perfetto::Track(trackId++);
77-
auto desc = track.Serialize();
78-
desc.set_name(trackNameRef);
79-
perfetto::TrackEvent::SetTrackDescriptor(track, desc);
80-
tracks.emplace(trackNameRef, track);
81-
return std::make_tuple(track, eventName);
82-
} else {
83-
return std::make_tuple(it->second, eventName);
84-
}
85-
}
86-
87-
// Perfetto's monotonic clock seems to match the std::chrono::steady_clock we
88-
// use in JSExecutor::performanceNow on Android platforms, but if that
89-
// assumption is incorrect we may need to manually offset perfetto timestamps.
90-
uint64_t performanceNowToPerfettoTraceTime(double perfNowTime) {
91-
if (perfNowTime == 0) {
92-
return perfetto::TrackEvent::GetTraceTimeNs();
93-
}
94-
return static_cast<uint64_t>(perfNowTime * 1.e6);
57+
return std::make_tuple(getPerfettoWebPerfTrack(trackNameRef), eventName);
9558
}
9659

9760
#endif
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#ifdef WITH_PERFETTO
9+
10+
#include <folly/json.h>
11+
#include <hermes/hermes.h>
12+
#include <perfetto.h>
13+
#include <iostream>
14+
15+
#include "HermesPerfettoDataSource.h"
16+
#include "ReactPerfetto.h"
17+
18+
namespace {
19+
20+
const int SAMPLING_HZ = 100;
21+
22+
int64_t hermesDeltaTime = 0;
23+
24+
using perfetto::TrackEvent;
25+
26+
uint64_t hermesToPerfettoTime(int64_t hermesTs) {
27+
if (hermesDeltaTime == 0) {
28+
hermesDeltaTime = TrackEvent::GetTraceTimeNs() -
29+
std::chrono::steady_clock::now().time_since_epoch().count();
30+
}
31+
return (hermesTs * 1000 + hermesDeltaTime);
32+
}
33+
34+
std::vector<folly::dynamic> getStack(
35+
const folly::dynamic& trace,
36+
const folly::dynamic& sample) {
37+
std::vector<folly::dynamic> stack;
38+
39+
auto stackFrameId = sample["sf"];
40+
auto stackFrame = trace["stackFrames"][stackFrameId.asString()];
41+
42+
while (!stackFrame.isNull()) {
43+
stack.push_back(stackFrame);
44+
auto parentStackFrameId = stackFrame["parent"];
45+
if (parentStackFrameId.isNull()) {
46+
break; // No more parents, we're done with this stack frame
47+
}
48+
stackFrame = trace["stackFrames"][parentStackFrameId.asString()];
49+
}
50+
std::reverse(stack.begin(), stack.end());
51+
return stack;
52+
}
53+
54+
void flushSample(
55+
const std::vector<folly::dynamic>& stack,
56+
uint64_t start,
57+
uint64_t end) {
58+
auto track = getPerfettoWebPerfTrack("JS Sampling");
59+
for (const auto& frame : stack) {
60+
std::string name = frame["name"].asString();
61+
TRACE_EVENT_BEGIN(
62+
"react-native", perfetto::DynamicString{name}, track, start);
63+
TRACE_EVENT_END("react-native", track, end);
64+
}
65+
}
66+
67+
void logHermesProfileToPerfetto(const std::string& traceStr) {
68+
auto trace = folly::parseJson(traceStr);
69+
auto samples = trace["samples"];
70+
71+
std::vector previousStack = std::vector<folly::dynamic>();
72+
uint64_t previousStartTS = 0;
73+
uint64_t previousEndTS = 0;
74+
for (const auto& sample : samples) {
75+
auto perfettoTS = hermesToPerfettoTime(sample["ts"].asInt());
76+
77+
// Flush previous sample
78+
if (previousStack.size() > 0) {
79+
flushSample(
80+
previousStack,
81+
previousStartTS,
82+
std::min(previousEndTS, perfettoTS - 1));
83+
}
84+
85+
previousStack = getStack(trace, sample);
86+
previousStartTS = perfettoTS;
87+
previousEndTS = previousStartTS + 1000000000 / SAMPLING_HZ;
88+
}
89+
if (previousStack.size() > 0) {
90+
flushSample(previousStack, previousStartTS, previousEndTS);
91+
}
92+
}
93+
94+
} // namespace
95+
96+
void HermesPerfettoDataSource::OnStart(const StartArgs&) {
97+
facebook::hermes::HermesRuntime::enableSamplingProfiler(SAMPLING_HZ);
98+
TRACE_EVENT_INSTANT(
99+
"react-native",
100+
perfetto::DynamicString{"Profiling Started"},
101+
getPerfettoWebPerfTrack("JS Sampling"),
102+
performanceNowToPerfettoTraceTime(0));
103+
}
104+
105+
void HermesPerfettoDataSource::OnFlush(const FlushArgs&) {
106+
// NOTE: We write data during OnFlush and not OnStop because we can't
107+
// use the TRACE_EVENT macros in OnStop.
108+
std::stringstream stream;
109+
facebook::hermes::HermesRuntime::dumpSampledTraceToStream(stream);
110+
std::string trace = stream.str();
111+
logHermesProfileToPerfetto(trace);
112+
}
113+
114+
void HermesPerfettoDataSource::OnStop(const StopArgs& a) {
115+
facebook::hermes::HermesRuntime::disableSamplingProfiler();
116+
}
117+
118+
PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(HermesPerfettoDataSource);
119+
120+
#endif // WITH_PERFETTO
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#ifdef WITH_PERFETTO
11+
12+
#include <perfetto.h>
13+
14+
class HermesPerfettoDataSource
15+
: public perfetto::DataSource<HermesPerfettoDataSource> {
16+
public:
17+
void OnSetup(const SetupArgs&) override {}
18+
19+
void OnStart(const StartArgs&) override;
20+
21+
void OnFlush(const FlushArgs&) override;
22+
23+
void OnStop(const StopArgs& a) override;
24+
25+
static void RegisterDataSource() {
26+
perfetto::DataSourceDescriptor dsd;
27+
dsd.set_name("com.facebook.hermes.profiler");
28+
HermesPerfettoDataSource::Register(dsd);
29+
}
30+
};
31+
32+
PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(HermesPerfettoDataSource);
33+
34+
#endif // WITH_PERFETTO
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#ifdef WITH_PERFETTO
9+
10+
#include <perfetto.h>
11+
#include <unordered_map>
12+
13+
#include "HermesPerfettoDataSource.h"
14+
#include "ReactPerfettoCategories.h"
15+
16+
std::once_flag perfettoInit;
17+
void initializePerfetto() {
18+
std::call_once(perfettoInit, []() {
19+
perfetto::TracingInitArgs args;
20+
args.backends |= perfetto::kSystemBackend;
21+
args.use_monotonic_clock = true;
22+
perfetto::Tracing::Initialize(args);
23+
perfetto::TrackEvent::Register();
24+
});
25+
26+
HermesPerfettoDataSource::RegisterDataSource();
27+
}
28+
29+
perfetto::Track getPerfettoWebPerfTrack(const std::string& trackName) {
30+
static std::unordered_map<std::string, perfetto::Track> tracks;
31+
// Offset for custom perfetto tracks
32+
static uint64_t trackId = 0x5F3759DF;
33+
auto it = tracks.find(trackName);
34+
if (it == tracks.end()) {
35+
auto track = perfetto::Track(trackId++);
36+
auto desc = track.Serialize();
37+
desc.set_name(trackName);
38+
perfetto::TrackEvent::SetTrackDescriptor(track, desc);
39+
tracks.emplace(trackName, track);
40+
return track;
41+
} else {
42+
return it->second;
43+
}
44+
}
45+
46+
// Perfetto's monotonic clock seems to match the std::chrono::steady_clock we
47+
// use in JSExecutor::performanceNow on Android platforms, but if that
48+
// assumption is incorrect we may need to manually offset perfetto timestamps.
49+
uint64_t performanceNowToPerfettoTraceTime(double perfNowTime) {
50+
if (perfNowTime == 0) {
51+
return perfetto::TrackEvent::GetTraceTimeNs();
52+
}
53+
return static_cast<uint64_t>(perfNowTime * 1.e6);
54+
}
55+
56+
#endif // WITH_PERFETTO
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#ifdef WITH_PERFETTO
11+
12+
#include <perfetto.h>
13+
#include <reactperflogger/ReactPerfettoCategories.h>
14+
#include <string>
15+
16+
void initializePerfetto();
17+
18+
perfetto::Track getPerfettoWebPerfTrack(const std::string& trackName);
19+
20+
uint64_t performanceNowToPerfettoTraceTime(double perfNowTime);
21+
22+
#endif // WITH_PERFETTO

packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
#include "ReactPerfettoCategories.h"
9-
108
#ifdef WITH_PERFETTO
119

10+
#include "ReactPerfettoCategories.h"
11+
1212
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
1313

1414
#endif

0 commit comments

Comments
 (0)