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

Commit 2c13c6f3 authored by Olivier Gaillard's avatar Olivier Gaillard
Browse files

Binder calls stats - random sampling.

Change the sampling method to be random. The previous mechanism recorded
one call for each key (uid/API name) and every X calls for each key:
- This is biased and will make it to interpret the data from
westworld.  It was especially unfair for apps using many different APIs
since the first call to each API was always recorded.
- It uses more memory since we will keep track of all the long tail

Simplify/unify the way we keep track of sampled calls.

Do not estimate the CPU usage of non-recorded calls
    long samplesCount = cs.callCount / mPeriodicSamplingInterval + 1;
    duration = cs.cpuTimeMicros / samplesCount;
It biases the results, let's use an example with 3 calls: 1ms, 3ms, 5ms
with an sampling interval of 2. With the previous algorithm we would get an
average per call of (1+1+3+2+5)/5=2.4ms. With the new one (1+3+5)/3=3ms.

Test: unit tests
Change-Id: I1dd7eb3c6c631b86a53485bccbfa397882cccc92
parent 2e8c7670
Loading
Loading
Loading
Loading
+3 −1
Original line number Original line Diff line number Diff line
@@ -23,6 +23,8 @@ import android.support.test.runner.AndroidJUnit4;


import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderCallsStats;


import java.util.Random;

