Add FuseboxPerfettoDataSource to emit Fusebox traces using Perfetto (#46376)

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

Ideally we would use Fusebox directly but it's not supported in release build. This is a clever work around that:
1) Allows us to use Perfetto to start/stop a trace, otherwise we'd need to build another way to do that which would be either hacky and/or complex.
2) Implements tacking Hermes JS samples and putting them inside of Fusebox traces.
3) Manually taking the trace and opening them in an instance of devtools.

Reviewed By: rubennorte

Differential Revision: D62263012

fbshipit-source-id: 49468914d2b7e5da4761329b176f140b917a404d
This commit is contained in:
Benoit Girard
2024-09-10 12:54:02 -07:00
committed by Facebook GitHub Bot
parent 199d194c46
commit ef0ea4d834
3 changed files with 193 additions and 0 deletions
@@ -0,0 +1,143 @@
/*
* 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 <android/log.h>
#include <folly/json.h>
#include <hermes/hermes.h>
#include <perfetto.h>
#include <iostream>
#include <reactperflogger/fusebox/FuseboxTracer.h>
#include "FuseboxPerfettoDataSource.h"
#include "ReactPerfetto.h"
namespace facebook::react {
namespace {
const std::string JS_SAMPLING_TRACK = "JS Sampling";
const int SAMPLING_HZ = 1000;
using perfetto::TrackEvent;
std::string getApplicationId() {
pid_t pid = getpid();
char path[64] = {0};
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
FILE* cmdline = fopen(path, "r");
if (cmdline) {
char application_id[64] = {0};
fread(application_id, sizeof(application_id), 1, cmdline);
fclose(cmdline);
return application_id;
}
return "";
}
uint64_t hermesToPerfettoTime(int64_t hermesTs) {
return (hermesTs / 1000);
}
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) {
for (size_t i = 0; i < stack.size(); i++) {
const auto& frame = stack[i];
// Omit elements that are not the first 25 or the last 25
if (i > 25 && i < stack.size() - 25) {
if (i == 26) {
FuseboxTracer::getFuseboxTracer().addEvent(
"...", start, end, JS_SAMPLING_TRACK);
}
continue;
}
std::string name = frame["name"].asString();
FuseboxTracer::getFuseboxTracer().addEvent(
name, start, end, JS_SAMPLING_TRACK);
}
}
void logHermesProfileToFusebox(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 FuseboxPerfettoDataSource::OnStart(const StartArgs&) {
FuseboxTracer::getFuseboxTracer().startTracing();
facebook::hermes::HermesRuntime::enableSamplingProfiler(SAMPLING_HZ);
}
void FuseboxPerfettoDataSource::OnFlush(const FlushArgs&) {}
void FuseboxPerfettoDataSource::OnStop(const StopArgs& a) {
std::stringstream stream;
facebook::hermes::HermesRuntime::dumpSampledTraceToStream(stream);
std::string trace = stream.str();
logHermesProfileToFusebox(trace);
// XXX: Adjust the package ID. Writing to other global tmp directories
// seems to fail.
std::string appId = getApplicationId();
__android_log_print(
ANDROID_LOG_INFO, "FuseboxTracer", "Application ID: %s", appId.c_str());
FuseboxTracer::getFuseboxTracer().stopTracingAndWriteToFile(
"/data/data/" + appId + "/cache/hermes_trace.json");
}
} // namespace facebook::react
PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(
facebook::react::FuseboxPerfettoDataSource);
#endif // WITH_PERFETTO
@@ -0,0 +1,48 @@
/*
* 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>
namespace facebook::react {
/**
* This is a special datasource that will use Perfetto to start/stop
* a FuseboxTracer trace (which will be saved to disk, not to the Perfetto
* trace) while we wait for Fusebox to be supported in release builds.
* Once it's supported we'll probably want to deprecate this data source.
*/
class FuseboxPerfettoDataSource
: public perfetto::DataSource<FuseboxPerfettoDataSource> {
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.react.fusebox.profiler");
FuseboxPerfettoDataSource::Register(dsd);
}
constexpr static perfetto::BufferExhaustedPolicy kBufferExhaustedPolicy =
perfetto::BufferExhaustedPolicy::kStall;
};
} // namespace facebook::react
PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(
facebook::react::FuseboxPerfettoDataSource);
#endif // WITH_PERFETTO
@@ -12,6 +12,7 @@
#include <perfetto.h>
#include <unordered_map>
#include "FuseboxPerfettoDataSource.h"
#include "HermesPerfettoDataSource.h"
#include "ReactPerfettoCategories.h"
@@ -32,6 +33,7 @@ void initializePerfetto() {
});
HermesPerfettoDataSource::RegisterDataSource();
FuseboxPerfettoDataSource::RegisterDataSource();
}
static perfetto::Track createTrack(const std::string& trackName) {