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

Commit 47435459 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov Committed by Siim Sammul
Browse files

Add plumbing to collect Binder stats for battery attribution

Cherry picked from internal master: https://googleplex-android-review.git.corp.google.com/c/platform/frameworks/base/+/9966816

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

Change-Id: Icbccd26e20bc4ead13ffeac76b851af39269ca89
Merged-In: Icbccd26e20bc4ead13ffeac76b851af39269ca89
parent 0dadb666
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -6126,6 +6126,17 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    /**
     * Records timing data related to an incoming Binder call in order to attribute
     * the power consumption to the calling app.
     */
    public void noteBinderCallStats(int workSourceUid,
            Collection<BinderCallsStats.CallStat> callStats) {
        synchronized (this) {
            getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(callStats);
        }
    }
    public String[] getWifiIfaces() {
        synchronized (mWifiNetworkLock) {
            return mWifiIfaces;
@@ -8692,6 +8703,17 @@ public class BatteryStatsImpl extends BatteryStats {
            }
        }
        /**
         * Notes incoming binder call stats associated with this work source UID.
         */
        public void noteBinderCallStatsLocked(Collection<BinderCallsStats.CallStat> callStats) {
            if (DEBUG) {
                Slog.d(TAG, "noteBinderCalls() workSourceUid = [" + mUid + "], callStats = ["
                        + new ArrayList<>(callStats) + "]");
            }
            // TODO(dplotnikov): finish the implementation by actually remembering the stats
        }
        /**
         * The statistics associated with a particular wake lock.
         */
+82 −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;
@@ -95,7 +99,33 @@ public class BinderCallsStats implements BinderInternal.Observer {
    private CachedDeviceState.Readonly mDeviceState;
    private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;

    private static final int CALL_STATS_OBSERVER_DEBOUNCE_MILLIS = 5000;
    private BinderLatencyObserver mLatencyObserver;
    private BinderInternal.CallStatsObserver mCallStatsObserver;
    private ArraySet<Integer> mSendUidsToObserver = new ArraySet<>(32);
    private final Handler mCallStatsObserverHandler;
    private Runnable mCallStatsObserverRunnable = new Runnable() {
        @Override
        public void run() {
            if (mCallStatsObserver == null) {
                return;
            }

            noteCallsStatsDelayed();

            synchronized (mLock) {
                int size = mSendUidsToObserver.size();
                for (int i = 0; i < size; i++) {
                    UidEntry uidEntry = mUidEntries.get(mSendUidsToObserver.valueAt(i));
                    if (uidEntry != null) {
                        mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
                                uidEntry.getCallStatsList());
                    }
                }
                mSendUidsToObserver.clear();
            }
        }
    };

    /** Injector for {@link BinderCallsStats}. */
    public static class Injector {
@@ -103,6 +133,10 @@ public class BinderCallsStats implements BinderInternal.Observer {
            return new Random();
        }

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

        public BinderLatencyObserver getLatencyObserver() {
            return new BinderLatencyObserver(new BinderLatencyObserver.Injector());
        }
@@ -110,6 +144,7 @@ public class BinderCallsStats implements BinderInternal.Observer {

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

@@ -121,6 +156,24 @@ public class BinderCallsStats implements BinderInternal.Observer {
        mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch();
    }

    /**
     * Registers an observer for call stats, which is invoked periodically with accumulated
     * binder call stats.
     */
    public void setCallStatsObserver(
            BinderInternal.CallStatsObserver callStatsObserver) {
        mCallStatsObserver = callStatsObserver;
        noteCallsStatsDelayed();
    }

    private void noteCallsStatsDelayed() {
        mCallStatsObserverHandler.removeCallbacks(mCallStatsObserverRunnable);
        if (mCallStatsObserver != null) {
            mCallStatsObserverHandler.postDelayed(mCallStatsObserverRunnable,
                    CALL_STATS_OBSERVER_DEBOUNCE_MILLIS);
        }
    }

    @Override
    @Nullable
    public CallSession callStarted(Binder binder, int code, int workSourceUid) {
@@ -233,6 +286,9 @@ public class BinderCallsStats implements BinderInternal.Observer {
                    callStat.callCount++;
                }
            }
            if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
                mSendUidsToObserver.add(workSourceUid);
            }
        }
    }