import org.junit.After;
import org.junit.After;
import org.junit.Before;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Rule;
@@ -45,7 +47,7 @@ public class BinderCallsStatsPerfTest {


    @Before
    @Before
    public void setUp() {
    public void setUp() {
        mBinderCallsStats = new BinderCallsStats();
        mBinderCallsStats = new BinderCallsStats(new Random());
    }
    }


    @After
    @After
+111 −128
Original line number Original line Diff line number Diff line
@@ -38,6 +38,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Queue;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.ToDoubleFunction;
import java.util.function.ToDoubleFunction;
@@ -57,7 +58,7 @@ public class BinderCallsStats {
    private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
    private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
    private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
    private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
    private static final CallSession NOT_ENABLED = new CallSession();
    private static final CallSession NOT_ENABLED = new CallSession();
    private static final BinderCallsStats sInstance = new BinderCallsStats();
    private static final BinderCallsStats sInstance = new BinderCallsStats(new Random());


    private volatile boolean mEnabled = ENABLED_DEFAULT;
    private volatile boolean mEnabled = ENABLED_DEFAULT;
    private volatile boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
    private volatile boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
@@ -68,12 +69,12 @@ public class BinderCallsStats {
    private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>();
    private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>();
    private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
    private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
    private final Object mLock = new Object();
    private final Object mLock = new Object();
    private final Random mRandom;
    private long mStartTime = System.currentTimeMillis();
    private long mStartTime = System.currentTimeMillis();
    @GuardedBy("mLock")
    private UidEntry mSampledEntries = new UidEntry(-1);


    @VisibleForTesting  // Use getInstance() instead.
    @VisibleForTesting  // Use getInstance() instead.
    public BinderCallsStats() {
    public BinderCallsStats(Random random) {
        this.mRandom = random;
    }
    }


    public CallSession callStarted(Binder binder, int code) {
    public CallSession callStarted(Binder binder, int code) {
@@ -98,17 +99,13 @@ public class BinderCallsStats {
        s.timeStarted = -1;
        s.timeStarted = -1;


        synchronized (mLock) {
        synchronized (mLock) {
            if (mDetailedTracking) {
            if (!mDetailedTracking && !shouldTrackCall()) {
                s.cpuTimeStarted = getThreadTimeMicro();
                return s;
                s.timeStarted = getElapsedRealtimeMicro();
            }
            } else {

                s.sampledCallStat = mSampledEntries.getOrCreate(s.callStat);
                if (s.sampledCallStat.callCount % mPeriodicSamplingInterval == 0) {
            s.cpuTimeStarted = getThreadTimeMicro();
            s.cpuTimeStarted = getThreadTimeMicro();
            s.timeStarted = getElapsedRealtimeMicro();
            s.timeStarted = getElapsedRealtimeMicro();
        }
        }
            }
        }
        return s;
        return s;
    }
    }


@@ -131,57 +128,39 @@ public class BinderCallsStats {
                return;
                return;
            }
            }


            long duration;
            final int callingUid = getCallingUid();
            long latencyDuration;
            if (mDetailedTracking) {
                duration = getThreadTimeMicro() - s.cpuTimeStarted;
                latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
            } else {
                CallStat cs = s.sampledCallStat;
                // Non-negative time signals beginning of the new sampling interval
                if (s.cpuTimeStarted >= 0) {
                    duration = getThreadTimeMicro() - s.cpuTimeStarted;
                    latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
                } else {
                    // callCount is always incremented, but time only once per sampling interval
                    long samplesCount = cs.callCount / mPeriodicSamplingInterval + 1;
                    duration = cs.cpuTimeMicros / samplesCount;
                    latencyDuration = cs.latencyMicros / samplesCount;
                }
            }

            int callingUid = getCallingUid();

            UidEntry uidEntry = mUidEntries.get(callingUid);
            UidEntry uidEntry = mUidEntries.get(callingUid);
            if (uidEntry == null) {
            if (uidEntry == null) {
                uidEntry = new UidEntry(callingUid);
                uidEntry = new UidEntry(callingUid);
                mUidEntries.put(callingUid, uidEntry);
                mUidEntries.put(callingUid, uidEntry);
            }
            }
            uidEntry.callCount++;
            CallStat callStat = uidEntry.getOrCreate(s.callStat);
            callStat.callCount++;


            CallStat callStat;
            // Non-negative time signals we need to record data for this call.
            final boolean recordCall = s.cpuTimeStarted >= 0;
            if (recordCall) {
                final long duration = getThreadTimeMicro() - s.cpuTimeStarted;
                final long latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
                uidEntry.cpuTimeMicros += duration;
                uidEntry.recordedCallCount++;

                callStat.recordedCallCount++;
                callStat.methodName = s.callStat.methodName;
                callStat.cpuTimeMicros += duration;
                callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
                callStat.latencyMicros += latencyDuration;
                callStat.maxLatencyMicros =
                        Math.max(callStat.maxLatencyMicros, latencyDuration);
                if (mDetailedTracking) {
                if (mDetailedTracking) {
                // Find CallStat entry and update its total time
                callStat = uidEntry.getOrCreate(s.callStat);
                    callStat.exceptionCount += s.exceptionThrown ? 1 : 0;
                    callStat.exceptionCount += s.exceptionThrown ? 1 : 0;
                    callStat.maxRequestSizeBytes =
                    callStat.maxRequestSizeBytes =
                            Math.max(callStat.maxRequestSizeBytes, parcelRequestSize);
                            Math.max(callStat.maxRequestSizeBytes, parcelRequestSize);
                    callStat.maxReplySizeBytes =
                    callStat.maxReplySizeBytes =
                            Math.max(callStat.maxReplySizeBytes, parcelReplySize);
                            Math.max(callStat.maxReplySizeBytes, parcelReplySize);
            } else {
                // update sampled timings in the beginning of each interval
                callStat = s.sampledCallStat;
                }
                }
            callStat.callCount++;
            callStat.methodName = s.callStat.methodName;
            if (s.cpuTimeStarted >= 0) {
                callStat.cpuTimeMicros += duration;
                callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
                callStat.latencyMicros += latencyDuration;
                callStat.maxLatencyMicros = Math.max(callStat.maxLatencyMicros, latencyDuration);
            }
            }

            uidEntry.cpuTimeMicros += duration;
            uidEntry.callCount++;
        }
        }
    }
    }


