Merge "Include binder stats in battery stats" am: 83d1954afe

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

Change-Id: I17856dbb12a1c1cff7b4d6df740a016876b0bb1a
This commit is contained in:
Siim Sammul
2021-05-20 10:10:27 +00:00
committed by Automerger Merge Worker
9 changed files with 365 additions and 51 deletions

View File

@@ -40,6 +40,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;
@@ -71,6 +72,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;
@@ -122,6 +124,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;
@@ -6130,10 +6133,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);
}
}
@@ -6579,6 +6583,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.
*/
@@ -6752,6 +6815,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;
@@ -6860,6 +6933,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();
@@ -7948,6 +8029,9 @@ public class BatteryStatsImpl extends BatteryStats {
}
mPackageStats.clear();
mBinderCallCount = 0;
mBinderCallStats.clear();
mLastStepUserTime = mLastStepSystemTime = 0;
mCurStepUserTime = mCurStepSystemTime = 0;
@@ -8703,15 +8787,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;
}
}
/**
@@ -13235,6 +13342,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);

View File

@@ -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;
@@ -118,8 +115,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();
@@ -248,6 +250,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
final UidEntry uidEntry = getUidEntry(workSourceUid);
uidEntry.callCount++;
uidEntry.incrementalCallCount++;
if (recordCall) {
uidEntry.cpuTimeMicros += duration;
@@ -263,6 +266,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
}
callStat.callCount++;
callStat.incrementalCallCount++;
callStat.recordedCallCount++;
callStat.cpuTimeMicros += duration;
callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
@@ -284,6 +288,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
screenInteractive);
if (callStat != null) {
callStat.callCount++;
callStat.incrementalCallCount++;
}
}
if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
@@ -322,30 +327,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.
*/
@@ -384,28 +365,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.
@@ -703,8 +679,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;
@@ -714,12 +692,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
@@ -772,13 +752,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

View File

@@ -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);
}
/**

View File

@@ -0,0 +1,89 @@
/*
* 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);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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";
}
}
}

View File

@@ -23,6 +23,7 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({
BatteryStatsCpuTimesTest.class,
BatteryStatsBackgroundStatsTest.class,
BatteryStatsBinderCallStatsTest.class,
BatteryStatsCounterTest.class,
BatteryStatsDualTimerTest.class,
BatteryStatsDurationTimerTest.class,

View File

@@ -767,7 +767,9 @@ public class BinderCallsStatsTest {
bcs.setTrackScreenInteractive(false);
final ArrayList<BinderCallsStats.CallStat> callStatsList = new ArrayList<>();
bcs.setCallStatsObserver((workSourceUid, callStats) -> callStatsList.addAll(callStats));
bcs.setCallStatsObserver(
(workSourceUid, incrementalCallCount, callStats) -> callStatsList.addAll(
callStats));
Binder binder = new Binder();

View File

@@ -49,6 +49,6 @@ public abstract class BatteryStatsInternal {
/**
* Informs battery stats of binder stats for the given work source UID.
*/
public abstract void noteBinderCallStats(int workSourceUid,
public abstract void noteBinderCallStats(int workSourceUid, long incrementalBinderCallCount,
Collection<BinderCallsStats.CallStat> callStats);
}

View File

@@ -308,9 +308,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
@Override
public void noteBinderCallStats(int workSourceUid,
public void noteBinderCallStats(int workSourceUid, long incrementatCallCount,
Collection<BinderCallsStats.CallStat> callStats) {
mStats.noteBinderCallStats(workSourceUid, callStats);
mStats.noteBinderCallStats(workSourceUid, incrementatCallCount, callStats);
}
}