Merge "Add plumbing to collect Binder stats for battery attribution"
This commit is contained in:
@@ -6126,6 +6126,17 @@ public class BatteryStatsImpl extends BatteryStats {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records timing data related to an incoming Binder call in order to attribute
|
||||
* the power consumption to the calling app.
|
||||
*/
|
||||
public void noteBinderCallStats(int workSourceUid,
|
||||
Collection<BinderCallsStats.CallStat> callStats) {
|
||||
synchronized (this) {
|
||||
getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(callStats);
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getWifiIfaces() {
|
||||
synchronized (mWifiNetworkLock) {
|
||||
return mWifiIfaces;
|
||||
@@ -8692,6 +8703,17 @@ public class BatteryStatsImpl extends BatteryStats {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notes incoming binder call stats associated with this work source UID.
|
||||
*/
|
||||
public void noteBinderCallStatsLocked(Collection<BinderCallsStats.CallStat> callStats) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "noteBinderCalls() workSourceUid = [" + mUid + "], callStats = ["
|
||||
+ new ArrayList<>(callStats) + "]");
|
||||
}
|
||||
// TODO(dplotnikov): finish the implementation by actually remembering the stats
|
||||
}
|
||||
|
||||
/**
|
||||
* The statistics associated with a particular wake lock.
|
||||
*/
|
||||
|
||||
@@ -19,10 +19,14 @@ package com.android.internal.os;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
@@ -95,7 +99,33 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
private CachedDeviceState.Readonly mDeviceState;
|
||||
private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
|
||||
|
||||
private static final int CALL_STATS_OBSERVER_DEBOUNCE_MILLIS = 5000;
|
||||
private BinderLatencyObserver mLatencyObserver;
|
||||
private BinderInternal.CallStatsObserver mCallStatsObserver;
|
||||
private ArraySet<Integer> mSendUidsToObserver = new ArraySet<>(32);
|
||||
private final Handler mCallStatsObserverHandler;
|
||||
private Runnable mCallStatsObserverRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mCallStatsObserver == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
noteCallsStatsDelayed();
|
||||
|
||||
synchronized (mLock) {
|
||||
int size = mSendUidsToObserver.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
UidEntry uidEntry = mUidEntries.get(mSendUidsToObserver.valueAt(i));
|
||||
if (uidEntry != null) {
|
||||
mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
|
||||
uidEntry.getCallStatsList());
|
||||
}
|
||||
}
|
||||
mSendUidsToObserver.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Injector for {@link BinderCallsStats}. */
|
||||
public static class Injector {
|
||||
@@ -103,6 +133,10 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
return new Random();
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public BinderLatencyObserver getLatencyObserver() {
|
||||
return new BinderLatencyObserver(new BinderLatencyObserver.Injector());
|
||||
}
|
||||
@@ -110,6 +144,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
|
||||
public BinderCallsStats(Injector injector) {
|
||||
this.mRandom = injector.getRandomGenerator();
|
||||
this.mCallStatsObserverHandler = injector.getHandler();
|
||||
this.mLatencyObserver = injector.getLatencyObserver();
|
||||
}
|
||||
|
||||
@@ -121,6 +156,24 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an observer for call stats, which is invoked periodically with accumulated
|
||||
* binder call stats.
|
||||
*/
|
||||
public void setCallStatsObserver(
|
||||
BinderInternal.CallStatsObserver callStatsObserver) {
|
||||
mCallStatsObserver = callStatsObserver;
|
||||
noteCallsStatsDelayed();
|
||||
}
|
||||
|
||||
private void noteCallsStatsDelayed() {
|
||||
mCallStatsObserverHandler.removeCallbacks(mCallStatsObserverRunnable);
|
||||
if (mCallStatsObserver != null) {
|
||||
mCallStatsObserverHandler.postDelayed(mCallStatsObserverRunnable,
|
||||
CALL_STATS_OBSERVER_DEBOUNCE_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CallSession callStarted(Binder binder, int code, int workSourceUid) {
|
||||
@@ -233,6 +286,9 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
callStat.callCount++;
|
||||
}
|
||||
}
|
||||
if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
|
||||
mSendUidsToObserver.add(workSourceUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +323,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) {
|
||||
private static Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) {
|
||||
try {
|
||||
return binder.getMethod("getDefaultTransactionName", int.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
@@ -277,16 +333,17 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
}
|
||||
|
||||
@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) {
|
||||
throw new RuntimeException(e);
|
||||
private static String resolveTransactionCode(Method getDefaultTransactionName,
|
||||
int transactionCode) {
|
||||
String resolvedCode = null;
|
||||
if (getDefaultTransactionName != null) {
|
||||
try {
|
||||
resolvedCode = (String) getDefaultTransactionName.invoke(null, transactionCode);
|
||||
} catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return resolvedCode == null ? String.valueOf(transactionCode) : resolvedCode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,11 +399,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
|| previous.transactionCode != exported.transactionCode;
|
||||
final String methodName;
|
||||
if (isClassDifferent || isCodeDifferent) {
|
||||
String resolvedCode = resolveTransactionCode(
|
||||
methodName = resolveTransactionCode(
|
||||
getDefaultTransactionName, exported.transactionCode);
|
||||
methodName = resolvedCode == null
|
||||
? String.valueOf(exported.transactionCode)
|
||||
: resolvedCode;
|
||||
} else {
|
||||
methodName = previousMethodName;
|
||||
}
|
||||
@@ -657,6 +711,20 @@ public class BinderCallsStats implements BinderInternal.Observer {
|
||||
this.transactionCode = transactionCode;
|
||||
this.screenInteractive = screenInteractive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CallStat{"
|
||||
+ "callingUid=" + callingUid
|
||||
+ ", transaction=" + binderClass.getSimpleName()
|
||||
+ '.' + resolveTransactionCode(
|
||||
getDefaultTransactionNameMethod(binderClass), transactionCode)
|
||||
+ ", callCount=" + callCount
|
||||
+ ", recordedCallCount=" + recordedCallCount
|
||||
+ ", cpuTimeMicros=" + cpuTimeMicros
|
||||
+ ", latencyMicros=" + latencyMicros
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
/** Key used to store CallStat object in a Map. */
|
||||
|
||||
@@ -31,6 +31,7 @@ import dalvik.system.VMRuntime;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Private and debugging Binder APIs.
|
||||
@@ -133,6 +134,16 @@ public class BinderInternal {
|
||||
public void callThrewException(CallSession s, Exception exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to track observe incoming binder call stats.
|
||||
*/
|
||||
public interface CallStatsObserver {
|
||||
/**
|
||||
* Notes incoming binder call stats associated with this work source UID.
|
||||
*/
|
||||
void noteCallStats(int workSourceUid, Collection<BinderCallsStats.CallStat> callStats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the calling thread to the IPC thread pool. This function does
|
||||
* not return until the current process is exiting.
|
||||
|
||||
@@ -16,10 +16,16 @@
|
||||
|
||||
package com.android.internal.os;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.ArrayMap;
|
||||
@@ -46,11 +52,12 @@ import java.util.Random;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@Presubmit
|
||||
public class BinderCallsStatsTest {
|
||||
private static final int WORKSOURCE_UID = 1;
|
||||
private static final int WORKSOURCE_UID = Process.FIRST_APPLICATION_UID;
|
||||
private static final int CALLING_UID = 2;
|
||||
private static final int REQUEST_SIZE = 2;
|
||||
private static final int REPLY_SIZE = 3;
|
||||
private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true);
|
||||
private final TestHandler mHandler = new TestHandler();
|
||||
|
||||
@Test
|
||||
public void testDetailedOff() {
|
||||
@@ -753,6 +760,51 @@ public class BinderCallsStatsTest {
|
||||
assertEquals(1, callStats.recordedCallCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallStatsObserver() {
|
||||
TestBinderCallsStats bcs = new TestBinderCallsStats();
|
||||
bcs.setSamplingInterval(1);
|
||||
bcs.setTrackScreenInteractive(false);
|
||||
|
||||
final ArrayList<BinderCallsStats.CallStat> callStatsList = new ArrayList<>();
|
||||
bcs.setCallStatsObserver((workSourceUid, callStats) -> callStatsList.addAll(callStats));
|
||||
|
||||
Binder binder = new Binder();
|
||||
|
||||
CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
|
||||
bcs.time += 10;
|
||||
bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
|
||||
|
||||
callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
|
||||
bcs.time += 20;
|
||||
bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
|
||||
|
||||
callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
|
||||
bcs.time += 30;
|
||||
bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
|
||||
|
||||
for (Runnable runnable: mHandler.mRunnables) {
|
||||
// Execute all pending runnables. Ignore the delay.
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
assertThat(callStatsList).hasSize(2);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
BinderCallsStats.CallStat callStats = callStatsList.get(i);
|
||||
if (callStats.transactionCode == 1) {
|
||||
assertEquals(2, callStats.callCount);
|
||||
assertEquals(2, callStats.recordedCallCount);
|
||||
assertEquals(30, callStats.cpuTimeMicros);
|
||||
assertEquals(20, callStats.maxCpuTimeMicros);
|
||||
} else {
|
||||
assertEquals(1, callStats.callCount);
|
||||
assertEquals(1, callStats.recordedCallCount);
|
||||
assertEquals(30, callStats.cpuTimeMicros);
|
||||
assertEquals(30, callStats.maxCpuTimeMicros);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLatencyCollectionEnabled() {
|
||||
TestBinderCallsStats bcs = new TestBinderCallsStats();
|
||||
@@ -781,6 +833,20 @@ public class BinderCallsStatsTest {
|
||||
assertEquals(0, bcs.getLatencyObserver().getLatencyHistograms().size());
|
||||
}
|
||||
|
||||
private static class TestHandler extends Handler {
|
||||
ArrayList<Runnable> mRunnables = new ArrayList<>();
|
||||
|
||||
TestHandler() {
|
||||
super(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
|
||||
mRunnables.add(msg.getCallback());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class TestBinderCallsStats extends BinderCallsStats {
|
||||
public int callingUid = CALLING_UID;
|
||||
public long time = 1234;
|
||||
@@ -803,6 +869,10 @@ public class BinderCallsStatsTest {
|
||||
};
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
public BinderLatencyObserver getLatencyObserver() {
|
||||
return new BinderLatencyObserverTest.TestBinderLatencyObserver();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package android.os;
|
||||
|
||||
import com.android.internal.os.BinderCallsStats;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Battery stats local system service interface. This is used to pass internal data out of
|
||||
* BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl.
|
||||
@@ -41,4 +45,10 @@ public abstract class BatteryStatsInternal {
|
||||
* @param sinceLast how long in millis has it been since a job was run
|
||||
*/
|
||||
public abstract void noteJobsDeferred(int uid, int numDeferred, long sinceLast);
|
||||
|
||||
/**
|
||||
* Informs battery stats of binder stats for the given work source UID.
|
||||
*/
|
||||
public abstract void noteBinderCallStats(int workSourceUid,
|
||||
Collection<BinderCallsStats.CallStat> callStats);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.BatteryStatsInternal;
|
||||
import android.os.Binder;
|
||||
import android.os.Process;
|
||||
import android.os.SystemProperties;
|
||||
@@ -173,10 +174,10 @@ public class BinderCallsStatsService extends Binder {
|
||||
}
|
||||
|
||||
try {
|
||||
mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
|
||||
Settings.Global.BINDER_CALLS_STATS));
|
||||
mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
|
||||
Settings.Global.BINDER_CALLS_STATS));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Slog.e(TAG, "Bad binder call stats settings", e);
|
||||
Slog.e(TAG, "Bad binder call stats settings", e);
|
||||
}
|
||||
mBinderCallsStats.setDetailedTracking(mParser.getBoolean(
|
||||
SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT));
|
||||
@@ -298,6 +299,11 @@ public class BinderCallsStatsService extends Binder {
|
||||
CachedDeviceState.Readonly deviceState = getLocalService(
|
||||
CachedDeviceState.Readonly.class);
|
||||
mBinderCallsStats.setDeviceState(deviceState);
|
||||
|
||||
BatteryStatsInternal batteryStatsInternal = getLocalService(
|
||||
BatteryStatsInternal.class);
|
||||
mBinderCallsStats.setCallStatsObserver(batteryStatsInternal::noteBinderCallStats);
|
||||
|
||||
// It needs to be called before mService.systemReady to make sure the observer is
|
||||
// initialized before installing it.
|
||||
mWorkSourceProvider.systemReady(getContext());
|
||||
|
||||
@@ -66,6 +66,7 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.app.IBatteryStats;
|
||||
import com.android.internal.os.BatteryStatsHelper;
|
||||
import com.android.internal.os.BatteryStatsImpl;
|
||||
import com.android.internal.os.BinderCallsStats;
|
||||
import com.android.internal.os.PowerProfile;
|
||||
import com.android.internal.os.RailStats;
|
||||
import com.android.internal.os.RpmStats;
|
||||
@@ -87,6 +88,7 @@ import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
@@ -304,6 +306,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
||||
if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
|
||||
BatteryStatsService.this.noteJobsDeferred(uid, numDeferred, sinceLast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noteBinderCallStats(int workSourceUid,
|
||||
Collection<BinderCallsStats.CallStat> callStats) {
|
||||
mStats.noteBinderCallStats(workSourceUid, callStats);
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitUninterruptibly(Future<?> future) {
|
||||
@@ -1771,5 +1779,4 @@ public final class BatteryStatsService extends IBatteryStats.Stub
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user