Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update agent to use new relic server time #918

Merged
merged 13 commits into from
Mar 28, 2024
3 changes: 3 additions & 0 deletions src/common/context/__mocks__/shared-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export const SharedContext = jest.fn(function () {
agentIdentifier: 'abcd',
ee: {
on: jest.fn()
},
timeKeeper: {
now: jest.fn(() => performance.now())
}
}
})
5 changes: 3 additions & 2 deletions src/common/context/shared-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

const model = {
agentIdentifier: '',
ee: undefined
ee: undefined,
timeKeeper: undefined
}

export class SharedContext {
Expand All @@ -15,7 +16,7 @@
if (Object.keys(model).includes(key)) this.sharedContext[key] = value
})
} catch (err) {
warn('An error occured while setting SharedContext', err)
warn('An error occurred while setting SharedContext', err)

Check warning on line 19 in src/common/context/shared-context.js

View check run for this annotation

Codecov / codecov/patch

src/common/context/shared-context.js#L19

Added line #L19 was not covered by tests
}
}
}
4 changes: 2 additions & 2 deletions src/common/harvest/harvest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import * as submitData from '../util/submit-data'
import { getLocation } from '../url/location'
import { getInfo, getConfigurationValue, getRuntime, getConfiguration } from '../config/config'
import { cleanURL } from '../url/clean-url'
import { now } from '../timing/now'
import { eventListenerOpts } from '../event-listener/event-listener-opts'
import { Obfuscator } from '../util/obfuscate'
import { applyFnToProps } from '../util/traverse'
import { SharedContext } from '../context/shared-context'
import { VERSION } from '../constants/env'
import { isWorkerScope, isIE } from '../constants/runtime'
import { warn } from '../util/console'
import { TimeKeeper } from '../timing/time-keeper'

const warnings = {}

