Loading core/java/com/android/internal/os/BatteryStatsImpl.java +22 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading core/java/com/android/internal/os/BinderCallsStats.java +82 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading @@ -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()); } Loading @@ -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(); } Loading @@ -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) { Loading Loading @@ -233,6 +286,9 @@ public class BinderCallsStats implements BinderInternal.Observer { callStat.callCount++; } } if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) { mSendUidsToObserver.add(workSourceUid); } } } Loading Loading @@ -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) { Loading @@ -277,17 +333,18 @@ public class BinderCallsStats implements BinderInternal.Observer { } @Nullable private String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) { if (getDefaultTransactionName == null) { return null; } private static String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) { String resolvedCode = null; if (getDefaultTransactionName != null) { try { return (String) getDefaultTransactionName.invoke(null, transactionCode); resolvedCode = (String) getDefaultTransactionName.invoke(null, transactionCode); } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) { throw new RuntimeException(e); } } return resolvedCode == null ? String.valueOf(transactionCode) : resolvedCode; } /** * This method is expensive to call. Loading Loading @@ -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; } Loading Loading @@ -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. */ Loading core/java/com/android/internal/os/BinderInternal.java +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +71 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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() { Loading Loading @@ -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(); Loading Loading @@ -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; Loading @@ -803,6 +869,10 @@ public class BinderCallsStatsTest { }; } public Handler getHandler() { return mHandler; } public BinderLatencyObserver getLatencyObserver() { return new BinderLatencyObserverTest.TestBinderLatencyObserver(); } Loading services/core/java/android/os/BatteryStatsInternal.java +10 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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); } Loading
core/java/com/android/internal/os/BatteryStatsImpl.java +22 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading
core/java/com/android/internal/os/BinderCallsStats.java +82 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading @@ -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()); } Loading @@ -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(); } Loading @@ -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) { Loading Loading @@ -233,6 +286,9 @@ public class BinderCallsStats implements BinderInternal.Observer { callStat.callCount++; } } if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) { mSendUidsToObserver.add(workSourceUid); } } } Loading Loading @@ -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) { Loading @@ -277,17 +333,18 @@ public class BinderCallsStats implements BinderInternal.Observer { } @Nullable private String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) { if (getDefaultTransactionName == null) { return null; } private static String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) { String resolvedCode = null; if (getDefaultTransactionName != null) { try { return (String) getDefaultTransactionName.invoke(null, transactionCode); resolvedCode = (String) getDefaultTransactionName.invoke(null, transactionCode); } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) { throw new RuntimeException(e); } } return resolvedCode == null ? String.valueOf(transactionCode) : resolvedCode; } /** * This method is expensive to call. Loading Loading @@ -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; } Loading Loading @@ -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. */ Loading
core/java/com/android/internal/os/BinderInternal.java +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading
core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +71 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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() { Loading Loading @@ -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(); Loading Loading @@ -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; Loading @@ -803,6 +869,10 @@ public class BinderCallsStatsTest { }; } public Handler getHandler() { return mHandler; } public BinderLatencyObserver getLatencyObserver() { return new BinderLatencyObserverTest.TestBinderLatencyObserver(); } Loading
services/core/java/android/os/BatteryStatsInternal.java +10 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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); }