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 */
13077export 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