Skip to content

Commit

Permalink
PROF-8461: make endpoint profiling independent of code hotspots (#3727)
Browse files Browse the repository at this point in the history
  • Loading branch information
szegedi authored and uurien committed Nov 3, 2023
1 parent f283097 commit 705b1c6
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 115 deletions.
20 changes: 3 additions & 17 deletions packages/dd-trace/src/profiling/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,24 +128,10 @@ class Config {
? options.profilers
: getProfilers({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS })

function getCodeHotspotsOptionsOr (defvalue) {
return coalesce(options.codeHotspotsEnabled,
DD_PROFILING_CODEHOTSPOTS_ENABLED,
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, defvalue)
}
this.codeHotspotsEnabled = isTrue(getCodeHotspotsOptionsOr(false))
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
DD_PROFILING_CODEHOTSPOTS_ENABLED,
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
if (this.endpointCollectionEnabled && !this.codeHotspotsEnabled) {
if (getCodeHotspotsOptionsOr(undefined) !== undefined) {
this.logger.warn(
'Endpoint collection is enabled, but Code Hotspots are disabled. ' +
'Enable Code Hotspots too for endpoint collection to work.')
this.endpointCollectionEnabled = false
} else {
this.logger.info('Code Hotspots are implicitly enabled by endpoint collection.')
this.codeHotspotsEnabled = true
}
}

this.profilers = ensureProfilers(profilers, this)
}
Expand Down
73 changes: 36 additions & 37 deletions packages/dd-trace/src/profiling/profilers/wall.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,36 +64,14 @@ function endpointNameFromTags (tags) {
].filter(v => v).join(' ')
}

function updateContext (context, span, startedSpans, endpointCollectionEnabled) {
context.spanId = span.context().toSpanId()
const rootSpan = startedSpans[0]
if (rootSpan) {
context.rootSpanId = rootSpan.context().toSpanId()
if (endpointCollectionEnabled) {
// Find the first webspan starting from the end:
// There might be several webspans, for example with next.js, http plugin creates a first span
// and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
for (let i = startedSpans.length - 1; i >= 0; i--) {
const tags = getSpanContextTags(startedSpans[i])
if (isWebServerSpan(tags)) {
context.webTags = tags
// endpoint may not be determined yet, but keep it as fallback
// if tags are not available anymore during serialization
context.endpoint = endpointNameFromTags(tags)
break
}
}
}
}
}

