Merge "Add plumbing to collect Binder stats for battery attribution" am: e6a97f86a7

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1709588

Change-Id: Iaa1fbb53a49ed0d0e3847d36adeb50b939191b27
This commit is contained in:
Siim Sammul
2021-05-18 09:40:44 +00:00
committed by Automerger Merge Worker
7 changed files with 213 additions and 19 deletions

View File

@@ -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() { public String[] getWifiIfaces() {
synchronized (mWifiNetworkLock) { synchronized (mWifiNetworkLock) {
return mWifiIfaces; 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. * The statistics associated with a particular wake lock.
*/ */

View File

@@ -19,10 +19,14 @@ package com.android.internal.os;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Process; import android.os.Process;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair; import android.util.Pair;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
@@ -95,7 +99,33 @@ public class BinderCallsStats implements BinderInternal.Observer {
private CachedDeviceState.Readonly mDeviceState; private CachedDeviceState.Readonly mDeviceState;
private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch; private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
private static final int CALL_STATS_OBSERVER_DEBOUNCE_MILLIS = 5000;
private BinderLatencyObserver mLatencyObserver; 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}. */ /** Injector for {@link BinderCallsStats}. */
public static class Injector { public static class Injector {
@@ -103,6 +133,10 @@ public class BinderCallsStats implements BinderInternal.Observer {
return new Random(); return new Random();
} }
public Handler getHandler() {
return new Handler(Looper.getMainLooper());
}
public BinderLatencyObserver getLatencyObserver() { public BinderLatencyObserver getLatencyObserver() {
return new BinderLatencyObserver(new BinderLatencyObserver.Injector()); return new BinderLatencyObserver(new BinderLatencyObserver.Injector());
} }
@@ -110,6 +144,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
public BinderCallsStats(Injector injector) { public BinderCallsStats(Injector injector) {
this.mRandom = injector.getRandomGenerator(); this.mRandom = injector.getRandomGenerator();
this.mCallStatsObserverHandler = injector.getHandler();
this.mLatencyObserver = injector.getLatencyObserver(); this.mLatencyObserver = injector.getLatencyObserver();
} }
@@ -121,6 +156,24 @@ public class BinderCallsStats implements BinderInternal.Observer {
mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch(); 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 @Override
@Nullable @Nullable
public CallSession callStarted(Binder binder, int code, int workSourceUid) { public CallSession callStarted(Binder binder, int code, int workSourceUid) {
@@ -233,6 +286,9 @@ public class BinderCallsStats implements BinderInternal.Observer {
callStat.callCount++; callStat.callCount++;
} }
} }
if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
mSendUidsToObserver.add(workSourceUid);
}
} }
} }
@@ -267,7 +323,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
} }
@Nullable @Nullable
private Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) { private static Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) {
try { try {
return binder.getMethod("getDefaultTransactionName", int.class); return binder.getMethod("getDefaultTransactionName", int.class);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
@@ -277,16 +333,17 @@ public class BinderCallsStats implements BinderInternal.Observer {
} }
@Nullable @Nullable
private String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) { private static String resolveTransactionCode(Method getDefaultTransactionName,
if (getDefaultTransactionName == null) { int transactionCode) {
return null; String resolvedCode = null;
} if (getDefaultTransactionName != null) {
try {
try { resolvedCode = (String) getDefaultTransactionName.invoke(null, transactionCode);
return (String) getDefaultTransactionName.invoke(null, transactionCode); } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
} catch (IllegalAccessException | InvocationTargetException | ClassCastException e) { throw new RuntimeException(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; || previous.transactionCode != exported.transactionCode;
final String methodName; final String methodName;
if (isClassDifferent || isCodeDifferent) { if (isClassDifferent || isCodeDifferent) {
String resolvedCode = resolveTransactionCode( methodName = resolveTransactionCode(
getDefaultTransactionName, exported.transactionCode); getDefaultTransactionName, exported.transactionCode);
methodName = resolvedCode == null
? String.valueOf(exported.transactionCode)
: resolvedCode;
} else { } else {
methodName = previousMethodName; methodName = previousMethodName;
} }
@@ -657,6 +711,20 @@ public class BinderCallsStats implements BinderInternal.Observer {
this.transactionCode = transactionCode; this.transactionCode = transactionCode;
this.screenInteractive = screenInteractive; 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. */ /** Key used to store CallStat object in a Map. */

View File

@@ -31,6 +31,7 @@ import dalvik.system.VMRuntime;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
/** /**
* Private and debugging Binder APIs. * Private and debugging Binder APIs.
@@ -133,6 +134,16 @@ public class BinderInternal {
public void callThrewException(CallSession s, Exception exception); 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 * Add the calling thread to the IPC thread pool. This function does
* not return until the current process is exiting. * not return until the current process is exiting.

View File

@@ -16,10 +16,16 @@
package com.android.internal.os; package com.android.internal.os;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.os.Binder; 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.os.SystemClock;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap; import android.util.ArrayMap;
@@ -46,11 +52,12 @@ import java.util.Random;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@Presubmit @Presubmit
public class BinderCallsStatsTest { 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 CALLING_UID = 2;
private static final int REQUEST_SIZE = 2; private static final int REQUEST_SIZE = 2;
private static final int REPLY_SIZE = 3; private static final int REPLY_SIZE = 3;
private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true); private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true);
private final TestHandler mHandler = new TestHandler();
@Test @Test
public void testDetailedOff() { public void testDetailedOff() {
@@ -753,6 +760,51 @@ public class BinderCallsStatsTest {
assertEquals(1, callStats.recordedCallCount); 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 @Test
public void testLatencyCollectionEnabled() { public void testLatencyCollectionEnabled() {
TestBinderCallsStats bcs = new TestBinderCallsStats(); TestBinderCallsStats bcs = new TestBinderCallsStats();
@@ -781,6 +833,20 @@ public class BinderCallsStatsTest {
assertEquals(0, bcs.getLatencyObserver().getLatencyHistograms().size()); 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 { class TestBinderCallsStats extends BinderCallsStats {
public int callingUid = CALLING_UID; public int callingUid = CALLING_UID;
public long time = 1234; public long time = 1234;
@@ -803,6 +869,10 @@ public class BinderCallsStatsTest {
}; };
} }
public Handler getHandler() {
return mHandler;
}
public BinderLatencyObserver getLatencyObserver() { public BinderLatencyObserver getLatencyObserver() {
return new BinderLatencyObserverTest.TestBinderLatencyObserver(); return new BinderLatencyObserverTest.TestBinderLatencyObserver();
} }

View File

@@ -16,6 +16,10 @@
package android.os; 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 * Battery stats local system service interface. This is used to pass internal data out of
* BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl. * 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 * @param sinceLast how long in millis has it been since a job was run
*/ */
public abstract void noteJobsDeferred(int uid, int numDeferred, long sinceLast); 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);
} }

View File

@@ -26,6 +26,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.net.Uri; import android.net.Uri;
import android.os.BatteryStatsInternal;
import android.os.Binder; import android.os.Binder;
import android.os.Process; import android.os.Process;
import android.os.SystemProperties; import android.os.SystemProperties;
@@ -173,10 +174,10 @@ public class BinderCallsStatsService extends Binder {
} }
try { try {
mParser.setString(Settings.Global.getString(mContext.getContentResolver(), mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.BINDER_CALLS_STATS)); Settings.Global.BINDER_CALLS_STATS));
} catch (IllegalArgumentException e) { } 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( mBinderCallsStats.setDetailedTracking(mParser.getBoolean(
SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT)); SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT));
@@ -298,6 +299,11 @@ public class BinderCallsStatsService extends Binder {
CachedDeviceState.Readonly deviceState = getLocalService( CachedDeviceState.Readonly deviceState = getLocalService(
CachedDeviceState.Readonly.class); CachedDeviceState.Readonly.class);
mBinderCallsStats.setDeviceState(deviceState); 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 // It needs to be called before mService.systemReady to make sure the observer is
// initialized before installing it. // initialized before installing it.
mWorkSourceProvider.systemReady(getContext()); mWorkSourceProvider.systemReady(getContext());

View File

@@ -66,6 +66,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats; import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerProfile;
import com.android.internal.os.RailStats; import com.android.internal.os.RailStats;
import com.android.internal.os.RpmStats; import com.android.internal.os.RpmStats;
@@ -87,6 +88,7 @@ import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction; import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; 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); if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
BatteryStatsService.this.noteJobsDeferred(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) { private static void awaitUninterruptibly(Future<?> future) {
@@ -1771,5 +1779,4 @@ public final class BatteryStatsService extends IBatteryStats.Stub
Binder.restoreCallingIdentity(ident); Binder.restoreCallingIdentity(ident);
} }
} }
} }