newrelic 9.7.0 → 9.7.2

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.
Files changed (37) hide show
  1. package/NEWS.md +42 -18
  2. package/THIRD_PARTY_NOTICES.md +203 -2
  3. package/api.js +155 -103
  4. package/lib/agent.js +29 -82
  5. package/lib/collector/api.js +234 -201
  6. package/lib/config/attribute-filter.js +35 -25
  7. package/lib/config/default.js +45 -19
  8. package/lib/config/index.js +260 -199
  9. package/lib/environment.js +16 -12
  10. package/lib/errors/error-collector.js +124 -55
  11. package/lib/errors/index.js +59 -49
  12. package/lib/instrumentation/@hapi/hapi.js +56 -50
  13. package/lib/instrumentation/@node-redis/client.js +50 -41
  14. package/lib/instrumentation/amqplib.js +116 -151
  15. package/lib/instrumentation/core/{async_hooks.js → async-hooks.js} +26 -11
  16. package/lib/instrumentation/core/globals.js +1 -1
  17. package/lib/instrumentation/core/http-outbound.js +193 -78
  18. package/lib/instrumentation/core/timers.js +106 -59
  19. package/lib/instrumentation/grpc-js/grpc.js +20 -23
  20. package/lib/instrumentation/mongodb/common.js +87 -85
  21. package/lib/instrumentation/redis.js +112 -90
  22. package/lib/instrumentation/undici.js +204 -192
  23. package/lib/instrumentation/{when.js → when/constants.js} +13 -10
  24. package/lib/instrumentation/when/contextualizer.js +168 -0
  25. package/lib/instrumentation/when/index.js +354 -0
  26. package/lib/instrumentation/when/nr-hooks.js +15 -0
  27. package/lib/instrumentations.js +1 -1
  28. package/lib/shim/shim.js +2 -0
  29. package/lib/shim/webframework-shim.js +19 -0
  30. package/lib/symbols.js +1 -0
  31. package/lib/system-info.js +240 -163
  32. package/lib/util/async-each-limit.js +30 -0
  33. package/lib/util/attributes.js +159 -0
  34. package/lib/util/code-level-metrics.js +58 -0
  35. package/lib/util/deep-equal.js +11 -144
  36. package/package.json +5 -4
  37. package/lib/instrumentation/promise.js +0 -572
@@ -5,113 +5,192 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- /* eslint sonarjs/cognitive-complexity: ["error", 29] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
9
-
10
- const exec = require('child_process').exec
11
- const readProc = require('./utilization/common').readProc
12
- const getBootId = require('./utilization/docker-info').getBootId
13
- const utilization = require('./utilization')
8
+ const util = require('util')
9
+ const execFile = util.promisify(require('child_process').execFile)
10
+ const readProc = util.promisify(require('./utilization/common').readProc)
11
+ const getBootId = util.promisify(require('./utilization/docker-info').getBootId)
12
+ const getVendors = util.promisify(require('./utilization').getVendors)
14
13
  const logger = require('./logger.js').child({ component: 'system-info' })
15
14
  const os = require('os')
16
15
  const parseCpuInfo = require('./parse-proc-cpuinfo')
17
16
  const parseMemInfo = require('./parse-proc-meminfo')
17
+ const Agent = require('./agent')
18
18
  const platform = os.platform()
19
19
 
20
20
  module.exports = fetchSystemInfo
21
21
 
22
- function isInteger(i) {
23
- return i === parseInt(i, 10)
22
+ /**
23
+ * Helper method for determining if given value can be an integer
24
+ *
25
+ * @param {*} value the value to check
26
+ * @returns {boolean} whether or not the value can be coerced to an integer
27
+ */
28
+ function isInteger(value) {
29
+ return value === parseInt(value, 10)
24
30
  }
25
31
 
