Skip to content

Commit

Permalink
Merge pull request newrelic#955 from NodeJS-agent/nwolfe/agent-reset
Browse files Browse the repository at this point in the history
Agent Restart Now Resets Naming Rules
  • Loading branch information
Bryan Clement committed Sep 27, 2016
2 parents b931360 + b19f98f commit 587e2f2
Show file tree
Hide file tree
Showing 20 changed files with 353 additions and 204 deletions.
146 changes: 86 additions & 60 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,83 +55,63 @@ function Agent(config) {

if (!config) throw new Error("Agent must be created with a configuration!")

// The agent base attributes which last throughout its lifetime.
this._state = 'stopped'

this.config = config
this.config.on('apdex_t', this._apdexTChange.bind(this))
this.config.on('data_report_period', this._harvesterIntervalChange.bind(this))

// high security mode
this.config.on('agent_enabled', this._enabledChange.bind(this))

// insights events
this.events = new Reservoir(this.config.transaction_events.max_samples_per_minute)
this.customEvents = new Reservoir(this.config.custom_insights_events.max_samples_stored)

this.environment = require('./environment')
this.version = this.config.version

this.collector = new CollectorAPI(this)
this.config.on('change', this._configChange.bind(this))

// error tracing
this.errors = new ErrorAggregator(this.config)
// Reset the agent to add all the sub-objects it needs. These object are the
// ones that get re-created if the agent is told to restart from the collector.
this.events = null
this.customEvents = null
this.errors = null
this.mapper = null
this.metricNameNormalizer = null
this.metrics = null
this.transactionNameNormalizer = null
this.urlNormalizer = null
this.txSegmentNormalizer = null
this.userNormalizer = null
this.reset()

// Transaction tracing.
this.tracer = this._setupTracer()
this.traces = new TraceAggregator(this.config)

// query tracing
// Query tracing.
this.queries = new QueryTracer(this.config)

// metrics
this.mapper = new MetricMapper()
this.metricNameNormalizer = new MetricNormalizer(this.config, 'metric name')
this.config.on(
'metric_name_rules',
this.metricNameNormalizer.load.bind(this.metricNameNormalizer)
)
this.metrics = new Metrics(this.config.apdex_t, this.mapper, this.metricNameNormalizer)

// transaction naming
this.transactionNameNormalizer = new MetricNormalizer(this.config, 'transaction name')
this.config.on(
'transaction_name_rules',
this.transactionNameNormalizer.load.bind(this.transactionNameNormalizer)
)
this.urlNormalizer = new MetricNormalizer(this.config, 'URL')
this.config.on('url_rules', this.urlNormalizer.load.bind(this.urlNormalizer))

// segment term based tx renaming for MGI mitigation
this.txSegmentNormalizer = new TxSegmentNormalizer()
this.config.on(
'transaction_segment_terms',
this.txSegmentNormalizer.load.bind(this.txSegmentNormalizer)
)

// user naming and ignoring rules
this.userNormalizer = new MetricNormalizer(this.config, 'user')
this.userNormalizer.loadFromConfig()

// transaction traces
this.tracer = this._setupTracer()
this.traces = new TraceAggregator(this.config)
// Set up all the configuration events the agent needs to listen for.
var self = this
this.config.on('apdex_t', this._apdexTChange.bind(this))
this.config.on('data_report_period', this._harvesterIntervalChange.bind(this))
this.config.on('agent_enabled', this._enabledChange.bind(this))
this.config.on('change', this._configChange.bind(this))
this.config.on('metric_name_rules', function updateMetricNameNormalizer() {
self.metricNameNormalizer.load.apply(self.metricNameNormalizer, arguments)
})
this.config.on('transaction_name_rules', function updateTransactionNameNormalizer() {
self.transactionNameNormalizer.load.apply(self.transactionNameNormalizer, arguments)
})
this.config.on('url_rules', function updateUrlNormalizer() {
self.urlNormalizer.load.apply(self.urlNormalizer, arguments)
})
this.config.on('transaction_segment_terms', function updateSegmentNormalizer() {
self.txSegmentNormalizer.load.apply(self.txSegmentNormalizer, arguments)
})

// entity tracking metrics
// Entity tracking metrics.
this.totalActiveSegments = 0
this.segmentsCreatedInHarvest = 0
this.segmentsClearedInHarvest = 0
this.activeTransactions = 0

// supportability
if (this.config.debug.internal_metrics) {
this.config.debug.supportability = new Metrics(
this.config.apdex_t,
this.mapper,
this.metricNameNormalizer
)
}

// hidden class
// Hidden class optimizations.
this.harvesterHandle = null

// agent events
// Finally, add listeners for the agent's own events.
this.on('transactionFinished', this._transactionFinished.bind(this))
}
util.inherits(Agent, EventEmitter)
Expand Down Expand Up @@ -233,6 +213,52 @@ Agent.prototype.stop = function stop(callback) {
}
}

