diff options
| author | 2018-07-16 16:55:58 +0100 | |
|---|---|---|
| committer | 2018-07-23 17:39:22 +0100 | |
| commit | 2c13c6f37b0c5375650e84ec24c79960809f5b6d (patch) | |
| tree | d4aa73d4cf4142c2d01cd401c9a041ee504d02df | |
| parent | 2e8c7670b12ce7075bcc34c1502d268a71f99a0d (diff) | |
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
3 files changed, 187 insertions, 164 deletions
diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java index e126fb807b99..2b8b8f2c611a 100644 --- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java +++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java @@ -23,6 +23,8 @@ import android.support.test.runner.AndroidJUnit4; import com.android.internal.os.BinderCallsStats; +import java.util.Random; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -45,7 +47,7 @@ public class BinderCallsStatsPerfTest { @Before public void setUp() { - mBinderCallsStats = new BinderCallsStats(); + mBinderCallsStats = new BinderCallsStats(new Random()); } @After diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index f87c081fd04f..63c7dd0e0cb3 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -38,6 +38,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.ToDoubleFunction; @@ -57,7 +58,7 @@ public class BinderCallsStats { private static final int MAX_EXCEPTION_COUNT_SIZE = 50; private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow"; 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 mDetailedTracking = DETAILED_TRACKING_DEFAULT; @@ -68,12 +69,12 @@ public class BinderCallsStats { private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>(); private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>(); private final Object mLock = new Object(); + private final Random mRandom; private long mStartTime = System.currentTimeMillis(); - @GuardedBy("mLock") - private UidEntry mSampledEntries = new UidEntry(-1); @VisibleForTesting // Use getInstance() instead. - public BinderCallsStats() { + public BinderCallsStats(Random random) { + this.mRandom = random; } public CallSession callStarted(Binder binder, int code) { @@ -82,7 +83,7 @@ public class BinderCallsStats { private CallSession callStarted(String className, int code, @Nullable String methodName) { if (!mEnabled) { - return NOT_ENABLED; + return NOT_ENABLED; } CallSession s = mCallSessionsPool.poll(); @@ -98,16 +99,12 @@ public class BinderCallsStats { s.timeStarted = -1; synchronized (mLock) { - if (mDetailedTracking) { - s.cpuTimeStarted = getThreadTimeMicro(); - s.timeStarted = getElapsedRealtimeMicro(); - } else { - s.sampledCallStat = mSampledEntries.getOrCreate(s.callStat); - if (s.sampledCallStat.callCount % mPeriodicSamplingInterval == 0) { - s.cpuTimeStarted = getThreadTimeMicro(); - s.timeStarted = getElapsedRealtimeMicro(); - } + if (!mDetailedTracking && !shouldTrackCall()) { + return s; } + + s.cpuTimeStarted = getThreadTimeMicro(); + s.timeStarted = getElapsedRealtimeMicro(); } return s; } @@ -115,7 +112,7 @@ public class BinderCallsStats { public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) { Preconditions.checkNotNull(s); if (s == NOT_ENABLED) { - return; + return; } processCallEnded(s, parcelRequestSize, parcelReplySize); @@ -128,60 +125,42 @@ public class BinderCallsStats { private void processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize) { synchronized (mLock) { if (!mEnabled) { - return; + return; } - long duration; - 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(); - + final int callingUid = getCallingUid(); UidEntry uidEntry = mUidEntries.get(callingUid); if (uidEntry == null) { uidEntry = new UidEntry(callingUid); mUidEntries.put(callingUid, uidEntry); } - - CallStat callStat; - if (mDetailedTracking) { - // Find CallStat entry and update its total time - callStat = uidEntry.getOrCreate(s.callStat); - callStat.exceptionCount += s.exceptionThrown ? 1 : 0; - callStat.maxRequestSizeBytes = - Math.max(callStat.maxRequestSizeBytes, parcelRequestSize); - callStat.maxReplySizeBytes = - Math.max(callStat.maxReplySizeBytes, parcelReplySize); - } else { - // update sampled timings in the beginning of each interval - callStat = s.sampledCallStat; - } + uidEntry.callCount++; + CallStat callStat = uidEntry.getOrCreate(s.callStat); callStat.callCount++; - callStat.methodName = s.callStat.methodName; - if (s.cpuTimeStarted >= 0) { + + // 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); + callStat.maxLatencyMicros = + Math.max(callStat.maxLatencyMicros, latencyDuration); + if (mDetailedTracking) { + callStat.exceptionCount += s.exceptionThrown ? 1 : 0; + callStat.maxRequestSizeBytes = + Math.max(callStat.maxRequestSizeBytes, parcelRequestSize); + callStat.maxReplySizeBytes = + Math.max(callStat.maxReplySizeBytes, parcelReplySize); + } } - - uidEntry.cpuTimeMicros += duration; - uidEntry.callCount++; } } @@ -195,28 +174,28 @@ public class BinderCallsStats { public void callThrewException(CallSession s, Exception exception) { Preconditions.checkNotNull(s); if (!mEnabled) { - return; + return; } s.exceptionThrown = true; try { String className = exception.getClass().getName(); synchronized (mLock) { if (mExceptionCounts.size() >= MAX_EXCEPTION_COUNT_SIZE) { - className = EXCEPTION_COUNT_OVERFLOW_NAME; + 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); + // Do not propagate the exception. We do not want to swallow original exception. + Log.wtf(TAG, "Unexpected exception while updating mExceptionCounts", e); } } public ArrayList<ExportedCallStat> getExportedCallStats() { // We do not collect all the data if detailed tracking is off. if (!mDetailedTracking) { - return new ArrayList<ExportedCallStat>(); + return new ArrayList<ExportedCallStat>(); } ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>(); @@ -229,11 +208,12 @@ public class BinderCallsStats { exported.uid = entry.uid; exported.className = stat.className; exported.methodName = stat.methodName == null - ? String.valueOf(stat.msg) : stat.methodName; + ? String.valueOf(stat.msg) : stat.methodName; exported.cpuTimeMicros = stat.cpuTimeMicros; exported.maxCpuTimeMicros = stat.maxCpuTimeMicros; exported.latencyMicros = stat.latencyMicros; exported.maxLatencyMicros = stat.maxLatencyMicros; + exported.recordedCallCount = stat.recordedCallCount; exported.callCount = stat.callCount; exported.maxRequestSizeBytes = stat.maxRequestSizeBytes; exported.maxReplySizeBytes = stat.maxReplySizeBytes; @@ -254,14 +234,16 @@ public class BinderCallsStats { private void dumpLocked(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) { if (!mEnabled) { - pw.println("Binder calls stats disabled."); - return; + pw.println("Binder calls stats disabled."); + return; } long totalCallsCount = 0; + long totalRecordedCallsCount = 0; long totalCpuTime = 0; pw.print("Start time: "); pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime)); + pw.println("Sampling interval period: " + mPeriodicSamplingInterval); List<UidEntry> entries = new ArrayList<>(); int uidEntriesSize = mUidEntries.size(); @@ -269,70 +251,53 @@ public class BinderCallsStats { UidEntry e = mUidEntries.valueAt(i); entries.add(e); totalCpuTime += e.cpuTimeMicros; + totalRecordedCallsCount += e.recordedCallCount; totalCallsCount += e.callCount; } entries.sort(Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed()); String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) "; StringBuilder sb = new StringBuilder(); - if (mDetailedTracking) { - pw.println("Per-UID raw data " + datasetSizeDesc - + "(package/uid, call_desc, cpu_time_micros, max_cpu_time_micros, " - + "latency_time_micros, max_latency_time_micros, exception_count, " - + "max_request_size_bytes, max_reply_size_bytes, call_count):"); - List<UidEntry> topEntries = verbose ? entries - : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9); - for (UidEntry uidEntry : topEntries) { - for (CallStat e : uidEntry.getCallStatsList()) { - sb.setLength(0); - sb.append(" ") - .append(uidToString(uidEntry.uid, appIdToPkgNameMap)) - .append(",").append(e) - .append(',').append(e.cpuTimeMicros) - .append(',').append(e.maxCpuTimeMicros) - .append(',').append(e.latencyMicros) - .append(',').append(e.maxLatencyMicros) - .append(',').append(e.exceptionCount) - .append(',').append(e.maxRequestSizeBytes) - .append(',').append(e.maxReplySizeBytes) - .append(',').append(e.callCount); - pw.println(sb); - } - } - 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) { + List<UidEntry> topEntries = verbose ? entries + : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9); + pw.println("Per-UID raw data " + datasetSizeDesc + + "(package/uid, call_desc, cpu_time_micros, max_cpu_time_micros, " + + "latency_time_micros, max_latency_time_micros, exception_count, " + + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, " + + "call_count):"); + for (UidEntry uidEntry : topEntries) { + for (CallStat e : uidEntry.getCallStatsList()) { sb.setLength(0); - sb.append(" ").append(e) - .append(',').append(e.cpuTimeMicros * mPeriodicSamplingInterval) - .append(',').append(e.callCount) - .append(',').append(e.exceptionCount); + sb.append(" ") + .append(uidToString(uidEntry.uid, appIdToPkgNameMap)) + .append(',').append(e) + .append(',').append(e.cpuTimeMicros) + .append(',').append(e.maxCpuTimeMicros) + .append(',').append(e.latencyMicros) + .append(',').append(e.maxLatencyMicros) + .append(',').append(mDetailedTracking ? e.exceptionCount : '_') + .append(',').append(mDetailedTracking ? e.maxRequestSizeBytes : '_') + .append(',').append(mDetailedTracking ? e.maxReplySizeBytes : '_') + .append(',').append(e.recordedCallCount) + .append(',').append(e.callCount); pw.println(sb); } - pw.println(); } + pw.println(); 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 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9); for (UidEntry entry : summaryEntries) { String uidStr = uidToString(entry.uid, appIdToPkgNameMap); - pw.println(String.format(" %10d %3.0f%% %8d %3d %s", - entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime, entry.callCount, - entry.exceptionCount, uidStr)); + pw.println(String.format(" %10d %3.0f%% %8d %8d %s", + entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime, + entry.recordedCallCount, entry.callCount, uidStr)); } pw.println(); pw.println(String.format(" Summary: total_cpu_time=%d, " - + "calls_count=%d, avg_call_cpu_time=%.0f", - totalCpuTime, totalCallsCount, (double)totalCpuTime / totalCallsCount)); + + "calls_count=%d, avg_call_cpu_time=%.0f", + totalCpuTime, totalCallsCount, (double)totalCpuTime / totalRecordedCallsCount)); pw.println(); pw.println("Exceptions thrown (exception_count, class_name):"); @@ -340,10 +305,15 @@ public class BinderCallsStats { // 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()))); + (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)); + 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."); } } @@ -366,16 +336,20 @@ public class BinderCallsStats { return SystemClock.elapsedRealtimeNanos() / 1000; } + private boolean shouldTrackCall() { + return mRandom.nextInt() % mPeriodicSamplingInterval == 0; + } + public static BinderCallsStats getInstance() { return sInstance; } public void setDetailedTracking(boolean enabled) { synchronized (mLock) { - if (enabled != mDetailedTracking) { - mDetailedTracking = enabled; - reset(); - } + if (enabled != mDetailedTracking) { + mDetailedTracking = enabled; + reset(); + } } } @@ -401,7 +375,6 @@ public class BinderCallsStats { synchronized (mLock) { mUidEntries.clear(); mExceptionCounts.clear(); - mSampledEntries.mCallStats.clear(); mStartTime = System.currentTimeMillis(); } } @@ -418,6 +391,7 @@ public class BinderCallsStats { public long latencyMicros; public long maxLatencyMicros; public long callCount; + public long recordedCallCount; public long maxRequestSizeBytes; public long maxReplySizeBytes; 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 // 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; + // 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 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 maxLatencyMicros; - public long callCount; // The following fields are only computed if mDetailedTracking is set. public long maxRequestSizeBytes; public long maxReplySizeBytes; @@ -455,7 +439,6 @@ public class BinderCallsStats { } CallStat callStat = (CallStat) o; - return msg == callStat.msg && (className.equals(callStat.className)); } @@ -477,15 +460,20 @@ public class BinderCallsStats { long timeStarted; boolean exceptionThrown; final CallStat callStat = new CallStat(); - CallStat sampledCallStat; } @VisibleForTesting public static class UidEntry { 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 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) { this.uid = uid; @@ -551,11 +539,6 @@ public class BinderCallsStats { } @VisibleForTesting - public UidEntry getSampledEntries() { - return mSampledEntries; - } - - @VisibleForTesting public ArrayMap<String, Integer> getExceptionCounts() { return mExceptionCounts; } diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index d46c1543e0f0..07e2af8d4c66 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; import static org.junit.Assert.assertEquals; @@ -48,6 +49,7 @@ public class BinderCallsStatsTest { public void testDetailedOff() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(false); + bcs.setSamplingInterval(5); Binder binder = new Binder(); BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1); @@ -58,44 +60,31 @@ public class BinderCallsStatsTest { assertEquals(1, uidEntries.size()); BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID); Assert.assertNotNull(uidEntry); + List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList(); assertEquals(1, uidEntry.callCount); + assertEquals(1, uidEntry.recordedCallCount); assertEquals(10, uidEntry.cpuTimeMicros); - assertEquals("Detailed tracking off - no entries should be returned", - 0, uidEntry.getCallStatsList().size()); - - 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); + assertEquals(binder.getClass().getName(), callStatsList.get(0).className); + assertEquals(1, callStatsList.get(0).msg); + // CPU usage is sampled, should not be tracked here. callSession = bcs.callStarted(binder, 1); bcs.time += 20; bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); - - uidEntry = bcs.getUidEntries().get(TEST_UID); assertEquals(2, uidEntry.callCount); - // When sampling is enabled, cpu time is only measured for the first transaction in the - // sampling interval, for others an average duration of previous transactions is used as - // approximation - assertEquals(20, uidEntry.cpuTimeMicros); - sampledCallStatsList = sampledEntries.getCallStatsList(); - assertEquals(1, sampledCallStatsList.size()); + assertEquals(1, uidEntry.recordedCallCount); + assertEquals(10, uidEntry.cpuTimeMicros); + assertEquals(1, callStatsList.size()); callSession = bcs.callStarted(binder, 2); bcs.time += 50; bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); uidEntry = bcs.getUidEntries().get(TEST_UID); assertEquals(3, uidEntry.callCount); - - // This is the first transaction of a new type, so the real CPU time will be measured - assertEquals(70, uidEntry.cpuTimeMicros); - sampledCallStatsList = sampledEntries.getCallStatsList(); - assertEquals(2, sampledCallStatsList.size()); + assertEquals(1, uidEntry.recordedCallCount); + // Still sampled even for another API. + callStatsList = uidEntry.getCallStatsList(); + assertEquals(2, callStatsList.size()); } @Test @@ -116,10 +105,6 @@ public class BinderCallsStatsTest { assertEquals(10, uidEntry.cpuTimeMicros); 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(); assertEquals(1, callStatsList.get(0).callCount); assertEquals(10, callStatsList.get(0).cpuTimeMicros); @@ -216,13 +201,57 @@ public class BinderCallsStatsTest { BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID); Assert.assertNotNull(uidEntry); assertEquals(3, uidEntry.callCount); - assertEquals(70, uidEntry.cpuTimeMicros); - assertEquals("Detailed tracking off - no entries should be returned", - 0, uidEntry.getCallStatsList().size()); + assertEquals(60 /* 10 + 50 */, uidEntry.cpuTimeMicros); + + 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(); - List<BinderCallsStats.CallStat> sampledCallStatsList = sampledEntries.getCallStatsList(); - assertEquals(1, sampledCallStatsList.size()); + SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); + assertEquals(1, uidEntries.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 @@ -374,6 +403,7 @@ public class BinderCallsStatsTest { assertEquals(20, stat.latencyMicros); assertEquals(20, stat.maxLatencyMicros); assertEquals(1, stat.callCount); + assertEquals(1, stat.recordedCallCount); assertEquals(REQUEST_SIZE, stat.maxRequestSizeBytes); assertEquals(REPLY_SIZE, stat.maxReplySizeBytes); assertEquals(0, stat.exceptionCount); @@ -385,6 +415,14 @@ public class BinderCallsStatsTest { long elapsedTime = 0; TestBinderCallsStats() { + // Make random generator not random. + super(new Random() { + int mCallCount = 0; + + public int nextInt() { + return mCallCount++; + } + }); } @Override |