Skip to content

Commit 5ce9e70

Browse files
feat: Prevent early harvests when in retry period (#1548)
Co-authored-by: Chunwai Li <cli@newrelic.com>
1 parent 0bf771c commit 5ce9e70

File tree

3 files changed

+34
-7
lines changed

3 files changed

+34
-7
lines changed

src/features/utils/aggregate-base.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export class AggregateBase extends FeatureBase {
3535
/** @type {Boolean} indicates if custom attributes are combined in each event payload for size estimation purposes. this is set to true in derived classes that need to evaluate custom attributes separately from the event payload */
3636
this.customAttributesAreSeparate = false
3737
/** @type {Boolean} indicates if the feature can harvest early. This is set to false in derived classes that need to block early harvests, like ajax under certain conditions */
38-
this.canHarvestEarly = true // this is set to false in derived classes that need to block early harvests, like ajax under certain conditions
38+
this.canHarvestEarly = true
39+
/** @type {Boolean} indicates if the feature is actively in a retry deferral period */
40+
this.isRetrying = false
3941

4042
this.harvestOpts = {} // features aggregate classes can define custom opts for when their harvest is called
4143

@@ -82,7 +84,7 @@ export class AggregateBase extends FeatureBase {
8284
* @returns void
8385
*/
8486
decideEarlyHarvest () {
85-
if (!this.canHarvestEarly) return
87+
if (!this.canHarvestEarly || this.blocked || this.isRetrying) return
8688
const estimatedSize = this.events.byteSize() + (this.customAttributesAreSeparate ? this.agentRef.runtime.jsAttributesMetadata.bytes : 0)
8789
if (estimatedSize > IDEAL_PAYLOAD_SIZE) {
8890
this.agentRef.runtime.harvester.triggerHarvestFor(this)
@@ -169,8 +171,8 @@ export class AggregateBase extends FeatureBase {
169171
* @param {boolean=} result.retry - whether the harvest should be retried
170172
*/
171173
postHarvestCleanup (result = {}) {
172-
const harvestFailed = result.sent && result.retry
173-
if (harvestFailed) this.events.reloadSave(this.harvestOpts, result.targetApp?.entityGuid)
174+
this.isRetrying = result.sent && result.retry
175+
if (this.isRetrying) this.events.reloadSave(this.harvestOpts, result.targetApp?.entityGuid)
174176
this.events.clearSave(this.harvestOpts, result.targetApp?.entityGuid)
175177
}
176178

tests/assets/harvest-early-block-internal.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
newrelic.setCustomAttribute("foo", "bar".repeat(16000));
2020
newrelic.addPageAction("test");
2121
newrelic.log("test");
22-
2322
window.sendAjax = function () {
2423
fetch("https://pokeapi.co/api/v2/pokemon/moltres");
2524
};

tests/specs/harvesting/harvest-early.e2e.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import { testAjaxEventsRequest, testInsRequest, testInteractionEventsRequest, testLogsRequest, testBlobTraceRequest } from '../../../tools/testing-server/utils/expect-tests'
1+
import { testAjaxEventsRequest, testInsRequest, testInteractionEventsRequest, testLogsRequest, testMetricsRequest, testBlobTraceRequest } from '../../../tools/testing-server/utils/expect-tests'
22

33
describe('should harvest early', () => {
44
let ajaxEventsCapture
55
let insightsCapture
66
let interactionEventsCapture
77
let loggingEventsCapture
8+
let metricsCapture
89
let testBlobTraceCapture
910

1011
beforeEach(async () => {
11-
;[ajaxEventsCapture, insightsCapture, interactionEventsCapture, loggingEventsCapture, testBlobTraceCapture] = await browser.testHandle.createNetworkCaptures('bamServer', [
12+
[ajaxEventsCapture, insightsCapture, interactionEventsCapture, loggingEventsCapture, metricsCapture, testBlobTraceCapture] = await browser.testHandle.createNetworkCaptures('bamServer', [
1213
{ test: testAjaxEventsRequest },
1314
{ test: testInsRequest },
1415
{ test: testInteractionEventsRequest },
1516
{ test: testLogsRequest },
17+
{ test: testMetricsRequest },
1618
{ test: testBlobTraceRequest }
1719
])
1820
})
@@ -36,6 +38,30 @@ describe('should harvest early', () => {
3638
expect(Date.now() - timeStart).toBeLessThan(30000) // should have harvested early before 30 seconds
3739
})
3840

41+
it('should NOT re-attempt to harvest early when rate limited', async () => {
42+
await browser.testHandle.scheduleReply('bamServer', {
43+
test: testInsRequest,
44+
statusCode: 429
45+
})
46+
47+
await browser.url(await browser.testHandle.assetURL('harvest-early-block-internal.html', { init: { harvest: { interval: 30 } } }))
48+
.then(() => browser.waitForAgentLoad())
49+
50+
/** not harvesting early in retry mode */
51+
const [smHarvest] = await Promise.all([
52+
metricsCapture.waitForResult({ totalCount: 1 }),
53+
browser.execute(function () {
54+
newrelic.addPageAction('test')
55+
})
56+
.then(() => browser.pause(10000))
57+
.then(() => browser.refresh())
58+
])
59+
60+
const insHarvestEarlySeen = smHarvest[0].request.body.sm.find(sm => sm.params.name === 'generic_events/Harvest/Early/Seen')
61+
// count (c) only exists if the same label is called more than once. It should have only early harvested once (on page load), which caused it to be denied by 429 by the scheduleReply. It should NOT try to early harvest twice since it is in retry mode.
62+
expect(insHarvestEarlySeen.stats.c).toBeUndefined()
63+
})
64+
3965
/** if we track internal and spawn early requests, we can potentially create a feedback loop that goes on forever with large ajax requests describing themselves */
4066
it('should not harvest AJAX early when agent is tracking internal calls', async () => {
4167
await browser.url(await browser.testHandle.assetURL('harvest-early.html'))

0 commit comments

Comments
 (0)