@newrelic/browser-agent 1.295.0 → 1.296.0-rc.0
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/CHANGELOG.md +12 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/wrap/wrap-events.js +2 -1
- package/dist/cjs/features/session_trace/aggregate/index.js +20 -21
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +198 -185
- package/dist/cjs/features/session_trace/aggregate/trace/utils.js +41 -0
- package/dist/cjs/features/session_trace/constants.js +3 -2
- package/dist/cjs/features/utils/aggregate-base.js +1 -2
- package/dist/cjs/features/utils/event-buffer.js +34 -3
- package/dist/cjs/features/utils/event-store-manager.js +18 -11
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/wrap/wrap-events.js +2 -1
- package/dist/esm/features/session_trace/aggregate/index.js +21 -21
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +199 -186
- package/dist/esm/features/session_trace/aggregate/trace/utils.js +34 -0
- package/dist/esm/features/session_trace/constants.js +2 -1
- package/dist/esm/features/utils/aggregate-base.js +1 -2
- package/dist/esm/features/utils/event-buffer.js +34 -3
- package/dist/esm/features/utils/event-store-manager.js +18 -11
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/wrap/wrap-events.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +5 -10
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +81 -39
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/utils.d.ts +7 -0
- package/dist/types/features/session_trace/aggregate/trace/utils.d.ts.map +1 -0
- package/dist/types/features/session_trace/constants.d.ts +1 -0
- package/dist/types/features/session_trace/constants.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/event-buffer.d.ts +18 -1
- package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
- package/dist/types/features/utils/event-store-manager.d.ts +12 -0
- package/dist/types/features/utils/event-store-manager.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/wrap/wrap-events.js +2 -1
- package/src/features/session_trace/aggregate/index.js +23 -15
- package/src/features/session_trace/aggregate/trace/storage.js +186 -189
- package/src/features/session_trace/aggregate/trace/utils.js +35 -0
- package/src/features/session_trace/constants.js +1 -0
- package/src/features/utils/aggregate-base.js +1 -2
- package/src/features/utils/event-buffer.js +35 -3
- package/src/features/utils/event-store-manager.js +18 -11
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.296.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.295.0...v1.296.0) (2025-08-19)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Harvest traces early ([#1532](https://github.com/newrelic/newrelic-browser-agent/issues/1532)) ([58f3c52](https://github.com/newrelic/newrelic-browser-agent/commit/58f3c52db5b57dcb41876792f2a1a14fa907d66d))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* Remove event buffer inspection event ([#1540](https://github.com/newrelic/newrelic-browser-agent/issues/1540)) ([3e3ca33](https://github.com/newrelic/newrelic-browser-agent/commit/3e3ca330a719dc1312019f5d1970c11dff4c6edf))
|
|
17
|
+
|
|
6
18
|
## [1.295.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.294.0...v1.295.0) (2025-08-04)
|
|
7
19
|
|
|
8
20
|
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.296.0-rc.0";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.
|
|
20
|
+
const VERSION = exports.VERSION = "1.296.0-rc.0";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -50,7 +50,8 @@ function wrapEvents(sharedEE) {
|
|
|
50
50
|
}
|
|
51
51
|
ee.on(ADD_EVENT_LISTENER + '-start', function (args, target) {
|
|
52
52
|
var originalListener = args[1];
|
|
53
|
-
if (originalListener === null || typeof originalListener !== 'function' && typeof originalListener !== 'object'
|
|
53
|
+
if (originalListener === null || typeof originalListener !== 'function' && typeof originalListener !== 'object' || args[0] === 'newrelic' // ignore our own window events
|
|
54
|
+
) {
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
var wrapped = (0, _getOrSet.getOrSet)(originalListener, flag, function () {
|
|
@@ -19,7 +19,6 @@ var _console = require("../../../common/util/console");
|
|
|
19
19
|
* SPDX-License-Identifier: Apache-2.0
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
23
22
|
/** Reserved room for query param attrs */
|
|
24
23
|
const QUERY_PARAM_PADDING = 5000;
|
|
25
24
|
class Aggregate extends _aggregateBase.AggregateBase {
|
|
@@ -34,8 +33,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
34
33
|
this.everHarvested = false;
|
|
35
34
|
/** If the harvest module is harvesting */
|
|
36
35
|
this.harvesting = false;
|
|
37
|
-
/** TraceStorage is
|
|
38
|
-
this.
|
|
36
|
+
/** TraceStorage is a middleware that decides how to format data before passing events to `this.events` */
|
|
37
|
+
this.traceStorage = new _storage.TraceStorage(this);
|
|
39
38
|
|
|
40
39
|
/* This agg needs information about sampling (sts) and entitlements (st) to make the appropriate decisions on running */
|
|
41
40
|
this.waitForFlags(['sts', 'st']).then(([stMode, stEntitled]) => this.initialize(stMode, stEntitled));
|
|
@@ -66,9 +65,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
66
65
|
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === _constants2.MODE.OFF) this.abort(2);
|
|
67
66
|
});
|
|
68
67
|
if (typeof PerformanceNavigationTiming !== 'undefined') {
|
|
69
|
-
this.
|
|
68
|
+
this.traceStorage.storeTiming(_runtime.globalScope.performance?.getEntriesByType?.('navigation')[0]);
|
|
70
69
|
} else {
|
|
71
|
-
this.
|
|
70
|
+
this.traceStorage.storeTiming(_runtime.globalScope.performance?.timing, true);
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|
|
@@ -82,13 +81,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
82
81
|
this.timeKeeper ??= this.agentRef.runtime.timeKeeper;
|
|
83
82
|
|
|
84
83
|
/** The handlers set up by the Inst file */
|
|
85
|
-
(0, _registerHandler.registerHandler)('bst', (...args) => this.
|
|
86
|
-
(0, _registerHandler.registerHandler)('bstResource', (...args) => this.
|
|
87
|
-
(0, _registerHandler.registerHandler)('bstHist', (...args) => this.
|
|
88
|
-
(0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.
|
|
89
|
-
(0, _registerHandler.registerHandler)('bstApi', (...args) => this.
|
|
90
|
-
(0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.
|
|
91
|
-
(0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.
|
|
84
|
+
(0, _registerHandler.registerHandler)('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee);
|
|
85
|
+
(0, _registerHandler.registerHandler)('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee);
|
|
86
|
+
(0, _registerHandler.registerHandler)('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee);
|
|
87
|
+
(0, _registerHandler.registerHandler)('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee);
|
|
88
|
+
(0, _registerHandler.registerHandler)('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee);
|
|
89
|
+
(0, _registerHandler.registerHandler)('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
|
|
90
|
+
(0, _registerHandler.registerHandler)('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
|
|
92
91
|
if (this.mode !== _constants2.MODE.FULL) {
|
|
93
92
|
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
94
93
|
(0, _registerHandler.registerHandler)('trace-jserror', () => {
|
|
@@ -113,18 +112,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
113
112
|
}
|
|
114
113
|
return true;
|
|
115
114
|
}
|
|
116
|
-
serializer({
|
|
117
|
-
stns
|
|
118
|
-
}) {
|
|
115
|
+
serializer(stns) {
|
|
119
116
|
if (!stns.length) return; // there are no processed nodes
|
|
120
117
|
this.everHarvested = true;
|
|
121
118
|
return (0, _traverse.applyFnToProps)(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string');
|
|
122
119
|
}
|
|
123
|
-
queryStringsBuilder({
|
|
124
|
-
stns,
|
|
125
|
-
earliestTimeStamp,
|
|
126
|
-
latestTimeStamp
|
|
127
|
-
}) {
|
|
120
|
+
queryStringsBuilder(stns) {
|
|
128
121
|
const firstSessionHarvest = !this.agentRef.runtime.session.state.traceHarvestStarted;
|
|
129
122
|
if (firstSessionHarvest) this.agentRef.runtime.session.write({
|
|
130
123
|
traceHarvestStarted: true
|
|
@@ -132,6 +125,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
132
125
|
const hasReplay = this.agentRef.runtime.session.state.sessionReplayMode === 1;
|
|
133
126
|
const endUserId = this.agentRef.info.jsAttributes['enduser.id'];
|
|
134
127
|
const entityGuid = this.agentRef.runtime.appMetadata.agents?.[0]?.entityGuid;
|
|
128
|
+
const earliestTimeStamp = stns.reduce((earliest, stn) => Math.min(earliest, stn.s), Infinity);
|
|
129
|
+
const latestTimeStamp = stns.reduce((latest, stn) => Math.max(latest, stn.s), -Infinity);
|
|
135
130
|
|
|
136
131
|
/* The blob consumer expects the following and will reject if not supplied:
|
|
137
132
|
* browser_monitoring_key
|
|
@@ -191,7 +186,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
191
186
|
});
|
|
192
187
|
if (prevMode === _constants2.MODE.OFF || !this.initialized) return this.initialize(this.mode, this.entitled);
|
|
193
188
|
if (this.initialized) {
|
|
194
|
-
this.
|
|
189
|
+
this.traceStorage.trimSTNsByTime(); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
|
|
195
190
|
this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
196
191
|
}
|
|
197
192
|
}
|
|
@@ -206,5 +201,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
206
201
|
});
|
|
207
202
|
this.events.clear();
|
|
208
203
|
}
|
|
204
|
+
postHarvestCleanup(result) {
|
|
205
|
+
this.traceStorage.clear(); // clear the trace storage state
|
|
206
|
+
super.postHarvestCleanup(result);
|
|
207
|
+
}
|
|
209
208
|
}
|
|
210
209
|
exports.Aggregate = Aggregate;
|
|
@@ -4,26 +4,24 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.TraceStorage = void 0;
|
|
7
|
-
var _runtime = require("../../../../common/constants/runtime");
|
|
8
7
|
var _constants = require("../../../../common/session/constants");
|
|
9
8
|
var _now = require("../../../../common/timing/now");
|
|
10
9
|
var _parseUrl = require("../../../../common/url/parse-url");
|
|
11
10
|
var _eventOrigin = require("../../../../common/util/event-origin");
|
|
12
11
|
var _constants2 = require("../../constants");
|
|
13
12
|
var _node = require("./node");
|
|
13
|
+
var _utils = require("./utils");
|
|
14
14
|
/**
|
|
15
15
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
16
16
|
* SPDX-License-Identifier: Apache-2.0
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
20
|
-
const SUPPORTS_PERFORMANCE_OBSERVER = typeof _runtime.globalScope.PerformanceObserver === 'function';
|
|
21
19
|
const ignoredEvents = {
|
|
22
|
-
// we find that certain events
|
|
20
|
+
// we find that certain events are noisy (and not easily smearable like mousemove) and/or duplicative (like with click vs mousedown/mouseup).
|
|
21
|
+
// These would ONLY ever be tracked in ST if the application has event listeners defined for these events... however, just in case - ignore these anyway.
|
|
23
22
|
global: {
|
|
24
23
|
mouseup: true,
|
|
25
|
-
mousedown: true
|
|
26
|
-
mousemove: true
|
|
24
|
+
mousedown: true
|
|
27
25
|
},
|
|
28
26
|
// certain events are present both in the window and in PVT metrics. PVT metrics are prefered so the window events should be ignored
|
|
29
27
|
window: {
|
|
@@ -35,128 +33,146 @@ const ignoredEvents = {
|
|
|
35
33
|
ignoreAll: true
|
|
36
34
|
}
|
|
37
35
|
};
|
|
38
|
-
const
|
|
39
|
-
typing:
|
|
40
|
-
scrolling:
|
|
41
|
-
mousing:
|
|
42
|
-
touching:
|
|
36
|
+
const SMEARABLES = {
|
|
37
|
+
typing: 'typing',
|
|
38
|
+
scrolling: 'scrolling',
|
|
39
|
+
mousing: 'mousing',
|
|
40
|
+
touching: 'touching'
|
|
41
|
+
};
|
|
42
|
+
const GAPS = {
|
|
43
|
+
[SMEARABLES.typing]: 1000,
|
|
44
|
+
// 1 second gap between typing events
|
|
45
|
+
[SMEARABLES.scrolling]: 100,
|
|
46
|
+
// 100ms gap between scrolling events
|
|
47
|
+
[SMEARABLES.mousing]: 1000,
|
|
48
|
+
// 1 second gap between mousing events
|
|
49
|
+
[SMEARABLES.touching]: 1000 // 1 second gap between touching events
|
|
50
|
+
};
|
|
51
|
+
const LENGTHS = {
|
|
52
|
+
[SMEARABLES.typing]: 2000,
|
|
53
|
+
// 2 seconds max length for typing events
|
|
54
|
+
[SMEARABLES.scrolling]: 1000,
|
|
55
|
+
// 1 second max length for scrolling events
|
|
56
|
+
[SMEARABLES.mousing]: 2000,
|
|
57
|
+
// 2 seconds max length for mousing events
|
|
58
|
+
[SMEARABLES.touching]: 2000 // 2 seconds max length for touching events
|
|
43
59
|
};
|
|
44
60
|
|
|
45
|
-
/** The purpose of this class is to manage, normalize, and
|
|
61
|
+
/** The purpose of this class is to manage, normalize, and drop various ST nodes as needed without polluting the main ST modules */
|
|
46
62
|
class TraceStorage {
|
|
47
|
-
|
|
48
|
-
trace = {};
|
|
49
|
-
earliestTimeStamp = Infinity;
|
|
50
|
-
latestTimeStamp = 0;
|
|
63
|
+
/** prevents duplication of event nodes by keeping a reference of each one seen per harvest cycle */
|
|
51
64
|
prevStoredEvents = new Set();
|
|
52
|
-
#backupTrace;
|
|
53
65
|
constructor(parent) {
|
|
54
66
|
this.parent = parent;
|
|
55
67
|
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checks if a trace node is smearable with previously stored nodes.
|
|
71
|
+
* @param {TraceNode} stn
|
|
72
|
+
* @returns {boolean} true if the node is smearable, false otherwise
|
|
73
|
+
*/
|
|
74
|
+
#isSmearable(stn) {
|
|
75
|
+
return stn.n in SMEARABLES;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Attempts to smear the current trace node with the last stored event in the event buffer.
|
|
80
|
+
* If the last stored event is smearable and matches the current node's origin and type, it will merge the two nodes and return true.
|
|
81
|
+
* If not, it will return false.
|
|
82
|
+
* This is used to reduce the number of smearable trace nodes created for events that occur in quick succession.
|
|
83
|
+
* @param {TraceNode} stn
|
|
84
|
+
* @returns {boolean} true if the node was successfully smeared, false otherwise
|
|
85
|
+
*/
|
|
86
|
+
#smear(stn) {
|
|
87
|
+
/**
|
|
88
|
+
* The matcher function to be executed by the event buffer merge method. It must be the same origin and node type,
|
|
89
|
+
* the start time of the new node must be within a certain length of the last seen node's start time,
|
|
90
|
+
* and the end time of the last seen node must be within a certain gap of the new node's start time.
|
|
91
|
+
* If all these conditions are met, we can merge the last seen node's end time with the new one.
|
|
92
|
+
* @param {TraceNode} storedEvent - the event already stored in the event buffer to potentially be merged with
|
|
93
|
+
*/
|
|
94
|
+
const matcher = storedEvent => {
|
|
95
|
+
return !(storedEvent.o !== stn.o || storedEvent.n !== stn.n || stn.s - storedEvent.s < LENGTHS[stn.o] || storedEvent.e > stn.s - GAPS[stn.o]);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** the data to be smeared together with a matching event -- if one is found in the event buffer using the matcher defined above */
|
|
99
|
+
const smearableData = {
|
|
100
|
+
e: stn.e
|
|
101
|
+
};
|
|
102
|
+
return this.parent.events.merge(matcher, smearableData);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks if the event should be ignored based on rules around its type and/or origin.
|
|
107
|
+
* @param {TraceNode} stn
|
|
108
|
+
* @returns {boolean} true if the event should be ignored, false otherwise
|
|
109
|
+
*/
|
|
110
|
+
#shouldIgnoreEvent(stn) {
|
|
111
|
+
if (stn.n in ignoredEvents.global) return true; // ignore noisy global events or window events that are already captured by PVT metrics
|
|
112
|
+
const origin = stn.o;
|
|
113
|
+
if (ignoredEvents[origin]?.ignoreAll || ignoredEvents[origin]?.[stn.n]) return true;
|
|
114
|
+
return origin === 'xhrOriginMissing' && stn.n === 'Ajax'; // ignore XHR events when the origin is missing
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks if a new node can be stored based on the current state of the trace storage class itself as well as the parent class.
|
|
119
|
+
* @returns {boolean} true if a new node can be stored, false otherwise
|
|
120
|
+
*/
|
|
56
121
|
#canStoreNewNode() {
|
|
57
122
|
if (this.parent.blocked) return false;
|
|
58
|
-
if (this.
|
|
123
|
+
if (this.parent.events.length >= _constants2.MAX_NODES_PER_HARVEST) {
|
|
59
124
|
// limit the amount of pending data awaiting next harvest
|
|
60
125
|
if (this.parent.mode !== _constants.MODE.ERROR) return false;
|
|
61
|
-
|
|
62
|
-
if (
|
|
126
|
+
this.trimSTNsByTime(); // if we're in error mode, we can try to clear the trace storage to make room for new nodes
|
|
127
|
+
if (this.parent.events.length >= _constants2.MAX_NODES_PER_HARVEST) this.trimSTNsByIndex(1); // if we still can't store new nodes, trim before index 1 to make room for new nodes
|
|
63
128
|
}
|
|
64
129
|
return true;
|
|
65
130
|
}
|
|
66
131
|
|
|
67
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Attempts to store a new trace node in the event buffer.
|
|
134
|
+
* @param {TraceNode} stn
|
|
135
|
+
* @returns {boolean} true if the node was successfully stored, false otherwise
|
|
136
|
+
*/
|
|
68
137
|
#storeSTN(stn) {
|
|
69
|
-
if (this
|
|
70
|
-
|
|
71
|
-
if
|
|
72
|
-
this.
|
|
138
|
+
if (this.#shouldIgnoreEvent(stn) || !this.#canStoreNewNode()) return false;
|
|
139
|
+
|
|
140
|
+
/** attempt to smear -- if not possible or it doesnt find a match -- just add it directly to the event buffer */
|
|
141
|
+
if (!this.#isSmearable(stn) || !this.#smear(stn)) this.parent.events.add(stn);
|
|
142
|
+
return true;
|
|
73
143
|
}
|
|
74
144
|
|
|
75
145
|
/**
|
|
76
|
-
*
|
|
77
|
-
* @param {
|
|
78
|
-
* @returns {
|
|
146
|
+
* Stores a new trace node in the event buffer.
|
|
147
|
+
* @param {TraceNode} node
|
|
148
|
+
* @returns {boolean} true if the node was successfully stored, false otherwise
|
|
79
149
|
*/
|
|
80
|
-
trimSTNs(lookbackDuration) {
|
|
81
|
-
let prunedNodes = 0;
|
|
82
|
-
const cutoffHighResTime = Math.max((0, _now.now)() - lookbackDuration, 0);
|
|
83
|
-
Object.keys(this.trace).forEach(nameCategory => {
|
|
84
|
-
const nodeList = this.trace[nameCategory];
|
|
85
|
-
/* Notice nodes are appending under their name's list as they end and are stored. This means each list is already (roughly) sorted in chronological order by end time.
|
|
86
|
-
* This isn't exact since nodes go through some processing & EE handlers chain, but it's close enough as we still capture nodes whose duration overlaps the lookback window.
|
|
87
|
-
* ASSUMPTION: all 'end' timings stored are relative to timeOrigin (DOMHighResTimeStamp) and not Unix epoch based. */
|
|
88
|
-
let cutoffIdx = nodeList.findIndex(node => cutoffHighResTime <= node.e);
|
|
89
|
-
if (cutoffIdx === 0) return;else if (cutoffIdx < 0) {
|
|
90
|
-
// whole list falls outside lookback window and is irrelevant
|
|
91
|
-
cutoffIdx = nodeList.length;
|
|
92
|
-
delete this.trace[nameCategory];
|
|
93
|
-
} else nodeList.splice(0, cutoffIdx); // chop off everything outside our window i.e. before the last <lookbackDuration> timeframe
|
|
94
|
-
|
|
95
|
-
this.nodeCount -= cutoffIdx;
|
|
96
|
-
prunedNodes += cutoffIdx;
|
|
97
|
-
});
|
|
98
|
-
return prunedNodes;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Used by session trace's harvester to create the payload body. */
|
|
102
|
-
takeSTNs() {
|
|
103
|
-
if (!SUPPORTS_PERFORMANCE_OBSERVER) {
|
|
104
|
-
// if PO isn't supported, this checks resourcetiming buffer every harvest.
|
|
105
|
-
this.storeResources(_runtime.globalScope.performance?.getEntriesByType?.('resource'));
|
|
106
|
-
}
|
|
107
|
-
const stns = Object.entries(this.trace).flatMap(([name, listOfSTNodes]) => {
|
|
108
|
-
// basically take the "this.trace" map-obj and concat all the list-type values
|
|
109
|
-
if (!(name in toAggregate)) return listOfSTNodes;
|
|
110
|
-
// Special processing for event nodes dealing with user inputs:
|
|
111
|
-
const reindexByOriginFn = this.smearEvtsByOrigin(name);
|
|
112
|
-
const partitionListByOriginMap = listOfSTNodes.sort((a, b) => a.s - b.s).reduce(reindexByOriginFn, {});
|
|
113
|
-
return Object.values(partitionListByOriginMap).flat(); // join the partitions back into 1-D, now ordered by origin then start time
|
|
114
|
-
}, this);
|
|
115
|
-
const earliestTimeStamp = this.earliestTimeStamp;
|
|
116
|
-
const latestTimeStamp = this.latestTimeStamp;
|
|
117
|
-
return {
|
|
118
|
-
stns,
|
|
119
|
-
earliestTimeStamp,
|
|
120
|
-
latestTimeStamp
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
smearEvtsByOrigin(name) {
|
|
124
|
-
const maxGap = toAggregate[name][0];
|
|
125
|
-
const maxLen = toAggregate[name][1];
|
|
126
|
-
const lastO = {};
|
|
127
|
-
return (byOrigin, evtNode) => {
|
|
128
|
-
let lastArr = byOrigin[evtNode.o];
|
|
129
|
-
if (!lastArr) lastArr = byOrigin[evtNode.o] = [];
|
|
130
|
-
const last = lastO[evtNode.o];
|
|
131
|
-
if (name === 'scrolling' && !trivial(evtNode)) {
|
|
132
|
-
lastO[evtNode.o] = null;
|
|
133
|
-
evtNode.n = 'scroll';
|
|
134
|
-
lastArr.push(evtNode);
|
|
135
|
-
} else if (last && evtNode.s - last.s < maxLen && last.e > evtNode.s - maxGap) {
|
|
136
|
-
last.e = evtNode.e;
|
|
137
|
-
} else {
|
|
138
|
-
lastO[evtNode.o] = evtNode;
|
|
139
|
-
lastArr.push(evtNode);
|
|
140
|
-
}
|
|
141
|
-
return byOrigin;
|
|
142
|
-
};
|
|
143
|
-
function trivial(node) {
|
|
144
|
-
const limit = 4;
|
|
145
|
-
return !!(node && typeof node.e === 'number' && typeof node.s === 'number' && node.e - node.s < limit);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
150
|
storeNode(node) {
|
|
149
|
-
|
|
150
|
-
this.#storeSTN(node);
|
|
151
|
+
return this.#storeSTN(node);
|
|
151
152
|
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Processes a PVT (Page Visibility Timing) entry.
|
|
156
|
+
* @param {*} name
|
|
157
|
+
* @param {*} value
|
|
158
|
+
* @param {*} attrs
|
|
159
|
+
* @returns {boolean} true if the node was successfully stored, false otherwise
|
|
160
|
+
*/
|
|
152
161
|
processPVT(name, value, attrs) {
|
|
153
|
-
this.storeTiming({
|
|
162
|
+
return this.storeTiming({
|
|
154
163
|
[name]: value
|
|
155
164
|
});
|
|
156
165
|
}
|
|
157
|
-
storeTiming(timingEntry, isAbsoluteTimestamp = false) {
|
|
158
|
-
if (!timingEntry) return;
|
|
159
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Stores a timing entry in the event buffer.
|
|
169
|
+
* @param {*} timingEntry
|
|
170
|
+
* @param {*} isAbsoluteTimestamp
|
|
171
|
+
* @returns {boolean} true if ALL possible nodes were successfully stored, false otherwise
|
|
172
|
+
*/
|
|
173
|
+
storeTiming(timingEntry, isAbsoluteTimestamp = false) {
|
|
174
|
+
if (!timingEntry) return false;
|
|
175
|
+
let allStored = true;
|
|
160
176
|
// loop iterates through prototype also (for FF)
|
|
161
177
|
for (let key in timingEntry) {
|
|
162
178
|
let val = timingEntry[key];
|
|
@@ -172,19 +188,27 @@ class TraceStorage {
|
|
|
172
188
|
if (this.parent.timeKeeper && this.parent.timeKeeper.ready && isAbsoluteTimestamp) {
|
|
173
189
|
val = this.parent.timeKeeper.convertAbsoluteTimestamp(Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val)));
|
|
174
190
|
}
|
|
175
|
-
if (!this.#
|
|
176
|
-
this.#storeSTN(new _node.TraceNode(key, val, val, 'document', 'timing'));
|
|
191
|
+
if (!this.#storeSTN(new _node.TraceNode(key, val, val, 'document', 'timing'))) allStored = false;
|
|
177
192
|
}
|
|
193
|
+
return allStored;
|
|
178
194
|
}
|
|
179
195
|
|
|
180
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Tracks the events and their listener's duration on objects wrapped by wrap-events.
|
|
198
|
+
* @param {*} currentEvent - the event to be stored
|
|
199
|
+
* @param {*} target - the target of the event
|
|
200
|
+
* @param {*} start - the start time of the event
|
|
201
|
+
* @param {*} end - the end time of the event
|
|
202
|
+
* @returns {boolean} true if the event was successfully stored, false otherwise
|
|
203
|
+
*/
|
|
181
204
|
storeEvent(currentEvent, target, start, end) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Important! -- This check NEEDS to be done directly in this handler, before converting to a TraceNode so that the
|
|
207
|
+
* reference pointer to the Event node itself is the object being checked for duplication
|
|
208
|
+
* **/
|
|
209
|
+
if (this.prevStoredEvents.has(currentEvent) || !this.#canStoreNewNode()) return false;
|
|
186
210
|
this.prevStoredEvents.add(currentEvent);
|
|
187
|
-
const evt = new _node.TraceNode(
|
|
211
|
+
const evt = new _node.TraceNode((0, _utils.evtName)(currentEvent.type), start, end, undefined, 'event');
|
|
188
212
|
try {
|
|
189
213
|
// webcomponents-lite.js can trigger an exception on currentEvent.target getter because
|
|
190
214
|
// it does not check currentEvent.currentTarget before calling getRootNode() on it
|
|
@@ -192,52 +216,30 @@ class TraceStorage {
|
|
|
192
216
|
} catch (e) {
|
|
193
217
|
evt.o = (0, _eventOrigin.eventOrigin)(null, target, this.parent.ee);
|
|
194
218
|
}
|
|
195
|
-
this.#storeSTN(evt);
|
|
196
|
-
}
|
|
197
|
-
shouldIgnoreEvent(event, target) {
|
|
198
|
-
if (event.type in ignoredEvents.global) return true;
|
|
199
|
-
const origin = (0, _eventOrigin.eventOrigin)(event.target, target, this.parent.ee);
|
|
200
|
-
if (!!ignoredEvents[origin] && ignoredEvents[origin].ignoreAll) return true;
|
|
201
|
-
return !!(!!ignoredEvents[origin] && event.type in ignoredEvents[origin]);
|
|
202
|
-
}
|
|
203
|
-
evtName(type) {
|
|
204
|
-
switch (type) {
|
|
205
|
-
case 'keydown':
|
|
206
|
-
case 'keyup':
|
|
207
|
-
case 'keypress':
|
|
208
|
-
return 'typing';
|
|
209
|
-
case 'mousemove':
|
|
210
|
-
case 'mouseenter':
|
|
211
|
-
case 'mouseleave':
|
|
212
|
-
case 'mouseover':
|
|
213
|
-
case 'mouseout':
|
|
214
|
-
return 'mousing';
|
|
215
|
-
case 'scroll':
|
|
216
|
-
return 'scrolling';
|
|
217
|
-
case 'touchstart':
|
|
218
|
-
case 'touchmove':
|
|
219
|
-
case 'touchend':
|
|
220
|
-
case 'touchcancel':
|
|
221
|
-
case 'touchenter':
|
|
222
|
-
case 'touchleave':
|
|
223
|
-
return 'touching';
|
|
224
|
-
default:
|
|
225
|
-
return type;
|
|
226
|
-
}
|
|
219
|
+
return this.#storeSTN(evt);
|
|
227
220
|
}
|
|
228
221
|
|
|
229
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Tracks when the window history API specified by wrap-history is used.
|
|
224
|
+
* @param {*} path
|
|
225
|
+
* @param {*} old
|
|
226
|
+
* @param {*} time
|
|
227
|
+
* @returns {boolean} true if the history node was successfully stored, false otherwise
|
|
228
|
+
*/
|
|
230
229
|
storeHist(path, old, time) {
|
|
231
|
-
|
|
232
|
-
this.#storeSTN(new _node.TraceNode('history.pushState', time, time, path, old));
|
|
230
|
+
return this.#storeSTN(new _node.TraceNode('history.pushState', time, time, path, old));
|
|
233
231
|
}
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Processes all the PerformanceResourceTiming entries captured (by observer).
|
|
235
|
+
* @param {*[]} resources
|
|
236
|
+
* @returns {boolean} true if all resource nodes were successfully stored, false otherwise
|
|
237
|
+
*/
|
|
236
238
|
storeResources(resources) {
|
|
237
|
-
if (!resources || resources.length === 0) return;
|
|
239
|
+
if (!resources || resources.length === 0) return false;
|
|
240
|
+
let allStored = true;
|
|
238
241
|
for (let i = 0; i < resources.length; i++) {
|
|
239
242
|
const currentResource = resources[i];
|
|
240
|
-
if ((currentResource.fetchStart | 0) <= this.#laststart) continue; // don't recollect already-seen resources
|
|
241
243
|
if (!this.#canStoreNewNode()) break; // stop processing if we can't store any more resource nodes anyways
|
|
242
244
|
|
|
243
245
|
const {
|
|
@@ -253,56 +255,67 @@ class TraceStorage {
|
|
|
253
255
|
pathname
|
|
254
256
|
} = (0, _parseUrl.parseUrl)(currentResource.name);
|
|
255
257
|
const res = new _node.TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, "".concat(protocol, "://").concat(hostname, ":").concat(port).concat(pathname), entryType);
|
|
256
|
-
this.#storeSTN(res);
|
|
258
|
+
if (!this.#storeSTN(res)) allStored = false;
|
|
257
259
|
}
|
|
258
|
-
|
|
260
|
+
return allStored;
|
|
259
261
|
}
|
|
260
262
|
|
|
261
|
-
|
|
263
|
+
/**
|
|
264
|
+
* JavascriptError (FEATURE) events pipes into ST here.
|
|
265
|
+
* @param {*} type
|
|
266
|
+
* @param {*} name
|
|
267
|
+
* @param {*} params
|
|
268
|
+
* @param {*} metrics
|
|
269
|
+
* @returns {boolean} true if the error node was successfully stored, false otherwise
|
|
270
|
+
*/
|
|
262
271
|
storeErrorAgg(type, name, params, metrics) {
|
|
263
|
-
if (type !== 'err') return; // internal errors are purposefully ignored
|
|
264
|
-
|
|
265
|
-
this.#storeSTN(new _node.TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
|
|
272
|
+
if (type !== 'err') return false; // internal errors are purposefully ignored
|
|
273
|
+
return this.#storeSTN(new _node.TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash));
|
|
266
274
|
}
|
|
267
275
|
|
|
268
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Ajax (FEATURE) events--XML & fetches--pipes into ST here.
|
|
278
|
+
* @param {*} type
|
|
279
|
+
* @param {*} name
|
|
280
|
+
* @param {*} params
|
|
281
|
+
* @param {*} metrics
|
|
282
|
+
* @returns {boolean} true if the Ajax node was successfully stored, false otherwise
|
|
283
|
+
*/
|
|
269
284
|
storeXhrAgg(type, name, params, metrics) {
|
|
270
|
-
if (type !== 'xhr') return;
|
|
271
|
-
|
|
272
|
-
this.#storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
|
|
285
|
+
if (type !== 'xhr') return false;
|
|
286
|
+
return this.#storeSTN(new _node.TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, "".concat(params.status, " ").concat(params.method, ": ").concat(params.host).concat(params.pathname), 'ajax'));
|
|
273
287
|
}
|
|
274
288
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this
|
|
289
|
+
/**
|
|
290
|
+
* Trims stored trace nodes in the event buffer by start time.
|
|
291
|
+
* @param {number} lookbackDuration
|
|
292
|
+
* @returns {void}
|
|
293
|
+
*/
|
|
294
|
+
trimSTNsByTime(lookbackDuration = _constants2.ERROR_MODE_SECONDS_WINDOW) {
|
|
295
|
+
this.parent.events.clear({
|
|
296
|
+
clearBeforeTime: Math.max(_now.now - lookbackDuration, 0),
|
|
297
|
+
timestampKey: 'e'
|
|
298
|
+
});
|
|
282
299
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Trims stored trace nodes in the event buffer before a given index value.
|
|
303
|
+
* @param {number} index
|
|
304
|
+
* @returns {void}
|
|
305
|
+
*/
|
|
306
|
+
trimSTNsByIndex(index = 0) {
|
|
307
|
+
this.parent.events.clear({
|
|
308
|
+
clearBeforeIndex: index // trims before index value
|
|
309
|
+
});
|
|
288
310
|
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* clears the stored events in the event buffer.
|
|
314
|
+
* This is used to release references to past events for garbage collection.
|
|
315
|
+
* @returns {void}
|
|
316
|
+
*/
|
|
289
317
|
clear() {
|
|
290
|
-
this.trace = {};
|
|
291
|
-
this.nodeCount = 0;
|
|
292
318
|
this.prevStoredEvents.clear(); // release references to past events for GC
|
|
293
|
-
this.earliestTimeStamp = Infinity;
|
|
294
|
-
this.latestTimeStamp = 0;
|
|
295
|
-
}
|
|
296
|
-
reloadSave() {
|
|
297
|
-
for (const stnsArray of Object.values(this.#backupTrace)) {
|
|
298
|
-
for (const stn of stnsArray) {
|
|
299
|
-
if (!this.#canStoreNewNode()) return; // stop attempting to re-store nodes
|
|
300
|
-
this.#storeSTN(stn);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
clearSave() {
|
|
305
|
-
this.#backupTrace = undefined;
|
|
306
319
|
}
|
|
307
320
|
}
|
|
308
321
|
exports.TraceStorage = TraceStorage;
|