@@ -234,6 +213,7 @@ public class BinderCallsStats {
                    exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
                    exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
                    exported.latencyMicros = stat.latencyMicros;
                    exported.latencyMicros = stat.latencyMicros;
                    exported.maxLatencyMicros = stat.maxLatencyMicros;
                    exported.maxLatencyMicros = stat.maxLatencyMicros;
                    exported.recordedCallCount = stat.recordedCallCount;
                    exported.callCount = stat.callCount;
                    exported.callCount = stat.callCount;
                    exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
                    exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
                    exported.maxReplySizeBytes = stat.maxReplySizeBytes;
                    exported.maxReplySizeBytes = stat.maxReplySizeBytes;
@@ -259,9 +239,11 @@ public class BinderCallsStats {
        }
        }


        long totalCallsCount = 0;
        long totalCallsCount = 0;
        long totalRecordedCallsCount = 0;
        long totalCpuTime = 0;
        long totalCpuTime = 0;
        pw.print("Start time: ");
        pw.print("Start time: ");
        pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime));
        pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime));
        pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
        List<UidEntry> entries = new ArrayList<>();
        List<UidEntry> entries = new ArrayList<>();


        int uidEntriesSize = mUidEntries.size();
        int uidEntriesSize = mUidEntries.size();
