Skip to content

Commit

Permalink
Add performance.memory API
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Xin Chen authored and facebook-github-bot committed Feb 16, 2023
1 parent ee4714e commit 70fb2dc
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 0 deletions.
45 changes: 45 additions & 0 deletions Libraries/WebPerformance/MemoryInfo.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
11 changes: 11 additions & 0 deletions Libraries/WebPerformance/NativePerformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "NativePerformance.h"
#include <glog/logging.h>
#include <jsi/instrumentation.h>
#include "PerformanceEntryReporter.h"

namespace facebook::react {
Expand Down Expand Up @@ -46,4 +47,14 @@ void NativePerformance::clearMeasures(
PerformanceEntryReporter::getInstance().clearMeasures(measureName);
}

std::unordered_map<std::string, double> NativePerformance::getSimpleMemoryInfo(
jsi::Runtime &rt) {
auto heapInfo = rt.instrumentation().getHeapInfo(false);
std::unordered_map<std::string, double> heapInfoToJs;
for (auto &entry : heapInfo) {
heapInfoToJs[entry.first] = static_cast<double>(entry.second);
}
return heapInfoToJs;
}

} // namespace facebook::react
13 changes: 13 additions & 0 deletions Libraries/WebPerformance/NativePerformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance>,
std::optional<std::string> endMark);
void clearMeasures(jsi::Runtime &rt, std::optional<std::string> 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<std::string, double> getSimpleMemoryInfo(jsi::Runtime &rt);

private:
};

Expand Down
3 changes: 3 additions & 0 deletions Libraries/WebPerformance/NativePerformance.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +27,7 @@ export interface Spec extends TurboModule {
endMark?: string,
) => void;
+clearMeasures: (measureName?: string) => void;
+getSimpleMemoryInfo: () => NativeMemoryInfo;
}

export default (TurboModuleRegistry.get<Spec>('NativePerformanceCxx'): ?Spec);
30 changes: 30 additions & 0 deletions Libraries/WebPerformance/Performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 70fb2dc

Please sign in to comment.