11'use strict'
22
3+ const { storage } = require ( '../../../../datadog-core' )
4+
5+ const dc = require ( '../../../../diagnostics_channel' )
6+
7+ const beforeCh = dc . channel ( 'dd-trace:storage:before' )
8+ const enterCh = dc . channel ( 'dd-trace:storage:enter' )
9+
10+ let kSampleCount
11+
12+ function getActiveSpan ( ) {
13+ const store = storage . getStore ( )
14+ return store && store . span
15+ }
16+
17+ function getStartedSpans ( context ) {
18+ return context . _trace . started
19+ }
20+
21+ function generateLabels ( { spanId, rootSpanId, webTags, endpoint } ) {
22+ const labels = { }
23+ if ( spanId ) {
24+ labels [ 'span id' ] = spanId
25+ }
26+ if ( rootSpanId ) {
27+ labels [ 'local root span id' ] = rootSpanId
28+ }
29+ if ( webTags && Object . keys ( webTags ) . length !== 0 ) {
30+ labels [ 'trace endpoint' ] = endpointNameFromTags ( webTags )
31+ } else if ( endpoint ) {
32+ // fallback to endpoint computed when sample was taken
33+ labels [ 'trace endpoint' ] = endpoint
34+ }
35+
36+ return labels
37+ }
38+
39+ function getSpanContextTags ( span ) {
40+ return span . context ( ) . _tags
41+ }
42+
43+ function isWebServerSpan ( tags ) {
44+ return tags [ 'span.type' ] === 'web'
45+ }
46+
47+ function endpointNameFromTags ( tags ) {
48+ return tags [ 'resource.name' ] || [
49+ tags [ 'http.method' ] ,
50+ tags [ 'http.route' ]
51+ ] . filter ( v => v ) . join ( ' ' )
52+ }
53+
54+ function updateContext ( context , span , startedSpans , endpointCollectionEnabled ) {
55+ context . spanId = span . context ( ) . toSpanId ( )
56+ const rootSpan = startedSpans [ 0 ]
57+ if ( rootSpan ) {
58+ context . rootSpanId = rootSpan . context ( ) . toSpanId ( )
59+ if ( endpointCollectionEnabled ) {
60+ for ( let i = startedSpans . length - 1 ; i >= 0 ; i -- ) {
61+ const tags = getSpanContextTags ( startedSpans [ i ] )
62+ if ( isWebServerSpan ( tags ) ) {
63+ context . webTags = tags
64+ // endpoint may not be determined yet, but keep it as fallback
65+ // if tags are not available anymore during serialization
66+ context . endpoint = endpointNameFromTags ( tags )
67+ break
68+ }
69+ }
70+ }
71+ }
72+ }
73+
374class NativeWallProfiler {
475 constructor ( options = { } ) {
576 this . type = 'wall'
677 this . _samplingIntervalMicros = options . samplingInterval || 1e6 / 99 // 99hz
778 this . _flushIntervalMillis = options . flushInterval || 60 * 1e3 // 60 seconds
879 this . _codeHotspotsEnabled = ! ! options . codeHotspotsEnabled
80+ this . _endpointCollectionEnabled = ! ! options . endpointCollectionEnabled
981 this . _mapper = undefined
1082 this . _pprof = undefined
1183
84+ // Bind to this so the same value can be used to unsubscribe later
85+ this . _enter = this . _enter . bind ( this )
1286 this . _logger = options . logger
1387 this . _started = false
88+ this . _profilerState = undefined
89+ this . _span = undefined
90+ this . _startedSpans = undefined
91+ }
92+
93+ codeHotspotsEnabled ( ) {
94+ return this . _codeHotspotsEnabled
1495 }
1596
1697 start ( { mapper } = { } ) {
1798 if ( this . _started ) return
1899
100+ if ( this . _codeHotspotsEnabled && ! this . _emittedFFMessage && this . _logger ) {
101+ this . _logger . debug (
102+ `Wall profiler: Enable config_trace_show_breakdown_profiling_for_node feature flag to see code hotspots.` )
103+ this . _emittedFFMessage = true
104+ }
105+
19106 this . _mapper = mapper
20107 this . _pprof = require ( '@datadog/pprof' )
108+ kSampleCount = this . _pprof . time . constants . kSampleCount
21109
22110 // pprof otherwise crashes in worker threads
23111 if ( ! process . _startProfilerIdleNotifier ) {
@@ -31,16 +119,57 @@ class NativeWallProfiler {
31119 intervalMicros : this . _samplingIntervalMicros ,
32120 durationMillis : this . _flushIntervalMillis ,
33121 sourceMapper : this . _mapper ,
34- customLabels : this . _codeHotspotsEnabled ,
122+ withContexts : this . _codeHotspotsEnabled ,
35123 lineNumbers : false
36124 } )
37125
126+ if ( this . _codeHotspotsEnabled ) {
127+ this . _profilerState = this . _pprof . time . getState ( )
128+ this . _currentContext = { }
129+ this . _pprof . time . setContext ( this . _currentContext )
130+
131+ beforeCh . subscribe ( this . _enter )
132+ enterCh . subscribe ( this . _enter )
133+ }
134+
38135 this . _started = true
39136 }
40137
41- profile ( ) {
138+ _enter ( ) {
42139 if ( ! this . _started ) return
43- return this . _pprof . time . stop ( true )
140+
141+ if ( this . _profilerState [ kSampleCount ] !== 0 ) {
142+ this . _profilerState [ kSampleCount ] = 0
143+ const context = this . _currentContext
144+ this . _currentContext = { }
145+ this . _pprof . time . setContext ( this . _currentContext )
146+
147+ if ( this . _span ) {
148+ updateContext ( context , this . _span , this . _startedSpans , this . _endpointCollectionEnabled )
149+ }
150+ }
151+
152+ const span = getActiveSpan ( )
153+ if ( span ) {
154+ this . _span = span
155+ this . _startedSpans = getStartedSpans ( span . context ( ) )
156+ } else {
157+ this . _startedSpans = undefined
158+ this . _span = undefined
159+ }
160+ }
161+
162+ _stop ( restart ) {
163+ if ( ! this . _started ) return
164+ if ( this . _codeHotspotsEnabled ) {
165+ // update last sample context if needed
166+ this . _enter ( )
167+ }
168+ return this . _pprof . time . stop ( restart , this . _codeHotspotsEnabled ? generateLabels : undefined )
169+ }
170+
171+ profile ( ) {
172+ return this . _stop ( true )
44173 }
45174
46175 encode ( profile ) {
@@ -50,7 +179,13 @@ class NativeWallProfiler {
50179 stop ( ) {
51180 if ( ! this . _started ) return
52181
53- const profile = this . _pprof . time . stop ( )
182+ const profile = this . _stop ( false )
183+ if ( this . _codeHotspotsEnabled ) {
184+ beforeCh . unsubscribe ( this . _enter )
185+ enterCh . subscribe ( this . _enter )
186+ this . _profilerState = undefined
187+ }
188+
54189 this . _started = false
55190 return profile
56191 }
0 commit comments