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

Commit 1f93a77b authored by Olivier Gaillard's avatar Olivier Gaillard
Browse files

Resolves transaction codes lazily when a dump is requested.

This change allows to save:
- some memory by removing the method name from CallStat and CallSession
- some CPU by not calling binder.resolveCode for every binder call
at the expense of some hackiness (calling getDefaultTransactionCode
using reflection)

BinderCallsStats#getExportedCallsStats is now more costly to call but it
will be invoked only once a day only.

Test: unit test
Change-Id: Id4f933644c6da9d4aa2d0671db4ab80676eac1b0
parent bd493762
Loading
Loading
Loading
Loading
+25 −11
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import com.android.internal.os.BinderInternal.CallSession;
import com.android.internal.util.Preconditions;

import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -77,11 +79,10 @@ public class BinderCallsStats implements BinderInternal.Observer {

    @Override
    public CallSession callStarted(Binder binder, int code) {
        return callStarted(binder.getClass(), code, binder.getTransactionName(code));
        return callStarted(binder.getClass(), code);
    }

    private CallSession callStarted(Class<? extends Binder> binderClass, int code,
            @Nullable String methodName) {
    private CallSession callStarted(Class<? extends Binder> binderClass, int code) {
        CallSession s = mCallSessionsPool.poll();
        if (s == null) {
            s = new CallSession();
@@ -89,7 +90,6 @@ public class BinderCallsStats implements BinderInternal.Observer {

        s.binderClass = binderClass;
        s.transactionCode = code;
        s.methodName = methodName;
        s.exceptionThrown = false;
        s.cpuTimeStarted = -1;
        s.timeStarted = -1;
@@ -147,7 +147,6 @@ public class BinderCallsStats implements BinderInternal.Observer {
                uidEntry.recordedCallCount++;

                callStat.recordedCallCount++;
                callStat.methodName = s.methodName;
                callStat.cpuTimeMicros += duration;
                callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
                callStat.latencyMicros += latencyDuration;
@@ -185,6 +184,22 @@ public class BinderCallsStats implements BinderInternal.Observer {
        }
    }

    @Nullable
    private String resolveTransactionCode(Class<? extends Binder> binder, int transactionCode) {
        final Method getDefaultTransactionName;
        try {
            getDefaultTransactionName = binder.getMethod("getDefaultTransactionName", int.class);
        } catch (NoSuchMethodException e) {
            // The method might not be present for stubs not generated with AIDL.
            return null;
        }
        try {
            return (String) getDefaultTransactionName.invoke(null, transactionCode);
        } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * This method is expensive to call.
     */
@@ -203,8 +218,11 @@ public class BinderCallsStats implements BinderInternal.Observer {
                    ExportedCallStat exported = new ExportedCallStat();
                    exported.uid = entry.uid;
                    exported.className = stat.binderClass.getName();
                    exported.methodName = stat.methodName == null
                            ? String.valueOf(stat.transactionCode) : stat.methodName;
                    // TODO refactor in order to call resolveTransactionCode outside of the lock.
                    String methodName =
                            resolveTransactionCode(stat.binderClass, stat.transactionCode);
                    exported.methodName = methodName == null
                            ? String.valueOf(stat.transactionCode) : methodName;
                    exported.cpuTimeMicros = stat.cpuTimeMicros;
                    exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
                    exported.latencyMicros = stat.latencyMicros;
@@ -218,7 +236,6 @@ public class BinderCallsStats implements BinderInternal.Observer {
                }
            }
        }

        return resultCallStats;
    }

@@ -386,9 +403,6 @@ public class BinderCallsStats implements BinderInternal.Observer {
    public static class CallStat {
        public Class<? extends Binder> binderClass;
        public int transactionCode;
        // Method name might be null when we cannot resolve the transaction code. For instance, if
        // the binder was not generated by AIDL.
        public @Nullable String methodName;
        // Number of calls for which we collected data for. We do not record data for all the calls
        // when sampling is on.
        public long recordedCallCount;
+0 −2
Original line number Diff line number Diff line
@@ -78,8 +78,6 @@ public class BinderInternal {
        public Class<? extends Binder> binderClass;
        // Binder transaction code.
        public int transactionCode;
        // Binder transaction method name.
        public String methodName;
        // CPU time at the beginning of the call.
        long cpuTimeStarted;
        // System time at the beginning of the call.
+23 −9
Original line number Diff line number Diff line
@@ -234,26 +234,40 @@ public class BinderCallsStatsTest {
        assertEquals(0, callStats.maxCpuTimeMicros);
    }

    private static class BinderWithGetTransactionName extends Binder {
        public static String getDefaultTransactionName(int code) {
            return "resolved";
        }
    }

    @Test
    public void testTransactionCodeResolved() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        Binder binder = new Binder() {
            @Override
            public String getTransactionName(int code) {
                return "resolved";
            }
        };
        Binder binder = new BinderWithGetTransactionName();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.time += 10;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);

        List<BinderCallsStats.CallStat> callStatsList =
                new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList());
        assertEquals(1, callStatsList.get(0).transactionCode);
        List<BinderCallsStats.ExportedCallStat> callStatsList =
                bcs.getExportedCallStats();
        assertEquals("resolved", callStatsList.get(0).methodName);
    }

    @Test
    public void testResolvingCodeDoesNotThrowWhenMethodNotPresent() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.time += 10;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);

        List<BinderCallsStats.ExportedCallStat> callStatsList =
                bcs.getExportedCallStats();
        assertEquals("1", callStatsList.get(0).methodName);
    }

    @Test
    public void testParcelSize() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();