Skip to content

Commit df904ef

Browse files
AbhiPrasadc298lee
authored andcommitted
ref(utils): Clean up timestamp calculation code (#10069)
This PR removes the usage of `dynamicRequire` from `packages/utils/src/time.ts` and cleans up our timestamp code to be simpler and reduce bundle size. Removing `dynamicRequire` means that we no longer rely on `perf_hooks` for the `performance` API, but instead try to grab it from `globalThis.performance`. `performance` was added to the global in Node 16, which means we'll fallback to using `Date.now()` in Node 8, 10, 12, 14 (and in v8 just Node 14). I think that is an acceptable tradeoff, we just reduce accuracy for those versions. This does not refactor `browserPerformanceTimeOrigin` code at the bottom of the file, I want to come back and touch that in a follow up PR to reduce the amount of changes made here. I would appreciate reviews/opinions on this, I'm not 100% confident in the changes.
1 parent ff980c8 commit df904ef

File tree

1 file changed

+31
-89
lines changed

1 file changed

+31
-89
lines changed

packages/utils/src/time.ts

Lines changed: 31 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,6 @@
1-
import { dynamicRequire, isNodeEnv } from './node';
2-
import { getGlobalObject } from './worldwide';
1+
import { GLOBAL_OBJ } from './worldwide';
32

4-
// eslint-disable-next-line deprecation/deprecation
5-
const WINDOW = getGlobalObject<Window>();
6-
7-
/**
8-
* An object that can return the current timestamp in seconds since the UNIX epoch.
9-
*/
10-
interface TimestampSource {
11-
nowSeconds(): number;
12-
}
13-
14-
/**
15-
* A TimestampSource implementation for environments that do not support the Performance Web API natively.
16-
*
17-
* Note that this TimestampSource does not use a monotonic clock. A call to `nowSeconds` may return a timestamp earlier
18-
* than a previously returned value. We do not try to emulate a monotonic behavior in order to facilitate debugging. It
19-
* is more obvious to explain "why does my span have negative duration" than "why my spans have zero duration".
20-
*/
21-
const dateTimestampSource: TimestampSource = {
22-
nowSeconds: () => Date.now() / 1000,
23-
};
3+
const ONE_SECOND_IN_MS = 1000;
244

255
/**
266
* A partial definition of the [Performance Web API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Performance}
@@ -37,89 +17,56 @@ interface Performance {
3717
now(): number;
3818
}
3919

20+
/**
21+
* Returns a timestamp in seconds since the UNIX epoch using the Date API.
22+
*
23+
* TODO(v8): Return type should be rounded.
24+
*/
25+
export function dateTimestampInSeconds(): number {
26+
return Date.now() / ONE_SECOND_IN_MS;
27+
}
28+
4029
/**
4130
* Returns a wrapper around the native Performance API browser implementation, or undefined for browsers that do not
4231
* support the API.
4332
*
4433
* Wrapping the native API works around differences in behavior from different browsers.
4534
*/
46-
function getBrowserPerformance(): Performance | undefined {
47-
const { performance } = WINDOW;
35+
function createUnixTimestampInSecondsFunc(): () => number {
36+
const { performance } = GLOBAL_OBJ as typeof GLOBAL_OBJ & { performance?: Performance };
4837
if (!performance || !performance.now) {
49-
return undefined;
38+
return dateTimestampInSeconds;
5039
}
5140

52-
// Replace performance.timeOrigin with our own timeOrigin based on Date.now().
53-
//
54-
// This is a partial workaround for browsers reporting performance.timeOrigin such that performance.timeOrigin +
55-
// performance.now() gives a date arbitrarily in the past.
56-
//
57-
// Additionally, computing timeOrigin in this way fills the gap for browsers where performance.timeOrigin is
58-
// undefined.
59-
//
60-
// The assumption that performance.timeOrigin + performance.now() ~= Date.now() is flawed, but we depend on it to
61-
// interact with data coming out of performance entries.
62-
//
63-
// Note that despite recommendations against it in the spec, browsers implement the Performance API with a clock that
64-
// might stop when the computer is asleep (and perhaps under other circumstances). Such behavior causes
65-
// performance.timeOrigin + performance.now() to have an arbitrary skew over Date.now(). In laptop computers, we have
66-
// observed skews that can be as long as days, weeks or months.
67-
//
68-
// See https://github.com/getsentry/sentry-javascript/issues/2590.
41+
// Some browser and environments don't have a timeOrigin, so we fallback to
42+
// using Date.now() to compute the starting time.
43+
const approxStartingTimeOrigin = Date.now() - performance.now();
44+
const timeOrigin = performance.timeOrigin == undefined ? approxStartingTimeOrigin : performance.timeOrigin;
45+
46+
// performance.now() is a monotonic clock, which means it starts at 0 when the process begins. To get the current
47+
// wall clock time (actual UNIX timestamp), we need to add the starting time origin and the current time elapsed.
6948
//
70-
// BUG: despite our best intentions, this workaround has its limitations. It mostly addresses timings of pageload
71-
// transactions, but ignores the skew built up over time that can aversely affect timestamps of navigation
72-
// transactions of long-lived web pages.
73-
const timeOrigin = Date.now() - performance.now();
74-
75-
return {
76-
now: () => performance.now(),
77-
timeOrigin,
49+
// TODO: This does not account for the case where the monotonic clock that powers performance.now() drifts from the
50+
// wall clock time, which causes the returned timestamp to be inaccurate. We should investigate how to detect and
51+
// correct for this.
52+
// See: https://github.com/getsentry/sentry-javascript/issues/2590
53+
// See: https://github.com/mdn/content/issues/4713
54+
// See: https://dev.to/noamr/when-a-millisecond-is-not-a-millisecond-3h6
55+
return () => {
56+
return (timeOrigin + performance.now()) / ONE_SECOND_IN_MS;
7857
};
7958
}
8059

81-
/**
82-
* Returns the native Performance API implementation from Node.js. Returns undefined in old Node.js versions that don't
83-
* implement the API.
84-
*/
85-
function getNodePerformance(): Performance | undefined {
86-
try {
87-
const perfHooks = dynamicRequire(module, 'perf_hooks') as { performance: Performance };
88-
return perfHooks.performance;
89-
} catch (_) {
90-
return undefined;
91-
}
92-
}
93-
94-
/**
95-
* The Performance API implementation for the current platform, if available.
96-
*/
97-
const platformPerformance: Performance | undefined = isNodeEnv() ? getNodePerformance() : getBrowserPerformance();
98-
99-
const timestampSource: TimestampSource =
100-
platformPerformance === undefined
101-
? dateTimestampSource
102-
: {
103-
nowSeconds: () => (platformPerformance.timeOrigin + platformPerformance.now()) / 1000,
104-
};
105-
106-
/**
107-
* Returns a timestamp in seconds since the UNIX epoch using the Date API.
108-
*/
109-
export const dateTimestampInSeconds: () => number = dateTimestampSource.nowSeconds.bind(dateTimestampSource);
110-
11160
/**
11261
* Returns a timestamp in seconds since the UNIX epoch using either the Performance or Date APIs, depending on the
11362
* availability of the Performance API.
11463
*
115-
* See `usingPerformanceAPI` to test whether the Performance API is used.
116-
*
11764
* BUG: Note that because of how browsers implement the Performance API, the clock might stop when the computer is
11865
* asleep. This creates a skew between `dateTimestampInSeconds` and `timestampInSeconds`. The
11966
* skew can grow to arbitrary amounts like days, weeks or months.
12067
* See https://github.com/getsentry/sentry-javascript/issues/2590.
12168
*/
122-
export const timestampInSeconds: () => number = timestampSource.nowSeconds.bind(timestampSource);
69+
export const timestampInSeconds = createUnixTimestampInSecondsFunc();
12370

12471
/**
12572
* Re-exported with an old name for backwards-compatibility.
@@ -129,11 +76,6 @@ export const timestampInSeconds: () => number = timestampSource.nowSeconds.bind(
12976
*/
13077
export const timestampWithMs = timestampInSeconds;
13178

132-
/**
133-
* A boolean that is true when timestampInSeconds uses the Performance API to produce monotonic timestamps.
134-
*/
135-
export const usingPerformanceAPI = platformPerformance !== undefined;
136-
13779
/**
13880
* Internal helper to store what is the source of browserPerformanceTimeOrigin below. For debugging only.
13981
*/
@@ -148,7 +90,7 @@ export const browserPerformanceTimeOrigin = ((): number | undefined => {
14890
// performance.timing.navigationStart, which results in poor results in performance data. We only treat time origin
14991
// data as reliable if they are within a reasonable threshold of the current time.
15092

151-
const { performance } = WINDOW;
93+
const { performance } = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
15294
if (!performance || !performance.now) {
15395
_browserPerformanceTimeOriginMode = 'none';
15496
return undefined;

0 commit comments

Comments
 (0)