1313public class AnrCulpritIdentifier {
1414
1515 // common Java and Android packages who are less relevant for being the actual culprit
16- private static final List <String > lowQualityPackages = new ArrayList <>(9 );
16+ private static final List <String > systemAndFrameWorkPackages = new ArrayList <>(9 );
1717
1818 static {
19- lowQualityPackages .add ("java.lang" );
20- lowQualityPackages .add ("java.util" );
21- lowQualityPackages .add ("android.app" );
22- lowQualityPackages .add ("android.os.Handler" );
23- lowQualityPackages .add ("android.os.Looper" );
24- lowQualityPackages .add ("android.view" );
25- lowQualityPackages .add ("android.widget" );
26- lowQualityPackages .add ("com.android.internal" );
27- lowQualityPackages .add ("com.google.android" );
19+ systemAndFrameWorkPackages .add ("java.lang" );
20+ systemAndFrameWorkPackages .add ("java.util" );
21+ systemAndFrameWorkPackages .add ("android.app" );
22+ systemAndFrameWorkPackages .add ("android.os.Handler" );
23+ systemAndFrameWorkPackages .add ("android.os.Looper" );
24+ systemAndFrameWorkPackages .add ("android.view" );
25+ systemAndFrameWorkPackages .add ("android.widget" );
26+ systemAndFrameWorkPackages .add ("com.android.internal" );
27+ systemAndFrameWorkPackages .add ("com.google.android" );
28+ }
29+
30+ private static final class StackTraceKey {
31+ private final @ NotNull StackTraceElement [] stack ;
32+ private final int startIdx ;
33+ private final int endIdx ;
34+ private final int hashCode ;
35+
36+ StackTraceKey (final @ NotNull StackTraceElement [] stack , final int startIdx , final int endIdx ) {
37+ this .stack = stack ;
38+ this .startIdx = startIdx ;
39+ this .endIdx = endIdx ;
40+ this .hashCode = computeHashCode ();
41+ }
42+
43+ private int computeHashCode () {
44+ int result = 1 ;
45+ for (int i = startIdx ; i <= endIdx ; i ++) {
46+ result = 31 * result + stack [i ].hashCode ();
47+ }
48+ return result ;
49+ }
50+
51+ @ Override
52+ public int hashCode () {
53+ return hashCode ;
54+ }
55+
56+ @ Override
57+ public boolean equals (final Object obj ) {
58+ if (this == obj ) {
59+ return true ;
60+ }
61+ if (!(obj instanceof StackTraceKey )) {
62+ return false ;
63+ }
64+
65+ final @ NotNull StackTraceKey other = (StackTraceKey ) obj ;
66+
67+ if (hashCode != other .hashCode ) {
68+ return false ;
69+ }
70+
71+ final int length = endIdx - startIdx + 1 ;
72+ final int otherLength = other .endIdx - other .startIdx + 1 ;
73+ if (length != otherLength ) {
74+ return false ;
75+ }
76+
77+ for (int i = 0 ; i < length ; i ++) {
78+ if (!stack [startIdx + i ].equals (other .stack [other .startIdx + i ])) {
79+ return false ;
80+ }
81+ }
82+
83+ return true ;
84+ }
2885 }
2986
3087 /**
31- * @param stacks the captured stacktraces
88+ * @param stacks a list of stack traces to analyze
3289 * @return the most common occurring stacktrace identified as the culprit
3390 */
3491 @ Nullable
@@ -38,27 +95,32 @@ public static AggregatedStackTrace identify(final @NotNull List<AnrStackTrace> s
3895 }
3996
4097 // fold all stacktraces and count their occurrences
41- final @ NotNull Map <Integer , AggregatedStackTrace > stackTraceMap = new HashMap <>();
42- for (final AnrStackTrace stackTrace : stacks ) {
43-
98+ final @ NotNull Map <StackTraceKey , AggregatedStackTrace > stackTraceMap = new HashMap <>();
99+ for (final @ NotNull AnrStackTrace stackTrace : stacks ) {
44100 if (stackTrace .stack .length < 2 ) {
45101 continue ;
46102 }
47103
48- // entry 0 is the most detailed element in the stacktrace
49- // so create sub-stacks (1 ..n, 2..n, ... ) to capture the most common root cause of an ANR
50- for ( int i = 0 ; i < stackTrace . stack . length - 1 ; i ++) {
51- // TODO using hashcode is actually a bad key
52- final int key = subArrayHashCode ( stackTrace . stack , i , stackTrace . stack . length - 1 ) ;
53- int quality = 10 ;
54- final String clazz = stackTrace .stack [ i ]. getClassName ();
55- for ( String ignoredPackage : lowQualityPackages ) {
56- if ( clazz . startsWith ( ignoredPackage )) {
57- quality = 1 ;
58- break ;
59- }
104+ // stack[0] is the most detailed element in the stacktrace
105+ // iterate from end to start (length-1 → 0) creating sub-stacks (i ..n-1 ) to find the most
106+ // common root cause
107+ // count app frames from the end to compute quality scores
108+ int appFramesCount = 0 ;
109+
110+ for ( int i = stackTrace .stack . length - 1 ; i >= 0 ; i --) {
111+
112+ final @ NotNull String topMostClassName = stackTrace . stack [ i ]. getClassName ();
113+ final boolean isSystemFrame = isSystemFrame ( topMostClassName ) ;
114+ if (! isSystemFrame ) {
115+ appFramesCount ++;
60116 }
61117
118+ final int totalFrames = stackTrace .stack .length - i ;
119+ final float quality = (float ) appFramesCount / totalFrames ;
120+
121+ final @ NotNull StackTraceKey key =
122+ new StackTraceKey (stackTrace .stack , i , stackTrace .stack .length - 1 );
123+
62124 @ Nullable AggregatedStackTrace aggregatedStackTrace = stackTraceMap .get (key );
63125 if (aggregatedStackTrace == null ) {
64126 aggregatedStackTrace =
@@ -70,7 +132,7 @@ public static AggregatedStackTrace identify(final @NotNull List<AnrStackTrace> s
70132 quality );
71133 stackTraceMap .put (key , aggregatedStackTrace );
72134 } else {
73- aggregatedStackTrace .add (stackTrace .timestampMs );
135+ aggregatedStackTrace .addOccurrence (stackTrace .timestampMs );
74136 }
75137 }
76138 }
@@ -82,22 +144,16 @@ public static AggregatedStackTrace identify(final @NotNull List<AnrStackTrace> s
82144 // the deepest stacktrace with most count wins
83145 return Collections .max (
84146 stackTraceMap .values (),
85- (c1 , c2 ) -> {
86- final int countComparison = Integer .compare (c1 .count * c1 .quality , c2 .count * c2 .quality );
87- if (countComparison == 0 ) {
88- return Integer .compare (c1 .depth , c2 .depth );
89- }
90- return countComparison ;
91- });
147+ (c1 , c2 ) ->
148+ Float .compare (c1 .count * c1 .quality * c1 .depth , c2 .count * c2 .quality * c2 .depth ));
92149 }
93150
94- private static int subArrayHashCode (
95- final @ NotNull Object [] arr , final int stackStartIdx , final int stackEndIdx ) {
96- int result = 1 ;
97- for (int i = stackStartIdx ; i <= stackEndIdx ; i ++) {
98- final Object item = arr [i ];
99- result = 31 * result + item .hashCode ();
151+ private static boolean isSystemFrame (final @ NotNull String clazz ) {
152+ for (final String systemPackage : systemAndFrameWorkPackages ) {
153+ if (clazz .startsWith (systemPackage )) {
154+ return true ;
155+ }
100156 }
101- return result ;
157+ return false ;
102158 }
103159}
0 commit comments