mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Introduce FuseboxTracer for DevTools tracing (#44840)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44840 Changelog: [Internal] Introduce a simplified and minimal tracing backend for Fusebox. This backend is sufficient to implement a pretty usable performance panel. Although the more I see how easy this is and how annoying working with Perfetto is, the more I think we should just maintain this going forward. Anyways we can figure that out incrementally. For now the plan is still for this to be temporary. Reviewed By: motiz88 Differential Revision: D57981944 fbshipit-source-id: b3d8c6e8c5a18311bbe98254f8ddf3810fa1334b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
82325b4260
commit
30087a6e00
@@ -23,4 +23,5 @@ target_link_libraries(jsinspector
|
||||
glog
|
||||
react_featureflags
|
||||
runtimeexecutor
|
||||
reactperflogger
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <reactperflogger/fusebox/FuseboxTracer.h>
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
@@ -146,25 +148,41 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
shouldSendOKResponse = true;
|
||||
isFinishedHandlingRequest = true;
|
||||
} else if (req.method == "Tracing.start") {
|
||||
// @cdp Tracing.start is implemented as a stub only.
|
||||
frontendChannel_(cdp::jsonNotification(
|
||||
// @cdp Tracing.bufferUsage is implemented as a stub only.
|
||||
"Tracing.bufferUsage",
|
||||
folly::dynamic::object("percentFull", 0)("eventCount", 0)("value", 0)));
|
||||
shouldSendOKResponse = true;
|
||||
// @cdp Tracing.start support is experimental.
|
||||
if (FuseboxTracer::getFuseboxTracer().startTracing()) {
|
||||
shouldSendOKResponse = true;
|
||||
} else {
|
||||
frontendChannel_(cdp::jsonError(
|
||||
req.id,
|
||||
cdp::ErrorCode::InternalError,
|
||||
"Tracing session already started"));
|
||||
return;
|
||||
}
|
||||
isFinishedHandlingRequest = true;
|
||||
} else if (req.method == "Tracing.end") {
|
||||
// @cdp Tracing.end is implemented as a stub only.
|
||||
// @cdp Tracing.end support is experimental.
|
||||
bool firstChunk = true;
|
||||
auto id = req.id;
|
||||
bool wasStopped = FuseboxTracer::getFuseboxTracer().stopTracing(
|
||||
[this, firstChunk, id](const folly::dynamic& eventsChunk) {
|
||||
if (firstChunk) {
|
||||
frontendChannel_(cdp::jsonResult(id));
|
||||
}
|
||||
frontendChannel_(cdp::jsonNotification(
|
||||
"Tracing.dataCollected",
|
||||
folly::dynamic::object("value", eventsChunk)));
|
||||
});
|
||||
if (!wasStopped) {
|
||||
frontendChannel_(cdp::jsonError(
|
||||
req.id,
|
||||
cdp::ErrorCode::InternalError,
|
||||
"Tracing session not started"));
|
||||
return;
|
||||
}
|
||||
frontendChannel_(cdp::jsonNotification(
|
||||
// @cdp Tracing.dataCollected is implemented as a stub only.
|
||||
"Tracing.dataCollected",
|
||||
folly::dynamic::object("value", folly::dynamic::array())));
|
||||
frontendChannel_(cdp::jsonNotification(
|
||||
// @cdp Tracing.tracingComplete is implemented as a stub only.
|
||||
"Tracing.tracingComplete",
|
||||
folly::dynamic::object("dataLossOccurred", false)));
|
||||
shouldSendOKResponse = true;
|
||||
isFinishedHandlingRequest = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFinishedHandlingRequest && instanceAgent_ &&
|
||||
|
||||
@@ -54,6 +54,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "DoubleConversion"
|
||||
s.dependency "React-runtimeexecutor", version
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "React-perflogger", version
|
||||
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
|
||||
s.dependency "hermes-engine"
|
||||
end
|
||||
|
||||
+13
-1
@@ -15,7 +15,8 @@
|
||||
#include <cxxreact/ReactMarker.h>
|
||||
#include <jsi/instrumentation.h>
|
||||
#include <react/performance/timeline/PerformanceEntryReporter.h>
|
||||
|
||||
#include <reactperflogger/fusebox/FuseboxTracer.h>
|
||||
#include "NativePerformance.h"
|
||||
#include "Plugins.h"
|
||||
|
||||
#ifdef WITH_PERFETTO
|
||||
@@ -112,6 +113,17 @@ void NativePerformance::measure(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
std::string trackName = "Web Performance";
|
||||
const int TRACK_PREFIX = 6;
|
||||
if (name.starts_with("Track:")) {
|
||||
const auto trackNameDelimiter = name.find(':', TRACK_PREFIX);
|
||||
if (trackNameDelimiter != std::string::npos) {
|
||||
trackName = name.substr(TRACK_PREFIX, trackNameDelimiter - TRACK_PREFIX);
|
||||
name = name.substr(trackNameDelimiter + 1);
|
||||
}
|
||||
}
|
||||
FuseboxTracer::getFuseboxTracer().addEvent(
|
||||
name, (uint64_t)startTime, (uint64_t)endTime, trackName);
|
||||
PerformanceEntryReporter::getInstance()->measure(
|
||||
name, startTime, endTime, duration, startMark, endMark);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,12 @@ add_compile_options(
|
||||
-Wall
|
||||
-Wpedantic)
|
||||
|
||||
file(GLOB reactperflogger_SRC CONFIGURE_DEPENDS reactperflogger/*.cpp)
|
||||
|
||||
file(GLOB reactperflogger_SRC CONFIGURE_DEPENDS
|
||||
reactperflogger/*.cpp
|
||||
fusebox/*.cpp)
|
||||
add_library(reactperflogger STATIC ${reactperflogger_SRC})
|
||||
|
||||
target_include_directories(reactperflogger PUBLIC .)
|
||||
|
||||
target_link_libraries(reactperflogger folly_runtime)
|
||||
|
||||
@@ -19,7 +19,13 @@ end
|
||||
folly_config = get_folly_config()
|
||||
folly_compiler_flags = folly_config[:compiler_flags]
|
||||
folly_version = folly_config[:version]
|
||||
boost_compiler_flags = '-Wno-documentation'
|
||||
|
||||
header_search_paths = [
|
||||
"\"$(PODS_TARGET_SRCROOT)/..\"",
|
||||
"\"$(PODS_ROOT)/RCT-Folly\"",
|
||||
"\"$(PODS_ROOT)/DoubleConversion\"",
|
||||
"\"$(PODS_ROOT)/fmt/include\""
|
||||
]
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-perflogger"
|
||||
@@ -30,7 +36,15 @@ Pod::Spec.new do |s|
|
||||
s.author = "Meta Platforms, Inc. and its affiliates"
|
||||
s.platforms = min_supported_versions
|
||||
s.source = source
|
||||
s.source_files = "**/*.{cpp,h}"
|
||||
s.source_files = "reactperflogger/*.{cpp,h}", "fusebox/*.{cpp,h}"
|
||||
s.header_dir = "reactperflogger"
|
||||
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20" }
|
||||
s.compiler_flags = folly_compiler_flags
|
||||
s.pod_target_xcconfig = {
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
|
||||
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
|
||||
}
|
||||
|
||||
s.dependency "RCT-Folly", folly_version
|
||||
s.dependency "DoubleConversion"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "FuseboxTracer.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
bool FuseboxTracer::isTracing() {
|
||||
std::lock_guard lock(mutex_);
|
||||
return tracing_;
|
||||
}
|
||||
|
||||
bool FuseboxTracer::startTracing() {
|
||||
std::lock_guard lock(mutex_);
|
||||
if (tracing_) {
|
||||
return false;
|
||||
}
|
||||
tracing_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FuseboxTracer::stopTracing(
|
||||
const std::function<void(const folly::dynamic& eventsChunk)>&
|
||||
resultCallback) {
|
||||
std::lock_guard lock(mutex_);
|
||||
|
||||
if (!tracing_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tracing_ = false;
|
||||
if (buffer_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto traceEvents = folly::dynamic::array();
|
||||
auto savedBuffer = std::move(buffer_);
|
||||
buffer_.clear();
|
||||
|
||||
std::unordered_map<std::string, uint64_t> trackIdMap;
|
||||
uint64_t nextTrack = 1000;
|
||||
|
||||
// Name the main process. Only one process is supported currently.
|
||||
traceEvents.push_back(folly::dynamic::object(
|
||||
"args", folly::dynamic::object("name", "Main App"))("cat", "__metadata")(
|
||||
"name", "process_name")("ph", "M")("pid", 1000)("tid", 0)("ts", 0));
|
||||
|
||||
for (auto& event : savedBuffer) {
|
||||
if (!trackIdMap.contains(event.track)) {
|
||||
auto trackId = nextTrack++;
|
||||
trackIdMap[event.track] = trackId;
|
||||
// New track
|
||||
traceEvents.push_back(folly::dynamic::object(
|
||||
"args", folly::dynamic::object("name", event.track))(
|
||||
"cat", "__metadata")("name", "thread_name")("ph", "M")("pid", 1000)(
|
||||
"tid", trackId)("ts", 0));
|
||||
}
|
||||
auto trackId = trackIdMap[event.track];
|
||||
|
||||
// New event
|
||||
traceEvents.push_back(folly::dynamic::object(
|
||||
"args", folly::dynamic::object())("cat", "react.native")(
|
||||
"dur", (event.end - event.start) * 1000)("name", event.name)("ph", "X")(
|
||||
"ts", event.start * 1000)("pid", 1000)("tid", trackId));
|
||||
|
||||
if (traceEvents.size() >= 1000) {
|
||||
resultCallback(traceEvents);
|
||||
traceEvents = folly::dynamic::array();
|
||||
}
|
||||
}
|
||||
|
||||
if (traceEvents.size() >= 1) {
|
||||
resultCallback(traceEvents);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FuseboxTracer::addEvent(
|
||||
const std::string& name,
|
||||
uint64_t start,
|
||||
uint64_t end,
|
||||
const std::string& track) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!tracing_) {
|
||||
return;
|
||||
}
|
||||
buffer_.push_back(BufferEvent{start, end, name, track});
|
||||
}
|
||||
|
||||
/* static */ FuseboxTracer& FuseboxTracer::getFuseboxTracer() {
|
||||
static FuseboxTracer tracer;
|
||||
return tracer;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "folly/dynamic.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
struct BufferEvent {
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
std::string name;
|
||||
std::string track;
|
||||
};
|
||||
|
||||
class FuseboxTracer {
|
||||
public:
|
||||
FuseboxTracer(const FuseboxTracer&) = delete;
|
||||
|
||||
bool isTracing();
|
||||
// Verifies that tracing isn't started and starts tracing all in one step.
|
||||
// Returns true if we were able to successful start tracing.
|
||||
bool startTracing();
|
||||
// Verifies that we're tracing and dumps the trace all in one step to avoid
|
||||
// TOCTOU bugs. Returns false if we're not tracing. No result callbacks
|
||||
// are expected in that scenario.
|
||||
bool stopTracing(const std::function<void(const folly::dynamic& eventsChunk)>&
|
||||
resultCallback);
|
||||
void addEvent(
|
||||
const std::string& name,
|
||||
uint64_t start,
|
||||
uint64_t end,
|
||||
const std::string& track);
|
||||
|
||||
static FuseboxTracer& getFuseboxTracer();
|
||||
|
||||
private:
|
||||
FuseboxTracer() {}
|
||||
|
||||
bool tracing_{false};
|
||||
std::vector<BufferEvent> buffer_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <reactperflogger/fusebox/FuseboxTracer.h>
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
namespace {
|
||||
|
||||
class FuseboxTracerTest : public ::testing::Test {
|
||||
protected:
|
||||
FuseboxTracerTest() = default;
|
||||
|
||||
~FuseboxTracerTest() override = default;
|
||||
|
||||
void SetUp() override {
|
||||
stopTracingAndCollect();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
stopTracingAndCollect();
|
||||
}
|
||||
|
||||
folly::dynamic stopTracingAndCollect() {
|
||||
folly::dynamic trace = folly::dynamic::array;
|
||||
FuseboxTracer::getFuseboxTracer().stopTracing(
|
||||
[&trace](const folly::dynamic& eventsChunk) {
|
||||
for (const auto& event : eventsChunk) {
|
||||
trace.push_back(event);
|
||||
}
|
||||
});
|
||||
return trace;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(FuseboxTracerTest, TracingOffByDefault) {
|
||||
EXPECT_FALSE(FuseboxTracer::getFuseboxTracer().isTracing());
|
||||
}
|
||||
|
||||
TEST_F(FuseboxTracerTest, TracingOn) {
|
||||
FuseboxTracer::getFuseboxTracer().startTracing();
|
||||
EXPECT_TRUE(FuseboxTracer::getFuseboxTracer().isTracing());
|
||||
stopTracingAndCollect();
|
||||
}
|
||||
|
||||
TEST_F(FuseboxTracerTest, DiscardEventWhenNotOn) {
|
||||
EXPECT_FALSE(FuseboxTracer::getFuseboxTracer().isTracing());
|
||||
EXPECT_EQ(stopTracingAndCollect().size(), 0);
|
||||
FuseboxTracer::getFuseboxTracer().addEvent("test", 0, 0, "default track");
|
||||
FuseboxTracer::getFuseboxTracer().addEvent("test", 0, 0, "default track");
|
||||
EXPECT_EQ(stopTracingAndCollect().size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(FuseboxTracerTest, NoDefaultEvents) {
|
||||
FuseboxTracer::getFuseboxTracer().startTracing();
|
||||
EXPECT_EQ(stopTracingAndCollect().size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(FuseboxTracerTest, SimpleEvent) {
|
||||
FuseboxTracer::getFuseboxTracer().startTracing();
|
||||
FuseboxTracer::getFuseboxTracer().addEvent("test", 0, 0, "default track");
|
||||
EXPECT_GE(stopTracingAndCollect().size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(FuseboxTracerTest, MultiEvents) {
|
||||
FuseboxTracer::getFuseboxTracer().startTracing();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
FuseboxTracer::getFuseboxTracer().addEvent("test", 0, 0, "default track");
|
||||
}
|
||||
EXPECT_GE(stopTracingAndCollect().size(), 10);
|
||||
EXPECT_EQ(stopTracingAndCollect().size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(FuseboxTracerTest, ShouldEndTracingEvenIfThereIsNoEvents) {
|
||||
FuseboxTracer::getFuseboxTracer().startTracing();
|
||||
EXPECT_EQ(stopTracingAndCollect().size(), 0);
|
||||
EXPECT_FALSE(FuseboxTracer::getFuseboxTracer().isTracing());
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
Reference in New Issue
Block a user