feat(profiling): Start profilers synchronously within tracer initialization#5906
feat(profiling): Start profilers synchronously within tracer initialization#5906
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #5906 +/- ##
==========================================
- Coverage 80.45% 80.09% -0.37%
==========================================
Files 748 744 -4
Lines 32405 32174 -231
==========================================
- Hits 26072 25770 -302
- Misses 6333 6404 +71
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Overall package sizeSelf size: 5.04 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.0.0 | 81.15 kB | 815.98 kB | | dc-polyfill | 0.1.10 | 26.73 kB | 26.73 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
This comment has been minimized.
This comment has been minimized.
7b483ee to
f503427
Compare
BenchmarksBenchmark execution time: 2026-03-23 13:57:48 Comparing candidate commit adfb0e9 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 228 metrics, 32 unstable metrics. |
f503427 to
ff3352a
Compare
Replace `await SourceMapper.create()` with a synchronous `new SourceMapper()` constructor call followed by a fire-and-forget `loadDirectory()`. The mapper is handed to profilers immediately (with an initially empty `infoMap`); `#sourceMapCount` is updated in the background `.then()` callback once the filesystem scan completes. This removes the only `await` from `_start()`, making it — and the entire profiler start path — synchronous. `start()` now uses try/catch instead of `.catch()` chaining. `proxy.js` is updated accordingly: `_profilerStarted` is stored as a plain boolean and `profilerStarted()` wraps it in `Promise.resolve()` for backwards compatibility with existing callers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move zlib/compression setup out of the synchronous startup path into a lazy `#getCompressionFn()` private method, initialized on first call. `zlib` and `util.promisify` are now required inside that method rather than at module load time, keeping the startup path free of I/O-adjacent module loading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ff3352a to
12f9f2f
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
With the synchronous profiler startup change, source map loading (DD_PROFILING_SOURCE_MAP defaults to true) now happens concurrently with profiling instead of before it. This causes source map directory scan events (dir.read, promises.opendir, etc.) to appear in the timeline profile without span IDs, failing the previous assertion that all events of a given type must have span IDs. Fix by skipping events without span IDs in gatherTimelineEvents instead of asserting they must be present. User-initiated operations (wrapped in tracer.trace()) still have span IDs and are still collected; the count check continues to verify they are all captured. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…zation (#5906) * feat(profiler): make profiler startup synchronous Replace `await SourceMapper.create()` with a synchronous `new SourceMapper()` constructor call followed by a fire-and-forget `loadDirectory()`. The mapper is handed to profilers immediately (with an initially empty `infoMap`); `#sourceMapCount` is updated in the background `.then()` callback once the filesystem scan completes. This removes the only `await` from `_start()`, making it — and the entire profiler start path — synchronous. `start()` now uses try/catch instead of `.catch()` chaining. `proxy.js` is updated accordingly: `_profilerStarted` is stored as a plain boolean and `profilerStarted()` wraps it in `Promise.resolve()` for backwards compatibility with existing callers. We also move zlib/compression setup out of the synchronous startup path into a lazy `#getCompressionFn()` private method, initialized on first call. `zlib` and `util.promisify` are now required inside that method rather than at module load time, keeping the startup path free of I/O-adjacent module loading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…zation (#5906) * feat(profiler): make profiler startup synchronous Replace `await SourceMapper.create()` with a synchronous `new SourceMapper()` constructor call followed by a fire-and-forget `loadDirectory()`. The mapper is handed to profilers immediately (with an initially empty `infoMap`); `#sourceMapCount` is updated in the background `.then()` callback once the filesystem scan completes. This removes the only `await` from `_start()`, making it — and the entire profiler start path — synchronous. `start()` now uses try/catch instead of `.catch()` chaining. `proxy.js` is updated accordingly: `_profilerStarted` is stored as a plain boolean and `profilerStarted()` wraps it in `Promise.resolve()` for backwards compatibility with existing callers. We also move zlib/compression setup out of the synchronous startup path into a lazy `#getCompressionFn()` private method, initialized on first call. `zlib` and `util.promisify` are now required inside that method rather than at module load time, keeping the startup path free of I/O-adjacent module loading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
What does this PR do?
Start profilers synchronously within tracer initialization.
Motivation
Profiler was started asynchronously because of source map loading – it uses async file IO. There are drawbacks to starting the profiler asynchronously though. A minor issue is that it won't start capturing CPU samples for the root program. A larger issue is that any long-lived allocations made after tracer init in the root program also won't ever be shown in the heap live size snapshots. We've had customers complaining about this.
Additional Notes
Jira: PROF-14055