newrelic 5.3.0 → 5.6.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.
- package/CONTRIBUTING.md +3 -4
- package/NEWS.md +91 -0
- package/api.js +27 -3
- package/index.js +24 -24
- package/lib/adaptive-sampler.js +8 -9
- package/lib/attributes.js +39 -20
- package/lib/collector/facts.js +6 -1
- package/lib/collector/remote-method.js +1 -1
- package/lib/config/attribute-filter.js +43 -13
- package/lib/config/default.js +5 -1
- package/lib/config/env.js +1 -1
- package/lib/config/index.js +40 -5
- package/lib/errors/index.js +1 -2
- package/lib/header-attributes.js +0 -2
- package/lib/instrumentation/core/http-outbound.js +13 -1
- package/lib/instrumentation/core/http.js +0 -6
- package/lib/instrumentation/memcached.js +3 -2
- package/lib/serverless/aws-lambda.js +1 -6
- package/lib/shim/datastore-shim.js +10 -8
- package/lib/shim/message-shim.js +2 -4
- package/lib/shim/shim.js +12 -0
- package/lib/spans/span-event.js +107 -93
- package/lib/timer.js +3 -1
- package/lib/transaction/index.js +3 -5
- package/lib/transaction/trace/exclusive-time-calculator.js +145 -0
- package/lib/transaction/trace/index.js +18 -29
- package/lib/transaction/trace/segment.js +63 -74
- package/lib/util/attribute-types.js +21 -0
- package/lib/util/byte-limit.js +38 -9
- package/package.json +1 -1
- package/lib/util/sum-children.js +0 -52
package/lib/transaction/index.js
CHANGED
|
@@ -43,7 +43,6 @@ const TRANSPORT_TYPES = {
|
|
|
43
43
|
}
|
|
44
44
|
const TRANSPORT_TYPES_SET = _makeValueSet(TRANSPORT_TYPES)
|
|
45
45
|
const REQUIRED_DT_KEYS = ['ty', 'ac', 'ap', 'tr', 'ti']
|
|
46
|
-
const TRANSACTION_SCOPE = 'transaction'
|
|
47
46
|
const DTPayload = require('./dt-payload')
|
|
48
47
|
const DTPayloadStub = DTPayload.Stub
|
|
49
48
|
|
|
@@ -380,7 +379,6 @@ function finalizeNameFromUri(requestURL, statusCode) {
|
|
|
380
379
|
for (var key in params) {
|
|
381
380
|
if (props.hasOwn(params, key)) {
|
|
382
381
|
this.trace.attributes.addAttribute(
|
|
383
|
-
TRANSACTION_SCOPE,
|
|
384
382
|
DESTS.NONE,
|
|
385
383
|
'request.parameters.' + key,
|
|
386
384
|
params[key]
|
|
@@ -426,7 +424,6 @@ Transaction.prototype._markAsWeb = function _markAsWeb(rawURL) {
|
|
|
426
424
|
for (var key in params) {
|
|
427
425
|
if (props.hasOwn(params, key)) {
|
|
428
426
|
this.trace.attributes.addAttribute(
|
|
429
|
-
TRANSACTION_SCOPE,
|
|
430
427
|
DESTS.NONE,
|
|
431
428
|
'request.parameters.' + key,
|
|
432
429
|
params[key]
|
|
@@ -990,7 +987,9 @@ function addDistributedTraceIntrinsics(attrs) {
|
|
|
990
987
|
Transaction.prototype._calculatePriority = function _calculatePriority() {
|
|
991
988
|
if (this.priority === null) {
|
|
992
989
|
this.priority = Math.random()
|
|
993
|
-
|
|
990
|
+
// We want to separate the priority roll from the decision roll to
|
|
991
|
+
// avoid biasing the priority range
|
|
992
|
+
this.sampled = this.agent.transactionSampler.shouldSample(Math.random())
|
|
994
993
|
if (this.sampled) {
|
|
995
994
|
this.priority += 1
|
|
996
995
|
}
|
|
@@ -1020,7 +1019,6 @@ function addRequestParameters(requestParameters) {
|
|
|
1020
1019
|
for (var key in requestParameters) {
|
|
1021
1020
|
if (props.hasOwn(requestParameters, key)) {
|
|
1022
1021
|
this.trace.attributes.addAttribute(
|
|
1023
|
-
TRANSACTION_SCOPE,
|
|
1024
1022
|
DESTS.NONE,
|
|
1025
1023
|
'request.parameters.' + key,
|
|
1026
1024
|
requestParameters[key]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class ExclusiveCalculator {
|
|
4
|
+
constructor(root) {
|
|
5
|
+
this.toProcess = [root]
|
|
6
|
+
// use a second stack to do a post-order traversal
|
|
7
|
+
this.parentStack = []
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Kicks off the exclusive duration calculation. This is performed
|
|
12
|
+
* using a depth first, postorder traversal over the tree.
|
|
13
|
+
*/
|
|
14
|
+
process() {
|
|
15
|
+
while (this.toProcess.length) {
|
|
16
|
+
const segment = this.toProcess.pop()
|
|
17
|
+
const children = segment.getChildren()
|
|
18
|
+
// when we hit a leaf, calc the exclusive time and report the time
|
|
19
|
+
// range to the parent
|
|
20
|
+
if (children.length === 0) {
|
|
21
|
+
segment._exclusiveDuration = segment.getDurationInMillis()
|
|
22
|
+
if (this.parentStack.length) {
|
|
23
|
+
this.finishLeaf(segment.timer.toRange())
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
// in the case we are processing an internal node, we just push it on the stack
|
|
27
|
+
// and push its children to be processed. all processing will be done after its
|
|
28
|
+
// children are all done (i.e. postorder)
|
|
29
|
+
this.parentStack.push({
|
|
30
|
+
childrenLeft: children.length,
|
|
31
|
+
segment: segment,
|
|
32
|
+
childPairs: []
|
|
33
|
+
})
|
|
34
|
+
for (var i = children.length - 1; i >= 0; --i) {
|
|
35
|
+
this.toProcess.push(children[i])
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Updates the immediate parent in the parent stack that a leaf node has
|
|
43
|
+
* been processed. If the parent isn't expecting any more children to
|
|
44
|
+
* be processed, it pops the stack and propagates the processing to
|
|
45
|
+
* more distant predecessors.
|
|
46
|
+
*
|
|
47
|
+
* @param {Array} childRange An array of start and end time for the finished leaf node
|
|
48
|
+
*/
|
|
49
|
+
finishLeaf(childRange) {
|
|
50
|
+
let parent = this.parentStack[this.parentStack.length - 1]
|
|
51
|
+
// push the current segment's range pair up to the parent's child pairs
|
|
52
|
+
parent.childPairs = merge(parent.childPairs, [childRange])
|
|
53
|
+
// decrement the number of children expected for the current parent; process the
|
|
54
|
+
// parent if it is not expecting any further children to finish (i.e. the number
|
|
55
|
+
// of children left to process is 0).
|
|
56
|
+
while (--parent.childrenLeft === 0) {
|
|
57
|
+
// pull off the finished parent and assign the exclusive duration
|
|
58
|
+
const {segment: finishedParent, childPairs} = this.parentStack.pop()
|
|
59
|
+
const timer = finishedParent.timer
|
|
60
|
+
const finishedEnd = timer.getDurationInMillis() + timer.start
|
|
61
|
+
let duration = finishedParent.getDurationInMillis()
|
|
62
|
+
for (let i = 0; i < childPairs.length; ++i) {
|
|
63
|
+
const pair = childPairs[i]
|
|
64
|
+
// since these are non-overlapping and ordered by start time, the first one
|
|
65
|
+
// to start after the parent's end marks the end of the segments we care
|
|
66
|
+
// about.
|
|
67
|
+
if (pair[0] >= finishedEnd) {
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
duration -= Math.min(pair[1], finishedEnd) - pair[0]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
finishedParent._exclusiveDuration = duration
|
|
74
|
+
parent = this.parentStack[this.parentStack.length - 1]
|
|
75
|
+
// since the parent was potentially a child of another segment, we need to
|
|
76
|
+
// rerun this for the parent's parent till we hit a parent with children yet
|
|
77
|
+
// to be processed.
|
|
78
|
+
if (parent) {
|
|
79
|
+
// merge the current child segments in with the finished parent's range
|
|
80
|
+
const inserted = merge(childPairs, [finishedParent.timer.toRange()])
|
|
81
|
+
// merge the finished parent's merged range into its parent's range
|
|
82
|
+
parent.childPairs = merge(parent.childPairs, inserted)
|
|
83
|
+
} else {
|
|
84
|
+
// in the case where the parent doesn't exist, we are done and can break out.
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function merge(first, second) {
|
|
92
|
+
if (!first.length) {
|
|
93
|
+
return second
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!second.length) {
|
|
97
|
+
return first
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const res = []
|
|
101
|
+
var resIdx = 0
|
|
102
|
+
var firstIdx = 0
|
|
103
|
+
var secondIdx = 0
|
|
104
|
+
// N.B. this is destructive, it will be updating the end times for range arrays in
|
|
105
|
+
// the input arrays. If we need to reuse these arrays for anything, this behavior
|
|
106
|
+
// must be changed.
|
|
107
|
+
var currInterval = first[firstIdx][0] < second[secondIdx][0]
|
|
108
|
+
? first[firstIdx++]
|
|
109
|
+
: second[secondIdx++]
|
|
110
|
+
|
|
111
|
+
while (firstIdx < first.length && secondIdx < second.length) {
|
|
112
|
+
var next = first[firstIdx][0] < second[secondIdx][0]
|
|
113
|
+
? first[firstIdx++]
|
|
114
|
+
: second[secondIdx++]
|
|
115
|
+
if (next[0] <= currInterval[1]) {
|
|
116
|
+
// if the segment overlaps, update the end of the current merged segment
|
|
117
|
+
currInterval[1] = Math.max(next[1], currInterval[1])
|
|
118
|
+
} else {
|
|
119
|
+
// if there is no overlap, start a new merging segment and push the old one
|
|
120
|
+
res[resIdx++] = currInterval
|
|
121
|
+
currInterval = next
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
const firstIsRemainder = firstIdx !== first.length
|
|
127
|
+
const remainder = firstIsRemainder ? first : second
|
|
128
|
+
let remainderIdx = firstIsRemainder ? firstIdx : secondIdx
|
|
129
|
+
|
|
130
|
+
// merge the segments overlapping with the current interval
|
|
131
|
+
while (remainder[remainderIdx] && remainder[remainderIdx][0] <= currInterval[1]) {
|
|
132
|
+
currInterval[1] = Math.max(remainder[remainderIdx++][1], currInterval[1])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
res[resIdx++] = currInterval
|
|
136
|
+
|
|
137
|
+
// append the remaining non-overlapping ranges
|
|
138
|
+
for (;remainderIdx < remainder.length; ++remainderIdx) {
|
|
139
|
+
res[resIdx++] = remainder[remainderIdx]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return res
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = ExclusiveCalculator
|
|
@@ -7,9 +7,9 @@ var logger = require('../../logger').child({component: 'trace'})
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
var {DESTINATIONS} = require('../../config/attribute-filter')
|
|
10
|
-
var NAMES = require('../../metrics/names')
|
|
11
10
|
var FROM_MILLIS = 1e-3
|
|
12
|
-
const
|
|
11
|
+
const ATTRIBUTE_SCOPE = 'transaction'
|
|
12
|
+
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* A Trace holds the root of the Segment graph and produces the final
|
|
@@ -21,7 +21,6 @@ function Trace(transaction) {
|
|
|
21
21
|
if (!transaction) throw new Error('All traces must be associated with a transaction.')
|
|
22
22
|
|
|
23
23
|
this.transaction = transaction
|
|
24
|
-
const filter = transaction.agent.config.attributeFilter
|
|
25
24
|
|
|
26
25
|
this.root = new Segment(transaction, 'ROOT')
|
|
27
26
|
this.root.start()
|
|
@@ -30,15 +29,14 @@ function Trace(transaction) {
|
|
|
30
29
|
this.segmentsSeen = 0
|
|
31
30
|
this.totalTimeCache = null
|
|
32
31
|
|
|
33
|
-
this.custom = new Attributes(
|
|
34
|
-
this.attributes = new Attributes(
|
|
32
|
+
this.custom = new Attributes(ATTRIBUTE_SCOPE, 64)
|
|
33
|
+
this.attributes = new Attributes(ATTRIBUTE_SCOPE)
|
|
35
34
|
|
|
36
35
|
// sending displayName if set by user
|
|
37
36
|
var displayName = transaction.agent.config.getDisplayHost()
|
|
38
37
|
var hostName = transaction.agent.config.getHostnameSafe()
|
|
39
38
|
if (displayName !== hostName) {
|
|
40
39
|
this.attributes.addAttribute(
|
|
41
|
-
TRANSACTION_SCOPE,
|
|
42
40
|
DESTINATIONS.TRANS_COMMON,
|
|
43
41
|
'host.displayName',
|
|
44
42
|
displayName
|
|
@@ -52,12 +50,16 @@ function Trace(transaction) {
|
|
|
52
50
|
* segments that support recording.
|
|
53
51
|
*/
|
|
54
52
|
Trace.prototype.end = function end() {
|
|
55
|
-
|
|
53
|
+
const segments = [this.root]
|
|
56
54
|
|
|
57
55
|
while (segments.length) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const segment = segments.pop()
|
|
57
|
+
segment.finalize()
|
|
58
|
+
|
|
59
|
+
const children = segment.getChildren()
|
|
60
|
+
for (let i = 0; i < children.length; ++i) {
|
|
61
|
+
segments.push(children[i])
|
|
62
|
+
}
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -89,10 +91,11 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
|
|
|
89
91
|
// the spans as seen.
|
|
90
92
|
spanAggregator.addSegment(segment, segmentInfo.parentId)
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const nodes = segment.getChildren()
|
|
95
|
+
for (let i = 0; i < nodes.length; ++i) {
|
|
96
|
+
const node = new DTTraceNode(nodes[i], segment.id)
|
|
97
|
+
toProcess.push(node)
|
|
98
|
+
}
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
}
|
|
@@ -102,15 +105,6 @@ function DTTraceNode(segment, parentId) {
|
|
|
102
105
|
this.parentId = parentId
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
function _softEndSegment(segment) {
|
|
106
|
-
if (segment.timer.softEnd()) {
|
|
107
|
-
segment._updateRootTimer()
|
|
108
|
-
// timer.softEnd() returns true if the timer was ended prematurely, so
|
|
109
|
-
// in that case we can name the segment as truncated
|
|
110
|
-
segment.name = NAMES.TRUNCATED.PREFIX + segment.name
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
108
|
/**
|
|
115
109
|
* Add a child to the list of segments.
|
|
116
110
|
*
|
|
@@ -157,12 +151,7 @@ Trace.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
|
|
|
157
151
|
)
|
|
158
152
|
}
|
|
159
153
|
|
|
160
|
-
this.custom.addAttribute(
|
|
161
|
-
TRANSACTION_SCOPE,
|
|
162
|
-
DESTINATIONS.TRANS_SCOPE,
|
|
163
|
-
key,
|
|
164
|
-
value
|
|
165
|
-
)
|
|
154
|
+
this.custom.addAttribute(DESTINATIONS.TRANS_SCOPE, key, value)
|
|
166
155
|
}
|
|
167
156
|
|
|
168
157
|
/**
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var hashes = require('../../util/hashes')
|
|
9
|
-
const Attributes = require('../../attributes')
|
|
3
|
+
const {DESTINATIONS} = require('../../config/attribute-filter')
|
|
4
|
+
const logger = require('../../logger').child({component: 'segment'})
|
|
5
|
+
const Timer = require('../../timer')
|
|
6
|
+
const urltils = require('../../util/urltils')
|
|
7
|
+
const hashes = require('../../util/hashes')
|
|
10
8
|
|
|
9
|
+
const Attributes = require('../../attributes')
|
|
10
|
+
const ExclusiveCalculator = require('./exclusive-time-calculator')
|
|
11
11
|
|
|
12
|
+
const NAMES = require('../../metrics/names')
|
|
12
13
|
const INSTANCE_UNKNOWN = 'unknown'
|
|
13
14
|
const STATE = {
|
|
14
15
|
EXTERNAL: 'EXTERNAL',
|
|
15
16
|
CALLBACK: 'CALLBACK'
|
|
16
17
|
}
|
|
17
|
-
const
|
|
18
|
+
const ATTRIBUTE_SCOPE = 'segment'
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Initializes the segment and binds the recorder to itself, if provided.
|
|
@@ -49,8 +51,7 @@ function TraceSegment(transaction, name, recorder) {
|
|
|
49
51
|
transaction.addRecorder(recorder.bind(null, this))
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
this.attributes = new Attributes({filter})
|
|
54
|
+
this.attributes = new Attributes(ATTRIBUTE_SCOPE)
|
|
54
55
|
|
|
55
56
|
this.children = []
|
|
56
57
|
|
|
@@ -78,8 +79,7 @@ function TraceSegment(transaction, name, recorder) {
|
|
|
78
79
|
TraceSegment.prototype.addAttribute =
|
|
79
80
|
function addAttribute(key, value, truncateExempt = false) {
|
|
80
81
|
this.attributes.addAttribute(
|
|
81
|
-
SEGMENT_SCOPE,
|
|
82
|
-
DESTINATIONS.TRANS_SEGMENT,
|
|
82
|
+
DESTINATIONS.SEGMENT_SCOPE,
|
|
83
83
|
key,
|
|
84
84
|
value,
|
|
85
85
|
truncateExempt
|
|
@@ -126,14 +126,9 @@ function captureDBInstanceAttributes(host, port, database) {
|
|
|
126
126
|
if (!host || host === 'UNKNOWN_BOX') { // Config's default name of a host.
|
|
127
127
|
host = INSTANCE_UNKNOWN
|
|
128
128
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
host,
|
|
134
|
-
port_path_or_id: String(port)
|
|
135
|
-
}
|
|
136
|
-
)
|
|
129
|
+
|
|
130
|
+
this.addAttribute('host', host)
|
|
131
|
+
this.addAttribute('port_path_or_id', String(port))
|
|
137
132
|
}
|
|
138
133
|
}
|
|
139
134
|
|
|
@@ -182,10 +177,7 @@ TraceSegment.prototype.markAsWeb = function markAsWeb() {
|
|
|
182
177
|
var traceAttrs = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE)
|
|
183
178
|
Object.keys(traceAttrs).forEach((key) => {
|
|
184
179
|
if (!this.attributes.has(key)) {
|
|
185
|
-
this.addAttribute(
|
|
186
|
-
key,
|
|
187
|
-
traceAttrs[key]
|
|
188
|
-
)
|
|
180
|
+
this.addAttribute(key, traceAttrs[key])
|
|
189
181
|
}
|
|
190
182
|
})
|
|
191
183
|
}
|
|
@@ -221,6 +213,20 @@ TraceSegment.prototype.end = function end() {
|
|
|
221
213
|
this._updateRootTimer()
|
|
222
214
|
}
|
|
223
215
|
|
|
216
|
+
TraceSegment.prototype.finalize = function finalize() {
|
|
217
|
+
if (this.timer.softEnd()) {
|
|
218
|
+
this._updateRootTimer()
|
|
219
|
+
// timer.softEnd() returns true if the timer was ended prematurely, so
|
|
220
|
+
// in that case we can name the segment as truncated
|
|
221
|
+
this.name = NAMES.TRUNCATED.PREFIX + this.name
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.addAttribute(
|
|
225
|
+
'nr_exclusive_duration_millis',
|
|
226
|
+
this.getExclusiveDurationInMillis()
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
224
230
|
/**
|
|
225
231
|
* Helper to set the end of the root timer to this segment's root if it is later
|
|
226
232
|
* in time.
|
|
@@ -309,17 +315,12 @@ function _setExclusiveDurationInMillis(duration) {
|
|
|
309
315
|
TraceSegment.prototype.getExclusiveDurationInMillis = getExclusiveDurationInMillis
|
|
310
316
|
|
|
311
317
|
function getExclusiveDurationInMillis() {
|
|
312
|
-
if (this._exclusiveDuration)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (this.children.length > 0) {
|
|
318
|
-
// convert the list of start, duration pairs to start, end pairs
|
|
319
|
-
total -= sumChildren(this._getChildPairs(end), end)
|
|
318
|
+
if (this._exclusiveDuration == null) {
|
|
319
|
+
// Calculate the exclusive time for the subtree rooted at `this`
|
|
320
|
+
const calculator = new ExclusiveCalculator(this)
|
|
321
|
+
calculator.process()
|
|
320
322
|
}
|
|
321
|
-
|
|
322
|
-
return total
|
|
323
|
+
return this._exclusiveDuration
|
|
323
324
|
}
|
|
324
325
|
|
|
325
326
|
TraceSegment.prototype.getChildren = function getChildren() {
|
|
@@ -401,59 +402,47 @@ TraceSegment.prototype._getChildPairs = function _getChildPairs(end) {
|
|
|
401
402
|
*/
|
|
402
403
|
TraceSegment.prototype.toJSON = function toJSON() {
|
|
403
404
|
// use depth-first search on the segment tree using stack
|
|
404
|
-
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
405
|
+
const resultDest = []
|
|
406
|
+
// array of objects relating a segment and the destination for its
|
|
407
|
+
// serialized data.
|
|
408
|
+
const segmentsToProcess = [{
|
|
409
|
+
segment: this,
|
|
410
|
+
destination: resultDest
|
|
411
|
+
}]
|
|
410
412
|
|
|
411
413
|
while (segmentsToProcess.length !== 0) {
|
|
412
|
-
const segment = segmentsToProcess.pop()
|
|
413
|
-
|
|
414
|
-
segment.addAttribute(
|
|
415
|
-
'nr_exclusive_duration_millis',
|
|
416
|
-
segment.getExclusiveDurationInMillis()
|
|
417
|
-
)
|
|
414
|
+
const {segment, destination} = segmentsToProcess.pop()
|
|
418
415
|
|
|
419
|
-
|
|
420
|
-
|
|
416
|
+
const start = segment.timer.startedRelativeTo(segment.transaction.trace.root.timer)
|
|
417
|
+
const duration = segment.getDurationInMillis()
|
|
421
418
|
|
|
422
|
-
|
|
419
|
+
const segmentChildren = segment.getCollectedChildren()
|
|
420
|
+
const childArray = []
|
|
423
421
|
|
|
424
|
-
|
|
422
|
+
// push serialized data into the specified destination
|
|
423
|
+
destination.push([
|
|
425
424
|
start,
|
|
426
425
|
start + duration,
|
|
427
426
|
segment.name,
|
|
428
427
|
segment.getAttributes(),
|
|
429
|
-
|
|
430
|
-
]
|
|
431
|
-
|
|
432
|
-
if (resultTreeJson === null) {
|
|
433
|
-
resultTreeJson = serializedSegment
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (parentStack.length !== 0) {
|
|
437
|
-
// get last visited parent
|
|
438
|
-
var parent = parentStack[parentStack.length - 1]
|
|
439
|
-
|
|
440
|
-
var parentChildren = parent[0][4]
|
|
441
|
-
var childIndex = --parent[1]
|
|
442
|
-
|
|
443
|
-
parentChildren[childIndex] = serializedSegment
|
|
444
|
-
|
|
445
|
-
// if the parent received all its children data, remove the parent from the stack
|
|
446
|
-
if (childIndex === 0) {
|
|
447
|
-
parentStack.pop()
|
|
448
|
-
}
|
|
449
|
-
}
|
|
428
|
+
childArray
|
|
429
|
+
])
|
|
450
430
|
|
|
451
431
|
if (segmentChildren.length) {
|
|
452
|
-
|
|
453
|
-
|
|
432
|
+
// push the children and the parent's children array into the stack.
|
|
433
|
+
// to preserve the chronological order of the children, push them
|
|
434
|
+
// onto the stack backwards (so the first one created is on top).
|
|
435
|
+
for (var i = segmentChildren.length - 1; i >= 0; --i) {
|
|
436
|
+
segmentsToProcess.push({
|
|
437
|
+
segment: segmentChildren[i],
|
|
438
|
+
destination: childArray
|
|
439
|
+
})
|
|
440
|
+
}
|
|
454
441
|
}
|
|
455
442
|
}
|
|
456
|
-
|
|
443
|
+
|
|
444
|
+
// pull the result out of the array we serialized it into
|
|
445
|
+
return resultDest[0]
|
|
457
446
|
}
|
|
458
447
|
|
|
459
448
|
module.exports = TraceSegment
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const VALID_ATTR_TYPES = new Set([
|
|
4
|
+
'string',
|
|
5
|
+
'number',
|
|
6
|
+
'boolean'
|
|
7
|
+
])
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Checks incoming attribute value against valid types:
|
|
11
|
+
* string, number, & boolean.
|
|
12
|
+
*
|
|
13
|
+
* @param {*} val
|
|
14
|
+
*
|
|
15
|
+
* @return {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function isValidType(val) {
|
|
18
|
+
return VALID_ATTR_TYPES.has(typeof val)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = isValidType
|
package/lib/util/byte-limit.js
CHANGED
|
@@ -7,7 +7,20 @@
|
|
|
7
7
|
* @param {number} limit - String byte limit
|
|
8
8
|
*/
|
|
9
9
|
function isValidLength(str, limit) {
|
|
10
|
-
return Buffer.byteLength(str, 'utf8')
|
|
10
|
+
return Buffer.byteLength(str, 'utf8') <= limit
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the relative position of the end of the string (in bytes) and the limit.
|
|
15
|
+
* >1 if the string is longer than the limit
|
|
16
|
+
* 0 if the string is at the limit
|
|
17
|
+
* <1 if the string is shorter than the limit
|
|
18
|
+
*
|
|
19
|
+
* @param {string} str
|
|
20
|
+
* @param {number} limit - String byte limit
|
|
21
|
+
*/
|
|
22
|
+
function compareLength(str, limit) {
|
|
23
|
+
return Buffer.byteLength(str) - limit
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
/**
|
|
@@ -22,19 +35,35 @@ function isValidLength(str, limit) {
|
|
|
22
35
|
*/
|
|
23
36
|
function truncate(val, limit) {
|
|
24
37
|
// First truncation handles the simple case of only one-byte characters.
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
val = val.substring(0, limit)
|
|
39
|
+
if (isValidLength(val, limit)) {
|
|
40
|
+
return val
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
|
|
29
44
|
// Our limitation is on byte length, and the string could contain multi-byte
|
|
30
|
-
// characters. Doing a byte-substring could chop a character in half.
|
|
31
|
-
//
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
// characters. Doing a byte-substring could chop a character in half. Instead
|
|
46
|
+
// we do a binary search over the byte length of the substrings.
|
|
47
|
+
var substrLen = val.length
|
|
48
|
+
var delta = Math.ceil(substrLen / 2)
|
|
49
|
+
var cmpVal = compareLength(val.substring(0, substrLen), limit)
|
|
50
|
+
|
|
51
|
+
// Continue the binary search till:
|
|
52
|
+
// 1) The string is the desired length (i.e. cmpVal = 0) OR
|
|
53
|
+
// 2) The desired string must split a character to acheive the desired byte length
|
|
54
|
+
// In this case, we should cut the character that would be split.
|
|
55
|
+
// (i.e. delta > 1 character OR the string is larger than the limit)
|
|
56
|
+
var substr
|
|
57
|
+
while (cmpVal && (cmpVal > 0 || delta > 1)) {
|
|
58
|
+
substrLen = cmpVal < 0 ? substrLen + delta : substrLen - delta
|
|
59
|
+
substr = val.substring(0, substrLen)
|
|
60
|
+
cmpVal = compareLength(substr, limit)
|
|
61
|
+
delta = Math.ceil(delta / 2)
|
|
35
62
|
}
|
|
36
|
-
|
|
63
|
+
|
|
64
|
+
return substr
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
module.exports.isValidLength = isValidLength
|
|
68
|
+
module.exports.compareLength = compareLength
|
|
40
69
|
module.exports.truncate = truncate
|
package/package.json
CHANGED
package/lib/util/sum-children.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Given an ordered list of disjoint intervals and a new interval to fold into
|
|
5
|
-
* it, determine if the new interval is a sub-interval (in which case it's
|
|
6
|
-
* redundant), an overlapping interval (in which case, replace the most recent
|
|
7
|
-
* interval on the list with an interval representing the union of the new and
|
|
8
|
-
* last intervals), or otherwise (it's disjoint to what we already
|
|
9
|
-
* have, in which case add it to the list). Meant to be used with
|
|
10
|
-
* Array.reduce().
|
|
11
|
-
*
|
|
12
|
-
* Assumes the list being reduced is sorted by interval start time.
|
|
13
|
-
*
|
|
14
|
-
* @param {Array} accum The accumulated list of reduced intervals.
|
|
15
|
-
* @param {Array} newest A new pair of range start and end to compare to the
|
|
16
|
-
* existing intervals.
|
|
17
|
-
*
|
|
18
|
-
* @return {Array} A list of intervals updated to include the new interval.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
function sumChildren(pairs, parentEnd) {
|
|
22
|
-
if (!pairs.length) return 0
|
|
23
|
-
|
|
24
|
-
pairs.sort(function cb_sort(a, b) {
|
|
25
|
-
return a[0] - b[0]
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
var start = pairs[0][0]
|
|
30
|
-
var end = start
|
|
31
|
-
var diff = 0
|
|
32
|
-
var segmentEnd
|
|
33
|
-
var pair
|
|
34
|
-
|
|
35
|
-
for (var i = 0, l = pairs.length; i < l; ++i) {
|
|
36
|
-
pair = pairs[i]
|
|
37
|
-
|
|
38
|
-
if (pair[0] > parentEnd) break
|
|
39
|
-
segmentEnd = pair[1] > parentEnd ? parentEnd : pair[1]
|
|
40
|
-
|
|
41
|
-
if (pair[0] > end) {
|
|
42
|
-
diff += pair[0] - end
|
|
43
|
-
end = segmentEnd
|
|
44
|
-
} else if (segmentEnd > end) {
|
|
45
|
-
end = segmentEnd
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return end - start - diff
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
module.exports = sumChildren
|