Skip to content

Commit 320737d

Browse files
committed
fix(collector): add back reservoir sampler
1 parent 257190d commit 320737d

4 files changed

Lines changed: 120 additions & 10 deletions

File tree

lib/agent/tracer/collector.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var ExpiringBuffer = require('./expiringBuffer')
66
var uuid = require('node-uuid')
77
var assign = require('lodash.assign')
88
var levels = require('./severity')
9+
var ReservoirSampler = require('./reservoirSampler')
910

1011
var EVENT_TYPE = {
1112
CLIENT_SEND: 'cs',
@@ -37,12 +38,13 @@ function Collector (options) {
3738
this.serviceKey = options.serviceKey
3839
this.mustCollectSeverity = levels.ERROR
3940
this.defaultSeverity = levels.INFO
41+
this.samplerLimit = options.samplerLimit || 100
4042

4143
// init required variables
4244
this._noStack = options.noStack
4345
this._eventTtl = options.eventTtl || 1
4446
this._eventBuffers = { }
45-
this._mustCollectBuffer = []
47+
this._sampler = new ReservoirSampler(this.samplerLimit)
4648
}
4749

4850
Collector.prototype.LEVELS = levels
@@ -410,16 +412,13 @@ Collector.prototype._flushCache = function (cacheId) {
410412
cacheId = cacheId || 'root'
411413
if (this._eventBuffers[cacheId]) {
412414
if (this._eventBuffers[cacheId].severity <= this.mustCollectSeverity) {
413-
Array.prototype.push.apply(
414-
this._mustCollectBuffer,
415-
this._eventBuffers[cacheId].buffer.elements())
415+
this._sampler.add(this._eventBuffers[cacheId].buffer.elements())
416416
}
417417
delete this._eventBuffers[cacheId]
418418
}
419419
}
420420

421421
Collector.prototype.collect = function () {
422-
var result = []
423422
var self = this
424423
Object.keys(this._eventBuffers).forEach(function (key) {
425424
if (self._eventBuffers[key].locked === true) {
@@ -430,7 +429,7 @@ Collector.prototype.collect = function () {
430429
delete self._eventBuffers[key]
431430
}
432431
} else if (levels.gte(self._eventBuffers[key].severity, self.mustCollectSeverity)) {
433-
Array.prototype.push.apply(result, self._eventBuffers[key].buffer.elements())
432+
self._sampler.add(self._eventBuffers[key].buffer.elements())
434433
delete self._eventBuffers[key].buffer
435434
self._eventBuffers[key].zombieTtl = self._eventTtl
436435
} else {
@@ -440,10 +439,10 @@ Collector.prototype.collect = function () {
440439
}
441440
}
442441
})
443-
444-
Array.prototype.push.apply(result, this._mustCollectBuffer)
445-
this._mustCollectBuffer = []
446-
return result
442+
return this._sampler.flush().reduce(function (acc, x) {
443+
Array.prototype.push.apply(acc, x)
444+
return acc
445+
}, [])
447446
}
448447

449448
Collector.prototype.getTransactionId = function (briefcase) {

lib/agent/tracer/collector.spec.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,38 @@ describe('Collector', function () {
646646
expect(tracer.collect()[0].d.r.stack).to.not.exist
647647
})
648648
})
649+
650+
describe('Sampling', function () {
651+
it('happens when there is a big amount of data', function () {
652+
this.sandbox.stub(microtime, 'now', function () {
653+
return 2
654+
})
655+
var tracer = new Collector(assign({ samplerLimit: 10 }, options))
656+
function report () {
657+
var sr = {
658+
payload: { },
659+
duffelBag: {
660+
communicationId: 'commId',
661+
transactionId: 'trId',
662+
severity: tracer.mustCollectSeverity + 1
663+
}
664+
}
665+
var ss = {
666+
payload: { },
667+
briefcase: {
668+
communication: {
669+
id: sr.duffelBag.communicationId,
670+
transactionId: sr.duffelBag.transactionId
671+
},
672+
severity: tracer.mustCollectSeverity
673+
}
674+
}
675+
676+
tracer.serverRecv(sr.payload, sr.duffelBag)
677+
tracer.serverSend(ss.payload, ss.briefcase)
678+
}
679+
for (var i = 0; i < 100; ++i) { report() }
680+
expect(tracer.collect().length).to.be.eql(20)
681+
})
682+
})
649683
})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
function Reservoir (limit) {
3+
this.MAX_ITEMS = limit || 10
4+
5+
this._itemsSeen = 0
6+
this._data = []
7+
}
8+
9+
Reservoir.prototype.add = function (item) {
10+
var inserted = true
11+
if (this._itemsSeen < this.MAX_ITEMS) {
12+
this._data.push(item)
13+
inserted = true
14+
} else {
15+
inserted = this._replace(item)
16+
}
17+
this._itemsSeen++
18+
return inserted
19+
}
20+
21+
Reservoir.prototype.flush = function () {
22+
var data = this._data
23+
this._data = []
24+
this._itemsSeen = 0
25+
return data
26+
}
27+
28+
Reservoir.prototype._replace = function (item) {
29+
var toReplaceIndex = Math.floor(Math.random() * (this._itemsSeen + 2))
30+
if (toReplaceIndex < this.MAX_ITEMS) {
31+
this._data[toReplaceIndex] = item
32+
return true
33+
}
34+
return false
35+
}
36+
37+
module.exports = Reservoir
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
var expect = require('chai').expect
2+
var ReservoirSampler = require('./reservoirSampler')
3+
4+
describe('The Reservoir Sampler', function () {
5+
var itemToAdd = {
6+
name: 'John Coin'
7+
}
8+
9+
describe('when seen count is below the size limit', function () {
10+
it('adds the item', function () {
11+
var sampler = new ReservoirSampler(3)
12+
var isAdded = sampler.add(itemToAdd)
13+
14+
expect(isAdded).to.eql(true)
15+
expect(sampler.flush()).to.eql([itemToAdd])
16+
})
17+
})
18+
19+
describe('when seen count is above the size limit', function () {
20+
it('adds the item if selected', function () {
21+
this.sandbox.stub(Math, 'random', function () {
22+
return 0
23+
})
24+
var sampler = new ReservoirSampler(1)
25+
sampler._itemsSeen = 1
26+
var isAdded = sampler.add(itemToAdd)
27+
expect(isAdded).to.eql(true)
28+
})
29+
30+
it('discards the item if not selected', function () {
31+
this.sandbox.stub(Math, 'random', function () {
32+
return 1
33+
})
34+
var sampler = new ReservoirSampler(1)
35+
sampler._itemsSeen = 1
36+
var isAdded = sampler.add(itemToAdd)
37+
expect(isAdded).to.eql(false)
38+
})
39+
})
40+
})

0 commit comments

Comments
 (0)