-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
2b343ca
commit 5a4cb3d
Showing
6 changed files
with
236 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
...ges/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
#ifdef WITH_PERFETTO | ||
|
||
#include <folly/json.h> | ||
#include <hermes/hermes.h> | ||
#include <perfetto.h> | ||
#include <iostream> | ||
|
||
#include "HermesPerfettoDataSource.h" | ||
#include "ReactPerfetto.h" | ||
|
||
namespace { | ||
|
||
const int SAMPLING_HZ = 100; | ||
|
||
int64_t hermesDeltaTime = 0; | ||
|
||
using perfetto::TrackEvent; | ||
|
||
uint64_t hermesToPerfettoTime(int64_t hermesTs) { | ||
if (hermesDeltaTime == 0) { | ||
hermesDeltaTime = TrackEvent::GetTraceTimeNs() - | ||
std::chrono::steady_clock::now().time_since_epoch().count(); | ||
} | ||
return (hermesTs * 1000 + hermesDeltaTime); | ||
} | ||
|
||
std::vector<folly::dynamic> getStack( | ||
const folly::dynamic& trace, | ||
const folly::dynamic& sample) { | ||
std::vector<folly::dynamic> stack; | ||
|
||
auto stackFrameId = sample["sf"]; | ||
auto stackFrame = trace["stackFrames"][stackFrameId.asString()]; | ||
|
||
while (!stackFrame.isNull()) { | ||
stack.push_back(stackFrame); | ||
auto parentStackFrameId = stackFrame["parent"]; | ||
if (parentStackFrameId.isNull()) { | ||
break; // No more parents, we're done with this stack frame | ||
} | ||
stackFrame = trace["stackFrames"][parentStackFrameId.asString()]; | ||
} | ||
std::reverse(stack.begin(), stack.end()); | ||
return stack; | ||
} | ||
|
||
void flushSample( | ||
const std::vector<folly::dynamic>& stack, | ||
uint64_t start, | ||
uint64_t end) { | ||
auto track = getPerfettoWebPerfTrack("JS Sampling"); | ||
for (const auto& frame : stack) { | ||
std::string name = frame["name"].asString(); | ||
TRACE_EVENT_BEGIN( | ||
"react-native", perfetto::DynamicString{name}, track, start); | ||
TRACE_EVENT_END("react-native", track, end); | ||
} | ||
} | ||
|
||
void logHermesProfileToPerfetto(const std::string& traceStr) { | ||
auto trace = folly::parseJson(traceStr); | ||
auto samples = trace["samples"]; | ||
|
||
std::vector previousStack = std::vector<folly::dynamic>(); | ||
uint64_t previousStartTS = 0; | ||
uint64_t previousEndTS = 0; | ||
for (const auto& sample : samples) { | ||
auto perfettoTS = hermesToPerfettoTime(sample["ts"].asInt()); | ||
|
||
// Flush previous sample | ||
if (previousStack.size() > 0) { | ||
flushSample( | ||
previousStack, | ||
previousStartTS, | ||
std::min(previousEndTS, perfettoTS - 1)); | ||
} | ||
|
||
previousStack = getStack(trace, sample); | ||
previousStartTS = perfettoTS; | ||
previousEndTS = previousStartTS + 1000000000 / SAMPLING_HZ; | ||
} | ||
if (previousStack.size() > 0) { | ||
flushSample(previousStack, previousStartTS, previousEndTS); | ||
} | ||
} | ||
|
||
} // namespace | ||
|
||
void HermesPerfettoDataSource::OnStart(const StartArgs&) { | ||
facebook::hermes::HermesRuntime::enableSamplingProfiler(SAMPLING_HZ); | ||
TRACE_EVENT_INSTANT( | ||
"react-native", | ||
perfetto::DynamicString{"Profiling Started"}, | ||
getPerfettoWebPerfTrack("JS Sampling"), | ||
performanceNowToPerfettoTraceTime(0)); | ||
} | ||
|
||
void HermesPerfettoDataSource::OnFlush(const FlushArgs&) { | ||
// NOTE: We write data during OnFlush and not OnStop because we can't | ||
// use the TRACE_EVENT macros in OnStop. | ||
std::stringstream stream; | ||
facebook::hermes::HermesRuntime::dumpSampledTraceToStream(stream); | ||
std::string trace = stream.str(); | ||
logHermesProfileToPerfetto(trace); | ||
} | ||
|
||
void HermesPerfettoDataSource::OnStop(const StopArgs& a) { | ||
facebook::hermes::HermesRuntime::disableSamplingProfiler(); | ||
} | ||
|
||
PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(HermesPerfettoDataSource); | ||
|
||
#endif // WITH_PERFETTO |
34 changes: 34 additions & 0 deletions
34
packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* 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> | ||
|
||
class HermesPerfettoDataSource | ||
: public perfetto::DataSource<HermesPerfettoDataSource> { | ||
public: | ||
void OnSetup(const SetupArgs&) override {} | ||
|
||
void OnStart(const StartArgs&) override; | ||
|
||
void OnFlush(const FlushArgs&) override; | ||
|
||
void OnStop(const StopArgs& a) override; | ||
|
||
static void RegisterDataSource() { | ||
perfetto::DataSourceDescriptor dsd; | ||
dsd.set_name("com.facebook.hermes.profiler"); | ||
HermesPerfettoDataSource::Register(dsd); | ||
} | ||
}; | ||
|
||
PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(HermesPerfettoDataSource); | ||
|
||
#endif // WITH_PERFETTO |
56 changes: 56 additions & 0 deletions
56
packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
#ifdef WITH_PERFETTO | ||
|
||
#include <perfetto.h> | ||
#include <unordered_map> | ||
|
||
#include "HermesPerfettoDataSource.h" | ||
#include "ReactPerfettoCategories.h" | ||
|
||
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(); | ||
}); | ||
|
||
HermesPerfettoDataSource::RegisterDataSource(); | ||
} | ||
|
||
perfetto::Track getPerfettoWebPerfTrack(const std::string& trackName) { | ||
static std::unordered_map<std::string, perfetto::Track> tracks; | ||
// Offset for custom perfetto tracks | ||
static uint64_t trackId = 0x5F3759DF; | ||
auto it = tracks.find(trackName); | ||
if (it == tracks.end()) { | ||
auto track = perfetto::Track(trackId++); | ||
auto desc = track.Serialize(); | ||
desc.set_name(trackName); | ||
perfetto::TrackEvent::SetTrackDescriptor(track, desc); | ||
tracks.emplace(trackName, track); | ||
return track; | ||
} else { | ||
return it->second; | ||
} | ||
} | ||
|
||
// 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 // WITH_PERFETTO |
22 changes: 22 additions & 0 deletions
22
packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* 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> | ||
#include <reactperflogger/ReactPerfettoCategories.h> | ||
#include <string> | ||
|
||
void initializePerfetto(); | ||
|
||
perfetto::Track getPerfettoWebPerfTrack(const std::string& trackName); | ||
|
||
uint64_t performanceNowToPerfettoTraceTime(double perfNowTime); | ||
|
||
#endif // WITH_PERFETTO |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters