Skip to content

Commit 2081a12

Browse files
committed
Fix metrics harvest on session reset before drain & autoStart considerations in harvester
1 parent 0e7ea85 commit 2081a12

File tree

5 files changed

+26
-9
lines changed

5 files changed

+26
-9
lines changed

docs/warning-codes.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,6 @@
9292
### 45
9393
`An internal agent process failed to execute.`
9494
### 46
95-
`A reserved eventType was provided to recordCustomEvent(...) -- The event was not recorded.`
95+
`A reserved eventType was provided to recordCustomEvent(...) -- The event was not recorded.`
96+
### 47
97+
`A feature was marked for auto starting but has still not started importing its aggregate.`

src/common/harvest/harvester.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ export class Harvester {
1515
constructor (agentRef) {
1616
this.agentRef = agentRef
1717

18-
const featuresInstruments = Object.values(agentRef.features)
19-
Promise.all(featuresInstruments.map(feature => feature.onAggregateImported)).then(loadedSuccessfullyArr => {
18+
const autoFeaturesInstruments = Object.values(agentRef.features).filter(feature => feature.auto) // features without autoStart won't be fully initialized until later
19+
const importingFeatures = []
20+
// Since the harvester is expected to be initialized by PVE Aggregate module, at such time, all the features should be awaiting import though featAggregate may not be available just yet.
21+
for (const feature of autoFeaturesInstruments) {
22+
if (!feature.onAggregateImported) warn(47, feature.featureName)
23+
else importingFeatures.push(feature)
24+
}
25+
Promise.all(importingFeatures.map(instrument => instrument.onAggregateImported)).then(loadedSuccessfullyArr => {
2026
// Double check that all aggregates have been initialized, successfully or not, before starting harvest schedule, which only queries the succesfully loaded ones.
21-
const featuresToHarvest = featuresInstruments.filter((instrumentInstance, index) => loadedSuccessfullyArr[index])
27+
const featuresToHarvest = importingFeatures.filter((instrumentInstance, index) => loadedSuccessfullyArr[index])
2228
this.initializedAggregates = featuresToHarvest.map(instrumentInstance => instrumentInstance.featAggregate)
2329
this.#startTimer(agentRef.init.harvest.interval)
2430
})

src/features/metrics/aggregate/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ export class Aggregate extends AggregateBase {
1919

2020
this.waitForFlags(['err']).then(([errFlag]) => {
2121
if (errFlag) {
22-
this.singleChecks() // checks that are run only one time, at script load
23-
this.eachSessionChecks() // the start of every time user engages with page
2422
this.drain()
2523
} else {
2624
this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
@@ -31,8 +29,13 @@ export class Aggregate extends AggregateBase {
3129
// Allow features external to the metrics feature to capture SMs and CMs through the event emitter
3230
registerHandler(SUPPORTABILITY_METRIC_CHANNEL, this.storeSupportabilityMetrics.bind(this), this.featureName, this.ee)
3331
registerHandler(CUSTOM_METRIC_CHANNEL, this.storeEventMetrics.bind(this), this.featureName, this.ee)
32+
33+
this.singleChecks() // checks that are run only one time, at script load
34+
this.eachSessionChecks() // the start of every time user engages with page
3435
}
3536

37+
preHarvestChecks () { return this.drained } // only allow any metrics to be sent if we know for sure it has gotten the go-ahead RUM flag
38+
3639
storeSupportabilityMetrics (name, value) {
3740
if (this.blocked) return
3841
const type = SUPPORTABILITY_METRIC

src/features/utils/instrument-base.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export class InstrumentBase extends FeatureBase {
4343
/**
4444
* @type {Promise} Assigned immediately after @see importAggregator runs. Serves as a signal for when the inner async fn finishes execution. Useful for features to await
4545
* one another if there are inter-features dependencies.
46-
* TODO: This is only used for the SPA feature component tests and should be refactored out.
4746
*/
4847
this.onAggregateImported = undefined
4948

@@ -58,6 +57,9 @@ export class InstrumentBase extends FeatureBase {
5857
registerDrain(agentRef.agentIdentifier, this.featureName)
5958
this.auto = true
6059
this.importAggregator(agentRef)
60+
this.onAggregateImported.then(loaded => { // "subscribe" the feature aggregate to harvest interval when it's ready
61+
if (loaded) agentRef.runtime.harvester.initializedAggregates.push(this.featAggregate)
62+
})
6163
}))
6264
}
6365
}

tests/unit/features/utils/instrument-base.test.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ test('should construct a new instrument', () => {
5454
expect(registerDrain).toHaveBeenCalledWith(agentIdentifier, featureName)
5555
})
5656

57-
test('should wait for feature opt-in to import the aggregate', () => {
57+
test('should wait for feature opt-in to import the aggregate', async () => {
5858
const instrument = new InstrumentBase(agentBase, featureName, false)
59-
jest.spyOn(instrument, 'importAggregator').mockImplementation(jest.fn)
59+
agentBase.runtime = { harvester: { initializedAggregates: [] } }
60+
jest.spyOn(instrument, 'importAggregator').mockImplementation(() => { instrument.onAggregateImported = Promise.resolve(true) })
6061

6162
expect(registerDrain).not.toHaveBeenCalled()
6263
expect(instrument.auto).toEqual(false)
@@ -67,6 +68,9 @@ test('should wait for feature opt-in to import the aggregate', () => {
6768
expect(registerDrain).toHaveBeenCalledWith(agentIdentifier, featureName)
6869
expect(instrument.importAggregator).toHaveBeenCalledTimes(1)
6970
expect(instrument.auto).toEqual(true)
71+
72+
await new Promise(process.nextTick)
73+
expect(agentBase.runtime.harvester.initializedAggregates).toContain(instrument.featAggregate)
7074
})
7175

7276
test('should import aggregator on window load', async () => {

0 commit comments

Comments
 (0)