From 3057e789cd55e08229466978e9dd5e0f1613a845 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 24 May 2023 11:24:20 +0200 Subject: [PATCH 1/5] feat(anr): Improve ANR root cause analysis --- .../events/interfaces/analyzeFrames.spec.tsx | 56 ++++++++- .../events/interfaces/analyzeFrames.tsx | 115 ++++++++++++------ .../interfaces/frame/deprecatedLine.spec.tsx | 60 ++++++++- .../interfaces/frame/deprecatedLine.tsx | 18 +++ .../events/interfaces/utils.spec.jsx | 29 +++++ .../components/events/interfaces/utils.tsx | 9 +- 6 files changed, 249 insertions(+), 38 deletions(-) diff --git a/static/app/components/events/interfaces/analyzeFrames.spec.tsx b/static/app/components/events/interfaces/analyzeFrames.spec.tsx index 548128584571f7..a7f5cee3c6d713 100644 --- a/static/app/components/events/interfaces/analyzeFrames.spec.tsx +++ b/static/app/components/events/interfaces/analyzeFrames.spec.tsx @@ -140,7 +140,7 @@ describe('analyzeAnrFrames', function () { ]); const rootCause = analyzeFramesForRootCause(event); expect(rootCause?.resources).toEqual( - 'Switch to SharedPreferences.commit and move commit to a background thread.' + 'SharedPreferences.apply will save data on background thread only if it happens before the activity/service finishes. Switch to SharedPreferences.commit and move commit to a background thread.' ); expect(rootCause?.culprit).toEqual( '/^android\\.app\\.SharedPreferencesImpl\\$EditorImpl\\$[0-9]/' @@ -170,7 +170,59 @@ describe('analyzeAnrFrames', function () { }, ]); const rootCause = analyzeFramesForRootCause(event); - expect(rootCause?.resources).toEqual('Move database operations off the main thread.'); + expect(rootCause?.resources).toEqual( + 'Database operations, such as querying, inserting, updating, or deleting data, can involve disk I/O, processing, and potentially long-running operations. Move database operations off the main thread to avoid this ANR.' + ); expect(rootCause?.culprit).toEqual('android.database.sqlite.SQLiteConnection'); }); + + it('picks anr root cause of the topmost frame', function () { + const event = makeEventWithFrames([ + { + filename: 'Instrumentation.java', + absPath: 'Instrumentation.java', + module: 'android.app.Instrumentation', + package: null, + platform: null, + instructionAddr: null, + symbolAddr: null, + function: 'callApplicationOnCreate', + rawFunction: null, + symbol: null, + context: [], + lineNo: 1176, + colNo: null, + inApp: false, + trust: null, + errors: null, + vars: null, + }, + { + filename: 'SharedPreferencesImpl.java', + absPath: 'SharedPreferencesImpl.java', + module: 'android.app.SharedPreferencesImpl$EditorImpl$1', + package: null, + platform: null, + instructionAddr: null, + symbolAddr: null, + function: 'run', + rawFunction: null, + symbol: null, + context: [], + lineNo: 366, + colNo: null, + inApp: false, + trust: null, + errors: null, + vars: null, + }, + ]); + const rootCause = analyzeFramesForRootCause(event); + expect(rootCause?.resources).toEqual( + 'SharedPreferences.apply will save data on background thread only if it happens before the activity/service finishes. Switch to SharedPreferences.commit and move commit to a background thread.' + ); + expect(rootCause?.culprit).toEqual( + '/^android\\.app\\.SharedPreferencesImpl\\$EditorImpl\\$[0-9]/' + ); + }); }); diff --git a/static/app/components/events/interfaces/analyzeFrames.tsx b/static/app/components/events/interfaces/analyzeFrames.tsx index ef34987fe4649c..075793a41a4733 100644 --- a/static/app/components/events/interfaces/analyzeFrames.tsx +++ b/static/app/components/events/interfaces/analyzeFrames.tsx @@ -4,9 +4,10 @@ import { getMappedThreadState, ThreadStates, } from 'sentry/components/events/interfaces/threads/threadSelector/threadStates'; +import {getCurrentThread} from 'sentry/components/events/interfaces/utils'; import ExternalLink from 'sentry/components/links/externalLink'; import {t, tct} from 'sentry/locale'; -import {EntryException, EntryThreads, EntryType, Event, Frame} from 'sentry/types'; +import {EntryException, EntryType, Event, Frame, Thread} from 'sentry/types'; type SuspectFrame = { module: string | RegExp; @@ -19,9 +20,24 @@ type SuspectFrame = { const CULPRIT_FRAMES: SuspectFrame[] = [ { module: 'libcore.io.Linux', - functions: ['read', 'write', 'fstat', 'fsync', 'fdatasync', 'access', 'open'], - offendingThreadStates: [ThreadStates.WAITING, ThreadStates.TIMED_WAITING], - resources: t('Move File I/O off the main thread.'), + functions: [ + 'read', + 'write', + 'fstat', + 'fsync', + 'fdatasync', + 'access', + 'open', + 'chmod', + ], + offendingThreadStates: [ + ThreadStates.WAITING, + ThreadStates.TIMED_WAITING, + ThreadStates.RUNNABLE, + ], + resources: t( + 'File I/O operations, such as reading from or writing to files on disk, can be time-consuming, especially if the file size is large or the storage medium is slow. Move File I/O off the main thread to avoid this ANR.' + ), }, { module: 'android.database.sqlite.SQLiteConnection', @@ -31,24 +47,39 @@ const CULPRIT_FRAMES: SuspectFrame[] = [ /nativeExecuteFor[a-zA-Z]+/, /nativeBind[a-zA-Z]+/, /nativeGet[a-zA-Z]+/, + 'nativePrepareStatement', + ], + offendingThreadStates: [ + ThreadStates.WAITING, + ThreadStates.TIMED_WAITING, + ThreadStates.RUNNABLE, ], - offendingThreadStates: [ThreadStates.WAITING, ThreadStates.TIMED_WAITING], - resources: t('Move database operations off the main thread.'), + resources: t( + 'Database operations, such as querying, inserting, updating, or deleting data, can involve disk I/O, processing, and potentially long-running operations. Move database operations off the main thread to avoid this ANR.' + ), }, { module: 'android.app.SharedPreferencesImpl$EditorImpl', functions: ['commit'], - offendingThreadStates: [ThreadStates.WAITING, ThreadStates.TIMED_WAITING], + offendingThreadStates: [ + ThreadStates.WAITING, + ThreadStates.TIMED_WAITING, + ThreadStates.RUNNABLE, + ], resources: t( - 'Switch to SharedPreferences.apply or move commit to a background thread.' + "If you have a particularly large or complex SharedPreferences file or if you're performing multiple simultaneous commits in quick succession, this can lead to ANR. Switch to SharedPreferences.apply or move commit to a background thread to avoid this ANR." ), }, { module: /^android\.app\.SharedPreferencesImpl\$EditorImpl\$[0-9]/, functions: ['run'], - offendingThreadStates: [ThreadStates.WAITING, ThreadStates.TIMED_WAITING], + offendingThreadStates: [ + ThreadStates.WAITING, + ThreadStates.TIMED_WAITING, + ThreadStates.RUNNABLE, + ], resources: t( - 'Switch to SharedPreferences.commit and move commit to a background thread.' + 'SharedPreferences.apply will save data on background thread only if it happens before the activity/service finishes. Switch to SharedPreferences.commit and move commit to a background thread.' ), }, { @@ -60,7 +91,7 @@ const CULPRIT_FRAMES: SuspectFrame[] = [ ThreadStates.RUNNABLE, ], resources: tct( - 'Optimize cold/warm app starts by offloading operations off the main thread and [link:lazily initializing] components.', + 'The app is initializing too many things on the main thread during app launch. To avoid this ANR, optimize cold/warm app starts by offloading operations off the main thread and [link:lazily initializing] components.', { link: ( @@ -81,7 +112,9 @@ const CULPRIT_FRAMES: SuspectFrame[] = [ ThreadStates.TIMED_WAITING, ThreadStates.RUNNABLE, ], - resources: t('If possible, move loading heavy assets off the main thread.'), + resources: t( + 'If the AssetManager operation involves reading or loading a large asset file on the main thread, this can lead to ANR. Move loading heavy assets off the main thread to avoid this ANR.' + ), }, { module: 'android.content.res.AssetManager', @@ -91,7 +124,9 @@ const CULPRIT_FRAMES: SuspectFrame[] = [ ThreadStates.TIMED_WAITING, ThreadStates.RUNNABLE, ], - resources: t('Look for heavy resources in the /res or /res/raw folders.'), + resources: t( + "If you're reading a particularly large raw file (for example, a video file) on the main thread, this can lead to ANR. Look for heavy resources in the '/res' or '/res/raw; folders to avoid this ANR." + ), }, { module: 'android.view.LayoutInflater', @@ -102,7 +137,7 @@ const CULPRIT_FRAMES: SuspectFrame[] = [ ThreadStates.RUNNABLE, ], resources: tct( - 'The app is potentially inflating a heavy, deeply-nested layout. [link:Optimize view hierarchy], use view stubs, use include/merge tags for reusing inflated views.', + 'The app is potentially inflating a heavy, deeply-nested layout. [link:Optimize view hierarchy], use view stubs, use include/merge tags for reusing inflated views to avoid this ANR.', { link: ( @@ -177,30 +212,42 @@ export function analyzeFramesForRootCause(event: Event): { return null; } - const threads = event.entries.find(entry => entry.type === EntryType.THREADS) as - | EntryThreads - | undefined; - const currentThread = threads?.data.values?.find(thread => thread.current); + const currentThread = getCurrentThread(event); - for (let index = 0; index < exceptionFrames.length; index++) { + // iterating the frames in reverse order, because the topmost frames most like the root cause + for (let index = exceptionFrames.length - 1; index > 0; index--) { const frame = exceptionFrames[index]; - for (let culpritIndex = 0; culpritIndex < CULPRIT_FRAMES.length; culpritIndex++) { - const possibleCulprit = CULPRIT_FRAMES[culpritIndex]; - if ( - satisfiesModuleCondition(frame, possibleCulprit) && - satisfiesFunctionCondition(frame, possibleCulprit) && - satisfiesOffendingThreadCondition(currentThread?.state, possibleCulprit) - ) { - return { - culprit: - typeof possibleCulprit.module === 'string' - ? possibleCulprit.module - : possibleCulprit.module.toString(), - resources: possibleCulprit.resources, - }; - } + const rootCause = analyzeFrameForRootCause(frame, currentThread); + if (!isNil(rootCause)) { + return rootCause; } } return null; } + +export function analyzeFrameForRootCause( + frame: Frame, + currentThread: Thread | undefined +): { + culprit: string; + resources: React.ReactNode; +} | null { + for (let culpritIndex = 0; culpritIndex < CULPRIT_FRAMES.length; culpritIndex++) { + const possibleCulprit = CULPRIT_FRAMES[culpritIndex]; + if ( + satisfiesModuleCondition(frame, possibleCulprit) && + satisfiesFunctionCondition(frame, possibleCulprit) && + satisfiesOffendingThreadCondition(currentThread?.state, possibleCulprit) + ) { + return { + culprit: + typeof possibleCulprit.module === 'string' + ? possibleCulprit.module + : possibleCulprit.module.toString(), + resources: possibleCulprit.resources, + }; + } + } + return null; +} diff --git a/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx b/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx index 6dcc4530746bd7..6ece470b1f9826 100644 --- a/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx +++ b/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx @@ -1,7 +1,7 @@ import {render, screen, within} from 'sentry-test/reactTestingLibrary'; import DeprecatedLine from 'sentry/components/events/interfaces/frame/deprecatedLine'; -import {Frame} from 'sentry/types'; +import {EntryType, Frame} from 'sentry/types'; describe('Frame - Line', function () { const event = TestStubs.Event(); @@ -191,4 +191,62 @@ describe('Frame - Line', function () { expect(container).toSnapshot(); }); }); + + describe('ANR suspect frame', () => { + it('should render suspect frame', () => { + const org = {...TestStubs.Organization(), features: ['anr-analyze-frames']}; + const eventWithThreads = { + ...event, + entries: [ + { + data: { + values: [ + { + id: 13920, + current: true, + crashed: true, + name: 'puma 002', + stacktrace: null, + rawStacktrace: null, + state: 'WAITING', + }, + ], + }, + type: EntryType.THREADS, + }, + ], + }; + const suspectFrame: Frame = { + filename: 'Instrumentation.java', + absPath: 'Instrumentation.java', + module: 'android.app.Instrumentation', + package: null, + platform: null, + instructionAddr: null, + symbolAddr: null, + function: 'callApplicationOnCreate', + rawFunction: null, + symbol: null, + context: [], + lineNo: 1176, + colNo: null, + inApp: false, + trust: null, + errors: null, + vars: null, + }; + + render( + , + {organization: org} + ); + expect(screen.getByText('Suspect Frame')).toBeInTheDocument(); + }); + }); }); diff --git a/static/app/components/events/interfaces/frame/deprecatedLine.tsx b/static/app/components/events/interfaces/frame/deprecatedLine.tsx index 6161f4a5854c06..b800a65f05c859 100644 --- a/static/app/components/events/interfaces/frame/deprecatedLine.tsx +++ b/static/app/components/events/interfaces/frame/deprecatedLine.tsx @@ -4,11 +4,13 @@ import classNames from 'classnames'; import scrollToElement from 'scroll-to-element'; import {Button} from 'sentry/components/button'; +import {analyzeFrameForRootCause} from 'sentry/components/events/interfaces/analyzeFrames'; import { StacktraceFilenameQuery, useSourceMapDebug, } from 'sentry/components/events/interfaces/crashContent/exception/useSourceMapDebug'; import LeadHint from 'sentry/components/events/interfaces/frame/line/leadHint'; +import {getCurrentThread} from 'sentry/components/events/interfaces/utils'; import StrictClick from 'sentry/components/strictClick'; import Tag from 'sentry/components/tag'; import {Tooltip} from 'sentry/components/tooltip'; @@ -197,6 +199,11 @@ export class DeprecatedLine extends Component { scrollToElement('#images-loaded'); }; + scrollToSuspectRootCause = event => { + event.stopPropagation(); // to prevent collapsing if collapsible + scrollToElement('#suspect-root-cause'); + }; + preventCollapse = evt => { evt.stopPropagation(); }; @@ -262,6 +269,8 @@ export class DeprecatedLine extends Component { renderDefaultLine() { const {isHoverPreviewed, debugFrames, data} = this.props; + const organization = this.props.organization; + const anrCulprit = analyzeFrameForRootCause(data, getCurrentThread(this.props.event)); return ( @@ -281,6 +290,11 @@ export class DeprecatedLine extends Component { {this.renderRepeats()} + {organization?.features.includes('anr-analyze-frames') && anrCulprit ? ( + + {t('Suspect Frame')} + + ) : null} {!data.inApp ? {t('System')} : {t('In App')}} {this.renderExpander()} @@ -500,3 +514,7 @@ const IconWrapper = styled('div')` align-items: center; margin-right: ${space(1)}; `; + +const SuspectFrameTag = styled(Tag)` + margin-right: ${space(1)}; +`; diff --git a/static/app/components/events/interfaces/utils.spec.jsx b/static/app/components/events/interfaces/utils.spec.jsx index cb380609b96fcd..5ee9599b6f0a79 100644 --- a/static/app/components/events/interfaces/utils.spec.jsx +++ b/static/app/components/events/interfaces/utils.spec.jsx @@ -1,11 +1,13 @@ import { getCurlCommand, + getCurrentThread, objectToSortedTupleArray, removeFilterMaskedEntries, stringifyQueryList, } from 'sentry/components/events/interfaces/utils'; import {MetaProxy, withMeta} from 'sentry/components/events/meta/metaProxy'; import {FILTER_MASK} from 'sentry/constants'; +import {EntryType} from 'sentry/types/event'; describe('components/interfaces/utils', function () { describe('getCurlCommand()', function () { @@ -244,4 +246,31 @@ describe('components/interfaces/utils', function () { ); }); }); + + describe('getCurrentThread()', function () { + const event = { + entries: [ + { + data: { + values: [ + { + id: 13920, + current: true, + crashed: true, + name: 'puma 002', + stacktrace: null, + rawStacktrace: null, + state: 'WAITING', + }, + ], + }, + type: EntryType.THREADS, + }, + ], + }; + it('should return current thread if available', function () { + const thread = getCurrentThread(event); + expect(thread.name).toEqual('puma 002'); + }); + }); }); diff --git a/static/app/components/events/interfaces/utils.tsx b/static/app/components/events/interfaces/utils.tsx index f1d1f51b2e91a6..c3d76e35dcf64c 100644 --- a/static/app/components/events/interfaces/utils.tsx +++ b/static/app/components/events/interfaces/utils.tsx @@ -8,7 +8,7 @@ import {FILTER_MASK} from 'sentry/constants'; import ConfigStore from 'sentry/stores/configStore'; import {Frame, PlatformType} from 'sentry/types'; import {Image} from 'sentry/types/debugImage'; -import {EntryRequest} from 'sentry/types/event'; +import {EntryRequest, EntryThreads, EntryType, Event} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import {fileExtensionToPlatform, getFileExtension} from 'sentry/utils/fileExtension'; @@ -251,3 +251,10 @@ export function isStacktraceNewestFirst() { return true; } } + +export function getCurrentThread(event: Event) { + const threads = event.entries?.find(entry => entry.type === EntryType.THREADS) as + | EntryThreads + | undefined; + return threads?.data.values?.find(thread => thread.current); +} From 1fe04d8e5e446ecbbb844ec797a38494d2b8d400 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Jun 2023 22:27:07 +0200 Subject: [PATCH 2/5] Address PR review --- static/app/components/events/interfaces/analyzeFrames.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/app/components/events/interfaces/analyzeFrames.tsx b/static/app/components/events/interfaces/analyzeFrames.tsx index 075793a41a4733..85962f535a9bf8 100644 --- a/static/app/components/events/interfaces/analyzeFrames.tsx +++ b/static/app/components/events/interfaces/analyzeFrames.tsx @@ -8,6 +8,7 @@ import {getCurrentThread} from 'sentry/components/events/interfaces/utils'; import ExternalLink from 'sentry/components/links/externalLink'; import {t, tct} from 'sentry/locale'; import {EntryException, EntryType, Event, Frame, Thread} from 'sentry/types'; +import { defined } from 'sentry/utils'; type SuspectFrame = { module: string | RegExp; @@ -218,7 +219,7 @@ export function analyzeFramesForRootCause(event: Event): { for (let index = exceptionFrames.length - 1; index > 0; index--) { const frame = exceptionFrames[index]; const rootCause = analyzeFrameForRootCause(frame, currentThread); - if (!isNil(rootCause)) { + if (defined(rootCause)) { return rootCause; } } @@ -233,8 +234,7 @@ export function analyzeFrameForRootCause( culprit: string; resources: React.ReactNode; } | null { - for (let culpritIndex = 0; culpritIndex < CULPRIT_FRAMES.length; culpritIndex++) { - const possibleCulprit = CULPRIT_FRAMES[culpritIndex]; + for (const possibleCulprit of CULPRIT_FRAMES) { if ( satisfiesModuleCondition(frame, possibleCulprit) && satisfiesFunctionCondition(frame, possibleCulprit) && From 22b9fa83320efa747c71d6e955309bd0a9fc090c Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:28:39 +0000 Subject: [PATCH 3/5] style(lint): Auto commit lint changes --- static/app/components/events/interfaces/analyzeFrames.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/components/events/interfaces/analyzeFrames.tsx b/static/app/components/events/interfaces/analyzeFrames.tsx index 85962f535a9bf8..c8064fac5faddc 100644 --- a/static/app/components/events/interfaces/analyzeFrames.tsx +++ b/static/app/components/events/interfaces/analyzeFrames.tsx @@ -8,7 +8,7 @@ import {getCurrentThread} from 'sentry/components/events/interfaces/utils'; import ExternalLink from 'sentry/components/links/externalLink'; import {t, tct} from 'sentry/locale'; import {EntryException, EntryType, Event, Frame, Thread} from 'sentry/types'; -import { defined } from 'sentry/utils'; +import {defined} from 'sentry/utils'; type SuspectFrame = { module: string | RegExp; From 4aa8dad23b40f8f34f8f8de0457b649e50702ece Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Jun 2023 11:37:07 +0200 Subject: [PATCH 4/5] Only detect suspect frames for ANRs --- .../events/interfaces/crashContent/stackTrace/content.tsx | 3 +++ .../events/interfaces/frame/deprecatedLine.spec.tsx | 1 + .../components/events/interfaces/frame/deprecatedLine.tsx | 6 ++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx b/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx index 2b74513458afbd..1467f7e77b46be 100644 --- a/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx +++ b/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx @@ -198,6 +198,8 @@ class Content extends Component { ); const isFrameAfterLastNonApp = this.isFrameAfterLastNonApp(); + const mechanism = platform === 'java' && event.tags?.find(({key}) => key === 'mechanism')?.value; + const isANR = mechanism === 'ANR' || mechanism === 'AppExitInfo'; (data.frames ?? []).forEach((frame, frameIdx) => { const prevFrame = (data.frames ?? [])[frameIdx - 1]; @@ -242,6 +244,7 @@ class Content extends Component { frameMeta={meta?.frames?.[frameIdx]} registersMeta={meta?.registers} debugFrames={debugFrames} + isANR={isANR} /> ); } diff --git a/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx b/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx index 6ece470b1f9826..886a0812c241ec 100644 --- a/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx +++ b/static/app/components/events/interfaces/frame/deprecatedLine.spec.tsx @@ -242,6 +242,7 @@ describe('Frame - Line', function () { registers={{}} components={[]} event={eventWithThreads} + isANR isExpanded />, {organization: org} diff --git a/static/app/components/events/interfaces/frame/deprecatedLine.tsx b/static/app/components/events/interfaces/frame/deprecatedLine.tsx index b800a65f05c859..acc1ac6c5985b4 100644 --- a/static/app/components/events/interfaces/frame/deprecatedLine.tsx +++ b/static/app/components/events/interfaces/frame/deprecatedLine.tsx @@ -53,6 +53,7 @@ type Props = { frameMeta?: Record; image?: React.ComponentProps['image']; includeSystemFrames?: boolean; + isANR?: boolean; isExpanded?: boolean; isFrameAfterLastNonApp?: boolean; /** @@ -268,9 +269,10 @@ export class DeprecatedLine extends Component { } renderDefaultLine() { - const {isHoverPreviewed, debugFrames, data} = this.props; + const {isHoverPreviewed, debugFrames, data, isANR} = this.props; const organization = this.props.organization; - const anrCulprit = analyzeFrameForRootCause(data, getCurrentThread(this.props.event)); + const anrCulprit = + isANR && analyzeFrameForRootCause(data, getCurrentThread(this.props.event)); return ( From f36861a79a31259e1b6147bf2a19d217fb31bb9a Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:38:46 +0000 Subject: [PATCH 5/5] style(lint): Auto commit lint changes --- .../events/interfaces/crashContent/stackTrace/content.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx b/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx index 1467f7e77b46be..adf67dcb3a3605 100644 --- a/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx +++ b/static/app/components/events/interfaces/crashContent/stackTrace/content.tsx @@ -198,7 +198,8 @@ class Content extends Component { ); const isFrameAfterLastNonApp = this.isFrameAfterLastNonApp(); - const mechanism = platform === 'java' && event.tags?.find(({key}) => key === 'mechanism')?.value; + const mechanism = + platform === 'java' && event.tags?.find(({key}) => key === 'mechanism')?.value; const isANR = mechanism === 'ANR' || mechanism === 'AppExitInfo'; (data.frames ?? []).forEach((frame, frameIdx) => {