Skip to content

feat(profile): add profile command to surface CPU profiling data#187

Open
BYK wants to merge 35 commits intomainfrom
feat/profile-command
Open

feat(profile): add profile command to surface CPU profiling data#187
BYK wants to merge 35 commits intomainfrom
feat/profile-command

Conversation

@BYK
Copy link
Member

@BYK BYK commented Feb 5, 2026

Summary

Adds a new sentry profile command with two subcommands to surface CPU profiling data from Sentry:

  • profile list - Lists transactions that have profiling data, with sample counts, p75/p95 durations, and short aliases
  • profile view <transaction> - Analyzes CPU profile for a transaction, showing hot paths and performance metrics

Features

  • Hot path detection with CPU time percentages
  • P75/P95/P99 percentile statistics per function
  • User code vs library code filtering (--allFrames to include all)
  • JSON output for CI/automation
  • --web flag to open profiles in Sentry UI
  • Configurable time periods (1h/24h/7d/14d/30d)
  • Transaction aliases for quick access via numeric index or short alias
  • Smart formatting: common prefix stripping, middle-truncation for long names
  • SAMPLES column showing real profile sample counts via count_unique(timestamp)

Usage Examples

List Transactions with Profiles

$ sentry profile list my-org/backend --period 7d

Transactions with Profiles in my-org/backend (last 7d):

  #   ALIAS   TRANSACTION                                         SAMPLES         p75         p95
─────────────────────────────────────────────────────────────────────────────────────────────────────
    1   u       projects/{project_id}/users/                            42        3.8s        5.0s
    2   a       webhooks/provision/account/                             18        2.7s        2.7s
    3   c       organizations/{org_id}/code-mappings/                    6        2.1s        2.1s
    4   e       projects/{project_id}/events/                          291        1.5s        8.6s
    5   i       organizations/{org_id}/issues/                         541        1.5s        2.8s

Common prefix stripped: /api/0/
Tip: Use 'sentry profile view 1' or 'sentry profile view <alias>' to analyze.

View Profile Analysis

Access by index, alias, or full transaction name:

# By numeric index (from the list)
$ sentry profile view my-org/backend 1

# By short alias
$ sentry profile view my-org/backend e

# By full transaction name
$ sentry profile view my-org/backend "/api/0/projects/{project_id}/events/"

Output:

/api/0/projects/{project_id}/events/: CPU Profile Analysis (last 7d)
════════════════════════════════════════════════════════════════════════════════

Performance Percentiles
  p75: 1.7s    p95: 12.1s    p99: 12.1s

Hot Paths (Top 10 by CPU time, user code only)
────────────────────────────────────────────────────────────
    #   Function                                  Location                        % Time
    1   EnvMiddleware.<locals>.EnvMiddleware_impl  middleware/env.py:14              7.7%
    2   access_log_middlewa…<locals>.middleware    middlew…ess_log.py:171            7.7%
    3   SubdomainMiddleware.__call__               middlew…ubdomain.py:53            7.7%
    4   AIAgentMiddleware.__call__                 middlew…ai_agent.py:97            7.6%
    5   IntegrationControlMiddleware.__call__      middlew…_control.py:60            7.6%

Stale Alias Detection

If you change the period or project, the CLI warns you:

$ sentry profile view i --period 24h

Error: Transaction alias 'i' is from a different time period (cached: 7d, requested: 24h).

Suggestion: Run 'sentry profile list my-org/backend --period 24h' to refresh aliases.

Other Options

# Include library/system frames
sentry profile view my-org/backend 1 --allFrames

# JSON output for automation
sentry profile view my-org/backend 1 --json

# Open in Sentry web UI
sentry profile view my-org/backend 1 --web

How Aliases Work

  • Aliases are generated from the last meaningful URL segment (e.g., /api/.../issues/i)
  • Stored in SQLite with fingerprint org:project:period for cache validation
  • Uses shortest unique prefix algorithm (same as project aliases for issues)

