Skip to content

Commit d2d44ae

Browse files
committed
refactor
1 parent 0356e15 commit d2d44ae

File tree

12 files changed

+169
-249
lines changed

12 files changed

+169
-249
lines changed

lib/agent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ function Agent(config) {
288288
this.harvester
289289
)
290290
this.transactionSampler = determineSampler({ agent: this, config, sampler: 'root' })
291-
// TODO: Does 3 AdaptiveSamplers triple the data being sent up?
291+
// TODO: This could create duplicate sampler instances, fix will be addressed in https://github.com/newrelic/node-newrelic/issues/3519.
292292
this.remoteParentSampledSampler = determineSampler({ agent: this, config, sampler: 'remote_parent_sampled' })
293293
this.remoteParentNotSampledSampler = determineSampler({ agent: this, config, sampler: 'remote_parent_not_sampled' })
294294

lib/samplers/adaptive-sampler.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
'use strict'
77

8-
class AdaptiveSampler {
8+
const Sampler = require('./sampler')
9+
10+
class AdaptiveSampler extends Sampler {
911
/**
1012
*
1113
* @param {object} opts Sampler options.
@@ -15,6 +17,7 @@ class AdaptiveSampler {
1517
* @param {boolean} opts.serverless - Indicates if the environment is serverless.
1618
*/
1719
constructor(opts) {
20+
super() // no-op
1821
this._serverless = opts.serverless
1922
this._seen = 0
2023
this._sampled = 0
@@ -103,6 +106,17 @@ class AdaptiveSampler {
103106
return false
104107
}
105108

109+
applySamplingDecision(transaction) {
110+
if (!transaction) return
111+
// eslint-disable-next-line sonarjs/pseudo-random
112+
const initPriority = Math.random()
113+
transaction.sampled = this.shouldSample(initPriority)
114+
transaction.priority = transaction.sampled ? initPriority + 1 : initPriority
115+
// Truncate the priority after potentially modifying it to avoid floating
116+
// point errors.
117+
transaction.priority = ((transaction.priority * 1e6) | 0) / 1e6
118+
}
119+
106120
/**
107121
* Starts a new sample period after adjusting the sampling statistics.
108122
*/

lib/samplers/always-off-sampler.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
const Sampler = require('./sampler')
7+
8+
class AlwaysOffSampler extends Sampler {
9+
applySamplingDecision(transaction) {
10+
if (!transaction) return
11+
transaction.priority = 0
12+
transaction.sampled = false
13+
}
14+
}
15+
16+
module.exports = AlwaysOffSampler

lib/samplers/always-on-sampler.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
const Sampler = require('./sampler')
7+
8+
class AlwaysOnSampler extends Sampler {
9+
applySamplingDecision(transaction) {
10+
if (!transaction) return
11+
transaction.priority = 2.0
12+
transaction.sampled = true
13+
}
14+
}
15+
16+
module.exports = AlwaysOnSampler

lib/samplers/determine-sampler.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
*/
55

66
const AdaptiveSampler = require('./adaptive-sampler')
7+
const AlwaysOffSampler = require('./always-off-sampler')
8+
const AlwaysOnSampler = require('./always-on-sampler')
79
const TraceIdRatioBasedSampler = require('./ratio-based-sampler')
10+
const Sampler = require('./sampler')
811

912
/**
1013
* A helper function for the Agent to determine what sampler
@@ -13,13 +16,23 @@ const TraceIdRatioBasedSampler = require('./ratio-based-sampler')
1316
* @param {Agent} params.agent The New Relic agent instance.
1417
* @param {object} params.config The entire agent config.
1518
* @param {string} params.sampler The sampler type to use: 'root', 'remote_parent_sampled', or 'remote_parent_not_sampled'.
16-
* @returns {object} An AdaptiveSampler or a TraceIdRatioBasedSampler, depending on given configuration.
19+
* @returns {Sampler} A Sampler e.g. AdaptiveSampler or TraceIdRatioBasedSampler
1720
*/
1821
function determineSampler({ agent, config, sampler = 'root' }) {
19-
const ratioBasedSampler = config?.distributed_tracing?.sampler?.[sampler]?.trace_id_ratio_based
22+
const samplerDefinition = config?.distributed_tracing?.sampler?.[sampler]
2023

21-
// This slightly complicated boolean check is for the use case
22-
// where the customer sets `ratio` to 0, which is a falsey value.
24+
// Always on?
25+
if (samplerDefinition === 'always_on') {
26+
return new AlwaysOnSampler()
27+
}
28+
29+
// Always off?
30+
if (samplerDefinition === 'always_off') {
31+
return new AlwaysOffSampler()
32+
}
33+
34+
// Is it TraceIdRatioBased?
35+
const ratioBasedSampler = samplerDefinition?.trace_id_ratio_based
2336
const validRatio = (typeof ratioBasedSampler?.ratio === 'number' && ratioBasedSampler.ratio >= 0 && ratioBasedSampler.ratio <= 1)
2437
if (validRatio) {
2538
return new TraceIdRatioBasedSampler({
@@ -28,11 +41,13 @@ function determineSampler({ agent, config, sampler = 'root' }) {
2841
})
2942
}
3043

44+
// Our default ^.^
45+
// We'll ignore https://github.com/newrelic/node-newrelic/issues/3519 for now 0.o
3146
return new AdaptiveSampler({
3247
agent,
3348
serverless: config.serverless_mode.enabled,
3449
period: config.sampling_target_period_in_seconds * 1000,
35-
target: config.sampling_target // alias for dt.sampler.adaptive_sampling_target, see config/index.js
50+
target: config.sampling_target // getter/setter for distributed_tracing.sampler.adaptive_sampling_target
3651
})
3752
}
3853

lib/samplers/ratio-based-sampler.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,42 @@
44
*/
55

66
'use strict'
7+
const Sampler = require('./sampler')
78

89
/**
910
* Trace ID ratio-based sampler adapted for New Relic agent use,
1011
* based on OpenTelemetry's sampler of the same name.
1112
*/
12-
class TraceIdRatioBasedSampler {
13+
class TraceIdRatioBasedSampler extends Sampler {
1314
/**
1415
* @param {object} opts Sampler options.
1516
* @param {Agent} opts.agent - The New Relic agent instance.
1617
* @param {number} opts.ratio - The sampling ratio (0 to 1).
1718
*/
1819
constructor(opts) {
20+
super() // no-op
1921
if (!opts.agent) {
2022
throw new Error('must have an agent instance')
2123
}
22-
this._logger = opts.agent.logger
2324
this._ratio = this._normalize(opts.ratio)
2425
this._upperBound = Math.floor(this._ratio * 0xffffffff)
2526
}
2627

28+
applySamplingDecision(transaction) {
29+
if (!transaction) return
30+
// eslint-disable-next-line sonarjs/pseudo-random
31+
const initPriority = Math.random()
32+
transaction.sampled = this.shouldSample(transaction.traceId)
33+
transaction.priority = transaction.sampled ? initPriority + 1 : initPriority
34+
// Truncate the priority after potentially modifying it to avoid floating
35+
// point errors.
36+
transaction.priority = ((transaction.priority * 1e6) | 0) / 1e6
37+
}
38+
2739
/**
2840
*
29-
* @param {string} traceId trace id
30-
* @returns {boolean}
41+
* @param {string} traceId transaction trace id
42+
* @returns {boolean} whether to sample a transaction based on given trace id
3143
*/
3244
shouldSample(traceId) {
3345
const accumulated = this._accumulate(traceId)
@@ -45,7 +57,6 @@ class TraceIdRatioBasedSampler {
4557
*/
4658
_normalize(ratio) {
4759
if (typeof ratio !== 'number' || isNaN(ratio)) {
48-
this._logger.warn('TraceIdRatioBasedSampler: invalid `ratio` provided, defaulting to 0')
4960
return 0
5061
}
5162
if (ratio >= 1) return 1

lib/samplers/sampler.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/**
7+
* @private
8+
* @interface
9+
*/
10+
class Sampler {
11+
/**
12+
* Sets `priority` and `sampled` on the transaction
13+
* in respect to this sampler's decision.
14+
* @param {Transaction} transaction the transaction to update
15+
*/
16+
applySamplingDecision(transaction) {
17+
throw new Error('must implement applySamplingDecision for %s', transaction)
18+
}
19+
}
20+
21+
module.exports = Sampler

lib/transaction/index.js

Lines changed: 28 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,32 +1028,11 @@ function acceptTraceContextPayload(traceparentHeader, tracestateHeader, transpor
10281028
}
10291029
}
10301030

1031-
decideSamplingFromW3cData({ transaction: this, traceparent, tracestate })
1032-
}
1033-
1034-
/**
1035-
* Updates the transaction with sampling configuration as determined by the
1036-
* agent's distributed tracing sampler configuration. In short, updates the
1037-
* transaction to always sample, never sample, or let the standard sampling
1038-
* algorithm make the decision.
1039-
*
1040-
* @param {object} params Input parameters.
1041-
* @param {Transaction} params.transaction The transaction to update.
1042-
* @param {Traceparent} params.traceparent The object representation of a
1043-
* traceparent header.
1044-
* @param {Tracestate} params.tracestate The object representation of a
1045-
* tracestate header.
1046-
*/
1047-
function decideSamplingFromW3cData({ transaction, traceparent, tracestate }) {
1048-
const agent = transaction.agent
1049-
const {
1050-
remote_parent_sampled: parentSampled,
1051-
remote_parent_not_sampled: parentNotSampled
1052-
} = agent.config.distributed_tracing.sampler
1031+
// Decide sampling from w3c data
10531032
if (traceparent.isSampled === true) {
1054-
applySamplingStrategy({ transaction, tracestate, sampler: agent.remoteParentSampledSampler, samplerType: parentSampled })
1033+
applySamplingStrategy({ transaction: this, tracestate, sampler: this.agent.remoteParentSampledSampler })
10551034
} else if (traceparent.isSampled === false) {
1056-
applySamplingStrategy({ transaction, tracestate, sampler: agent.remoteParentNotSampledSampler, samplerType: parentNotSampled })
1035+
applySamplingStrategy({ transaction: this, tracestate, sampler: this.agent.remoteParentNotSampledSampler })
10571036
}
10581037
}
10591038

@@ -1063,51 +1042,22 @@ function decideSamplingFromW3cData({ transaction, traceparent, tracestate }) {
10631042
*
10641043
* @param {*} params Function parameters.
10651044
* @param {Transaction} params.transaction The transaction to update.
1066-
* @param {Tracestate} params.tracestate A tracestate object with embedded New Relic intrinsics.
1067-
* @param {object} params.sampler The sampler to use, AdaptiveSampler or TraceIdRatioBasedSampler.
1068-
* @param {string|object} params.samplerType A string describing the sampler being used
1069-
* ('default', 'always_on', 'always_off'), or an object if 'trace_id_ratio_based'.
1045+
* @param {Tracestate} [params.tracestate] A tracestate object with embedded New Relic intrinsics.
1046+
* @param {object} params.sampler The sampler to use, e.g. AdaptiveSampler or TraceIdRatioBasedSampler.
10701047
*/
1071-
function applySamplingStrategy({ transaction, tracestate, sampler, samplerType }) {
1072-
if (sampler?.toString() === 'TraceIdRatioBasedSampler') {
1073-
transaction.sampled = sampler.shouldSample(transaction.traceId)
1074-
// Force calc priority because transaction.sampled likekly has changed
1075-
// eslint-disable-next-line sonarjs/pseudo-random
1076-
transaction.priority = Math.random()
1077-
if (transaction.sampled) {
1078-
transaction.priority += 1
1079-
}
1080-
1081-
// Truncate the priority after potentially modifying it to avoid floating
1082-
// point errors.
1083-
transaction.priority = ((transaction.priority * 1e6) | 0) / 1e6
1084-
}
1085-
1086-
// TODO: Make these their own samplers?
1087-
switch (samplerType) {
1088-
case 'default': {
1089-
// This case is if we're coming from the deprecated newrelic header logic
1090-
// where we don't want to override the sampled and priority values
1091-
if (!tracestate) {
1092-
return
1093-
}
1094-
1095-
transaction.sampled = tracestate?.intrinsics ? tracestate.isSampled : null
1096-
transaction.priority = tracestate?.intrinsics ? tracestate.priority : null
1097-
break
1098-
}
1099-
1100-
case 'always_on': {
1101-
transaction.sampled = true
1102-
transaction.priority = 2.0
1103-
break
1048+
function applySamplingStrategy({ transaction, tracestate = null, sampler }) {
1049+
if (sampler?.toString() !== 'AdaptiveSampler') {
1050+
sampler.applySamplingDecision(transaction)
1051+
} else {
1052+
// This case is if we're coming from the deprecated newrelic header logic
1053+
// and we don't want to override the sampled and priority values
1054+
if (!tracestate) {
1055+
return
11041056
}
11051057

1106-
case 'always_off': {
1107-
transaction.sampled = false
1108-
transaction.priority = 0
1109-
break
1110-
}
1058+
// Explicitly set sampled and priority from tracestate intrinsics if available
1059+
transaction.sampled = tracestate?.intrinsics ? tracestate.isSampled : null
1060+
transaction.priority = tracestate?.intrinsics ? tracestate.priority : null
11111061
}
11121062
}
11131063

@@ -1209,19 +1159,15 @@ const _dtDefineAttrsFromTraceData = function _dtDefineAttrsFromTraceData(data, t
12091159
this.traceId = data.tr
12101160

12111161
if (data.pr) {
1212-
// TODO: We need to consider the sampler before blindly accepting this value.
12131162
this.priority = data.pr
12141163
this.sampled = data.sa != null ? data.sa : this.sampled
1215-
// we don't have traceparent or tracestate, but this will update
1216-
// sampling decision based on our samplers
1217-
const {
1218-
remote_parent_sampled: parentSampled,
1219-
remote_parent_not_sampled: parentNotSampled
1220-
} = this.agent.config.distributed_tracing.sampler
1164+
// Even though New Relic headers are deprecated,
1165+
// we still have to apply our sampling decision on top
1166+
// of the priority and sampled values we receive.
12211167
if (data?.sa) {
1222-
applySamplingStrategy({ transaction: this, sampler: this.agent.remoteParentSampledSampler, samplerType: parentSampled })
1168+
applySamplingStrategy({ transaction: this, sampler: this.agent.remoteParentSampledSampler })
12231169
} else {
1224-
applySamplingStrategy({ transaction: this, sampler: this.agent.remoteParentNotSampledSampler, samplerType: parentNotSampled })
1170+
applySamplingStrategy({ transaction: this, sampler: this.agent.remoteParentNotSampledSampler })
12251171
}
12261172
}
12271173

@@ -1430,48 +1376,15 @@ Transaction.prototype.isSampled = function isSampled() {
14301376
}
14311377

14321378
/**
1433-
* Generates a priority for the transaction if it does not have one already.
1434-
* @param {object} sampler The sampler to calculate priority with. Defaults to `this.agent.transactionSampler`.
1379+
* If `priority` does not exist on this transaction already,
1380+
* this.agent.transactionSampler will apply a sampling decision
1381+
* to the transaction, which assigns `priority` and updates `sampled`
1382+
* in respect to this sampling decision.
14351383
*/
1436-
Transaction.prototype._calculatePriority = function _calculatePriority(sampler) {
1437-
if (!sampler) {
1438-
sampler = this.agent.transactionSampler
1439-
}
1384+
Transaction.prototype._calculatePriority = function _calculatePriority() {
14401385
if (this.priority === null) {
1441-
// eslint-disable-next-line sonarjs/pseudo-random
1442-
this.priority = Math.random()
1443-
1444-
// Determine sampling parameter based on type of sampler being used
1445-
const shouldSampleParam = sampler.toString() === 'TraceIdRatioBasedSampler'
1446-
? this.traceId
1447-
// We want to separate the priority roll from the decision roll to
1448-
// avoid biasing the priority range
1449-
// eslint-disable-next-line sonarjs/pseudo-random
1450-
: Math.random()
1451-
this.sampled = sampler.shouldSample(shouldSampleParam)
1452-
if (this.sampled) {
1453-
this.priority += 1
1454-
}
1455-
1456-
// Truncate the priority after potentially modifying it to avoid floating
1457-
// point errors.
1458-
this.priority = ((this.priority * 1e6) | 0) / 1e6
1459-
1460-
// IGNORE THIS
1461-
if (sampler === this.agent.transactionSampler) {
1462-
const samplerType = this.agent.config.distributed_tracing.sampler.root
1463-
1464-
// TODO: have to account if there's no headers
1465-
if (samplerType === 'always_on') {
1466-
this.sampled = true
1467-
this.priority = 2.0
1468-
}
1469-
1470-
if (samplerType === 'always_off') {
1471-
this.sampled = false
1472-
this.priority = 0
1473-
}
1474-
}
1386+
const sampler = this.agent.transactionSampler
1387+
sampler.applySamplingDecision(this)
14751388
}
14761389
}
14771390

0 commit comments

Comments
 (0)