Skip to content

Commit

Permalink
Add API and scaffolding for Performance.mark implementation
Browse files Browse the repository at this point in the history
Summary:
[Changelog][Internal]

Adds API definition for [Performance.mark](https://www.w3.org/TR/user-timing/#mark-method) support.

This is a bare bone implementation, that just logs events on the native side. The next step is the native logic for queuing, flushing etc.

Note that here I route both JS and native marks to native for now, for simplicity sake - ultimately this may not be what we want,  as it may be more efficient to process marks, logged from JS, on the JS side.

Reviewed By: rubennorte

Differential Revision: D41472148

fbshipit-source-id: bdf2b182b8472a71a5500235849bca5af1c2f360
  • Loading branch information
rshest authored and pull[bot] committed Feb 14, 2024
1 parent 9d4d118 commit afe6521
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 10 deletions.
9 changes: 9 additions & 0 deletions Libraries/WebPerformance/NativePerformanceObserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,13 @@ void NativePerformanceObserver::setOnPerformanceEntryCallback(
<< (callback ? "non-empty" : "empty");
}

void NativePerformanceObserver::logEntryForDebug(
jsi::Runtime &rt,
RawPerformanceEntry entry) {
LOG(INFO) << "NativePerformanceObserver::logEntry: "
<< "name=" << entry.name << " type=" << entry.entryType
<< " startTime=" << entry.startTime
<< " duration=" << entry.duration;
}

} // namespace facebook::react
2 changes: 2 additions & 0 deletions Libraries/WebPerformance/NativePerformanceObserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class NativePerformanceObserver
jsi::Runtime &rt,
std::optional<AsyncCallback<>> callback);

void logEntryForDebug(jsi::Runtime &rt, RawPerformanceEntry entry);

private:
std::optional<AsyncCallback<>> callback_;
};
Expand Down
4 changes: 4 additions & 0 deletions Libraries/WebPerformance/NativePerformanceObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';

export const RawPerformanceEntryTypeValues = {
UNDEFINED: 0,
MARK: 1,
};

export type RawPerformanceEntryType = number;
Expand All @@ -34,6 +35,9 @@ export interface Spec extends TurboModule {
+stopReporting: (entryType: string) => void;
+getPendingEntries: () => $ReadOnlyArray<RawPerformanceEntry>;
+setOnPerformanceEntryCallback: (callback?: () => void) => void;

// NOTE: this is for dev-only purposes (potentially is going to be moved elsewhere)
+logEntryForDebug?: (entry: RawPerformanceEntry) => void;
}

export default (TurboModuleRegistry.get<Spec>(
Expand Down
66 changes: 66 additions & 0 deletions Libraries/WebPerformance/Performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* 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
*/

import type {HighResTimeStamp} from './PerformanceObserver';

import NativePerformanceObserver, {
RawPerformanceEntryTypeValues,
} from './NativePerformanceObserver';
import {PerformanceEntry} from './PerformanceObserver';

type DetailType = mixed;

export type PerformanceMarkOptions = {
detail?: DetailType,
startTime?: HighResTimeStamp,
};

function getCurrentTimeStamp(): HighResTimeStamp {
return global.nativePerformanceNow?.() ?? Date.now();
}

export class PerformanceMark extends PerformanceEntry {
detail: DetailType;

constructor(markName: string, markOptions?: PerformanceMarkOptions) {
let startTime = markOptions?.startTime ?? getCurrentTimeStamp();
super({name: markName, entryType: 'mark', startTime, duration: 0});
if (markOptions !== undefined) {
this.detail = markOptions.detail;
}
}
}

/**
* 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 {
mark(
markName: string,
markOptions?: PerformanceMarkOptions,
): PerformanceMark {
const mark = new PerformanceMark(markName, markOptions);
NativePerformanceObserver?.logEntryForDebug?.({
name: markName,
entryType: RawPerformanceEntryTypeValues.MARK,
startTime: mark.startTime,
duration: mark.duration,
});
return mark;
}

clearMarks(markName?: string): void {}

now(): HighResTimeStamp {
return getCurrentTimeStamp();
}
}
19 changes: 9 additions & 10 deletions Libraries/WebPerformance/PerformanceObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import NativePerformanceObserver from './NativePerformanceObserver';

export type HighResTimeStamp = number;
// TODO: Extend once new types (such as event) are supported.
// TODO: Get rid of the "undefined" once there is at least one type supported.
export type PerformanceEntryType = 'undefined';
export type PerformanceEntryType = 'undefined' | 'mark';

export class PerformanceEntry {
name: string;
Expand Down Expand Up @@ -53,7 +52,7 @@ export class PerformanceEntry {
function rawToPerformanceEntryType(
type: RawPerformanceEntryType,
): PerformanceEntryType {
return 'undefined';
return 'mark';
}

function rawToPerformanceEntry(entry: RawPerformanceEntry): PerformanceEntry {
Expand Down Expand Up @@ -166,15 +165,15 @@ export default class PerformanceObserver {
} else {
this._entryTypes = new Set([options.type]);
}
this._entryTypes.forEach(type => {
for (const type of this._entryTypes) {
if (!_observedEntryTypeRefCount.has(type)) {
NativePerformanceObserver.startReporting(type);
}
_observedEntryTypeRefCount.set(
type,
(_observedEntryTypeRefCount.get(type) ?? 0) + 1,
);
});
}
_observers.add(this);
}

Expand All @@ -183,15 +182,15 @@ export default class PerformanceObserver {
warnNoNativePerformanceObserver();
return;
}
this._entryTypes.forEach(type => {
for (const type of this._entryTypes) {
const entryTypeRefCount = _observedEntryTypeRefCount.get(type) ?? 0;
if (entryTypeRefCount === 1) {
_observedEntryTypeRefCount.delete(type);
NativePerformanceObserver.stopReporting(type);
} else if (entryTypeRefCount !== 0) {
_observedEntryTypeRefCount.set(type, entryTypeRefCount - 1);
}
});
}
_observers.delete(this);
if (_observers.size === 0) {
NativePerformanceObserver.setOnPerformanceEntryCallback(undefined);
Expand All @@ -201,7 +200,7 @@ export default class PerformanceObserver {

static supportedEntryTypes: $ReadOnlyArray<PerformanceEntryType> =
// TODO: add types once they are fully supported
Object.freeze([]);
Object.freeze(['mark']);
}

// This is a callback that gets scheduled and periodically called from the native side
Expand All @@ -211,13 +210,13 @@ function onPerformanceEntry() {
}
const rawEntries = NativePerformanceObserver.getPendingEntries();
const entries = rawEntries.map(rawToPerformanceEntry);
_observers.forEach(observer => {
for (const observer of _observers) {
const entriesForObserver: PerformanceEntryList = entries.filter(entry =>
observer._entryTypes.has(entry.entryType),
);
observer._callback(
new PerformanceObserverEntryList(entriesForObserver),
observer,
);
});
}
}

0 comments on commit afe6521

Please sign in to comment.