Formatting Improvements

  • SAMPLES column: Uses count_unique(timestamp) which returns real sample counts (not count() which returned 0)
  • Common prefix stripping: Strips shared path prefix (e.g., /api/0/) across all transactions for readability
  • Middle-truncation: Long names are truncated with in the middle, preserving both start and end
  • Location column: Renamed from "File:Line", widened to 30 chars with middle-truncation
  • No-data error: Suggests running profile list instead of vague "check the transaction name"

Closes #56

Add new `sentry profile` command with two subcommands:

- `profile list` - Lists transactions with profiling data available
- `profile view` - Analyzes CPU profile for a specific transaction,
  showing hot paths, percentiles, and optimization recommendations

Features:
- Flamegraph API integration for detailed call stack analysis
- Hot path detection with CPU time percentages
- P75/P95/P99 percentile statistics per function
- User code vs library code filtering (--all-frames to include all)
- JSON output support for CI/automation
- Web flag to open profiles in Sentry UI
- Configurable time periods (1h/24h/7d/14d/30d)

Closes #56
@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Build

  • Add hole-punch tool to reduce compressed binary size by BYK in #245
  • Add gzip-compressed binary downloads by BYK in #244

Other

  • (args) Parse Sentry web URLs as CLI arguments by BYK in #252
  • (profile) Add profile command to surface CPU profiling data by BYK in #187

Bug Fixes 🐛

Telemetry

  • Reduce noise from version-check JSON parse errors by BYK in #253
  • Skip Sentry reporting for 4xx API errors by BYK in #251
  • Handle EPIPE errors from piped stdout gracefully by BYK in #250
  • Upgrade Sentry SDK to 10.39.0 and remove custom patches by BYK in #249

Other

  • (db) Handle readonly database gracefully instead of crashing by betegon in #235
  • (polyfill) Add exited promise and stdin to Bun.spawn Node.js polyfill by BYK in #248
  • (project-list) Add pagination and flexible target parsing by BYK in #221
  • (upgrade) Remove v prefix from release URLs and work around Bun.write streaming bug by BYK in #243

Internal Changes 🔧

  • (build) Replace local hole-punch script with binpunch package by BYK in #246
  • Use @sentry/api client for requests by MathurAditya724 in #226

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

Codecov Results 📊

✅ Patch coverage is 92.27%. Project has 4052 uncovered lines.
✅ Project coverage is 72.83%. Comparing base (base) to head (head).

