Skip to content

Commit 529f55c

Browse files
rubennortefacebook-github-bot
authored andcommitted
Implement performance.timeOrigin (#53660)
Summary: Pull Request resolved: #53660 Changelog: [internal] (This is internal because these APIs aren't enabled in OSS yet) Implements `performance.timeOrigin` to allow converting timestamps from `performance.now()` to be based on the Unix epoch. This implementation isn't fully spec-compliant to align with the current implementation of `performance.now()`, where the base of the clock is system boot time instead of app startup / navigation time. Reviewed By: huntie Differential Revision: D82016724 fbshipit-source-id: e3a066721cecf41e2fd963beb94a0a2f1c5d6493
1 parent 481fb61 commit 529f55c

7 files changed

Lines changed: 82 additions & 4 deletions

File tree

packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,18 @@ NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
131131
: NativePerformanceCxxSpec(std::move(jsInvoker)) {}
132132

133133
HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
134+
// This is not spec-compliant, as this is the duration from system boot to
135+
// now, instead of from app startup to now.
136+
// This should be carefully changed eventually.
134137
return HighResTimeStamp::now();
135138
}
136139

140+
HighResDuration NativePerformance::timeOrigin(jsi::Runtime& /*rt*/) {
141+
// This is not spec-compliant, as this is an approximation from Unix epoch to
142+
// system boot, instead of a precise duration from Unix epoch to app startup.
143+
return HighResTimeStamp::unsafeOriginFromUnixTimeStamp();
144+
}
145+
137146
void NativePerformance::reportMark(
138147
jsi::Runtime& rt,
139148
const std::string& name,

packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
8888
// https://www.w3.org/TR/hr-time-3/#now-method
8989
HighResTimeStamp now(jsi::Runtime& rt);
9090

91+
// https://www.w3.org/TR/hr-time-3/#timeorigin-attribute
92+
HighResDuration timeOrigin(jsi::Runtime& rt);
93+
9194
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
9295

9396
void reportMark(

packages/react-native/ReactCommon/react/timing/primitives.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ class HighResTimeStamp {
201201
return HighResTimeStamp(chronoNow());
202202
}
203203

204+
static HighResDuration unsafeOriginFromUnixTimeStamp() noexcept {
205+
static auto origin = computeUnsafeOriginFromUnixTimeStamp();
206+
return origin;
207+
}
208+
204209
static constexpr HighResTimeStamp min() noexcept {
205210
return HighResTimeStamp(std::chrono::steady_clock::time_point::min());
206211
}
@@ -285,6 +290,13 @@ class HighResTimeStamp {
285290

286291
std::chrono::steady_clock::time_point chronoTimePoint_;
287292

293+
static HighResDuration computeUnsafeOriginFromUnixTimeStamp() noexcept {
294+
auto systemNow = std::chrono::system_clock::now();
295+
auto steadyNow = std::chrono::steady_clock::now();
296+
return HighResDuration(
297+
systemNow.time_since_epoch() - steadyNow.time_since_epoch());
298+
}
299+
288300
#ifdef REACT_NATIVE_DEBUG
289301
static std::function<std::chrono::steady_clock::time_point()>&
290302
getTimeStampProvider() {

packages/react-native/flow/bom.js.flow

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,11 @@ declare interface EventCounts {
199199
}
200200

201201
declare class Performance {
202-
clearMarks(name?: string): void;
202+
+eventCounts: EventCounts;
203+
+timeOrigin: DOMHighResTimeStamp;
203204

205+
clearMarks(name?: string): void;
204206
clearMeasures(name?: string): void;
205-
206-
eventCounts: EventCounts;
207207
getEntries: (
208208
options?: PerformanceEntryFilterOptions,
209209
) => Array<PerformanceEntry>;
@@ -215,7 +215,7 @@ declare class Performance {
215215
startMarkOrOptions?: string | PerformanceMeasureOptions,
216216
endMark?: string,
217217
): PerformanceMeasure;
218-
now: () => DOMHighResTimeStamp;
218+
now(): DOMHighResTimeStamp;
219219
toJSON(): string;
220220
}
221221

packages/react-native/src/private/webapis/performance/Performance.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const cachedReportMeasure = NativePerformance.reportMeasure;
6363
const cachedGetMarkTime = NativePerformance.getMarkTime;
6464
const cachedNativeClearMarks = NativePerformance.clearMarks;
6565
const cachedNativeClearMeasures = NativePerformance.clearMeasures;
66+
let cachedTimeOrigin: ?DOMHighResTimeStamp;
6667

6768
const MARK_OPTIONS_REUSABLE_OBJECT: PerformanceMarkOptions = {
6869
startTime: 0,
@@ -139,6 +140,24 @@ export default class Performance {
139140
});
140141
}
141142

143+
/**
144+
* Returns the high resolution timestamp that is used as the baseline for
145+
* performance-related timestamps.
146+
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin
147+
*/
148+
get timeOrigin(): DOMHighResTimeStamp {
149+
if (cachedTimeOrigin == null) {
150+
if (NativePerformance.timeOrigin) {
151+
cachedTimeOrigin = NativePerformance?.timeOrigin();
152+
} else {
153+
// Very naive polyfill.
154+
cachedTimeOrigin = Date.now() - getCurrentTimeStamp();
155+
}
156+
}
157+
158+
return cachedTimeOrigin;
159+
}
160+
142161
mark(
143162
markName: string,
144163
markOptions?: PerformanceMarkOptions,

packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,37 @@ describe('Performance', () => {
2222
return new PerformanceEntry();
2323
}).toThrow("Failed to construct 'PerformanceEntry': Illegal constructor");
2424
});
25+
26+
describe('now', () => {
27+
it('provides increasing timestamps since boot time', () => {
28+
const first = performance.now();
29+
const second = performance.now();
30+
const third = performance.now();
31+
32+
expect(typeof first).toBe('number');
33+
expect(typeof second).toBe('number');
34+
expect(typeof third).toBe('number');
35+
36+
expect(first).toBeGreaterThan(0);
37+
expect(second).toBeGreaterThan(first);
38+
expect(third).toBeGreaterThan(second);
39+
});
40+
});
41+
42+
describe('timeOrigin', () => {
43+
it('allows moving timestamps to Unix epoch', () => {
44+
// We need to truncate timestamps because `Date.now()` only provides
45+
// integer millisecond precision.
46+
const adjustedMonotonicTime = Math.trunc(
47+
performance.now() + performance.timeOrigin,
48+
);
49+
const wallTime = Date.now();
50+
const adjustedMonotonicTimeAfter = Math.trunc(
51+
performance.now() + performance.timeOrigin,
52+
);
53+
54+
expect(wallTime).toBeGreaterThanOrEqual(adjustedMonotonicTime);
55+
expect(adjustedMonotonicTimeAfter).toBeGreaterThanOrEqual(wallTime);
56+
});
57+
});
2558
});

packages/react-native/src/private/webapis/performance/specs/NativePerformance.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export type PerformanceObserverInit = {
5454

5555
export interface Spec extends TurboModule {
5656
+now: () => number;
57+
+timeOrigin?: () => number;
58+
5759
+reportMark: (name: string, startTime: number, entry: mixed) => void;
5860
+reportMeasure: (
5961
name: string,

0 commit comments

Comments
 (0)