Skip to content

Commit

Permalink
Add support for devtools' profiler
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jpporto authored and facebook-github-bot committed Apr 2, 2022
1 parent 184dfb8 commit fefa7b6
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 2 deletions.
61 changes: 61 additions & 0 deletions ReactCommon/hermes/inspector/chrome/Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -135,6 +137,11 @@ class Connection::Impl : public inspector::InspectorObserver,
void sendNotificationToClientViaExecutor(const m::Notification &note);
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_;

Expand Down Expand Up @@ -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
Expand Down
112 changes: 111 additions & 1 deletion ReactCommon/hermes/inspector/chrome/MessageTypes.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
// @generated SignedSource<<130ce3da2ad67004eb7bb91f19028a89>>
// @generated SignedSource<<f6f9a72f332587809b4e50ab054e0a74>>

#include "MessageTypes.h"

Expand Down Expand Up @@ -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>},
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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") {}

Expand Down Expand Up @@ -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");

Expand Down
73 changes: 72 additions & 1 deletion ReactCommon/hermes/inspector/chrome/MessageTypes.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
// @generated SignedSource<<41786947d74eb3f47d8168b82b5ccf85>>
// @generated SignedSource<<fcbcfeecbc72ca30c8ed005ad47839bb>>

#pragma once

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions ReactCommon/hermes/inspector/chrome/tests/AsyncHermesRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit fefa7b6

Please sign in to comment.