Loading core/java/android/os/Binder.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading core/java/com/android/internal/os/BinderCallsStats.java +41 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -279,6 +313,7 @@ public class BinderCallsStats { public void reset() { synchronized (mLock) { mUidEntries.clear(); mExceptionCounts.clear(); mSampledEntries.mCallStats.clear(); mStartTime = System.currentTimeMillis(); } Loading Loading @@ -413,6 +448,11 @@ public class BinderCallsStats { return mSampledEntries; } @VisibleForTesting public ArrayMap<String, Integer> getExceptionCounts() { return mExceptionCounts; } @VisibleForTesting public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble, double percentile) { Loading core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +39 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading
core/java/android/os/Binder.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading
core/java/com/android/internal/os/BinderCallsStats.java +41 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -279,6 +313,7 @@ public class BinderCallsStats { public void reset() { synchronized (mLock) { mUidEntries.clear(); mExceptionCounts.clear(); mSampledEntries.mCallStats.clear(); mStartTime = System.currentTimeMillis(); } Loading Loading @@ -413,6 +448,11 @@ public class BinderCallsStats { return mSampledEntries; } @VisibleForTesting public ArrayMap<String, Integer> getExceptionCounts() { return mExceptionCounts; } @VisibleForTesting public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble, double percentile) { Loading
core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +39 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading