diff --git a/package.json b/package.json index 028a309f5b3..caca00e3134 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@datadog/native-iast-rewriter": "2.0.1", "@datadog/native-iast-taint-tracking": "^1.5.0", "@datadog/native-metrics": "^2.0.0", - "@datadog/pprof": "2.2.3", + "@datadog/pprof": "3.0.0", "@datadog/sketches-js": "^2.1.0", "@opentelemetry/api": "^1.0.0", "@opentelemetry/core": "^1.14.0", diff --git a/packages/dd-trace/src/profiling/config.js b/packages/dd-trace/src/profiling/config.js index fac6a759c0d..0f94dcbcb99 100644 --- a/packages/dd-trace/src/profiling/config.js +++ b/packages/dd-trace/src/profiling/config.js @@ -7,7 +7,6 @@ const { URL, format, pathToFileURL } = require('url') const { AgentExporter } = require('./exporters/agent') const { FileExporter } = require('./exporters/file') const { ConsoleLogger } = require('./loggers/console') -const CpuProfiler = require('./profilers/cpu') const WallProfiler = require('./profilers/wall') const SpaceProfiler = require('./profilers/space') const { oomExportStrategies, snapshotKinds } = require('./constants') @@ -202,8 +201,6 @@ function getProfiler (name, options) { return new WallProfiler(options) case 'space': return new SpaceProfiler(options) - case 'cpu-experimental': - return new CpuProfiler(options) default: options.logger.error(`Unknown profiler "${name}"`) } diff --git a/packages/dd-trace/src/profiling/index.js b/packages/dd-trace/src/profiling/index.js index b72549bef5f..1b65945c8b8 100644 --- a/packages/dd-trace/src/profiling/index.js +++ b/packages/dd-trace/src/profiling/index.js @@ -1,7 +1,6 @@ 'use strict' const { Profiler, ServerlessProfiler } = require('./profiler') -const CpuProfiler = require('./profilers/cpu') const WallProfiler = require('./profilers/wall') const SpaceProfiler = require('./profilers/space') const { AgentExporter } = require('./exporters/agent') @@ -14,7 +13,6 @@ module.exports = { profiler, AgentExporter, FileExporter, - CpuProfiler, WallProfiler, SpaceProfiler, ConsoleLogger diff --git a/packages/dd-trace/src/profiling/profilers/cpu.js b/packages/dd-trace/src/profiling/profilers/cpu.js deleted file mode 100644 index 3be1ecce548..00000000000 --- a/packages/dd-trace/src/profiling/profilers/cpu.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict' - -const { storage } = require('../../../../datadog-core') - -const dc = require('../../../../diagnostics_channel') - -const beforeCh = dc.channel('dd-trace:storage:before') -const afterCh = dc.channel('dd-trace:storage:after') - -function getActiveSpan () { - const store = storage.getStore() - if (!store) return - return store.span -} - -function getStartedSpans (activeSpan) { - const context = activeSpan.context() - if (!context) return - return context._trace.started -} - -function getSpanContextTags (span) { - return span.context()._tags -} - -function isWebServerSpan (tags) { - return tags['span.type'] === 'web' -} - -function endpointNameFromTags (tags) { - return tags['resource.name'] || [ - tags['http.method'], - tags['http.route'] - ].filter(v => v).join(' ') -} - -class NativeCpuProfiler { - constructor (options = {}) { - this.type = 'cpu' - this._frequency = options.frequency || 99 - this._mapper = undefined - this._pprof = undefined - this._started = false - this._cpuProfiler = undefined - this._endpointCollection = options.endpointCollection - - // Bind to this so the same value can be used to unsubscribe later - this._enter = this._enter.bind(this) - this._exit = this._exit.bind(this) - } - - _enter () { - if (!this._cpuProfiler) return - - const active = getActiveSpan() - if (!active) return - - const activeCtx = active.context() - if (!activeCtx) return - - const spans = getStartedSpans(active) - if (!spans || !spans.length) return - - const firstCtx = spans[0].context() - if (!firstCtx) return - - const labels = { - 'local root span id': firstCtx.toSpanId(), - 'span id': activeCtx.toSpanId() - } - - if (this._endpointCollection) { - const webServerTags = spans - .map(getSpanContextTags) - .filter(isWebServerSpan)[0] - - if (webServerTags) { - labels['trace endpoint'] = endpointNameFromTags(webServerTags) - } - } - - this._cpuProfiler.labels = labels - } - - _exit () { - if (!this._cpuProfiler) return - this._cpuProfiler.labels = {} - } - - start ({ mapper } = {}) { - if (this._started) return - this._started = true - - this._mapper = mapper - if (!this._pprof) { - this._pprof = require('@datadog/pprof') - this._cpuProfiler = new this._pprof.CpuProfiler() - } - - this._cpuProfiler.start(this._frequency) - - this._enter() - beforeCh.subscribe(this._enter) - afterCh.subscribe(this._exit) - } - - profile () { - if (!this._started) return - return this._cpuProfiler.profile() - } - - encode (profile) { - return this._pprof.encode(profile) - } - - stop () { - if (!this._started) return - this._started = false - - this._cpuProfiler.stop() - beforeCh.unsubscribe(this._enter) - afterCh.unsubscribe(this._exit) - } -} - -module.exports = NativeCpuProfiler diff --git a/packages/dd-trace/src/profiling/profilers/wall.js b/packages/dd-trace/src/profiling/profilers/wall.js index 1f4457060ae..820c035040f 100644 --- a/packages/dd-trace/src/profiling/profilers/wall.js +++ b/packages/dd-trace/src/profiling/profilers/wall.js @@ -3,12 +3,19 @@ class NativeWallProfiler { constructor (options = {}) { this.type = 'wall' - this._samplingInterval = options.samplingInterval || 1e6 / 99 // 99hz + this._samplingIntervalMicros = options.samplingInterval || 1e6 / 99 // 99hz + this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds + this._codeHotspotsEnabled = !!options.codeHotspotsEnabled this._mapper = undefined this._pprof = undefined + + this._logger = options.logger + this._started = false } start ({ mapper } = {}) { + if (this._started) return + this._mapper = mapper this._pprof = require('@datadog/pprof') @@ -20,12 +27,20 @@ class NativeWallProfiler { process._stopProfilerIdleNotifier = () => {} } - this._record() + this._pprof.time.start({ + intervalMicros: this._samplingIntervalMicros, + durationMillis: this._flushIntervalMillis, + sourceMapper: this._mapper, + customLabels: this._codeHotspotsEnabled, + lineNumbers: false + }) + + this._started = true } profile () { - if (!this._stop) return - return this._stop(true) + if (!this._started) return + return this._pprof.time.stop(true) } encode (profile) { @@ -33,14 +48,11 @@ class NativeWallProfiler { } stop () { - if (!this._stop) return - this._stop() - this._stop = undefined - } + if (!this._started) return - _record () { - this._stop = this._pprof.time.start(this._samplingInterval, null, - this._mapper, false) + const profile = this._pprof.time.stop() + this._started = false + return profile } } diff --git a/packages/dd-trace/test/profiling/config.spec.js b/packages/dd-trace/test/profiling/config.spec.js index 6e99fa87c27..b49a20c6f16 100644 --- a/packages/dd-trace/test/profiling/config.spec.js +++ b/packages/dd-trace/test/profiling/config.spec.js @@ -7,7 +7,6 @@ const os = require('os') const path = require('path') const { AgentExporter } = require('../../src/profiling/exporters/agent') const { FileExporter } = require('../../src/profiling/exporters/file') -const CpuProfiler = require('../../src/profiling/profilers/cpu') const WallProfiler = require('../../src/profiling/profilers/wall') const SpaceProfiler = require('../../src/profiling/profilers/space') const { ConsoleLogger } = require('../../src/profiling/loggers/console') @@ -58,7 +57,7 @@ describe('config', () => { error () { } }, exporters: 'agent,file', - profilers: 'wall,cpu-experimental', + profilers: 'wall', url: 'http://localhost:1234/' } @@ -79,9 +78,8 @@ describe('config', () => { expect(config.exporters[0]._url.toString()).to.equal(options.url) expect(config.exporters[1]).to.be.an.instanceof(FileExporter) expect(config.profilers).to.be.an('array') - expect(config.profilers.length).to.equal(2) + expect(config.profilers.length).to.equal(1) expect(config.profilers[0]).to.be.an.instanceOf(WallProfiler) - expect(config.profilers[1]).to.be.an.instanceOf(CpuProfiler) }) it('should filter out invalid profilers', () => { diff --git a/packages/dd-trace/test/profiling/profilers/cpu.spec.js b/packages/dd-trace/test/profiling/profilers/cpu.spec.js deleted file mode 100644 index f9b847825f0..00000000000 --- a/packages/dd-trace/test/profiling/profilers/cpu.spec.js +++ /dev/null @@ -1,175 +0,0 @@ -'use strict' - -require('../../setup/tap') - -const { expect } = require('chai') -const proxyquire = require('proxyquire') -const sinon = require('sinon') -const tracer = require('../../..') - -describe('profilers/native/cpu', () => { - let NativeCpuProfiler - let CpuProfiler - let pprof - let start - let stop - let profile - let setter - let getter - - beforeEach(() => { - CpuProfiler = sinon.stub() - start = sinon.stub() - stop = sinon.stub() - profile = sinon.stub().returns('profile') - setter = sinon.stub() - getter = sinon.stub() - - CpuProfiler.prototype.start = start - CpuProfiler.prototype.stop = stop - CpuProfiler.prototype.profile = profile - Object.defineProperty(CpuProfiler.prototype, 'labels', { - set: setter, - get: getter - }) - - pprof = { - encode: sinon.stub().returns(Promise.resolve()), - CpuProfiler - } - - NativeCpuProfiler = proxyquire('../../../src/profiling/profilers/cpu', { - '@datadog/pprof': pprof - }) - }) - - it('should start the internal time profiler', () => { - const profiler = new NativeCpuProfiler() - - expect(profiler._started).to.be.false - profiler.start() - expect(profiler._started).to.be.true - - sinon.assert.calledOnce(start) - sinon.assert.calledWith(start, 99) - }) - - it('should use given sample frequency', () => { - const profiler = new NativeCpuProfiler({ - frequency: 123 - }) - - profiler.start() - - sinon.assert.calledOnce(start) - sinon.assert.calledWith(start, 123) - }) - - it('should not get profile when not started', () => { - const profiler = new NativeCpuProfiler() - - profiler.profile() - - sinon.assert.notCalled(profile) - }) - - it('should get profile when started', () => { - const profiler = new NativeCpuProfiler() - - profiler.start() - profiler.profile() - - sinon.assert.calledOnce(profile) - }) - - it('should not stop when not started', () => { - const profiler = new NativeCpuProfiler() - - profiler.stop() - - sinon.assert.notCalled(stop) - }) - - it('should stop when started', () => { - const profiler = new NativeCpuProfiler() - - expect(profiler._started).to.be.false - profiler.start() - expect(profiler._started).to.be.true - profiler.stop() - expect(profiler._started).to.be.false - - sinon.assert.calledOnce(stop) - }) - - it('should encode profile', () => { - const profiler = new NativeCpuProfiler() - - profiler.start() - - const profile = profiler.profile() - - profiler.encode(profile) - - sinon.assert.calledOnce(pprof.encode) - }) - - it('should track labels across async boundaries', () => { - tracer.init() - tracer.trace('foo.bar', { - service: 'service', - resource: 'resource', - type: 'web' - }, (span, cb) => { - const profiler = new NativeCpuProfiler() - profiler.start() - - // Should immediately have labels available - const spanId = span.context().toSpanId() - sinon.assert.calledWithMatch(setter, { - 'local root span id': spanId, - 'span id': spanId - }) - - setter.resetHistory() - - setImmediate(() => { - // Should also have labels available asynchronously - sinon.assert.calledWithMatch(setter, { - 'local root span id': spanId, - 'span id': spanId - }) - cb() - }) - }) - }) - - it('should collect endpoints', () => { - tracer.init() - tracer.trace('foo.bar', { - service: 'service', - resource: 'resource', - type: 'web' - }, (_, cb) => { - const profiler = new NativeCpuProfiler({ - endpointCollection: true - }) - profiler.start() - - // Should immediately have labels available - sinon.assert.calledWithMatch(setter, { - 'trace endpoint': 'resource' - }) - - setter.resetHistory() - - setImmediate(() => { - // Should also have labels available asynchronously - sinon.assert.calledWithMatch(setter, { - 'trace endpoint': 'resource' - }) - cb() - }) - }) - }) -}) diff --git a/packages/dd-trace/test/profiling/profilers/wall.spec.js b/packages/dd-trace/test/profiling/profilers/wall.spec.js index 7dac7539817..4b6d3731cd9 100644 --- a/packages/dd-trace/test/profiling/profilers/wall.spec.js +++ b/packages/dd-trace/test/profiling/profilers/wall.spec.js @@ -9,14 +9,13 @@ const sinon = require('sinon') describe('profilers/native/wall', () => { let NativeWallProfiler let pprof - let stop beforeEach(() => { - stop = sinon.stub().returns('profile') pprof = { encode: sinon.stub().returns(Promise.resolve()), time: { - start: sinon.stub().returns(stop) + start: sinon.stub(), + stop: sinon.stub().returns('profile') } } @@ -45,7 +44,12 @@ describe('profilers/native/wall', () => { process._stopProfilerIdleNotifier = stop sinon.assert.calledOnce(pprof.time.start) - sinon.assert.calledWith(pprof.time.start, 1e6 / 99) + sinon.assert.calledWith(pprof.time.start, + { intervalMicros: 1e6 / 99, + durationMillis: 60000, + sourceMapper: undefined, + customLabels: false, + lineNumbers: false }) }) it('should use the provided configuration options', () => { @@ -53,8 +57,14 @@ describe('profilers/native/wall', () => { const profiler = new NativeWallProfiler({ samplingInterval }) profiler.start() + profiler.stop() - sinon.assert.calledWith(pprof.time.start, samplingInterval) + sinon.assert.calledWith(pprof.time.start, + { intervalMicros: 500, + durationMillis: 60000, + sourceMapper: undefined, + customLabels: false, + lineNumbers: false }) }) it('should not stop when not started', () => { @@ -62,7 +72,7 @@ describe('profilers/native/wall', () => { profiler.stop() - sinon.assert.notCalled(stop) + sinon.assert.notCalled(pprof.time.stop) }) it('should stop the internal time profiler', () => { @@ -71,7 +81,7 @@ describe('profilers/native/wall', () => { profiler.start() profiler.stop() - sinon.assert.calledOnce(stop) + sinon.assert.calledOnce(pprof.time.stop) }) it('should stop the internal time profiler only once', () => { @@ -81,7 +91,7 @@ describe('profilers/native/wall', () => { profiler.stop() profiler.stop() - sinon.assert.calledOnce(stop) + sinon.assert.calledOnce(pprof.time.stop) }) it('should collect profiles from the internal time profiler', () => { @@ -93,8 +103,9 @@ describe('profilers/native/wall', () => { expect(profile).to.equal('profile') - sinon.assert.calledOnce(stop) + sinon.assert.calledOnce(pprof.time.stop) sinon.assert.calledOnce(pprof.time.start) + profiler.stop() }) it('should encode profiles from the pprof time profiler', () => { @@ -106,6 +117,8 @@ describe('profilers/native/wall', () => { profiler.encode(profile) + profiler.stop() + sinon.assert.calledOnce(pprof.encode) }) @@ -115,7 +128,13 @@ describe('profilers/native/wall', () => { const mapper = {} profiler.start({ mapper }) + profiler.stop() - sinon.assert.calledWith(pprof.time.start, 1e6 / 99, null, mapper, false) + sinon.assert.calledWith(pprof.time.start, + { intervalMicros: 1e6 / 99, + durationMillis: 60000, + sourceMapper: mapper, + customLabels: false, + lineNumbers: false }) }) }) diff --git a/yarn.lock b/yarn.lock index 19995e5c764..043e09088b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -409,18 +409,16 @@ node-addon-api "^6.1.0" node-gyp-build "^3.9.0" -"@datadog/pprof@2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-2.2.3.tgz#a22ca30e386f5aa8559f4b2e297b76c80551c26d" - integrity sha512-cZXvNBBzvTMUx2xOxp49cZJ7/HOF7geVxqeRbveeJUVKwi8ZxmU1rQGcWPFX4iEEtfQu1M3NqbhmNtYsMJdEsQ== +"@datadog/pprof@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-3.0.0.tgz#b5e6df853589512ceb470c44cfc50cbe2ebfa5ab" + integrity sha512-nE/iWBX6h61SXrVWA6DeJ/m2iJX9ZNHrcoxFzHkK0LnSbNzrMlQZ0yw24ExhvIPLOFP4uFW057Cm48rsgr0NyQ== dependencies: delay "^5.0.0" - node-gyp-build "^3.9.0" + node-gyp-build "<4.0" p-limit "^3.1.0" - pify "^5.0.0" pprof-format "^2.0.7" - source-map "^0.7.3" - split "^1.0.1" + source-map "^0.7.4" "@datadog/sketches-js@^2.1.0": version "2.1.0" @@ -3314,7 +3312,7 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== -node-gyp-build@^3.9.0: +node-gyp-build@<4.0, node-gyp-build@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== @@ -3612,11 +3610,6 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - pkg-dir@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -4116,7 +4109,7 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: +source-map@^0.7.4: version "0.7.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== @@ -4133,13 +4126,6 @@ spawn-wrap@^2.0.0: signal-exit "^3.0.2" which "^2.0.1" -split@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -4377,7 +4363,7 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -through@2, through@~2.3.4, through@~2.3.8: +through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==