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

Commit c80c81a5 authored by Marcin Oczeretko's avatar Marcin Oczeretko
Browse files

Extract CachedDeviceState from BinderCallsStats

Add a service that tracks the device state properties which are
interesting to System Server telemetry services. Allows the services to
share this code and have consistent state information.

Test: Unit tests and manually tested
Change-Id: Ia5c78c45a55414a0c5c46202db2a37283b50a703
parent 3680ae62
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -23,8 +23,7 @@ import android.support.test.runner.AndroidJUnit4;

import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal.CallSession;

import java.util.Random;
import com.android.internal.os.CachedDeviceState;

import org.junit.After;
import org.junit.Before;
@@ -32,8 +31,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertNull;


/**
 * Performance tests for {@link BinderCallsStats}
@@ -49,6 +46,8 @@ public class BinderCallsStatsPerfTest {
    @Before
    public void setUp() {
        mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
        CachedDeviceState deviceState = new CachedDeviceState(false, false);
        mBinderCallsStats.setDeviceState(deviceState.getReadonlyClient());
    }

    @After
+18 −99
Original line number Diff line number Diff line
@@ -16,16 +16,9 @@

package com.android.internal.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.OsProtoEnums;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateFormat;
@@ -37,7 +30,6 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BinderInternal.CallSession;
import com.android.server.LocalServices;

import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
@@ -63,7 +55,6 @@ public class BinderCallsStats implements BinderInternal.Observer {

    private static final String TAG = "BinderCallsStats";
    private static final int CALL_SESSIONS_POOL_SIZE = 100;
    private static final int PERIODIC_SAMPLING_INTERVAL = 10;
    private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
    private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";

@@ -81,25 +72,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
    private final Random mRandom;
    private long mStartTime = System.currentTimeMillis();

    // State updated by the broadcast receiver below.
    private boolean mScreenInteractive;
    private boolean mCharging;
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case Intent.ACTION_BATTERY_CHANGED:
                    mCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
                    break;
                case Intent.ACTION_SCREEN_ON:
                    mScreenInteractive = true;
                    break;
                case Intent.ACTION_SCREEN_OFF:
                    mScreenInteractive = false;
                    break;
            }
        }
    };
    private CachedDeviceState.Readonly mDeviceState;

    /** Injector for {@link BinderCallsStats}. */
    public static class Injector {
@@ -112,65 +85,14 @@ public class BinderCallsStats implements BinderInternal.Observer {
        this.mRandom = injector.getRandomGenerator();
    }

    public void systemReady(Context context) {
        registerBroadcastReceiver(context);
        setInitialState(queryScreenInteractive(context), queryIsCharging());
    }

    /**
     * Listens for screen/battery state changes.
     */
    @VisibleForTesting
    public void registerBroadcastReceiver(Context context) {
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        context.registerReceiver(mBroadcastReceiver, filter);
    }

    /**
     * Sets the battery/screen initial state.
     *
     * This has to be updated *after* the broadcast receiver is installed.
     */
    @VisibleForTesting
    public void setInitialState(boolean isScreenInteractive, boolean isCharging) {
        this.mScreenInteractive = isScreenInteractive;
        this.mCharging = isCharging;
        // Data collected previously was not accurate since the battery/screen state was not set.
        reset();
    }

    private boolean queryIsCharging() {
        final BatteryManagerInternal batteryManager =
                LocalServices.getService(BatteryManagerInternal.class);
        if (batteryManager == null) {
            Slog.wtf(TAG, "BatteryManager null while starting BinderCallsStatsService");
            // Default to true to not collect any data.
            return true;
        } else {
            return batteryManager.getPlugType() != OsProtoEnums.BATTERY_PLUGGED_NONE;
        }
    }

    private boolean queryScreenInteractive(Context context) {
        final PowerManager powerManager = context.getSystemService(PowerManager.class);
        final boolean screenInteractive;
        if (powerManager == null) {
            Slog.wtf(TAG, "PowerManager null while starting BinderCallsStatsService",
                    new Throwable());
            return true;
        } else {
            return powerManager.isInteractive();
        }
    public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
        mDeviceState = deviceState;
    }

    @Override
    @Nullable
    public CallSession callStarted(Binder binder, int code) {
        if (mCharging) {
        if (mDeviceState == null || mDeviceState.isCharging()) {
            return null;
        }

@@ -221,7 +143,7 @@ public class BinderCallsStats implements BinderInternal.Observer {

        synchronized (mLock) {
            // This was already checked in #callStart but check again while synchronized.
            if (mCharging) {
            if (mDeviceState == null || mDeviceState.isCharging()) {
                return;
            }

@@ -233,7 +155,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
                uidEntry.recordedCallCount++;

                final CallStat callStat = uidEntry.getOrCreate(
                        s.binderClass, s.transactionCode, mScreenInteractive);
                        s.binderClass, s.transactionCode, mDeviceState.isScreenInteractive());
                callStat.callCount++;
                callStat.recordedCallCount++;
                callStat.cpuTimeMicros += duration;
@@ -252,7 +174,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
                // Only record the total call count if we already track data for this key.
                // It helps to keep the memory usage down when sampling is enabled.
                final CallStat callStat = uidEntry.get(
                        s.binderClass, s.transactionCode, mScreenInteractive);
                        s.binderClass, s.transactionCode, mDeviceState.isScreenInteractive());
                if (callStat != null) {
                    callStat.callCount++;
                }
@@ -319,7 +241,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
    public ArrayList<ExportedCallStat> getExportedCallStats() {
        // We do not collect all the data if detailed tracking is off.
        if (!mDetailedTracking) {
            return new ArrayList<ExportedCallStat>();
            return new ArrayList<>();
        }

        ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
@@ -387,13 +309,15 @@ public class BinderCallsStats implements BinderInternal.Observer {
        }
    }

    /** Writes the collected statistics to the supplied {@link PrintWriter}.*/
    public void dump(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap, boolean verbose) {
        synchronized (mLock) {
            dumpLocked(pw, appIdToPkgNameMap, verbose);
        }
    }

    private void dumpLocked(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
    private void dumpLocked(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap,
            boolean verbose) {
        long totalCallsCount = 0;
        long totalRecordedCallsCount = 0;
        long totalCpuTime = 0;
@@ -723,11 +647,6 @@ public class BinderCallsStats implements BinderInternal.Observer {
        return result;
    }

    @VisibleForTesting
    public BroadcastReceiver getBroadcastReceiver() {
        return mBroadcastReceiver;
    }

    private static int compareByCpuDesc(
            ExportedCallStat a, ExportedCallStat b) {
        return Long.compare(b.cpuTimeMicros, a.cpuTimeMicros);
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 com.android.internal.annotations.VisibleForTesting;

/**
 * Stores the device state (e.g. charging/on battery, screen on/off) to be shared with
 * the System Server telemetry services.
 *
 * @hide
 */
public class CachedDeviceState {
    private volatile boolean mScreenInteractive;
    private volatile boolean mCharging;

    public CachedDeviceState() {
        mCharging = true;
        mScreenInteractive = false;
    }

    @VisibleForTesting
    public CachedDeviceState(boolean isCharging, boolean isScreenInteractive) {
        mCharging = isCharging;
        mScreenInteractive = isScreenInteractive;
    }

    public void setScreenInteractive(boolean screenInteractive) {
        mScreenInteractive = screenInteractive;
    }

    public void setCharging(boolean charging) {
        mCharging = charging;
    }

    public Readonly getReadonlyClient() {
        return new CachedDeviceState.Readonly();
    }

    /**
     * Allows for only a readonly access to the device state.
     */
    public class Readonly {
        public boolean isCharging() {
            return mCharging;
        }

        public boolean isScreenInteractive() {
            return mScreenInteractive;
        }
    }
}
+37 −12
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ public class LooperStats implements Looper.Observer {
    private static final int TOKEN_POOL_SIZE = 50;

    @GuardedBy("mLock")
    private final SparseArray<Entry> mEntries = new SparseArray<>(256);
    private final SparseArray<Entry> mEntries = new SparseArray<>(512);
    private final Object mLock = new Object();
    private final Entry mOverflowEntry = new Entry("OVERFLOW");
    private final Entry mHashCollisionEntry = new Entry("HASH_COLLISION");
@@ -47,15 +47,20 @@ public class LooperStats implements Looper.Observer {
            new ConcurrentLinkedQueue<>();
    private final int mEntriesSizeCap;
    private int mSamplingInterval;
    private CachedDeviceState.Readonly mDeviceState;

    public LooperStats(int samplingInterval, int entriesSizeCap) {
        this.mSamplingInterval = samplingInterval;
        this.mEntriesSizeCap = entriesSizeCap;
    }

    public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
        mDeviceState = deviceState;
    }

    @Override
    public Object messageDispatchStarting() {
        if (shouldCollectDetailedData()) {
        if (deviceStateAllowsCollection() && shouldCollectDetailedData()) {
            DispatchSession session = mSessionPool.poll();
            session = session == null ? new DispatchSession() : session;
            session.startTimeMicro = getElapsedRealtimeMicro();
@@ -68,6 +73,10 @@ public class LooperStats implements Looper.Observer {

    @Override
    public void messageDispatched(Object token, Message msg) {
        if (!deviceStateAllowsCollection()) {
            return;
        }

        DispatchSession session = (DispatchSession) token;
        Entry entry = getOrCreateEntry(msg);
        synchronized (entry) {
@@ -88,6 +97,10 @@ public class LooperStats implements Looper.Observer {

    @Override
    public void dispatchingThrewException(Object token, Message msg, Exception exception) {
        if (!deviceStateAllowsCollection()) {
            return;
        }

        DispatchSession session = (DispatchSession) token;
        Entry entry = getOrCreateEntry(msg);
        synchronized (entry) {
@@ -96,6 +109,11 @@ public class LooperStats implements Looper.Observer {
        recycleSession(session);
    }

    private boolean deviceStateAllowsCollection() {
        // Do not collect data if on charger or the state is not set.
        return mDeviceState != null && !mDeviceState.isCharging();
    }

    /** Returns an array of {@link ExportedEntry entries} with the aggregated statistics. */
    public List<ExportedEntry> getEntries() {
        final ArrayList<ExportedEntry> entries;
@@ -142,7 +160,8 @@ public class LooperStats implements Looper.Observer {

    @NonNull
    private Entry getOrCreateEntry(Message msg) {
        final int id = Entry.idFor(msg);
        final boolean isInteractive = mDeviceState.isScreenInteractive();
        final int id = Entry.idFor(msg, isInteractive);
        Entry entry;
        synchronized (mLock) {
            entry = mEntries.get(id);
@@ -151,14 +170,14 @@ public class LooperStats implements Looper.Observer {
                    // If over the size cap, track totals under a single entry.
                    return mOverflowEntry;
                }
                entry = new Entry(msg);
                entry = new Entry(msg, isInteractive);
                mEntries.put(id, entry);
            }
        }

        if (entry.handler.getClass() != msg.getTarget().getClass()
                || entry.handler.getLooper().getThread()
                != msg.getTarget().getLooper().getThread()) {
                || entry.handler.getLooper().getThread() != msg.getTarget().getLooper().getThread()
                || entry.isInteractive != isInteractive) {
            // If a hash collision happened, track totals under a single entry.
            return mHashCollisionEntry;
        }
@@ -192,6 +211,7 @@ public class LooperStats implements Looper.Observer {
    private static class Entry {
        public final Handler handler;
        public final String messageName;
        public final boolean isInteractive;
        public long messageCount;
        public long recordedMessageCount;
        public long exceptionCount;
@@ -200,14 +220,16 @@ public class LooperStats implements Looper.Observer {
        public long cpuUsageMicro;
        public long maxCpuUsageMicro;

        Entry(Message msg) {
            handler = msg.getTarget();
            messageName = handler.getMessageName(msg);
        Entry(Message msg, boolean isInteractive) {
            this.handler = msg.getTarget();
            this.messageName = handler.getMessageName(msg);
            this.isInteractive = isInteractive;
        }

        Entry(String specialEntryName) {
            handler = null;
            messageName = specialEntryName;
            this.messageName = specialEntryName;
            this.handler = null;
            this.isInteractive = false;
        }

        void reset() {
@@ -220,10 +242,11 @@ public class LooperStats implements Looper.Observer {
            maxCpuUsageMicro = 0;
        }

        static int idFor(Message msg) {
        static int idFor(Message msg, boolean isInteractive) {
            int result = 7;
            result = 31 * result + msg.getTarget().getLooper().getThread().hashCode();
            result = 31 * result + msg.getTarget().getClass().hashCode();
            result = 31 * result + (isInteractive ? 1231 : 1237);
            if (msg.getCallback() != null) {
                return 31 * result + msg.getCallback().getClass().hashCode();
            } else {
@@ -237,6 +260,7 @@ public class LooperStats implements Looper.Observer {
        public final String handlerClassName;
        public final String threadName;
        public final String messageName;
        public final boolean isInteractive;
        public final long messageCount;
        public final long recordedMessageCount;
        public final long exceptionCount;
@@ -254,6 +278,7 @@ public class LooperStats implements Looper.Observer {
                this.handlerClassName = "";
                this.threadName = "";
            }
            this.isInteractive = entry.isInteractive;
            this.messageName = entry.messageName;
            this.messageCount = entry.messageCount;
            this.recordedMessageCount = entry.recordedMessageCount;
+14 −35
Original line number Diff line number Diff line
@@ -18,10 +18,7 @@ package com.android.internal.os;

import static org.junit.Assert.assertEquals;

import android.content.Intent;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.OsProtoEnums;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -34,7 +31,6 @@ import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;


import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
@@ -50,6 +46,7 @@ public class BinderCallsStatsTest {
    private static final int TEST_UID = 1;
    private static final int REQUEST_SIZE = 2;
    private static final int REPLY_SIZE = 3;
    private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true);

    @Test
    public void testDetailedOff() {
@@ -388,43 +385,27 @@ public class BinderCallsStatsTest {
    }

    @Test
    public void testDataResetWhenInitialStateSet() {
    public void testNoDataCollectedBeforeInitialDeviceStateSet() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDeviceState(null);
        bcs.setDetailedTracking(true);
        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.time += 10;
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);

        bcs.setInitialState(true, true);
        bcs.setDeviceState(mDeviceState.getReadonlyClient());

        SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries();
        assertEquals(0, uidEntries.size());
    }

    @Test
    public void testScreenAndChargerInitialStates() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        Binder binder = new Binder();
        bcs.setInitialState(true /** screen iteractive */, false);

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

        List<BinderCallsStats.CallStat> callStatsList =
                new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList());
        assertEquals(true, callStatsList.get(0).screenInteractive);
    }

    @Test
    public void testNoDataCollectedOnCharger() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
                .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC);
        bcs.getBroadcastReceiver().onReceive(null, intent);
        mDeviceState.setCharging(true);

        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
@@ -436,7 +417,7 @@ public class BinderCallsStatsTest {
    public void testScreenOff() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_OFF));
        mDeviceState.setScreenInteractive(false);
        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
@@ -453,7 +434,7 @@ public class BinderCallsStatsTest {
    public void testScreenOn() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON));
        mDeviceState.setScreenInteractive(true);
        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
@@ -470,9 +451,8 @@ public class BinderCallsStatsTest {
    public void testOnCharger() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
                .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC);
        bcs.getBroadcastReceiver().onReceive(null, intent);
        mDeviceState.setCharging(true);

        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
@@ -484,9 +464,8 @@ public class BinderCallsStatsTest {
    public void testOnBattery() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED)
                .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_NONE);
        bcs.getBroadcastReceiver().onReceive(null, intent);
        mDeviceState.setCharging(false);

        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
@@ -522,7 +501,6 @@ public class BinderCallsStatsTest {
    public void testGetExportedStatsWhenDetailedTrackingEnabled() {
        TestBinderCallsStats bcs = new TestBinderCallsStats();
        bcs.setDetailedTracking(true);
        bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON));

        Binder binder = new Binder();
        CallSession callSession = bcs.callStarted(binder, 1);
@@ -561,7 +539,7 @@ public class BinderCallsStatsTest {
        assertEquals(0, bcs.getExceptionCounts().size());
    }

    static class TestBinderCallsStats extends BinderCallsStats {
    class TestBinderCallsStats extends BinderCallsStats {
        int callingUid = TEST_UID;
        long time = 1234;
        long elapsedTime = 0;
@@ -580,6 +558,7 @@ public class BinderCallsStatsTest {
                }
            });
            setSamplingInterval(1);
            setDeviceState(mDeviceState.getReadonlyClient());
        }

        @Override
Loading