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

Commit 305ee3e9 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Add plumbing to collect Binder stats for battery attribution

Bug: 158232997
Test: atest FrameworksCoreTests:com.android.internal.os.BinderCallsStatsTest

Change-Id: Icbccd26e20bc4ead13ffeac76b851af39269ca89
parent 2d1ef477
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -6123,6 +6123,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;
@@ -8689,6 +8700,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.
         */
+83 −14
Original line number Diff line number Diff line
@@ -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;
@@ -93,15 +97,47 @@ 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 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 {
        public Random getRandomGenerator() {
            return new Random();
        }

        public Handler getHandler() {
            return new Handler(Looper.getMainLooper());
        }
    }

    public BinderCallsStats(Injector injector) {
        this.mRandom = injector.getRandomGenerator();
        this.mCallStatsObserverHandler = injector.getHandler();
    }

    public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
@@ -112,6 +148,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) {
@@ -217,6 +271,9 @@ public class BinderCallsStats implements BinderInternal.Observer {
                    callStat.callCount++;
                }
            }
            if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
                mSendUidsToObserver.add(workSourceUid);
            }
        }
    }

@@ -251,7 +308,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) {
@@ -261,17 +318,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.
@@ -326,11 +384,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;
            }
@@ -628,6 +683,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. */
+11 −0
Original line number Diff line number Diff line
@@ -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.
@@ -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.
+71 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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() {
@@ -753,6 +760,65 @@ 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);
            }
        }
    }

    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;
@@ -774,6 +840,10 @@ public class BinderCallsStatsTest {
                        }
                    };
                }

                public Handler getHandler() {
                    return mHandler;
                }
            });
            setSamplingInterval(1);
            setAddDebugEntries(false);
+10 −0
Original line number Diff line number Diff line
@@ -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.
@@ -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