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

Commit f31dfb94 authored by Olivier Gaillard's avatar Olivier Gaillard
Browse files

Optimize resolving transaction codes.

Do the heavy work outside of the main lock and only resolve the same
method once.

Test: unit test
Change-Id: I083b3e66d35178311b5ee71443b39efe1112ad55
parent 1f93a77b
Loading
Loading
Loading
Loading
+61 −19
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;

/**
@@ -185,14 +186,21 @@ public class BinderCallsStats implements BinderInternal.Observer {
    }

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

    @Nullable
    private String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) {
        if (getDefaultTransactionName == null) {
            return null;
        }

        try {
            return (String) getDefaultTransactionName.invoke(null, transactionCode);
        } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
@@ -218,11 +226,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
                    ExportedCallStat exported = new ExportedCallStat();
                    exported.uid = entry.uid;
                    exported.className = stat.binderClass.getName();
                    // 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.binderClass = stat.binderClass;
                    exported.transactionCode = stat.transactionCode;
                    exported.cpuTimeMicros = stat.cpuTimeMicros;
                    exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
                    exported.latencyMicros = stat.latencyMicros;
@@ -236,6 +241,36 @@ public class BinderCallsStats implements BinderInternal.Observer {
                }
            }
        }

        // Resolve codes outside of the lock since it can be slow.
        ExportedCallStat previous = null;
        // Cache the previous method/transaction code.
        Method getDefaultTransactionName = null;
        String previousMethodName = null;
        resultCallStats.sort(BinderCallsStats::compareByBinderClassAndCode);
        for (ExportedCallStat exported : resultCallStats) {
            final boolean isClassDifferent = previous == null
                    || !previous.className.equals(exported.className);
            if (isClassDifferent) {
                getDefaultTransactionName = getDefaultTransactionNameMethod(exported.binderClass);
            }

            final boolean isCodeDifferent = previous == null
                    || previous.transactionCode != exported.transactionCode;
            final String methodName;
            if (isClassDifferent || isCodeDifferent) {
                String resolvedCode = resolveTransactionCode(
                        getDefaultTransactionName, exported.transactionCode);
                methodName = resolvedCode == null
                        ? String.valueOf(exported.transactionCode)
                        : resolvedCode;
            } else {
                methodName = previousMethodName;
            }
            previousMethodName = methodName;
            exported.methodName = methodName;
        }

        return resultCallStats;
    }

@@ -280,7 +315,9 @@ public class BinderCallsStats implements BinderInternal.Observer {
                + "latency_time_micros, max_latency_time_micros, exception_count, "
                + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
                + "call_count):");
        for (ExportedCallStat e : sortByCpuDesc(getExportedCallStats())) {
        List<ExportedCallStat> exportedCallStats = getExportedCallStats();
        exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
        for (ExportedCallStat e : exportedCallStats) {
            sb.setLength(0);
            sb.append("    ")
                    .append(uidToString(e.uid, appIdToPkgNameMap))
@@ -397,6 +434,10 @@ public class BinderCallsStats implements BinderInternal.Observer {
        public long maxRequestSizeBytes;
        public long maxReplySizeBytes;
        public long exceptionCount;

        // Used internally.
        Class<? extends Binder> binderClass;
        int transactionCode;
    }

    @VisibleForTesting
@@ -554,15 +595,16 @@ public class BinderCallsStats implements BinderInternal.Observer {
        return result;
    }

    private List<ExportedCallStat> sortByCpuDesc(List<ExportedCallStat> callStats) {
        callStats.sort((o1, o2) -> {
            if (o1.cpuTimeMicros < o2.cpuTimeMicros) {
                return 1;
            } else if (o1.cpuTimeMicros > o2.cpuTimeMicros) {
                return -1;
    private static int compareByCpuDesc(
            ExportedCallStat a, ExportedCallStat b) {
        return Long.compare(b.cpuTimeMicros, a.cpuTimeMicros);
    }
            return 0;
        });
        return callStats;

    private static int compareByBinderClassAndCode(
            ExportedCallStat a, ExportedCallStat b) {
        int result = a.className.compareTo(b.className);
        return result != 0
                ? result
                : Integer.compare(a.transactionCode, b.transactionCode);
    }
}
+38 −0
Original line number Diff line number Diff line
@@ -240,6 +240,12 @@ public class BinderCallsStatsTest {
        }
    }

    private static class AnotherBinderWithGetTransactionName extends Binder {
        public static String getDefaultTransactionName(int code) {
            return "foo" + code;
        }
    }

    @Test
    public void testTransactionCodeResolved() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
@@ -254,6 +260,38 @@ public class BinderCallsStatsTest {
        assertEquals("resolved", callStatsList.get(0).methodName);
    }

    @Test
    public void testMultipleTransactionCodeResolved() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);

        Binder binder = new AnotherBinderWithGetTransactionName();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.time += 10;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);

        Binder binder2 = new BinderWithGetTransactionName();
        callSession = bcs.callStarted(binder2, 1);
        bcs.time += 10;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);

        callSession = bcs.callStarted(binder, 2);
        bcs.time += 10;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);

        List<BinderCallsStats.ExportedCallStat> callStatsList =
                bcs.getExportedCallStats();
        assertEquals("foo1", callStatsList.get(0).methodName);
        assertEquals(AnotherBinderWithGetTransactionName.class.getName(),
                callStatsList.get(0).className);
        assertEquals("foo2", callStatsList.get(1).methodName);
        assertEquals(AnotherBinderWithGetTransactionName.class.getName(),
                callStatsList.get(1).className);
        assertEquals("resolved", callStatsList.get(2).methodName);
        assertEquals(BinderWithGetTransactionName.class.getName(),
                callStatsList.get(2).className);
    }

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