newrelic 7.2.1 → 7.3.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/NEWS.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
### v7.3.0 (2021-04-06)
|
|
2
|
+
|
|
3
|
+
* Added new feature-flag 'new_promise_tracking' which enables cleaning up of segment references on native promise resolve instead of destroy. Includes usage of async-await. This can be enabled via `feature_flag: { new_promise_tracking: true }` in the config file or `NEW_RELIC_FEATURE_FLAG_NEW_PROMISE_TRACKING=1` in your ENV vars.
|
|
4
|
+
|
|
5
|
+
Applications with heavy promise usage or high-throughput applications with some promise usage should see moderate to high reduction in memory usage and may see a slight reduction in CPU usage. A bump in throughput may also be noticed in some cases. Results will vary by application.
|
|
6
|
+
|
|
7
|
+
If you are experiencing high overhead levels with your promise usage and the agent attached, we recommend testing your application with 'new_promise_tracking' set to true to see if overhead is reduced. You'll also want to verify your data is still being captured correctly in case it falls into a known or unknown limitation of this approach. **NOTE: chaining of promise continuations onto an already resolved promise across an async hop (scheduled timer) will result in state-loss with this new functionality turned on. This is a less-common use-case but worth considering with your applications.**
|
|
8
|
+
|
|
9
|
+
* Fixed memory leak introduced when Infinite Tracing is enabled.
|
|
10
|
+
|
|
11
|
+
When Infinite Tracing endpoints reconnected they would instantiate a new gRPC client prior to calling `client.recordSpan()`. It appears several objects created by grpc-js (`ChannelImplementation` and child objects, promises, etc.) are held in memory indefinitely due to scheduled timers even when the client is no-longer referenced and the associated stream closed. We now avoid this situation by only creating the client once and then reusing it to establish new stream connections.
|
|
12
|
+
|
|
1
13
|
### v7.2.1 (2021-03-29)
|
|
2
14
|
|
|
3
15
|
* Dev-only sub-dependency bump of 'y18n' to clear npm audit warnings.
|
package/lib/feature_flags.js
CHANGED
|
@@ -15,7 +15,8 @@ exports.prerelease = {
|
|
|
15
15
|
fastify_instrumentation: false,
|
|
16
16
|
// Starts by defaulting true as introducing for deprecation/removal purposes.
|
|
17
17
|
// Will eventually set false prior full removal of feature.
|
|
18
|
-
certificate_bundle: true
|
|
18
|
+
certificate_bundle: true,
|
|
19
|
+
new_promise_tracking: false
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
// flags that are no longer used for released features
|
package/lib/grpc/connection.js
CHANGED
|
@@ -14,14 +14,14 @@ const util = require('util')
|
|
|
14
14
|
|
|
15
15
|
const connectionStates = require('./connection/states')
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const PROTO_OPTIONS = {keepCase: true,
|
|
18
18
|
longs: String,
|
|
19
19
|
enums: String,
|
|
20
20
|
defaults: true,
|
|
21
21
|
oneofs: true
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const PROTO_DEFINITION_PATH = __dirname +
|
|
25
25
|
'../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
|
|
26
26
|
|
|
27
27
|
const DEFAULT_RECONNECT_DELAY_MS = 15 * 1000
|
|
@@ -54,11 +54,13 @@ class GrpcConnection extends EventEmitter {
|
|
|
54
54
|
this._metrics = metrics
|
|
55
55
|
this._setState(connectionStates.disconnected)
|
|
56
56
|
|
|
57
|
-
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
58
57
|
this._licensekey = null
|
|
59
58
|
this._runId = null
|
|
60
59
|
this._requestHeadersMap = null
|
|
61
60
|
|
|
61
|
+
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
62
|
+
|
|
63
|
+
this._client = null
|
|
62
64
|
this.stream = null
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -320,26 +322,48 @@ class GrpcConnection extends EventEmitter {
|
|
|
320
322
|
* a stream for further writing.
|
|
321
323
|
*/
|
|
322
324
|
_connectSpans() {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const credentials = this._generateCredentials(grpc)
|
|
330
|
-
const client = new serviceDefinition.IngestService(
|
|
331
|
-
this._endpoint,
|
|
332
|
-
credentials
|
|
333
|
-
)
|
|
325
|
+
if (!this._client) {
|
|
326
|
+
// Only create once to avoid potential memory leak.
|
|
327
|
+
// We create here (currently) for consistent error handling.
|
|
328
|
+
this._client = this._createClient(this._endpoint)
|
|
329
|
+
}
|
|
334
330
|
|
|
335
331
|
const metadata =
|
|
336
332
|
this._getMetadata(this._licenseKey, this._runId, this._requestHeadersMap, process.env)
|
|
337
333
|
|
|
338
|
-
const stream =
|
|
334
|
+
const stream = this._client.recordSpan(metadata)
|
|
339
335
|
this._setupSpanStreamObservers(stream)
|
|
340
336
|
|
|
341
337
|
return stream
|
|
342
338
|
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Creates gRPC service client to use for establishing gRPC streams.
|
|
342
|
+
*
|
|
343
|
+
* WARNING: creating a client more than once can result in a memory leak.
|
|
344
|
+
* ChannelImplementation and related objects will stay in memory even after
|
|
345
|
+
* the stream is closed and we do not have a handle to the client. Currently
|
|
346
|
+
* impacting grpc-js@1.2.11 and several earlier versions.
|
|
347
|
+
*/
|
|
348
|
+
_createClient(endpoint) {
|
|
349
|
+
logger.trace('Creating gRPC client for: ', endpoint)
|
|
350
|
+
|
|
351
|
+
const packageDefinition = protoLoader.loadSync(PROTO_DEFINITION_PATH, PROTO_OPTIONS)
|
|
352
|
+
|
|
353
|
+
const protoDescriptor = grpc.loadPackageDefinition(
|
|
354
|
+
packageDefinition
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
const traceApi = protoDescriptor.com.newrelic.trace.v1
|
|
358
|
+
|
|
359
|
+
const credentials = this._generateCredentials(grpc)
|
|
360
|
+
const client = new traceApi.IngestService(
|
|
361
|
+
endpoint,
|
|
362
|
+
credentials
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
return client
|
|
366
|
+
}
|
|
343
367
|
}
|
|
344
368
|
|
|
345
369
|
module.exports = GrpcConnection
|
|
@@ -40,7 +40,7 @@ function initialize(agent, shim) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function tryAsyncHooks(agent, shim) {
|
|
43
|
-
|
|
43
|
+
let asyncHooks = null
|
|
44
44
|
try {
|
|
45
45
|
asyncHooks = require('async_hooks')
|
|
46
46
|
} catch (e) {
|
|
@@ -53,7 +53,25 @@ function tryAsyncHooks(agent, shim) {
|
|
|
53
53
|
const segmentMap = new Map()
|
|
54
54
|
module.exports.segmentMap = segmentMap
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
let hookHandlers = getStandardHooks(segmentMap, agent, shim)
|
|
57
|
+
|
|
58
|
+
if (agent.config.feature_flag.new_promise_tracking) {
|
|
59
|
+
logger.info('Enabling new promise tracking style via new_promise_tracking feature flag.')
|
|
60
|
+
hookHandlers = getPromiseResolveStyleHooks(segmentMap, agent, shim)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const hook = asyncHooks.createHook(hookHandlers)
|
|
64
|
+
hook.enable()
|
|
65
|
+
|
|
66
|
+
agent.on('unload', function disableHook() {
|
|
67
|
+
hook.disable()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getStandardHooks(segmentMap, agent, shim) {
|
|
74
|
+
const hooks = {
|
|
57
75
|
init: function initHook(id, type, triggerId, promiseWrap) {
|
|
58
76
|
if (type !== 'PROMISE') {
|
|
59
77
|
return
|
|
@@ -78,7 +96,7 @@ function tryAsyncHooks(agent, shim) {
|
|
|
78
96
|
},
|
|
79
97
|
|
|
80
98
|
before: function beforeHook(id) {
|
|
81
|
-
|
|
99
|
+
const hookSegment = segmentMap.get(id)
|
|
82
100
|
|
|
83
101
|
if (!hookSegment) {
|
|
84
102
|
return
|
|
@@ -88,7 +106,7 @@ function tryAsyncHooks(agent, shim) {
|
|
|
88
106
|
shim.setActiveSegment(hookSegment)
|
|
89
107
|
},
|
|
90
108
|
after: function afterHook(id) {
|
|
91
|
-
|
|
109
|
+
const hookSegment = segmentMap.get(id)
|
|
92
110
|
|
|
93
111
|
// hookSegment is the segment that was active before the promise
|
|
94
112
|
// executed. If the promise is executing before a segment has been
|
|
@@ -104,11 +122,72 @@ function tryAsyncHooks(agent, shim) {
|
|
|
104
122
|
destroy: function destHook(id) {
|
|
105
123
|
segmentMap.delete(id)
|
|
106
124
|
}
|
|
107
|
-
}
|
|
125
|
+
}
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
})
|
|
127
|
+
return hooks
|
|
128
|
+
}
|
|
112
129
|
|
|
113
|
-
|
|
130
|
+
function getPromiseResolveStyleHooks(segmentMap, agent, shim) {
|
|
131
|
+
const hooks = {
|
|
132
|
+
init: function initHook(id, type, triggerId, asyncResource) {
|
|
133
|
+
if (type !== 'PROMISE') {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let parentSegment = segmentMap.get(triggerId)
|
|
138
|
+
|
|
139
|
+
if (parentSegment && !parentSegment.transaction.isActive()) {
|
|
140
|
+
// Stop propagating if the transaction was ended.
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!parentSegment && !agent.getTransaction()) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const activeSegment = shim.getActiveSegment() || parentSegment
|
|
149
|
+
if (asyncResource && asyncResource.promise) {
|
|
150
|
+
asyncResource.promise.__NR_id = id
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
segmentMap.set(id, activeSegment)
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
before: function beforeHook(id) {
|
|
157
|
+
const hookSegment = segmentMap.get(id)
|
|
158
|
+
|
|
159
|
+
if (!hookSegment) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
segmentMap.set(id, shim.getActiveSegment())
|
|
164
|
+
shim.setActiveSegment(hookSegment)
|
|
165
|
+
},
|
|
166
|
+
after: function afterHook(id) {
|
|
167
|
+
const hookSegment = segmentMap.get(id)
|
|
168
|
+
|
|
169
|
+
// hookSegment is the segment that was active before the promise
|
|
170
|
+
// executed. If the promise is executing before a segment has been
|
|
171
|
+
// restored, hookSegment will be null and should be restored. Thus
|
|
172
|
+
// undefined is the only invalid value here.
|
|
173
|
+
if (hookSegment === undefined) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
segmentMap.set(id, shim.getActiveSegment())
|
|
178
|
+
shim.setActiveSegment(hookSegment)
|
|
179
|
+
},
|
|
180
|
+
promiseResolve: function promiseResolveHandler(id) {
|
|
181
|
+
const hookSegment = segmentMap.get(id)
|
|
182
|
+
segmentMap.delete(id)
|
|
183
|
+
|
|
184
|
+
if (hookSegment === undefined) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
shim.setActiveSegment(hookSegment)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return hooks
|
|
114
193
|
}
|
|
@@ -72,6 +72,7 @@ function createStandardAggregator(config, collector, metrics) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function validateInfiniteTracing(trace_observer) {
|
|
75
|
+
// TODO: Remove semver check when Node 10 support dropped.
|
|
75
76
|
if (!psemver.satisfies('>=10.10.0')) {
|
|
76
77
|
logger.warn(
|
|
77
78
|
'Infinite tracing disabled: this version of Node is not supported (must be >=10.10.0)'
|