forked from newrelic/node-newrelic
-
Notifications
You must be signed in to change notification settings - Fork 0
/
adaptive-sampler.js
137 lines (118 loc) · 3.36 KB
/
adaptive-sampler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
'use strict'
class AdaptiveSampler {
constructor(opts) {
this._serverless = opts.serverless
this._seen = 0
this._sampled = 0
this._samplingPeriod = 0
this._samplingTarget = opts.target
this._maxSamples = 2 * opts.target
this._samplingThreshold = 0
this._resetCount = 0
this._resetInterval = null
this.samplingPeriod = opts.period
if (this._serverless) {
this._windowStart = null
opts.agent.on('transactionStarted', this.maybeUpdateWindow.bind(this))
}
}
get sampled() {
return this._sampled
}
get samplingThreshold() {
return this._samplingThreshold
}
get samplingTarget() {
return this._samplingTarget
}
set samplingTarget(target) {
this._samplingTarget = target
this._maxSamples = 2 * target
this._adjustStats(this._samplingTarget)
}
get samplingPeriod() {
return this._samplingPeriod
}
set samplingPeriod(period) {
this._samplingPeriod = period
if (!this._serverless) {
clearInterval(this._resetInterval)
if (period) {
this._resetInterval = setInterval(() => this._reset(), period)
this._resetInterval.unref()
}
}
}
/**
* Used to determine if the sampling window should be reset based on the start time
* of the provided transaction.
*
* @param {object} transaction - The transaction to compare against the current
* window.
*/
maybeUpdateWindow(transaction) {
const timestamp = transaction.timer.start
if (!this._windowStart || timestamp - this._windowStart >= this._samplingPeriod) {
this._windowStart = timestamp
this._reset()
}
}
/**
* Determines if an object should be sampled based on the object's priority and
* the number of objects sampled in this window.
*
* @param {number} roll - The number to compare against the threshold
* @returns {boolean} True if the object should be sampled.
*/
shouldSample(roll) {
++this._seen
if (roll >= this._samplingThreshold) {
this._incrementSampled()
return true
}
return false
}
/**
* Starts a new sample period after adjusting the sampling statistics.
*/
_reset() {
++this._resetCount
this._adjustStats(this._samplingTarget)
this._seen = 0
this._sampled = 0
}
/**
* Increments the sampled counter and adjusted the sampling threshold to maintain
* a steady sample rate.
*/
_incrementSampled() {
if (++this._sampled >= this._samplingTarget) {
// For the first sample window we take the first 10 transactions and only
// the first 10.
let adjustedTarget = 0
if (this._resetCount > 0) {
const target = this._samplingTarget
const ratio = target / this._sampled
const max = target / this._maxSamples
adjustedTarget = Math.pow(target, ratio) - Math.pow(target, max)
}
this._adjustStats(adjustedTarget)
}
}
/**
* Adjusts the statistics used to determine if an object should be sampled.
*
* @param {number} target - The target number of objects to sample.
*/
_adjustStats(target) {
if (this._seen) {
const ratio = Math.min(target / this._seen, 1)
this._samplingThreshold = 1 - ratio
}
}
}
module.exports = AdaptiveSampler