diff --git a/Libraries/WebPerformance/MemoryInfo.js b/Libraries/WebPerformance/MemoryInfo.js new file mode 100644 index 00000000000000..fb469514e0478c --- /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 51db30395df4e8..94b0f4c5413a07 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 7078f50a943143..06a18da5f9534e 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 85435b4799750c..f25954d9c235ad 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 0c8ffb700d8a80..81ded123bc2d8e 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,