newrelic
Version:
New Relic agent
180 lines (160 loc) • 5.82 kB
JavaScript
'use strict'
/*
*
* CONSTANTS
*
*/
var TO_MILLIS = 1e3
/**
* Locus for the complicated logic surrounding the selection of slow
* transaction traces for submission to the collector.
*
* @param {object} config Dictionary containing transaction tracing
* parameters. Required.
*/
function TraceAggregator(config) {
if (!config) throw new Error("Trace aggregator needs configuration at creation.")
/*
* From
*
* https://newrelic.atlassian.net/wiki/display/eng/Transaction+Trace+Collection+Improvements
*
* 5 Transaction Trace Guarantee
*
* For the initial experience problem, the Agent will sample up to 1
* transaction per minute until it has sampled 5 transactions. This
* guarantees that the agent will always report some transaction traces.
* There is no time out for this sampling period - the agent always
* samples until it has collected 5 transactions. The agent doesn't
* simply report the first 5 transactions that it sees because it's
* likely (particularly for a local dev test) that all 5 transactions
* would be associated with one request (a single web page and its
* resources).
*/
this.reported = 0
this.config = config
// Setting up top n capacity.
this.capacity = 1
if (config.transaction_tracer &&
config.transaction_tracer.top_n) {
this.capacity = config.transaction_tracer.top_n
}
// hidden class optimization
this.trace = null
this.syntheticsTraces = []
this.requestTimes = Object.create(null)
this.noTraceSubmitted = 0
}
/**
* For every five harvest cycles (or "minutes"), if no new slow transactions
* have been added, reset the requestTime match and allow a new set of five
* to start populating the Top N Slow Trace list.
*/
TraceAggregator.prototype.resetTimingTracker = function resetTT() {
this.requestTimes = Object.create(null)
this.noTraceSubmitted = 0
}
/**
* Add a trace to the slow trace list, if and only if it fulfills the necessary
* criteria.
*
* @param {Transaction} transaction The transaction, which we need to check
* apdexT, as well as getting the trace.
*/
TraceAggregator.prototype.add = function add(transaction) {
if (
this.config.collect_traces &&
this.config.transaction_tracer &&
this.config.transaction_tracer.enabled &&
transaction &&
transaction.metrics
) {
var trace = transaction.trace
var name = transaction.getFullName()
var duration = trace.getDurationInMillis()
var apdexT = transaction.metrics.apdexT
if (transaction.syntheticsData && this.syntheticsTraces.length < 20) {
this.syntheticsTraces.push(trace)
} else if (this.isBetter(name, duration, apdexT)) {
this.trace = trace
// because of the "first 5" rule, this may or may not be the slowest
if (!this.requestTimes[name] || this.requestTimes[name] < duration) {
this.requestTimes[name] = duration
}
}
}
}
/**
* Reset the trace diversity settings.
*/
TraceAggregator.prototype.reset = function reset() {
this.trace = null
this.syntheticsTraces = []
}
/**
* Determine whether a new trace is more worth keeping than an old one.
* This gets called on every single transactionFinished event, so return as
* quickly as possible and call as few external functions as possible. On the
* converse, there's some complicated logic here, so spell things out.
*
* All specifications are from
* https://newrelic.atlassian.net/wiki/display/eng/Transaction+Trace+Collection+Improvements
*
* @param {string} name Name of this transaction's key metric.
* @param {number} duration Time the transaction took, in milliseconds.
* @param {number} apdexT Apdex tolerating threshold, in seconds.
*/
TraceAggregator.prototype.isBetter = function isBetter(name, duration, apdexT) {
/* 1. If the transaction duration is below the tracing threshold, the
* transaction is skipped.
*
* The threshold for slow traces defaults to apdex_f, which is 4 * apdex_t.
*/
var config = this.config.transaction_tracer
var isOverThreshold
if (config &&
config.transaction_threshold != null &&
config.transaction_threshold !== 'apdex_f' &&
typeof config.transaction_threshold === 'number') {
isOverThreshold = duration >= config.transaction_threshold * TO_MILLIS
} else {
isOverThreshold = duration >= 4 * TO_MILLIS * apdexT
}
if (!isOverThreshold) return false
/* 2. If the transaction duration is less than the duration of the current
* slow transaction, the transaction is skipped.
*/
var slowerThanExisting = true
if (this.trace) {
slowerThanExisting = this.trace.getDurationInMillis() < duration
}
if (!slowerThanExisting) return false
/* We always gather some slow transactions at the start, regardless of
* the size of Top N. This changes the behavior of the rest of the
* decision-making process in some subtle ways.
*/
var hasMetGuarantee = this.reported >= 5
/* 3. If the transaction's name is in the transaction map and its duration
* is less than the response time in the map, it is skipped.
*/
var slowerThanCaptured = true
if (hasMetGuarantee) {
if (this.requestTimes[name]) {
slowerThanCaptured = this.requestTimes[name] < duration
}
}
if (!slowerThanCaptured) return false
/* Not part of enumerated rules, but necessary for Top N support:
* Ensure this name is either already in the request time map
* or that the map still hasn't hit capacity.
*/
if (hasMetGuarantee &&
!this.requestTimes[name] &&
Object.keys(this.requestTimes).length >= this.capacity) {
return false
}
/* 4. The transaction is held as the slowest transaction.
*/
return true
}
module.exports = TraceAggregator