/**
* Builds all of the sub-properties of the agent that rely on configurations.
*/
Agent.prototype.reset = function reset() {
// Insights events.
if (!this.events) {
this.events = new Reservoir()
}
this.events.setLimit(this.config.transaction_events.max_samples_per_minute)
if (!this.customEvents) {
this.customEvents = new Reservoir()
}
this.customEvents.setLimit(this.config.custom_insights_events.max_samples_stored)

// Error tracing.
if (!this.errors) {
this.errors = new ErrorAggregator(this.config)
}
this.errors.reconfigure(this.config)

// Metrics.
this.mapper = new MetricMapper()
this.metricNameNormalizer = new MetricNormalizer(this.config, 'metric name')
this.metrics = new Metrics(this.config.apdex_t, this.mapper, this.metricNameNormalizer)

// Transaction naming.
this.transactionNameNormalizer = new MetricNormalizer(this.config, 'transaction name')
this.urlNormalizer = new MetricNormalizer(this.config, 'URL')

// Segment term based tx renaming for MGI mitigation.
this.txSegmentNormalizer = new TxSegmentNormalizer()

// User naming and ignoring rules.
this.userNormalizer = new MetricNormalizer(this.config, 'user')
this.userNormalizer.loadFromConfig()

// Supportability.
if (this.config.debug.internal_metrics) {
this.config.debug.supportability = new Metrics(
this.config.apdex_t,
this.mapper,
this.metricNameNormalizer
)
}
}

/**
* On agent startup, an interval timer is started that calls this method once
* a minute, which in turn invokes the pieces of the harvest cycle. It calls
Expand Down
5 changes: 5 additions & 0 deletions lib/errors/aggregator.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,8 @@ ErrorAggregator.prototype.clearErrors = function clearErrors() {
this.webTransactionErrorCount = 0
this.otherTransactionErrorCount = 0
}

ErrorAggregator.prototype.reconfigure = function reconfigure(config) {
this.config = config
this.events.setLimit(this.config.error_collector.max_event_samples_stored)
}
1 change: 1 addition & 0 deletions lib/metrics/normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ util.inherits(MetricNormalizer, EventEmitter)
*/
MetricNormalizer.prototype.load = function load(json) {
if (json) {
this.rules = []
logger.debug("Received %s %s normalization rule(s) from the server",
json.length, this.type)

Expand Down
7 changes: 7 additions & 0 deletions lib/reservoir.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ Reservoir.prototype.merge = function merge(items) {
}
}

Reservoir.prototype.setLimit = function setLimit(newLimit) {
this.limit = newLimit
if (this._data.length > newLimit) {
this._data = this._data.slice(0, newLimit)
}
}

module.exports = Reservoir
1 change: 0 additions & 1 deletion lib/transaction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ Transaction.prototype.setName = function setName(requestURL, statusCode) {
transactionName: this.name}, 'Setting transaction name')

this._partialName = this.nameState.getName()

this.url = urltils.scrub(requestURL)
this.statusCode = statusCode

Expand Down
25 changes: 16 additions & 9 deletions test/integration/connection-failure/413.tap.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
'use strict'

var path = require('path')
, test = require('tap').test
, nock = require('nock')
, configurator = require('../../../lib/config.js')
, Agent = require('../../../lib/agent.js')
, Transaction = require('../../../lib/transaction')
, mockAWSInfo = require('../../lib/nock/aws.js').mockAWSInfo

var test = require('tap').test
var nock = require('nock')
var configurator = require('../../../lib/config.js')
var Agent = require('../../../lib/agent.js')
var Transaction = require('../../../lib/transaction')
var mockAWSInfo = require('../../lib/nock/aws.js').mockAWSInfo


// XXX Remove this when deprecating Node v0.8.
if (!global.setImmediate) {
var timers = require('timers')
timers.setImmediate = global.setImmediate = function(fn) {
global.setTimeout(fn, 0)
}
}

nock.disableNetConnect()

