Skip to content

Commit bf9be5a

Browse files
committed
Start profilers synchronously within tracer initialization. Source maps are still loaded asynchronously.
1 parent ee3f034 commit bf9be5a

File tree

3 files changed

+66
-49
lines changed

3 files changed

+66
-49
lines changed

packages/dd-trace/src/profiling/profiler.js

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ const zlib = require('zlib')
1414
const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
1515
const spanFinishedChannel = dc.channel('dd-trace:span:finish')
1616

17-
function maybeSourceMap (sourceMap, SourceMapper, debug) {
18-
if (!sourceMap) return
19-
return SourceMapper.create([
20-
process.cwd()
21-
], debug)
22-
}
23-
2417
function logError (logger, ...args) {
2518
if (logger) {
2619
logger.error(...args)
@@ -50,22 +43,19 @@ class Profiler extends EventEmitter {
5043
this._timer = undefined
5144
this._lastStart = undefined
5245
this._timeoutInterval = undefined
46+
this._sourceMapsLoaded = undefined
5347
this.endpointCounts = new Map()
5448
}
5549

56-
start (options) {
57-
return this._start(options).catch((err) => {
58-
logError(options.logger, 'Error starting profiler. For troubleshooting tips, see ' +
59-
'<https://dtdg.co/nodejs-profiler-troubleshooting>', err)
60-
return false
61-
})
62-
}
63-
6450
_logError (err) {
6551
logError(this._logger, err)
6652
}
6753

68-
async _start (options) {
54+
sourceMapsLoaded () {
55+
return this._sourceMapsLoaded
56+
}
57+
58+
start (options) {
6959
if (this._enabled) return true
7060

7161
const config = this._config = new Config(options)
@@ -77,19 +67,44 @@ class Profiler extends EventEmitter {
7767
// Log errors if the source map finder fails, but don't prevent the rest
7868
// of the profiler from running without source maps.
7969
let mapper
80-
try {
81-
const { setLogger, SourceMapper } = require('@datadog/pprof')
82-
setLogger(config.logger)
83-
84-
mapper = await maybeSourceMap(config.sourceMap, SourceMapper, config.debugSourceMaps)
85-
if (config.sourceMap && config.debugSourceMaps) {
86-
this._logger.debug(() => {
87-
return mapper.infoMap.size === 0
88-
? 'Found no source maps'
89-
: `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
90-
})
70+
if (config.sourceMap) {
71+
try {
72+
const { setLogger, SourceMapper } = require('@datadog/pprof')
73+
setLogger(config.logger)
74+
75+
const maybeMapperLoadingPromise = SourceMapper.create([process.cwd()], config.debugSourceMaps)
76+
if (maybeMapperLoadingPromise instanceof Promise) {
77+
mapper = {
78+
hasMappingInfo: () => false,
79+
mappingInfo: (l) => l
80+
}
81+
this._sourceMapsLoaded = maybeMapperLoadingPromise.then((sourceMap) => {
82+
if (config.debugSourceMaps) {
83+
this._logger.debug(() => {
84+
return sourceMap.infoMap.size === 0
85+
? 'Found no source maps'
86+
: `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
87+
})
88+
}
89+
mapper.hasMappingInfo = sourceMap.hasMappingInfo.bind(sourceMap)
90+
mapper.mappingInfo = sourceMap.mappingInfo.bind(sourceMap)
91+
}).catch((err) => {
92+
this._logError(err)
93+
})
94+
} else {
95+
// If the result of SourceMapper.create is not a promise, it is already loaded
96+
mapper = maybeMapperLoadingPromise
97+
}
98+
} catch (err) {
99+
this._logError(err)
91100
}
101+
}
102+
103+
if (this._sourceMapsLoaded === undefined) {
104+
this._sourceMapsLoaded = Promise.resolve()
105+
}
92106

107+
try {
93108
const clevel = config.uploadCompression.level
94109
switch (config.uploadCompression.method) {
95110
case 'gzip':

packages/dd-trace/src/proxy.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ class Tracer extends NoopProxy {
159159
ssiHeuristics.start()
160160
let mockProfiler = null
161161
if (config.profiling.enabled === 'true') {
162-
this._profilerStarted = this._startProfiler(config)
162+
this._startProfiler(config)
163+
this._profilerStarted = Promise.resolve(true)
163164
} else if (ssiHeuristics.emitsTelemetry) {
164165
// Start a mock profiler that emits mock profile-submitted events for the telemetry.
165166
// It will be stopped if the real profiler is started by the heuristics.

packages/dd-trace/test/profiling/profiler.spec.js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ describe('profiler', function () {
106106
})
107107

108108
it('should start the internal time profilers', async () => {
109-
await profiler._start({ profilers, exporters })
109+
profiler.start({ profilers, exporters })
110110

111111
sinon.assert.calledOnce(wallProfiler.start)
112112
sinon.assert.calledOnce(spaceProfiler.start)
113113
})
114114

115115
it('should start only once', async () => {
116-
await profiler._start({ profilers, exporters })
117-
await profiler._start({ profilers, exporters })
116+
profiler.start({ profilers, exporters })
117+
profiler.start({ profilers, exporters })
118118

119119
sinon.assert.calledOnce(wallProfiler.start)
120120
sinon.assert.calledOnce(spaceProfiler.start)
@@ -127,7 +127,7 @@ describe('profiler', function () {
127127
]
128128

129129
for (const exporters of checks) {
130-
await profiler._start({
130+
profiler.start({
131131
sourceMap: false,
132132
exporters
133133
})
@@ -149,7 +149,7 @@ describe('profiler', function () {
149149
].map(profilers => profilers.filter(profiler => samplingContextsAvailable || profiler !== EventsProfiler))
150150

151151
for (const [profilers, ...expected] of checks) {
152-
await profiler._start({
152+
profiler.start({
153153
sourceMap: false,
154154
profilers
155155
})
@@ -164,7 +164,7 @@ describe('profiler', function () {
164164
})
165165

166166
it('should stop the internal profilers', async () => {
167-
await profiler._start({ profilers, exporters })
167+
profiler.start({ profilers, exporters })
168168
profiler.stop()
169169

170170
sinon.assert.calledOnce(wallProfiler.stop)
@@ -174,7 +174,7 @@ describe('profiler', function () {
174174
it('should stop when starting failed', async () => {
175175
wallProfiler.start.throws()
176176

177-
await profiler._start({ profilers, exporters, logger })
177+
profiler.start({ profilers, exporters, logger })
178178

179179
sinon.assert.calledOnce(wallProfiler.stop)
180180
sinon.assert.calledOnce(spaceProfiler.stop)
@@ -184,7 +184,7 @@ describe('profiler', function () {
184184
it('should stop when capturing failed', async () => {
185185
wallProfiler.profile.throws(new Error('boom'))
186186

187-
await profiler._start({ profilers, exporters, logger })
187+
profiler.start({ profilers, exporters, logger })
188188

189189
clock.tick(interval)
190190

@@ -200,7 +200,7 @@ describe('profiler', function () {
200200
const rejected = Promise.reject(new Error('boom'))
201201
wallProfiler.encode.returns(rejected)
202202

203-
await profiler._start({ profilers, exporters, logger })
203+
profiler.start({ profilers, exporters, logger })
204204

205205
clock.tick(interval)
206206

@@ -217,7 +217,7 @@ describe('profiler', function () {
217217
const rejected = Promise.reject(new Error('boom'))
218218
exporter.export.returns(rejected)
219219

220-
await profiler._start({ profilers, exporters, logger })
220+
profiler.start({ profilers, exporters, logger })
221221

222222
clock.tick(interval)
223223

@@ -230,7 +230,7 @@ describe('profiler', function () {
230230
})
231231

232232
it('should flush when the interval is reached', async () => {
233-
await profiler._start({ profilers, exporters })
233+
profiler.start({ profilers, exporters })
234234

235235
clock.tick(interval)
236236

@@ -240,7 +240,7 @@ describe('profiler', function () {
240240
})
241241

242242
it('should flush when the profiler is stopped', async () => {
243-
await profiler._start({ profilers, exporters })
243+
profiler.start({ profilers, exporters })
244244

245245
profiler.stop()
246246

@@ -268,7 +268,7 @@ describe('profiler', function () {
268268
process.env = {
269269
DD_PROFILING_DEBUG_UPLOAD_COMPRESSION: compression
270270
}
271-
await profiler._start({ profilers, exporters, tags: { foo: 'foo' } })
271+
profiler.start({ profilers, exporters, tags: { foo: 'foo' } })
272272
process.env = env
273273

274274
clock.tick(interval)
@@ -310,7 +310,7 @@ describe('profiler', function () {
310310
it('should log exporter errors', async () => {
311311
exporter.export.rejects(new Error('boom'))
312312

313-
await profiler._start({ profilers, exporters, logger })
313+
profiler.start({ profilers, exporters, logger })
314314

315315
clock.tick(interval)
316316

@@ -322,7 +322,7 @@ describe('profiler', function () {
322322
it('should log encoded profile', async () => {
323323
exporter.export.rejects(new Error('boom'))
324324

325-
await profiler._start({ profilers, exporters, logger })
325+
profiler.start({ profilers, exporters, logger })
326326

327327
clock.tick(interval)
328328

@@ -347,7 +347,7 @@ describe('profiler', function () {
347347
})
348348

349349
it('should have a new start time for each capture', async () => {
350-
await profiler._start({ profilers, exporters })
350+
profiler.start({ profilers, exporters })
351351

352352
clock.tick(interval)
353353
await waitForExport()
@@ -374,7 +374,7 @@ describe('profiler', function () {
374374
})
375375

376376
it('should not pass source mapper to profilers when disabled', async () => {
377-
await profiler._start({ profilers, exporters, sourceMap: false })
377+
profiler.start({ profilers, exporters, sourceMap: false })
378378

379379
const options = profilers[0].start.args[0][0]
380380
expect(options).to.have.property('mapper', undefined)
@@ -383,7 +383,7 @@ describe('profiler', function () {
383383
it('should pass source mapper to profilers when enabled', async () => {
384384
const mapper = {}
385385
sourceMapCreate.returns(mapper)
386-
await profiler._start({ profilers, exporters, sourceMap: true })
386+
profiler.start({ profilers, exporters, sourceMap: true })
387387

388388
const options = profilers[0].start.args[0][0]
389389
expect(options).to.have.property('mapper')
@@ -393,7 +393,8 @@ describe('profiler', function () {
393393
it('should work with a root working dir and source maps on', async () => {
394394
const error = new Error('fail')
395395
sourceMapCreate.rejects(error)
396-
await profiler._start({ profilers, exporters, logger, sourceMap: true })
396+
profiler.start({ profilers, exporters, logger, sourceMap: true })
397+
await profiler.sourceMapsLoaded()
397398
expect(consoleLogger.error.args[0][0]).to.equal(error)
398399
expect(profiler._enabled).to.equal(true)
399400
})
@@ -429,7 +430,7 @@ describe('profiler', function () {
429430
})
430431

431432
it('should increment profiled intervals after one interval elapses', async () => {
432-
await profiler._start({ profilers, exporters })
433+
profiler.start({ profilers, exporters })
433434
expect(profiler._profiledIntervals).to.equal(0)
434435

435436
clock.tick(interval)
@@ -439,7 +440,7 @@ describe('profiler', function () {
439440
})
440441

441442
it('should flush when flush after intervals is reached', async () => {
442-
await profiler._start({ profilers, exporters })
443+
profiler.start({ profilers, exporters })
443444

444445
// flushAfterIntervals + 1 becauses flushes after last interval
445446
for (let i = 0; i < flushAfterIntervals + 1; i++) {

0 commit comments

Comments
 (0)