Skip to content

Commit

Permalink
Add Perfetto Hermes Sampling Data Source (facebook#44818)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#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
Benoit Girard authored and kosmydel committed Jun 11, 2024
1 parent 410915c commit ae3d90b
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
#include "Plugins.h"

#ifdef WITH_PERFETTO
#include <perfetto.h>
#include <reactperflogger/ReactPerfettoCategories.h>
#include <reactperflogger/ReactPerfetto.h>
#endif

std::shared_ptr<facebook::react::TurboModule> NativePerformanceModuleProvider(
Expand All @@ -35,21 +34,6 @@ 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";

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

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);
return std::make_tuple(getPerfettoWebPerfTrack(trackNameRef), eventName);
}

#endif
Expand Down
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
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
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/

#include "ReactPerfettoCategories.h"

#ifdef WITH_PERFETTO

#include "ReactPerfettoCategories.h"

PERFETTO_TRACK_EVENT_STATIC_STORAGE();

#endif

0 comments on commit ae3d90b

Please sign in to comment.