#7813 CPU profiling report is vague and doesn't link to source code
CPU profiles show bundled code positions, not source-mapped. keepNames preserves function names but not file locations. Inspector proxy doesn't transform profile data.
Implement source-mapping of CPU profile call frames in inspector proxy
Analysis Report
Issue Review: cloudflare/workers-sdk#7813
Summary
CPU profiling in Chrome DevTools during wrangler dev shows bundled code locations and vague function names instead of linking to original source code.
Findings
- Created: 2025-01-18
- Updated: 2025-10-30
- Version: 3.103.2 -> 4.60.0 (current)
- Component: Wrangler (dev mode profiling)
- Labels: bug
- Comments: 0
Key Evidence
Issue is reproducible: The reporter's scenario is valid - when CPU profiling a Worker during
wrangler dev, the profiler shows bundled code locations rather than original source files.keepNames fix exists but doesn't solve this: PR #6902 (merged Oct 2024) added
keepNames: trueto preserve function.nameproperties. However, this only helps with runtime function name identification, not CPU profile source mapping.Source maps are generated but not applied to profiles:
- Wrangler correctly generates source maps (
sourcemap: truein esbuild) - Source maps work for stack traces and debugging via
Network.loadNetworkResource - However, CPU profile data from V8 contains positions in bundled code, and DevTools may not automatically apply source maps to profile flame graphs
- Wrangler correctly generates source maps (
No existing fix found:
- No merged PRs explicitly fix this issue
- Changelog does not mention issue #7813
- The issue remains valid and unfixed
Root cause identified: The V8 CPU profiler reports call frames with positions in the executed (bundled) code. While Chrome DevTools can resolve source maps for debugging, CPU profile visualization requires additional source map processing that isn't currently happening.
Recommendation
Status: KEEP OPEN
Reasoning: This is a valid bug affecting developer experience during profiling. The keepNames fix doesn't address the core issue of CPU profile source mapping. Source maps are generated correctly but aren't being applied to CPU profile data in DevTools.
Action: Keep open for engineering investigation. This may require changes to how the inspector proxy handles CPU profile data, potentially source-mapping the profile before sending to DevTools.
Root Cause Analysis
The issue stems from how CPU profiling works with bundled code:
Bundle step (
packages/wrangler/src/deployment-bundle/bundle.ts:387):- esbuild bundles the worker with
sourcemap: true - Source maps are correctly generated with mappings
- esbuild bundles the worker with
Source URL injection (
packages/wrangler/src/deployment-bundle/source-url.ts:14-38):withSourceURLs()adds//# sourceURLcomments to help V8 identify files- This enables DevTools to request source maps via
Debugger.scriptParsed
DevTools source map loading (
packages/wrangler/src/dev/inspect.ts:212-239):- When DevTools requests source maps, they are served with
x_google_ignoreListfor Wrangler internals - This works for debugging but not for profiling
- When DevTools requests source maps, they are served with
CPU Profile issue (
packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts):- CPU profile data from
Profiler.stopcontains positions in bundled code - The profile data is passed through without source map transformation
- DevTools shows bundled code locations in the flame graph
- CPU profile data from
Why keepNames doesn't fix this
The keepNames: true option (packages/wrangler/src/deployment-bundle/bundle.ts:369) injects code like:
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// ...
__name(addNumbers, "addNumbers");
This preserves the function's .name property at runtime, which helps with:
Error.stacktracesFunction.prototype.nameaccess- Some DevTools debugging features
However, CPU profile call frames contain:
- Script ID
- Line number in executed (bundled) code
- Column number in executed (bundled) code
- Function name (which IS preserved by keepNames)
The function names appear correct, but the file locations still point to bundled code positions.
Proposed Solution
Option 1: Source-map CPU profiles in the inspector proxy (Recommended)
Modify the inspector proxy to transform CPU profile data before sending to DevTools.
Files to modify:
packages/wrangler/templates/startDevWorker/InspectorProxyWorker.tspackages/wrangler/src/dev/inspect.ts(add profile transformation utilities)
Implementation approach:
// In InspectorProxyWorker.ts, intercept Profiler.stop responses
handleRuntimeMessage = (event: MessageEvent) => {
const message = JSON.parse(event.data);
if (message.method === "Profiler.stop" && message.result?.profile) {
// Transform profile to use source-mapped positions
message.result.profile = this.sourcemapProfile(message.result.profile);
}
this.sendDevToolsMessage(message);
};
sourcemapProfile(profile: CPUProfile): CPUProfile {
// For each node in profile.nodes:
// 1. Get the scriptId -> find corresponding source map
// 2. Map (lineNumber, columnNumber) through source map
// 3. Update the call frame with source position
return {
...profile,
nodes: profile.nodes.map(node => ({
...node,
callFrame: this.sourcemapCallFrame(node.callFrame)
}))
};
}
Option 2: Use names array in source maps
Ensure esbuild populates the names array in generated source maps. Currently it's empty:
{ "names": [] }
This requires esbuild configuration or a post-processing step to populate original identifier names.
Note: This alone won't fix line/column mapping but would improve name resolution in some tools.
Option 3: Documentation workaround
Document that users can:
- Use
wrangler check startupfor source-mapped CPU profiles of startup - Use
--no-bundleflag during dev for direct source debugging (with limitations)
Implementation Difficulty
Medium
Justification:
- The infrastructure for source map handling already exists
- CPU profile format is well-documented (Chrome DevTools Protocol)
- Similar source-mapping is done for stack traces in
getSourceMappedString() - Main complexity is ensuring all profile nodes are correctly mapped
- Need to handle cases where source maps aren't available
Files to Modify
packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts- Add CPU profile interception and transformation
- Implement source map lookup for profile call frames
packages/wrangler/src/dev/inspect.ts- Add utility function for source-mapping CPU profile data
- Reuse existing source map infrastructure
packages/wrangler/src/api/startDevWorker/bundle-allowed-paths.ts- Potentially extend to track source maps for profile transformation
packages/wrangler/src/check/commands.ts- Apply same transformation to
wrangler check startupprofiles
- Apply same transformation to
Testing Recommendations
Unit tests:
- Test source-mapping of CPU profile call frames
- Test handling of missing source maps
- Test handling of external/node_modules code
Integration tests:
- Profile a worker during dev, verify source locations in output
- Test with various bundling configurations
- Test with TypeScript and JavaScript sources
Manual testing:
- Record CPU profile in Chrome DevTools during
wrangler dev - Verify flame graph shows original source file names and line numbers
- Click through to source files works correctly
- Record CPU profile in Chrome DevTools during
Suggested Comment
Thank you for reporting this issue. We've investigated and confirmed this is a valid bug.
Summary: While
keepNames: truewas added in PR #6902 to preserve function names, the core issue is that CPU profile data from V8 contains positions in the bundled code rather than source-mapped positions. Chrome DevTools receives and displays these bundled locations.Root cause: The inspector proxy passes CPU profile data through without applying source map transformations. Unlike stack traces (which are source-mapped), CPU profiles show raw bundled code locations.
Potential fix: Transform CPU profile data in the inspector proxy to use source-mapped positions before sending to DevTools.
We're keeping this open for engineering prioritization.
Notes & Feedback (0)
No notes yet.