summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2018-07-03 22:41:47 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-07-03 22:41:47 +0000
commite89e466e9168f2816615ab5fb8dc550b0c3f28e2 (patch)
tree144141d3c4873cc19d1a8251ef498a9705f8ac1c
parent191f146b2db5c3c12c9543dce8edf9562dee5f84 (diff)
parentf82d2e7369d6eb3d0c30c0686ccfedb6df624da9 (diff)
Merge "Tracks exception count by class name."
-rw-r--r--core/java/android/os/Binder.java2
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java42
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java39
3 files changed, 81 insertions, 2 deletions
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ac871055dc4a..5923529bdc21 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -730,7 +730,7 @@ public class Binder implements IBinder {
}
res = onTransact(code, data, reply, flags);
} catch (RemoteException|RuntimeException e) {
- binderCallsStats.callThrewException(callSession);
+ binderCallsStats.callThrewException(callSession, e);
if (LOG_RUNTIME_EXCEPTION) {
Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 75d714f3ba4c..96702a07fb8c 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -21,6 +21,8 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -29,10 +31,12 @@ import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
+import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.ToDoubleFunction;
@@ -41,13 +45,18 @@ import java.util.function.ToDoubleFunction;
* per thread, uid or call description.
*/
public class BinderCallsStats {
+ private static final String TAG = "BinderCallsStats";
private static final int CALL_SESSIONS_POOL_SIZE = 100;
private static final int PERIODIC_SAMPLING_INTERVAL = 10;
+ private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
+ private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
private static final BinderCallsStats sInstance = new BinderCallsStats();
private volatile boolean mDetailedTracking = false;
@GuardedBy("mLock")
private final SparseArray<UidEntry> mUidEntries = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>();
private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
private final Object mLock = new Object();
private long mStartTime = System.currentTimeMillis();
@@ -158,9 +167,22 @@ public class BinderCallsStats {
* <li>Do not throw an exception in this method, it will swallow the original exception thrown
* by the binder transaction.
*/
- public void callThrewException(CallSession s) {
+ public void callThrewException(CallSession s, Exception exception) {
Preconditions.checkNotNull(s);
s.exceptionThrown = true;
+ try {
+ String className = exception.getClass().getName();
+ synchronized (mLock) {
+ if (mExceptionCounts.size() >= MAX_EXCEPTION_COUNT_SIZE) {
+ className = EXCEPTION_COUNT_OVERFLOW_NAME;
+ }
+ Integer count = mExceptionCounts.get(className);
+ mExceptionCounts.put(className, count == null ? 1 : count + 1);
+ }
+ } catch (RuntimeException e) {
+ // Do not propagate the exception. We do not want to swallow original exception.
+ Log.wtf(TAG, "Unexpected exception while updating mExceptionCounts", e);
+ }
}
public void dump(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
@@ -244,6 +266,18 @@ public class BinderCallsStats {
pw.println(String.format(" Summary: total_cpu_time=%d, "
+ "calls_count=%d, avg_call_cpu_time=%.0f",
totalCpuTime, totalCallsCount, (double)totalCpuTime / totalCallsCount));
+ pw.println();
+
+ pw.println("Exceptions thrown (exception_count, class_name):");
+ List<Pair<String, Integer>> exceptionEntries = new ArrayList<>();
+ // We cannot use new ArrayList(Collection) constructor because MapCollections does not
+ // implement toArray method.
+ mExceptionCounts.entrySet().iterator().forEachRemaining(
+ (e) -> exceptionEntries.add(Pair.create(e.getKey(), e.getValue())));
+ exceptionEntries.sort((e1, e2) -> Integer.compare(e2.second, e1.second));
+ for (Pair<String, Integer> entry : exceptionEntries) {
+ pw.println(String.format(" %6d %s", entry.second, entry.first));
+ }
}
private static String uidToString(int uid, Map<Integer, String> pkgNameMap) {
@@ -279,6 +313,7 @@ public class BinderCallsStats {
public void reset() {
synchronized (mLock) {
mUidEntries.clear();
+ mExceptionCounts.clear();
mSampledEntries.mCallStats.clear();
mStartTime = System.currentTimeMillis();
}
@@ -414,6 +449,11 @@ public class BinderCallsStats {
}
@VisibleForTesting
+ public ArrayMap<String, Integer> getExceptionCounts() {
+ return mExceptionCounts;
+ }
+
+ @VisibleForTesting
public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble,
double percentile) {
List<T> sortedList = new ArrayList<>(list);
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 18263ab0fa17..2f9f758fa793 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -20,14 +20,19 @@ import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
import android.util.SparseArray;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import static org.junit.Assert.assertEquals;
@@ -198,6 +203,40 @@ public class BinderCallsStatsTest {
assertEquals(Arrays.asList(4, 3, 2), highestValues);
}
+ @Test
+ public void testExceptionCount() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats(true);
+ Binder binder = new Binder();
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callThrewException(callSession, new IllegalStateException());
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ callSession = bcs.callStarted(binder, 1);
+ bcs.callThrewException(callSession, new IllegalStateException());
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ callSession = bcs.callStarted(binder, 1);
+ bcs.callThrewException(callSession, new RuntimeException());
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ ArrayMap<String, Integer> expected = new ArrayMap<>();
+ expected.put("java.lang.IllegalStateException", 2);
+ expected.put("java.lang.RuntimeException", 1);
+ assertEquals(expected, bcs.getExceptionCounts());
+ }
+
+ @Test
+ public void testDumpDoesNotThrowException() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats(true);
+ Binder binder = new Binder();
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callThrewException(callSession, new IllegalStateException());
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ PrintWriter pw = new PrintWriter(new StringWriter());
+ bcs.dump(pw, new HashMap<>(), true);
+ }
+
static class TestBinderCallsStats extends BinderCallsStats {
int callingUid = TEST_UID;
long time = 1234;