newrelic
Version:
New Relic agent
170 lines (146 loc) • 4.85 kB
JavaScript
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
'use strict'
const Sampler = require('./sampler')
class AdaptiveSampler extends Sampler {
/**
*
* @param {object} opts Sampler options.
* @param {Agent} opts.agent - The New Relic agent instance.
* @param {number} opts.period - The time period over which to collect samples (ms).
* @param {number} opts.target - The target number of samples (transactions) to collect.
* @param {boolean} opts.serverless - Indicates if the environment is serverless.
*/
constructor(opts) {
super() // no-op
this._serverless = opts.serverless
this._seen = 0
this._sampled = 0
this._samplingPeriod = 0
// sampling target cannot be a float
// config should enforce that values outside
// of [1,120] are not allowed
this._samplingTarget = parseInt(opts.target, 10)
this._maxSamples = 2 * this._samplingTarget
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
}
applySamplingDecision({ transaction, tracestate, partialType }) {
if (!transaction) return
transaction.partialType = partialType
if (tracestate) {
// Explicitly set sampled and priority from tracestate intrinsics if available
transaction.sampled = tracestate?.intrinsics ? tracestate.isSampled : null
transaction.priority = tracestate?.intrinsics ? tracestate.priority : null
return
}
// If a tracestate is not defined, then do our normal priority calculation.
const initPriority = Sampler.generatePriority()
transaction.sampled = this.shouldSample(initPriority)
// only add 1 to priority when doing a full trace
transaction.priority = transaction.sampled && !partialType ? Sampler.incrementPriority(initPriority, 1) : initPriority
}
/**
* 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