Skip to content

Commit

Permalink
Keep a profiling context object in spans (#4763)
Browse files Browse the repository at this point in the history
Allows for cheapest sampling context updates on async context switches and opens the path for profiling custom context.
  • Loading branch information
szegedi authored Oct 8, 2024
1 parent a2b318d commit 2d175d3
Showing 1 changed file with 57 additions and 69 deletions.
126 changes: 57 additions & 69 deletions packages/dd-trace/src/profiling/profilers/wall.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const enterCh = dc.channel('dd-trace:storage:enter')
const spanFinishCh = dc.channel('dd-trace:span:finish')
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')

const MemoizedWebTags = Symbol('NativeWallProfiler.MemoizedWebTags')
const ProfilingContext = Symbol('NativeWallProfiler.ProfilingContext')

let kSampleCount

Expand All @@ -44,38 +44,6 @@ function endpointNameFromTags (tags) {
].filter(v => v).join(' ')
}

function getWebTags (startedSpans, i, span) {
// Are web tags for this span already memoized?
const memoizedWebTags = span[MemoizedWebTags]
if (memoizedWebTags !== undefined) {
return memoizedWebTags
}
// No, we'll have to memoize a new value
function memoize (tags) {
span[MemoizedWebTags] = tags
return tags
}
// Is this span itself a web span?
const context = span.context()
const tags = context._tags
if (isWebServerSpan(tags)) {
return memoize(tags)
}
// It isn't. Get parent's web tags (memoize them too recursively.)
// There might be several webspans, for example with next.js, http plugin creates the first span
// and then next.js plugin creates a child span, and this child span has the correct endpoint
// information. That's why we always use the tags of the closest ancestor web span.
const parentId = context._parentId
while (--i >= 0) {
const ispan = startedSpans[i]
if (ispan.context()._spanId === parentId) {
return memoize(getWebTags(startedSpans, i, ispan))
}
}
// Local root span with no web span
return memoize(null)
}

let channelsActivated = false
function ensureChannelsActivated () {
if (channelsActivated) return
Expand Down Expand Up @@ -184,14 +152,10 @@ class NativeWallProfiler {
})

if (this._withContexts) {
this._currentContext = {}
this._pprof.time.setContext(this._currentContext)
this._setNewContext()

if (this._captureSpanData) {
this._profilerState = this._pprof.time.getState()
this._lastSpan = undefined
this._lastStartedSpans = undefined
this._lastWebTags = undefined
this._lastSampleCount = 0

beforeCh.subscribe(this._enter)
Expand All @@ -209,51 +173,78 @@ class NativeWallProfiler {
const sampleCount = this._profilerState[kSampleCount]
if (sampleCount !== this._lastSampleCount) {
this._lastSampleCount = sampleCount
const context = this._currentContext
this._currentContext = {}
this._pprof.time.setContext(this._currentContext)
const context = this._currentContext.ref
this._setNewContext()

this._updateContext(context)
}

const span = getActiveSpan()
if (span) {
this._currentContext.ref = span ? this._getProfilingContext(span) : {}
}

_getProfilingContext (span) {
let profilingContext = span[ProfilingContext]
if (profilingContext === undefined) {
const context = span.context()
this._lastSpan = span
const startedSpans = getStartedSpans(context)
this._lastStartedSpans = startedSpans

let spanId
let rootSpanId
if (this._codeHotspotsEnabled) {
spanId = context._spanId
rootSpanId = startedSpans.length ? startedSpans[0].context()._spanId : context._spanId
}

let webTags
if (this._endpointCollectionEnabled) {
this._lastWebTags = getWebTags(startedSpans, startedSpans.length, span)
const tags = context._tags
if (isWebServerSpan(tags)) {
webTags = tags
} else {
// Get parent's context's web tags
const parentId = context._parentId
for (let i = startedSpans.length; --i >= 0;) {
const ispan = startedSpans[i]
if (ispan.context()._spanId === parentId) {
webTags = this._getProfilingContext(ispan).webTags
break
}
}
}
}
} else {
this._lastStartedSpans = undefined
this._lastSpan = undefined
this._lastWebTags = undefined

profilingContext = { spanId, rootSpanId, webTags }
span[ProfilingContext] = profilingContext
}
return profilingContext
}

_setNewContext () {
this._pprof.time.setContext(
this._currentContext = {
ref: {}
}
)
}

_updateContext (context) {
if (!this._lastSpan) {
return
if (typeof context.spanId === 'object') {
context.spanId = context.spanId.toString(10)
}
if (this._codeHotspotsEnabled) {
context.spanId = this._lastSpan.context().toSpanId()
const rootSpan = this._lastStartedSpans[0]
if (rootSpan) {
context.rootSpanId = rootSpan.context().toSpanId()
}
if (typeof context.rootSpanId === 'object') {
context.rootSpanId = context.rootSpanId.toString(10)
}
if (this._lastWebTags) {
context.webTags = this._lastWebTags
if (context.webTags !== undefined && context.endpoint === undefined) {
// endpoint may not be determined yet, but keep it as fallback
// if tags are not available anymore during serialization
context.endpoint = endpointNameFromTags(this._lastWebTags)
context.endpoint = endpointNameFromTags(context.webTags)
}
}

_spanFinished (span) {
if (span[MemoizedWebTags]) {
span[MemoizedWebTags] = undefined
if (span[ProfilingContext] !== undefined) {
span[ProfilingContext] = undefined
}
}

Expand Down Expand Up @@ -288,9 +279,6 @@ class NativeWallProfiler {
enterCh.unsubscribe(this._enter)
spanFinishCh.unsubscribe(this._spanFinished)
this._profilerState = undefined
this._lastSpan = undefined
this._lastStartedSpans = undefined
this._lastWebTags = undefined
}
this._started = false
}
Expand All @@ -313,20 +301,20 @@ class NativeWallProfiler {

const labels = { ...getThreadLabels() }

const { context: { spanId, rootSpanId, webTags, endpoint }, timestamp } = context
const { context: { ref: { spanId, rootSpanId, webTags, endpoint } }, timestamp } = context

if (this._timelineEnabled) {
// Incoming timestamps are in microseconds, we emit nanos.
labels[END_TIMESTAMP_LABEL] = timestamp * 1000n
}

if (spanId) {
if (spanId !== undefined) {
labels[SPAN_ID_LABEL] = spanId
}
if (rootSpanId) {
if (rootSpanId !== undefined) {
labels[LOCAL_ROOT_SPAN_ID_LABEL] = rootSpanId
}
if (webTags && Object.keys(webTags).length !== 0) {
if (webTags !== undefined && Object.keys(webTags).length !== 0) {
labels['trace endpoint'] = endpointNameFromTags(webTags)
} else if (endpoint) {
// fallback to endpoint computed when sample was taken
Expand Down

0 comments on commit 2d175d3

Please sign in to comment.