@newrelic/browser-agent 1.323.0-rc.11 → 1.323.0-rc.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common/constants/agent-constants.js +5 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/util/attribute-size.js +5 -2
- package/dist/cjs/features/utils/aggregate-base.js +10 -1
- package/dist/esm/common/constants/agent-constants.js +4 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/util/attribute-size.js +5 -2
- package/dist/esm/features/utils/aggregate-base.js +11 -2
- package/dist/types/common/constants/agent-constants.d.ts +4 -0
- package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
- package/dist/types/common/util/attribute-size.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +5 -1
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/constants/agent-constants.js +4 -0
- package/src/common/util/attribute-size.js +5 -2
- package/src/features/utils/aggregate-base.js +11 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.SUPPORTS_REGISTERED_ENTITIES = exports.SESSION_ERROR = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.DEFAULT_KEY = void 0;
|
|
6
|
+
exports.SUPPORTS_REGISTERED_ENTITIES = exports.SESSION_ERROR = exports.MIN_EARLY_HARVEST_INTERVAL = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.EARLY_HARVEST_BYPASS_SIZE = exports.DEFAULT_KEY = void 0;
|
|
7
7
|
var _features = require("../../loaders/features/features");
|
|
8
8
|
/**
|
|
9
9
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
@@ -12,6 +12,10 @@ var _features = require("../../loaders/features/features");
|
|
|
12
12
|
|
|
13
13
|
const IDEAL_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = 16000;
|
|
14
14
|
const MAX_PAYLOAD_SIZE = exports.MAX_PAYLOAD_SIZE = 1000000;
|
|
15
|
+
/** The minimum time (ms) between early harvests of a single feature. Data that arrives inside this window stays buffered for the next harvest. */
|
|
16
|
+
const MIN_EARLY_HARVEST_INTERVAL = exports.MIN_EARLY_HARVEST_INTERVAL = 500;
|
|
17
|
+
/** The estimated payload size at which a feature may harvest early even inside the minimum interval, so that a flood of data cannot fill the buffer to MAX_PAYLOAD_SIZE (and start dropping events) while waiting. */
|
|
18
|
+
const EARLY_HARVEST_BYPASS_SIZE = exports.EARLY_HARVEST_BYPASS_SIZE = MAX_PAYLOAD_SIZE / 2;
|
|
15
19
|
const DEFAULT_KEY = exports.DEFAULT_KEY = 'NR_CONTAINER_AGENT';
|
|
16
20
|
const SESSION_ERROR = exports.SESSION_ERROR = 'SESSION_ERROR';
|
|
17
21
|
const SUPPORTS_REGISTERED_ENTITIES = exports.SUPPORTS_REGISTERED_ENTITIES = {
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.323.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.323.0-rc.12";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.323.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.323.0-rc.12";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.trackObjectAttributeSize = trackObjectAttributeSize;
|
|
7
7
|
var _stringify = require("./stringify");
|
|
8
8
|
/**
|
|
9
|
-
* Copyright 2020-
|
|
9
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
10
10
|
* SPDX-License-Identifier: Apache-2.0
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -18,12 +18,15 @@ function trackObjectAttributeSize(parent, object) {
|
|
|
18
18
|
// proxy attribute to calculate its size when changed
|
|
19
19
|
parent[object] = new Proxy(originalAttribute, {
|
|
20
20
|
set(target, prop, value) {
|
|
21
|
+
/* An overwrite replaces the existing entry, so its previous size must be removed first. Otherwise repeated sets of the same key inflate the count far beyond the real size. */
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) output.bytes -= prop.length + (0, _stringify.stringify)(target[prop]).length;
|
|
21
23
|
output.bytes += prop.length + (0, _stringify.stringify)(value).length;
|
|
22
24
|
target[prop] = value;
|
|
23
25
|
return true;
|
|
24
26
|
},
|
|
25
27
|
deleteProperty(target, prop) {
|
|
26
|
-
|
|
28
|
+
/* Deleting a key that was never set removes nothing, so the count must not change. */
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) output.bytes -= prop.length + (0, _stringify.stringify)(target[prop]).length;
|
|
27
30
|
return delete target[prop];
|
|
28
31
|
}
|
|
29
32
|
});
|
|
@@ -13,6 +13,7 @@ var _constants = require("../metrics/constants");
|
|
|
13
13
|
var _eventAggregator = require("../../common/aggregate/event-aggregator");
|
|
14
14
|
var _agentConstants = require("../../common/constants/agent-constants");
|
|
15
15
|
var _console = require("../../common/util/console");
|
|
16
|
+
var _now = require("../../common/timing/now");
|
|
16
17
|
/**
|
|
17
18
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
18
19
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -33,6 +34,8 @@ class AggregateBase extends _featureBase.FeatureBase {
|
|
|
33
34
|
this.canHarvestEarly = true;
|
|
34
35
|
/** @type {Boolean} indicates if the feature is actively in a retry deferral period */
|
|
35
36
|
this.isRetrying = false;
|
|
37
|
+
/** @type {number|undefined} the timestamp (ms, relative to page start) of the last early harvest triggered by this feature, if any */
|
|
38
|
+
this.lastEarlyHarvestAt = undefined;
|
|
36
39
|
this.harvestOpts = {}; // features aggregate classes can define custom opts for when their harvest is called
|
|
37
40
|
|
|
38
41
|
this.#setupEventStore();
|
|
@@ -88,13 +91,19 @@ class AggregateBase extends _featureBase.FeatureBase {
|
|
|
88
91
|
|
|
89
92
|
/**
|
|
90
93
|
* Evaluates whether a harvest should be made early by estimating the size of the current payload. Currently, this only happens if the event storage is EventBuffer, since that triggers this method directly.
|
|
91
|
-
* If conditions are met, a new harvest will be triggered immediately.
|
|
94
|
+
* If conditions are met, a new harvest will be triggered immediately, unless this feature already harvested early within the last `MIN_EARLY_HARVEST_INTERVAL` ms.
|
|
95
|
+
* Data held back by that limit stays buffered until the next early, interval, or final harvest. Once the estimated payload reaches `EARLY_HARVEST_BYPASS_SIZE`
|
|
96
|
+
* the limit no longer applies, so a flood of data cannot fill the buffer to its maximum size, where new events would be dropped, while waiting.
|
|
92
97
|
* @returns void
|
|
93
98
|
*/
|
|
94
99
|
decideEarlyHarvest() {
|
|
95
100
|
if (!this.canHarvestEarly || this.blocked || this.isRetrying) return;
|
|
96
101
|
const estimatedSize = this.events.byteSize() + (this.customAttributesAreSeparate ? this.agentRef.runtime.jsAttributesMetadata.bytes : 0);
|
|
97
102
|
if (estimatedSize > _agentConstants.IDEAL_PAYLOAD_SIZE) {
|
|
103
|
+
const currentTime = (0, _now.now)();
|
|
104
|
+
/* Bounds the request rate when a page produces data in a flood, regardless of how fast or whether requests complete. Large payloads skip the limit to avoid dropping data. */
|
|
105
|
+
if (estimatedSize < _agentConstants.EARLY_HARVEST_BYPASS_SIZE && this.lastEarlyHarvestAt !== undefined && currentTime - this.lastEarlyHarvestAt < _agentConstants.MIN_EARLY_HARVEST_INTERVAL) return;
|
|
106
|
+
this.lastEarlyHarvestAt = currentTime;
|
|
98
107
|
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
99
108
|
this.reportSupportabilityMetric("".concat(this.featureName, "/Harvest/Early/Seen"), estimatedSize);
|
|
100
109
|
}
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
import { FEATURE_NAMES } from '../../loaders/features/features';
|
|
6
6
|
export const IDEAL_PAYLOAD_SIZE = 16000;
|
|
7
7
|
export const MAX_PAYLOAD_SIZE = 1000000;
|
|
8
|
+
/** The minimum time (ms) between early harvests of a single feature. Data that arrives inside this window stays buffered for the next harvest. */
|
|
9
|
+
export const MIN_EARLY_HARVEST_INTERVAL = 500;
|
|
10
|
+
/** The estimated payload size at which a feature may harvest early even inside the minimum interval, so that a flood of data cannot fill the buffer to MAX_PAYLOAD_SIZE (and start dropping events) while waiting. */
|
|
11
|
+
export const EARLY_HARVEST_BYPASS_SIZE = MAX_PAYLOAD_SIZE / 2;
|
|
8
12
|
export const DEFAULT_KEY = 'NR_CONTAINER_AGENT';
|
|
9
13
|
export const SESSION_ERROR = 'SESSION_ERROR';
|
|
10
14
|
export const SUPPORTS_REGISTERED_ENTITIES = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { stringify } from './stringify';
|
|
@@ -11,12 +11,15 @@ export function trackObjectAttributeSize(parent, object) {
|
|
|
11
11
|
// proxy attribute to calculate its size when changed
|
|
12
12
|
parent[object] = new Proxy(originalAttribute, {
|
|
13
13
|
set(target, prop, value) {
|
|
14
|
+
/* An overwrite replaces the existing entry, so its previous size must be removed first. Otherwise repeated sets of the same key inflate the count far beyond the real size. */
|
|
15
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) output.bytes -= prop.length + stringify(target[prop]).length;
|
|
14
16
|
output.bytes += prop.length + stringify(value).length;
|
|
15
17
|
target[prop] = value;
|
|
16
18
|
return true;
|
|
17
19
|
},
|
|
18
20
|
deleteProperty(target, prop) {
|
|
19
|
-
|
|
21
|
+
/* Deleting a key that was never set removes nothing, so the count must not change. */
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) output.bytes -= prop.length + stringify(target[prop]).length;
|
|
20
23
|
return delete target[prop];
|
|
21
24
|
}
|
|
22
25
|
});
|
|
@@ -9,8 +9,9 @@ import { EventBuffer } from './event-buffer';
|
|
|
9
9
|
import { handle } from '../../common/event-emitter/handle';
|
|
10
10
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../metrics/constants';
|
|
11
11
|
import { EventAggregator } from '../../common/aggregate/event-aggregator';
|
|
12
|
-
import { MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE, SUPPORTS_REGISTERED_ENTITIES } from '../../common/constants/agent-constants';
|
|
12
|
+
import { MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE, MIN_EARLY_HARVEST_INTERVAL, EARLY_HARVEST_BYPASS_SIZE, SUPPORTS_REGISTERED_ENTITIES } from '../../common/constants/agent-constants';
|
|
13
13
|
import { warn } from '../../common/util/console';
|
|
14
|
+
import { now } from '../../common/timing/now';
|
|
14
15
|
export class AggregateBase extends FeatureBase {
|
|
15
16
|
/**
|
|
16
17
|
* Create an AggregateBase instance.
|
|
@@ -26,6 +27,8 @@ export class AggregateBase extends FeatureBase {
|
|
|
26
27
|
this.canHarvestEarly = true;
|
|
27
28
|
/** @type {Boolean} indicates if the feature is actively in a retry deferral period */
|
|
28
29
|
this.isRetrying = false;
|
|
30
|
+
/** @type {number|undefined} the timestamp (ms, relative to page start) of the last early harvest triggered by this feature, if any */
|
|
31
|
+
this.lastEarlyHarvestAt = undefined;
|
|
29
32
|
this.harvestOpts = {}; // features aggregate classes can define custom opts for when their harvest is called
|
|
30
33
|
|
|
31
34
|
this.#setupEventStore();
|
|
@@ -81,13 +84,19 @@ export class AggregateBase extends FeatureBase {
|
|
|
81
84
|
|
|
82
85
|
/**
|
|
83
86
|
* Evaluates whether a harvest should be made early by estimating the size of the current payload. Currently, this only happens if the event storage is EventBuffer, since that triggers this method directly.
|
|
84
|
-
* If conditions are met, a new harvest will be triggered immediately.
|
|
87
|
+
* If conditions are met, a new harvest will be triggered immediately, unless this feature already harvested early within the last `MIN_EARLY_HARVEST_INTERVAL` ms.
|
|
88
|
+
* Data held back by that limit stays buffered until the next early, interval, or final harvest. Once the estimated payload reaches `EARLY_HARVEST_BYPASS_SIZE`
|
|
89
|
+
* the limit no longer applies, so a flood of data cannot fill the buffer to its maximum size, where new events would be dropped, while waiting.
|
|
85
90
|
* @returns void
|
|
86
91
|
*/
|
|
87
92
|
decideEarlyHarvest() {
|
|
88
93
|
if (!this.canHarvestEarly || this.blocked || this.isRetrying) return;
|
|
89
94
|
const estimatedSize = this.events.byteSize() + (this.customAttributesAreSeparate ? this.agentRef.runtime.jsAttributesMetadata.bytes : 0);
|
|
90
95
|
if (estimatedSize > IDEAL_PAYLOAD_SIZE) {
|
|
96
|
+
const currentTime = now();
|
|
97
|
+
/* Bounds the request rate when a page produces data in a flood, regardless of how fast or whether requests complete. Large payloads skip the limit to avoid dropping data. */
|
|
98
|
+
if (estimatedSize < EARLY_HARVEST_BYPASS_SIZE && this.lastEarlyHarvestAt !== undefined && currentTime - this.lastEarlyHarvestAt < MIN_EARLY_HARVEST_INTERVAL) return;
|
|
99
|
+
this.lastEarlyHarvestAt = currentTime;
|
|
91
100
|
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
92
101
|
this.reportSupportabilityMetric("".concat(this.featureName, "/Harvest/Early/Seen"), estimatedSize);
|
|
93
102
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export const IDEAL_PAYLOAD_SIZE: 16000;
|
|
2
2
|
export const MAX_PAYLOAD_SIZE: 1000000;
|
|
3
|
+
/** The minimum time (ms) between early harvests of a single feature. Data that arrives inside this window stays buffered for the next harvest. */
|
|
4
|
+
export const MIN_EARLY_HARVEST_INTERVAL: 500;
|
|
5
|
+
/** The estimated payload size at which a feature may harvest early even inside the minimum interval, so that a flood of data cannot fill the buffer to MAX_PAYLOAD_SIZE (and start dropping events) while waiting. */
|
|
6
|
+
export const EARLY_HARVEST_BYPASS_SIZE: number;
|
|
3
7
|
export const DEFAULT_KEY: "NR_CONTAINER_AGENT";
|
|
4
8
|
export const SESSION_ERROR: "SESSION_ERROR";
|
|
5
9
|
export const SUPPORTS_REGISTERED_ENTITIES: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-constants.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/agent-constants.js"],"names":[],"mappings":"AAMA,iCAAkC,KAAK,CAAA;AACvC,+BAAgC,OAAO,CAAA;AACvC,0BAA2B,oBAAoB,CAAA;AAC/C,4BAA6B,eAAe,CAAA;AAE5C;IACE,CAAC,aAAa,CAAC,OAAO,CAAC,UAAM;IAC7B,CAAC,aAAa,CAAC,aAAa,CAAC,UAAM;IACnC,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAM;IAC9B,CAAC,aAAa,CAAC,IAAI,CAAC,UAAM;EAC3B;
|
|
1
|
+
{"version":3,"file":"agent-constants.d.ts","sourceRoot":"","sources":["../../../../src/common/constants/agent-constants.js"],"names":[],"mappings":"AAMA,iCAAkC,KAAK,CAAA;AACvC,+BAAgC,OAAO,CAAA;AACvC,kJAAkJ;AAClJ,yCAA0C,GAAG,CAAA;AAC7C,sNAAsN;AACtN,+CAA6D;AAC7D,0BAA2B,oBAAoB,CAAA;AAC/C,4BAA6B,eAAe,CAAA;AAE5C;IACE,CAAC,aAAa,CAAC,OAAO,CAAC,UAAM;IAC7B,CAAC,aAAa,CAAC,aAAa,CAAC,UAAM;IACnC,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAM;IAC9B,CAAC,aAAa,CAAC,IAAI,CAAC,UAAM;EAC3B;8BAhB6B,iCAAiC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attribute-size.d.ts","sourceRoot":"","sources":["../../../../src/common/util/attribute-size.js"],"names":[],"mappings":"AAMA;;
|
|
1
|
+
{"version":3,"file":"attribute-size.d.ts","sourceRoot":"","sources":["../../../../src/common/util/attribute-size.js"],"names":[],"mappings":"AAMA;;EAoBC"}
|
|
@@ -11,6 +11,8 @@ export class AggregateBase extends FeatureBase {
|
|
|
11
11
|
canHarvestEarly: boolean;
|
|
12
12
|
/** @type {Boolean} indicates if the feature is actively in a retry deferral period */
|
|
13
13
|
isRetrying: boolean;
|
|
14
|
+
/** @type {number|undefined} the timestamp (ms, relative to page start) of the last early harvest triggered by this feature, if any */
|
|
15
|
+
lastEarlyHarvestAt: number | undefined;
|
|
14
16
|
harvestOpts: {};
|
|
15
17
|
events: any;
|
|
16
18
|
/** @type {Boolean} indicates if the feature supports registered entities and the harvest requirements therein. Also read by getter "harvestEndpointVersion". Controlled by feature flag in pre-release phase. */
|
|
@@ -25,7 +27,9 @@ export class AggregateBase extends FeatureBase {
|
|
|
25
27
|
drained: boolean | undefined;
|
|
26
28
|
/**
|
|
27
29
|
* Evaluates whether a harvest should be made early by estimating the size of the current payload. Currently, this only happens if the event storage is EventBuffer, since that triggers this method directly.
|
|
28
|
-
* If conditions are met, a new harvest will be triggered immediately.
|
|
30
|
+
* If conditions are met, a new harvest will be triggered immediately, unless this feature already harvested early within the last `MIN_EARLY_HARVEST_INTERVAL` ms.
|
|
31
|
+
* Data held back by that limit stays buffered until the next early, interval, or final harvest. Once the estimated payload reaches `EARLY_HARVEST_BYPASS_SIZE`
|
|
32
|
+
* the limit no longer applies, so a flood of data cannot fill the buffer to its maximum size, where new events would be dropped, while waiting.
|
|
29
33
|
* @returns void
|
|
30
34
|
*/
|
|
31
35
|
decideEarlyHarvest(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"aggregate-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/aggregate-base.js"],"names":[],"mappings":"AAeA;IACE;;;;OAIG;IACH,sBAHW,MAAM,eACN,MAAM,EAmBhB;IAdC,uOAAuO;IACvO,qCAAwC;IACxC,gLAAgL;IAChL,yBAA2B;IAC3B,sFAAsF;IACtF,oBAAuB;IACvB,sIAAsI;IACtI,oBADW,MAAM,GAAC,SAAS,CACQ;IAEnC,gBAAqB;IAqBjB,YAAsE;IAW5E,iNAAiN;IACjN,0CAEC;IAED;;;;OAIG;IACH,8BAHU,MAAM,CAKf;IAED,qBAOC;IAFG,6BAAmB;IAIvB;;;;;;OAMG;IACH,2BAWC;IAED;;;;OAIG;IACH,yBAHW,MAAM,EAAE,gBAyBlB;IAED;;OAEG;IACH,cAEC;IAED,qCAEC;IAED;;;;;;OAMG;IACH,uDAJW,MAAM,GAAC,SAAS,SAsB1B;IAED;;;;;OAKG;IACH,4BAHG;QAAyB,IAAI,GAArB,OAAO,YAAC;QACS,KAAK,GAAtB,OAAO,YAAC;KAClB,QAKA;IAED;;;;OAIG;IACH,uCAHW,GAAC,UACD,GAAC,QAIX;;CACF;4BA/L2B,gBAAgB"}
|
package/package.json
CHANGED
|
@@ -6,6 +6,10 @@ import { FEATURE_NAMES } from '../../loaders/features/features'
|
|
|
6
6
|
|
|
7
7
|
export const IDEAL_PAYLOAD_SIZE = 16000
|
|
8
8
|
export const MAX_PAYLOAD_SIZE = 1000000
|
|
9
|
+
/** The minimum time (ms) between early harvests of a single feature. Data that arrives inside this window stays buffered for the next harvest. */
|
|
10
|
+
export const MIN_EARLY_HARVEST_INTERVAL = 500
|
|
11
|
+
/** The estimated payload size at which a feature may harvest early even inside the minimum interval, so that a flood of data cannot fill the buffer to MAX_PAYLOAD_SIZE (and start dropping events) while waiting. */
|
|
12
|
+
export const EARLY_HARVEST_BYPASS_SIZE = MAX_PAYLOAD_SIZE / 2
|
|
9
13
|
export const DEFAULT_KEY = 'NR_CONTAINER_AGENT'
|
|
10
14
|
export const SESSION_ERROR = 'SESSION_ERROR'
|
|
11
15
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { stringify } from './stringify'
|
|
@@ -10,12 +10,15 @@ export function trackObjectAttributeSize (parent, object) {
|
|
|
10
10
|
// proxy attribute to calculate its size when changed
|
|
11
11
|
parent[object] = new Proxy(originalAttribute, {
|
|
12
12
|
set (target, prop, value) {
|
|
13
|
+
/* An overwrite replaces the existing entry, so its previous size must be removed first. Otherwise repeated sets of the same key inflate the count far beyond the real size. */
|
|
14
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) output.bytes -= prop.length + stringify(target[prop]).length
|
|
13
15
|
output.bytes += prop.length + stringify(value).length
|
|
14
16
|
target[prop] = value
|
|
15
17
|
return true
|
|
16
18
|
},
|
|
17
19
|
deleteProperty (target, prop) {
|
|
18
|
-
|
|
20
|
+
/* Deleting a key that was never set removes nothing, so the count must not change. */
|
|
21
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) output.bytes -= prop.length + stringify(target[prop]).length
|
|
19
22
|
return delete target[prop]
|
|
20
23
|
}
|
|
21
24
|
})
|
|
@@ -9,8 +9,9 @@ import { EventBuffer } from './event-buffer'
|
|
|
9
9
|
import { handle } from '../../common/event-emitter/handle'
|
|
10
10
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../metrics/constants'
|
|
11
11
|
import { EventAggregator } from '../../common/aggregate/event-aggregator'
|
|
12
|
-
import { MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE, SUPPORTS_REGISTERED_ENTITIES } from '../../common/constants/agent-constants'
|
|
12
|
+
import { MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE, MIN_EARLY_HARVEST_INTERVAL, EARLY_HARVEST_BYPASS_SIZE, SUPPORTS_REGISTERED_ENTITIES } from '../../common/constants/agent-constants'
|
|
13
13
|
import { warn } from '../../common/util/console'
|
|
14
|
+
import { now } from '../../common/timing/now'
|
|
14
15
|
|
|
15
16
|
export class AggregateBase extends FeatureBase {
|
|
16
17
|
/**
|
|
@@ -27,6 +28,8 @@ export class AggregateBase extends FeatureBase {
|
|
|
27
28
|
this.canHarvestEarly = true
|
|
28
29
|
/** @type {Boolean} indicates if the feature is actively in a retry deferral period */
|
|
29
30
|
this.isRetrying = false
|
|
31
|
+
/** @type {number|undefined} the timestamp (ms, relative to page start) of the last early harvest triggered by this feature, if any */
|
|
32
|
+
this.lastEarlyHarvestAt = undefined
|
|
30
33
|
|
|
31
34
|
this.harvestOpts = {} // features aggregate classes can define custom opts for when their harvest is called
|
|
32
35
|
|
|
@@ -85,13 +88,19 @@ export class AggregateBase extends FeatureBase {
|
|
|
85
88
|
|
|
86
89
|
/**
|
|
87
90
|
* Evaluates whether a harvest should be made early by estimating the size of the current payload. Currently, this only happens if the event storage is EventBuffer, since that triggers this method directly.
|
|
88
|
-
* If conditions are met, a new harvest will be triggered immediately.
|
|
91
|
+
* If conditions are met, a new harvest will be triggered immediately, unless this feature already harvested early within the last `MIN_EARLY_HARVEST_INTERVAL` ms.
|
|
92
|
+
* Data held back by that limit stays buffered until the next early, interval, or final harvest. Once the estimated payload reaches `EARLY_HARVEST_BYPASS_SIZE`
|
|
93
|
+
* the limit no longer applies, so a flood of data cannot fill the buffer to its maximum size, where new events would be dropped, while waiting.
|
|
89
94
|
* @returns void
|
|
90
95
|
*/
|
|
91
96
|
decideEarlyHarvest () {
|
|
92
97
|
if (!this.canHarvestEarly || this.blocked || this.isRetrying) return
|
|
93
98
|
const estimatedSize = this.events.byteSize() + (this.customAttributesAreSeparate ? this.agentRef.runtime.jsAttributesMetadata.bytes : 0)
|
|
94
99
|
if (estimatedSize > IDEAL_PAYLOAD_SIZE) {
|
|
100
|
+
const currentTime = now()
|
|
101
|
+
/* Bounds the request rate when a page produces data in a flood, regardless of how fast or whether requests complete. Large payloads skip the limit to avoid dropping data. */
|
|
102
|
+
if (estimatedSize < EARLY_HARVEST_BYPASS_SIZE && this.lastEarlyHarvestAt !== undefined && currentTime - this.lastEarlyHarvestAt < MIN_EARLY_HARVEST_INTERVAL) return
|
|
103
|
+
this.lastEarlyHarvestAt = currentTime
|
|
95
104
|
this.agentRef.runtime.harvester.triggerHarvestFor(this)
|
|
96
105
|
this.reportSupportabilityMetric(`${this.featureName}/Harvest/Early/Seen`, estimatedSize)
|
|
97
106
|
}
|