feat(anr): Profile main thread when ANR and report ANR profiles to Sentry#4899
feat(anr): Profile main thread when ANR and report ANR profiles to Sentry#4899
Conversation
|
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfileManager.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrCulpritIdentifier.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AggregatedStackTrace.java
Outdated
Show resolved
Hide resolved
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| b6cfb57 | 372.92 ms | 507.77 ms | 134.85 ms |
| ce0a49e | 532.00 ms | 609.96 ms | 77.96 ms |
| e59e22a | 374.68 ms | 442.14 ms | 67.46 ms |
| fc5ccaf | 256.80 ms | 322.36 ms | 65.56 ms |
| d15471f | 294.13 ms | 399.49 ms | 105.36 ms |
| d15471f | 303.49 ms | 439.08 ms | 135.59 ms |
| abfcc92 | 337.38 ms | 427.39 ms | 90.00 ms |
| ee35ac3 | 346.83 ms | 435.48 ms | 88.65 ms |
| d15471f | 322.58 ms | 396.08 ms | 73.50 ms |
| f634d01 | 359.58 ms | 433.88 ms | 74.30 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| b6cfb57 | 1.58 MiB | 2.28 MiB | 718.80 KiB |
| ce0a49e | 1.58 MiB | 2.10 MiB | 532.94 KiB |
| e59e22a | 1.58 MiB | 2.20 MiB | 635.34 KiB |
| fc5ccaf | 1.58 MiB | 2.13 MiB | 557.54 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| abfcc92 | 1.58 MiB | 2.13 MiB | 557.31 KiB |
| ee35ac3 | 1.58 MiB | 2.13 MiB | 558.77 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| f634d01 | 1.58 MiB | 2.10 MiB | 533.40 KiB |
Previous results on branch: markushi/feat/anr-profiling
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| fca8df8 | 326.79 ms | 379.69 ms | 52.90 ms |
| 00299fd | 359.87 ms | 424.85 ms | 64.98 ms |
| 2cee1ab | 318.29 ms | 361.00 ms | 42.71 ms |
| eb02e45 | 362.67 ms | 431.71 ms | 69.04 ms |
| c10e603 | 367.92 ms | 393.50 ms | 25.58 ms |
| 4c0ffee | 314.94 ms | 377.79 ms | 62.86 ms |
| fa76e86 | 274.32 ms | 349.63 ms | 75.31 ms |
| 8c4e7d6 | 309.15 ms | 359.27 ms | 50.12 ms |
| eb7143a | 347.66 ms | 408.54 ms | 60.88 ms |
| ddbbe91 | 289.51 ms | 359.74 ms | 70.23 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| fca8df8 | 1.58 MiB | 2.29 MiB | 723.68 KiB |
| 00299fd | 1.58 MiB | 2.29 MiB | 723.50 KiB |
| 2cee1ab | 1.58 MiB | 2.29 MiB | 723.68 KiB |
| eb02e45 | 1.58 MiB | 2.29 MiB | 725.26 KiB |
| c10e603 | 1.58 MiB | 2.29 MiB | 723.72 KiB |
| 4c0ffee | 1.58 MiB | 2.29 MiB | 723.67 KiB |
| fa76e86 | 1.58 MiB | 2.29 MiB | 724.06 KiB |
| 8c4e7d6 | 1.58 MiB | 2.29 MiB | 727.03 KiB |
| eb7143a | 1.58 MiB | 2.29 MiB | 724.12 KiB |
| ddbbe91 | 1.58 MiB | 2.29 MiB | 724.15 KiB |
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrCulpritIdentifier.java
Show resolved
Hide resolved
…ntry-java into markushi/feat/anr-profiling
sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrStackTrace.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Show resolved
Hide resolved
|
@sentry review |
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfile.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfileManager.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfileManager.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfileManager.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrCulpritIdentifier.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfileRotationHelper.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Outdated
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java
Show resolved
Hide resolved
romtsn
left a comment
There was a problem hiding this comment.
Great work already! I believe there are some things to address but I can check once more after that
sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java
Show resolved
Hide resolved
There was a problem hiding this comment.
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.
| value.serialize(dos); | ||
| dos.flush(); | ||
| } | ||
| sink.flush(); |
There was a problem hiding this comment.
toStream closes caller-owned stream then flushes it
Low Severity
The toStream converter wraps sink in a DataOutputStream try-with-resources, which closes both dos and the underlying sink when the block exits. The sink.flush() call afterward operates on an already-closed stream. This works only because the actual sink from FileObjectQueue is a ByteArrayOutputStream where close() is a no-op, but the pattern violates stream ownership conventions and the post-close flush is dead code.
There was a problem hiding this comment.
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.
| } | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
hasOnlySystemFrames returns true with no frames present
Medium Severity
hasOnlySystemFrames returns true when exceptions have no stacktrace or empty frames, not just when all frames are system frames. When the main thread is absent from the ANR thread dump, setAnrExceptions creates a dummy exception with a null stacktrace. The loop in hasOnlySystemFrames skips exceptions with null stacktraces and never finds any non-system frame, so it returns true. This causes setDefaultAnrFingerprint to apply the "system-frames-only-anr" fingerprint, grouping these no-information ANRs with ANRs that genuinely contain only system frames — two different categories that likely warrant separate investigation.
Additional Locations (1)
There was a problem hiding this comment.
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.
sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java
Outdated
Show resolved
Hide resolved
| final @Nullable SentryId profilerId = captureAnrProfile(anrTimestamp, anrProfile); | ||
| final @NotNull StackTraceElement[] stack = culprit.getStack(); | ||
|
|
||
| if (stack.length > 0) { |
There was a problem hiding this comment.
Bug: The captureAnrProfile method returns the locally generated SentryId instead of the one returned from Sentry.getCurrentScopes().captureProfileChunk(), which could be different.
Severity: MEDIUM
Suggested Fix
The captureAnrProfile method should return the SentryId that is returned by the Sentry.getCurrentScopes().captureProfileChunk(chunk) call. If the capture fails and returns Sentry.EMPTY_ID, the method should return null as it currently does.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location:
sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java#L874-L877
Potential issue: The `captureAnrProfile` method creates a `ProfileChunk` with a new,
locally generated `SentryId`. It then calls
`Sentry.getCurrentScopes().captureProfileChunk(chunk)`, which captures the profile and
returns a `SentryId`. However, the method ignores this returned ID and instead returns
the original, locally generated ID from `chunk.getProfilerId()`. This can lead to a
mismatch between the `profiler_id` attached to the ANR event and the actual ID of the
profile chunk stored by Sentry, making it impossible to link them.
There was a problem hiding this comment.
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.
| return Collections.max( | ||
| stackTraceMap.values(), | ||
| (c1, c2) -> | ||
| Float.compare(c1.count * c1.quality * c1.depth, c2.count * c2.quality * c2.depth)); |
There was a problem hiding this comment.
Scoring formula collapses to zero for system-only stacks
Medium Severity
The scoring formula count * quality * depth evaluates to zero for every candidate when all stack frames are system frames (quality = 0). This causes Collections.max to return an arbitrary sub-stack from the HashMap values — potentially a shallow 1-frame sub-stack rather than the full, most informative stack trace. The comment says "the deepest stacktrace with most count wins," but with quality = 0, count and depth are effectively ignored.
| } | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
hasOnlySystemFrames returns true when frames are absent
Low Severity
hasOnlySystemFrames returns true when exceptions exist but have null stacktraces or empty frame lists, since the loop completes without finding any non-system frame. This causes the "system-frames-only-anr" fingerprint to be applied to events that have no stack information at all — a semantically different case from events whose stacks contain only system frames.
romtsn
left a comment
There was a problem hiding this comment.
some bugbot comments still left, but to me this looks great, hats off!


📜 Description
Adds ANR (Application Not Responding) profiling integration that profiles the main thread when an ANR is detected and reports the captured profiles to Sentry.
Key Changes:
AnrProfilingIntegrationto capture profiles during ANR eventsAnrV2Integrationnow takes care of matching and capturing the profile on the next start💡 Motivation and Context
This feature enables better ANR diagnostics by capturing profiling data at the time of ANR detection, allowing developers to identify performance bottlenecks and problematic code paths causing application hangs.
Example event: https://sentry-sdks.sentry.io/issues/7229210096/events/4598ff6fcc0f402d8ecca615005e7f64/
💚 How did you test it?
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps