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.
@@ -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
- this.sampled = this.agent.transactionSampler.shouldSample(this)
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 TRANSACTION_SCOPE = 'transaction'
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({limit: 64, filter})
34
- this.attributes = new Attributes({filter})
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
- var segments = [this.root]
53
+ const segments = [this.root]
56
54
 
57
55
  while (segments.length) {
58
- var segment = segments.pop()
59
- _softEndSegment(segment)
60
- Array.prototype.push.apply(segments, segment.getChildren())
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
- Array.prototype.push.apply(
93
- toProcess,
94
- segment.getChildren().map(child => new DTTraceNode(child, segment.id))
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
- var {DESTINATIONS} = require('../../config/attribute-filter')
4
- var logger = require('../../logger').child({component: 'segment'})
5
- var sumChildren = require('../../util/sum-children')
6
- var Timer = require('../../timer')
7
- var urltils = require('../../util/urltils')
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 SEGMENT_SCOPE = 'segment'
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
- const filter = transaction.agent.config.attributeFilter
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
- this.attributes.addAttributes(
130
- SEGMENT_SCOPE,
131
- DESTINATIONS.TRANS_SEGMENT,
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) return this._exclusiveDuration
313
-
314
- var total = this.getDurationInMillis()
315
- var end = this.timer.toRange()[1]
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
- var segmentsToProcess = [this]
405
- // used to keep track of the last parent to add child JSONs to, it will hold
406
- // pairs of the parent serialized segment and number of children it is expecting
407
- // to have added
408
- var parentStack = []
409
- var resultTreeJson = null
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
- var start = segment.timer.startedRelativeTo(segment.transaction.trace.root.timer)
420
- var duration = segment.getDurationInMillis()
416
+ const start = segment.timer.startedRelativeTo(segment.transaction.trace.root.timer)
417
+ const duration = segment.getDurationInMillis()
421
418
 
422
- var segmentChildren = segment.getCollectedChildren()
419
+ const segmentChildren = segment.getCollectedChildren()
420
+ const childArray = []
423
421
 
424
- var serializedSegment = [
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
- new Array(segmentChildren.length)
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
- parentStack.push([serializedSegment, segmentChildren.length])
453
- segmentsToProcess = segmentsToProcess.concat(segmentChildren)
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
- return resultTreeJson
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
@@ -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') < limit
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
- if (!isValidLength(val + 1)) {
26
- val = val.substring(0, limit)
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. We need
31
- // to pop the remaining characters off one by one until we have a good length.
32
- var l = val.length
33
- while (!isValidLength(val, limit + 1)) {
34
- val = val.substring(0, --l)
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
- return val
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "5.3.0",
3
+ "version": "5.6.2",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "licenses": [
6
6
  {
@@ -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