@@ -63,15 +63,14 @@ export class UIProfiler {
6363
6464 /** Initialize the profiler with client, session sampling and lifecycle mode. */
6565 public initialize ( client : Client , sessionSampled : boolean , lifecycleMode : 'manual' | 'trace' ) : void {
66- this . _client = client ;
67- this . _sessionSampled = sessionSampled ;
68- this . _lifecycleMode = lifecycleMode ;
69-
70- // One profiler ID for the entire profiling session (user session)
7166 this . _profilerId = uuid4 ( ) ;
7267
7368 DEBUG_BUILD && debug . log ( `[Profiling] Initializing profiler (lifecycle='${ lifecycleMode } ').` ) ;
7469
70+ this . _client = client ;
71+ this . _sessionSampled = sessionSampled ;
72+ this . _lifecycleMode = lifecycleMode ;
73+
7574 if ( ! sessionSampled ) {
7675 DEBUG_BUILD && debug . log ( '[Profiling] Session not sampled; profiler will remain inactive.' ) ;
7776 }
@@ -118,33 +117,42 @@ export class UIProfiler {
118117 this . _endProfiling ( ) ;
119118 }
120119
121- /** Notify the profiler of a root span active at setup time (used only in trace mode). */
120+ /** Handle an already-active root span at integration setup time (used only in trace mode). */
122121 public notifyRootSpanActive ( span : Span ) : void {
123122 if ( this . _lifecycleMode !== 'trace' || ! this . _sessionSampled ) {
124123 return ;
125124 }
125+
126126 const spanId = span . spanContext ( ) . spanId ;
127127 if ( ! spanId || this . _activeRootSpanIds . has ( spanId ) ) {
128128 return ;
129129 }
130130 this . _registerTraceRootSpan ( spanId ) ;
131- }
132131
133- /* ========================= Internal Helpers ========================= */
132+ const rootSpanCount = this . _activeRootSpanIds . size ;
133+
134+ if ( rootSpanCount === 1 ) {
135+ DEBUG_BUILD &&
136+ debug . log ( '[Profiling] Detected already active root span during setup. Active root spans now:' , rootSpanCount ) ;
137+
138+ this . _beginProfiling ( ) ;
139+ }
140+ }
134141
135- /** Begin profiling session (shared path used by manual start or trace activation) . */
142+ /** Begin profiling if not already running . */
136143 private _beginProfiling ( ) : void {
137144 if ( this . _isRunning ) {
138145 return ;
139146 }
140147 this . _isRunning = true ;
141148
142- // Expose profiler_id so emitted events can be associated
143- getGlobalScope ( ) . setContext ( 'profile' , { profiler_id : this . _profilerId } ) ;
144-
145149 DEBUG_BUILD && debug . log ( '[Profiling] Started profiling with profiler ID:' , this . _profilerId ) ;
146150
151+ // Expose profiler_id to match root spans with profiles
152+ getGlobalScope ( ) . setContext ( 'profile' , { profiler_id : this . _profilerId } ) ;
153+
147154 this . _startProfilerInstance ( ) ;
155+
148156 if ( ! this . _profiler ) {
149157 DEBUG_BUILD && debug . log ( '[Profiling] Failed to start JS Profiler; stopping.' ) ;
150158 this . _resetProfilerInfo ( ) ;
@@ -154,7 +162,7 @@ export class UIProfiler {
154162 this . _startPeriodicChunking ( ) ;
155163 }
156164
157- /** End profiling session, collect final chunk. */
165+ /** End profiling session; final chunk will be collected and sent . */
158166 private _endProfiling ( ) : void {
159167 if ( ! this . _isRunning ) {
160168 return ;
@@ -166,51 +174,51 @@ export class UIProfiler {
166174 this . _chunkTimer = undefined ;
167175 }
168176
169- // Clear trace-mode timeouts if any
170177 this . _clearAllRootSpanTimeouts ( ) ;
171178
172179 this . _collectCurrentChunk ( ) . catch ( e => {
173180 DEBUG_BUILD && debug . error ( '[Profiling] Failed to collect current profile chunk on stop():' , e ) ;
174181 } ) ;
175182
176- // Clear context so subsequent events aren't marked as profiled
177- getGlobalScope ( ) . setContext ( 'profile' , { } ) ;
183+ // Clear context so subsequent events aren't marked as profiled in manual mode.
184+ // todo: test in manual mode
185+ if ( this . _lifecycleMode === 'manual' ) {
186+ getGlobalScope ( ) . setContext ( 'profile' , { } ) ;
187+ }
178188 }
179189
180190 /** Trace-mode: attach spanStart/spanEnd listeners. */
181191 private _setupTraceLifecycleListeners ( client : Client ) : void {
182192 client . on ( 'spanStart' , span => {
183193 if ( ! this . _sessionSampled ) {
184- DEBUG_BUILD && debug . log ( '[Profiling] Session not sampled; ignoring spanStart .' ) ;
194+ DEBUG_BUILD && debug . log ( '[Profiling] Session not sampled because of negative sampling decision .' ) ;
185195 return ;
186196 }
187197 if ( span !== getRootSpan ( span ) ) {
188198 return ; // only care about root spans
189199 }
190200 // Only count sampled root spans
191201 if ( ! span . isRecording ( ) ) {
192- DEBUG_BUILD && debug . log ( '[Profiling] Ignoring non-sampled root span.' ) ;
202+ DEBUG_BUILD && debug . log ( '[Profiling] Discarding profile because root span was not sampled .' ) ;
193203 return ;
194204 }
195205
196- /*
197- // Matching root spans with profiles
198- getGlobalScope().setContext('profile', {
199- profiler_id: this._profilerId,
200- });
201- */
206+ // Match emitted chunks with events: set profiler_id on global scope
207+ // do I need this?
208+ // getGlobalScope().setContext('profile', { profiler_id: this._profilerId });
202209
203210 const spanId = span . spanContext ( ) . spanId ;
204211 if ( ! spanId || this . _activeRootSpanIds . has ( spanId ) ) {
205212 return ;
206213 }
214+
207215 this . _registerTraceRootSpan ( spanId ) ;
208216
209- const count = this . _activeRootSpanIds . size ;
210- if ( count === 1 ) {
217+ const rootSpanCount = this . _activeRootSpanIds . size ;
218+ if ( rootSpanCount === 1 ) {
211219 DEBUG_BUILD &&
212220 debug . log (
213- `[Profiling] Root span ${ spanId } started. Profiling active while there are active root spans (count=${ count } ).` ,
221+ `[Profiling] Root span ${ spanId } started. Profiling active while there are active root spans (count=${ rootSpanCount } ).` ,
214222 ) ;
215223 this . _beginProfiling ( ) ;
216224 }
@@ -225,46 +233,25 @@ export class UIProfiler {
225233 return ;
226234 }
227235 this . _activeRootSpanIds . delete ( spanId ) ;
236+ const rootSpanCount = this . _activeRootSpanIds . size ;
228237
229- const count = this . _activeRootSpanIds . size ;
230- DEBUG_BUILD && debug . log ( `[Profiling] Root span ${ spanId } ended. Remaining active root spans (count= ${ count } ).` ) ;
231-
232- if ( count === 0 ) {
233- // Collect final chunk before stopping
238+ DEBUG_BUILD &&
239+ debug . log (
240+ `[Profiling] Root span with ID ${ spanId } ended. Will continue profiling for as long as there are active root spans (currently: ${ rootSpanCount } ).` ,
241+ ) ;
242+ if ( rootSpanCount === 0 ) {
234243 this . _collectCurrentChunk ( ) . catch ( e => {
235- DEBUG_BUILD && debug . error ( '[Profiling] Failed to collect current profile chunk on last spanEnd:' , e ) ;
244+ DEBUG_BUILD && debug . error ( '[Profiling] Failed to collect current profile chunk on last ` spanEnd` :' , e ) ;
236245 } ) ;
237246 this . _endProfiling ( ) ;
238247 }
239248 } ) ;
240249 }
241250
242- /** Register root span and schedule safeguard timeout (trace mode). */
243- private _registerTraceRootSpan ( spanId : string ) : void {
244- this . _activeRootSpanIds . add ( spanId ) ;
245- const timeout = setTimeout ( ( ) => this . _onRootSpanTimeout ( spanId ) , MAX_ROOT_SPAN_PROFILE_MS ) ;
246- this . _rootSpanTimeouts . set ( spanId , timeout ) ;
247- }
248-
249- /** Root span timeout handler (trace mode). */
250- private _onRootSpanTimeout ( spanId : string ) : void {
251- if ( ! this . _rootSpanTimeouts . has ( spanId ) ) {
252- return ; // span already ended
253- }
254- this . _rootSpanTimeouts . delete ( spanId ) ;
255-
256- if ( ! this . _activeRootSpanIds . has ( spanId ) ) {
257- return ;
258- }
259-
260- DEBUG_BUILD &&
261- debug . log ( `[Profiling] Reached 5-minute timeout for root span ${ spanId } . Did you forget to call .end()?` ) ;
262-
263- this . _activeRootSpanIds . delete ( spanId ) ;
264-
265- if ( this . _activeRootSpanIds . size === 0 ) {
266- this . _endProfiling ( ) ;
267- }
251+ /** Reset running state and profiling context (used on failure). */
252+ private _resetProfilerInfo ( ) : void {
253+ this . _isRunning = false ;
254+ getGlobalScope ( ) . setContext ( 'profile' , { } ) ;
268255 }
269256
270257 /** Clear all trace-mode root span timeouts. */
@@ -273,10 +260,11 @@ export class UIProfiler {
273260 this . _rootSpanTimeouts . clear ( ) ;
274261 }
275262
276- /** Reset running state and profiling context (used on failure). */
277- private _resetProfilerInfo ( ) : void {
278- this . _isRunning = false ;
279- getGlobalScope ( ) . setContext ( 'profile' , { } ) ;
263+ /** Register root span and schedule safeguard timeout (trace mode). */
264+ private _registerTraceRootSpan ( spanId : string ) : void {
265+ this . _activeRootSpanIds . add ( spanId ) ;
266+ const timeout = setTimeout ( ( ) => this . _onRootSpanTimeout ( spanId ) , MAX_ROOT_SPAN_PROFILE_MS ) ;
267+ this . _rootSpanTimeouts . set ( spanId , timeout ) ;
280268 }
281269
282270 /** Start JS self profiler instance if needed. */
@@ -315,6 +303,27 @@ export class UIProfiler {
315303 } , CHUNK_INTERVAL_MS ) ;
316304 }
317305
306+ /** Root span timeout handler (trace mode). */
307+ private _onRootSpanTimeout ( spanId : string ) : void {
308+ if ( ! this . _rootSpanTimeouts . has ( spanId ) ) {
309+ return ; // span already ended
310+ }
311+ this . _rootSpanTimeouts . delete ( spanId ) ;
312+
313+ if ( ! this . _activeRootSpanIds . has ( spanId ) ) {
314+ return ;
315+ }
316+
317+ DEBUG_BUILD &&
318+ debug . log ( `[Profiling] Reached 5-minute timeout for root span ${ spanId } . Did you forget to call .end()?` ) ;
319+
320+ this . _activeRootSpanIds . delete ( spanId ) ;
321+
322+ if ( this . _activeRootSpanIds . size === 0 ) {
323+ this . _endProfiling ( ) ;
324+ }
325+ }
326+
318327 /** Stop current profiler instance, convert profile to chunk & send. */
319328 private async _collectCurrentChunk ( ) : Promise < void > {
320329 const prev = this . _profiler ;
0 commit comments