@@ -269,70 +251,53 @@ public class BinderCallsStats {
            UidEntry e = mUidEntries.valueAt(i);
            UidEntry e = mUidEntries.valueAt(i);
            entries.add(e);
            entries.add(e);
            totalCpuTime += e.cpuTimeMicros;
            totalCpuTime += e.cpuTimeMicros;
            totalRecordedCallsCount += e.recordedCallCount;
            totalCallsCount += e.callCount;
            totalCallsCount += e.callCount;
        }
        }


        entries.sort(Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
        entries.sort(Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
        String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
        String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
        StringBuilder sb = new StringBuilder();
        StringBuilder sb = new StringBuilder();
        if (mDetailedTracking) {
        List<UidEntry> topEntries = verbose ? entries
                : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
        pw.println("Per-UID raw data " + datasetSizeDesc
        pw.println("Per-UID raw data " + datasetSizeDesc
                + "(package/uid, call_desc, cpu_time_micros, max_cpu_time_micros, "
                + "(package/uid, call_desc, cpu_time_micros, max_cpu_time_micros, "
                + "latency_time_micros, max_latency_time_micros, exception_count, "
                + "latency_time_micros, max_latency_time_micros, exception_count, "
                    + "max_request_size_bytes, max_reply_size_bytes, call_count):");
                + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
            List<UidEntry> topEntries = verbose ? entries
                + "call_count):");
                    : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
        for (UidEntry uidEntry : topEntries) {
        for (UidEntry uidEntry : topEntries) {
            for (CallStat e : uidEntry.getCallStatsList()) {
            for (CallStat e : uidEntry.getCallStatsList()) {
                sb.setLength(0);
                sb.setLength(0);
                sb.append("    ")
                sb.append("    ")
                        .append(uidToString(uidEntry.uid, appIdToPkgNameMap))
                        .append(uidToString(uidEntry.uid, appIdToPkgNameMap))
                            .append(",").append(e)
                        .append(',').append(e)
                        .append(',').append(e.cpuTimeMicros)
                        .append(',').append(e.cpuTimeMicros)
                        .append(',').append(e.maxCpuTimeMicros)
                        .append(',').append(e.maxCpuTimeMicros)
                        .append(',').append(e.latencyMicros)
                        .append(',').append(e.latencyMicros)
                        .append(',').append(e.maxLatencyMicros)
                        .append(',').append(e.maxLatencyMicros)
                            .append(',').append(e.exceptionCount)
                        .append(',').append(mDetailedTracking ? e.exceptionCount : '_')
                            .append(',').append(e.maxRequestSizeBytes)
                        .append(',').append(mDetailedTracking ? e.maxRequestSizeBytes : '_')
                            .append(',').append(e.maxReplySizeBytes)
                        .append(',').append(mDetailedTracking ? e.maxReplySizeBytes : '_')
                        .append(',').append(e.recordedCallCount)
                        .append(',').append(e.callCount);
                        .append(',').append(e.callCount);
                pw.println(sb);
                pw.println(sb);
            }
            }
        }
        }
        pw.println();
        pw.println();
        } else {
            pw.println("Sampled stats " + datasetSizeDesc
                    + "(call_desc, cpu_time, call_count, exception_count):");
            List<CallStat> sampledStatsList = mSampledEntries.getCallStatsList();
            // Show all if verbose, otherwise 90th percentile
            if (!verbose) {
                sampledStatsList = getHighestValues(sampledStatsList,
                        value -> value.cpuTimeMicros, 0.9);
            }
            for (CallStat e : sampledStatsList) {
                sb.setLength(0);
                sb.append("    ").append(e)
                        .append(',').append(e.cpuTimeMicros * mPeriodicSamplingInterval)
                        .append(',').append(e.callCount)
                        .append(',').append(e.exceptionCount);
                pw.println(sb);
            }
            pw.println();
        }
        pw.println("Per-UID Summary " + datasetSizeDesc
        pw.println("Per-UID Summary " + datasetSizeDesc
                + "(cpu_time, % of total cpu_time, call_count, exception_count, package/uid):");
                + "(cpu_time, % of total cpu_time, recorded_call_count, call_count, package/uid):");
        List<UidEntry> summaryEntries = verbose ? entries
        List<UidEntry> summaryEntries = verbose ? entries
                : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
                : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
        for (UidEntry entry : summaryEntries) {
        for (UidEntry entry : summaryEntries) {
            String uidStr = uidToString(entry.uid, appIdToPkgNameMap);
            String uidStr = uidToString(entry.uid, appIdToPkgNameMap);
            pw.println(String.format("  %10d %3.0f%% %8d %3d %s",
            pw.println(String.format("  %10d %3.0f%% %8d %8d %s",
                    entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime, entry.callCount,
                        entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime,
                    entry.exceptionCount, uidStr));
                        entry.recordedCallCount, entry.callCount, uidStr));
        }
        }
        pw.println();
        pw.println();
        pw.println(String.format("  Summary: total_cpu_time=%d, "
        pw.println(String.format("  Summary: total_cpu_time=%d, "
                    + "calls_count=%d, avg_call_cpu_time=%.0f",
                    + "calls_count=%d, avg_call_cpu_time=%.0f",
                totalCpuTime, totalCallsCount, (double)totalCpuTime / totalCallsCount));
                    totalCpuTime, totalCallsCount, (double)totalCpuTime / totalRecordedCallsCount));
        pw.println();
        pw.println();


        pw.println("Exceptions thrown (exception_count, class_name):");
        pw.println("Exceptions thrown (exception_count, class_name):");
@@ -345,6 +310,11 @@ public class BinderCallsStats {
        for (Pair<String, Integer> entry : exceptionEntries) {
        for (Pair<String, Integer> entry : exceptionEntries) {
            pw.println(String.format("  %6d %s", entry.second, entry.first));
            pw.println(String.format("  %6d %s", entry.second, entry.first));
        }
        }

        if (!mDetailedTracking && mPeriodicSamplingInterval != 1) {
            pw.println("");
            pw.println("/!\\ Displayed data is sampled. See sampling interval at the top.");
        }
    }
    }


    private static String uidToString(int uid, Map<Integer, String> pkgNameMap) {
    private static String uidToString(int uid, Map<Integer, String> pkgNameMap) {
@@ -366,6 +336,10 @@ public class BinderCallsStats {
        return SystemClock.elapsedRealtimeNanos() / 1000;
        return SystemClock.elapsedRealtimeNanos() / 1000;
    }
    }


    private boolean shouldTrackCall() {
        return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
    }

    public static BinderCallsStats getInstance() {
    public static BinderCallsStats getInstance() {
        return sInstance;
        return sInstance;
    }
    }
@@ -401,7 +375,6 @@ public class BinderCallsStats {
        synchronized (mLock) {
        synchronized (mLock) {
            mUidEntries.clear();
            mUidEntries.clear();
            mExceptionCounts.clear();
            mExceptionCounts.clear();
            mSampledEntries.mCallStats.clear();
            mStartTime = System.currentTimeMillis();
            mStartTime = System.currentTimeMillis();
        }
        }
    }
    }
@@ -418,6 +391,7 @@ public class BinderCallsStats {
        public long latencyMicros;
        public long latencyMicros;
        public long maxLatencyMicros;
        public long maxLatencyMicros;
        public long callCount;
        public long callCount;
        public long recordedCallCount;
        public long maxRequestSizeBytes;
        public long maxRequestSizeBytes;
        public long maxReplySizeBytes;
        public long maxReplySizeBytes;
        public long exceptionCount;
        public long exceptionCount;
@@ -430,11 +404,21 @@ public class BinderCallsStats {
        // Method name might be null when we cannot resolve the transaction code. For instance, if
        // Method name might be null when we cannot resolve the transaction code. For instance, if
        // the binder was not generated by AIDL.
        // the binder was not generated by AIDL.
        public @Nullable String methodName;
        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;
        // Real number of total calls.
        public long callCount;
        // Total CPU of all for all the recorded calls.
        // Approximate total CPU usage can be computed by
        // cpuTimeMicros * callCount / recordedCallCount
        public long cpuTimeMicros;
        public long cpuTimeMicros;
        public long maxCpuTimeMicros;
        public long maxCpuTimeMicros;
        // Total latency of all for all the recorded calls.
        // Approximate average latency can be computed by
        // latencyMicros * callCount / recordedCallCount
        public long latencyMicros;
        public long latencyMicros;
        public long maxLatencyMicros;
        public long maxLatencyMicros;
        public long callCount;
        // The following fields are only computed if mDetailedTracking is set.
        // The following fields are only computed if mDetailedTracking is set.
        public long maxRequestSizeBytes;
        public long maxRequestSizeBytes;
        public long maxReplySizeBytes;
        public long maxReplySizeBytes;
@@ -455,7 +439,6 @@ public class BinderCallsStats {
            }
            }


            CallStat callStat = (CallStat) o;
            CallStat callStat = (CallStat) o;

            return msg == callStat.msg && (className.equals(callStat.className));
            return msg == callStat.msg && (className.equals(callStat.className));
        }
        }