Expand Down Expand Up @@ -59,7 +66,7 @@ test("harvesting with a mocked collector that returns 413 after connect", functi
// need sample data to give the harvest cycle something to send
agent.errors.add(transaction, new Error('test error'))

transaction.end(function() {
transaction.end(function() {
agent.traces.trace = transaction.trace

agent.harvest(function cb_harvest(error) {
Expand Down
23 changes: 15 additions & 8 deletions test/integration/connection-failure/415.tap.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
'use strict'

var path = require('path')
, test = require('tap').test
, nock = require('nock')
, configurator = require('../../../lib/config.js')
, Agent = require('../../../lib/agent.js')
, Transaction = require('../../../lib/transaction')
, mockAWSInfo = require('../../lib/nock/aws.js').mockAWSInfo
var test = require('tap').test
var nock = require('nock')
var configurator = require('../../../lib/config.js')
var Agent = require('../../../lib/agent.js')
var Transaction = require('../../../lib/transaction')
var mockAWSInfo = require('../../lib/nock/aws.js').mockAWSInfo


// XXX Remove this when deprecating Node v0.8.
if (!global.setImmediate) {
global.setImmediate = function(fn) {
global.setTimeout(fn, 0)
}
}

nock.disableNetConnect()

Expand Down Expand Up @@ -58,7 +65,7 @@ test("harvesting with a mocked collector that returns 415 after connect", functi

// need sample data to give the harvest cycle something to send
agent.errors.add(transaction, new Error('test error'))
transaction.end(function() {
transaction.end(function() {
agent.traces.trace = transaction.trace

agent.harvest(function cb_harvest(error) {
Expand Down
22 changes: 14 additions & 8 deletions test/integration/connection-failure/503.tap.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
'use strict'

var path = require('path')
, test = require('tap').test
, nock = require('nock')
, configurator = require('../../../lib/config.js')
, Agent = require('../../../lib/agent.js')
, Transaction = require('../../../lib/transaction')
, mockAWSInfo = require('../../lib/nock/aws.js').mockAWSInfo

var test = require('tap').test
var nock = require('nock')
var configurator = require('../../../lib/config.js')
var Agent = require('../../../lib/agent.js')
var Transaction = require('../../../lib/transaction')
var mockAWSInfo = require('../../lib/nock/aws.js').mockAWSInfo


// XXX Remove this when deprecating Node v0.8.
if (!global.setImmediate) {
global.setImmediate = function(fn) {
global.setTimeout(fn, 0)
}
}
nock.disableNetConnect()

test("harvesting with a mocked collector that returns 503 after connect", function (t) {
Expand Down
4 changes: 3 additions & 1 deletion test/integration/core/timers.tap.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
'use strict'

var semver = require('semver')
var test = require('tap').test
var timers = require('timers')
var helper = require('../../lib/agent_helper')
var verifySegments = require('./verify.js')

var HAS_SETIMMEDIATE = !!global.setImmediate
// XXX Remove once Node v0.8 is deprecated.
var HAS_SETIMMEDIATE = semver.satisfies(process.version, '>=0.9')

test('setTimeout', function testSetTimeout(t) {
var agent = setupAgent(t)
Expand Down
7 changes: 7 additions & 0 deletions test/integration/pricing/aws-info.tap.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ var expectedMetrics = {}

var testDirectory = '../../lib/cross_agent_tests/'

// XXX Remove this when deprecating Node v0.8.
if (!global.setImmediate) {
global.setImmediate = function(fn) {
global.setTimeout(fn, 0)
}
}

nock.disableNetConnect()

fs.readFile(testDirectory + 'aws.json', function readCasefile(err, data) {
Expand Down
8 changes: 8 additions & 0 deletions test/integration/pricing/system-info.tap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ var assert = require('chai').assert
var fetchSystemInfo = require('../../../lib/system-info')
var EventEmitter = require('events').EventEmitter


// XXX Remove this when deprecating Node v0.8.
if (!global.setImmediate) {
global.setImmediate = function(fn) {
global.setTimeout(fn, 0)
}
}

var awsHost = "http://169.254.169.254"

var awsResponses = {
Expand Down
9 changes: 9 additions & 0 deletions test/lib/nock/aws.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict'

var nock = require('nock')

// XXX Remove this when deprecating Node v0.8.
if (!global.setImmediate) {
global.setImmediate = function(fn) {
global.setTimeout(fn, 0)
}
}

module.exports.mockAWSInfo = function() {
var awsHost = "http://169.254.169.254"
var awsResponses = {
Expand Down
2 changes: 1 addition & 1 deletion test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"mkdirp": "*",
"mongodb": "*",
"mysql": "*",
"nock": "0.31.2",
"nock": "0.49.0",
"node-cassandra-cql": "^0.5.0",
"npm-license": "*",
"proxy": "^0.2.3",
Expand Down
Loading

0 comments on commit 587e2f2

Please sign in to comment.