From cff9590864c4be153a4eb49757b7cac8b3f23f66 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 1 Mar 2022 10:09:05 -0800 Subject: [PATCH] Implement Runtime.getHeapUsage for hermes chrome inspector (#33173) Summary: Reland of https://github.com/facebook/react-native/issues/32895 with fix for optional params object. Original description: I was looking at the hermes chrome devtools integration and noticed requests to `Runtime.getHeapUsage` which was not implemented. When implemented it will show a summary of memory usage of the javascript instance in devtools. image ## Changelog [General] [Added] - Implement Runtime.getHeapUsage for hermes chrome inspector Pull Request resolved: https://github.com/facebook/react-native/pull/33173 Test Plan: I was able to reproduce the issue that caused the initial PR to be reverted using the resume request in Flipper. Pausing JS execution then resuming it will cause it to crash before this change, and works fine after. Before image After image Reviewed By: jpporto Differential Revision: D34446672 Pulled By: ShikaSD fbshipit-source-id: 6e26b8d53cd88cddded36437c72a01822551b9d0 --- .../hermes/inspector/chrome/Connection.cpp | 18 ++++ .../hermes/inspector/chrome/MessageTypes.cpp | 86 ++++++++++++++++--- .../hermes/inspector/chrome/MessageTypes.h | 25 ++++++ .../inspector/chrome/tests/MessageTests.cpp | 5 +- .../hermes/inspector/tools/message_types.txt | 1 + .../inspector/tools/msggen/package.json | 2 +- .../tools/msggen/src/ImplementationWriter.js | 15 +++- .../inspector/tools/msggen/src/index.js | 19 +++- .../hermes/inspector/tools/msggen/yarn.lock | 8 +- ReactCommon/hermes/inspector/tools/run_msggen | 11 +-- 10 files changed, 166 insertions(+), 24 deletions(-) diff --git a/ReactCommon/hermes/inspector/chrome/Connection.cpp b/ReactCommon/hermes/inspector/chrome/Connection.cpp index 2fa87b5386452c..e554f654dcbf51 100644 --- a/ReactCommon/hermes/inspector/chrome/Connection.cpp +++ b/ReactCommon/hermes/inspector/chrome/Connection.cpp @@ -103,6 +103,7 @@ class Connection::Impl : public inspector::InspectorObserver, void handle(const m::heapProfiler::GetHeapObjectIdRequest &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; void handle(const m::runtime::GetPropertiesRequest &req) override; void handle(const m::runtime::RunIfWaitingForDebuggerRequest &req) override; @@ -1348,6 +1349,23 @@ Connection::Impl::makePropsFromValue( return result; } +void Connection::Impl::handle(const m::runtime::GetHeapUsageRequest &req) { + auto resp = std::make_shared(); + resp->id = req.id; + + inspector_ + ->executeIfEnabled( + "Runtime.getHeapUsage", + [this, req, resp](const debugger::ProgramState &state) { + auto heapInfo = getRuntime().instrumentation().getHeapInfo(false); + resp->usedSize = heapInfo["hermes_allocatedBytes"]; + resp->totalSize = heapInfo["hermes_heapSize"]; + }) + .via(executor_.get()) + .thenValue([this, resp](auto &&) { sendResponseToClient(*resp); }) + .thenError(sendErrorToClient(req.id)); +} + void Connection::Impl::handle(const m::runtime::GetPropertiesRequest &req) { auto resp = std::make_shared(); resp->id = req.id; diff --git a/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp b/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp index 170f90593b9281..b7cab9c550e8dc 100644 --- a/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp +++ b/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp @@ -62,6 +62,7 @@ std::unique_ptr Request::fromJsonThrowOnError(const std::string &str) { makeUnique}, {"Runtime.callFunctionOn", makeUnique}, {"Runtime.evaluate", makeUnique}, + {"Runtime.getHeapUsage", makeUnique}, {"Runtime.getProperties", makeUnique}, {"Runtime.runIfWaitingForDebugger", makeUnique}, @@ -503,12 +504,22 @@ debugger::ResumeRequest::ResumeRequest(const dynamic &obj) : Request("Debugger.resume") { assign(id, obj, "id"); assign(method, obj, "method"); + + auto it = obj.find("params"); + if (it != obj.items().end()) { + dynamic params = it->second; + assign(terminateOnResume, params, "terminateOnResume"); + } } dynamic debugger::ResumeRequest::toDynamic() const { + dynamic params = dynamic::object; + put(params, "terminateOnResume", terminateOnResume); + dynamic obj = dynamic::object; put(obj, "id", id); put(obj, "method", method); + put(obj, "params", std::move(params)); return obj; } @@ -817,8 +828,11 @@ heapProfiler::StartSamplingRequest::StartSamplingRequest(const dynamic &obj) assign(id, obj, "id"); assign(method, obj, "method"); - dynamic params = obj.at("params"); - assign(samplingInterval, params, "samplingInterval"); + auto it = obj.find("params"); + if (it != obj.items().end()) { + dynamic params = it->second; + assign(samplingInterval, params, "samplingInterval"); + } } dynamic heapProfiler::StartSamplingRequest::toDynamic() const { @@ -845,8 +859,11 @@ heapProfiler::StartTrackingHeapObjectsRequest::StartTrackingHeapObjectsRequest( assign(id, obj, "id"); assign(method, obj, "method"); - dynamic params = obj.at("params"); - assign(trackAllocations, params, "trackAllocations"); + auto it = obj.find("params"); + if (it != obj.items().end()) { + dynamic params = it->second; + assign(trackAllocations, params, "trackAllocations"); + } } dynamic heapProfiler::StartTrackingHeapObjectsRequest::toDynamic() const { @@ -894,15 +911,20 @@ heapProfiler::StopTrackingHeapObjectsRequest::StopTrackingHeapObjectsRequest( assign(id, obj, "id"); assign(method, obj, "method"); - dynamic params = obj.at("params"); - assign(reportProgress, params, "reportProgress"); - assign(treatGlobalObjectsAsRoots, params, "treatGlobalObjectsAsRoots"); + auto it = obj.find("params"); + if (it != obj.items().end()) { + dynamic params = it->second; + assign(reportProgress, params, "reportProgress"); + assign(treatGlobalObjectsAsRoots, params, "treatGlobalObjectsAsRoots"); + assign(captureNumericValue, params, "captureNumericValue"); + } } dynamic heapProfiler::StopTrackingHeapObjectsRequest::toDynamic() const { dynamic params = dynamic::object; put(params, "reportProgress", reportProgress); put(params, "treatGlobalObjectsAsRoots", treatGlobalObjectsAsRoots); + put(params, "captureNumericValue", captureNumericValue); dynamic obj = dynamic::object; put(obj, "id", id); @@ -925,15 +947,20 @@ heapProfiler::TakeHeapSnapshotRequest::TakeHeapSnapshotRequest( assign(id, obj, "id"); assign(method, obj, "method"); - dynamic params = obj.at("params"); - assign(reportProgress, params, "reportProgress"); - assign(treatGlobalObjectsAsRoots, params, "treatGlobalObjectsAsRoots"); + auto it = obj.find("params"); + if (it != obj.items().end()) { + dynamic params = it->second; + assign(reportProgress, params, "reportProgress"); + assign(treatGlobalObjectsAsRoots, params, "treatGlobalObjectsAsRoots"); + assign(captureNumericValue, params, "captureNumericValue"); + } } dynamic heapProfiler::TakeHeapSnapshotRequest::toDynamic() const { dynamic params = dynamic::object; put(params, "reportProgress", reportProgress); put(params, "treatGlobalObjectsAsRoots", treatGlobalObjectsAsRoots); + put(params, "captureNumericValue", captureNumericValue); dynamic obj = dynamic::object; put(obj, "id", id); @@ -1030,6 +1057,26 @@ void runtime::EvaluateRequest::accept(RequestHandler &handler) const { handler.handle(*this); } +runtime::GetHeapUsageRequest::GetHeapUsageRequest() + : Request("Runtime.getHeapUsage") {} + +runtime::GetHeapUsageRequest::GetHeapUsageRequest(const dynamic &obj) + : Request("Runtime.getHeapUsage") { + assign(id, obj, "id"); + assign(method, obj, "method"); +} + +dynamic runtime::GetHeapUsageRequest::toDynamic() const { + dynamic obj = dynamic::object; + put(obj, "id", id); + put(obj, "method", method); + return obj; +} + +void runtime::GetHeapUsageRequest::accept(RequestHandler &handler) const { + handler.handle(*this); +} + runtime::GetPropertiesRequest::GetPropertiesRequest() : Request("Runtime.getProperties") {} @@ -1284,6 +1331,25 @@ dynamic runtime::EvaluateResponse::toDynamic() const { return obj; } +runtime::GetHeapUsageResponse::GetHeapUsageResponse(const dynamic &obj) { + assign(id, obj, "id"); + + dynamic res = obj.at("result"); + assign(usedSize, res, "usedSize"); + assign(totalSize, res, "totalSize"); +} + +dynamic runtime::GetHeapUsageResponse::toDynamic() const { + dynamic res = dynamic::object; + put(res, "usedSize", usedSize); + put(res, "totalSize", totalSize); + + dynamic obj = dynamic::object; + put(obj, "id", id); + put(obj, "result", std::move(res)); + return obj; +} + runtime::GetPropertiesResponse::GetPropertiesResponse(const dynamic &obj) { assign(id, obj, "id"); diff --git a/ReactCommon/hermes/inspector/chrome/MessageTypes.h b/ReactCommon/hermes/inspector/chrome/MessageTypes.h index 78468d6fb11db6..10ff9e4ca32c9d 100644 --- a/ReactCommon/hermes/inspector/chrome/MessageTypes.h +++ b/ReactCommon/hermes/inspector/chrome/MessageTypes.h @@ -59,6 +59,8 @@ struct ExceptionDetails; struct ExecutionContextCreatedNotification; struct ExecutionContextDescription; using ExecutionContextId = int; +struct GetHeapUsageRequest; +struct GetHeapUsageResponse; struct GetPropertiesRequest; struct GetPropertiesResponse; struct InternalPropertyDescriptor; @@ -127,6 +129,7 @@ struct RequestHandler { virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &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; virtual void handle(const runtime::GetPropertiesRequest &req) = 0; virtual void handle(const runtime::RunIfWaitingForDebuggerRequest &req) = 0; }; @@ -162,6 +165,7 @@ struct NoopRequestHandler : public RequestHandler { void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {} void handle(const runtime::CallFunctionOnRequest &req) override {} void handle(const runtime::EvaluateRequest &req) override {} + void handle(const runtime::GetHeapUsageRequest &req) override {} void handle(const runtime::GetPropertiesRequest &req) override {} void handle(const runtime::RunIfWaitingForDebuggerRequest &req) override {} }; @@ -400,6 +404,8 @@ struct debugger::ResumeRequest : public Request { folly::dynamic toDynamic() const override; void accept(RequestHandler &handler) const override; + + folly::Optional terminateOnResume; }; struct debugger::SetBreakpointRequest : public Request { @@ -548,6 +554,7 @@ struct heapProfiler::StopTrackingHeapObjectsRequest : public Request { folly::Optional reportProgress; folly::Optional treatGlobalObjectsAsRoots; + folly::Optional captureNumericValue; }; struct heapProfiler::TakeHeapSnapshotRequest : public Request { @@ -559,6 +566,7 @@ struct heapProfiler::TakeHeapSnapshotRequest : public Request { folly::Optional reportProgress; folly::Optional treatGlobalObjectsAsRoots; + folly::Optional captureNumericValue; }; struct runtime::CallFunctionOnRequest : public Request { @@ -596,6 +604,14 @@ struct runtime::EvaluateRequest : public Request { folly::Optional awaitPromise; }; +struct runtime::GetHeapUsageRequest : public Request { + GetHeapUsageRequest(); + explicit GetHeapUsageRequest(const folly::dynamic &obj); + + folly::dynamic toDynamic() const override; + void accept(RequestHandler &handler) const override; +}; + struct runtime::GetPropertiesRequest : public Request { GetPropertiesRequest(); explicit GetPropertiesRequest(const folly::dynamic &obj); @@ -709,6 +725,15 @@ struct runtime::EvaluateResponse : public Response { folly::Optional exceptionDetails; }; +struct runtime::GetHeapUsageResponse : public Response { + GetHeapUsageResponse() = default; + explicit GetHeapUsageResponse(const folly::dynamic &obj); + folly::dynamic toDynamic() const override; + + double usedSize{}; + double totalSize{}; +}; + struct runtime::GetPropertiesResponse : public Response { GetPropertiesResponse() = default; explicit GetPropertiesResponse(const folly::dynamic &obj); diff --git a/ReactCommon/hermes/inspector/chrome/tests/MessageTests.cpp b/ReactCommon/hermes/inspector/chrome/tests/MessageTests.cpp index 2b433f33aea5fe..dae6513a04b902 100644 --- a/ReactCommon/hermes/inspector/chrome/tests/MessageTests.cpp +++ b/ReactCommon/hermes/inspector/chrome/tests/MessageTests.cpp @@ -692,7 +692,10 @@ TEST(MessageTests, testResumeRequest) { std::string message = R"( { "id": 10, - "method": "Debugger.resume" + "method": "Debugger.resume", + "params": { + "terminateOnResume": false + } } )"; diff --git a/ReactCommon/hermes/inspector/tools/message_types.txt b/ReactCommon/hermes/inspector/tools/message_types.txt index 8921a8292561cc..d2868e6ab3c331 100644 --- a/ReactCommon/hermes/inspector/tools/message_types.txt +++ b/ReactCommon/hermes/inspector/tools/message_types.txt @@ -32,5 +32,6 @@ Runtime.callFunctionOn Runtime.consoleAPICalled Runtime.evaluate Runtime.executionContextCreated +Runtime.getHeapUsage Runtime.getProperties Runtime.runIfWaitingForDebugger diff --git a/ReactCommon/hermes/inspector/tools/msggen/package.json b/ReactCommon/hermes/inspector/tools/msggen/package.json index 6e42007bf68bfb..b3dcba4544fbe1 100644 --- a/ReactCommon/hermes/inspector/tools/msggen/package.json +++ b/ReactCommon/hermes/inspector/tools/msggen/package.json @@ -12,7 +12,7 @@ "test": "jest" }, "dependencies": { - "devtools-protocol": "0.0.730699", + "devtools-protocol": "0.0.959523", "yargs": "^14.2.0" }, "devDependencies": { diff --git a/ReactCommon/hermes/inspector/tools/msggen/src/ImplementationWriter.js b/ReactCommon/hermes/inspector/tools/msggen/src/ImplementationWriter.js index 5e2b690771409b..e2ff75628271b9 100644 --- a/ReactCommon/hermes/inspector/tools/msggen/src/ImplementationWriter.js +++ b/ReactCommon/hermes/inspector/tools/msggen/src/ImplementationWriter.js @@ -266,13 +266,26 @@ export function emitRequestDef(stream: Writable, command: Command) { assign(method, obj, "method");\n\n`); if (props.length > 0) { - stream.write('dynamic params = obj.at("params");\n'); + const optionalParams = props.every(p => p.optional); + if (optionalParams) { + stream.write(` + auto it = obj.find("params"); + if (it != obj.items().end()) { + dynamic params = it->second; + `); + } else { + stream.write('dynamic params = obj.at("params");\n'); + } for (const prop of props) { const id = prop.getCppIdentifier(); const name = prop.name; stream.write(`assign(${id}, params, "${name}");\n`); } + + if (optionalParams) { + stream.write('}'); + } } stream.write('}\n\n'); diff --git a/ReactCommon/hermes/inspector/tools/msggen/src/index.js b/ReactCommon/hermes/inspector/tools/msggen/src/index.js index cf6f3d881ec6ca..eb9a4f3ba96501 100644 --- a/ReactCommon/hermes/inspector/tools/msggen/src/index.js +++ b/ReactCommon/hermes/inspector/tools/msggen/src/index.js @@ -41,6 +41,7 @@ const proto = mergeDomains(standard, custom); function parseDomains( domainObjs: Array, ignoreExperimental: boolean, + includeExperimental: Set, ): Descriptor { const desc = { types: [], @@ -59,7 +60,12 @@ function parseDomains( } for (const commandObj of obj.commands || []) { - const command = Command.create(domain, commandObj, ignoreExperimental); + const command = Command.create( + domain, + commandObj, + !includeExperimental.has(`${domain}.${commandObj.name}`) && + ignoreExperimental, + ); if (command) { desc.commands.push(command); } @@ -199,18 +205,27 @@ function main() { .boolean('e') .alias('e', 'ignore-experimental') .describe('e', 'ignore experimental commands, props, and types') + .alias('i', 'include-experimental') + .describe('i', 'experimental commands to include') .alias('r', 'roots') .describe('r', 'path to a file listing root types, events, and commands') .nargs('r', 1) .demandCommand(2, 2).argv; const ignoreExperimental = !!args.e; + const includeExperimental = new Set( + typeof args.i === 'string' ? args.i.split(',') : [], + ); const [headerPath, implPath] = args._; const headerStream = fs.createWriteStream(headerPath); const implStream = fs.createWriteStream(implPath); - const desc = parseDomains(proto.domains, ignoreExperimental); + const desc = parseDomains( + proto.domains, + ignoreExperimental, + includeExperimental, + ); const graph = buildGraph(desc); const roots = parseRoots(desc, String(args.roots)); diff --git a/ReactCommon/hermes/inspector/tools/msggen/yarn.lock b/ReactCommon/hermes/inspector/tools/msggen/yarn.lock index b6b1be6b96f535..40a02746282e98 100644 --- a/ReactCommon/hermes/inspector/tools/msggen/yarn.lock +++ b/ReactCommon/hermes/inspector/tools/msggen/yarn.lock @@ -2434,10 +2434,10 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -devtools-protocol@0.0.730699: - version "0.0.730699" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.730699.tgz#4d18f6a9b7fb7cf3f1ffe73bfe14aad66cf3b2ef" - integrity sha512-dprBpuPzVIIXXL6GevzhvWe2wg836h3d5hY+n6IzzHbKLsUh6QlVmcIy15za0J3MhDFbmEH60s6uYsrw/tgBbw== +devtools-protocol@0.0.959523: + version "0.0.959523" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.959523.tgz#a7ce62c6b88876081fe5bec866f70e467bc021ba" + integrity sha512-taOcAND/oJA5FhJD2I3RA+I8RPdrpPJWwvMBPzTq7Sugev1xTOG3lgtlSfkh5xkjTYw0Ti2CRQq016goFHMoPQ== diff-sequences@^26.6.2: version "26.6.2" diff --git a/ReactCommon/hermes/inspector/tools/run_msggen b/ReactCommon/hermes/inspector/tools/run_msggen index 053cb3673727e5..c349b70be6c9e7 100755 --- a/ReactCommon/hermes/inspector/tools/run_msggen +++ b/ReactCommon/hermes/inspector/tools/run_msggen @@ -2,24 +2,25 @@ set -e -DIR=$(dirname "${BASH_SOURCE[0]}") +DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd) cd "${DIR}/msggen" yarn install yarn build -FBSOURCE=$(hg root) -MSGTYPES_PATH="${FBSOURCE}/xplat/js/react-native-github/ReactCommon/hermes/inspector/tools/message_types.txt" -HEADER_PATH="${FBSOURCE}/xplat/js/react-native-github/ReactCommon/hermes/inspector/chrome/MessageTypes.h" -CPP_PATH="${FBSOURCE}/xplat/js/react-native-github/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp" +MSGTYPES_PATH="${DIR}/message_types.txt" +HEADER_PATH="${DIR}/../chrome/MessageTypes.h" +CPP_PATH="${DIR}/../chrome/MessageTypes.cpp" node bin/index.js \ --ignore-experimental \ + --include-experimental=Runtime.getHeapUsage \ --roots "$MSGTYPES_PATH" \ "$HEADER_PATH" "$CPP_PATH" clang-format -i --style=file "$HEADER_PATH" clang-format -i --style=file "$CPP_PATH" +FBSOURCE=$(hg root) "${FBSOURCE}/tools/signedsource" sign "$HEADER_PATH" "${FBSOURCE}/tools/signedsource" sign "$CPP_PATH"