Skip to content

Commit

Permalink
Ability to always log certain performance entry types (#36820)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
rshest authored and facebook-github-bot committed Apr 6, 2023
1 parent caa3fd8 commit 3506c8d
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
34 changes: 23 additions & 11 deletions packages/react-native/Libraries/WebPerformance/Performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;

Expand Down Expand Up @@ -261,22 +270,24 @@ 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 [];
}
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}`,
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 [];
}
Expand All @@ -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 [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -82,7 +89,7 @@ void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) {
eventCounts_[entry.name]++;
}

if (!isReporting(entryType)) {
if (!isReporting(entryType) && !isAlwaysLogged(entryType)) {
return;
}

Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> = [];
Expand All @@ -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 = [];
Expand All @@ -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 &&
Expand Down Expand Up @@ -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),
);
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
});
});
Loading

0 comments on commit 3506c8d

Please sign in to comment.