mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Ability to always log certain performance entry types (#36820)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36820 ## Changelog: [Internal] - A follow-up to the D44584166 (which was abandoned, but this change makes sense nevertheless). This allows to selectively enable logging of certain event types regardless of whether they are observed or not. For now it's marks and measures, but potentially it may be also e.g. navigation/resource entries. Also expands unit tests for the JS side of the `Performance` API correspondingly. Note that "always logged" and "observed" have different semantics. An "always logged" entry won't be sent back from native to JS, unless either: * explicitly requested via `Performance.getEntries*` * actually observed via `PerformanceObserver` Reviewed By: rubennorte Differential Revision: D44712550 fbshipit-source-id: 7fc891b09bd00fa9b510d1dc059cf908d5caea07
This commit is contained in:
committed by
Facebook GitHub Bot
parent
caa3fd83a6
commit
3506c8d740
@@ -45,6 +45,16 @@ void NativePerformanceObserver::stopReporting(
|
||||
static_cast<PerformanceEntryType>(entryType));
|
||||
}
|
||||
|
||||
void NativePerformanceObserver::setIsBuffered(
|
||||
jsi::Runtime &rt,
|
||||
std::vector<int32_t> entryTypes,
|
||||
bool isBuffered) {
|
||||
for (const int32_t entryType : entryTypes) {
|
||||
PerformanceEntryReporter::getInstance().setAlwaysLogged(
|
||||
static_cast<PerformanceEntryType>(entryType), isBuffered);
|
||||
}
|
||||
}
|
||||
|
||||
GetPendingEntriesResult NativePerformanceObserver::popPendingEntries(
|
||||
jsi::Runtime &rt) {
|
||||
return PerformanceEntryReporter::getInstance().popPendingEntries();
|
||||
|
||||
@@ -63,6 +63,11 @@ class NativePerformanceObserver
|
||||
|
||||
void stopReporting(jsi::Runtime &rt, int32_t entryType);
|
||||
|
||||
void setIsBuffered(
|
||||
jsi::Runtime &rt,
|
||||
std::vector<int32_t> entryTypes,
|
||||
bool isBuffered);
|
||||
|
||||
GetPendingEntriesResult popPendingEntries(jsi::Runtime &rt);
|
||||
|
||||
void setOnPerformanceEntryCallback(
|
||||
|
||||
@@ -33,6 +33,10 @@ export type GetPendingEntriesResult = {|
|
||||
export interface Spec extends TurboModule {
|
||||
+startReporting: (entryType: RawPerformanceEntryType) => void;
|
||||
+stopReporting: (entryType: RawPerformanceEntryType) => void;
|
||||
+setIsBuffered: (
|
||||
entryTypes: $ReadOnlyArray<RawPerformanceEntryType>,
|
||||
isBuffered: boolean,
|
||||
) => void;
|
||||
+popPendingEntries: () => GetPendingEntriesResult;
|
||||
+setOnPerformanceEntryCallback: (callback?: () => void) => void;
|
||||
+logRawEntry: (entry: RawPerformanceEntry) => void;
|
||||
|
||||
+23
-11
@@ -18,7 +18,7 @@ import EventCounts from './EventCounts';
|
||||
import MemoryInfo from './MemoryInfo';
|
||||
import NativePerformance from './NativePerformance';
|
||||
import NativePerformanceObserver from './NativePerformanceObserver';
|
||||
import {PerformanceEntry} from './PerformanceEntry';
|
||||
import {ALWAYS_LOGGED_ENTRY_TYPES, PerformanceEntry} from './PerformanceEntry';
|
||||
import {warnNoNativePerformanceObserver} from './PerformanceObserver';
|
||||
import {
|
||||
performanceEntryTypeToRaw,
|
||||
@@ -43,6 +43,15 @@ const getCurrentTimeStamp: () => HighResTimeStamp = global.nativePerformanceNow
|
||||
? global.nativePerformanceNow
|
||||
: () => Date.now();
|
||||
|
||||
// We want some of the performance entry types to be always logged,
|
||||
// even if they are not currently observed - this is either to be able to
|
||||
// retrieve them at any time via Performance.getEntries* or to refer by other entries
|
||||
// (such as when measures may refer to marks, even if the latter are not observed)
|
||||
NativePerformanceObserver?.setIsBuffered(
|
||||
ALWAYS_LOGGED_ENTRY_TYPES.map(performanceEntryTypeToRaw),
|
||||
true,
|
||||
);
|
||||
|
||||
export class PerformanceMark extends PerformanceEntry {
|
||||
detail: DetailType;
|
||||
|
||||
@@ -261,7 +270,7 @@ export default class Performance {
|
||||
* https://www.w3.org/TR/performance-timeline/#extensions-to-the-performance-interface
|
||||
*/
|
||||
getEntries(): PerformanceEntryList {
|
||||
if (!NativePerformanceObserver?.clearEntries) {
|
||||
if (!NativePerformanceObserver?.getEntries) {
|
||||
warnNoNativePerformanceObserver();
|
||||
return [];
|
||||
}
|
||||
@@ -269,14 +278,16 @@ export default class Performance {
|
||||
}
|
||||
|
||||
getEntriesByType(entryType: PerformanceEntryType): PerformanceEntryList {
|
||||
if (entryType !== 'mark' && entryType !== 'measure') {
|
||||
console.log(
|
||||
`Performance.getEntriesByType: Only valid for 'mark' and 'measure' entry types, got ${entryType}`,
|
||||
if (!ALWAYS_LOGGED_ENTRY_TYPES.includes(entryType)) {
|
||||
console.warn(
|
||||
`Performance.getEntriesByType: Only valid for ${JSON.stringify(
|
||||
ALWAYS_LOGGED_ENTRY_TYPES,
|
||||
)} entry types, got ${entryType}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!NativePerformanceObserver?.clearEntries) {
|
||||
if (!NativePerformanceObserver?.getEntries) {
|
||||
warnNoNativePerformanceObserver();
|
||||
return [];
|
||||
}
|
||||
@@ -291,16 +302,17 @@ export default class Performance {
|
||||
): PerformanceEntryList {
|
||||
if (
|
||||
entryType !== undefined &&
|
||||
entryType !== 'mark' &&
|
||||
entryType !== 'measure'
|
||||
!ALWAYS_LOGGED_ENTRY_TYPES.includes(entryType)
|
||||
) {
|
||||
console.log(
|
||||
`Performance.getEntriesByName: Only valid for 'mark' and 'measure' entry types, got ${entryType}`,
|
||||
console.warn(
|
||||
`Performance.getEntriesByName: Only valid for ${JSON.stringify(
|
||||
ALWAYS_LOGGED_ENTRY_TYPES,
|
||||
)} entry types, got ${entryType}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!NativePerformanceObserver?.clearEntries) {
|
||||
if (!NativePerformanceObserver?.getEntries) {
|
||||
warnNoNativePerformanceObserver();
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
export type HighResTimeStamp = number;
|
||||
export type PerformanceEntryType = 'mark' | 'measure' | 'event';
|
||||
|
||||
export const ALWAYS_LOGGED_ENTRY_TYPES: $ReadOnlyArray<PerformanceEntryType> = [
|
||||
'mark',
|
||||
'measure',
|
||||
];
|
||||
|
||||
export class PerformanceEntry {
|
||||
name: string;
|
||||
entryType: PerformanceEntryType;
|
||||
|
||||
@@ -36,6 +36,13 @@ void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) {
|
||||
buffer.durationThreshold = DEFAULT_DURATION_THRESHOLD;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::setAlwaysLogged(
|
||||
PerformanceEntryType entryType,
|
||||
bool isAlwaysLogged) {
|
||||
auto &buffer = getBuffer(entryType);
|
||||
buffer.isAlwaysLogged = isAlwaysLogged;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::setDurationThreshold(
|
||||
PerformanceEntryType entryType,
|
||||
double durationThreshold) {
|
||||
@@ -82,7 +89,7 @@ void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) {
|
||||
eventCounts_[entry.name]++;
|
||||
}
|
||||
|
||||
if (!isReporting(entryType)) {
|
||||
if (!isReporting(entryType) && !isAlwaysLogged(entryType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -328,7 +335,7 @@ static const SupportedEventTypeRegistry &getSupportedEvents() {
|
||||
}
|
||||
|
||||
EventTag PerformanceEntryReporter::onEventStart(const char *name) {
|
||||
if (!isReportingEvents()) {
|
||||
if (!isReporting(PerformanceEntryType::EVENT)) {
|
||||
return 0;
|
||||
}
|
||||
const auto &supportedEvents = getSupportedEvents();
|
||||
@@ -355,7 +362,7 @@ EventTag PerformanceEntryReporter::onEventStart(const char *name) {
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::onEventDispatch(EventTag tag) {
|
||||
if (!isReportingEvents() || tag == 0) {
|
||||
if (!isReporting(PerformanceEntryType::EVENT) || tag == 0) {
|
||||
return;
|
||||
}
|
||||
auto timeStamp = JSExecutor::performanceNow();
|
||||
@@ -369,7 +376,7 @@ void PerformanceEntryReporter::onEventDispatch(EventTag tag) {
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::onEventEnd(EventTag tag) {
|
||||
if (!isReportingEvents() || tag == 0) {
|
||||
if (!isReporting(PerformanceEntryType::EVENT) || tag == 0) {
|
||||
return;
|
||||
}
|
||||
auto timeStamp = JSExecutor::performanceNow();
|
||||
|
||||
@@ -49,6 +49,7 @@ constexpr size_t DEFAULT_MAX_BUFFER_SIZE = 1024;
|
||||
struct PerformanceEntryBuffer {
|
||||
BoundedConsumableBuffer<RawPerformanceEntry> entries{DEFAULT_MAX_BUFFER_SIZE};
|
||||
bool isReporting{false};
|
||||
bool isAlwaysLogged{false};
|
||||
double durationThreshold{DEFAULT_DURATION_THRESHOLD};
|
||||
bool hasNameLookup{false};
|
||||
PerformanceEntryRegistryType nameLookup;
|
||||
@@ -80,6 +81,7 @@ class PerformanceEntryReporter : public EventLogger {
|
||||
void startReporting(PerformanceEntryType entryType);
|
||||
void stopReporting(PerformanceEntryType entryType);
|
||||
void stopReporting();
|
||||
void setAlwaysLogged(PerformanceEntryType entryType, bool isAlwaysLogged);
|
||||
void setDurationThreshold(
|
||||
PerformanceEntryType entryType,
|
||||
double durationThreshold);
|
||||
@@ -101,8 +103,8 @@ class PerformanceEntryReporter : public EventLogger {
|
||||
return getBuffer(entryType).isReporting;
|
||||
}
|
||||
|
||||
bool isReportingEvents() const {
|
||||
return isReporting(PerformanceEntryType::EVENT);
|
||||
bool isAlwaysLogged(PerformanceEntryType entryType) const {
|
||||
return getBuffer(entryType).isAlwaysLogged;
|
||||
}
|
||||
|
||||
uint32_t getDroppedEntryCount() const {
|
||||
|
||||
+21
-3
@@ -18,6 +18,7 @@ import type {
|
||||
import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry';
|
||||
|
||||
const reportingType: Set<RawPerformanceEntryType> = new Set();
|
||||
const isAlwaysLogged: Set<RawPerformanceEntryType> = new Set();
|
||||
const eventCounts: Map<string, number> = new Map();
|
||||
const durationThresholds: Map<RawPerformanceEntryType, number> = new Map();
|
||||
let entries: Array<RawPerformanceEntry> = [];
|
||||
@@ -33,6 +34,19 @@ const NativePerformanceObserverMock: NativePerformanceObserver = {
|
||||
durationThresholds.delete(entryType);
|
||||
},
|
||||
|
||||
setIsBuffered: (
|
||||
entryTypes: $ReadOnlyArray<RawPerformanceEntryType>,
|
||||
isBuffered: boolean,
|
||||
) => {
|
||||
for (const entryType of entryTypes) {
|
||||
if (isBuffered) {
|
||||
isAlwaysLogged.add(entryType);
|
||||
} else {
|
||||
isAlwaysLogged.delete(entryType);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
popPendingEntries: (): GetPendingEntriesResult => {
|
||||
const res = entries;
|
||||
entries = [];
|
||||
@@ -47,7 +61,10 @@ const NativePerformanceObserverMock: NativePerformanceObserver = {
|
||||
},
|
||||
|
||||
logRawEntry: (entry: RawPerformanceEntry) => {
|
||||
if (reportingType.has(entry.entryType)) {
|
||||
if (
|
||||
reportingType.has(entry.entryType) ||
|
||||
isAlwaysLogged.has(entry.entryType)
|
||||
) {
|
||||
const durationThreshold = durationThresholds.get(entry.entryType);
|
||||
if (
|
||||
durationThreshold !== undefined &&
|
||||
@@ -81,8 +98,9 @@ const NativePerformanceObserverMock: NativePerformanceObserver = {
|
||||
clearEntries: (entryType: RawPerformanceEntryType, entryName?: string) => {
|
||||
entries = entries.filter(
|
||||
e =>
|
||||
e.entryType === entryType &&
|
||||
(entryName == null || e.name === entryName),
|
||||
(entryType !== RawPerformanceEntryTypeValues.UNDEFINED &&
|
||||
e.entryType !== entryType) ||
|
||||
(entryName != null && e.name !== entryName),
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
Vendored
+52
@@ -53,4 +53,56 @@ describe('NativePerformanceObserver', () => {
|
||||
|
||||
NativePerformanceObserverMock.stopReporting('mark');
|
||||
});
|
||||
|
||||
it('correctly clears/gets entries', async () => {
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'mark1',
|
||||
entryType: RawPerformanceEntryTypeValues.MARK,
|
||||
startTime: 0,
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'event1',
|
||||
entryType: RawPerformanceEntryTypeValues.EVENT,
|
||||
startTime: 0,
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
NativePerformanceObserverMock.clearEntries(
|
||||
RawPerformanceEntryTypeValues.UNDEFINED,
|
||||
);
|
||||
|
||||
expect(NativePerformanceObserverMock.getEntries()).toStrictEqual([]);
|
||||
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'entry1',
|
||||
entryType: RawPerformanceEntryTypeValues.MARK,
|
||||
startTime: 0,
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'entry2',
|
||||
entryType: RawPerformanceEntryTypeValues.MARK,
|
||||
startTime: 0,
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'entry1',
|
||||
entryType: RawPerformanceEntryTypeValues.EVENT,
|
||||
startTime: 0,
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
NativePerformanceObserverMock.clearEntries(
|
||||
RawPerformanceEntryTypeValues.UNDEFINED,
|
||||
'entry1',
|
||||
);
|
||||
|
||||
expect(
|
||||
NativePerformanceObserverMock.getEntries().map(e => e.name),
|
||||
).toStrictEqual(['entry2']);
|
||||
});
|
||||
});
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
const Performance = require('../Performance').default;
|
||||
|
||||
jest.mock(
|
||||
'../NativePerformance',
|
||||
() => require('../__mocks__/NativePerformance').default,
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'../NativePerformanceObserver',
|
||||
() => require('../__mocks__/NativePerformanceObserver').default,
|
||||
);
|
||||
|
||||
describe('Performance', () => {
|
||||
it('clearEntries removes correct entry types', async () => {
|
||||
const performance = new Performance();
|
||||
performance.mark('entry1', 0, 0);
|
||||
performance.mark('mark2', 0, 0);
|
||||
|
||||
performance.measure('entry1', {start: 0, duration: 0});
|
||||
performance.measure('measure2', {start: 0, duration: 0});
|
||||
|
||||
performance.clearMarks();
|
||||
|
||||
expect(performance.getEntries().map(e => e.name)).toStrictEqual([
|
||||
'entry1',
|
||||
'measure2',
|
||||
]);
|
||||
|
||||
performance.mark('entry2', 0, 0);
|
||||
performance.mark('mark3', 0, 0);
|
||||
|
||||
performance.clearMeasures();
|
||||
|
||||
expect(performance.getEntries().map(e => e.name)).toStrictEqual([
|
||||
'entry2',
|
||||
'mark3',
|
||||
]);
|
||||
|
||||
performance.clearMarks();
|
||||
|
||||
expect(performance.getEntries().map(e => e.name)).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('getEntries only works with allowed entry types', async () => {
|
||||
const performance = new Performance();
|
||||
performance.clearMarks();
|
||||
performance.clearMeasures();
|
||||
|
||||
performance.mark('entry1', 0, 0);
|
||||
performance.mark('mark2', 0, 0);
|
||||
|
||||
jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
performance.getEntriesByType('mark');
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
|
||||
performance.getEntriesByType('measure');
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
|
||||
performance.getEntriesByName('entry1');
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
|
||||
performance.getEntriesByName('entry1', 'event');
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
|
||||
performance.getEntriesByName('entry1', 'mark');
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
|
||||
performance.getEntriesByType('event');
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('getEntries works with marks and measures', async () => {
|
||||
const performance = new Performance();
|
||||
performance.clearMarks();
|
||||
performance.clearMeasures();
|
||||
|
||||
performance.mark('entry1', 0, 0);
|
||||
performance.mark('mark2', 0, 0);
|
||||
|
||||
performance.measure('entry1', {start: 0, duration: 0});
|
||||
performance.measure('measure2', {start: 0, duration: 0});
|
||||
|
||||
expect(performance.getEntries().map(e => e.name)).toStrictEqual([
|
||||
'entry1',
|
||||
'mark2',
|
||||
'entry1',
|
||||
'measure2',
|
||||
]);
|
||||
|
||||
expect(performance.getEntriesByType('mark').map(e => e.name)).toStrictEqual(
|
||||
['entry1', 'mark2'],
|
||||
);
|
||||
|
||||
expect(
|
||||
performance.getEntriesByType('measure').map(e => e.name),
|
||||
).toStrictEqual(['entry1', 'measure2']);
|
||||
|
||||
expect(
|
||||
performance.getEntriesByName('entry1').map(e => e.entryType),
|
||||
).toStrictEqual(['mark', 'measure']);
|
||||
|
||||
expect(
|
||||
performance.getEntriesByName('entry1', 'measure').map(e => e.entryType),
|
||||
).toStrictEqual(['measure']);
|
||||
});
|
||||
});
|
||||
-1
@@ -44,7 +44,6 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestStartReporting) {
|
||||
ASSERT_TRUE(reporter.isReporting(PerformanceEntryType::MEASURE));
|
||||
|
||||
ASSERT_FALSE(reporter.isReporting(PerformanceEntryType::EVENT));
|
||||
ASSERT_FALSE(reporter.isReportingEvents());
|
||||
}
|
||||
|
||||
TEST(PerformanceEntryReporter, PerformanceEntryReporterTestStopReporting) {
|
||||
|
||||
Reference in New Issue
Block a user