Skip to content

Commit 91b1f96

Browse files
feat: Calculate New Relic time in the agent (#911)
1 parent a52c57b commit 91b1f96

File tree

17 files changed

+461
-105
lines changed

17 files changed

+461
-105
lines changed

src/cdn/polyfills.js

+1
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ import 'core-js/stable/object/get-own-property-descriptors'
2222
import 'core-js/stable/url'
2323
import 'core-js/stable/url-search-params'
2424
import 'core-js/stable/string/starts-with'
25+
import 'core-js/stable/number/is-nan'

src/common/context/observation-context-manager.js

-55
This file was deleted.

src/common/event-emitter/contextual-ee.js

+10-20
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import { gosNREUM } from '../window/nreum'
77
import { getOrSet } from '../util/get-or-set'
88
import { getRuntime } from '../config/config'
9-
import { EventContext } from '../context/event-context'
10-
import { ObservationContextManager } from '../context/observation-context-manager'
9+
import { EventContext } from './event-context'
10+
import { bundleId } from '../ids/bundle-id'
1111

12+
// create a unique id to store event context data for the current agent bundle
13+
const contextId = `nr@context:${bundleId}`
1214
// create global emitter instance that can be shared among bundles
1315
const globalInstance = ee(undefined, 'globalEE')
1416

@@ -18,7 +20,7 @@ if (!nr.ee) {
1820
nr.ee = globalInstance
1921
}
2022

21-
export { globalInstance as ee }
23+
export { globalInstance as ee, contextId }
2224

2325
function ee (old, debugId) {
2426
var handlers = {}
@@ -50,8 +52,8 @@ function ee (old, debugId) {
5052
aborted: false,
5153
isBuffering,
5254
debugId,
53-
backlog: isolatedBacklog ? {} : old && typeof old.backlog === 'object' ? old.backlog : {},
54-
observationContextManager: null
55+
backlog: isolatedBacklog ? {} : old && typeof old.backlog === 'object' ? old.backlog : {}
56+
5557
}
5658

5759
return emitter
@@ -60,15 +62,9 @@ function ee (old, debugId) {
6062
if (contextOrStore && contextOrStore instanceof EventContext) {
6163
return contextOrStore
6264
} else if (contextOrStore) {
63-
return getOrSet(contextOrStore, ObservationContextManager.contextId, () =>
64-
emitter.observationContextManager
65-
? emitter.observationContextManager.getCreateContext(contextOrStore)
66-
: new EventContext(ObservationContextManager.contextId)
67-
)
65+
return getOrSet(contextOrStore, contextId, () => new EventContext(contextId))
6866
} else {
69-
return emitter.observationContextManager
70-
? emitter.observationContextManager.getCreateContext({})
71-
: new EventContext(ObservationContextManager.contextId)
67+
return new EventContext(contextId)
7268
}
7369
}
7470

@@ -116,13 +112,7 @@ function ee (old, debugId) {
116112
}
117113

118114
function getOrCreate (name) {
119-
const newEventEmitter = (emitters[name] = emitters[name] || ee(emitter, name))
120-
121-
if (!newEventEmitter.observationContextManager && emitter.observationContextManager) {
122-
newEventEmitter.observationContextManager = emitter.observationContextManager
123-
}
124-
125-
return newEventEmitter
115+
return (emitters[name] = emitters[name] || ee(emitter, name))
126116
}
127117

128118
function bufferEventsByGroup (types, group) {

src/common/harvest/harvest.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export class Harvest extends SharedContext {
145145
result.addEventListener('loadend', function () {
146146
// `this` refers to the XHR object in this scope, do not change this to a fat arrow
147147
// status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
148-
const cbResult = { sent: this.status !== 0, status: this.status }
148+
const cbResult = { sent: this.status !== 0, status: this.status, xhr: this, fullUrl }
149149
if (this.status === 429) {
150150
cbResult.retry = true
151151
cbResult.delay = harvestScope.tooManyRequestsDelay

src/common/harvest/harvest.test.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ describe('_send', () => {
368368
expect(xhrAddEventListener).toHaveBeenCalledWith('loadend', expect.any(Function), expect.any(Object))
369369
expect(result).toEqual(jest.mocked(submitDataModule.xhr).mock.results[0].value)
370370
expect(submitMethod).not.toHaveBeenCalled()
371-
expect(spec.cbFinished).toHaveBeenCalledWith({ ...xhrState, sent: true })
371+
expect(spec.cbFinished).toHaveBeenCalledWith({ ...xhrState, sent: true, xhr: xhrState, fullUrl: expect.any(String) })
372372
})
373373

374374
test('should set cbFinished state retry to true with delay when xhr has 429 status', () => {
@@ -392,7 +392,9 @@ describe('_send', () => {
392392
...xhrState,
393393
sent: true,
394394
retry: true,
395-
delay: harvestInstance.tooManyRequestsDelay
395+
delay: harvestInstance.tooManyRequestsDelay,
396+
xhr: xhrState,
397+
fullUrl: expect.any(String)
396398
})
397399
})
398400

@@ -417,7 +419,9 @@ describe('_send', () => {
417419
expect(spec.cbFinished).toHaveBeenCalledWith({
418420
...xhrState,
419421
sent: true,
420-
retry: true
422+
retry: true,
423+
xhr: xhrState,
424+
fullUrl: expect.any(String)
421425
})
422426
})
423427

@@ -441,7 +445,9 @@ describe('_send', () => {
441445
expect(submitMethod).not.toHaveBeenCalled()
442446
expect(spec.cbFinished).toHaveBeenCalledWith({
443447
...xhrState,
444-
sent: true
448+
sent: true,
449+
xhr: xhrState,
450+
fullUrl: expect.any(String)
445451
})
446452
})
447453

@@ -466,7 +472,9 @@ describe('_send', () => {
466472
expect(spec.cbFinished).toHaveBeenCalledWith({
467473
...xhrState,
468474
responseText: undefined,
469-
sent: true
475+
sent: true,
476+
xhr: xhrState,
477+
fullUrl: expect.any(String)
470478
})
471479
})
472480

@@ -488,7 +496,9 @@ describe('_send', () => {
488496
expect(submitMethod).not.toHaveBeenCalled()
489497
expect(spec.cbFinished).toHaveBeenCalledWith({
490498
...xhrState,
491-
sent: false
499+
sent: false,
500+
xhr: xhrState,
501+
fullUrl: expect.any(String)
492502
})
493503
})
494504
})

src/common/timing/time-keeper.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { gosNREUM } from '../window/nreum'
2+
import { globalScope } from '../constants/runtime'
3+
import { getRuntime } from '../config/config'
4+
5+
/**
6+
* Class used to adjust the timestamp of harvested data to New Relic server time. This
7+
* is done by tracking the performance timings of the RUM call and applying a calculation
8+
* to the harvested data event offset time.
9+
*/
10+
export class TimeKeeper {
11+
#agent
12+
13+
/**
14+
* Represents the browser origin time corrected to NR server time.
15+
* @type {number}
16+
*/
17+
#correctedOriginTime
18+
19+
/**
20+
* Represents the difference in milliseconds between the calculated NR server time and
21+
* the local time.
22+
* @type {number}
23+
*/
24+
#localTimeDiff
25+
26+
constructor (agent) {
27+
this.#agent = agent
28+
}
29+
30+
static getTimeKeeperByAgentIdentifier (agentIdentifier) {
31+
const nr = gosNREUM()
32+
return Object.keys(nr?.initializedAgents || {}).indexOf(agentIdentifier) > -1
33+
? nr.initializedAgents[agentIdentifier].timeKeeper
34+
: undefined
35+
}
36+
37+
get correctedPageOriginTime () {
38+
return this.#correctedOriginTime
39+
}
40+
41+
/**
42+
* Process a rum request to calculate NR server time.
43+
* @param rumRequest {XMLHttpRequest} The xhr for the rum request
44+
* @param rumRequestUrl {string} The full url of the rum request
45+
*/
46+
processRumRequest (rumRequest, rumRequestUrl) {
47+
const responseDateHeader = rumRequest.getResponseHeader('Date')
48+
if (!responseDateHeader) {
49+
throw new Error('Missing date header on rum response.')
50+
}
51+
52+
const resourceEntries = globalScope.performance.getEntriesByName(rumRequestUrl, 'resource')
53+
if (!Array.isArray((resourceEntries)) || resourceEntries.length === 0) {
54+
throw new Error('Missing rum request performance entry.')
55+
}
56+
57+
let medianRumOffset = 0
58+
let serverOffset = 0
59+
if (typeof resourceEntries[0].responseStart === 'number' && resourceEntries[0].responseStart !== 0) {
60+
// Cors is enabled and we can make a more accurate calculation of NR server time
61+
medianRumOffset = (resourceEntries[0].responseStart - resourceEntries[0].requestStart) / 2
62+
serverOffset = Math.floor(resourceEntries[0].requestStart + medianRumOffset)
63+
} else {
64+
// Cors is disabled or erred, we need to use a less accurate calculation
65+
medianRumOffset = (resourceEntries[0].responseEnd - resourceEntries[0].fetchStart) / 2
66+
serverOffset = Math.floor(resourceEntries[0].fetchStart + medianRumOffset)
67+
}
68+
69+
// Corrected page origin time
70+
this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset)
71+
this.#localTimeDiff = getRuntime(this.#agent.agentIdentifier).offset - this.#correctedOriginTime
72+
73+
if (Number.isNaN(this.#correctedOriginTime)) {
74+
throw new Error('Date header invalid format.')
75+
}
76+
}
77+
78+
/**
79+
* Converts a page origin relative time to an absolute timestamp
80+
* corrected to NR server time.
81+
* @param relativeTime {number} The relative time of the event in milliseconds
82+
* @returns {number} The correct timestamp as a unix/epoch timestamp value
83+
*/
84+
convertRelativeTimestamp (relativeTime) {
85+
return this.#correctedOriginTime + relativeTime
86+
}
87+
88+
/**
89+
* Corrects an event timestamp to NR server time.
90+
* @param timestamp {number} The unix/epoch timestamp of the event with milliseconds
91+
* @return {number} Corrected unix/epoch timestamp
92+
*/
93+
correctAbsoluteTimestamp (timestamp) {
94+
return Math.floor(timestamp - this.#localTimeDiff)
95+
}
96+
}

0 commit comments

Comments
 (0)