From ef0ea4d834ec61c5deede487b46a787a363f6a64 Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Tue, 10 Sep 2024 12:54:02 -0700 Subject: [PATCH] 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 --- .../FuseboxPerfettoDataSource.cpp | 143 ++++++++++++++++++ .../FuseboxPerfettoDataSource.h | 48 ++++++ .../reactperflogger/ReactPerfetto.cpp | 2 + 3 files changed, 193 insertions(+) create mode 100644 packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.cpp create mode 100644 packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.h diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.cpp new file mode 100644 index 00000000000..7e48f93bcea --- /dev/null +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.cpp @@ -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 +#include +#include +#include +#include + +#include +#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 getStack( + const folly::dynamic& trace, + const folly::dynamic& sample) { + std::vector 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& 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(); + 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 diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.h b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.h new file mode 100644 index 00000000000..da0936a4a05 --- /dev/null +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/FuseboxPerfettoDataSource.h @@ -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 + +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 { + 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 diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp index 2cf41629c1a..a0786d4c649 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp @@ -12,6 +12,7 @@ #include #include +#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) {