Skip to content

Commit fefa7b6

Browse files
jpportofacebook-github-bot
authored andcommitted
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
1 parent 184dfb8 commit fefa7b6

File tree

7 files changed

+341
-2
lines changed

7 files changed

+341
-2
lines changed

ReactCommon/hermes/inspector/chrome/Connection.cpp

+61
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ class Connection::Impl : public inspector::InspectorObserver,
101101
void handle(
102102
const m::heapProfiler::GetObjectByHeapObjectIdRequest &req) override;
103103
void handle(const m::heapProfiler::GetHeapObjectIdRequest &req) override;
104+
void handle(const m::profiler::StartRequest &req) override;
105+
void handle(const m::profiler::StopRequest &req) override;
104106
void handle(const m::runtime::CallFunctionOnRequest &req) override;
105107
void handle(const m::runtime::EvaluateRequest &req) override;
106108
void handle(const m::runtime::GetHeapUsageRequest &req) override;
@@ -135,6 +137,11 @@ class Connection::Impl : public inspector::InspectorObserver,
135137
void sendNotificationToClientViaExecutor(const m::Notification &note);
136138
void sendErrorToClientViaExecutor(int id, const std::string &error);
137139

140+
template <typename C>
141+
void runInExecutor(int id, C callback) {
142+
folly::via(executor_.get(), [cb = std::move(callback)]() { cb(); });
143+
}
144+
138145
std::shared_ptr<RuntimeAdapter> runtimeAdapter_;
139146
std::string title_;
140147

@@ -721,6 +728,60 @@ void Connection::Impl::handle(
721728
.thenError<std::exception>(sendErrorToClient(req.id));
722729
}
723730

