Skip to content

feat: add profile export command#38

Merged
piotrski merged 5 commits into
callstackincubator:mainfrom
kacper-mikolajczak:feat/profile-export
Mar 17, 2026
Merged

feat: add profile export command#38
piotrski merged 5 commits into
callstackincubator:mainfrom
kacper-mikolajczak:feat/profile-export

Conversation

@kacper-mikolajczak
Copy link
Copy Markdown
Contributor

Summary

  • Adds profile export <file> CLI command that exports profiling data in React DevTools Profiler JSON format (version 5)
  • The exported file can be imported directly into React DevTools browser extension via the Profiler tab's import button
  • Maps internal ProfilingSession data to the ProfilingDataExport schema: commits, fiber durations, change descriptions, and component tree snapshots

Usage

agent-react-devtools profile start
# ... interact with app ...
agent-react-devtools profile stop
agent-react-devtools profile export profiling-data.json

Then open React DevTools Profiler tab and click the import button to load the JSON file.

What's included in the export

Field Source Notes
commitData ProfilingSession.commits Full mapping including durations, change descriptions
snapshots ComponentTree nodes Current tree state at time of export
initialTreeBaseDurations First commit's fiberSelfDurations Approximation - exact base durations not tracked
operations Empty arrays Not captured during profiling; flamegraph/ranked still work
effectDuration / passiveEffectDuration null Not available from the devtools protocol

Test plan

  • 8 new unit tests covering: null cases, version format, commit mapping, change descriptions, snapshots, tree base durations, JSON serialization
  • All 76 tests pass
  • Manual: profile a real app, export, import into React DevTools Profiler tab

Export profiling data in React DevTools Profiler JSON format (version 5)
so it can be imported into the browser extension's Profiler tab.

- New IPC command: profile-export
- New CLI command: profile export <file>
- Maps internal ProfilingSession to ProfilingDataExport schema
- Includes commit data, fiber durations, change descriptions, snapshots

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 6, 2026

🦋 Changeset detected

Latest commit: e456530

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-react-devtools Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kacper-mikolajczak
Copy link
Copy Markdown
Contributor Author

CC @piotrski

@kacper-mikolajczak kacper-mikolajczak marked this pull request as ready for review March 6, 2026 14:27
@piotrski
Copy link
Copy Markdown
Collaborator

Nice work, thanks for the contribution! I tested this end-to-end with the Vite example app and found a few issues that I've fixed in fix/profile-export-review-fixes. Feel free to cherry-pick these into your branch or let me know if you'd prefer I merge them differently.

Fixes:

  1. Race condition in profiling data collectionprocessProfilingData() rejects data when stoppedAt is set, but stopProfilingAndCollect() requests data asynchronously and the response arrives after profiler.stop() runs. This results in 0 commits captured. Fix: remove the stoppedAt guard.

  2. Incomplete initialTreeBaseDurations — only included nodes from the first commit's fiberSelfDurations, leaving most tree nodes without a base duration. The React DevTools flame graph needs an entry for every node to render. Fix: include all nodes in the subtree, defaulting to 0 for components that never rendered.

  3. Multi-root snapshot duplication — all nodes were assigned to every root's snapshots. Fix: walk each root's subtree and only include its own nodes. Also restore element type 11 for root nodes (was mapped to 9/other).

  4. SnapshotNodeExport.key type — was number | string | null but ComponentNode.key is string | null.

  5. Pretty-print exported JSONJSON.stringify(data, null, 2) for readability.

piotrski and others added 4 commits March 17, 2026 18:17
processProfilingData() rejected data when stoppedAt was set, but
stopProfilingAndCollect() requests data from renderers asynchronously —
the response often arrives after profiler.stop() has already run,
resulting in 0 commits captured. Remove the stoppedAt guard since the
bridge controls data collection timing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
key was typed as number | string | null but ComponentNode.key is
string | null — the number case was unreachable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The profile export was producing broken flame graphs because we
discarded the raw profiling payload and reconstructed it with
missing/incorrect data (empty operations, null effect durations,
wrong base durations).

- Store raw per-root data from React DevTools for export passthrough
- Capture additional commit fields (effectDuration, priorityLevel, etc.)
- Build snapshots from ComponentTree (protocol sends them empty)
- Extract export logic into profile-export.ts for clarity
- Fix all tree nodes included in initialTreeBaseDurations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@piotrski piotrski merged commit 5c5ace6 into callstackincubator:main Mar 17, 2026
@kacper-mikolajczak
Copy link
Copy Markdown
Contributor Author

Thanks for the fixes! Merged them into the branch.

One thing I wanted to check - my earlier commits had a raw data passthrough that preserved some extra commit-level fields on export:

  • effectDuration / passiveEffectDuration - would show up in DevTools commit details
  • priorityLevel - React's scheduling priority per commit
  • updaters - which components triggered a render

With the current approach these end up as null in the export. DevTools still loads fine, it just won't display that info. Do you think it's worth preserving those, or is the simpler reconstruction approach good enough for now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants