Loading core/java/com/android/internal/os/BatteryStatsImpl.java +151 −5 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; Loading Loading @@ -70,6 +71,7 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.IntArray; import android.util.KeyValueListParser; Loading Loading @@ -120,6 +122,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; Loading Loading @@ -6127,10 +6130,11 @@ 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, public void noteBinderCallStats(int workSourceUid, long incrementalCallCount, Collection<BinderCallsStats.CallStat> callStats) { synchronized (this) { getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(callStats); getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(incrementalCallCount, callStats); } } Loading Loading @@ -6576,6 +6580,65 @@ public class BatteryStatsImpl extends BatteryStats { } } /** * Accumulates stats for a specific binder transaction. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected static class BinderCallStats { static final Comparator<BinderCallStats> COMPARATOR = Comparator.comparing(BinderCallStats::getClassName) .thenComparing(BinderCallStats::getMethodName); public Class<? extends Binder> binderClass; public int transactionCode; public String methodName; public long callCount; public long recordedCallCount; public long recordedCpuTimeMicros; @Override public int hashCode() { return binderClass.hashCode() * 31 + transactionCode; } @Override public boolean equals(Object obj) { if (!(obj instanceof BinderCallStats)) { return false; } BinderCallStats bcsk = (BinderCallStats) obj; return binderClass.equals(bcsk.binderClass) && transactionCode == bcsk.transactionCode; } public String getClassName() { return binderClass.getName(); } public String getMethodName() { return methodName; } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void ensureMethodName(BinderTransactionNameResolver resolver) { if (methodName == null) { methodName = resolver.getMethodName(binderClass, transactionCode); } } @Override public String toString() { return "BinderCallStats{" + binderClass + " transaction=" + transactionCode + " callCount=" + callCount + " recordedCallCount=" + recordedCallCount + " recorderCpuTimeMicros=" + recordedCpuTimeMicros + "}"; } } /** * The statistics associated with a particular uid. */ Loading Loading @@ -6749,6 +6812,16 @@ public class BatteryStatsImpl extends BatteryStats { */ final SparseArray<Pid> mPids = new SparseArray<>(); /** * Grand total of system server binder calls made by this uid. */ private long mBinderCallCount; /** * Detailed information about system server binder calls made by this uid. */ private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>(); public Uid(BatteryStatsImpl bsi, int uid) { mBsi = bsi; mUid = uid; Loading Loading @@ -6857,6 +6930,14 @@ public class BatteryStatsImpl extends BatteryStats { return nullIfAllZeros(mProcStateScreenOffTimeMs[procState], which); } public long getBinderCallCount() { return mBinderCallCount; } public ArraySet<BinderCallStats> getBinderCallStats() { return mBinderCallStats; } public void addIsolatedUid(int isolatedUid) { if (mChildUids == null) { mChildUids = new IntArray(); Loading Loading @@ -7945,6 +8026,9 @@ public class BatteryStatsImpl extends BatteryStats { } mPackageStats.clear(); mBinderCallCount = 0; mBinderCallStats.clear(); mLastStepUserTime = mLastStepSystemTime = 0; mCurStepUserTime = mCurStepSystemTime = 0; Loading Loading @@ -8700,15 +8784,38 @@ public class BatteryStatsImpl extends BatteryStats { } } // Reusable object used as a key to lookup values in mBinderCallStats private static BinderCallStats sTempBinderCallStats = new BinderCallStats(); /** * Notes incoming binder call stats associated with this work source UID. */ public void noteBinderCallStatsLocked(Collection<BinderCallsStats.CallStat> callStats) { public void noteBinderCallStatsLocked(long incrementalCallCount, Collection<BinderCallsStats.CallStat> callStats) { if (DEBUG) { Slog.d(TAG, "noteBinderCalls() workSourceUid = [" + mUid + "], callStats = [" Slog.d(TAG, "noteBinderCalls() workSourceUid = [" + mUid + "], " + " incrementalCallCount: " + incrementalCallCount + " callStats = [" + new ArrayList<>(callStats) + "]"); } // TODO(dplotnikov): finish the implementation by actually remembering the stats mBinderCallCount += incrementalCallCount; for (BinderCallsStats.CallStat stat : callStats) { BinderCallStats bcs; sTempBinderCallStats.binderClass = stat.binderClass; sTempBinderCallStats.transactionCode = stat.transactionCode; int index = mBinderCallStats.indexOf(sTempBinderCallStats); if (index >= 0) { bcs = mBinderCallStats.valueAt(index); } else { bcs = new BinderCallStats(); bcs.binderClass = stat.binderClass; bcs.transactionCode = stat.transactionCode; mBinderCallStats.add(bcs); } bcs.callCount += stat.incrementalCallCount; bcs.recordedCallCount = stat.recordedCallCount; bcs.recordedCpuTimeMicros = stat.cpuTimeMicros; } } /** Loading Loading @@ -13220,6 +13327,45 @@ public class BatteryStatsImpl extends BatteryStats { pw.print(uid.getUserCpuTimeUs(STATS_SINCE_CHARGED) / 1000); pw.print(" "); pw.println(uid.getSystemCpuTimeUs(STATS_SINCE_CHARGED) / 1000); } pw.println("Per UID system service calls:"); BinderTransactionNameResolver nameResolver = new BinderTransactionNameResolver(); for (int i = 0; i < size; i++) { int u = mUidStats.keyAt(i); Uid uid = mUidStats.get(u); long binderCallCount = uid.getBinderCallCount(); if (binderCallCount != 0) { pw.print(" "); pw.print(u); pw.print(" system service calls: "); pw.print(binderCallCount); ArraySet<BinderCallStats> binderCallStats = uid.getBinderCallStats(); if (!binderCallStats.isEmpty()) { pw.println(", including"); BinderCallStats[] bcss = new BinderCallStats[binderCallStats.size()]; binderCallStats.toArray(bcss); for (BinderCallStats bcs : bcss) { bcs.ensureMethodName(nameResolver); } Arrays.sort(bcss, BinderCallStats.COMPARATOR); for (BinderCallStats callStats : bcss) { pw.print(" "); pw.print(callStats.getClassName()); pw.print('#'); pw.print(callStats.getMethodName()); pw.print(" calls: "); pw.print(callStats.callCount); if (callStats.recordedCallCount != 0) { pw.print(" time: "); pw.print(callStats.callCount * callStats.recordedCpuTimeMicros / callStats.recordedCallCount / 1000); } pw.println(); } } else { pw.println(); } } } pw.println("Per UID CPU active time in ms:"); for (int i = 0; i < size; i++) { int u = mUidStats.keyAt(i); core/java/com/android/internal/os/BinderCallsStats.java +23 −41 Original line number Diff line number Diff line Loading @@ -36,13 +36,10 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BinderInternal.CallSession; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Random; import java.util.concurrent.ConcurrentLinkedQueue; Loading Loading @@ -115,8 +112,13 @@ public class BinderCallsStats implements BinderInternal.Observer { for (int i = 0; i < size; i++) { UidEntry uidEntry = mUidEntries.get(mSendUidsToObserver.valueAt(i)); if (uidEntry != null) { ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats; mCallStatsObserver.noteCallStats(uidEntry.workSourceUid, uidEntry.getCallStatsList()); uidEntry.incrementalCallCount, callStats.values()); uidEntry.incrementalCallCount = 0; for (int j = callStats.size() - 1; j >= 0; j--) { callStats.valueAt(j).incrementalCallCount = 0; } } } mSendUidsToObserver.clear(); Loading Loading @@ -233,6 +235,7 @@ public class BinderCallsStats implements BinderInternal.Observer { final UidEntry uidEntry = getUidEntry(workSourceUid); uidEntry.callCount++; uidEntry.incrementalCallCount++; if (recordCall) { uidEntry.cpuTimeMicros += duration; Loading @@ -248,6 +251,7 @@ public class BinderCallsStats implements BinderInternal.Observer { } callStat.callCount++; callStat.incrementalCallCount++; callStat.recordedCallCount++; callStat.cpuTimeMicros += duration; callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration); Loading @@ -269,6 +273,7 @@ public class BinderCallsStats implements BinderInternal.Observer { screenInteractive); if (callStat != null) { callStat.callCount++; callStat.incrementalCallCount++; } } if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) { Loading Loading @@ -307,30 +312,6 @@ public class BinderCallsStats implements BinderInternal.Observer { } } @Nullable private static Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) { try { return binder.getMethod("getDefaultTransactionName", int.class); } catch (NoSuchMethodException e) { // The method might not be present for stubs not generated with AIDL. return null; } } @Nullable 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; } /** * This method is expensive to call. */ Loading Loading @@ -369,28 +350,23 @@ public class BinderCallsStats implements BinderInternal.Observer { // Resolve codes outside of the lock since it can be slow. ExportedCallStat previous = null; // Cache the previous method/transaction code. Method getDefaultTransactionName = null; String previousMethodName = null; resultCallStats.sort(BinderCallsStats::compareByBinderClassAndCode); BinderTransactionNameResolver resolver = new BinderTransactionNameResolver(); for (ExportedCallStat exported : resultCallStats) { final boolean isClassDifferent = previous == null || !previous.className.equals(exported.className); if (isClassDifferent) { getDefaultTransactionName = getDefaultTransactionNameMethod(exported.binderClass); } final boolean isCodeDifferent = previous == null || previous.transactionCode != exported.transactionCode; final String methodName; if (isClassDifferent || isCodeDifferent) { methodName = resolveTransactionCode( getDefaultTransactionName, exported.transactionCode); methodName = resolver.getMethodName(exported.binderClass, exported.transactionCode); } else { methodName = previousMethodName; } previousMethodName = methodName; exported.methodName = methodName; previous = exported; } // Debug entries added to help validate the data. Loading Loading @@ -675,8 +651,10 @@ public class BinderCallsStats implements BinderInternal.Observer { public long maxRequestSizeBytes; public long maxReplySizeBytes; public long exceptionCount; // Call count since reset public long incrementalCallCount; CallStat(int callingUid, Class<? extends Binder> binderClass, int transactionCode, public CallStat(int callingUid, Class<? extends Binder> binderClass, int transactionCode, boolean screenInteractive) { this.callingUid = callingUid; this.binderClass = binderClass; Loading @@ -686,12 +664,14 @@ public class BinderCallsStats implements BinderInternal.Observer { @Override public String toString() { // This is expensive, but CallStat.toString() is only used for debugging. String methodName = new BinderTransactionNameResolver().getMethodName(binderClass, transactionCode); return "CallStat{" + "callingUid=" + callingUid + ", transaction=" + binderClass.getSimpleName() + '.' + resolveTransactionCode( getDefaultTransactionNameMethod(binderClass), transactionCode) + ", transaction=" + binderClass.getSimpleName() + '.' + methodName + ", callCount=" + callCount + ", incrementalCallCount=" + incrementalCallCount + ", recordedCallCount=" + recordedCallCount + ", cpuTimeMicros=" + cpuTimeMicros + ", latencyMicros=" + latencyMicros Loading Loading @@ -744,13 +724,15 @@ public class BinderCallsStats implements BinderInternal.Observer { // Approximate total CPU usage can be computed by // cpuTimeMicros * callCount / recordedCallCount public long cpuTimeMicros; // Call count that gets reset after delivery to BatteryStats public long incrementalCallCount; UidEntry(int uid) { this.workSourceUid = uid; } // Aggregate time spent per each call name: call_desc -> cpu_time_micros private Map<CallStatKey, CallStat> mCallStats = new ArrayMap<>(); private ArrayMap<CallStatKey, CallStat> mCallStats = new ArrayMap<>(); private CallStatKey mTempKey = new CallStatKey(); @Nullable Loading core/java/com/android/internal/os/BinderInternal.java +2 −1 Original line number Diff line number Diff line Loading @@ -141,7 +141,8 @@ public class BinderInternal { /** * Notes incoming binder call stats associated with this work source UID. */ void noteCallStats(int workSourceUid, Collection<BinderCallsStats.CallStat> callStats); void noteCallStats(int workSourceUid, long incrementalCallCount, Collection<BinderCallsStats.CallStat> callStats); } /** Loading core/java/com/android/internal/os/BinderTransactionNameResolver.java 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import android.os.Binder; import com.android.internal.annotations.VisibleForTesting; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; /** * Maps a binder class and transaction code to the default transaction name. Since this * resolution is class-based as opposed to instance-based, any custom implementation of * {@link Binder#getTransactionName} will be ignored. * * The class is NOT thread safe * * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class BinderTransactionNameResolver { private static final Method NO_GET_DEFAULT_TRANSACTION_NAME_METHOD; /** * Generates the default transaction method name, which is just the transaction code. * Used when the binder does not define a static "getDefaultTransactionName" method. * * @hide */ public static String noDefaultTransactionName(int transactionCode) { return String.valueOf(transactionCode); } static { try { NO_GET_DEFAULT_TRANSACTION_NAME_METHOD = BinderTransactionNameResolver.class.getMethod( "noDefaultTransactionName", int.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private final HashMap<Class<? extends Binder>, Method> mGetDefaultTransactionNameMethods = new HashMap<>(); /** * Given a binder class name and transaction code, returns the corresponding method name. * * @hide */ public String getMethodName(Class<? extends Binder> binderClass, int transactionCode) { Method method = mGetDefaultTransactionNameMethods.get(binderClass); if (method == null) { try { method = binderClass.getMethod("getDefaultTransactionName", int.class); } catch (NoSuchMethodException e) { method = NO_GET_DEFAULT_TRANSACTION_NAME_METHOD; } if (method.getReturnType() != String.class || !Modifier.isStatic(method.getModifiers())) { method = NO_GET_DEFAULT_TRANSACTION_NAME_METHOD; } mGetDefaultTransactionNameMethods.put(binderClass, method); } try { return (String) method.invoke(null, transactionCode); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import android.os.Binder; import android.os.Process; import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import junit.framework.TestCase; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Collection; /** * Test cases for android.os.BatteryStats, system server Binder call stats. */ @RunWith(AndroidJUnit4.class) @SmallTest public class BatteryStatsBinderCallStatsTest extends TestCase { private static final int TRANSACTION_CODE = 100; /** * Test BatteryStatsImpl.Uid.noteBinderCallStats. */ @Test public void testNoteBinderCallStats() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); int callingUid = Process.FIRST_APPLICATION_UID + 1; int workSourceUid = Process.FIRST_APPLICATION_UID + 1; Collection<BinderCallsStats.CallStat> callStats = new ArrayList<>(); BinderCallsStats.CallStat stat1 = new BinderCallsStats.CallStat(callingUid, MockBinder.class, TRANSACTION_CODE, true /*screenInteractive */); stat1.incrementalCallCount = 21; stat1.recordedCallCount = 5; stat1.cpuTimeMicros = 1000; callStats.add(stat1); bi.noteBinderCallStats(workSourceUid, 42, callStats); callStats.clear(); BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(callingUid, MockBinder.class, TRANSACTION_CODE, true /*screenInteractive */); stat2.incrementalCallCount = 9; stat2.recordedCallCount = 8; stat2.cpuTimeMicros = 500; callStats.add(stat2); bi.noteBinderCallStats(workSourceUid, 8, callStats); BatteryStatsImpl.Uid uid = bi.getUidStatsLocked(workSourceUid); assertEquals(42 + 8, uid.getBinderCallCount()); BinderTransactionNameResolver resolver = new BinderTransactionNameResolver(); ArraySet<BatteryStatsImpl.BinderCallStats> stats = uid.getBinderCallStats(); assertEquals(1, stats.size()); BatteryStatsImpl.BinderCallStats value = stats.valueAt(0); value.ensureMethodName(resolver); assertEquals("testMethod", value.getMethodName()); assertEquals(21 + 9, value.callCount); assertEquals(8, value.recordedCallCount); assertEquals(500, value.recordedCpuTimeMicros); } private static class MockBinder extends Binder { public static String getDefaultTransactionName(int txCode) { return txCode == TRANSACTION_CODE ? "testMethod" : "unknown"; } } } Loading
core/java/com/android/internal/os/BatteryStatsImpl.java +151 −5 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; Loading Loading @@ -70,6 +71,7 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.IntArray; import android.util.KeyValueListParser; Loading Loading @@ -120,6 +122,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; Loading Loading @@ -6127,10 +6130,11 @@ 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, public void noteBinderCallStats(int workSourceUid, long incrementalCallCount, Collection<BinderCallsStats.CallStat> callStats) { synchronized (this) { getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(callStats); getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(incrementalCallCount, callStats); } } Loading Loading @@ -6576,6 +6580,65 @@ public class BatteryStatsImpl extends BatteryStats { } } /** * Accumulates stats for a specific binder transaction. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected static class BinderCallStats { static final Comparator<BinderCallStats> COMPARATOR = Comparator.comparing(BinderCallStats::getClassName) .thenComparing(BinderCallStats::getMethodName); public Class<? extends Binder> binderClass; public int transactionCode; public String methodName; public long callCount; public long recordedCallCount; public long recordedCpuTimeMicros; @Override public int hashCode() { return binderClass.hashCode() * 31 + transactionCode; } @Override public boolean equals(Object obj) { if (!(obj instanceof BinderCallStats)) { return false; } BinderCallStats bcsk = (BinderCallStats) obj; return binderClass.equals(bcsk.binderClass) && transactionCode == bcsk.transactionCode; } public String getClassName() { return binderClass.getName(); } public String getMethodName() { return methodName; } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void ensureMethodName(BinderTransactionNameResolver resolver) { if (methodName == null) { methodName = resolver.getMethodName(binderClass, transactionCode); } } @Override public String toString() { return "BinderCallStats{" + binderClass + " transaction=" + transactionCode + " callCount=" + callCount + " recordedCallCount=" + recordedCallCount + " recorderCpuTimeMicros=" + recordedCpuTimeMicros + "}"; } } /** * The statistics associated with a particular uid. */ Loading Loading @@ -6749,6 +6812,16 @@ public class BatteryStatsImpl extends BatteryStats { */ final SparseArray<Pid> mPids = new SparseArray<>(); /** * Grand total of system server binder calls made by this uid. */ private long mBinderCallCount; /** * Detailed information about system server binder calls made by this uid. */ private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>(); public Uid(BatteryStatsImpl bsi, int uid) { mBsi = bsi; mUid = uid; Loading Loading @@ -6857,6 +6930,14 @@ public class BatteryStatsImpl extends BatteryStats { return nullIfAllZeros(mProcStateScreenOffTimeMs[procState], which); } public long getBinderCallCount() { return mBinderCallCount; } public ArraySet<BinderCallStats> getBinderCallStats() { return mBinderCallStats; } public void addIsolatedUid(int isolatedUid) { if (mChildUids == null) { mChildUids = new IntArray(); Loading Loading @@ -7945,6 +8026,9 @@ public class BatteryStatsImpl extends BatteryStats { } mPackageStats.clear(); mBinderCallCount = 0; mBinderCallStats.clear(); mLastStepUserTime = mLastStepSystemTime = 0; mCurStepUserTime = mCurStepSystemTime = 0; Loading Loading @@ -8700,15 +8784,38 @@ public class BatteryStatsImpl extends BatteryStats { } } // Reusable object used as a key to lookup values in mBinderCallStats private static BinderCallStats sTempBinderCallStats = new BinderCallStats(); /** * Notes incoming binder call stats associated with this work source UID. */ public void noteBinderCallStatsLocked(Collection<BinderCallsStats.CallStat> callStats) { public void noteBinderCallStatsLocked(long incrementalCallCount, Collection<BinderCallsStats.CallStat> callStats) { if (DEBUG) { Slog.d(TAG, "noteBinderCalls() workSourceUid = [" + mUid + "], callStats = [" Slog.d(TAG, "noteBinderCalls() workSourceUid = [" + mUid + "], " + " incrementalCallCount: " + incrementalCallCount + " callStats = [" + new ArrayList<>(callStats) + "]"); } // TODO(dplotnikov): finish the implementation by actually remembering the stats mBinderCallCount += incrementalCallCount; for (BinderCallsStats.CallStat stat : callStats) { BinderCallStats bcs; sTempBinderCallStats.binderClass = stat.binderClass; sTempBinderCallStats.transactionCode = stat.transactionCode; int index = mBinderCallStats.indexOf(sTempBinderCallStats); if (index >= 0) { bcs = mBinderCallStats.valueAt(index); } else { bcs = new BinderCallStats(); bcs.binderClass = stat.binderClass; bcs.transactionCode = stat.transactionCode; mBinderCallStats.add(bcs); } bcs.callCount += stat.incrementalCallCount; bcs.recordedCallCount = stat.recordedCallCount; bcs.recordedCpuTimeMicros = stat.cpuTimeMicros; } } /** Loading Loading @@ -13220,6 +13327,45 @@ public class BatteryStatsImpl extends BatteryStats { pw.print(uid.getUserCpuTimeUs(STATS_SINCE_CHARGED) / 1000); pw.print(" "); pw.println(uid.getSystemCpuTimeUs(STATS_SINCE_CHARGED) / 1000); } pw.println("Per UID system service calls:"); BinderTransactionNameResolver nameResolver = new BinderTransactionNameResolver(); for (int i = 0; i < size; i++) { int u = mUidStats.keyAt(i); Uid uid = mUidStats.get(u); long binderCallCount = uid.getBinderCallCount(); if (binderCallCount != 0) { pw.print(" "); pw.print(u); pw.print(" system service calls: "); pw.print(binderCallCount); ArraySet<BinderCallStats> binderCallStats = uid.getBinderCallStats(); if (!binderCallStats.isEmpty()) { pw.println(", including"); BinderCallStats[] bcss = new BinderCallStats[binderCallStats.size()]; binderCallStats.toArray(bcss); for (BinderCallStats bcs : bcss) { bcs.ensureMethodName(nameResolver); } Arrays.sort(bcss, BinderCallStats.COMPARATOR); for (BinderCallStats callStats : bcss) { pw.print(" "); pw.print(callStats.getClassName()); pw.print('#'); pw.print(callStats.getMethodName()); pw.print(" calls: "); pw.print(callStats.callCount); if (callStats.recordedCallCount != 0) { pw.print(" time: "); pw.print(callStats.callCount * callStats.recordedCpuTimeMicros / callStats.recordedCallCount / 1000); } pw.println(); } } else { pw.println(); } } } pw.println("Per UID CPU active time in ms:"); for (int i = 0; i < size; i++) { int u = mUidStats.keyAt(i);
core/java/com/android/internal/os/BinderCallsStats.java +23 −41 Original line number Diff line number Diff line Loading @@ -36,13 +36,10 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BinderInternal.CallSession; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Random; import java.util.concurrent.ConcurrentLinkedQueue; Loading Loading @@ -115,8 +112,13 @@ public class BinderCallsStats implements BinderInternal.Observer { for (int i = 0; i < size; i++) { UidEntry uidEntry = mUidEntries.get(mSendUidsToObserver.valueAt(i)); if (uidEntry != null) { ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats; mCallStatsObserver.noteCallStats(uidEntry.workSourceUid, uidEntry.getCallStatsList()); uidEntry.incrementalCallCount, callStats.values()); uidEntry.incrementalCallCount = 0; for (int j = callStats.size() - 1; j >= 0; j--) { callStats.valueAt(j).incrementalCallCount = 0; } } } mSendUidsToObserver.clear(); Loading Loading @@ -233,6 +235,7 @@ public class BinderCallsStats implements BinderInternal.Observer { final UidEntry uidEntry = getUidEntry(workSourceUid); uidEntry.callCount++; uidEntry.incrementalCallCount++; if (recordCall) { uidEntry.cpuTimeMicros += duration; Loading @@ -248,6 +251,7 @@ public class BinderCallsStats implements BinderInternal.Observer { } callStat.callCount++; callStat.incrementalCallCount++; callStat.recordedCallCount++; callStat.cpuTimeMicros += duration; callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration); Loading @@ -269,6 +273,7 @@ public class BinderCallsStats implements BinderInternal.Observer { screenInteractive); if (callStat != null) { callStat.callCount++; callStat.incrementalCallCount++; } } if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) { Loading Loading @@ -307,30 +312,6 @@ public class BinderCallsStats implements BinderInternal.Observer { } } @Nullable private static Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) { try { return binder.getMethod("getDefaultTransactionName", int.class); } catch (NoSuchMethodException e) { // The method might not be present for stubs not generated with AIDL. return null; } } @Nullable 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; } /** * This method is expensive to call. */ Loading Loading @@ -369,28 +350,23 @@ public class BinderCallsStats implements BinderInternal.Observer { // Resolve codes outside of the lock since it can be slow. ExportedCallStat previous = null; // Cache the previous method/transaction code. Method getDefaultTransactionName = null; String previousMethodName = null; resultCallStats.sort(BinderCallsStats::compareByBinderClassAndCode); BinderTransactionNameResolver resolver = new BinderTransactionNameResolver(); for (ExportedCallStat exported : resultCallStats) { final boolean isClassDifferent = previous == null || !previous.className.equals(exported.className); if (isClassDifferent) { getDefaultTransactionName = getDefaultTransactionNameMethod(exported.binderClass); } final boolean isCodeDifferent = previous == null || previous.transactionCode != exported.transactionCode; final String methodName; if (isClassDifferent || isCodeDifferent) { methodName = resolveTransactionCode( getDefaultTransactionName, exported.transactionCode); methodName = resolver.getMethodName(exported.binderClass, exported.transactionCode); } else { methodName = previousMethodName; } previousMethodName = methodName; exported.methodName = methodName; previous = exported; } // Debug entries added to help validate the data. Loading Loading @@ -675,8 +651,10 @@ public class BinderCallsStats implements BinderInternal.Observer { public long maxRequestSizeBytes; public long maxReplySizeBytes; public long exceptionCount; // Call count since reset public long incrementalCallCount; CallStat(int callingUid, Class<? extends Binder> binderClass, int transactionCode, public CallStat(int callingUid, Class<? extends Binder> binderClass, int transactionCode, boolean screenInteractive) { this.callingUid = callingUid; this.binderClass = binderClass; Loading @@ -686,12 +664,14 @@ public class BinderCallsStats implements BinderInternal.Observer { @Override public String toString() { // This is expensive, but CallStat.toString() is only used for debugging. String methodName = new BinderTransactionNameResolver().getMethodName(binderClass, transactionCode); return "CallStat{" + "callingUid=" + callingUid + ", transaction=" + binderClass.getSimpleName() + '.' + resolveTransactionCode( getDefaultTransactionNameMethod(binderClass), transactionCode) + ", transaction=" + binderClass.getSimpleName() + '.' + methodName + ", callCount=" + callCount + ", incrementalCallCount=" + incrementalCallCount + ", recordedCallCount=" + recordedCallCount + ", cpuTimeMicros=" + cpuTimeMicros + ", latencyMicros=" + latencyMicros Loading Loading @@ -744,13 +724,15 @@ public class BinderCallsStats implements BinderInternal.Observer { // Approximate total CPU usage can be computed by // cpuTimeMicros * callCount / recordedCallCount public long cpuTimeMicros; // Call count that gets reset after delivery to BatteryStats public long incrementalCallCount; UidEntry(int uid) { this.workSourceUid = uid; } // Aggregate time spent per each call name: call_desc -> cpu_time_micros private Map<CallStatKey, CallStat> mCallStats = new ArrayMap<>(); private ArrayMap<CallStatKey, CallStat> mCallStats = new ArrayMap<>(); private CallStatKey mTempKey = new CallStatKey(); @Nullable Loading
core/java/com/android/internal/os/BinderInternal.java +2 −1 Original line number Diff line number Diff line Loading @@ -141,7 +141,8 @@ public class BinderInternal { /** * Notes incoming binder call stats associated with this work source UID. */ void noteCallStats(int workSourceUid, Collection<BinderCallsStats.CallStat> callStats); void noteCallStats(int workSourceUid, long incrementalCallCount, Collection<BinderCallsStats.CallStat> callStats); } /** Loading
core/java/com/android/internal/os/BinderTransactionNameResolver.java 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import android.os.Binder; import com.android.internal.annotations.VisibleForTesting; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; /** * Maps a binder class and transaction code to the default transaction name. Since this * resolution is class-based as opposed to instance-based, any custom implementation of * {@link Binder#getTransactionName} will be ignored. * * The class is NOT thread safe * * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class BinderTransactionNameResolver { private static final Method NO_GET_DEFAULT_TRANSACTION_NAME_METHOD; /** * Generates the default transaction method name, which is just the transaction code. * Used when the binder does not define a static "getDefaultTransactionName" method. * * @hide */ public static String noDefaultTransactionName(int transactionCode) { return String.valueOf(transactionCode); } static { try { NO_GET_DEFAULT_TRANSACTION_NAME_METHOD = BinderTransactionNameResolver.class.getMethod( "noDefaultTransactionName", int.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private final HashMap<Class<? extends Binder>, Method> mGetDefaultTransactionNameMethods = new HashMap<>(); /** * Given a binder class name and transaction code, returns the corresponding method name. * * @hide */ public String getMethodName(Class<? extends Binder> binderClass, int transactionCode) { Method method = mGetDefaultTransactionNameMethods.get(binderClass); if (method == null) { try { method = binderClass.getMethod("getDefaultTransactionName", int.class); } catch (NoSuchMethodException e) { method = NO_GET_DEFAULT_TRANSACTION_NAME_METHOD; } if (method.getReturnType() != String.class || !Modifier.isStatic(method.getModifiers())) { method = NO_GET_DEFAULT_TRANSACTION_NAME_METHOD; } mGetDefaultTransactionNameMethods.put(binderClass, method); } try { return (String) method.invoke(null, transactionCode); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } }
core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import android.os.Binder; import android.os.Process; import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import junit.framework.TestCase; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Collection; /** * Test cases for android.os.BatteryStats, system server Binder call stats. */ @RunWith(AndroidJUnit4.class) @SmallTest public class BatteryStatsBinderCallStatsTest extends TestCase { private static final int TRANSACTION_CODE = 100; /** * Test BatteryStatsImpl.Uid.noteBinderCallStats. */ @Test public void testNoteBinderCallStats() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); int callingUid = Process.FIRST_APPLICATION_UID + 1; int workSourceUid = Process.FIRST_APPLICATION_UID + 1; Collection<BinderCallsStats.CallStat> callStats = new ArrayList<>(); BinderCallsStats.CallStat stat1 = new BinderCallsStats.CallStat(callingUid, MockBinder.class, TRANSACTION_CODE, true /*screenInteractive */); stat1.incrementalCallCount = 21; stat1.recordedCallCount = 5; stat1.cpuTimeMicros = 1000; callStats.add(stat1); bi.noteBinderCallStats(workSourceUid, 42, callStats); callStats.clear(); BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(callingUid, MockBinder.class, TRANSACTION_CODE, true /*screenInteractive */); stat2.incrementalCallCount = 9; stat2.recordedCallCount = 8; stat2.cpuTimeMicros = 500; callStats.add(stat2); bi.noteBinderCallStats(workSourceUid, 8, callStats); BatteryStatsImpl.Uid uid = bi.getUidStatsLocked(workSourceUid); assertEquals(42 + 8, uid.getBinderCallCount()); BinderTransactionNameResolver resolver = new BinderTransactionNameResolver(); ArraySet<BatteryStatsImpl.BinderCallStats> stats = uid.getBinderCallStats(); assertEquals(1, stats.size()); BatteryStatsImpl.BinderCallStats value = stats.valueAt(0); value.ensureMethodName(resolver); assertEquals("testMethod", value.getMethodName()); assertEquals(21 + 9, value.callCount); assertEquals(8, value.recordedCallCount); assertEquals(500, value.recordedCpuTimeMicros); } private static class MockBinder extends Binder { public static String getDefaultTransactionName(int txCode) { return txCode == TRANSACTION_CODE ? "testMethod" : "unknown"; } } }