From 70fb2dce4557da1195289a24638b1e4d2c2edbf7 Mon Sep 17 00:00:00 2001 From: Xin Chen Date: Wed, 15 Feb 2023 20:52:48 -0800 Subject: [PATCH] Add performance.memory API Summary: This diff adds new performance API `memory`, which is a read-only property that gets the current JS heap size from native side. Note that the JSI API returns an unordered map with unknown list of memory information, which is different from the [web spec](https://fburl.com/p0vpbt33). We may enforce specific memory info type on the JSI API so that it can be properly translate to the web spec. - Update the JS spec - Update Native implementation and return memory information with JSI API `jsi::instrumentation()::getHeapInfo()` - Add native performance module to catalyst package Changelog: [General][Added] - Add performance memory API with native memory Info Reviewed By: rubennorte Differential Revision: D43137071 fbshipit-source-id: 319f1a6ba78fce61e665b00849ecf2579094af83 --- Libraries/WebPerformance/MemoryInfo.js | 45 +++++++++++++++++++ .../WebPerformance/NativePerformance.cpp | 11 +++++ Libraries/WebPerformance/NativePerformance.h | 13 ++++++ Libraries/WebPerformance/NativePerformance.js | 3 ++ Libraries/WebPerformance/Performance.js | 30 +++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 Libraries/WebPerformance/MemoryInfo.js diff --git a/Libraries/WebPerformance/MemoryInfo.js b/Libraries/WebPerformance/MemoryInfo.js new file mode 100644 index 00000000000..fb469514e04 --- /dev/null +++ b/Libraries/WebPerformance/MemoryInfo.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + * @oncall react_native + */ + +// flowlint unsafe-getters-setters:off + +export type MemoryInfoLike = { + jsHeapSizeLimit: ?number, + totalJSHeapSize: ?number, + usedJSHeapSize: ?number, +}; + +// Read-only object with JS memory information. This is returned by the performance.memory API. +export default class MemoryInfo { + _jsHeapSizeLimit: ?number; + _totalJSHeapSize: ?number; + _usedJSHeapSize: ?number; + + constructor(memoryInfo: ?MemoryInfoLike) { + if (memoryInfo != null) { + this._jsHeapSizeLimit = memoryInfo.jsHeapSizeLimit; + this._totalJSHeapSize = memoryInfo.totalJSHeapSize; + this._usedJSHeapSize = memoryInfo.usedJSHeapSize; + } + } + + get jsHeapSizeLimit(): ?number { + return this._jsHeapSizeLimit; + } + + get totalJSHeapSize(): ?number { + return this._totalJSHeapSize; + } + + get usedJSHeapSize(): ?number { + return this._usedJSHeapSize; + } +} diff --git a/Libraries/WebPerformance/NativePerformance.cpp b/Libraries/WebPerformance/NativePerformance.cpp index 51db30395df..94b0f4c5413 100644 --- a/Libraries/WebPerformance/NativePerformance.cpp +++ b/Libraries/WebPerformance/NativePerformance.cpp @@ -7,6 +7,7 @@ #include "NativePerformance.h" #include +#include #include "PerformanceEntryReporter.h" namespace facebook::react { @@ -46,4 +47,14 @@ void NativePerformance::clearMeasures( PerformanceEntryReporter::getInstance().clearMeasures(measureName); } +std::unordered_map NativePerformance::getSimpleMemoryInfo( + jsi::Runtime &rt) { + auto heapInfo = rt.instrumentation().getHeapInfo(false); + std::unordered_map heapInfoToJs; + for (auto &entry : heapInfo) { + heapInfoToJs[entry.first] = static_cast(entry.second); + } + return heapInfoToJs; +} + } // namespace facebook::react diff --git a/Libraries/WebPerformance/NativePerformance.h b/Libraries/WebPerformance/NativePerformance.h index 7078f50a943..06a18da5f95 100644 --- a/Libraries/WebPerformance/NativePerformance.h +++ b/Libraries/WebPerformance/NativePerformance.h @@ -39,6 +39,19 @@ class NativePerformance : public NativePerformanceCxxSpec, std::optional endMark); void clearMeasures(jsi::Runtime &rt, std::optional measureName); + // To align with web API, we will make sure to return three properties + // (jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize) + anything needed from + // the VM side. + // `jsHeapSizeLimit`: The maximum size of the heap, in bytes, that + // is available to the context. + // `totalJSHeapSize`: The total allocated heap size, in bytes. + // `usedJSHeapSize`: The currently active segment of JS heap, in + // bytes. + // + // Note that we use int64_t here and it's ok to lose precision in JS doubles + // for heap size information, as double's 2^53 sig bytes is large enough. + std::unordered_map getSimpleMemoryInfo(jsi::Runtime &rt); + private: }; diff --git a/Libraries/WebPerformance/NativePerformance.js b/Libraries/WebPerformance/NativePerformance.js index 85435b47997..f25954d9c23 100644 --- a/Libraries/WebPerformance/NativePerformance.js +++ b/Libraries/WebPerformance/NativePerformance.js @@ -12,6 +12,8 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; +export type NativeMemoryInfo = {[key: string]: number}; + export interface Spec extends TurboModule { +mark: (name: string, startTime: number, duration: number) => void; +clearMarks: (markName?: string) => void; @@ -25,6 +27,7 @@ export interface Spec extends TurboModule { endMark?: string, ) => void; +clearMeasures: (measureName?: string) => void; + +getSimpleMemoryInfo: () => NativeMemoryInfo; } export default (TurboModuleRegistry.get('NativePerformanceCxx'): ?Spec); diff --git a/Libraries/WebPerformance/Performance.js b/Libraries/WebPerformance/Performance.js index 0c8ffb700d8..81ded123bc2 100644 --- a/Libraries/WebPerformance/Performance.js +++ b/Libraries/WebPerformance/Performance.js @@ -8,9 +8,12 @@ * @flow strict */ +// flowlint unsafe-getters-setters:off + import type {HighResTimeStamp} from './PerformanceEntry'; import warnOnce from '../Utilities/warnOnce'; +import MemoryInfo from './MemoryInfo'; import NativePerformance from './NativePerformance'; import {PerformanceEntry} from './PerformanceEntry'; @@ -86,6 +89,33 @@ function warnNoNativePerformance() { * https://www.w3.org/TR/user-timing/#extensions-performance-interface */ export default class Performance { + // Get the current JS memory information. + get memory(): MemoryInfo { + if (NativePerformance?.getSimpleMemoryInfo) { + // JSI API implementations may have different variants of names for the JS + // heap information we need here. We will parse the result based on our + // guess of the implementation for now. + const memoryInfo = NativePerformance.getSimpleMemoryInfo(); + if (memoryInfo.hasOwnProperty('hermes_heapSize')) { + // We got memory information from Hermes + const {hermes_heapSize, hermes_allocatedBytes} = memoryInfo; + const totalJSHeapSize = Number(hermes_heapSize); + const usedJSHeapSize = Number(hermes_allocatedBytes); + + return new MemoryInfo({ + jsHeapSizeLimit: null, // We don't know the heap size limit from Hermes. + totalJSHeapSize: isNaN(totalJSHeapSize) ? null : totalJSHeapSize, + usedJSHeapSize: isNaN(usedJSHeapSize) ? null : usedJSHeapSize, + }); + } else { + // JSC and V8 has no native implementations for memory information in JSI::Instrumentation + return new MemoryInfo(); + } + } + + return new MemoryInfo(); + } + mark( markName: string, markOptions?: PerformanceMarkOptions,