Files with missing lines (77)
File Patch % Lines
human.ts 58.29% ⚠️ 395 Missing
list.ts 14.39% ⚠️ 345 Missing
resolve-target.ts 25.17% ⚠️ 327 Missing
api-client.ts 62.48% ⚠️ 302 Missing
list.ts 23.47% ⚠️ 212 Missing
oauth.ts 30.94% ⚠️ 183 Missing
plan.ts 19.37% ⚠️ 154 Missing
resolver.ts 3.23% ⚠️ 120 Missing
help.ts 19.85% ⚠️ 109 Missing
upgrade.ts 61.37% ⚠️ 107 Missing
view.ts 40.23% ⚠️ 104 Missing
interactive-login.ts 9.17% ⚠️ 99 Missing
errors.ts 5.94% ⚠️ 95 Missing
view.ts 25.81% ⚠️ 92 Missing
view.ts 39.44% ⚠️ 86 Missing
clipboard.ts 4.49% ⚠️ 85 Missing
status.ts 24.07% ⚠️ 82 Missing
migration.ts 47.44% ⚠️ 82 Missing
list.ts 27.18% ⚠️ 75 Missing
browser.ts 4.11% ⚠️ 70 Missing
login.ts 33.33% ⚠️ 64 Missing
list.ts 86.12% ⚠️ 64 Missing
span-tree.ts 5.00% ⚠️ 57 Missing
explain.ts 33.33% ⚠️ 56 Missing
api.ts 89.80% ⚠️ 47 Missing
upgrade.ts 66.91% ⚠️ 46 Missing
seer.ts 75.54% ⚠️ 45 Missing
schema.ts 90.74% ⚠️ 41 Missing
refresh.ts 40.63% ⚠️ 38 Missing
seer.ts 79.87% ⚠️ 30 Missing
preload.ts 53.23% ⚠️ 29 Missing
view.ts 87.27% ⚠️ 28 Missing
telemetry.ts 93.01% ⚠️ 27 Missing
utils.ts 88.94% ⚠️ 25 Missing
view.ts 61.54% ⚠️ 25 Missing
fix.ts 89.29% ⚠️ 24 Missing
detector.ts 90.10% ⚠️ 20 Missing
arg-parsing.ts 89.29% ⚠️ 18 Missing
binary.ts 88.67% ⚠️ 17 Missing
list.ts 91.16% ⚠️ 16 Missing
list.ts 90.70% ⚠️ 16 Missing
help.ts 57.14% ⚠️ 15 Missing
view.ts 91.02% ⚠️ 15 Missing
sentry-client.ts 92.17% ⚠️ 13 Missing
dsn-cache.ts 94.62% ⚠️ 12 Missing
code-scanner.ts 96.25% ⚠️ 12 Missing
logout.ts 56.00% ⚠️ 11 Missing
token.ts 52.17% ⚠️ 11 Missing
qrcode.ts 33.33% ⚠️ 10 Missing
fs-utils.ts 57.14% ⚠️ 9 Missing
list.ts 95.29% ⚠️ 8 Missing
view.ts 94.70% ⚠️ 7 Missing
project-root.ts 97.73% ⚠️ 7 Missing
version-check.ts 92.47% ⚠️ 7 Missing
feedback.ts 84.21% ⚠️ 6 Missing
shared.ts 14.29% ⚠️ 6 Missing
auth.ts 95.56% ⚠️ 6 Missing
shell.ts 96.23% ⚠️ 6 Missing
app.ts 94.05% ⚠️ 5 Missing
setup.ts 97.84% ⚠️ 4 Missing
list.ts 97.33% ⚠️ 4 Missing
project-aliases.ts 97.40% ⚠️ 2 Missing
project-root-cache.ts 96.92% ⚠️ 2 Missing
transaction-aliases.ts 98.28% ⚠️ 2 Missing
output.ts 89.47% ⚠️ 2 Missing
profile.ts 98.97% ⚠️ 2 Missing
alias.ts 99.42% ⚠️ 1 Missing
completions.ts 99.37% ⚠️ 1 Missing
index.ts 98.99% ⚠️ 1 Missing
env-file.ts 99.19% ⚠️ 1 Missing
parser.ts 98.63% ⚠️ 1 Missing
colors.ts 98.21% ⚠️ 1 Missing
trace.ts 99.16% ⚠️ 1 Missing
region.ts 97.30% ⚠️ 1 Missing
resolve-transaction.ts 99.08% ⚠️ 1 Missing
helpers.ts 97.62% ⚠️ 1 Missing
helpers.ts 94.74% ⚠️ 1 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    71.21%    72.83%    +1.62%
==========================================
  Files          111       121       +10
  Lines        13764     14915     +1151
  Branches         0         0         —
==========================================
+ Hits          9801     10863     +1062
- Misses        3963      4052       +89
- Partials         0         0         —

Generated by Codecov Action

BYK added 7 commits February 5, 2026 11:29
- Make shared.profiles optional in schema (may be absent when no data)
- Handle null values for sample_durations_ns and sample_counts
- Fix hot path percentage calculation to use total self time
Enable quick access to transactions via numeric indices or short aliases:

- `sentry profile list` now shows # and ALIAS columns
- `sentry profile view 1` - access by numeric index
- `sentry profile view i` - access by alias (last unique segment)

Implementation:
- Add transaction_aliases SQLite table (schema v5)
- Add TransactionAliasEntry type for cached aliases
- Add buildTransactionAliases() using existing alias algorithm
- Add resolveTransaction() with stale cache detection
- Update formatters to show alias columns

Aliases are fingerprinted by org:project:period and error with helpful
messages when stale (e.g., different period) or unknown.
Add comprehensive test coverage for the transaction alias system:

- test/lib/transaction-alias.property.test.ts: Property-based tests
  using fast-check to verify extractTransactionSegment and
  buildTransactionAliases invariants

