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

fix: Make Metrics harvest only on EoL for new Harvester #1311

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/common/harvest/harvester.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class Harvester {
const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === xhrMethod // always retry all features harvests except for final
let dataToSendArr; let ranSend = false
if (!localOpts.directSend) { // primarily used by rum call to bypass makeHarvestPayload by providing payload directly
dataToSendArr = aggregateInst.makeHarvestPayload(shouldRetryOnFail) // be sure the 'this' of makeHarvestPayload is the aggregate w/ access to its harvestOpts
dataToSendArr = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts) // be sure the 'this' of makeHarvestPayload is the aggregate w/ access to its harvestOpts
if (!dataToSendArr) return false // can be undefined if storage is empty or preharvest checks failed
} else dataToSendArr = [localOpts.directSend]

Expand Down
2 changes: 1 addition & 1 deletion src/features/metrics/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class Aggregate extends AggregateBase {
this.eachSessionChecks() // the start of every time user engages with page
}

preHarvestChecks () { return this.drained } // only allow any metrics to be sent if we know for sure it has gotten the go-ahead RUM flag
preHarvestChecks (opts) { return this.drained && opts.isFinalHarvest } // only allow any metrics to be sent after we get the right RUM flag and only on EoL

storeSupportabilityMetrics (name, value) {
if (this.blocked) return
Expand Down
14 changes: 7 additions & 7 deletions src/features/utils/aggregate-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,18 @@ export class AggregateBase extends FeatureBase {
/**
* Return harvest payload. A "serializer" function can be defined on a derived class to format the payload.
* @param {Boolean} shouldRetryOnFail - harvester flag to backup payload for retry later if harvest request fails; this should be moved to harvester logic
* @param {object|undefined} target - the target app passed onto the event store manager to determine which app's data to return; if none provided, all apps data will be returned
* @param {object|undefined} opts.target - the target app passed onto the event store manager to determine which app's data to return; if none provided, all apps data will be returned
* @returns {Array} Final payload tagged with their targeting browser app. The value of `payload` can be undefined if there are no pending events for an app. This should be a minimum length of 1.
*/
makeHarvestPayload (shouldRetryOnFail = false, target) {
if (this.events.isEmpty(this.harvestOpts, target)) return
makeHarvestPayload (shouldRetryOnFail = false, opts = {}) {
if (this.events.isEmpty(this.harvestOpts, opts.target)) return
// Other conditions and things to do when preparing harvest that is required.
if (this.preHarvestChecks && !this.preHarvestChecks()) return
if (this.preHarvestChecks && !this.preHarvestChecks(opts)) return

if (shouldRetryOnFail) this.events.save(this.harvestOpts, target)
const returnedDataArr = this.events.get(this.harvestOpts, target)
if (shouldRetryOnFail) this.events.save(this.harvestOpts, opts.target)
const returnedDataArr = this.events.get(this.harvestOpts, opts.target)
if (!returnedDataArr.length) throw new Error('Unexpected problem encountered. There should be at least one app for harvest!')
this.events.clear(this.harvestOpts, target)
this.events.clear(this.harvestOpts, opts.target)

return returnedDataArr.map(({ targetApp, data }) => {
// A serializer or formatter assists in creating the payload `body` from stored events on harvest when defined by derived feature class.
Expand Down
19 changes: 14 additions & 5 deletions tests/components/metrics/aggregate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ afterEach(() => {
test(`${name} with no value creates a metric with just a count`, () => {
createAndStoreMetric(undefined, isSupportability)

const records = metricsAggregate.events.get([type])[type]
const records = metricsAggregate.events.get({ aggregatorTypes: [type] })[0].data[type]
.filter(x => x?.params?.name === metricName)
expect(records.length).toEqual(1)

Expand All @@ -47,7 +47,7 @@ afterEach(() => {
createAndStoreMetric(undefined, isSupportability)
createAndStoreMetric(undefined, isSupportability)

const records = metricsAggregate.events.get([type])[type]
const records = metricsAggregate.events.get({ aggregatorTypes: [type] })[0].data[type]
.filter(x => x?.params?.name === metricName)
expect(records.length).toEqual(1)

Expand All @@ -62,7 +62,7 @@ afterEach(() => {
test(`${name} with a value ${auxDescription}`, () => {
createAndStoreMetric(isSupportability ? 500 : { time: 500 }, isSupportability)

const records = metricsAggregate.events.get([type])[type]
const records = metricsAggregate.events.get({ aggregatorTypes: [type] })[0].data[type]
.filter(x => x?.params?.name === metricName)
expect(records.length).toEqual(1)

Expand All @@ -80,7 +80,7 @@ afterEach(() => {
.fill(null).map(() => faker.number.int({ min: 100, max: 1000 }))
values.forEach(v => createAndStoreMetric(isSupportability ? v : { time: v }, isSupportability))

const records = metricsAggregate.events.get([type])[type]
const records = metricsAggregate.events.get({ aggregatorTypes: [type] })[0].data[type]
.filter(x => x?.params?.name === metricName)
expect(records.length).toEqual(1)

Expand Down Expand Up @@ -108,7 +108,7 @@ afterEach(() => {
test('storeEvent (custom) with an invalid value type does not create a named metric object in metrics section', () => {
createAndStoreMetric(faker.number.float(), false)

const records = metricsAggregate.events.get([CUSTOM_METRIC])[CUSTOM_METRIC]
const records = metricsAggregate.events.get({ aggregatorTypes: [CUSTOM_METRIC] })[0].data[CUSTOM_METRIC]
.filter(x => x?.params?.name === metricName)
expect(records.length).toEqual(1)

Expand All @@ -118,6 +118,15 @@ afterEach(() => {
}
})

test('Metrics does not harvest on interval, only on EoL', () => {
createAndStoreMetric(undefined, true)
expect(metricsAggregate.events.isEmpty(metricsAggregate.harvestOpts)).toEqual(false) // double check so that harvest should proceed
metricsAggregate.drained = true // this is a requisite for harvest in preHarvestChecks

expect(mainAgent.runtime.harvester.triggerHarvestFor(metricsAggregate)).toEqual(false) // mimics what the harveseter does on interval
expect(mainAgent.runtime.harvester.triggerHarvestFor(metricsAggregate, { isFinalHarvest: true })).toEqual(true) // mimics what the harveseter does on EoL
})

function createAndStoreMetric (value, isSupportability) {
const method = isSupportability ? (...args) => metricsAggregate.storeSupportabilityMetrics(...args) : (...args) => metricsAggregate.storeEventMetrics(...args)
method(metricName, value)
Expand Down
4 changes: 2 additions & 2 deletions tests/components/setup-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { ee } from '../../src/common/event-emitter/contextual-ee'
import { TimeKeeper } from '../../src/common/timing/time-keeper'
import { getRuntime } from '../../src/common/config/runtime'
import { setupAgentSession } from '../../src/features/utils/agent-session'
import { EventAggregator } from '../../src/common/aggregate/event-aggregator'
import { Harvester } from '../../src/common/harvest/harvester'
import { EventStoreManager } from '../../src/features/utils/event-store-manager'

/**
* Sets up a new agent for component testing. This should be called only
Expand Down Expand Up @@ -39,7 +39,7 @@ export function setupAgent ({ agentOverrides = {}, info = {}, init = {}, loaderC
const fakeAgent = {
agentIdentifier,
ee: eventEmitter,
sharedAggregator: new EventAggregator(),
sharedAggregator: new EventStoreManager({ licenseKey: info.licenseKey, appId: info.applicationID }, 2),
...agentOverrides
}
setNREUMInitializedAgent(agentIdentifier, fakeAgent)
Expand Down
Loading