mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add support for the sampling memory profiler to the Chrome inspector
Summary: In the Chrome inspector, add support for `HeapProfiler.startSampling` and `HeapProfiler.stopSampling`. These two protocols turn the sampling heap profiler on and off, and write the results on the socket. Then Chrome's visualizer pieces the information together to produce a flame graph of memory usage. Added a unit test to make sure sampling can be turned on and off, and there are some samples returned. Changelog: [Internal] Reviewed By: avp Differential Revision: D26835148 fbshipit-source-id: d1be3cee791e42da5d9e117c3b8259b6622b98f4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a9bae13d07
commit
bcc79bd4c7
@@ -93,6 +93,8 @@ class Connection::Impl : public inspector::InspectorObserver,
|
||||
const m::heapProfiler::StartTrackingHeapObjectsRequest &req) override;
|
||||
void handle(
|
||||
const m::heapProfiler::StopTrackingHeapObjectsRequest &req) override;
|
||||
void handle(const m::heapProfiler::StartSamplingRequest &req) override;
|
||||
void handle(const m::heapProfiler::StopSamplingRequest &req) override;
|
||||
void handle(const m::heapProfiler::CollectGarbageRequest &req) override;
|
||||
void handle(const m::runtime::EvaluateRequest &req) override;
|
||||
void handle(const m::runtime::GetPropertiesRequest &req) override;
|
||||
@@ -580,6 +582,45 @@ void Connection::Impl::handle(
|
||||
/* stopStackTraceCapture */ true);
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(
|
||||
const m::heapProfiler::StartSamplingRequest &req) {
|
||||
const auto id = req.id;
|
||||
// This is the same default sampling interval that Chrome uses.
|
||||
// https://chromedevtools.github.io/devtools-protocol/tot/HeapProfiler/#method-startSampling
|
||||
constexpr size_t kDefaultSamplingInterval = 1 << 15;
|
||||
const size_t samplingInterval =
|
||||
req.samplingInterval.value_or(kDefaultSamplingInterval);
|
||||
|
||||
inspector_
|
||||
->executeIfEnabled(
|
||||
"HeapProfiler.startSampling",
|
||||
[this, samplingInterval](const debugger::ProgramState &) {
|
||||
getRuntime().instrumentation().startHeapSampling(samplingInterval);
|
||||
})
|
||||
.via(executor_.get())
|
||||
.thenValue(
|
||||
[this, id](auto &&) { sendResponseToClient(m::makeOkResponse(id)); })
|
||||
.thenError<std::exception>(sendErrorToClient(req.id));
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(const m::heapProfiler::StopSamplingRequest &req) {
|
||||
inspector_
|
||||
->executeIfEnabled(
|
||||
"HeapProfiler.stopSampling",
|
||||
[this, id = req.id](const debugger::ProgramState &) {
|
||||
std::ostringstream stream;
|
||||
getRuntime().instrumentation().stopHeapSampling(stream);
|
||||
folly::dynamic json = folly::parseJson(stream.str());
|
||||
m::heapProfiler::StopSamplingResponse resp;
|
||||
resp.id = id;
|
||||
m::heapProfiler::SamplingHeapProfile profile{json};
|
||||
resp.profile = profile;
|
||||
sendResponseToClient(resp);
|
||||
})
|
||||
.via(executor_.get())
|
||||
.thenError<std::exception>(sendErrorToClient(req.id));
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(
|
||||
const m::heapProfiler::CollectGarbageRequest &req) {
|
||||
const auto id = req.id;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// @generated SignedSource<<e4c911229f0e8cac24dbc3ec8a933d5e>>
|
||||
// @generated SignedSource<<f195ef454dab0ca2be532d6cdb2ebd0a>>
|
||||
|
||||
#include "MessageTypes.h"
|
||||
|
||||
@@ -46,8 +46,12 @@ std::unique_ptr<Request> Request::fromJsonThrowOnError(const std::string &str) {
|
||||
{"Debugger.stepOver", makeUnique<debugger::StepOverRequest>},
|
||||
{"HeapProfiler.collectGarbage",
|
||||
makeUnique<heapProfiler::CollectGarbageRequest>},
|
||||
{"HeapProfiler.startSampling",
|
||||
makeUnique<heapProfiler::StartSamplingRequest>},
|
||||
{"HeapProfiler.startTrackingHeapObjects",
|
||||
makeUnique<heapProfiler::StartTrackingHeapObjectsRequest>},
|
||||
{"HeapProfiler.stopSampling",
|
||||
makeUnique<heapProfiler::StopSamplingRequest>},
|
||||
{"HeapProfiler.stopTrackingHeapObjects",
|
||||
makeUnique<heapProfiler::StopTrackingHeapObjectsRequest>},
|
||||
{"HeapProfiler.takeHeapSnapshot",
|
||||
@@ -219,6 +223,53 @@ dynamic debugger::CallFrame::toDynamic() const {
|
||||
return obj;
|
||||
}
|
||||
|
||||
heapProfiler::SamplingHeapProfileNode::SamplingHeapProfileNode(
|
||||
const dynamic &obj) {
|
||||
assign(callFrame, obj, "callFrame");
|
||||
assign(selfSize, obj, "selfSize");
|
||||
assign(id, obj, "id");
|
||||
assign(children, obj, "children");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::SamplingHeapProfileNode::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
|
||||
put(obj, "callFrame", callFrame);
|
||||
put(obj, "selfSize", selfSize);
|
||||
put(obj, "id", id);
|
||||
put(obj, "children", children);
|
||||
return obj;
|
||||
}
|
||||
|
||||
heapProfiler::SamplingHeapProfileSample::SamplingHeapProfileSample(
|
||||
const dynamic &obj) {
|
||||
assign(size, obj, "size");
|
||||
assign(nodeId, obj, "nodeId");
|
||||
assign(ordinal, obj, "ordinal");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::SamplingHeapProfileSample::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
|
||||
put(obj, "size", size);
|
||||
put(obj, "nodeId", nodeId);
|
||||
put(obj, "ordinal", ordinal);
|
||||
return obj;
|
||||
}
|
||||
|
||||
heapProfiler::SamplingHeapProfile::SamplingHeapProfile(const dynamic &obj) {
|
||||
assign(head, obj, "head");
|
||||
assign(samples, obj, "samples");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::SamplingHeapProfile::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
|
||||
put(obj, "head", head);
|
||||
put(obj, "samples", samples);
|
||||
return obj;
|
||||
}
|
||||
|
||||
runtime::ExecutionContextDescription::ExecutionContextDescription(
|
||||
const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
@@ -679,6 +730,33 @@ void heapProfiler::CollectGarbageRequest::accept(
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
heapProfiler::StartSamplingRequest::StartSamplingRequest()
|
||||
: Request("HeapProfiler.startSampling") {}
|
||||
|
||||
heapProfiler::StartSamplingRequest::StartSamplingRequest(const dynamic &obj)
|
||||
: Request("HeapProfiler.startSampling") {
|
||||
assign(id, obj, "id");
|
||||
assign(method, obj, "method");
|
||||
|
||||
dynamic params = obj.at("params");
|
||||
assign(samplingInterval, params, "samplingInterval");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::StartSamplingRequest::toDynamic() const {
|
||||
dynamic params = dynamic::object;
|
||||
put(params, "samplingInterval", samplingInterval);
|
||||
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "method", method);
|
||||
put(obj, "params", std::move(params));
|
||||
return obj;
|
||||
}
|
||||
|
||||
void heapProfiler::StartSamplingRequest::accept(RequestHandler &handler) const {
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
heapProfiler::StartTrackingHeapObjectsRequest::StartTrackingHeapObjectsRequest()
|
||||
: Request("HeapProfiler.startTrackingHeapObjects") {}
|
||||
|
||||
@@ -708,6 +786,26 @@ void heapProfiler::StartTrackingHeapObjectsRequest::accept(
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
heapProfiler::StopSamplingRequest::StopSamplingRequest()
|
||||
: Request("HeapProfiler.stopSampling") {}
|
||||
|
||||
heapProfiler::StopSamplingRequest::StopSamplingRequest(const dynamic &obj)
|
||||
: Request("HeapProfiler.stopSampling") {
|
||||
assign(id, obj, "id");
|
||||
assign(method, obj, "method");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::StopSamplingRequest::toDynamic() const {
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "method", method);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void heapProfiler::StopSamplingRequest::accept(RequestHandler &handler) const {
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
heapProfiler::StopTrackingHeapObjectsRequest::StopTrackingHeapObjectsRequest()
|
||||
: Request("HeapProfiler.stopTrackingHeapObjects") {}
|
||||
|
||||
@@ -973,6 +1071,23 @@ dynamic debugger::SetInstrumentationBreakpointResponse::toDynamic() const {
|
||||
return obj;
|
||||
}
|
||||
|
||||
heapProfiler::StopSamplingResponse::StopSamplingResponse(const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
|
||||
dynamic res = obj.at("result");
|
||||
assign(profile, res, "profile");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::StopSamplingResponse::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::EvaluateResponse::EvaluateResponse(const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// @generated SignedSource<<e3e5526b8e266b560b9dc9e42cc0d6c5>>
|
||||
// @generated SignedSource<<0961e921eb7c5201466836c8ce82de73>>
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -75,7 +75,13 @@ struct CollectGarbageRequest;
|
||||
struct HeapStatsUpdateNotification;
|
||||
struct LastSeenObjectIdNotification;
|
||||
struct ReportHeapSnapshotProgressNotification;
|
||||
struct SamplingHeapProfile;
|
||||
struct SamplingHeapProfileNode;
|
||||
struct SamplingHeapProfileSample;
|
||||
struct StartSamplingRequest;
|
||||
struct StartTrackingHeapObjectsRequest;
|
||||
struct StopSamplingRequest;
|
||||
struct StopSamplingResponse;
|
||||
struct StopTrackingHeapObjectsRequest;
|
||||
struct TakeHeapSnapshotRequest;
|
||||
} // namespace heapProfiler
|
||||
@@ -101,8 +107,10 @@ struct RequestHandler {
|
||||
virtual void handle(const debugger::StepOutRequest &req) = 0;
|
||||
virtual void handle(const debugger::StepOverRequest &req) = 0;
|
||||
virtual void handle(const heapProfiler::CollectGarbageRequest &req) = 0;
|
||||
virtual void handle(const heapProfiler::StartSamplingRequest &req) = 0;
|
||||
virtual void handle(
|
||||
const heapProfiler::StartTrackingHeapObjectsRequest &req) = 0;
|
||||
virtual void handle(const heapProfiler::StopSamplingRequest &req) = 0;
|
||||
virtual void handle(
|
||||
const heapProfiler::StopTrackingHeapObjectsRequest &req) = 0;
|
||||
virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0;
|
||||
@@ -130,8 +138,10 @@ struct NoopRequestHandler : public RequestHandler {
|
||||
void handle(const debugger::StepOutRequest &req) override {}
|
||||
void handle(const debugger::StepOverRequest &req) override {}
|
||||
void handle(const heapProfiler::CollectGarbageRequest &req) override {}
|
||||
void handle(const heapProfiler::StartSamplingRequest &req) override {}
|
||||
void handle(
|
||||
const heapProfiler::StartTrackingHeapObjectsRequest &req) override {}
|
||||
void handle(const heapProfiler::StopSamplingRequest &req) override {}
|
||||
void handle(
|
||||
const heapProfiler::StopTrackingHeapObjectsRequest &req) override {}
|
||||
void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {}
|
||||
@@ -230,6 +240,36 @@ struct debugger::CallFrame : public Serializable {
|
||||
folly::Optional<runtime::RemoteObject> returnValue;
|
||||
};
|
||||
|
||||
struct heapProfiler::SamplingHeapProfileNode : public Serializable {
|
||||
SamplingHeapProfileNode() = default;
|
||||
explicit SamplingHeapProfileNode(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
runtime::CallFrame callFrame{};
|
||||
double selfSize{};
|
||||
int id{};
|
||||
std::vector<heapProfiler::SamplingHeapProfileNode> children;
|
||||
};
|
||||
|
||||
struct heapProfiler::SamplingHeapProfileSample : public Serializable {
|
||||
SamplingHeapProfileSample() = default;
|
||||
explicit SamplingHeapProfileSample(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
double size{};
|
||||
int nodeId{};
|
||||
double ordinal{};
|
||||
};
|
||||
|
||||
struct heapProfiler::SamplingHeapProfile : public Serializable {
|
||||
SamplingHeapProfile() = default;
|
||||
explicit SamplingHeapProfile(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
heapProfiler::SamplingHeapProfileNode head{};
|
||||
std::vector<heapProfiler::SamplingHeapProfileSample> samples;
|
||||
};
|
||||
|
||||
struct runtime::ExecutionContextDescription : public Serializable {
|
||||
ExecutionContextDescription() = default;
|
||||
explicit ExecutionContextDescription(const folly::dynamic &obj);
|
||||
@@ -424,6 +464,16 @@ struct heapProfiler::CollectGarbageRequest : public Request {
|
||||
void accept(RequestHandler &handler) const override;
|
||||
};
|
||||
|
||||
struct heapProfiler::StartSamplingRequest : public Request {
|
||||
StartSamplingRequest();
|
||||
explicit StartSamplingRequest(const folly::dynamic &obj);
|
||||
|
||||
folly::dynamic toDynamic() const override;
|
||||
void accept(RequestHandler &handler) const override;
|
||||
|
||||
folly::Optional<double> samplingInterval;
|
||||
};
|
||||
|
||||
struct heapProfiler::StartTrackingHeapObjectsRequest : public Request {
|
||||
StartTrackingHeapObjectsRequest();
|
||||
explicit StartTrackingHeapObjectsRequest(const folly::dynamic &obj);
|
||||
@@ -434,6 +484,14 @@ struct heapProfiler::StartTrackingHeapObjectsRequest : public Request {
|
||||
folly::Optional<bool> trackAllocations;
|
||||
};
|
||||
|
||||
struct heapProfiler::StopSamplingRequest : public Request {
|
||||
StopSamplingRequest();
|
||||
explicit StopSamplingRequest(const folly::dynamic &obj);
|
||||
|
||||
folly::dynamic toDynamic() const override;
|
||||
void accept(RequestHandler &handler) const override;
|
||||
};
|
||||
|
||||
struct heapProfiler::StopTrackingHeapObjectsRequest : public Request {
|
||||
StopTrackingHeapObjectsRequest();
|
||||
explicit StopTrackingHeapObjectsRequest(const folly::dynamic &obj);
|
||||
@@ -544,6 +602,14 @@ struct debugger::SetInstrumentationBreakpointResponse : public Response {
|
||||
debugger::BreakpointId breakpointId{};
|
||||
};
|
||||
|
||||
struct heapProfiler::StopSamplingResponse : public Response {
|
||||
StopSamplingResponse() = default;
|
||||
explicit StopSamplingResponse(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
heapProfiler::SamplingHeapProfile profile{};
|
||||
};
|
||||
|
||||
struct runtime::EvaluateResponse : public Response {
|
||||
EvaluateResponse() = default;
|
||||
explicit EvaluateResponse(const folly::dynamic &obj);
|
||||
|
||||
@@ -199,6 +199,12 @@ ResponseType send(SyncConnection &conn, int id) {
|
||||
return expectResponse<ResponseType>(conn, id);
|
||||
}
|
||||
|
||||
template <typename RequestType, typename ResponseType = m::OkResponse>
|
||||
ResponseType send(SyncConnection &conn, RequestType req) {
|
||||
conn.send(req.toJson());
|
||||
return expectResponse<ResponseType>(conn, req.id);
|
||||
}
|
||||
|
||||
void sendRuntimeEvalRequest(
|
||||
SyncConnection &conn,
|
||||
int id,
|
||||
@@ -2449,6 +2455,63 @@ TEST(ConnectionTests, runIfWaitingForDebugger) {
|
||||
expectNotification<m::debugger::ResumedNotification>(conn);
|
||||
}
|
||||
|
||||
TEST(ConnectionTests, heapProfilerSampling) {
|
||||
TestContext context;
|
||||
AsyncHermesRuntime &asyncRuntime = context.runtime();
|
||||
SyncConnection &conn = context.conn();
|
||||
int msgId = 1;
|
||||
|
||||
send<m::debugger::EnableRequest>(conn, msgId++);
|
||||
expectExecutionContextCreated(conn);
|
||||
|
||||
asyncRuntime.executeScriptAsync(R"(
|
||||
debugger;
|
||||
function allocator() {
|
||||
// Do some allocation.
|
||||
return new Object;
|
||||
}
|
||||
(function main() {
|
||||
var a = [];
|
||||
for (var i = 0; i < 100; i++) {
|
||||
a[i] = allocator();
|
||||
}
|
||||
})();
|
||||
debugger;
|
||||
)");
|
||||
expectNotification<m::debugger::ScriptParsedNotification>(conn);
|
||||
|
||||
// We should get a pause before the first statement.
|
||||
expectNotification<m::debugger::PausedNotification>(conn);
|
||||
|
||||
{
|
||||
m::heapProfiler::StartSamplingRequest req;
|
||||
req.id = msgId++;
|
||||
// Sample every 256 bytes to ensure there are some samples. The default is
|
||||
// 32768, which is too high for a small example. Note that sampling is a
|
||||
// random process, so there's no guarantee there will be any samples in any
|
||||
// finite number of allocations. In practice the likelihood is so high that
|
||||
// there shouldn't be any issues.
|
||||
req.samplingInterval = 256;
|
||||
send(conn, req);
|
||||
}
|
||||
// Resume, run the allocations, and once it's paused again, stop them.
|
||||
send<m::debugger::ResumeRequest>(conn, msgId++);
|
||||
expectNotification<m::debugger::ResumedNotification>(conn);
|
||||
expectNotification<m::debugger::PausedNotification>(conn);
|
||||
// Send the stop sampling request, expect the value coming back to be JSON.
|
||||
auto resp = send<
|
||||
m::heapProfiler::StopSamplingRequest,
|
||||
m::heapProfiler::StopSamplingResponse>(conn, msgId++);
|
||||
// Make sure there were some samples.
|
||||
EXPECT_NE(resp.profile.samples.size(), 0);
|
||||
// Don't test the content of the JSON, that is tested via the
|
||||
// SamplingHeapProfilerTest.
|
||||
|
||||
// Resume and exit
|
||||
send<m::debugger::ResumeRequest>(conn, msgId++);
|
||||
expectNotification<m::debugger::ResumedNotification>(conn);
|
||||
}
|
||||
|
||||
} // namespace chrome
|
||||
} // namespace inspector
|
||||
} // namespace hermes
|
||||
|
||||
@@ -22,6 +22,8 @@ HeapProfiler.reportHeapSnapshotProgress
|
||||
HeapProfiler.takeHeapSnapshot
|
||||
HeapProfiler.startTrackingHeapObjects
|
||||
HeapProfiler.stopTrackingHeapObjects
|
||||
HeapProfiler.startSampling
|
||||
HeapProfiler.stopSampling
|
||||
HeapProfiler.heapStatsUpdate
|
||||
HeapProfiler.lastSeenObjectId
|
||||
Runtime.consoleAPICalled
|
||||
|
||||
Reference in New Issue
Block a user