Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e89e466e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Tracks exception count by class name."

parents 191f146b f82d2e73
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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);
            }
+41 −1
Original line number Diff line number Diff line
@@ -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();
        }
@@ -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) {
+39 −0
Original line number Diff line number Diff line
@@ -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;