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:
Riley Dulin
2021-03-18 10:40:42 -07:00
committed by Facebook GitHub Bot
parent a9bae13d07
commit bcc79bd4c7
5 changed files with 289 additions and 2 deletions
@@ -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