mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement HeapProfiler.getObjectByHeapObjectId
Summary: Implement the API for querying the properties of an object found in a heap snapshot. Now when you are debugging and take a heap snapshot, you can hover over an object and inspect it! Only works for subclasses of JSObject. Doesn't work for stuff like HiddenClass, PropertyAccessor, native objects like WeakValueMap, etc. Those internal objects display "Preview is not available" which matches what Chrome prints for its own internal stuff. Changelog: [Internal] Reviewed By: avp Differential Revision: D27834672 fbshipit-source-id: 607a8984b5a48b76c5ae57f9bd5bf53168f3ec3f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2b27323308
commit
7579ed3f6f
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
|
||||
#include <folly/Conv.h>
|
||||
#include <folly/Executor.h>
|
||||
@@ -96,6 +97,9 @@ class Connection::Impl : public inspector::InspectorObserver,
|
||||
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::heapProfiler::GetObjectByHeapObjectIdRequest &req) override;
|
||||
void handle(const m::heapProfiler::GetHeapObjectIdRequest &req) override;
|
||||
void handle(const m::runtime::EvaluateRequest &req) override;
|
||||
void handle(const m::runtime::GetPropertiesRequest &req) override;
|
||||
void handle(const m::runtime::RunIfWaitingForDebuggerRequest &req) override;
|
||||
@@ -637,6 +641,76 @@ void Connection::Impl::handle(
|
||||
.thenError<std::exception>(sendErrorToClient(req.id));
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(
|
||||
const m::heapProfiler::GetObjectByHeapObjectIdRequest &req) {
|
||||
uint64_t objID = atoi(req.objectId.c_str());
|
||||
folly::Optional<std::string> group = req.objectGroup;
|
||||
auto remoteObjPtr = std::make_shared<m::runtime::RemoteObject>();
|
||||
|
||||
inspector_
|
||||
->executeIfEnabled(
|
||||
"HeapProfiler.getObjectByHeapObjectId",
|
||||
[this, remoteObjPtr, objID, group](const debugger::ProgramState &) {
|
||||
jsi::Runtime *rt = &getRuntime();
|
||||
if (auto *hermesRT = dynamic_cast<HermesRuntime *>(rt)) {
|
||||
jsi::Value val = hermesRT->getObjectForID(objID);
|
||||
if (val.isNull()) {
|
||||
return;
|
||||
}
|
||||
*remoteObjPtr = m::runtime::makeRemoteObject(
|
||||
getRuntime(), val, objTable_, group.value_or(""));
|
||||
}
|
||||
})
|
||||
.via(executor_.get())
|
||||
.thenValue([this, id = req.id, remoteObjPtr](auto &&) {
|
||||
if (!remoteObjPtr->type.empty()) {
|
||||
m::heapProfiler::GetObjectByHeapObjectIdResponse resp;
|
||||
resp.id = id;
|
||||
resp.result = *remoteObjPtr;
|
||||
sendResponseToClient(resp);
|
||||
} else {
|
||||
sendResponseToClient(m::makeErrorResponse(
|
||||
id, m::ErrorCode::ServerError, "Object is not available"));
|
||||
}
|
||||
})
|
||||
.thenError<std::exception>(sendErrorToClient(req.id));
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(
|
||||
const m::heapProfiler::GetHeapObjectIdRequest &req) {
|
||||
// Use a shared_ptr because the stack frame will go away.
|
||||
std::shared_ptr<uint64_t> snapshotID = std::make_shared<uint64_t>(0);
|
||||
|
||||
inspector_
|
||||
->executeIfEnabled(
|
||||
"HeapProfiler.getHeapObjectId",
|
||||
[this, req, snapshotID](const debugger::ProgramState &) {
|
||||
if (const jsi::Value *valuePtr = objTable_.getValue(req.objectId)) {
|
||||
jsi::Runtime *rt = &getRuntime();
|
||||
if (auto *hermesRT = dynamic_cast<HermesRuntime *>(rt)) {
|
||||
*snapshotID = hermesRT->getUniqueID(*valuePtr);
|
||||
}
|
||||
}
|
||||
})
|
||||
.via(executor_.get())
|
||||
.thenValue([this, id = req.id, snapshotID](auto &&) {
|
||||
if (*snapshotID) {
|
||||
m::heapProfiler::GetHeapObjectIdResponse resp;
|
||||
resp.id = id;
|
||||
// std::to_string is not available on Android, use a std::ostream
|
||||
// instead.
|
||||
std::ostringstream stream;
|
||||
stream << *snapshotID;
|
||||
resp.heapSnapshotObjectId = stream.str();
|
||||
sendResponseToClient(resp);
|
||||
} else {
|
||||
sendResponseToClient(m::makeErrorResponse(
|
||||
id, m::ErrorCode::ServerError, "Object is not available"));
|
||||
}
|
||||
})
|
||||
.thenError<std::exception>(sendErrorToClient(req.id));
|
||||
}
|
||||
|
||||
void Connection::Impl::handle(const m::runtime::EvaluateRequest &req) {
|
||||
auto remoteObjPtr = std::make_shared<m::runtime::RemoteObject>();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// @generated SignedSource<<f195ef454dab0ca2be532d6cdb2ebd0a>>
|
||||
// @generated SignedSource<<522f29c54f207a4f7b5c33af07cf64d0>>
|
||||
|
||||
#include "MessageTypes.h"
|
||||
|
||||
@@ -46,6 +46,10 @@ std::unique_ptr<Request> Request::fromJsonThrowOnError(const std::string &str) {
|
||||
{"Debugger.stepOver", makeUnique<debugger::StepOverRequest>},
|
||||
{"HeapProfiler.collectGarbage",
|
||||
makeUnique<heapProfiler::CollectGarbageRequest>},
|
||||
{"HeapProfiler.getHeapObjectId",
|
||||
makeUnique<heapProfiler::GetHeapObjectIdRequest>},
|
||||
{"HeapProfiler.getObjectByHeapObjectId",
|
||||
makeUnique<heapProfiler::GetObjectByHeapObjectIdRequest>},
|
||||
{"HeapProfiler.startSampling",
|
||||
makeUnique<heapProfiler::StartSamplingRequest>},
|
||||
{"HeapProfiler.startTrackingHeapObjects",
|
||||
@@ -730,6 +734,65 @@ void heapProfiler::CollectGarbageRequest::accept(
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
heapProfiler::GetHeapObjectIdRequest::GetHeapObjectIdRequest()
|
||||
: Request("HeapProfiler.getHeapObjectId") {}
|
||||
|
||||
heapProfiler::GetHeapObjectIdRequest::GetHeapObjectIdRequest(const dynamic &obj)
|
||||
: Request("HeapProfiler.getHeapObjectId") {
|
||||
assign(id, obj, "id");
|
||||
assign(method, obj, "method");
|
||||
|
||||
dynamic params = obj.at("params");
|
||||
assign(objectId, params, "objectId");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::GetHeapObjectIdRequest::toDynamic() const {
|
||||
dynamic params = dynamic::object;
|
||||
put(params, "objectId", objectId);
|
||||
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "method", method);
|
||||
put(obj, "params", std::move(params));
|
||||
return obj;
|
||||
}
|
||||
|
||||
void heapProfiler::GetHeapObjectIdRequest::accept(
|
||||
RequestHandler &handler) const {
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
heapProfiler::GetObjectByHeapObjectIdRequest::GetObjectByHeapObjectIdRequest()
|
||||
: Request("HeapProfiler.getObjectByHeapObjectId") {}
|
||||
|
||||
heapProfiler::GetObjectByHeapObjectIdRequest::GetObjectByHeapObjectIdRequest(
|
||||
const dynamic &obj)
|
||||
: Request("HeapProfiler.getObjectByHeapObjectId") {
|
||||
assign(id, obj, "id");
|
||||
assign(method, obj, "method");
|
||||
|
||||
dynamic params = obj.at("params");
|
||||
assign(objectId, params, "objectId");
|
||||
assign(objectGroup, params, "objectGroup");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::GetObjectByHeapObjectIdRequest::toDynamic() const {
|
||||
dynamic params = dynamic::object;
|
||||
put(params, "objectId", objectId);
|
||||
put(params, "objectGroup", objectGroup);
|
||||
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "method", method);
|
||||
put(obj, "params", std::move(params));
|
||||
return obj;
|
||||
}
|
||||
|
||||
void heapProfiler::GetObjectByHeapObjectIdRequest::accept(
|
||||
RequestHandler &handler) const {
|
||||
handler.handle(*this);
|
||||
}
|
||||
|
||||
heapProfiler::StartSamplingRequest::StartSamplingRequest()
|
||||
: Request("HeapProfiler.startSampling") {}
|
||||
|
||||
@@ -1071,6 +1134,42 @@ dynamic debugger::SetInstrumentationBreakpointResponse::toDynamic() const {
|
||||
return obj;
|
||||
}
|
||||
|
||||
heapProfiler::GetHeapObjectIdResponse::GetHeapObjectIdResponse(
|
||||
const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
|
||||
dynamic res = obj.at("result");
|
||||
assign(heapSnapshotObjectId, res, "heapSnapshotObjectId");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::GetHeapObjectIdResponse::toDynamic() const {
|
||||
dynamic res = dynamic::object;
|
||||
put(res, "heapSnapshotObjectId", heapSnapshotObjectId);
|
||||
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "result", std::move(res));
|
||||
return obj;
|
||||
}
|
||||
|
||||
heapProfiler::GetObjectByHeapObjectIdResponse::GetObjectByHeapObjectIdResponse(
|
||||
const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
|
||||
dynamic res = obj.at("result");
|
||||
assign(result, res, "result");
|
||||
}
|
||||
|
||||
dynamic heapProfiler::GetObjectByHeapObjectIdResponse::toDynamic() const {
|
||||
dynamic res = dynamic::object;
|
||||
put(res, "result", result);
|
||||
|
||||
dynamic obj = dynamic::object;
|
||||
put(obj, "id", id);
|
||||
put(obj, "result", std::move(res));
|
||||
return obj;
|
||||
}
|
||||
|
||||
heapProfiler::StopSamplingResponse::StopSamplingResponse(const dynamic &obj) {
|
||||
assign(id, obj, "id");
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// @generated SignedSource<<0961e921eb7c5201466836c8ce82de73>>
|
||||
// @generated SignedSource<<a541d174394c8959b9fb6a7c575e7040>>
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -72,6 +72,11 @@ using UnserializableValue = std::string;
|
||||
namespace heapProfiler {
|
||||
struct AddHeapSnapshotChunkNotification;
|
||||
struct CollectGarbageRequest;
|
||||
struct GetHeapObjectIdRequest;
|
||||
struct GetHeapObjectIdResponse;
|
||||
struct GetObjectByHeapObjectIdRequest;
|
||||
struct GetObjectByHeapObjectIdResponse;
|
||||
using HeapSnapshotObjectId = std::string;
|
||||
struct HeapStatsUpdateNotification;
|
||||
struct LastSeenObjectIdNotification;
|
||||
struct ReportHeapSnapshotProgressNotification;
|
||||
@@ -107,6 +112,9 @@ 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::GetHeapObjectIdRequest &req) = 0;
|
||||
virtual void handle(
|
||||
const heapProfiler::GetObjectByHeapObjectIdRequest &req) = 0;
|
||||
virtual void handle(const heapProfiler::StartSamplingRequest &req) = 0;
|
||||
virtual void handle(
|
||||
const heapProfiler::StartTrackingHeapObjectsRequest &req) = 0;
|
||||
@@ -138,6 +146,9 @@ 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::GetHeapObjectIdRequest &req) override {}
|
||||
void handle(
|
||||
const heapProfiler::GetObjectByHeapObjectIdRequest &req) override {}
|
||||
void handle(const heapProfiler::StartSamplingRequest &req) override {}
|
||||
void handle(
|
||||
const heapProfiler::StartTrackingHeapObjectsRequest &req) override {}
|
||||
@@ -464,6 +475,27 @@ struct heapProfiler::CollectGarbageRequest : public Request {
|
||||
void accept(RequestHandler &handler) const override;
|
||||
};
|
||||
|
||||
struct heapProfiler::GetHeapObjectIdRequest : public Request {
|
||||
GetHeapObjectIdRequest();
|
||||
explicit GetHeapObjectIdRequest(const folly::dynamic &obj);
|
||||
|
||||
folly::dynamic toDynamic() const override;
|
||||
void accept(RequestHandler &handler) const override;
|
||||
|
||||
runtime::RemoteObjectId objectId{};
|
||||
};
|
||||
|
||||
struct heapProfiler::GetObjectByHeapObjectIdRequest : public Request {
|
||||
GetObjectByHeapObjectIdRequest();
|
||||
explicit GetObjectByHeapObjectIdRequest(const folly::dynamic &obj);
|
||||
|
||||
folly::dynamic toDynamic() const override;
|
||||
void accept(RequestHandler &handler) const override;
|
||||
|
||||
heapProfiler::HeapSnapshotObjectId objectId{};
|
||||
folly::Optional<std::string> objectGroup;
|
||||
};
|
||||
|
||||
struct heapProfiler::StartSamplingRequest : public Request {
|
||||
StartSamplingRequest();
|
||||
explicit StartSamplingRequest(const folly::dynamic &obj);
|
||||
@@ -602,6 +634,22 @@ struct debugger::SetInstrumentationBreakpointResponse : public Response {
|
||||
debugger::BreakpointId breakpointId{};
|
||||
};
|
||||
|
||||
struct heapProfiler::GetHeapObjectIdResponse : public Response {
|
||||
GetHeapObjectIdResponse() = default;
|
||||
explicit GetHeapObjectIdResponse(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
heapProfiler::HeapSnapshotObjectId heapSnapshotObjectId{};
|
||||
};
|
||||
|
||||
struct heapProfiler::GetObjectByHeapObjectIdResponse : public Response {
|
||||
GetObjectByHeapObjectIdResponse() = default;
|
||||
explicit GetObjectByHeapObjectIdResponse(const folly::dynamic &obj);
|
||||
folly::dynamic toDynamic() const override;
|
||||
|
||||
runtime::RemoteObject result{};
|
||||
};
|
||||
|
||||
struct heapProfiler::StopSamplingResponse : public Response {
|
||||
StopSamplingResponse() = default;
|
||||
explicit StopSamplingResponse(const folly::dynamic &obj);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <hermes/DebuggerAPI.h>
|
||||
#include <hermes/hermes.h>
|
||||
#include <hermes/inspector/chrome/MessageTypes.h>
|
||||
#include <jsi/instrumentation.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace hermes {
|
||||
@@ -2512,6 +2513,86 @@ TEST(ConnectionTests, heapProfilerSampling) {
|
||||
expectNotification<m::debugger::ResumedNotification>(conn);
|
||||
}
|
||||
|
||||
TEST(ConnectionTests, heapSnapshotRemoteObject) {
|
||||
TestContext context;
|
||||
AsyncHermesRuntime &asyncRuntime = context.runtime();
|
||||
std::shared_ptr<HermesRuntime> runtime = asyncRuntime.runtime();
|
||||
SyncConnection &conn = context.conn();
|
||||
int msgId = 1;
|
||||
|
||||
send<m::debugger::EnableRequest>(conn, msgId++);
|
||||
expectExecutionContextCreated(conn);
|
||||
|
||||
asyncRuntime.executeScriptAsync(R"(
|
||||
storeValue([1, 2, 3]);
|
||||
debugger;
|
||||
)");
|
||||
expectNotification<m::debugger::ScriptParsedNotification>(conn);
|
||||
|
||||
// We should get a pause before the first statement.
|
||||
expectNotification<m::debugger::PausedNotification>(conn);
|
||||
|
||||
{
|
||||
// Take a heap snapshot first to assign IDs.
|
||||
m::heapProfiler::TakeHeapSnapshotRequest req;
|
||||
req.id = msgId++;
|
||||
req.reportProgress = false;
|
||||
// We don't need the response because we can directly query for object IDs
|
||||
// from the runtime.
|
||||
send(conn, req);
|
||||
}
|
||||
|
||||
const uint64_t globalObjID = runtime->getUniqueID(runtime->global());
|
||||
jsi::Value storedValue = asyncRuntime.awaitStoredValue();
|
||||
const uint64_t storedObjID =
|
||||
runtime->getUniqueID(storedValue.asObject(*runtime));
|
||||
|
||||
auto testObject = [&msgId, &conn](
|
||||
uint64_t objID,
|
||||
const char *type,
|
||||
const char *className,
|
||||
const char *description,
|
||||
const char *subtype) {
|
||||
// Get the object by its snapshot ID.
|
||||
m::heapProfiler::GetObjectByHeapObjectIdRequest req;
|
||||
req.id = msgId++;
|
||||
req.objectId = std::to_string(objID);
|
||||
auto resp = send<
|
||||
m::heapProfiler::GetObjectByHeapObjectIdRequest,
|
||||
m::heapProfiler::GetObjectByHeapObjectIdResponse>(conn, req);
|
||||
EXPECT_EQ(resp.result.type, type);
|
||||
EXPECT_EQ(resp.result.className, className);
|
||||
EXPECT_EQ(resp.result.description, description);
|
||||
if (subtype) {
|
||||
EXPECT_EQ(resp.result.subtype, subtype);
|
||||
}
|
||||
|
||||
// Check that fetching the object by heap snapshot ID works.
|
||||
m::heapProfiler::GetHeapObjectIdRequest idReq;
|
||||
idReq.id = msgId++;
|
||||
idReq.objectId = resp.result.objectId.value();
|
||||
auto idResp = send<
|
||||
m::heapProfiler::GetHeapObjectIdRequest,
|
||||
m::heapProfiler::GetHeapObjectIdResponse>(conn, idReq);
|
||||
EXPECT_EQ(atoi(idResp.heapSnapshotObjectId.c_str()), objID);
|
||||
};
|
||||
|
||||
// Test once before a collection.
|
||||
testObject(globalObjID, "object", "Object", "Object", nullptr);
|
||||
testObject(storedObjID, "object", "Array", "Array(3)", "array");
|
||||
// Force a collection to move the heap.
|
||||
runtime->instrumentation().collectGarbage("test");
|
||||
// A collection should not disturb the unique ID lookup, and it should be the
|
||||
// same object as before. Note that it won't have the same remote ID, because
|
||||
// Hermes doesn't do uniquing.
|
||||
testObject(globalObjID, "object", "Object", "Object", nullptr);
|
||||
testObject(storedObjID, "object", "Array", "Array(3)", "array");
|
||||
|
||||
// Resume and exit.
|
||||
send<m::debugger::ResumeRequest>(conn, msgId++);
|
||||
expectNotification<m::debugger::ResumedNotification>(conn);
|
||||
}
|
||||
|
||||
} // namespace chrome
|
||||
} // namespace inspector
|
||||
} // namespace hermes
|
||||
|
||||
@@ -26,6 +26,8 @@ HeapProfiler.startSampling
|
||||
HeapProfiler.stopSampling
|
||||
HeapProfiler.heapStatsUpdate
|
||||
HeapProfiler.lastSeenObjectId
|
||||
HeapProfiler.getObjectByHeapObjectId
|
||||
HeapProfiler.getHeapObjectId
|
||||
Runtime.consoleAPICalled
|
||||
Runtime.evaluate
|
||||
Runtime.executionContextCreated
|
||||
|
||||
Reference in New Issue
Block a user