newrelic
Version:
New Relic agent
138 lines (118 loc) • 3.44 kB
JavaScript
/*
* 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