Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b36f9469 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Include binder stats in battery stats"

parents a47b1d0c d2e374b6
Loading
Loading
Loading
Loading
+151 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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);
        }
    }
@@ -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.
     */
@@ -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;
@@ -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();
@@ -7945,6 +8026,9 @@ public class BatteryStatsImpl extends BatteryStats {
            }
            mPackageStats.clear();
            mBinderCallCount = 0;
            mBinderCallStats.clear();
            mLastStepUserTime = mLastStepSystemTime = 0;
            mCurStepUserTime = mCurStepSystemTime = 0;
@@ -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;
            }
        }
        /**
@@ -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);
+23 −41
Original line number Diff line number Diff line
@@ -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;
@@ -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();
@@ -233,6 +235,7 @@ public class BinderCallsStats implements BinderInternal.Observer {

            final UidEntry uidEntry = getUidEntry(workSourceUid);
            uidEntry.callCount++;
            uidEntry.incrementalCallCount++;

            if (recordCall) {
                uidEntry.cpuTimeMicros += duration;
@@ -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);
@@ -269,6 +273,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
                        screenInteractive);
                if (callStat != null) {
                    callStat.callCount++;
                    callStat.incrementalCallCount++;
                }
            }
            if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
@@ -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.
     */
@@ -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.
@@ -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;
@@ -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
@@ -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
+2 −1
Original line number Diff line number Diff line
@@ -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);
    }

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