Expand Down Expand Up @@ -180,7 +180,7 @@ export class Harvest extends SharedContext {
encodeParam('v', VERSION),
transactionNameParam(info),
encodeParam('ct', runtime.customTransaction),
'&rst=' + now(),
'&rst=' + TimeKeeper.now(),
'&ck=0', // ck param DEPRECATED - still expected by backend
'&s=' + (runtime.session?.state.value || '0'), // the 0 id encaps all untrackable and default traffic
encodeParam('ref', ref),
Expand Down
1 change: 0 additions & 1 deletion src/common/timing/__mocks__/now.js

This file was deleted.

3 changes: 3 additions & 0 deletions src/common/timing/__mocks__/time-keeper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const TimeKeeper = jest.fn(function () {})
TimeKeeper.now = jest.fn(() => performance.now())
TimeKeeper.getTimeKeeperByAgentIdentifier = jest.fn(() => new TimeKeeper())
9 changes: 0 additions & 9 deletions src/common/timing/now.js

This file was deleted.

41 changes: 26 additions & 15 deletions src/common/timing/time-keeper.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { gosNREUM } from '../window/nreum'
import { getRuntime } from '../config/config'

/**
* Class used to adjust the timestamp of harvested data to New Relic server time. This
* is done by tracking the performance timings of the RUM call and applying a calculation
* to the harvested data event offset time.
*/
export class TimeKeeper {
#agent
/**
* Represents the browser origin time.
* @type {number}
*/
#originTime

/**
* Represents the browser origin time corrected to NR server time.
Expand All @@ -22,8 +25,9 @@
*/
#localTimeDiff

constructor (agent) {
this.#agent = agent
constructor (originTime) {
if (!originTime) throw new Error('TimeKeeper must be supplied a browser origin time.')
this.#originTime = originTime
}

static getTimeKeeperByAgentIdentifier (agentIdentifier) {
Expand All @@ -33,7 +37,20 @@
: undefined
}

get correctedPageOriginTime () {
/**
* Returns the current time offset from page origin.
* @return {number}
*/
static now () {
return Math.floor(performance.now())
}

get originTime () {
return this.#originTime

Check warning on line 49 in src/common/timing/time-keeper.js

View check run for this annotation

Codecov / codecov/patch

src/common/timing/time-keeper.js#L49

Added line #L49 was not covered by tests
}

get correctedOriginTime () {
if (!this.#correctedOriginTime) throw new Error('InvalidState: Access to correctedOriginTime attempted before NR time calculated.')
return this.#correctedOriginTime
}

Expand All @@ -54,7 +71,7 @@

// Corrected page origin time
this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset)
this.#localTimeDiff = getRuntime(this.#agent.agentIdentifier).offset - this.#correctedOriginTime
this.#localTimeDiff = this.#originTime - this.#correctedOriginTime

if (Number.isNaN(this.#correctedOriginTime)) {
throw new Error('Date header invalid format.')
Expand All @@ -65,9 +82,10 @@
* Converts a page origin relative time to an absolute timestamp
* corrected to NR server time.
* @param relativeTime {number} The relative time of the event in milliseconds
* @returns {number} The correct timestamp as a unix/epoch timestamp value
* @returns {number} Corrected unix/epoch timestamp
*/
convertRelativeTimestamp (relativeTime) {
if (!this.#correctedOriginTime) return this.originTime + relativeTime
return this.#correctedOriginTime + relativeTime
}

Expand All @@ -77,14 +95,7 @@
* @return {number} Corrected unix/epoch timestamp
*/
correctAbsoluteTimestamp (timestamp) {
if (!this.#localTimeDiff) return timestamp
return Math.floor(timestamp - this.#localTimeDiff)
}

/**
* Returns the current time offset from page origin.
* @return {number}
*/
now () {
return Math.floor(performance.now())
}
}
13 changes: 4 additions & 9 deletions src/common/timing/time-keeper.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { faker } from '@faker-js/faker'
import { TimeKeeper } from './time-keeper'
import * as configModule from '../config/config'

Expand All @@ -10,7 +9,6 @@ const endTime = 600

let localTime
let serverTime
let mockAgent
let runtimeConfig
let timeKeeper
beforeEach(() => {
Expand All @@ -21,16 +19,13 @@ beforeEach(() => {
now: localTime
})

mockAgent = {
agentIdentifier: faker.string.uuid()
}
runtimeConfig = {
offset: localTime
}

jest.spyOn(configModule, 'getRuntime').mockImplementation(() => runtimeConfig)

timeKeeper = new TimeKeeper(mockAgent)
timeKeeper = new TimeKeeper(Date.now())
})

describe('processRumRequest', () => {
Expand All @@ -41,7 +36,7 @@ describe('processRumRequest', () => {

timeKeeper.processRumRequest(mockRumRequest, startTime, endTime)

expect(timeKeeper.correctedPageOriginTime).toEqual(1706213060475)
expect(timeKeeper.correctedOriginTime).toEqual(1706213060475)
})

it('should calculate a newer corrected page origin', () => {
Expand All @@ -53,7 +48,7 @@ describe('processRumRequest', () => {

timeKeeper.processRumRequest(mockRumRequest, startTime, endTime)

expect(timeKeeper.correctedPageOriginTime).toEqual(1706213055475)
expect(timeKeeper.correctedOriginTime).toEqual(1706213055475)
})

it.each([undefined, null, 0])('should fallback to unprotected time values when responseStart is %s', (responseStart) => {
Expand All @@ -63,7 +58,7 @@ describe('processRumRequest', () => {

timeKeeper.processRumRequest(mockRumRequest, startTime, endTime)

expect(timeKeeper.correctedPageOriginTime).toEqual(1706213060475)
expect(timeKeeper.correctedOriginTime).toEqual(1706213060475)
})

it.each([null, undefined])('should throw an error when rumRequest is %s', (rumRequest) => {
Expand Down
4 changes: 2 additions & 2 deletions src/common/window/nreum.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { now } from '../timing/now'
import { globalScope } from '../constants/runtime'
import { TimeKeeper } from '../timing/time-keeper'

export const defaults = {
beacon: 'bam.nr-data.net',
Expand Down Expand Up @@ -71,7 +71,7 @@ export function setNREUMInitializedAgent (id, newAgentInstance) {
let nr = gosNREUM()
nr.initializedAgents ??= {}
newAgentInstance.initializedAt = {
ms: now(),
ms: TimeKeeper.now(),
date: new Date()
}
nr.initializedAgents[id] = newAgentInstance
Expand Down
3 changes: 2 additions & 1 deletion src/features/ajax/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class Aggregate extends AggregateBase {
let spaAjaxEvents = {}
let sentAjaxEvents = []
const ee = this.ee
const timeKeeper = this.timeKeeper

const harvestTimeSeconds = agentInit.ajax.harvestTimeSeconds || 10
const MAX_PAYLOAD_SIZE = agentInit.ajax.maxPayloadSize || 1000000
Expand Down Expand Up @@ -125,7 +126,7 @@ export class Aggregate extends AggregateBase {
if (xhrContext.dt) {
event.spanId = xhrContext.dt.spanId
event.traceId = xhrContext.dt.traceId
event.spanTimestamp = xhrContext.dt.timestamp
event.spanTimestamp = timeKeeper.correctAbsoluteTimestamp(xhrContext.dt.timestamp)
}

// parsed from the AJAX body, looking for operationName param & parsing query for operationType
Expand Down
18 changes: 9 additions & 9 deletions src/features/ajax/instrument/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { ffVersion, globalScope, isBrowserScope } from '../../../common/constants/runtime'
import { dataSize } from '../../../common/util/data-size'
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts'
import { now } from '../../../common/timing/now'
import { wrapFetch, wrapXhr } from '../../../common/wrap'
import { parseUrl } from '../../../common/url/parse-url'
import { DT } from './distributed-tracing'
Expand All @@ -17,6 +16,7 @@
import { FEATURE_NAME } from '../constants'
import { FEATURE_NAMES } from '../../../loaders/features/features'
import { SUPPORTABILITY_METRIC } from '../../metrics/constants'
import { TimeKeeper } from '../../../common/timing/time-keeper'

var handlers = ['load', 'error', 'abort', 'timeout']
var handlersLen = handlers.length
Expand Down Expand Up @@ -156,7 +156,7 @@
if (size) metrics.txSize = size
}

this.startTime = now()
this.startTime = TimeKeeper.now()

this.body = data

Expand Down Expand Up @@ -206,7 +206,7 @@
}

function onXhrResolved () {
this.endTime = now()
this.endTime = TimeKeeper.now()
}

// Listen for load listeners to be added to xhr objects
Expand All @@ -222,12 +222,12 @@
function onFnStart (args, xhr, methodName) {
if (xhr instanceof origXHR) {
if (methodName === 'onload') this.onload = true
if ((args[0] && args[0].type) === 'load' || this.onload) this.xhrCbStart = now()
if ((args[0] && args[0].type) === 'load' || this.onload) this.xhrCbStart = TimeKeeper.now()
}
}

function onFnEnd (args, xhr) {
if (this.xhrCbStart) ee.emit('xhr-cb-time', [now() - this.xhrCbStart, this.onload, xhr], xhr)
if (this.xhrCbStart) ee.emit('xhr-cb-time', [TimeKeeper.now() - this.xhrCbStart, this.onload, xhr], xhr)
}

// this event only handles DT
Expand Down Expand Up @@ -310,7 +310,7 @@
function onFetchStart (fetchArguments, dtPayload) {
this.params = {}
this.metrics = {}
this.startTime = now()
this.startTime = TimeKeeper.now()
this.dt = dtPayload

if (fetchArguments.length >= 1) this.target = fetchArguments[0]
Expand Down Expand Up @@ -340,7 +340,7 @@
// we capture failed call as status 0, the actual error is ignored
// eslint-disable-next-line handle-callback-err
function onFetchDone (_, res) {
this.endTime = now()
this.endTime = TimeKeeper.now()

Check warning on line 343 in src/features/ajax/instrument/index.js

View check run for this annotation

Codecov / codecov/patch

src/features/ajax/instrument/index.js#L343

Added line #L343 was not covered by tests
if (!this.params) {
this.params = {}
}
Expand All @@ -355,7 +355,7 @@
var metrics = {
txSize: this.txSize,
rxSize: responseSize,
duration: now() - this.startTime
duration: TimeKeeper.now() - this.startTime
}

handler('xhr', [this.params, metrics, this.startTime, this.endTime, 'fetch'], this, FEATURE_NAMES.ajax)
Expand All @@ -374,7 +374,7 @@
}

if (params.aborted) return
metrics.duration = now() - this.startTime
metrics.duration = TimeKeeper.now() - this.startTime

Check warning on line 377 in src/features/ajax/instrument/index.js

View check run for this annotation

Codecov / codecov/patch

src/features/ajax/instrument/index.js#L377

Added line #L377 was not covered by tests
if (!this.loadCaptureCalled && xhr.readyState === 4) {
captureXhrData(this, xhr)
} else if (params.status == null) {
Expand Down
7 changes: 4 additions & 3 deletions src/features/jserrors/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
import { handle } from '../../../common/event-emitter/handle'
import { mapOwn } from '../../../common/util/map-own'
import { getInfo, getConfigurationValue, getRuntime } from '../../../common/config/config'
import { now } from '../../../common/timing/now'
import { globalScope } from '../../../common/constants/runtime'

import { FEATURE_NAME } from '../constants'
import { FEATURE_NAMES } from '../../../loaders/features/features'
import { AggregateBase } from '../../utils/aggregate-base'
import { getNREUMInitializedAgent } from '../../../common/window/nreum'
import { deregisterDrain } from '../../../common/drain/drain'
import { TimeKeeper } from '../../../common/timing/time-keeper'

/**
* @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
Expand Down Expand Up @@ -135,7 +135,7 @@

storeError (err, time, internal, customAttributes) {
// are we in an interaction
time = time || now()
time = time || TimeKeeper.now()
const agentRuntime = getRuntime(this.agentIdentifier)
let filterOutput

Expand Down Expand Up @@ -173,7 +173,7 @@
if (!this.stackReported[bucketHash]) {
this.stackReported[bucketHash] = true
params.stack_trace = truncateSize(stackInfo.stackString)
this.observedAt[bucketHash] = agentRuntime.offset + time
this.observedAt[bucketHash] = this.timeKeeper.convertRelativeTimestamp(time)

Check warning on line 176 in src/features/jserrors/aggregate/index.js

View check run for this annotation

Codecov / codecov/patch

src/features/jserrors/aggregate/index.js#L176

Added line #L176 was not covered by tests
} else {
params.browser_stack_hash = stringHashCode(stackInfo.stackString)
}
Expand All @@ -191,6 +191,7 @@

if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true
params.firstOccurrenceTimestamp = this.observedAt[bucketHash]
params.timestamp = this.observedAt[bucketHash]

var type = internal ? 'ierr' : 'err'
var newMetrics = { time }
Expand Down
Loading
Loading