731+
void Connection::Impl::handle(const m::profiler::StartRequest &req) {
732+
auto *hermesRT = dynamic_cast<HermesRuntime *>(&getRuntime());
733+
734+
if (!hermesRT) {
735+
sendResponseToClientViaExecutor(m::makeErrorResponse(
736+
req.id, m::ErrorCode::ServerError, "Unhandled Runtime kind."));
737+
return;
738+
}
739+
740+
runInExecutor(req.id, [this, id = req.id]() {
741+
HermesRuntime::enableSamplingProfiler();
742+
sendResponseToClient(m::makeOkResponse(id));
743+
});
744+
}
745+
746+
void Connection::Impl::handle(const m::profiler::StopRequest &req) {
747+
auto *hermesRT = dynamic_cast<HermesRuntime *>(&getRuntime());
748+
749+
if (!hermesRT) {
750+
sendResponseToClientViaExecutor(m::makeErrorResponse(
751+
req.id, m::ErrorCode::ServerError, "Unhandled Runtime kind."));
752+
return;
753+
}
754+
755+
runInExecutor(req.id, [this, id = req.id, hermesRT]() {
756+
HermesRuntime::disableSamplingProfiler();
757+
758+
std::ostringstream profileStream;
759+
// HermesRuntime instance methods are usually unsafe to be called with a
760+
// running VM, but sampledTraceToStreamInDevToolsFormat is an exception to
761+
// that rule -- it synchronizes access to shared resources so it can be
762+
// safely invoked with a running VM.
763+
hermesRT->sampledTraceToStreamInDevToolsFormat(profileStream);
764+
765+
// Hermes can emit the proper format directly, but it still needs to
766+
// be parsed into a dynamic.
767+
try {
768+
m::profiler::StopResponse resp;
769+
resp.id = id;
770+
// parseJson throws on errors, so make sure we don't crash the app
771+
// if somehow the sampling profiler output is borked.
772+
resp.profile = m::profiler::Profile(
773+
folly::parseJson(std::move(profileStream).str()));
774+
sendResponseToClient(resp);
775+
} catch (const std::exception &) {
776+
LOG(ERROR) << "Failed to parse Sampling Profiler output";
777+
sendResponseToClient(m::makeErrorResponse(
778+
id,
779+
m::ErrorCode::InternalError,
780+
"Hermes profile output could not be parsed."));
781+
}
782+
});
783+
}
784+
724785
namespace {
725786
/// Runtime.CallArguments can have their values specified "inline", or they can
726787
/// have remote references. The inline values are eval'd together with the

ReactCommon/hermes/inspector/chrome/MessageTypes.cpp

+111-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
2-
// @generated SignedSource<<130ce3da2ad67004eb7bb91f19028a89>>
2+
// @generated SignedSource<<f6f9a72f332587809b4e50ab054e0a74>>
33

44
#include "MessageTypes.h"
55

@@ -60,6 +60,8 @@ std::unique_ptr<Request> Request::fromJsonThrowOnError(const std::string &str) {
6060
makeUnique<heapProfiler::StopTrackingHeapObjectsRequest>},
6161
{"HeapProfiler.takeHeapSnapshot",
6262
makeUnique<heapProfiler::TakeHeapSnapshotRequest>},
63+
{"Profiler.start", makeUnique<profiler::StartRequest>},
64+
{"Profiler.stop", makeUnique<profiler::StopRequest>},
6365
{"Runtime.callFunctionOn", makeUnique<runtime::CallFunctionOnRequest>},
6466
{"Runtime.evaluate", makeUnique<runtime::EvaluateRequest>},
6567
{"Runtime.getHeapUsage", makeUnique<runtime::GetHeapUsageRequest>},
@@ -276,6 +278,59 @@ dynamic heapProfiler::SamplingHeapProfile::toDynamic() const {
276278
return obj;
277279
}
278280

281+
profiler::PositionTickInfo::PositionTickInfo(const dynamic &obj) {
282+
assign(line, obj, "line");
283+
assign(ticks, obj, "ticks");
284+
}
285+
286+
dynamic profiler::PositionTickInfo::toDynamic() const {
287+
dynamic obj = dynamic::object;
288+
289+
put(obj, "line", line);
290+
put(obj, "ticks", ticks);
291+
return obj;
292+
}
293+
294+
profiler::ProfileNode::ProfileNode(const dynamic &obj) {
295+
assign(id, obj, "id");
296+
assign(callFrame, obj, "callFrame");
297+
assign(hitCount, obj, "hitCount");
298+
assign(children, obj, "children");
299+
assign(deoptReason, obj, "deoptReason");
300+
assign(positionTicks, obj, "positionTicks");
301+
}
302+
303+
dynamic profiler::ProfileNode::toDynamic() const {
304+
dynamic obj = dynamic::object;
305+
306+
put(obj, "id", id);
307+
put(obj, "callFrame", callFrame);
308+
put(obj, "hitCount", hitCount);
309+
put(obj, "children", children);
310+
put(obj, "deoptReason", deoptReason);
311+
put(obj, "positionTicks", positionTicks);
312+
return obj;
313+
}
314+
315+
profiler::Profile::Profile(const dynamic &obj) {
316+
assign(nodes, obj, "nodes");
317+
assign(startTime, obj, "startTime");
318+
assign(endTime, obj, "endTime");
319+
assign(samples, obj, "samples");
320+
assign(timeDeltas, obj, "timeDeltas");
321+
}
322+
323+
dynamic profiler::Profile::toDynamic() const {
324+
dynamic obj = dynamic::object;
325+
326+
put(obj, "nodes", nodes);
327+
put(obj, "startTime", startTime);
328+
put(obj, "endTime", endTime);
329+
put(obj, "samples", samples);
330+
put(obj, "timeDeltas", timeDeltas);
331+
return obj;
332+
}
333+
279334
runtime::CallArgument::CallArgument(const dynamic &obj) {
280335
assign(value, obj, "value");
281336
assign(unserializableValue, obj, "unserializableValue");
@@ -974,6 +1029,44 @@ void heapProfiler::TakeHeapSnapshotRequest::accept(
9741029
handler.handle(*this);
9751030
}
9761031

1032+
profiler::StartRequest::StartRequest() : Request("Profiler.start") {}
1033+
1034+
profiler::StartRequest::StartRequest(const dynamic &obj)
1035+
: Request("Profiler.start") {
1036+
assign(id, obj, "id");
1037+
assign(method, obj, "method");
1038+
}
1039+
1040+
dynamic profiler::StartRequest::toDynamic() const {
1041+
dynamic obj = dynamic::object;
1042+
put(obj, "id", id);
1043+
put(obj, "method", method);
1044+
return obj;
1045+
}
1046+
1047+
void profiler::StartRequest::accept(RequestHandler &handler) const {
1048+
handler.handle(*this);
1049+
}
1050+
1051+
profiler::StopRequest::StopRequest() : Request("Profiler.stop") {}
1052+
1053+
profiler::StopRequest::StopRequest(const dynamic &obj)
1054+
: Request("Profiler.stop") {
1055+
assign(id, obj, "id");
1056+
assign(method, obj, "method");
1057+
}
1058+
1059+
dynamic profiler::StopRequest::toDynamic() const {
1060+
dynamic obj = dynamic::object;
1061+
put(obj, "id", id);
1062+
put(obj, "method", method);
1063+
return obj;
1064+
}
1065+
1066+
void profiler::StopRequest::accept(RequestHandler &handler) const {
1067+
handler.handle(*this);
1068+
}
1069+
9771070
runtime::CallFunctionOnRequest::CallFunctionOnRequest()
9781071
: Request("Runtime.callFunctionOn") {}
9791072

@@ -1293,6 +1386,23 @@ dynamic heapProfiler::StopSamplingResponse::toDynamic() const {
12931386
return obj;
12941387
}
12951388

1389+
profiler::StopResponse::StopResponse(const dynamic &obj) {
1390+
assign(id, obj, "id");
1391+
1392+
dynamic res = obj.at("result");
1393+
assign(profile, res, "profile");
1394+
}
1395+
1396+
dynamic profiler::StopResponse::toDynamic() const {
1397+
dynamic res = dynamic::object;
1398+
put(res, "profile", profile);
1399+
1400+
dynamic obj = dynamic::object;
1401+
put(obj, "id", id);
1402+
put(obj, "result", std::move(res));
1403+
return obj;
1404+
}
1405+
12961406
runtime::CallFunctionOnResponse::CallFunctionOnResponse(const dynamic &obj) {
12971407
assign(id, obj, "id");
12981408

ReactCommon/hermes/inspector/chrome/MessageTypes.h

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
2-
// @generated SignedSource<<41786947d74eb3f47d8168b82b5ccf85>>
2+
// @generated SignedSource<<fcbcfeecbc72ca30c8ed005ad47839bb>>
33

44
#pragma once
55

@@ -96,6 +96,15 @@ struct StopTrackingHeapObjectsRequest;
9696
struct TakeHeapSnapshotRequest;
9797
} // namespace heapProfiler
9898

99+
namespace profiler {
100+
struct PositionTickInfo;
101+
struct Profile;
102+
struct ProfileNode;
103+
struct StartRequest;
104+
struct StopRequest;
105+
struct StopResponse;
106+
} // namespace profiler
107+
99108
/// RequestHandler handles requests via the visitor pattern.
100109
struct RequestHandler {
101110
virtual ~RequestHandler() = default;
@@ -127,6 +136,8 @@ struct RequestHandler {
127136
virtual void handle(
128137
const heapProfiler::StopTrackingHeapObjectsRequest &req) = 0;
129138
virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0;
139+
virtual void handle(const profiler::StartRequest &req) = 0;
140+
virtual void handle(const profiler::StopRequest &req) = 0;
130141
virtual void handle(const runtime::CallFunctionOnRequest &req) = 0;
131142
virtual void handle(const runtime::EvaluateRequest &req) = 0;
132143
virtual void handle(const runtime::GetHeapUsageRequest &req) = 0;
@@ -163,6 +174,8 @@ struct NoopRequestHandler : public RequestHandler {
163174
void handle(
164175
const heapProfiler::StopTrackingHeapObjectsRequest &req) override {}
165176
void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {}
177+
void handle(const profiler::StartRequest &req) override {}
178+
void handle(const profiler::StopRequest &req) override {}
166179
void handle(const runtime::CallFunctionOnRequest &req) override {}
167180
void handle(const runtime::EvaluateRequest &req) override {}
168181
void handle(const runtime::GetHeapUsageRequest &req) override {}
@@ -290,6 +303,40 @@ struct heapProfiler::SamplingHeapProfile : public Serializable {
290303
std::vector<heapProfiler::SamplingHeapProfileSample> samples;
291304
};
292305

306+
struct profiler::PositionTickInfo : public Serializable {
307+
PositionTickInfo() = default;
308+
explicit PositionTickInfo(const folly::dynamic &obj);
309+
folly::dynamic toDynamic() const override;
310+
311+
int line{};
312+
int ticks{};
313+
};
314+
315+
struct profiler::ProfileNode : public Serializable {
316+
ProfileNode() = default;
317+
explicit ProfileNode(const folly::dynamic &obj);
318+
folly::dynamic toDynamic() const override;
319+
320+
int id{};
321+
runtime::CallFrame callFrame{};
322+
folly::Optional<int> hitCount;
323+
folly::Optional<std::vector<int>> children;
324+
folly::Optional<std::string> deoptReason;
325+
folly::Optional<std::vector<profiler::PositionTickInfo>> positionTicks;
326+
};
327+
328+
struct profiler::Profile : public Serializable {
329+
Profile() = default;
330+
explicit Profile(const folly::dynamic &obj);
331+
folly::dynamic toDynamic() const override;
332+
333+
std::vector<profiler::ProfileNode> nodes;
334+
double startTime{};
335+
double endTime{};
336+
folly::Optional<std::vector<int>> samples;
337+
folly::Optional<std::vector<int>> timeDeltas;
338+
};
339+
293340
struct runtime::CallArgument : public Serializable {
294341
CallArgument() = default;
295342
explicit CallArgument(const folly::dynamic &obj);
@@ -569,6 +616,22 @@ struct heapProfiler::TakeHeapSnapshotRequest : public Request {
569616
folly::Optional<bool> captureNumericValue;
570617
};
571618

619+
struct profiler::StartRequest : public Request {
620+
StartRequest();
621+
explicit StartRequest(const folly::dynamic &obj);
622+
623+
folly::dynamic toDynamic() const override;
624+
void accept(RequestHandler &handler) const override;
625+
};
626+
627+
struct profiler::StopRequest : public Request {
628+
StopRequest();
629+
explicit StopRequest(const folly::dynamic &obj);
630+
631+
folly::dynamic toDynamic() const override;
632+
void accept(RequestHandler &handler) const override;
633+
};
634+
572635
struct runtime::CallFunctionOnRequest : public Request {
573636
CallFunctionOnRequest();
574637
explicit CallFunctionOnRequest(const folly::dynamic &obj);
@@ -707,6 +770,14 @@ struct heapProfiler::StopSamplingResponse : public Response {
707770
heapProfiler::SamplingHeapProfile profile{};
708771
};
709772

773+
struct profiler::StopResponse : public Response {
774+
StopResponse() = default;
775+
explicit StopResponse(const folly::dynamic &obj);
776+
folly::dynamic toDynamic() const override;
777+
778+
profiler::Profile profile{};
779+
};
780+
710781
struct runtime::CallFunctionOnResponse : public Response {
711782
CallFunctionOnResponse() = default;
712783
explicit CallFunctionOnResponse(const folly::dynamic &obj);

ReactCommon/hermes/inspector/chrome/tests/AsyncHermesRuntime.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,27 @@ std::string AsyncHermesRuntime::getLastThrownExceptionMessage() {
139139
return thrownExceptions_.back();
140140
}
141141

142+
void AsyncHermesRuntime::registerForProfilingInExecutor() {
143+
// Sampling profiler registration needs to happen in the thread where JS runs.
144+
folly::via(executor_.get(), [runtime = runtime_]() {
145+
runtime->registerForProfiling();
146+
});
147+
148+
// Wait until the executor is registered for profiling.
149+
wait();
150+
}
151+
152+
void AsyncHermesRuntime::unregisterForProfilingInExecutor() {
153+
// Sampling profiler deregistration needs to happen in the thread where JS
154+
// runs.
155+
folly::via(executor_.get(), [runtime = runtime_]() {
156+
runtime->unregisterForProfiling();
157+
});
158+
159+
// Wait until the executor is unregistered for profiling.
160+
wait();
161+
}
162+
142163
} // namespace chrome
143164
} // namespace inspector
144165
} // namespace hermes

0 commit comments

Comments
 (0)