- test/lib/db/transaction-aliases.test.ts: Unit tests for SQLite
  storage layer including fingerprint building, CRUD operations,
  and stale detection

- test/lib/resolve-transaction.test.ts: Unit tests for transaction
  resolution including index/alias lookup, full name pass-through,
  and stale alias error handling
Resolve conflicts by combining:
- Profile commands (profile list, profile view)
- Log commands (log list, logs shortcut)
- Both profiling and logs API functions in api-client.ts
- Schema v5 with transaction_aliases table (composite primary key)
- SKILL.md documentation for both profile and log commands
…segments

The fallback path was returning numeric segments when all other segments
were placeholders or numeric. Now the fallback also filters out numeric
and placeholder patterns, defaulting to 'txn' if nothing meaningful found.

Fixes flaky property-based test 'does not return purely numeric segments'.
@BYK BYK marked this pull request as ready for review February 6, 2026 16:09
BYK added 2 commits February 6, 2026 17:01
- Fix mismatched default periods: both list and view now use 24h default
- Handle duplicate segments with numeric suffixes (issues, issues2, etc.)
- Add --web/-w flag to profile list command
- Fix schema repair for transaction_aliases to use custom DDL with
  composite primary key
Cherry-pick from refactor/positional-args-flags branch (PR #204).
Migrate profile view command from --org/--project flags to use the
<org>/<project> positional argument syntax for consistency with other commands.

Changes:
- profile view now uses: sentry profile view [<org>/<project>] <transaction>
- Export parsePositionalArgs for unit testing
- Add unit tests for parsePositionalArgs

Co-authored-by: Burak Yigit Kaya <byk@sentry.io>
BYK added 2 commits February 9, 2026 12:29
Resolve conflicts in:
- src/lib/api-client.ts (keep both profile and logs imports)
- src/lib/sentry-urls.ts (keep both profile and logs URL functions)
- Add unit + property-based tests for profile analyzer (nsToMs,
  formatDuration, hasProfileData, analyzeHotPaths, calculatePercentiles,
  analyzeFlamegraph)
- Add unit tests for profile formatters (formatProfileAnalysis,
  formatProfileListHeader, formatProfileListTableHeader,
  formatProfileListRow, formatProfileListFooter)
- Add property tests for buildProfileUrl and buildProfilingSummaryUrl
- Add edge case tests for extractTransactionSegment (empty, placeholder-only,
  numeric-only inputs)
- Fix buildProfilingSummaryUrl to use numeric project ID instead of slug
- Fix getFlamegraph default statsPeriod from '7d' to '24h' to match CLI
BYK added 2 commits February 9, 2026 13:46
- Add 18 mock-based tests for listCommand covering target resolution,
  --web, --json, empty state, human-readable output, alias building
- Add resolveFromProjectSearch tests (4 tests) with spyOn mocking
- Add 13 mock-based tests for viewCommand covering target resolution,
  --web, --json, no profile data, human-readable output
- Export resolveFromProjectSearch for testability
- Use Stricli loader() pattern for command function access in tests
The 'alias is a prefix of the extracted segment' property test could
fail with duplicate transaction inputs because disambiguateSegments
appends numeric suffixes to duplicates, breaking the prefix
relationship with the raw extracted segment. Use uniqueArray to
ensure unique transactions in the property test.
- Fix disambiguateSegments collision: track all result values in a Set
  and increment suffix until unique (Bug 1)
- Fix orgSlug usage: replace resolveFromProjectSearch with shared
  resolveProjectBySlug that uses foundProject.orgSlug (Bug 2)
- Deduplicate resolveFromProjectSearch across event/log/profile view
  commands into resolveProjectBySlug in resolve-target.ts (Bug 3)
- Fix buildProfileUrl: wrap transaction in encoded quotes for proper
  Sentry search syntax with spaces (Bug 4)
- Extract parsePeriod/VALID_PERIODS to shared.ts to avoid duplication
  between profile list and view commands (Bug 5)
- Fix profile list project-search: add resolveListTarget with proper
  findProjectsBySlug handling instead of broken resolveOrgAndProject
  passthrough (Bug 6)
…tDurationMs

- profile list: delegate project-search to shared resolveProjectBySlug()
  instead of duplicating findProjectsBySlug + error handling logic
- Rename formatDuration → formatDurationMs in analyzer.ts to avoid name
  collision with formatDuration in formatters/human.ts (different input units)
- Update all call sites and tests accordingly
@BYK
Copy link
Member Author

BYK commented Feb 9, 2026

Re: formatDuration name collision (Bug 8) — Fixed: renamed formatDurationformatDurationMs in analyzer.ts to disambiguate from the formatDuration in formatters/human.ts (which takes seconds). Updated all call sites in formatters/profile.ts and the test file.

@BYK BYK closed this Feb 9, 2026
getStaleFingerprint and getStaleIndexFingerprint now accept the current
fingerprint and use 'AND fingerprint != ?' to exclude it from results.
Previously the queries could return the current context's own fingerprint,
leading to incorrect stale-alias error messages.
# Conflicts:
#	plugins/sentry-cli/skills/sentry-cli/SKILL.md
#	src/app.ts
#	src/commands/event/view.ts
#	src/commands/log/view.ts
#	src/lib/api-client.ts
#	src/lib/db/schema.ts
#	src/lib/resolve-target.ts
#	test/commands/event/view.test.ts
#	test/commands/log/view.test.ts
BYK added 2 commits February 17, 2026 11:54
- Remove misleading PROFILES column (always 0 from profile_functions dataset)
- Add p95 column alongside p75 for better performance context
- Sort by p75 descending instead of count (which was always 0)
- Smart transaction name truncation: strip common prefix, middle-truncate
- Rename File:Line column to Location, widen to 30 chars with middle-truncation
- Improve no-data error message to suggest running profile list
- Add tests for truncateMiddle and findCommonPrefix utilities
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

BYK and others added 2 commits February 17, 2026 12:45
Use count_unique(timestamp) instead of count() to show real sample
counts in the profile list table. Add documentation page with
anonymized example outputs for both list and view commands.
@github-actions
Copy link
Contributor

github-actions bot commented Feb 17, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/pr-preview/pr-187/

Built to branch gh-pages at 2026-02-17 14:40 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

- Fix common prefix stripping for null transactions ("unknown" fallback
  was being sliced by the prefix length, producing empty strings)
- Fix misaligned table rows when hasAliases is true but a row has no
  alias (now pads index/alias columns to maintain column alignment)
- Fix formatDurationMs boundary rounding for 1-10ms and µs ranges
  (9.999ms no longer renders as "10.00ms", 0.9995ms no longer as "1000µs")
- Change formatProfileListRow to use options object for clearer API
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Remove custom DDL handling (CUSTOM_DDL_TABLES, TRANSACTION_ALIASES_DDL,
repairTransactionAliasesTable) since TABLE_SCHEMAS already supports
compositePrimaryKey (used by pagination_cursors). Add
compositePrimaryKey: ["fingerprint", "idx"] to the schema definition.

Bump schema version 5→6 and split the migration so databases already at
v5 (from pagination_cursors) correctly pick up the transaction_aliases
table via the new v5→v6 migration block.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Pad # to padStart(3) and ALIAS to padEnd(6) in the header to match
the data row formatting. Fix non-alias divider width from 91 to 87
to match actual content width.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

- Remove trailing \d* from ALIAS_PATTERN since disambiguateSegments
  uses 'x' prefix, not numeric suffix. This prevents misclassifying
  inputs like 'process2' as aliases.
- Separate index creation from table DDL in repairMissingTables so
  a failed index doesn't prevent subsequent repair passes from
  creating it (table exists check was skipping the index).
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

When project is null, the stale/unknown ref error messages suggested
'sentry profile list --org <org>' but profile list has no --org flag.
Now omits the target when project is unknown, letting auto-detection
handle it.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

…ion example

- Explicit org/project targets now use parsed values directly instead
  of routing through resolveOrgAndProject (consistent with view.ts)
- Add disambiguationExample to resolveProjectBySlug call in list.ts
  so users see an actionable command when a project exists in multiple orgs
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

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.

Add profile command to surface profiling data

1 participant