Skip to content

Commit f77380b

Browse files
cwli24ptang-nr
andauthored
feat: Refactor feature storages (#1241)
Co-authored-by: ptang-nr <[email protected]>
1 parent 911d8d1 commit f77380b

File tree

41 files changed

+697
-925
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+697
-925
lines changed

.github/workflows/wdio-single-browser.yml

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ on:
1616
required: false
1717
type: boolean
1818
default: false
19+
concurrency:
20+
description: 'The number of test runner threads to spawn for a run'
21+
required: false
22+
type: number
23+
default: 10
1924
workflow_call:
2025
inputs:
2126
browser-target:

src/common/aggregate/aggregator.js

+22-32
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22
* Copyright 2020 New Relic Corporation. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
6-
import { SharedContext } from '../context/shared-context'
7-
8-
export class Aggregator extends SharedContext {
9-
constructor (parent) {
10-
super(parent)
5+
export class Aggregator {
6+
constructor () {
117
this.aggregatedData = {}
128
}
139

@@ -16,13 +12,14 @@ export class Aggregator extends SharedContext {
1612
// metrics are the numeric values to be aggregated
1713

1814
store (type, name, params, newMetrics, customParams) {
19-
var bucket = this.getBucket(type, name, params, customParams)
15+
var bucket = this.#getBucket(type, name, params, customParams)
2016
bucket.metrics = aggregateMetrics(newMetrics, bucket.metrics)
2117
return bucket
2218
}
2319

24-
merge (type, name, metrics, params, customParams) {
25-
var bucket = this.getBucket(type, name, params, customParams)
20+
merge (type, name, metrics, params, customParams, overwriteParams = false) {
21+
var bucket = this.#getBucket(type, name, params, customParams)
22+
if (overwriteParams) bucket.params = params // replace current params with incoming params obj
2623

2724
if (!bucket.metrics) {
2825
bucket.metrics = metrics
@@ -50,32 +47,13 @@ export class Aggregator extends SharedContext {
5047
}
5148

5249
storeMetric (type, name, params, value) {
53-
var bucket = this.getBucket(type, name, params)
50+
var bucket = this.#getBucket(type, name, params)
5451
bucket.stats = updateMetric(value, bucket.stats)
5552
return bucket
5653
}
5754

58-
getBucket (type, name, params, customParams) {
59-
if (!this.aggregatedData[type]) this.aggregatedData[type] = {}
60-
var bucket = this.aggregatedData[type][name]
61-
if (!bucket) {
62-
bucket = this.aggregatedData[type][name] = { params: params || {} }
63-
if (customParams) {
64-
bucket.custom = customParams
65-
}
66-
}
67-
return bucket
68-
}
69-
70-
get (type, name) {
71-
// if name is passed, get a single bucket
72-
if (name) return this.aggregatedData[type] && this.aggregatedData[type][name]
73-
// else, get all buckets of that type
74-
return this.aggregatedData[type]
75-
}
76-
77-
// Like get, but for many types and it deletes the retrieved content from the aggregatedData
78-
take (types) {
55+
// Get all listed types buckets and it deletes the retrieved content from the aggregatedData
56+
take (types, deleteWhenRetrieved = true) {
7957
var results = {}
8058
var type = ''
8159
var hasData = false
@@ -84,10 +62,22 @@ export class Aggregator extends SharedContext {
8462
results[type] = Object.values(this.aggregatedData[type] || {})
8563

8664
if (results[type].length) hasData = true
87-
delete this.aggregatedData[type]
65+
if (deleteWhenRetrieved) delete this.aggregatedData[type]
8866
}
8967
return hasData ? results : null
9068
}
69+
70+
#getBucket (type, name, params, customParams) {
71+
if (!this.aggregatedData[type]) this.aggregatedData[type] = {}
72+
var bucket = this.aggregatedData[type][name]
73+
if (!bucket) {
74+
bucket = this.aggregatedData[type][name] = { params: params || {} }
75+
if (customParams) {
76+
bucket.custom = customParams
77+
}
78+
}
79+
return bucket
80+
}
9181
}
9282

9383
function aggregateMetrics (newMetrics, oldMetrics) {
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Aggregator } from './aggregator'
2+
3+
/**
4+
* An extension of the Aggregator class that provides an interface similar to that of EventBuffer class.
5+
* This typecasting allow features that uses Aggregator as their event handler to share the same AggregateBase.events utilization by those features.
6+
*/
7+
export class EventAggregator {
8+
#aggregator = new Aggregator()
9+
#savedNamesToBuckets = {}
10+
11+
isEmpty ({ aggregatorTypes }) {
12+
if (!aggregatorTypes) return Object.keys(this.#aggregator.aggregatedData).length === 0
13+
return aggregatorTypes.every(type => !this.#aggregator.aggregatedData[type]) // no bucket exist for any of the types we're looking for
14+
}
15+
16+
add (type, name, params, newMetrics, customParams) {
17+
// Do we need to track byte size here like EventBuffer?
18+
this.#aggregator.store(type, name, params, newMetrics, customParams)
19+
return true
20+
}
21+
22+
addMetric (type, name, params, value) {
23+
this.#aggregator.storeMetric(type, name, params, value)
24+
return true
25+
}
26+
27+
save ({ aggregatorTypes }) {
28+
const key = aggregatorTypes.toString() // the stringified types serve as the key to each save call, e.g. ['err', 'ierr', 'xhr'] => 'err,ierr,xhr'
29+
const backupAggregatedDataSubset = {}
30+
aggregatorTypes.forEach(type => (backupAggregatedDataSubset[type] = this.#aggregator.aggregatedData[type])) // make a subset of the aggregatedData for each of the types we want to save
31+
this.#savedNamesToBuckets[key] = backupAggregatedDataSubset
32+
/*
33+
{ 'err,ierr,xhr': {
34+
'err': {
35+
<aggregateHash>: { metrics: { count: 1, time, ... }, params: {}, custom: {} },
36+
<otherHashName>: { metrics: { count: 1, ... }, ... }
37+
},
38+
'ierr': { ... },
39+
'xhr': { ... }
40+
}
41+
}
42+
*/
43+
}
44+
45+
get (opts) {
46+
const aggregatorTypes = Array.isArray(opts) ? opts : opts.aggregatorTypes
47+
return this.#aggregator.take(aggregatorTypes, false)
48+
}
49+
50+
clear ({ aggregatorTypes } = {}) {
51+
if (!aggregatorTypes) {
52+
this.#aggregator.aggregatedData = {}
53+
return
54+
}
55+
aggregatorTypes.forEach(type => delete this.#aggregator.aggregatedData[type])
56+
}
57+
58+
reloadSave ({ aggregatorTypes }) {
59+
const key = aggregatorTypes.toString()
60+
const backupAggregatedDataSubset = this.#savedNamesToBuckets[key]
61+
// Grabs the previously stored subset and merge it back into aggregatedData.
62+
aggregatorTypes.forEach(type => {
63+
Object.keys(backupAggregatedDataSubset[type] || {}).forEach(name => {
64+
const bucket = backupAggregatedDataSubset[type][name]
65+
// The older aka saved params take effect over the newer one. This is especially important when merging back for a failed harvest retry if, for example,
66+
// the first-ever occurrence of an error is in the retry: it contains the params.stack_trace whereas the newer or current bucket.params would not.
67+
this.#aggregator.merge(type, name, bucket.metrics, bucket.params, bucket.custom, true)
68+
})
69+
})
70+
}
71+
72+
clearSave ({ aggregatorTypes }) {
73+
const key = aggregatorTypes.toString()
74+
delete this.#savedNamesToBuckets[key]
75+
}
76+
}

src/common/harvest/harvest-scheduler.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class HarvestScheduler extends SharedContext {
9898
let payload
9999

100100
if (this.opts.getPayload) {
101-
// Ajax & PVT & SR features provide a callback function to get data for harvesting
101+
// Ajax, PVT, Softnav, Logging, SR & ST features provide a single callback function to get data for harvesting
102102
submitMethod = submitData.getSubmitMethod({ isFinalHarvest: opts?.unload })
103103
if (!submitMethod) return false
104104

src/common/harvest/harvest.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,7 @@ export class Harvest extends SharedContext {
106106
const gzip = !!qs?.attributes?.includes('gzip')
107107

108108
if (!gzip) {
109-
if (endpoint === 'events') {
110-
body = body.e
111-
} else {
112-
body = stringify(body)
113-
}
109+
if (endpoint !== 'events') body = stringify(body) // all features going to /events/ endpoint should already be serialized & stringified
114110
/** Warn --once per endpoint-- if the agent tries to send large payloads */
115111
if (body.length > 750000 && (warnings[endpoint] = (warnings?.[endpoint] || 0) + 1) === 1) warn(28, endpoint)
116112
}

src/common/harvest/types.js

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
* @typedef {object} HarvestPayload
1313
* @property {object} qs Map of values that should be sent as part of the request query string.
1414
* @property {object} body Map of values that should be sent as the body of the request.
15-
* @property {string} body.e Special case of body used for browser interactions.
1615
*/
1716

1817
/**

src/features/ajax/aggregate/chunk.js

-52
This file was deleted.

0 commit comments

Comments
 (0)