313 lines
8.5 KiB
JavaScript
313 lines
8.5 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
* @format
|
|
* @flow strict
|
|
*/
|
|
|
|
// flowlint unsafe-getters-setters:off
|
|
|
|
import type {HighResTimeStamp, PerformanceEntryType} from './PerformanceEntry';
|
|
import type {PerformanceEntryList} from './PerformanceObserver';
|
|
|
|
import warnOnce from '../Utilities/warnOnce';
|
|
import EventCounts from './EventCounts';
|
|
import MemoryInfo from './MemoryInfo';
|
|
import NativePerformance from './NativePerformance';
|
|
import NativePerformanceObserver from './NativePerformanceObserver';
|
|
import {PerformanceEntry} from './PerformanceEntry';
|
|
import {warnNoNativePerformanceObserver} from './PerformanceObserver';
|
|
import {
|
|
performanceEntryTypeToRaw,
|
|
rawToPerformanceEntry,
|
|
} from './RawPerformanceEntry';
|
|
import {RawPerformanceEntryTypeValues} from './RawPerformanceEntry';
|
|
import ReactNativeStartupTiming from './ReactNativeStartupTiming';
|
|
|
|
type DetailType = mixed;
|
|
|
|
export type PerformanceMarkOptions = {
|
|
detail?: DetailType,
|
|
startTime?: HighResTimeStamp,
|
|
};
|
|
|
|
declare var global: {
|
|
// This value is defined directly via JSI, if available.
|
|
+nativePerformanceNow?: ?() => number,
|
|
};
|
|
|
|
const getCurrentTimeStamp: () => HighResTimeStamp = global.nativePerformanceNow
|
|
? global.nativePerformanceNow
|
|
: () => Date.now();
|
|
|
|
export class PerformanceMark extends PerformanceEntry {
|
|
detail: DetailType;
|
|
|
|
constructor(markName: string, markOptions?: PerformanceMarkOptions) {
|
|
super({
|
|
name: markName,
|
|
entryType: 'mark',
|
|
startTime: markOptions?.startTime ?? getCurrentTimeStamp(),
|
|
duration: 0,
|
|
});
|
|
|
|
if (markOptions) {
|
|
this.detail = markOptions.detail;
|
|
}
|
|
}
|
|
}
|
|
|
|
export type TimeStampOrName = HighResTimeStamp | string;
|
|
|
|
export type PerformanceMeasureOptions = {
|
|
detail?: DetailType,
|
|
start?: TimeStampOrName,
|
|
end?: TimeStampOrName,
|
|
duration?: HighResTimeStamp,
|
|
};
|
|
|
|
export class PerformanceMeasure extends PerformanceEntry {
|
|
detail: DetailType;
|
|
|
|
constructor(measureName: string, measureOptions?: PerformanceMeasureOptions) {
|
|
super({
|
|
name: measureName,
|
|
entryType: 'measure',
|
|
startTime: 0,
|
|
duration: measureOptions?.duration ?? 0,
|
|
});
|
|
|
|
if (measureOptions) {
|
|
this.detail = measureOptions.detail;
|
|
}
|
|
}
|
|
}
|
|
|
|
function warnNoNativePerformance() {
|
|
warnOnce(
|
|
'missing-native-performance',
|
|
'Missing native implementation of Performance',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Partial implementation of the Performance interface for RN,
|
|
* corresponding to the standard in
|
|
* https://www.w3.org/TR/user-timing/#extensions-performance-interface
|
|
*/
|
|
export default class Performance {
|
|
eventCounts: EventCounts = new EventCounts();
|
|
|
|
// 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: totalJSHeapSize,
|
|
hermes_allocatedBytes: usedJSHeapSize,
|
|
} = memoryInfo;
|
|
|
|
return new MemoryInfo({
|
|
jsHeapSizeLimit: null, // We don't know the heap size limit from Hermes.
|
|
totalJSHeapSize,
|
|
usedJSHeapSize,
|
|
});
|
|
} else {
|
|
// JSC and V8 has no native implementations for memory information in JSI::Instrumentation
|
|
return new MemoryInfo();
|
|
}
|
|
}
|
|
|
|
return new MemoryInfo();
|
|
}
|
|
|
|
// Startup metrics is not used in web, but only in React Native.
|
|
get reactNativeStartupTiming(): ReactNativeStartupTiming {
|
|
if (NativePerformance?.getReactNativeStartupTiming) {
|
|
return new ReactNativeStartupTiming(
|
|
NativePerformance.getReactNativeStartupTiming(),
|
|
);
|
|
}
|
|
return new ReactNativeStartupTiming();
|
|
}
|
|
|
|
mark(
|
|
markName: string,
|
|
markOptions?: PerformanceMarkOptions,
|
|
): PerformanceMark {
|
|
const mark = new PerformanceMark(markName, markOptions);
|
|
|
|
if (NativePerformance?.mark) {
|
|
NativePerformance.mark(markName, mark.startTime, mark.duration);
|
|
} else {
|
|
warnNoNativePerformance();
|
|
}
|
|
|
|
return mark;
|
|
}
|
|
|
|
clearMarks(markName?: string): void {
|
|
if (!NativePerformanceObserver?.clearEntries) {
|
|
warnNoNativePerformanceObserver();
|
|
return;
|
|
}
|
|
|
|
NativePerformanceObserver?.clearEntries(
|
|
RawPerformanceEntryTypeValues.MARK,
|
|
markName,
|
|
);
|
|
}
|
|
|
|
measure(
|
|
measureName: string,
|
|
startMarkOrOptions?: string | PerformanceMeasureOptions,
|
|
endMark?: string,
|
|
): PerformanceMeasure {
|
|
let options;
|
|
let startMarkName,
|
|
endMarkName = endMark,
|
|
duration,
|
|
startTime = 0,
|
|
endTime = 0;
|
|
|
|
if (typeof startMarkOrOptions === 'string') {
|
|
startMarkName = startMarkOrOptions;
|
|
} else if (startMarkOrOptions !== undefined) {
|
|
options = startMarkOrOptions;
|
|
if (endMark !== undefined) {
|
|
throw new TypeError(
|
|
"Performance.measure: Can't have both options and endMark",
|
|
);
|
|
}
|
|
if (options.start === undefined && options.end === undefined) {
|
|
throw new TypeError(
|
|
'Performance.measure: Must have at least one of start/end specified in options',
|
|
);
|
|
}
|
|
if (
|
|
options.start !== undefined &&
|
|
options.end !== undefined &&
|
|
options.duration !== undefined
|
|
) {
|
|
throw new TypeError(
|
|
"Performance.measure: Can't have both start/end and duration explicitly in options",
|
|
);
|
|
}
|
|
|
|
if (typeof options.start === 'number') {
|
|
startTime = options.start;
|
|
} else {
|
|
startMarkName = options.start;
|
|
}
|
|
|
|
if (typeof options.end === 'number') {
|
|
endTime = options.end;
|
|
} else {
|
|
endMarkName = options.end;
|
|
}
|
|
|
|
duration = options.duration ?? duration;
|
|
}
|
|
|
|
const measure = new PerformanceMeasure(measureName, options);
|
|
|
|
if (NativePerformance?.measure) {
|
|
NativePerformance.measure(
|
|
measureName,
|
|
startTime,
|
|
endTime,
|
|
duration,
|
|
startMarkName,
|
|
endMarkName,
|
|
);
|
|
} else {
|
|
warnNoNativePerformance();
|
|
}
|
|
|
|
return measure;
|
|
}
|
|
|
|
clearMeasures(measureName?: string): void {
|
|
if (!NativePerformanceObserver?.clearEntries) {
|
|
warnNoNativePerformanceObserver();
|
|
return;
|
|
}
|
|
|
|
NativePerformanceObserver?.clearEntries(
|
|
RawPerformanceEntryTypeValues.MEASURE,
|
|
measureName,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns a double, measured in milliseconds.
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
|
|
*/
|
|
now(): HighResTimeStamp {
|
|
return getCurrentTimeStamp();
|
|
}
|
|
|
|
/**
|
|
* An extension that allows to get back to JS all currently logged marks/measures
|
|
* (in our case, be it from JS or native), see
|
|
* https://www.w3.org/TR/performance-timeline/#extensions-to-the-performance-interface
|
|
*/
|
|
getEntries(): PerformanceEntryList {
|
|
if (!NativePerformanceObserver?.clearEntries) {
|
|
warnNoNativePerformanceObserver();
|
|
return [];
|
|
}
|
|
return NativePerformanceObserver.getEntries().map(rawToPerformanceEntry);
|
|
}
|
|
|
|
getEntriesByType(entryType: PerformanceEntryType): PerformanceEntryList {
|
|
if (entryType !== 'mark' && entryType !== 'measure') {
|
|
console.log(
|
|
`Performance.getEntriesByType: Only valid for 'mark' and 'measure' entry types, got ${entryType}`,
|
|
);
|
|
return [];
|
|
}
|
|
|
|
if (!NativePerformanceObserver?.clearEntries) {
|
|
warnNoNativePerformanceObserver();
|
|
return [];
|
|
}
|
|
return NativePerformanceObserver.getEntries(
|
|
performanceEntryTypeToRaw(entryType),
|
|
).map(rawToPerformanceEntry);
|
|
}
|
|
|
|
getEntriesByName(
|
|
entryName: string,
|
|
entryType?: PerformanceEntryType,
|
|
): PerformanceEntryList {
|
|
if (
|
|
entryType !== undefined &&
|
|
entryType !== 'mark' &&
|
|
entryType !== 'measure'
|
|
) {
|
|
console.log(
|
|
`Performance.getEntriesByName: Only valid for 'mark' and 'measure' entry types, got ${entryType}`,
|
|
);
|
|
return [];
|
|
}
|
|
|
|
if (!NativePerformanceObserver?.clearEntries) {
|
|
warnNoNativePerformanceObserver();
|
|
return [];
|
|
}
|
|
return NativePerformanceObserver.getEntries(
|
|
entryType != null ? performanceEntryTypeToRaw(entryType) : undefined,
|
|
entryName,
|
|
).map(rawToPerformanceEntry);
|
|
}
|
|
}
|