@@ -267,7 +323,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
    }

    @Nullable
    private Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) {
    private static Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) {
        try {
            return binder.getMethod("getDefaultTransactionName", int.class);
        } catch (NoSuchMethodException e) {
@@ -277,17 +333,18 @@ public class BinderCallsStats implements BinderInternal.Observer {
    }

    @Nullable
    private String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) {
        if (getDefaultTransactionName == null) {
            return null;
        }

    private static String resolveTransactionCode(Method getDefaultTransactionName,
            int transactionCode) {
        String resolvedCode = null;
        if (getDefaultTransactionName != null) {
            try {
            return (String) getDefaultTransactionName.invoke(null, transactionCode);
                resolvedCode = (String) getDefaultTransactionName.invoke(null, transactionCode);
            } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
                throw new RuntimeException(e);
            }
        }
        return resolvedCode == null ? String.valueOf(transactionCode) : resolvedCode;
    }

    /**
     * This method is expensive to call.
@@ -342,11 +399,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
                    || previous.transactionCode != exported.transactionCode;
            final String methodName;
            if (isClassDifferent || isCodeDifferent) {
                String resolvedCode = resolveTransactionCode(
                methodName = resolveTransactionCode(
                        getDefaultTransactionName, exported.transactionCode);
                methodName = resolvedCode == null
                        ? String.valueOf(exported.transactionCode)
                        : resolvedCode;
            } else {
                methodName = previousMethodName;
            }
@@ -657,6 +711,20 @@ public class BinderCallsStats implements BinderInternal.Observer {
            this.transactionCode = transactionCode;
            this.screenInteractive = screenInteractive;
        }

        @Override
        public String toString() {
            return "CallStat{"
                    + "callingUid=" + callingUid
                    + ", transaction=" + binderClass.getSimpleName()
                    + '.' + resolveTransactionCode(
                    getDefaultTransactionNameMethod(binderClass), transactionCode)
                    + ", callCount=" + callCount
                    + ", recordedCallCount=" + recordedCallCount
                    + ", cpuTimeMicros=" + cpuTimeMicros
                    + ", latencyMicros=" + latencyMicros
                    + '}';
        }
    }

    /** Key used to store CallStat object in a Map. */
+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,51 @@ public class BinderCallsStatsTest {
        assertEquals(1, callStats.recordedCallCount);
    }

    @Test
    public void testCallStatsObserver() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setSamplingInterval(1);
        bcs.setTrackScreenInteractive(false);

        final ArrayList<BinderCallsStats.CallStat> callStatsList = new ArrayList<>();
        bcs.setCallStatsObserver((workSourceUid, callStats) -> callStatsList.addAll(callStats));

        Binder binder = new Binder();

        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
        bcs.time += 10;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);

        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
        bcs.time += 20;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);

        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
        bcs.time += 30;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);

        for (Runnable runnable: mHandler.mRunnables) {
            // Execute all pending runnables. Ignore the delay.
            runnable.run();
        }

        assertThat(callStatsList).hasSize(2);
        for (int i = 0; i < 2; i++) {
            BinderCallsStats.CallStat callStats = callStatsList.get(i);
            if (callStats.transactionCode == 1) {
                assertEquals(2, callStats.callCount);
                assertEquals(2, callStats.recordedCallCount);
                assertEquals(30, callStats.cpuTimeMicros);
                assertEquals(20, callStats.maxCpuTimeMicros);
            } else {
                assertEquals(1, callStats.callCount);
                assertEquals(1, callStats.recordedCallCount);
                assertEquals(30, callStats.cpuTimeMicros);
                assertEquals(30, callStats.maxCpuTimeMicros);
            }
        }
    }

    @Test
    public void testLatencyCollectionEnabled() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
@@ -781,6 +833,20 @@ public class BinderCallsStatsTest {
        assertEquals(0, bcs.getLatencyObserver().getLatencyHistograms().size());
    }

    private static class TestHandler extends Handler {
        ArrayList<Runnable> mRunnables = new ArrayList<>();

        TestHandler() {
            super(Looper.getMainLooper());
        }

        @Override
        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            mRunnables.add(msg.getCallback());
            return true;
        }
    }

    class TestBinderCallsStats extends BinderCallsStats {
        public int callingUid = CALLING_UID;
        public long time = 1234;
@@ -803,6 +869,10 @@ public class BinderCallsStatsTest {
                    };
                }

                public Handler getHandler() {
                    return mHandler;
                }

                public BinderLatencyObserver getLatencyObserver() {
                    return new BinderLatencyObserverTest.TestBinderLatencyObserver();
                }
+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