mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add support for devtools' profiler
Summary: Add support for analyzing sampling profiler data in devtools' JavaScript Profiler tab. Changelog: [Added] Reviewed By: neildhar Differential Revision: D34114709 fbshipit-source-id: 1bf02ce02a250f68af1189c6516fb79795a8e887
This commit is contained in:
committed by
Facebook GitHub Bot
parent
184dfb8f8b
commit
fefa7b6ac8
@@ -101,6 +101,8 @@ class Connection::Impl : public inspector::InspectorObserver,
|
||||
void handle(
|
||||
const m::heapProfiler::GetObjectByHeapObjectIdRequest &req) override;
|
||||
void handle(const m::heapProfiler::GetHeapObjectIdRequest &req) override;
|
||||
void handle(const m::profiler::StartRequest &req) override;
|
||||
void handle(const m::profiler::StopRequest &req) override;
|
||||
void handle(const m::runtime::CallFunctionOnRequest &req) override;
|
||||
void handle(const m::runtime::EvaluateRequest &req) override;
|
||||
void handle(const m::runtime::GetHeapUsageRequest &req) override;
|
||||
@@ -135,6 +137,11 @@ class Connection::Impl : public inspector::InspectorObserver,
|
||||
void sendNotificationToClientViaExecutor(const m::Notification ¬e);
|
||||
void sendErrorToClientViaExecutor(int id, const std::string &error);
|
||||
|
||||
template <typename C>
|
||||
void runInExecutor(int id, C callback) {
|
||||
folly::via(executor_.get(), [cb = std::move(callback)]() { cb(); });
|
||||
}
|
||||
|
||||
std::shared_ptr<RuntimeAdapter> runtimeAdapter_;
|
||||
std::string title_;
|
||||
|
||||
@@ -721,6 +728,60 @@ void Connection::Impl::handle(
|
||||
.thenError<std::exception>(sendErrorToClient(req.id));
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(const m::profiler::StartRequest &req) {
|
||||
auto *hermesRT = dynamic_cast<HermesRuntime *>(&getRuntime());
|
||||
|
||||
if (!hermesRT) {
|
||||
sendResponseToClientViaExecutor(m::makeErrorResponse(
|
||||
req.id, m::ErrorCode::ServerError, "Unhandled Runtime kind."));
|
||||
return;
|
||||
}
|
||||
|
||||
runInExecutor(req.id, [this, id = req.id]() {
|
||||
HermesRuntime::enableSamplingProfiler();
|
||||
sendResponseToClient(m::makeOkResponse(id));
|
||||
});
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(const m::profiler::StopRequest &req) {
|
||||
auto *hermesRT = dynamic_cast<HermesRuntime *>(&getRuntime());
|
||||
|
||||
if (!hermesRT) {
|
||||
sendResponseToClientViaExecutor(m::makeErrorResponse(
|
||||
req.id, m::ErrorCode::ServerError, "Unhandled Runtime kind."));
|
||||
return;
|
||||
}
|
||||
|
||||
runInExecutor(req.id, [this, id = req.id, hermesRT]() {
|
||||
HermesRuntime::disableSamplingProfiler();
|
||||
|
||||
std::ostringstream profileStream;
|
||||
// HermesRuntime instance methods are usually unsafe to be called with a
|
||||
// running VM, but sampledTraceToStreamInDevToolsFormat is an exception to
|
||||
// that rule -- it synchronizes access to shared resources so it can be
|
||||
// safely invoked with a running VM.
|
||||
hermesRT->sampledTraceToStreamInDevToolsFormat(profileStream);
|
||||
|
||||
// Hermes can emit the proper format directly, but it still needs to
|
||||
// be parsed into a dynamic.
|
||||
try {
|
||||
m::profiler::StopResponse resp;
|
||||
resp.id = id;
|
||||
// parseJson throws on errors, so make sure we don't crash the app
|
||||
// if somehow the sampling profiler output is borked.
|
||||
resp.profile = m::profiler::Profile(
|
||||
folly::parseJson(std::move(profileStream).str()));
|
||||
sendResponseToClient(resp);
|
||||
} catch (const std::exception &) {
|
||||
LOG(ERROR) << "Failed to parse Sampling Profiler output";
|
||||
sendResponseToClient(m::makeErrorResponse(
|
||||
id,
|
||||
m::ErrorCode::InternalError,
|
||||
"Hermes profile output could not be parsed."));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// Runtime.CallArguments can have their values specified "inline", or they can
|
||||
/// have remote references. The inline values are eval'd together with the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
|
||||
// @generated SignedSource<<130ce3da2ad67004eb7bb91f19028a89>>
|
||||
// @generated SignedSource<<f6f9a72f332587809b4e50ab054e0a74>>
|
||||
|
||||
#include "MessageTypes.h"
|
||||
|
||||
@@ -60,6 +60,8 @@ std::unique_ptr<Request> Request::fromJsonThrowOnError(const std::string &str) {
|
||||
makeUnique<heapProfiler::StopTrackingHeapObjectsRequest>},
|
||||
{"HeapProfiler.takeHeapSnapshot",
|
||||
makeUnique<heapProfiler::TakeHeapSnapshotRequest>},
|
||||
{"Profiler.start", makeUnique<profiler::StartRequest>},
|
||||
{"Profiler.stop", makeUnique<profiler::StopRequest>},
|
||||
{"Runtime.callFunctionOn", makeUnique<runtime::CallFunctionOnRequest>},
|
||||
{"Runtime.evaluate", makeUnique<runtime::EvaluateRequest>},
|
||||
{"Runtime.getHeapUsage", makeUnique<runtime::GetHeapUsageRequest>},
|
||||
@@ -276,6 +278,59 @@ dynamic heapProfiler::SamplingHeapProfile::toDynamic() const {
|
||||
return obj;
|
||||
}
|
||||
|
||||
profiler::PositionTickInfo::PositionTickInfo(const dynamic &obj) {
|
||||
assign(line, obj, "line");
|
||||
assign(ticks, obj, "ticks");
|
||||
}
|
||||
|
||||
dynamic profiler::PositionTickInfo::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
|
||||
put(obj, "line", line);
|
||||
put(obj, "ticks", ticks);
|
||||
return obj;
|
||||
}
|
||||
|
||||
profiler::ProfileNode::ProfileNode(const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
assign(callFrame, obj, "callFrame");
|
||||
assign(hitCount, obj, "hitCount");
|
||||
assign(children, obj, "children");
|
||||
assign(deoptReason, obj, "deoptReason");
|
||||
assign(positionTicks, obj, "positionTicks");
|
||||
}
|
||||
|
||||
dynamic profiler::ProfileNode::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
|
||||
put(obj, "id", id);
|
||||
put(obj, "callFrame", callFrame);
|
||||
put(obj, "hitCount", hitCount);
|
||||
put(obj, "children", children);
|
||||
put(obj, "deoptReason", deoptReason);
|
||||
put(obj, "positionTicks", positionTicks);
|
||||
return obj;
|
||||
}
|
||||
|
||||
profiler::Profile::Profile(const dynamic &obj) {
|
||||
assign(nodes, obj, "nodes");
|
||||
assign(startTime, obj, "startTime");
|
||||
assign(endTime, obj, "endTime");
|
||||
assign(samples, obj, "samples");
|
||||
assign(timeDeltas, obj, "timeDeltas");
|
||||
}
|
||||
|
||||
dynamic profiler::Profile::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
|
||||
put(obj, "nodes", nodes);
|
||||
put(obj, "startTime", startTime);
|
||||
put(obj, "endTime", endTime);
|
||||
put(obj, "samples", samples);
|
||||
put(obj, "timeDeltas", timeDeltas);
|
||||
return obj;
|
||||
}
|
||||
|
||||
runtime::CallArgument::CallArgument(const dynamic &obj) {
|
||||
assign(value, obj, "value");
|
||||
assign(unserializableValue, obj, "unserializableValue");
|
||||
@@ -974,6 +1029,44 @@ void heapProfiler::TakeHeapSnapshotRequest::accept(
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
profiler::StartRequest::StartRequest() : Request("Profiler.start") {}
|
||||
|
||||
profiler::StartRequest::StartRequest(const dynamic &obj)
|
||||
: Request("Profiler.start") {
|
||||
assign(id, obj, "id");
|
||||
assign(method, obj, "method");
|
||||
}
|
||||
|
||||
dynamic profiler::StartRequest::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "method", method);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void profiler::StartRequest::accept(RequestHandler &handler) const {
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
profiler::StopRequest::StopRequest() : Request("Profiler.stop") {}
|
||||
|
||||
profiler::StopRequest::StopRequest(const dynamic &obj)
|
||||
: Request("Profiler.stop") {
|
||||
assign(id, obj, "id");
|
||||
assign(method, obj, "method");
|
||||
}
|
||||
|
||||
dynamic profiler::StopRequest::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "method", method);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void profiler::StopRequest::accept(RequestHandler &handler) const {
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
runtime::CallFunctionOnRequest::CallFunctionOnRequest()
|
||||
: Request("Runtime.callFunctionOn") {}
|
||||
|
||||
@@ -1293,6 +1386,23 @@ dynamic heapProfiler::StopSamplingResponse::toDynamic() const {
|
||||
return obj;
|
||||
}
|
||||
|
||||
profiler::StopResponse::StopResponse(const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
|
||||
dynamic res = obj.at("result");
|
||||
assign(profile, res, "profile");
|
||||
}
|
||||
|
||||
dynamic profiler::StopResponse::toDynamic() const {
|
||||
dynamic res = dynamic::object;
|
||||
put(res, "profile", profile);
|
||||
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "result", std::move(res));
|
||||
return obj;
|
||||
}
|
||||
|
||||
runtime::CallFunctionOnResponse::CallFunctionOnResponse(const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
|
||||
// @generated SignedSource<<41786947d74eb3f47d8168b82b5ccf85>>
|
||||
// @generated SignedSource<<fcbcfeecbc72ca30c8ed005ad47839bb>>
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -96,6 +96,15 @@ struct StopTrackingHeapObjectsRequest;
|
||||
struct TakeHeapSnapshotRequest;
|
||||
} // namespace heapProfiler
|
||||
|
||||
namespace profiler {
|
||||
struct PositionTickInfo;
|
||||
struct Profile;
|
||||
struct ProfileNode;
|
||||
struct StartRequest;
|
||||
struct StopRequest;
|
||||
struct StopResponse;
|
||||
} // namespace profiler
|
||||
|
||||
/// RequestHandler handles requests via the visitor pattern.
|
||||
struct RequestHandler {
|
||||
virtual ~RequestHandler() = default;
|
||||
@@ -127,6 +136,8 @@ struct RequestHandler {
|
||||
virtual void handle(
|
||||
const heapProfiler::StopTrackingHeapObjectsRequest &req) = 0;
|
||||
virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0;
|
||||
virtual void handle(const profiler::StartRequest &req) = 0;
|
||||
virtual void handle(const profiler::StopRequest &req) = 0;
|
||||
virtual void handle(const runtime::CallFunctionOnRequest &req) = 0;
|
||||
virtual void handle(const runtime::EvaluateRequest &req) = 0;
|
||||
virtual void handle(const runtime::GetHeapUsageRequest &req) = 0;
|
||||
@@ -163,6 +174,8 @@ struct NoopRequestHandler : public RequestHandler {
|
||||
void handle(
|
||||
const heapProfiler::StopTrackingHeapObjectsRequest &req) override {}
|
||||
void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {}
|
||||
void handle(const profiler::StartRequest &req) override {}
|
||||
void handle(const profiler::StopRequest &req) override {}
|
||||
void handle(const runtime::CallFunctionOnRequest &req) override {}
|
||||
void handle(const runtime::EvaluateRequest &req) override {}
|
||||
void handle(const runtime::GetHeapUsageRequest &req) override {}
|
||||
@@ -290,6 +303,40 @@ struct heapProfiler::SamplingHeapProfile : public Serializable {
|
||||
std::vector<heapProfiler::SamplingHeapProfileSample> samples;
|
||||
};
|
||||
|
||||
struct profiler::PositionTickInfo : public Serializable {
|
||||
PositionTickInfo() = default;
|
||||
explicit PositionTickInfo(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
int line{};
|
||||
int ticks{};
|
||||
};
|
||||
|
||||
struct profiler::ProfileNode : public Serializable {
|
||||
ProfileNode() = default;
|
||||
explicit ProfileNode(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
int id{};
|
||||
runtime::CallFrame callFrame{};
|
||||
folly::Optional<int> hitCount;
|
||||
folly::Optional<std::vector<int>> children;
|
||||
folly::Optional<std::string> deoptReason;
|
||||
folly::Optional<std::vector<profiler::PositionTickInfo>> positionTicks;
|
||||
};
|
||||
|
||||
struct profiler::Profile : public Serializable {
|
||||
Profile() = default;
|
||||
explicit Profile(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
std::vector<profiler::ProfileNode> nodes;
|
||||
double startTime{};
|
||||
double endTime{};
|
||||
folly::Optional<std::vector<int>> samples;
|
||||
folly::Optional<std::vector<int>> timeDeltas;
|
||||
};
|
||||
|
||||
struct runtime::CallArgument : public Serializable {
|
||||
CallArgument() = default;
|
||||
explicit CallArgument(const folly::dynamic &obj);
|
||||
@@ -569,6 +616,22 @@ struct heapProfiler::TakeHeapSnapshotRequest : public Request {
|
||||
folly::Optional<bool> captureNumericValue;
|
||||
};
|
||||
|
||||
struct profiler::StartRequest : public Request {
|
||||
StartRequest();
|
||||
explicit StartRequest(const folly::dynamic &obj);
|
||||
|
||||
folly::dynamic toDynamic() const override;
|
||||
void accept(RequestHandler &handler) const override;
|
||||
};
|
||||
|
||||
struct profiler::StopRequest : public Request {
|
||||
StopRequest();
|
||||
explicit StopRequest(const folly::dynamic &obj);
|
||||
|
||||
folly::dynamic toDynamic() const override;
|
||||
void accept(RequestHandler &handler) const override;
|
||||
};
|
||||
|
||||
struct runtime::CallFunctionOnRequest : public Request {
|
||||
CallFunctionOnRequest();
|
||||
explicit CallFunctionOnRequest(const folly::dynamic &obj);
|
||||
@@ -707,6 +770,14 @@ struct heapProfiler::StopSamplingResponse : public Response {
|
||||
heapProfiler::SamplingHeapProfile profile{};
|
||||
};
|
||||
|
||||
struct profiler::StopResponse : public Response {
|
||||
StopResponse() = default;
|
||||
explicit StopResponse(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
profiler::Profile profile{};
|
||||
};
|
||||
|
||||
struct runtime::CallFunctionOnResponse : public Response {
|
||||
CallFunctionOnResponse() = default;
|
||||
explicit CallFunctionOnResponse(const folly::dynamic &obj);
|
||||
|
||||
@@ -139,6 +139,27 @@ std::string AsyncHermesRuntime::getLastThrownExceptionMessage() {
|
||||
return thrownExceptions_.back();
|
||||
}
|
||||
|
||||
void AsyncHermesRuntime::registerForProfilingInExecutor() {
|
||||
// Sampling profiler registration needs to happen in the thread where JS runs.
|
||||
folly::via(executor_.get(), [runtime = runtime_]() {
|
||||
runtime->registerForProfiling();
|
||||
});
|
||||
|
||||
// Wait until the executor is registered for profiling.
|
||||
wait();
|
||||
}
|
||||
|
||||
void AsyncHermesRuntime::unregisterForProfilingInExecutor() {
|
||||
// Sampling profiler deregistration needs to happen in the thread where JS
|
||||
// runs.
|
||||
folly::via(executor_.get(), [runtime = runtime_]() {
|
||||
runtime->unregisterForProfiling();
|
||||
});
|
||||
|
||||
// Wait until the executor is unregistered for profiling.
|
||||
wait();
|
||||
}
|
||||
|
||||
} // namespace chrome
|
||||
} // namespace inspector
|
||||
} // namespace hermes
|
||||
|
||||
@@ -95,6 +95,16 @@ class AsyncHermesRuntime {
|
||||
*/
|
||||
std::string getLastThrownExceptionMessage();
|
||||
|
||||
/**
|
||||
* registers the runtime for profiling in the executor thread.
|
||||
*/
|
||||
void registerForProfilingInExecutor();
|
||||
|
||||
/**
|
||||
* unregisters the runtime for profiling in the executor thread.
|
||||
*/
|
||||
void unregisterForProfilingInExecutor();
|
||||
|
||||
private:
|
||||
jsi::Value shouldStop(
|
||||
jsi::Runtime &runtime,
|
||||
@@ -115,6 +125,23 @@ class AsyncHermesRuntime {
|
||||
std::vector<std::string> thrownExceptions_;
|
||||
};
|
||||
|
||||
/// RAII-style class dealing with sampling profiler registration in tests. This
|
||||
/// is especially important in tests -- if any test failure is caused by an
|
||||
/// uncaught exception, stack unwinding will destroy a VM registered for
|
||||
/// profiling in a thread that's not the one where registration happened, which
|
||||
/// will lead to a hermes fatal error. Using this RAII class ensure that the
|
||||
/// proper test failure cause is reported.
|
||||
struct SamplingProfilerRAII {
|
||||
explicit SamplingProfilerRAII(AsyncHermesRuntime &rt) : runtime_(rt) {
|
||||
runtime_.registerForProfilingInExecutor();
|
||||
}
|
||||
|
||||
~SamplingProfilerRAII() {
|
||||
runtime_.unregisterForProfilingInExecutor();
|
||||
}
|
||||
|
||||
AsyncHermesRuntime &runtime_;
|
||||
};
|
||||
} // namespace chrome
|
||||
} // namespace inspector
|
||||
} // namespace hermes
|
||||
|
||||
@@ -2837,6 +2837,53 @@ TEST(ConnectionTests, heapSnapshotRemoteObject) {
|
||||
expectNotification<m::debugger::ResumedNotification>(conn);
|
||||
}
|
||||
|
||||
TEST(ConnectionTests, testBasicProfilerOperation) {
|
||||
TestContext context;
|
||||
AsyncHermesRuntime &asyncRuntime = context.runtime();
|
||||
SamplingProfilerRAII spRegistration(asyncRuntime);
|
||||
SyncConnection &conn = context.conn();
|
||||
int msgId = 1;
|
||||
|
||||
asyncRuntime.executeScriptAsync(R"(
|
||||
while(!shouldStop());
|
||||
)");
|
||||
|
||||
send<m::debugger::EnableRequest>(conn, msgId++);
|
||||
expectExecutionContextCreated(conn);
|
||||
expectNotification<m::debugger::ScriptParsedNotification>(conn);
|
||||
|
||||
// Start the sampling profiler. At this point it is not safe to manipulate the
|
||||
// VM, so...
|
||||
send<m::profiler::StartRequest>(conn, msgId++);
|
||||
|
||||
// Disable the debugger.
|
||||
send<m::debugger::DisableRequest>(conn, msgId++);
|
||||
|
||||
// Keep the profiler running for a small amount of time to allow for some
|
||||
// samples to be collected.
|
||||
std::this_thread::sleep_for(500ms);
|
||||
|
||||
// Finally, re-enable the debugger in order to stop profiling.
|
||||
send<m::debugger::EnableRequest>(conn, msgId++);
|
||||
expectExecutionContextCreated(conn);
|
||||
|
||||
// Being re-attached to the VM, send the stop sampling profile request.
|
||||
{
|
||||
auto resp = send<m::profiler::StopRequest, m::profiler::StopResponse>(
|
||||
conn, msgId++);
|
||||
|
||||
const m::profiler::Profile &profile = resp.profile;
|
||||
EXPECT_GT(profile.nodes.size(), 0);
|
||||
EXPECT_LT(profile.startTime, profile.endTime);
|
||||
ASSERT_TRUE(profile.samples);
|
||||
EXPECT_FALSE(profile.samples->empty());
|
||||
ASSERT_TRUE(profile.timeDeltas);
|
||||
EXPECT_EQ(profile.samples->size(), profile.timeDeltas->size());
|
||||
}
|
||||
|
||||
asyncRuntime.stop();
|
||||
}
|
||||
|
||||
} // namespace chrome
|
||||
} // namespace inspector
|
||||
} // namespace hermes
|
||||
|
||||
@@ -28,6 +28,8 @@ HeapProfiler.heapStatsUpdate
|
||||
HeapProfiler.lastSeenObjectId
|
||||
HeapProfiler.getObjectByHeapObjectId
|
||||
HeapProfiler.getHeapObjectId
|
||||
Profiler.start
|
||||
Profiler.stop
|
||||
Runtime.callFunctionOn
|
||||
Runtime.consoleAPICalled
|
||||
Runtime.evaluate
|
||||
|
||||
Reference in New Issue
Block a user