Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/aggregators/base-aggregator.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ class Aggregator extends EventEmitter {

this.defaultPeriod = this.periodMs = opts.periodMs
this.defaultLimit = this.limit = opts.limit
this.delay = opts.delay ?? 0
this.duration = opts.duration ?? 0
this.runId = opts.runId
this.isAsync = opts.isAsync || false
// function to pass in to determine if we can start a given aggregator
Expand Down
7 changes: 6 additions & 1 deletion lib/aggregators/profiling-aggregator.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ class ProfilingAggregator extends BaseAggregator {
* and send the gzipped binary encoded data for each profiler
*/
start() {
logger.trace(`${this.method} aggregator started.`)
this.profilingManager.register()
const started = this.profilingManager.start()
if (!started) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not logging any messages as the profiling manager logs a message if there are no registered profilers

return
}

logger.trace(`${this.method} aggregator started.`)

if (!this.sendTimer) {
this.sendTimer = setInterval(this.collectData.bind(this), this.periodMs)
Expand Down
21 changes: 18 additions & 3 deletions lib/harvester.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,41 @@
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const defaultLogger = require('./logger').child({ component: 'harvester' })

/**
* @class
* @classdesc Used to keep track of all registered aggregators.
*/
module.exports = class Harvester {
constructor() {
constructor({ logger = defaultLogger } = {}) {
this.aggregators = []
this.logger = logger
}

/**
* Calls start on every registered aggregator that is enabled.
*/
start() {
for (const aggregator of this.aggregators) {
if (aggregator.enabled) {
if (aggregator.enabled && aggregator.delay > 0) {
this.logger.debug(`Delay start of ${aggregator.method} by ${aggregator.delay} milliseconds`)
const timeout = setTimeout(() => {
aggregator.start()
}, aggregator.delay)
timeout.unref()
} else if (aggregator.enabled) {
aggregator.start()
}

if (aggregator.enabled && aggregator.duration > 0) {
this.logger.debug(`Running ${aggregator.method} for ${aggregator.duration} milliseconds`)
const durationTimeout = setTimeout(() => {
aggregator.stop()
}, aggregator.delay + aggregator.duration)
durationTimeout.unref()
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion lib/profiling/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ class ProfilingManager {
start() {
if (this.profilers.length === 0) {
this.logger.warn('No profilers have been included in `config.profiling.include`, not starting any profilers.')
return
return false
}

for (const profiler of this.profilers) {
this.logger.debug(`Starting ${profiler.name}`)
profiler.start()
}
return true
}

stop() {
Expand Down
18 changes: 3 additions & 15 deletions test/unit/aggregators/profiling-aggregator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,14 @@ const sinon = require('sinon')
const ProfilingAggregator = require('#agentlib/aggregators/profiling-aggregator.js')
const helper = require('#testlib/agent_helper.js')
const RUN_ID = 1337
const createProfiler = require('../mocks/profiler')

test.beforeEach((ctx) => {
const sandbox = sinon.createSandbox()
const agent = helper.loadMockedAgent()
const cpuProfiler = {
name: 'CpuProfiler',
stop: sandbox.stub(),
collect() {
return 'cpu profile data'
}
}

const cpuProfiler = createProfiler({ sandbox, name: 'CpuProfiler', data: 'cpu profile data' })
const heapProfiler = createProfiler({ sandbox, name: 'HeapProfiler', data: 'heap profile data' })
const clock = sinon.useFakeTimers()
const heapProfiler = {
name: 'HeapProfiler',
stop: sandbox.stub(),
collect() {
return 'heap profile data'
}
}
sandbox.spy(agent.collector, 'send')
const profilingAggregator = new ProfilingAggregator({ runId: RUN_ID, periodMs: 100 }, agent)
const profilingManager = profilingAggregator.profilingManager
Expand Down
68 changes: 61 additions & 7 deletions test/unit/harvester.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class FakeAggregator extends EventEmitter {
super()
this.enabled = opts.enabled
this.method = opts.method
this.delay = opts.delay ?? 0
this.duration = opts.duration ?? 0
}

start() {}
Expand All @@ -40,19 +42,21 @@ function createAggregator(sandbox, opts) {
}

test.beforeEach((ctx) => {
ctx.nr = {}

const sandbox = sinon.createSandbox()
const aggregators = [
createAggregator(sandbox, { enabled: true, method: 'agg1' }),
createAggregator(sandbox, { enabled: false, method: 'agg2' })
]
const harvester = new Harvester()
const logger = require('./mocks/logger')(sandbox)
const harvester = new Harvester({ logger })
aggregators.forEach((a) => harvester.add(a))

ctx.nr.sandbox = sandbox
ctx.nr.aggregators = aggregators
ctx.nr.harvester = harvester
ctx.nr = {
sandbox,
aggregators,
harvester,
logger
}
})

test.afterEach((ctx) => {
Expand All @@ -71,10 +75,11 @@ test('should add aggregator to this.aggregators', (t) => {
})

test('should start all aggregators that are enabled', (t) => {
const { harvester, aggregators } = t.nr
const { harvester, aggregators, logger } = t.nr
harvester.start()
assert.equal(aggregators[0].start.callCount, 1, 'should start enabled aggregator')
assert.equal(aggregators[1].start.callCount, 0, 'should not start disabled aggregator')
assert.equal(logger.debug.callCount, 0)
})

test('should stop all aggregators', (t) => {
Expand Down Expand Up @@ -103,3 +108,52 @@ test('resolve when all data is sent', async (t) => {
})
await promise
})

test('should delay starting of aggregator when it has a delay property', (t) => {
const { sandbox, logger } = t.nr
const clock = sandbox.useFakeTimers()
const delayAggregator = createAggregator(sandbox, { enabled: true, method: 'test-method', delay: 200 })
const harvester = new Harvester({ logger })
harvester.add(delayAggregator)
harvester.start()
assert.equal(logger.debug.callCount, 1)
assert.equal(logger.debug.args[0][0], 'Delay start of test-method by 200 milliseconds')
const { aggregators } = harvester
assert.equal(aggregators[0].start.callCount, 0, 'should not start delayed aggregator yet')
clock.tick(201)
assert.equal(aggregators[0].start.callCount, 1, 'should start delayed aggregator after delay has elapsed')
})

test('should stop aggregator dynamically when it has a duration property', (t) => {
const { sandbox, logger } = t.nr
const clock = sandbox.useFakeTimers()
const delayAggregator = createAggregator(sandbox, { enabled: true, method: 'test-method', duration: 200 })
const harvester = new Harvester({ logger })
harvester.add(delayAggregator)
harvester.start()
assert.equal(logger.debug.callCount, 1)
assert.equal(logger.debug.args[0][0], 'Running test-method for 200 milliseconds')
const { aggregators } = harvester
assert.equal(aggregators[0].start.callCount, 1, 'should start aggregator')
assert.equal(aggregators[0].stop.callCount, 0, 'should not stop aggregator yet')
clock.tick(201)
assert.equal(aggregators[0].stop.callCount, 1, 'should stop aggregator after duration has elapsed')
})

test('should delay start and stop aggregator after duration', (t) => {
const { sandbox, logger } = t.nr
const clock = sandbox.useFakeTimers()
const delayAggregator = createAggregator(sandbox, { enabled: true, method: 'test-method', delay: 100, duration: 200 })
const harvester = new Harvester({ logger })
harvester.add(delayAggregator)
harvester.start()
assert.equal(logger.debug.callCount, 2)
const { aggregators } = harvester
assert.equal(aggregators[0].start.callCount, 0, 'should not start delayed aggregator yet')
assert.equal(aggregators[0].stop.callCount, 0, 'should not stop aggregator yet')
clock.tick(101)
assert.equal(aggregators[0].start.callCount, 1, 'should start delayed aggregator after delay has elapsed')
assert.equal(aggregators[0].stop.callCount, 0, 'should not stop aggregator yet')
clock.tick(200)
assert.equal(aggregators[0].stop.callCount, 1, 'should stop aggregator after duration has elapsed')
})
23 changes: 7 additions & 16 deletions test/unit/lib/profiling/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { describe, test } = require('node:test')
const assert = require('node:assert')
const sinon = require('sinon')
const ProfilingManager = require('#agentlib/profiling/index.js')
const createProfiler = require('../../mocks/profiler')

test.beforeEach((ctx) => {
const sandbox = sinon.createSandbox()
Expand All @@ -21,19 +22,8 @@ test.beforeEach((ctx) => {
}
}
}
const cpuProfiler = {
name: 'cpu',
start: sandbox.stub(),
stop: sandbox.stub(),
collect: sandbox.stub()
}

const heapProfiler = {
name: 'heap',
start: sandbox.stub(),
stop: sandbox.stub(),
collect: sandbox.stub()
}
const cpuProfiler = createProfiler({ sandbox, name: 'cpu' })
const heapProfiler = createProfiler({ sandbox, name: 'heap' })
ctx.nr = {
agent,
cpuProfiler,
Expand Down Expand Up @@ -81,7 +71,8 @@ describe('start', () => {
const { agent, logger } = t.nr
const profilingManager = new ProfilingManager(agent, { logger })

profilingManager.start()
const started = profilingManager.start()
assert.equal(started, false)

assert.equal(logger.warn.callCount, 1)
assert.ok(
Expand All @@ -95,8 +86,8 @@ describe('start', () => {
const { agent, cpuProfiler, heapProfiler, logger } = t.nr
const profilingManager = new ProfilingManager(agent, { logger })
profilingManager.profilers = [cpuProfiler, heapProfiler]
profilingManager.start()

const started = profilingManager.start()
assert.equal(started, true)
assert.equal(cpuProfiler.start.callCount, 1)
assert.equal(heapProfiler.start.callCount, 1)
assert.ok(
Expand Down
19 changes: 19 additions & 0 deletions test/unit/mocks/profiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2026 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const sinon = require('sinon')
const DEFAULT_DATA = Buffer.from('test-data')

function createProfiler({ sandbox = sinon, name = 'TestProfiler', data = DEFAULT_DATA } = {}) {
return {
name,
start: sandbox.stub(),
stop: sandbox.stub(),
collect: sandbox.stub().returns(data)
}
}

module.exports = createProfiler
Loading