@@ -477,15 +460,20 @@ public class BinderCallsStats {
        long timeStarted;
        long timeStarted;
        boolean exceptionThrown;
        boolean exceptionThrown;
        final CallStat callStat = new CallStat();
        final CallStat callStat = new CallStat();
        CallStat sampledCallStat;
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    public static class UidEntry {
    public static class UidEntry {
        int uid;
        int uid;
        public long cpuTimeMicros;
        // 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;
        // Real number of total calls.
        public long callCount;
        public long callCount;
        public int exceptionCount;
        // Total CPU of all for all the recorded calls.
        // Approximate total CPU usage can be computed by
        // cpuTimeMicros * callCount / recordedCallCount
        public long cpuTimeMicros;


        UidEntry(int uid) {
        UidEntry(int uid) {
            this.uid = uid;
            this.uid = uid;
@@ -550,11 +538,6 @@ public class BinderCallsStats {
        return mUidEntries;
        return mUidEntries;
    }
    }


    @VisibleForTesting
    public UidEntry getSampledEntries() {
        return mSampledEntries;
    }

    @VisibleForTesting
    @VisibleForTesting
    public ArrayMap<String, Integer> getExceptionCounts() {
    public ArrayMap<String, Integer> getExceptionCounts() {
        return mExceptionCounts;
        return mExceptionCounts;
+73 −35
Original line number Original line Diff line number Diff line
@@ -33,6 +33,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashMap;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Random;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertEquals;


@@ -48,6 +49,7 @@ public class BinderCallsStatsTest {
    public void testDetailedOff() {
    public void testDetailedOff() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(false);
        bcs.setDetailedTracking(false);
        bcs.setSamplingInterval(5);


        Binder binder = new Binder();
        Binder binder = new Binder();
        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
@@ -58,44 +60,31 @@ public class BinderCallsStatsTest {
        assertEquals(1, uidEntries.size());
        assertEquals(1, uidEntries.size());
        BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
        BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
        Assert.assertNotNull(uidEntry);
        Assert.assertNotNull(uidEntry);
        List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList();
        assertEquals(1, uidEntry.callCount);
        assertEquals(1, uidEntry.callCount);
        assertEquals(1, uidEntry.recordedCallCount);
        assertEquals(10, uidEntry.cpuTimeMicros);
        assertEquals(10, uidEntry.cpuTimeMicros);
        assertEquals("Detailed tracking off - no entries should be returned",
        assertEquals(binder.getClass().getName(), callStatsList.get(0).className);
                0, uidEntry.getCallStatsList().size());
        assertEquals(1, callStatsList.get(0).msg);

        BinderCallsStats.UidEntry sampledEntries = bcs.getSampledEntries();
        List<BinderCallsStats.CallStat> sampledCallStatsList = sampledEntries.getCallStatsList();
        assertEquals(1, sampledCallStatsList.size());


        assertEquals(1, sampledCallStatsList.get(0).callCount);
        assertEquals(10, sampledCallStatsList.get(0).cpuTimeMicros);
        assertEquals(binder.getClass().getName(), sampledCallStatsList.get(0).className);
        assertEquals(1, sampledCallStatsList.get(0).msg);


        // CPU usage is sampled, should not be tracked here.
        callSession = bcs.callStarted(binder, 1);
        callSession = bcs.callStarted(binder, 1);
        bcs.time += 20;
        bcs.time += 20;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);

        uidEntry = bcs.getUidEntries().get(TEST_UID);
        assertEquals(2, uidEntry.callCount);
        assertEquals(2, uidEntry.callCount);
        // When sampling is enabled, cpu time is only measured for the first transaction in the
        assertEquals(1, uidEntry.recordedCallCount);
        // sampling interval, for others an average duration of previous transactions is used as
        assertEquals(10, uidEntry.cpuTimeMicros);
        // approximation
        assertEquals(1, callStatsList.size());
        assertEquals(20, uidEntry.cpuTimeMicros);
        sampledCallStatsList = sampledEntries.getCallStatsList();
        assertEquals(1, sampledCallStatsList.size());


        callSession = bcs.callStarted(binder, 2);
        callSession = bcs.callStarted(binder, 2);
        bcs.time += 50;
        bcs.time += 50;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
        uidEntry = bcs.getUidEntries().get(TEST_UID);
        uidEntry = bcs.getUidEntries().get(TEST_UID);
        assertEquals(3, uidEntry.callCount);
        assertEquals(3, uidEntry.callCount);

        assertEquals(1, uidEntry.recordedCallCount);
        // This is the first transaction of a new type, so the real CPU time will be measured
        // Still sampled even for another API.
        assertEquals(70, uidEntry.cpuTimeMicros);
        callStatsList = uidEntry.getCallStatsList();
        sampledCallStatsList = sampledEntries.getCallStatsList();
        assertEquals(2, callStatsList.size());
        assertEquals(2, sampledCallStatsList.size());
    }
    }


    @Test
    @Test
@@ -116,10 +105,6 @@ public class BinderCallsStatsTest {
        assertEquals(10, uidEntry.cpuTimeMicros);
        assertEquals(10, uidEntry.cpuTimeMicros);
        assertEquals(1, uidEntry.getCallStatsList().size());
        assertEquals(1, uidEntry.getCallStatsList().size());


        BinderCallsStats.UidEntry sampledEntries = bcs.getSampledEntries();
        assertEquals("Sampling is not used when detailed tracking on",
                0, bcs.getSampledEntries().getCallStatsList().size());

        List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList();
        List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList();
        assertEquals(1, callStatsList.get(0).callCount);
        assertEquals(1, callStatsList.get(0).callCount);
        assertEquals(10, callStatsList.get(0).cpuTimeMicros);
        assertEquals(10, callStatsList.get(0).cpuTimeMicros);
@@ -216,13 +201,57 @@ public class BinderCallsStatsTest {
        BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
        BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
        Assert.assertNotNull(uidEntry);
        Assert.assertNotNull(uidEntry);
        assertEquals(3, uidEntry.callCount);
        assertEquals(3, uidEntry.callCount);
        assertEquals(70, uidEntry.cpuTimeMicros);
        assertEquals(60 /* 10 + 50 */, uidEntry.cpuTimeMicros);
        assertEquals("Detailed tracking off - no entries should be returned",

                0, uidEntry.getCallStatsList().size());
        List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList();
        assertEquals(1, callStatsList.size());
        BinderCallsStats.CallStat callStats = callStatsList.get(0);
        assertEquals(3, callStats.callCount);
        assertEquals(2, callStats.recordedCallCount);
        assertEquals(60, callStats.cpuTimeMicros);
        assertEquals(50, callStats.maxCpuTimeMicros);
        assertEquals(0, callStats.maxRequestSizeBytes);
        assertEquals(0, callStats.maxReplySizeBytes);
    }

    @Test
    public void testSamplingWithDifferentApis() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(false);
        bcs.setSamplingInterval(2);

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

        callSession = bcs.callStarted(binder, 2 /* another method */);
        bcs.time += 1000;  // shoud be ignored.
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);



        BinderCallsStats.UidEntry sampledEntries = bcs.getSampledEntries();
        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
        List<BinderCallsStats.CallStat> sampledCallStatsList = sampledEntries.getCallStatsList();
        assertEquals(1, uidEntries.size());
        assertEquals(1, sampledCallStatsList.size());
        BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
        assertEquals(2, uidEntry.callCount);
        assertEquals(1, uidEntry.recordedCallCount);
        assertEquals(10, uidEntry.cpuTimeMicros);

        List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList();
        assertEquals(2, callStatsList.size());

        BinderCallsStats.CallStat callStats = callStatsList.get(0);
        assertEquals(1, callStats.callCount);
        assertEquals(1, callStats.recordedCallCount);
        assertEquals(10, callStats.cpuTimeMicros);
        assertEquals(10, callStats.maxCpuTimeMicros);

        // Only call count should is tracked, rest is sampled.
        callStats = callStatsList.get(1);
        assertEquals(1, callStats.callCount);
        assertEquals(0, callStats.recordedCallCount);
        assertEquals(0, callStats.cpuTimeMicros);
        assertEquals(0, callStats.maxCpuTimeMicros);
    }
    }


    @Test
    @Test
@@ -374,6 +403,7 @@ public class BinderCallsStatsTest {
        assertEquals(20, stat.latencyMicros);
        assertEquals(20, stat.latencyMicros);
        assertEquals(20, stat.maxLatencyMicros);
        assertEquals(20, stat.maxLatencyMicros);
        assertEquals(1, stat.callCount);
        assertEquals(1, stat.callCount);
        assertEquals(1, stat.recordedCallCount);
        assertEquals(REQUEST_SIZE, stat.maxRequestSizeBytes);
        assertEquals(REQUEST_SIZE, stat.maxRequestSizeBytes);
        assertEquals(REPLY_SIZE, stat.maxReplySizeBytes);
        assertEquals(REPLY_SIZE, stat.maxReplySizeBytes);
        assertEquals(0, stat.exceptionCount);
        assertEquals(0, stat.exceptionCount);
@@ -385,6 +415,14 @@ public class BinderCallsStatsTest {
        long elapsedTime = 0;
        long elapsedTime = 0;


        TestBinderCallsStats() {
        TestBinderCallsStats() {
          // Make random generator not random.
          super(new Random() {
            int mCallCount = 0;

            public int nextInt() {
              return mCallCount++;
            }
          });
        }
        }


        @Override
        @Override