diff --git a/packages/dd-trace/src/profiling/index.js b/packages/dd-trace/src/profiling/index.js index 1b65945c8b8..774d39d3a3f 100644 --- a/packages/dd-trace/src/profiling/index.js +++ b/packages/dd-trace/src/profiling/index.js @@ -1,13 +1,13 @@ 'use strict' -const { Profiler, ServerlessProfiler } = require('./profiler') +const { Profiler } = require('./profiler') const WallProfiler = require('./profilers/wall') const SpaceProfiler = require('./profilers/space') const { AgentExporter } = require('./exporters/agent') const { FileExporter } = require('./exporters/file') const { ConsoleLogger } = require('./loggers/console') -const profiler = process.env.AWS_LAMBDA_FUNCTION_NAME ? new ServerlessProfiler() : new Profiler() +const profiler = new Profiler() module.exports = { profiler, diff --git a/packages/dd-trace/src/profiling/profiler.js b/packages/dd-trace/src/profiling/profiler.js index 50b6fa13c53..b00221fa290 100644 --- a/packages/dd-trace/src/profiling/profiler.js +++ b/packages/dd-trace/src/profiling/profiler.js @@ -198,29 +198,6 @@ class Profiler extends EventEmitter { } } -class ServerlessProfiler extends Profiler { - constructor () { - super() - this._profiledIntervals = 0 - this._interval = 1 - this._flushAfterIntervals = undefined - } - - _setInterval () { - this._timeoutInterval = this._interval * 1000 - this._flushAfterIntervals = this._config.flushInterval / 1000 - } - - async _collect (snapshotKind, restart = true) { - if (this._profiledIntervals >= this._flushAfterIntervals || !restart) { - this._profiledIntervals = 0 - await super._collect(snapshotKind, restart) - } else { - this._profiledIntervals += 1 - this._capture(this._timeoutInterval, new Date()) - // Don't submit profile until 65 (flushAfterIntervals) intervals have elapsed - } - } -} +const ServerlessProfiler = Profiler module.exports = { Profiler, ServerlessProfiler } diff --git a/packages/dd-trace/test/profiling/profiler.spec.js b/packages/dd-trace/test/profiling/profiler.spec.js index dc94061ff1f..e052b9cd15a 100644 --- a/packages/dd-trace/test/profiling/profiler.spec.js +++ b/packages/dd-trace/test/profiling/profiler.spec.js @@ -81,309 +81,254 @@ describe('profiler', function () { sourceMapCreate = sinon.stub() } - describe('not serverless', function () { - function initProfiler () { - Profiler = proxyquire('../src/profiling/profiler', { - '@datadog/pprof': { - SourceMapper: { - create: sourceMapCreate - } + function initProfiler () { + Profiler = proxyquire('../src/profiling/profiler', { + '@datadog/pprof': { + SourceMapper: { + create: sourceMapCreate } - }).Profiler + } + }).Profiler - profiler = new Profiler() - } + profiler = new Profiler() + } - beforeEach(() => { - setUpProfiler() - initProfiler() - }) + beforeEach(() => { + setUpProfiler() + initProfiler() + }) - afterEach(() => { - profiler.stop() - clock.restore() - }) + afterEach(() => { + profiler.stop() + clock.restore() + }) - it('should start the internal time profilers', async () => { - await profiler._start({ profilers, exporters }) + it('should start the internal time profilers', async () => { + await profiler._start({ profilers, exporters }) - sinon.assert.calledOnce(wallProfiler.start) - sinon.assert.calledOnce(spaceProfiler.start) - }) + sinon.assert.calledOnce(wallProfiler.start) + sinon.assert.calledOnce(spaceProfiler.start) + }) - it('should start only once', async () => { - await profiler._start({ profilers, exporters }) - await profiler._start({ profilers, exporters }) + it('should start only once', async () => { + await profiler._start({ profilers, exporters }) + await profiler._start({ profilers, exporters }) - sinon.assert.calledOnce(wallProfiler.start) - sinon.assert.calledOnce(spaceProfiler.start) - }) + sinon.assert.calledOnce(wallProfiler.start) + sinon.assert.calledOnce(spaceProfiler.start) + }) - it('should allow configuring exporters by string or string array', async () => { - const checks = [ - 'agent', - ['agent'] - ] + it('should allow configuring exporters by string or string array', async () => { + const checks = [ + 'agent', + ['agent'] + ] - for (const exporters of checks) { - await profiler._start({ - sourceMap: false, - exporters - }) + for (const exporters of checks) { + await profiler._start({ + sourceMap: false, + exporters + }) - expect(profiler._config.exporters[0].export).to.be.a('function') + expect(profiler._config.exporters[0].export).to.be.a('function') - profiler.stop() - } - }) - - it('should allow configuring profilers by string or string arrays', async () => { - const checks = [ - ['space', SpaceProfiler], - ['wall', WallProfiler, EventsProfiler], - ['space,wall', SpaceProfiler, WallProfiler, EventsProfiler], - ['wall,space', WallProfiler, SpaceProfiler, EventsProfiler], - [['space', 'wall'], SpaceProfiler, WallProfiler, EventsProfiler], - [['wall', 'space'], WallProfiler, SpaceProfiler, EventsProfiler] - ].map(profilers => profilers.filter(profiler => samplingContextsAvailable || profiler !== EventsProfiler)) - - for (const [profilers, ...expected] of checks) { - await profiler._start({ - sourceMap: false, - profilers - }) - - expect(profiler._config.profilers.length).to.equal(expected.length) - for (let i = 0; i < expected.length; i++) { - expect(profiler._config.profilers[i]).to.be.instanceOf(expected[i]) - } + profiler.stop() + } + }) - profiler.stop() + it('should allow configuring profilers by string or string arrays', async () => { + const checks = [ + ['space', SpaceProfiler], + ['wall', WallProfiler, EventsProfiler], + ['space,wall', SpaceProfiler, WallProfiler, EventsProfiler], + ['wall,space', WallProfiler, SpaceProfiler, EventsProfiler], + [['space', 'wall'], SpaceProfiler, WallProfiler, EventsProfiler], + [['wall', 'space'], WallProfiler, SpaceProfiler, EventsProfiler] + ].map(profilers => profilers.filter(profiler => samplingContextsAvailable || profiler !== EventsProfiler)) + + for (const [profilers, ...expected] of checks) { + await profiler._start({ + sourceMap: false, + profilers + }) + + expect(profiler._config.profilers.length).to.equal(expected.length) + for (let i = 0; i < expected.length; i++) { + expect(profiler._config.profilers[i]).to.be.instanceOf(expected[i]) } - }) - it('should stop the internal profilers', async () => { - await profiler._start({ profilers, exporters }) profiler.stop() + } + }) - sinon.assert.calledOnce(wallProfiler.stop) - sinon.assert.calledOnce(spaceProfiler.stop) - }) - - it('should stop when starting failed', async () => { - wallProfiler.start.throws() - - await profiler._start({ profilers, exporters, logger }) - - sinon.assert.calledOnce(wallProfiler.stop) - sinon.assert.calledOnce(spaceProfiler.stop) - sinon.assert.calledOnce(consoleLogger.error) - }) - - it('should stop when capturing failed', async () => { - const rejected = Promise.reject(new Error('boom')) - wallProfiler.encode.returns(rejected) - - await profiler._start({ profilers, exporters, logger }) - - clock.tick(interval) - - await rejected.catch(() => {}) - - sinon.assert.calledOnce(wallProfiler.stop) - sinon.assert.calledOnce(spaceProfiler.stop) - sinon.assert.calledOnce(consoleLogger.error) - }) - - it('should flush when the interval is reached', async () => { - await profiler._start({ profilers, exporters }) - - clock.tick(interval) - - await waitForExport() - - sinon.assert.calledOnce(exporter.export) - }) + it('should stop the internal profilers', async () => { + await profiler._start({ profilers, exporters }) + profiler.stop() - it('should flush when the profiler is stopped', async () => { - await profiler._start({ profilers, exporters }) + sinon.assert.calledOnce(wallProfiler.stop) + sinon.assert.calledOnce(spaceProfiler.stop) + }) - profiler.stop() + it('should stop when starting failed', async () => { + wallProfiler.start.throws() - await waitForExport() + await profiler._start({ profilers, exporters, logger }) - sinon.assert.calledOnce(exporter.export) - }) + sinon.assert.calledOnce(wallProfiler.stop) + sinon.assert.calledOnce(spaceProfiler.stop) + sinon.assert.calledOnce(consoleLogger.error) + }) - it('should export profiles', async () => { - await profiler._start({ profilers, exporters, tags: { foo: 'foo' } }) + it('should stop when capturing failed', async () => { + const rejected = Promise.reject(new Error('boom')) + wallProfiler.encode.returns(rejected) - clock.tick(interval) + await profiler._start({ profilers, exporters, logger }) - await waitForExport() + clock.tick(interval) - const { profiles, start, end, tags } = exporter.export.args[0][0] + await rejected.catch(() => {}) - expect(profiles).to.have.property('wall', wallProfile) - expect(profiles).to.have.property('space', spaceProfile) - expect(start).to.be.a('date') - expect(end).to.be.a('date') - expect(end - start).to.equal(65000) - expect(tags).to.have.property('foo', 'foo') - }) + sinon.assert.calledOnce(wallProfiler.stop) + sinon.assert.calledOnce(spaceProfiler.stop) + sinon.assert.calledOnce(consoleLogger.error) + }) - it('should log exporter errors', async () => { - exporter.export.rejects(new Error('boom')) + it('should flush when the interval is reached', async () => { + await profiler._start({ profilers, exporters }) - await profiler._start({ profilers, exporters, logger }) + clock.tick(interval) - clock.tick(interval) + await waitForExport() - await waitForExport() + sinon.assert.calledOnce(exporter.export) + }) - sinon.assert.calledOnce(consoleLogger.error) - }) + it('should flush when the profiler is stopped', async () => { + await profiler._start({ profilers, exporters }) - it('should log encoded profile', async () => { - exporter.export.rejects(new Error('boom')) + profiler.stop() - await profiler._start({ profilers, exporters, logger }) + await waitForExport() - clock.tick(interval) + sinon.assert.calledOnce(exporter.export) + }) - await waitForExport() + it('should export profiles', async () => { + await profiler._start({ profilers, exporters, tags: { foo: 'foo' } }) - const [ - startWall, - startSpace, - collectWall, - collectSpace, - submit - ] = consoleLogger.debug.getCalls() + clock.tick(interval) - sinon.assert.calledWithMatch(startWall, 'Started wall profiler') - sinon.assert.calledWithMatch(startSpace, 'Started space profiler') + await waitForExport() - expect(collectWall.args[0]()).to.match(/^Collected wall profile: /) - expect(collectSpace.args[0]()).to.match(/^Collected space profile: /) + const { profiles, start, end, tags } = exporter.export.args[0][0] - sinon.assert.calledWithMatch(submit, 'Submitted profiles') - }) + expect(profiles).to.have.property('wall', wallProfile) + expect(profiles).to.have.property('space', spaceProfile) + expect(start).to.be.a('date') + expect(end).to.be.a('date') + expect(end - start).to.equal(65000) + expect(tags).to.have.property('foo', 'foo') + }) - it('should skip submit with no profiles', async () => { - const start = new Date() - const end = new Date() - try { - await profiler._submit({}, start, end) - throw new Error('should have got exception from _submit') - } catch (err) { - expect(err.message).to.equal('No profiles to submit') - } - }) + it('should log exporter errors', async () => { + exporter.export.rejects(new Error('boom')) - it('should have a new start time for each capture', async () => { - await profiler._start({ profilers, exporters }) + await profiler._start({ profilers, exporters, logger }) - clock.tick(interval) - await waitForExport() + clock.tick(interval) - const { start, end } = exporter.export.args[0][0] - expect(start).to.be.a('date') - expect(end).to.be.a('date') - expect(end - start).to.equal(65000) + await waitForExport() - sinon.assert.calledOnce(exporter.export) + sinon.assert.calledOnce(consoleLogger.error) + }) - exporter.export.resetHistory() + it('should log encoded profile', async () => { + exporter.export.rejects(new Error('boom')) - clock.tick(interval) - await waitForExport() + await profiler._start({ profilers, exporters, logger }) - const { start: start2, end: end2 } = exporter.export.args[0][0] - expect(start2).to.be.greaterThanOrEqual(end) - expect(start2).to.be.a('date') - expect(end2).to.be.a('date') - expect(end2 - start2).to.equal(65000) + clock.tick(interval) - sinon.assert.calledOnce(exporter.export) - }) + await waitForExport() - it('should not pass source mapper to profilers when disabled', async () => { - await profiler._start({ profilers, exporters, sourceMap: false }) + const [ + startWall, + startSpace, + collectWall, + collectSpace, + submit + ] = consoleLogger.debug.getCalls() - const options = profilers[0].start.args[0][0] - expect(options).to.have.property('mapper', undefined) - }) + sinon.assert.calledWithMatch(startWall, 'Started wall profiler') + sinon.assert.calledWithMatch(startSpace, 'Started space profiler') - it('should pass source mapper to profilers when enabled', async () => { - const mapper = {} - sourceMapCreate.returns(mapper) - await profiler._start({ profilers, exporters, sourceMap: true }) + expect(collectWall.args[0]()).to.match(/^Collected wall profile: /) + expect(collectSpace.args[0]()).to.match(/^Collected space profile: /) - const options = profilers[0].start.args[0][0] - expect(options).to.have.property('mapper') - .which.equals(mapper) - }) + sinon.assert.calledWithMatch(submit, 'Submitted profiles') + }) - it('should work with a root working dir and source maps on', async () => { - const error = new Error('fail') - sourceMapCreate.rejects(error) - await profiler._start({ profilers, exporters, logger, sourceMap: true }) - expect(consoleLogger.error.args[0][0]).to.equal(error) - expect(profiler._enabled).to.equal(true) - }) + it('should skip submit with no profiles', async () => { + const start = new Date() + const end = new Date() + try { + await profiler._submit({}, start, end) + throw new Error('should have got exception from _submit') + } catch (err) { + expect(err.message).to.equal('No profiles to submit') + } }) - describe('serverless', function () { - const flushAfterIntervals = 65 + it('should have a new start time for each capture', async () => { + await profiler._start({ profilers, exporters }) - function initServerlessProfiler () { - Profiler = proxyquire('../src/profiling/profiler', { - '@datadog/pprof': { - SourceMapper: { - create: sourceMapCreate - } - } - }).ServerlessProfiler + clock.tick(interval) + await waitForExport() - interval = 1 * 1000 + const { start, end } = exporter.export.args[0][0] + expect(start).to.be.a('date') + expect(end).to.be.a('date') + expect(end - start).to.equal(65000) - profiler = new Profiler() - } + sinon.assert.calledOnce(exporter.export) - beforeEach(() => { - process.env.AWS_LAMBDA_FUNCTION_NAME = 'foobar' - setUpProfiler() - initServerlessProfiler() - }) + exporter.export.resetHistory() - afterEach(() => { - profiler.stop() - clock.restore() - delete process.env.AWS_LAMBDA_FUNCTION_NAME - }) + clock.tick(interval) + await waitForExport() - it('should increment profiled intervals after one interval elapses', async () => { - await profiler._start({ profilers, exporters }) - expect(profiler._profiledIntervals).to.equal(0) + const { start: start2, end: end2 } = exporter.export.args[0][0] + expect(start2).to.be.greaterThanOrEqual(end) + expect(start2).to.be.a('date') + expect(end2).to.be.a('date') + expect(end2 - start2).to.equal(65000) - clock.tick(interval) + sinon.assert.calledOnce(exporter.export) + }) - expect(profiler._profiledIntervals).to.equal(1) - sinon.assert.notCalled(exporter.export) - }) + it('should not pass source mapper to profilers when disabled', async () => { + await profiler._start({ profilers, exporters, sourceMap: false }) - it('should flush when flush after intervals is reached', async () => { - await profiler._start({ profilers, exporters }) + const options = profilers[0].start.args[0][0] + expect(options).to.have.property('mapper', undefined) + }) - // flushAfterIntervals + 1 becauses flushes after last interval - for (let i = 0; i < flushAfterIntervals + 1; i++) { - clock.tick(interval) - } + it('should pass source mapper to profilers when enabled', async () => { + const mapper = {} + sourceMapCreate.returns(mapper) + await profiler._start({ profilers, exporters, sourceMap: true }) - await waitForExport() + const options = profilers[0].start.args[0][0] + expect(options).to.have.property('mapper') + .which.equals(mapper) + }) - sinon.assert.calledOnce(exporter.export) - }) + it('should work with a root working dir and source maps on', async () => { + const error = new Error('fail') + sourceMapCreate.rejects(error) + await profiler._start({ profilers, exporters, logger, sourceMap: true }) + expect(consoleLogger.error.args[0][0]).to.equal(error) + expect(profiler._enabled).to.equal(true) }) })