-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
ref(browser): Move trace lifecycle listeners to class function #18231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,76 +62,7 @@ export class UIProfiler { | |
| this._client = client; | ||
| this._sessionSampled = sessionSampled; | ||
|
|
||
| client.on('spanStart', span => { | ||
| if (!this._sessionSampled) { | ||
| DEBUG_BUILD && debug.log('[Profiling] Session not sampled because of negative sampling decision.'); | ||
| return; | ||
| } | ||
| if (span !== getRootSpan(span)) { | ||
| return; | ||
| } | ||
| // Only count sampled root spans | ||
| if (!span.isRecording()) { | ||
| DEBUG_BUILD && debug.log('[Profiling] Discarding profile because root span was not sampled.'); | ||
| return; | ||
| } | ||
|
|
||
| // Matching root spans with profiles | ||
| getGlobalScope().setContext('profile', { | ||
| profiler_id: this._profilerId, | ||
| }); | ||
|
|
||
| const spanId = span.spanContext().spanId; | ||
| if (!spanId) { | ||
| return; | ||
| } | ||
| if (this._activeRootSpanIds.has(spanId)) { | ||
| return; | ||
| } | ||
|
|
||
| this._activeRootSpanIds.add(spanId); | ||
| const rootSpanCount = this._activeRootSpanIds.size; | ||
|
|
||
| const timeout = setTimeout(() => { | ||
| this._onRootSpanTimeout(spanId); | ||
| }, MAX_ROOT_SPAN_PROFILE_MS); | ||
| this._rootSpanTimeouts.set(spanId, timeout); | ||
|
|
||
| if (rootSpanCount === 1) { | ||
| DEBUG_BUILD && | ||
| debug.log( | ||
| `[Profiling] Root span with ID ${spanId} started. Will continue profiling for as long as there are active root spans (currently: ${rootSpanCount}).`, | ||
| ); | ||
|
|
||
| this.start(); | ||
| } | ||
| }); | ||
|
|
||
| client.on('spanEnd', span => { | ||
| if (!this._sessionSampled) { | ||
| return; | ||
| } | ||
|
|
||
| const spanId = span.spanContext().spanId; | ||
| if (!spanId || !this._activeRootSpanIds.has(spanId)) { | ||
| return; | ||
| } | ||
|
|
||
| this._activeRootSpanIds.delete(spanId); | ||
| const rootSpanCount = this._activeRootSpanIds.size; | ||
|
|
||
| DEBUG_BUILD && | ||
| debug.log( | ||
| `[Profiling] Root span with ID ${spanId} ended. Will continue profiling for as long as there are active root spans (currently: ${rootSpanCount}).`, | ||
| ); | ||
| if (rootSpanCount === 0) { | ||
| this._collectCurrentChunk().catch(e => { | ||
| DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on `spanEnd`:', e); | ||
| }); | ||
|
|
||
| this.stop(); | ||
| } | ||
| }); | ||
| this._setupTraceLifecycleListeners(client); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -170,6 +101,9 @@ export class UIProfiler { | |
|
|
||
| DEBUG_BUILD && debug.log('[Profiling] Started profiling with profile ID:', this._profilerId); | ||
|
|
||
| // Expose profiler_id to match root spans with profiles | ||
| getGlobalScope().setContext('profile', { profiler_id: this._profilerId }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Profiler Context: Spans AdriftMoving the global scope context setting from the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Works as intended, this is still attached at the correct time. |
||
|
|
||
| this._startProfilerInstance(); | ||
|
|
||
| if (!this._profiler) { | ||
|
|
@@ -203,6 +137,63 @@ export class UIProfiler { | |
| }); | ||
| } | ||
|
|
||
| /** Trace-mode: attach spanStart/spanEnd listeners. */ | ||
| private _setupTraceLifecycleListeners(client: Client): void { | ||
| client.on('spanStart', span => { | ||
| if (!this._sessionSampled) { | ||
| DEBUG_BUILD && debug.log('[Profiling] Session not sampled because of negative sampling decision.'); | ||
| return; | ||
| } | ||
| if (span !== getRootSpan(span)) { | ||
| return; // only care about root spans | ||
| } | ||
| // Only count sampled root spans | ||
| if (!span.isRecording()) { | ||
| DEBUG_BUILD && debug.log('[Profiling] Discarding profile because root span was not sampled.'); | ||
| return; | ||
| } | ||
|
|
||
| const spanId = span.spanContext().spanId; | ||
| if (!spanId || this._activeRootSpanIds.has(spanId)) { | ||
| return; | ||
| } | ||
|
|
||
| this._registerTraceRootSpan(spanId); | ||
|
|
||
| const rootSpanCount = this._activeRootSpanIds.size; | ||
| if (rootSpanCount === 1) { | ||
| DEBUG_BUILD && | ||
| debug.log( | ||
| `[Profiling] Root span ${spanId} started. Profiling active while there are active root spans (count=${rootSpanCount}).`, | ||
| ); | ||
| this.start(); | ||
| } | ||
| }); | ||
|
|
||
| client.on('spanEnd', span => { | ||
| if (!this._sessionSampled) { | ||
| return; | ||
| } | ||
| const spanId = span.spanContext().spanId; | ||
| if (!spanId || !this._activeRootSpanIds.has(spanId)) { | ||
| return; | ||
| } | ||
| this._activeRootSpanIds.delete(spanId); | ||
| const rootSpanCount = this._activeRootSpanIds.size; | ||
|
|
||
| DEBUG_BUILD && | ||
| debug.log( | ||
| `[Profiling] Root span with ID ${spanId} ended. Will continue profiling for as long as there are active root spans (currently: ${rootSpanCount}).`, | ||
| ); | ||
| if (rootSpanCount === 0) { | ||
| this._collectCurrentChunk().catch(e => { | ||
| DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on last `spanEnd`:', e); | ||
| }); | ||
| this.stop(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Resets profiling information from scope and resets running state | ||
| */ | ||
|
|
@@ -219,6 +210,13 @@ export class UIProfiler { | |
| this._rootSpanTimeouts.clear(); | ||
| } | ||
|
|
||
| /** Register root span and schedule safeguard timeout (trace mode). */ | ||
| private _registerTraceRootSpan(spanId: string): void { | ||
| this._activeRootSpanIds.add(spanId); | ||
| const timeout = setTimeout(() => this._onRootSpanTimeout(spanId), MAX_ROOT_SPAN_PROFILE_MS); | ||
| this._rootSpanTimeouts.set(spanId, timeout); | ||
| } | ||
|
|
||
| /** | ||
| * Start a profiler instance if needed. | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug:
notifyRootSpanActive()fails to register a safeguard timeout for already-active root spans, leading to indefinite profiling ifspanEndis not called.Severity: CRITICAL | Confidence: 0.95
🔍 Detailed Analysis
The
notifyRootSpanActive()method, used for already-active root spans during profiler initialization, adds the span ID to_activeRootSpanIdsbut fails to call_registerTraceRootSpan(). This omission means no safeguard timeout is registered for these spans. If such a pre-existing root span never fires itsspanEndevent, the profiler will continue running indefinitely, wasting resources by continuously collecting and sending profile chunks every 60 seconds, contradicting the intended design ofMAX_ROOT_SPAN_PROFILE_MS.💡 Suggested Fix
Modify
notifyRootSpanActive()to call_registerTraceRootSpan(spanId)instead of directly addingspanIdto_activeRootSpanIds. This ensures that all active root spans, regardless of how they are initialized, have the necessary safeguard timeout registered.🤖 Prompt for AI Agent
Did we get this right? 👍 / 👎 to inform future reviews.
Reference_id: 2726774
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is done in another function - no problem here
It's kept track of it in
_rootSpanTimeouts