Workers SDK Issue Reports

← Back to Dashboard

#7813 CPU profiling report is vague and doesn't link to source code

Download Reproduction
Recommendation:KEEP OPEN
Difficulty:medium
Reasoning:

CPU profiles show bundled code positions, not source-mapped. keepNames preserves function names but not file locations. Inspector proxy doesn't transform profile data.

Suggested Action:

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

  1. 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.

  2. keepNames fix exists but doesn't solve this: PR #6902 (merged Oct 2024) added keepNames: true to preserve function .name properties. However, this only helps with runtime function name identification, not CPU profile source mapping.

  3. Source maps are generated but not applied to profiles:

    • Wrangler correctly generates source maps (sourcemap: true in 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
  4. No existing fix found:

    • No merged PRs explicitly fix this issue
    • Changelog does not mention issue #7813
    • The issue remains valid and unfixed
  5. 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:

  1. Bundle step (packages/wrangler/src/deployment-bundle/bundle.ts:387):

    • esbuild bundles the worker with sourcemap: true
    • Source maps are correctly generated with mappings
  2. Source URL injection (packages/wrangler/src/deployment-bundle/source-url.ts:14-38):

    • withSourceURLs() adds //# sourceURL comments to help V8 identify files
    • This enables DevTools to request source maps via Debugger.scriptParsed
  3. DevTools source map loading (packages/wrangler/src/dev/inspect.ts:212-239):

    • When DevTools requests source maps, they are served with x_google_ignoreList for Wrangler internals
    • This works for debugging but not for profiling
  4. CPU Profile issue (packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts):

    • CPU profile data from Profiler.stop contains positions in bundled code
    • The profile data is passed through without source map transformation
    • DevTools shows bundled code locations in the flame graph

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.stack traces
  • Function.prototype.name access
  • 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.ts
  • packages/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:

  1. Use wrangler check startup for source-mapped CPU profiles of startup
  2. Use --no-bundle flag 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

  1. packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts

    • Add CPU profile interception and transformation
    • Implement source map lookup for profile call frames
  2. packages/wrangler/src/dev/inspect.ts

    • Add utility function for source-mapping CPU profile data
    • Reuse existing source map infrastructure
  3. packages/wrangler/src/api/startDevWorker/bundle-allowed-paths.ts

    • Potentially extend to track source maps for profile transformation
  4. packages/wrangler/src/check/commands.ts

    • Apply same transformation to wrangler check startup profiles

Testing Recommendations

  1. Unit tests:

    • Test source-mapping of CPU profile call frames
    • Test handling of missing source maps
    • Test handling of external/node_modules code
  2. Integration tests:

    • Profile a worker during dev, verify source locations in output
    • Test with various bundling configurations
    • Test with TypeScript and JavaScript sources
  3. 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

Suggested Comment

Thank you for reporting this issue. We've investigated and confirmed this is a valid bug.

Summary: While keepNames: true was 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.

Add Note