26
- function fetchSystemInfo(agent, callback, numTasks = 5) {
27
- const config = agent.config
28
- const systemInfo = {
29
- processorArch: os.arch()
32
+ /**
33
+ * Helper method for updating the utilization info with processor information from the config
34
+ *
35
+ * @param {*} processorConfig agent.config.utilization.logical_processors
36
+ * @param {object} utilizationConfig Utilization configuration object defined in #fetchSystemInfo
37
+ */
38
+ function maybeAddProcessorUtilization(processorConfig, utilizationConfig) {
39
+ const parsedConfigProcessors = parseFloat(processorConfig, 10)
40
+ if (!isNaN(parsedConfigProcessors) && isInteger(parsedConfigProcessors)) {
41
+ utilizationConfig.logical_processors = parsedConfigProcessors
42
+ } else {
43
+ logger.info(
44
+ '%s supplied in config for utilization.logical_processors, expected a number',
45
+ processorConfig
46
+ )
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Helper method for updating the utilization info with RAM information from the config
52
+ *
53
+ * @param {*} ramConfig agent.config.utilization.total_ram_mib
54
+ * @param {object} utilizationConfig Utilization configuration object defined in #fetchSystemInfo
55
+ */
56
+ function maybeAddRamUtilization(ramConfig, utilizationConfig) {
57
+ const parsedConfigRam = parseFloat(ramConfig, 10)
58
+ if (!isNaN(parsedConfigRam) && isInteger(parsedConfigRam)) {
59
+ utilizationConfig.total_ram_mib = parsedConfigRam
60
+ } else {
61
+ logger.info('%s supplied in config for utilization.total_ram_mib, expected a number', ramConfig)
30
62
  }
63
+ }
64
+
65
+ /**
66
+ * Helper method for updating the utilization info with hostname information from the config
67
+ *
68
+ * @param {*} configHostname agent.config.utilization.billing_hostname
69
+ * @param {object} utilizationConfig Utilization configuration object defined in #fetchSystemInfo
70
+ */
71
+ function maybeAddHostUtilization(configHostname, utilizationConfig) {
72
+ if (typeof configHostname === 'string') {
73
+ utilizationConfig.hostname = configHostname
74
+ } else {
75
+ logger.info('%s supplied in config for utilization.Hostname, expected a string', configHostname)
76
+ }
77
+ }
31
78
 
79
+ /**
80
+ * Helper method for updating the system info with architecture dependent processor info
81
+ *
82
+ * @param {*} processorStats output of #getProcessorStats
83
+ * @param {object} systemInfo System Information object defined in #fetchSystemInfo
84
+ */
85
+ function maybeSetProcessorStats(processorStats, systemInfo) {
86
+ if (processorStats) {
87
+ systemInfo.packages = processorStats.packages
88
+ systemInfo.logicalProcessors = processorStats.logical
89
+ systemInfo.cores = processorStats.cores
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Helper method for updating the system info with architecture dependent RAM info
95
+ *
96
+ * @param {*} memoryStats output of #getMemoryStats
97
+ * @param {object} systemInfo System Information object defined in #fetchSystemInfo
98
+ */
99
+ function maybeSetMemoryStats(memoryStats, systemInfo) {
100
+ if (memoryStats) {
101
+ systemInfo.memory = memoryStats
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Helper method for updating the system info with architecture dependent Kernel info
107
+ *
108
+ * @param {*} kernelStats output of #getKernelVersion
109
+ * @param {object} systemInfo System Information object defined in #fetchSystemInfo
110
+ */
111
+ function maybeSetKernelStats(kernelStats, systemInfo) {
112
+ if (kernelStats) {
113
+ systemInfo.kernelVersion = kernelStats
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Helper method for updating the system info with vendor info
119
+ *
120
+ * @param {*} vendorStats output of #utilization.getVendors
121
+ * @param {object} systemInfo System Information object defined in #fetchSystemInfo
122
+ */
123
+ function maybeSetVendorStats(vendorStats, systemInfo) {
124
+ if (vendorStats) {
125
+ systemInfo.vendors = vendorStats
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Helper method for updating the system info with the Docker boot id
131
+ *
132
+ * @param {*} bootId output of #utilization/docker-info.getBootId
133
+ * @param {object} systemInfo System Information object defined in #fetchSystemInfo
134
+ */
135
+ function maybeSetBootId(bootId, systemInfo) {
136
+ if (bootId) {
137
+ systemInfo.bootId = bootId
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Main method for retrieving system level statistics, used for fact gathering on Agent startup
143
+ *
144
+ * @param {Agent} agent Instantiation of Node.js agent
145
+ * @param {Function} callback Callback to fire after we've gathered all the necessary stats
146
+ */
147
+ async function fetchSystemInfo(agent, callback) {
32
148
  const utilizationConfig = Object.create(null)
33
- if (config.utilization) {
34
- const configProcessors = config.utilization.logical_processors
35
- const configRam = config.utilization.total_ram_mib
36
- const configHostname = config.utilization.billing_hostname
37
-
38
- if (configProcessors) {
39
- const parsedConfigProcessors = parseFloat(configProcessors, 10)
40
- if (!isNaN(parsedConfigProcessors) && isInteger(parsedConfigProcessors)) {
41
- utilizationConfig.logical_processors = parsedConfigProcessors
42
- } else {
43
- logger.info(
44
- '%s supplied in config for utilization.logical_processors, expected a number',
45
- configProcessors
46
- )
47
- }
48
- }
149
+ const systemInfo = {
150
+ processorArch: os.arch()
151
+ }
49
152
 
50
- if (configRam) {
51
- const parsedConfigRam = parseFloat(configRam, 10)
52
- if (!isNaN(parsedConfigRam) && isInteger(parsedConfigRam)) {
53
- utilizationConfig.total_ram_mib = parsedConfigRam
54
- } else {
55
- logger.info(
56
- '%s supplied in config for utilization.total_ram_mib, expected a number',
57
- configRam
58
- )
59
- }
60
- }
153
+ const processorConfig = agent.config.utilization?.logical_processors
154
+ if (processorConfig) {
155
+ maybeAddProcessorUtilization(processorConfig, utilizationConfig)
156
+ }
61
157
 
62
- if (configHostname) {
63
- if (typeof configHostname === 'string') {
64
- utilizationConfig.hostname = configHostname
65
- } else {
66
- logger.info(
67
- '%s supplied in config for utilization.Hostname, expected a string',
68
- configHostname
69
- )
70
- }
71
- }
158
+ const ramConfig = agent.config.utilization?.total_ram_mib
159
+ if (ramConfig) {
160
+ maybeAddRamUtilization(ramConfig, utilizationConfig)
161
+ }
72
162
 
73
- if (Object.keys(utilizationConfig).length > 0) {
74
- systemInfo.config = utilizationConfig
75
- }
163
+ const configHostname = agent.config.utilization?.billing_hostname
164
+ if (configHostname) {
165
+ maybeAddHostUtilization(configHostname, utilizationConfig)
76
166
  }
77
167
 
78
- let tasksDone = 0
79
- function finishedResponse() {
80
- if (++tasksDone === numTasks) {
81
- callback(null, systemInfo)
82
- }
168
+ if (Object.keys(utilizationConfig).length > 0) {
169
+ systemInfo.config = utilizationConfig
83
170
  }
84
171
 
85
- module.exports._getProcessorStats(function getProcessCB(processorStats) {
86
- systemInfo.packages = processorStats.packages
87
- systemInfo.logicalProcessors = processorStats.logical
88
- systemInfo.cores = processorStats.cores
89
- finishedResponse()
90
- })
91
- module.exports._getMemoryStats(function getMemCB(memory) {
92
- systemInfo.memory = memory
93
- finishedResponse()
94
- })
95
- getKernelVersion(function getVersionCB(kernelVersion) {
96
- systemInfo.kernelVersion = kernelVersion
97
- finishedResponse()
98
- })
99
- utilization.getVendors(agent, function getVendorInfo(err, vendors) {
100
- if (vendors) {
101
- systemInfo.vendors = vendors
102
- }
103
- finishedResponse()
104
- })
105
- getBootId(agent, function reportBootId(err, bootId) {
106
- if (bootId) {
107
- systemInfo.bootId = bootId
108
- }
109
- finishedResponse()
110
- })
172
+ const processorStats = await module.exports._getProcessorStats()
173
+ const memoryStats = await module.exports._getMemoryStats()
174
+ const kernelStats = await getKernelVersion()
175
+ const vendorStats = await getVendors(agent)
176
+ const bootId = await getBootId(agent)
177
+
178
+ maybeSetProcessorStats(processorStats, systemInfo)
179
+ maybeSetMemoryStats(memoryStats, systemInfo)
180
+ maybeSetKernelStats(kernelStats, systemInfo)
181
+ maybeSetVendorStats(vendorStats, systemInfo)
182
+ maybeSetBootId(bootId, systemInfo)
183
+
184
+ callback(null, systemInfo)
111
185
  }
112
186
 
113
- // placed on module for mocking purposes in tests
114
- module.exports._getProcessorStats = function getProcessorStats(callback) {
187
+ /**
188
+ * Helper method for getting detailed, architecture specific processor information from the system
189
+ * Exported for testing purposes
190
+ *
191
+ * @returns {*} null if unknown platform, otherwise the processor stats
192
+ */
193
+ module.exports._getProcessorStats = async function getProcessorStats() {
115
194
  const processorStats = {
116
195
  logical: null,
117
196
  cores: null,
@@ -119,101 +198,99 @@ module.exports._getProcessorStats = function getProcessorStats(callback) {
119
198
  }
120
199
 
121
200
  if (platform.match(/darwin/i)) {
122
- getSysctlValue(['hw.packages'], function getPackages(packages) {
123
- getSysctlValue(['hw.physicalcpu_max', 'hw.physicalcpu'], function getCores(cores) {
124
- getSysctlValue(
125
- ['hw.logicalcpu_max', 'hw.logicalcpu', 'hw.ncpu'],
126
- function getLogicalCpu(logical) {
127
- processorStats.logical = parseFloat(logical, 10)
128
- processorStats.cores = parseFloat(cores, 10)
129
- processorStats.packages = parseFloat(packages, 10)
130
-
131
- for (const key in processorStats) {
132
- if (!processorStats[key] || !isInteger(processorStats[key])) {
133
- processorStats[key] = null
134
- }
135
- }
136
-
137
- callback(processorStats)
138
- }
139
- )
140
- })
141
- })
201
+ const packages = await getSysctlValue(['hw.packages'])
202
+ const cores = await getSysctlValue(['hw.physicalcpu_max', 'hw.physicalcpu'])
203
+ const logical = await getSysctlValue(['hw.logicalcpu_max', 'hw.logicalcpu', 'hw.ncpu'])
204
+
205
+ processorStats.logical = isInteger(logical) ? parseFloat(logical, 10) : null
206
+ processorStats.cores = isInteger(cores) ? parseFloat(cores, 10) : null
207
+ processorStats.packages = isInteger(packages) ? parseFloat(packages, 10) : null
208
+
209
+ return processorStats
142
210
  } else if (platform.match(/bsd/i)) {
143
- getSysctlValue(['hw.ncpu'], function getLogicalCpu(logical) {
144
- processorStats.logical = logical
145
- callback(processorStats)
146
- })
211
+ const logical = await getSysctlValue(['hw.ncpu'])
212
+
213
+ processorStats.logical = logical
214
+
215
+ return processorStats
147
216
  } else if (platform.match(/linux/i)) {
148
- readProc('/proc/cpuinfo', function parseProc(err, data) {
149
- callback(parseCpuInfo(data))
150
- })
151
- } else {
152
- logger.debug('Unknown platform: %s; could not retrieve processor info', platform)
153
- callback(processorStats)
217
+ const data = await readProc('/proc/cpuinfo')
218
+
219
+ return parseCpuInfo(data)
154
220
  }
221
+
222
+ logger.debug('Unknown platform: %s; could not retrieve processor info', platform)
223
+ return processorStats
155
224
  }
156
225
 
157
- // placed on module for mocking purposes in tests
158
- module.exports._getMemoryStats = function getMemoryStats(callback) {
226
+ /**
227
+ * Helper method for getting detailed, architecture specific RAM information from the system
228
+ * Exported for testing purposes
229
+ *
230
+ * @returns {*} null if unknown platform, otherwise the RAM amount
231
+ */
232
+ module.exports._getMemoryStats = async function getMemoryStats() {
159
233
  if (platform.match(/darwin/i)) {
160
- getSysctlValue(['hw.memsize'], function getMem(memory) {
161
- callback(parseInt(memory, 10) / (1024 * 1024))
162
- })
234
+ const memory = await getSysctlValue(['hw.memsize'])
235
+ return parseInt(memory, 10) / (1024 * 1024)
163
236
  } else if (platform.match(/bsd/i)) {
164
- getSysctlValue(['hw.realmem'], function getMem(memory) {
165
- callback(parseInt(memory, 10) / (1024 * 1024))
166
- })
237
+ const memory = await getSysctlValue(['hw.realmem'])
238
+ return parseInt(memory, 10) / (1024 * 1024)
167
239
  } else if (platform.match(/linux/i)) {
168
- readProc('/proc/meminfo', function parseProc(err, data) {
169
- callback(parseMemInfo(data))
170
- })
171
- } else {
172
- logger.debug('Unknown platform: %s; could not retrieve memory info', platform)
173
- callback(null)
240
+ const data = await readProc('/proc/meminfo')
241
+ return parseMemInfo(data)
174
242
  }
243
+
244
+ logger.debug('Unknown platform: %s; could not retrieve memory info', platform)
245
+ return null
175
246
  }
176
247
 
177
- function getKernelVersion(callback) {
248
+ /**
249
+ * Helper method for retrieving Kernel version information for different platforms
250
+ *
251
+ * @returns {*} null if unknown platform, otherwise string representation of kernel version
252
+ */
253
+ async function getKernelVersion() {
178
254
  if (platform.match(/darwin/i) || platform.match(/bsd/i)) {
179
- getSysctlValue(['kern.version'], function getMem(version) {
180
- callback(version)
181
- })
255
+ return await getSysctlValue(['kern.version'])
182
256
  } else if (platform.match(/linux/i)) {
183
- readProc('/proc/version', function parseProc(err, data) {
184
- callback(data)
185
- })
186
- } else {
187
- logger.debug('Unknown platform: %s; could not read kernel version', platform)
188
- callback(null)
257
+ return await readProc('/proc/version')
189
258
  }
259
+
260
+ logger.debug('Unknown platform: %s; could not read kernel version', platform)
261
+ return null
190
262
  }
191
263
 
192
- function getSysctlValue(names, callback) {
193
- if (!names) {
194
- return callback(null)
195
- }
196
- let returned = false
197
- let ran = 0
198
- names.forEach(function sysctlName(name) {
199
- exec('sysctl -n ' + name, respond)
264
+ /**
265
+ * Helper method for getting sysctl information given a list of potential values to look up
266
+ * Returns the first successful sysctl's output
267
+ *
268
+ * @param {Array.<string>} names List of sysctl values to look up
269
+ * @returns {*} null if we failed to lookup any info (error or not), or the first successful sysctl's output
270
+ */
271
+ async function getSysctlValue(names = []) {
272
+ let returnValue = null
200
273
 
201
- function respond(err, stdout, stderr) {
202
- if (returned) {
203
- return
204
- }
205
- if (err) {
206
- logger.debug('Error when trying to run: sysctl -n %s: %s', name, err.message)
207
- callback(null)
208
- returned = true
209
- } else if (!stderr) {
210
- callback(stdout)
211
- returned = true
212
- }
213
- if (++ran === names.length && !returned) {
214
- logger.debug('No sysctl info found for names: %j', names)
215
- callback(null)
274
+ for (const name of names) {
275
+ // returnValue being set means we already found what we were looking for, early exit for performance
276
+ if (returnValue) {
277
+ break
278
+ }
279
+
280
+ try {
281
+ const { stderr, stdout } = await execFile('sysctl', ['-n', name])
282
+
283
+ if (!stderr) {
284
+ returnValue = stdout
216
285
  }
286
+ } catch (err) {
287
+ logger.debug('Error when trying to run: sysctl -n %s: %s', name, err.message)
217
288
  }
218
- })
289
+ }
290
+
291
+ if (returnValue === null) {
292
+ logger.debug('No sysctl info found for names: %j', names)
293
+ }
294
+
295
+ return returnValue
219
296
  }
@@ -0,0 +1,30 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ /**
9
+ * Helper method for limiting number of concurrent async executions to a certain limit
10
+ * Native replacement for async.eachLimit, fixes https://issues.newrelic.com/browse/NR-69739
11
+ *
12
+ * Shamelessly ripped off from https://github.com/nodejs/help/issues/2192#issuecomment-533730280
13
+ * and https://timtech.blog/posts/limiting-async-operations-promise-concurrency-javascript/
14
+ *
15
+ * @param {Array} items Array to iterate over
16
+ * @param {Function} fn the callback from Array.map you would normally write that contains an async operation
17
+ * @param {number} limit the maximum allowed concurrent invocations of `fn`
18
+ */
19
+ async function eachLimit(items, fn, limit) {
20
+ const results = []
21
+
22
+ while (items.length) {
23
+ const resolved = await Promise.all(items.splice(0, limit).map(fn))
24
+ results.push(...resolved)
25
+ }
26
+
27
+ return results
28
+ }
29
+
30
+ module.exports = eachLimit
@@ -0,0 +1,159 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const NAMES = require('../metrics/names')
9
+ const hashes = require('../util/hashes')
10
+
11
+ /**
12
+ * Helper method for modifying attributes by reference if transaction has queue metrics
13
+ *
14
+ * @param {object} transaction The current transaction
15
+ * @param {object} attributes The attributes object to modify (by reference)
16
+ */
17
+ function maybeAddQueueAttributes(transaction, attributes) {
18
+ const metric = transaction.metrics.getMetric(NAMES.QUEUETIME)
19
+
20
+ if (metric) {
21
+ attributes.queueDuration = metric.total
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Helper method for modifying attributes by reference if transaction has external call metrics
27
+ *
28
+ * @param {object} transaction The current transaction
29
+ * @param {object} attributes The attributes object to modify (by reference)
30
+ */
31
+ function maybeAddExternalAttributes(transaction, attributes) {
32
+ const metric = transaction.metrics.getMetric(NAMES.EXTERNAL.ALL)
33
+
34
+ if (metric) {
35
+ attributes.externalDuration = metric.total
36
+ attributes.externalCallCount = metric.callCount
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Helper method for modifying attributes by reference if transaction has database metrics
42
+ *
43
+ * @param {object} transaction The current transaction
44
+ * @param {object} attributes The attributes object to modify (by reference)
45
+ */
46
+ function maybeAddDatabaseAttributes(transaction, attributes) {
47
+ const metric = transaction.metrics.getMetric(NAMES.DB.ALL)
48
+
49
+ if (metric) {
50
+ attributes.databaseDuration = metric.total
51
+ attributes.databaseCallCount = metric.callCount
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Helper method for modifying attributes by reference if transaction has DT metrics
57
+ *
58
+ * @param {object} transaction The current transaction
59
+ * @param {object} attributes The attributes object to modify (by reference)
60
+ */
61
+ function maybeAddParentAttributes(transaction, attributes) {
62
+ if (transaction.parentSpanId) {
63
+ attributes.parentSpanId = transaction.parentSpanId
64
+ }
65
+
66
+ if (transaction.parentId) {
67
+ attributes.parentId = transaction.parentId
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Helper method for modifying attributes by reference if transaction has CAT metrics
73
+ *
74
+ * @param {object} transaction The current transaction
75
+ * @param {object} attributes The attributes object to modify (by reference)
76
+ * @param {object} configuration Agent configuration options
77
+ */
78
+ function addRequiredCATAttributes(transaction, attributes, configuration) {
79
+ attributes['nr.guid'] = transaction.id
80
+ attributes['nr.tripId'] = transaction.tripId || transaction.id
81
+ attributes['nr.pathHash'] = hashes.calculatePathHash(
82
+ configuration.applications()[0],
83
+ transaction.getFullName(),
84
+ transaction.referringPathHash
85
+ )
86
+ }
87
+
88
+ /**
89
+ * Helper method for modifying attributes by reference if transaction has additional CAT metrics
90
+ *
91
+ * @param {object} transaction The current transaction
92
+ * @param {object} attributes The attributes object to modify (by reference)
93
+ * @param {object} configuration Agent configuration options
94
+ */
95
+ function maybeAddExtraCATAttributes(transaction, attributes, configuration) {
96
+ if (transaction.referringPathHash) {
97
+ attributes['nr.referringPathHash'] = transaction.referringPathHash
98
+ }
99
+
100
+ if (transaction.referringTransactionGuid) {
101
+ const refId = transaction.referringTransactionGuid
102
+ attributes['nr.referringTransactionGuid'] = refId
103
+ }
104
+
105
+ const alternatePathHashes = transaction.alternatePathHashes()
106
+ if (alternatePathHashes) {
107
+ attributes['nr.alternatePathHashes'] = alternatePathHashes
108
+ }
109
+
110
+ if (transaction.baseSegment && transaction.type === 'web') {
111
+ const apdex =
112
+ configuration.web_transactions_apdex[transaction.getFullName()] || configuration.apdex_t
113
+ const duration = transaction.baseSegment.getDurationInMillis() / 1000
114
+ attributes['nr.apdexPerfZone'] = calculateApdexZone(duration, apdex)
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Helper method for determining a transaction's apdex score based on an apdex threshold and transaction duration
120
+ *
121
+ * @param {number} duration number of seconds the transaction took
122
+ * @param {number} apdexT the apdex threshold
123
+ * @returns {string} String representation of apdex "zone"
124
+ */
125
+ function calculateApdexZone(duration, apdexT) {
126
+ if (duration <= apdexT) {
127
+ return 'S' // satisfied
128
+ }
129
+
130
+ if (duration <= apdexT * 4) {
131
+ return 'T' // tolerating
132
+ }
133
+
134
+ return 'F' // frustrated
135
+ }
136
+
137
+ /**
138
+ * Helper method for modifying attributes by reference if transaction has Synthetics metrics
139
+ *
140
+ * @param {object} transaction The current transaction
141
+ * @param {object} attributes The attributes object to modify (by reference)
142
+ */
143
+ function maybeAddSyntheticAttributes(transaction, attributes) {
144
+ if (transaction.syntheticsData) {
145
+ attributes['nr.syntheticsResourceId'] = transaction.syntheticsData.resourceId
146
+ attributes['nr.syntheticsJobId'] = transaction.syntheticsData.jobId
147
+ attributes['nr.syntheticsMonitorId'] = transaction.syntheticsData.monitorId
148
+ }
149
+ }
150
+
151
+ module.exports = {
152
+ maybeAddQueueAttributes,
153
+ maybeAddExternalAttributes,
154
+ maybeAddDatabaseAttributes,
155
+ maybeAddParentAttributes,
156
+ addRequiredCATAttributes,
157
+ maybeAddExtraCATAttributes,
158
+ maybeAddSyntheticAttributes
159
+ }