class NativeWallProfiler {
constructor (options = {}) {
this.type = 'wall'
this._samplingIntervalMicros = options.samplingInterval || 1e6 / 99 // 99hz
this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
this._withContexts = this._codeHotspotsEnabled || this._endpointCollectionEnabled
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
this._mapper = undefined
this._pprof = undefined
Expand All @@ -115,12 +93,6 @@ class NativeWallProfiler {
start ({ mapper } = {}) {
if (this._started) return

if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
this._logger.debug(
`Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
this._emittedFFMessage = true
}

this._mapper = mapper
this._pprof = require('@datadog/pprof')
kSampleCount = this._pprof.time.constants.kSampleCount
Expand All @@ -137,12 +109,12 @@ class NativeWallProfiler {
intervalMicros: this._samplingIntervalMicros,
durationMillis: this._flushIntervalMillis,
sourceMapper: this._mapper,
withContexts: this._codeHotspotsEnabled,
withContexts: this._withContexts,
lineNumbers: false,
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
})

if (this._codeHotspotsEnabled) {
if (this._withContexts) {
this._profilerState = this._pprof.time.getState()
this._currentContext = {}
this._pprof.time.setContext(this._currentContext)
Expand All @@ -167,9 +139,7 @@ class NativeWallProfiler {
this._currentContext = {}
this._pprof.time.setContext(this._currentContext)

if (this._lastSpan) {
updateContext(context, this._lastSpan, this._lastStartedSpans, this._endpointCollectionEnabled)
}
this._updateContext(context)
}

const span = getActiveSpan()
Expand All @@ -182,6 +152,35 @@ class NativeWallProfiler {
}
}

_updateContext (context) {
if (!this._lastSpan) {
return
}
if (this._codeHotspotsEnabled) {
context.spanId = this._lastSpan.context().toSpanId()
const rootSpan = this._lastStartedSpans[0]
if (rootSpan) {
context.rootSpanId = rootSpan.context().toSpanId()
}
}
if (this._endpointCollectionEnabled) {
const startedSpans = this._lastStartedSpans
// Find the first webspan starting from the end:
// There might be several webspans, for example with next.js, http plugin creates a first span
// and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
for (let i = startedSpans.length - 1; i >= 0; i--) {
const tags = getSpanContextTags(startedSpans[i])
if (isWebServerSpan(tags)) {
context.webTags = tags
// endpoint may not be determined yet, but keep it as fallback
// if tags are not available anymore during serialization
context.endpoint = endpointNameFromTags(tags)
break
}
}
}
}

_reportV8bug (maybeBug) {
const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
Expand All @@ -194,12 +193,12 @@ class NativeWallProfiler {

_stop (restart) {
if (!this._started) return
if (this._codeHotspotsEnabled) {
if (this._withContexts) {
// update last sample context if needed
this._enter()
this._lastSampleCount = 0
}
const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
const profile = this._pprof.time.stop(restart, this._withContexts ? generateLabels : undefined)
if (restart) {
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
if (v8BugDetected !== 0) {
Expand All @@ -221,7 +220,7 @@ class NativeWallProfiler {
if (!this._started) return

const profile = this._stop(false)
if (this._codeHotspotsEnabled) {
if (this._withContexts) {
beforeCh.unsubscribe(this._enter)
enterCh.unsubscribe(this._enter)
this._profilerState = undefined
Expand Down
61 changes: 0 additions & 61 deletions packages/dd-trace/test/profiling/config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,67 +237,6 @@ describe('config', () => {
expect(config.profilers[0].endpointCollectionEnabled()).false
})

it('should implicitly turn on code hotspots for endpoint profiling when they are not explicitly disabled', () => {
process.env = {
DD_PROFILING_PROFILERS: 'wall',
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED: '1'
}
const infos = []
const options = {
logger: {
debug () {},
info (info) {
infos.push(info)
},
warn () {},
error () {}
}
}

const config = new Config(options)

expect(infos.length).to.equal(1)
expect(infos[0]).to.equal('Code Hotspots are implicitly enabled by endpoint collection.')

expect(config.profilers).to.be.an('array')
expect(config.profilers.length).to.equal(1)
expect(config.profilers[0]).to.be.an.instanceOf(WallProfiler)
expect(config.profilers[0].codeHotspotsEnabled()).true
expect(config.profilers[0].endpointCollectionEnabled()).true
})

it('should warn about code hotspots being explicitly disabled with endpoint profiling', () => {
process.env = {
DD_PROFILING_PROFILERS: 'wall',
DD_PROFILING_CODEHOTSPOTS_ENABLED: '0',
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED: '1'
}
const warnings = []
const options = {
logger: {
debug () {},
info () {},
warn (warning) {
warnings.push(warning)
},
error () {}
}
}

const config = new Config(options)

expect(warnings.length).to.equal(1)
expect(warnings[0]).to.equal(
'Endpoint collection is enabled, but Code Hotspots are disabled. ' +
'Enable Code Hotspots too for endpoint collection to work.')

expect(config.profilers).to.be.an('array')
expect(config.profilers.length).to.equal(1)
expect(config.profilers[0]).to.be.an.instanceOf(WallProfiler)
expect(config.profilers[0].codeHotspotsEnabled()).false
expect(config.profilers[0].endpointCollectionEnabled()).false
})

it('should support tags', () => {
const tags = {
env: 'dev'
Expand Down

0 comments on commit 705b1c6

Please sign in to comment.