Add support for battery/screen state changes.
Only collect data when the device is charging to be consistent with what
battery stats is doing.
Add a screen interactive dimension to have more context to analysis the
binder calls data.
Test: unit test
Change-Id: Id31c53ae315d905e5d8e67918f64780f34ff5d72
diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
index 05293a2..66a2600d 100644
--- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
@@ -48,7 +48,7 @@
@Before
public void setUp() {
- mBinderCallsStats = new BinderCallsStats(new Random());
+ mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
}
@After
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index baf6a4d..eb369e1 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -17,12 +17,19 @@
package com.android.internal.os;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
import android.os.Binder;
+import android.os.OsProtoEnums;
+import android.os.PowerManager;
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.Slog;
import android.util.SparseArray;
@@ -31,6 +38,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BinderInternal.CallSession;
import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
@@ -74,12 +82,99 @@
private final Random mRandom;
private long mStartTime = System.currentTimeMillis();
- public BinderCallsStats(Random random) {
- this.mRandom = random;
+ // State updated by the broadcast receiver below.
+ private boolean mScreenInteractive;
+ private boolean mCharging;
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_BATTERY_CHANGED:
+ mCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
+ break;
+ case Intent.ACTION_SCREEN_ON:
+ mScreenInteractive = true;
+ break;
+ case Intent.ACTION_SCREEN_OFF:
+ mScreenInteractive = false;
+ break;
+ }
+ }
+ };
+
+ /** Injector for {@link BinderCallsStats}. */
+ public static class Injector {
+ public Random getRandomGenerator() {
+ return new Random();
+ }
+ }
+
+ public BinderCallsStats(Injector injector) {
+ this.mRandom = injector.getRandomGenerator();
+ }
+
+ public void systemReady(Context context) {
+ registerBroadcastReceiver(context);
+ setInitialState(queryScreenInteractive(context), queryIsCharging());
+ }
+
+ /**
+ * Listens for screen/battery state changes.
+ */
+ @VisibleForTesting
+ public void registerBroadcastReceiver(Context context) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ context.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ /**
+ * Sets the battery/screen initial state.
+ *
+ * This has to be updated *after* the broadcast receiver is installed.
+ */
+ @VisibleForTesting
+ public void setInitialState(boolean isScreenInteractive, boolean isCharging) {
+ this.mScreenInteractive = isScreenInteractive;
+ this.mCharging = isCharging;
+ // Data collected previously was not accurate since the battery/screen state was not set.
+ reset();
+ }
+
+ private boolean queryIsCharging() {
+ final BatteryManagerInternal batteryManager =
+ LocalServices.getService(BatteryManagerInternal.class);
+ if (batteryManager == null) {
+ Slog.wtf(TAG, "BatteryManager null while starting BinderCallsStatsService");
+ // Default to true to not collect any data.
+ return true;
+ } else {
+ return batteryManager.getPlugType() != OsProtoEnums.BATTERY_PLUGGED_NONE;
+ }
+ }
+
+ private boolean queryScreenInteractive(Context context) {
+ final PowerManager powerManager = context.getSystemService(PowerManager.class);
+ final boolean screenInteractive;
+ if (powerManager == null) {
+ Slog.wtf(TAG, "PowerManager null while starting BinderCallsStatsService",
+ new Throwable());
+ return true;
+ } else {
+ return powerManager.isInteractive();
+ }
}
@Override
+ @Nullable
public CallSession callStarted(Binder binder, int code) {
+ if (mCharging) {
+ return null;
+ }
+
final CallSession s = obtainCallSession();
s.binderClass = binder.getClass();
s.transactionCode = code;
@@ -126,9 +221,15 @@
final int callingUid = getCallingUid();
synchronized (mLock) {
+ // This was already checked in #callStart but check again while synchronized.
+ if (mCharging) {
+ return;
+ }
+
final UidEntry uidEntry = getUidEntry(callingUid);
uidEntry.callCount++;
- final CallStat callStat = uidEntry.getOrCreate(s.binderClass, s.transactionCode);
+ final CallStat callStat = uidEntry.getOrCreate(
+ s.binderClass, s.transactionCode, mScreenInteractive);
callStat.callCount++;
if (recordCall) {
@@ -178,7 +279,7 @@
}
} 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);
+ Slog.wtf(TAG, "Unexpected exception while updating mExceptionCounts");
}
}
@@ -225,6 +326,7 @@
exported.className = stat.binderClass.getName();
exported.binderClass = stat.binderClass;
exported.transactionCode = stat.transactionCode;
+ exported.screenInteractive = stat.screenInteractive;
exported.cpuTimeMicros = stat.cpuTimeMicros;
exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
exported.latencyMicros = stat.latencyMicros;
@@ -308,7 +410,8 @@
final 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, "
+ + "(package/uid, call_desc, screen_interactive, "
+ + "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):");
@@ -320,6 +423,7 @@
.append(uidToString(e.uid, appIdToPkgNameMap))
.append(',').append(e.className)
.append('#').append(e.methodName)
+ .append(',').append(e.screenInteractive)
.append(',').append(e.cpuTimeMicros)
.append(',').append(e.maxCpuTimeMicros)
.append(',').append(e.latencyMicros)
@@ -441,6 +545,8 @@
public static class CallStat {
public Class<? extends Binder> binderClass;
public int transactionCode;
+ // True if the screen was interactive when the call ended.
+ public boolean screenInteractive;
// 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;
@@ -461,9 +567,11 @@
public long maxReplySizeBytes;
public long exceptionCount;
- CallStat(Class<? extends Binder> binderClass, int transactionCode) {
+ CallStat(Class<? extends Binder> binderClass, int transactionCode,
+ boolean screenInteractive) {
this.binderClass = binderClass;
this.transactionCode = transactionCode;
+ this.screenInteractive = screenInteractive;
}
}
@@ -471,6 +579,7 @@
public static class CallStatKey {
public Class<? extends Binder> binderClass;
public int transactionCode;
+ private boolean screenInteractive;
@Override
public boolean equals(Object o) {
@@ -480,6 +589,7 @@
final CallStatKey key = (CallStatKey) o;
return transactionCode == key.transactionCode
+ && screenInteractive == key.screenInteractive
&& (binderClass.equals(key.binderClass));
}
@@ -487,6 +597,7 @@
public int hashCode() {
int result = binderClass.hashCode();
result = 31 * result + transactionCode;
+ result = 31 * result + (screenInteractive ? 1231 : 1237);
return result;
}
}
@@ -513,17 +624,20 @@
private Map<CallStatKey, CallStat> mCallStats = new ArrayMap<>();
private CallStatKey mTempKey = new CallStatKey();
- CallStat getOrCreate(Class<? extends Binder> binderClass, int transactionCode) {
+ CallStat getOrCreate(Class<? extends Binder> binderClass, int transactionCode,
+ boolean screenInteractive) {
// Use a global temporary key to avoid creating new objects for every lookup.
mTempKey.binderClass = binderClass;
mTempKey.transactionCode = transactionCode;
+ mTempKey.screenInteractive = screenInteractive;
CallStat mapCallStat = mCallStats.get(mTempKey);
// Only create CallStat if it's a new entry, otherwise update existing instance
if (mapCallStat == null) {
- mapCallStat = new CallStat(binderClass, transactionCode);
+ mapCallStat = new CallStat(binderClass, transactionCode, screenInteractive);
CallStatKey key = new CallStatKey();
key.binderClass = binderClass;
key.transactionCode = transactionCode;
+ key.screenInteractive = screenInteractive;
mCallStats.put(key, mapCallStat);
}
return mapCallStat;
@@ -592,6 +706,11 @@
return result;
}
+ @VisibleForTesting
+ public BroadcastReceiver getBroadcastReceiver() {
+ return mBroadcastReceiver;
+ }
+
private static int compareByCpuDesc(
ExportedCallStat a, ExportedCallStat b) {
return Long.compare(b.cpuTimeMicros, a.cpuTimeMicros);
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 5599ee6..2f83190 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -16,7 +16,11 @@
package com.android.internal.os;
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
import android.os.Binder;
+import android.os.OsProtoEnums;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -392,6 +396,113 @@
}
@Test
+ public void testDataResetWhenInitialStateSet() {
+ 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);
+
+ bcs.setInitialState(true, true);
+
+ SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
+ assertEquals(0, uidEntries.size());
+ }
+
+ @Test
+ public void testScreenAndChargerInitialStates() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Binder binder = new Binder();
+ bcs.setInitialState(true /** screen iteractive */, false);
+
+ 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(true, callStatsList.get(0).screenInteractive);
+ }
+
+ @Test
+ public void testNoDataCollectedOnCharger() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC);
+ bcs.getBroadcastReceiver().onReceive(null, intent);
+ Binder binder = new Binder();
+ CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ assertEquals(0, bcs.getUidEntries().size());
+ }
+
+ @Test
+ public void testScreenOff() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_OFF));
+ Binder binder = new Binder();
+ CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
+ assertEquals(1, uidEntries.size());
+ BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
+ Assert.assertNotNull(uidEntry);
+ List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList());
+ assertEquals(false, callStatsList.get(0).screenInteractive);
+ }
+
+ @Test
+ public void testScreenOn() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON));
+ Binder binder = new Binder();
+ CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
+ assertEquals(1, uidEntries.size());
+ BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID);
+ Assert.assertNotNull(uidEntry);
+ List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList());
+ assertEquals(true, callStatsList.get(0).screenInteractive);
+ }
+
+ @Test
+ public void testOnCharger() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC);
+ bcs.getBroadcastReceiver().onReceive(null, intent);
+ Binder binder = new Binder();
+ CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ assertEquals(0, bcs.getExportedCallStats().size());
+ }
+
+ @Test
+ public void testOnBattery() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_NONE);
+ bcs.getBroadcastReceiver().onReceive(null, intent);
+ Binder binder = new Binder();
+ CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ assertEquals(1, bcs.getExportedCallStats().size());
+ }
+
+ @Test
public void testDumpDoesNotThrowException() {
TestBinderCallsStats bcs = new TestBinderCallsStats();
bcs.setDetailedTracking(true);
@@ -419,6 +530,8 @@
public void testGetExportedStatsWhenDetailedTrackingEnabled() {
TestBinderCallsStats bcs = new TestBinderCallsStats();
bcs.setDetailedTracking(true);
+ bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON));
+
Binder binder = new Binder();
CallSession callSession = bcs.callStarted(binder, 1);
bcs.time += 10;
@@ -430,6 +543,7 @@
assertEquals(TEST_UID, stat.uid);
assertEquals("android.os.Binder", stat.className);
assertEquals("1", stat.methodName);
+ assertEquals(true, stat.screenInteractive);
assertEquals(10, stat.cpuTimeMicros);
assertEquals(10, stat.maxCpuTimeMicros);
assertEquals(20, stat.latencyMicros);
@@ -462,11 +576,15 @@
TestBinderCallsStats() {
// Make random generator not random.
- super(new Random() {
- int mCallCount = 0;
+ super(new Injector() {
+ public Random getRandomGenerator() {
+ return new Random() {
+ int mCallCount = 0;
- public int nextInt() {
- return mCallCount++;
+ public int nextInt() {
+ return mCallCount++;
+ }
+ };
}
});
}
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index c63b8f4..9a7c345 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -34,7 +34,6 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
-import com.android.server.LocalServices;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -131,6 +130,7 @@
public static class LifeCycle extends SystemService {
private BinderCallsStatsService mService;
+ private BinderCallsStats mBinderCallsStats;
public LifeCycle(Context context) {
super(context);
@@ -138,9 +138,9 @@
@Override
public void onStart() {
- BinderCallsStats binderCallsStats = new BinderCallsStats(new Random());
- mService = new BinderCallsStatsService(binderCallsStats);
- LocalServices.addService(Internal.class, new Internal(binderCallsStats));
+ mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
+ mService = new BinderCallsStatsService(mBinderCallsStats);
+ publishLocalService(Internal.class, new Internal(mBinderCallsStats));
publishBinderService("binder_calls_stats", mService);
boolean detailedTrackingEnabled = SystemProperties.getBoolean(
PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false);
@@ -149,7 +149,7 @@
Slog.i(TAG, "Enabled CPU usage tracking for binder calls. Controlled by "
+ PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
+ " or via dumpsys binder_calls_stats --enable-detailed-tracking");
- binderCallsStats.setDetailedTracking(true);
+ mBinderCallsStats.setDetailedTracking(true);
}
}
@@ -157,6 +157,7 @@
public void onBootPhase(int phase) {
if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
mService.systemReady(getContext());
+ mBinderCallsStats.systemReady(getContext());
}
}
}