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

Commit 561a8d9d authored by Mike Ma's avatar Mike Ma
Browse files

Record Bluetooth Cumulative Stats Properly

updateBluetoothStateLocked does not check for null values, causing
NPE. This patch reverts part of the change in 3445570. Instead, an
update method is added to LongSamplingCounter, which accepts
cumulative values.
Fixes: 75963520
Test: LongSamplingCounterTest

Change-Id: I89eba8e20f8f055c60f4ff250653345a22536189
parent 31d58db7
Loading
Loading
Loading
Loading
+100 −75
Original line number Diff line number Diff line
@@ -137,7 +137,7 @@ public class BatteryStatsImpl extends BatteryStats {
    private static final int MAGIC = 0xBA757475; // 'BATSTATS'
    // Current on-disk Parcel version
    private static final int VERSION = 176 + (USE_OLD_HISTORY ? 1000 : 0);
    private static final int VERSION = 177 + (USE_OLD_HISTORY ? 1000 : 0);
    // Maximum number of items we will record in the history.
    private static final int MAX_HISTORY_ITEMS;
@@ -1550,27 +1550,31 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    @VisibleForTesting
    public static class LongSamplingCounter extends LongCounter implements TimeBaseObs {
        final TimeBase mTimeBase;
        long mCount;
        long mLoadedCount;
        long mUnpluggedCount;
        public long mCount;
        public long mCurrentCount;
        public long mLoadedCount;
        public long mUnpluggedCount;
        LongSamplingCounter(TimeBase timeBase, Parcel in) {
        public LongSamplingCounter(TimeBase timeBase, Parcel in) {
            mTimeBase = timeBase;
            mCount = in.readLong();
            mCurrentCount = in.readLong();
            mLoadedCount = in.readLong();
            mUnpluggedCount = in.readLong();
            timeBase.add(this);
        }
        LongSamplingCounter(TimeBase timeBase) {
        public LongSamplingCounter(TimeBase timeBase) {
            mTimeBase = timeBase;
            timeBase.add(this);
        }
        public void writeToParcel(Parcel out) {
            out.writeLong(mCount);
            out.writeLong(mCurrentCount);
            out.writeLong(mLoadedCount);
            out.writeLong(mUnpluggedCount);
        }
@@ -1597,24 +1601,37 @@ public class BatteryStatsImpl extends BatteryStats {
        @Override
        public void logState(Printer pw, String prefix) {
            pw.println(prefix + "mCount=" + mCount
                    + " mCurrentCount=" + mCurrentCount
                    + " mLoadedCount=" + mLoadedCount
                    + " mUnpluggedCount=" + mUnpluggedCount);
        }
        void addCountLocked(long count) {
            addCountLocked(count, mTimeBase.isRunning());
        public void addCountLocked(long count) {
            update(mCurrentCount + count, mTimeBase.isRunning());
        }
        public void addCountLocked(long count, boolean isRunning) {
            update(mCurrentCount + count, isRunning);
        }
        public void update(long count) {
            update(count, mTimeBase.isRunning());
        }
        void addCountLocked(long count, boolean isRunning) {
        public void update(long count, boolean isRunning) {
            if (count < mCurrentCount) {
                mCurrentCount = 0;
            }
            if (isRunning) {
                mCount += count;
                mCount += count - mCurrentCount;
            }
            mCurrentCount = count;
        }
        /**
         * Clear state of this counter.
         */
        void reset(boolean detachIfReset) {
        public void reset(boolean detachIfReset) {
            mCount = 0;
            mLoadedCount = mUnpluggedCount = 0;
            if (detachIfReset) {
@@ -1622,18 +1639,16 @@ public class BatteryStatsImpl extends BatteryStats {
            }
        }
        void detach() {
        public void detach() {
            mTimeBase.remove(this);
        }
        void writeSummaryFromParcelLocked(Parcel out) {
        public void writeSummaryFromParcelLocked(Parcel out) {
            out.writeLong(mCount);
        }
        void readSummaryFromParcelLocked(Parcel in) {
            mLoadedCount = in.readLong();
            mCount = mLoadedCount;
            mUnpluggedCount = mLoadedCount;
        public void readSummaryFromParcelLocked(Parcel in) {
            mCount = mUnpluggedCount= mLoadedCount = in.readLong();
        }
    }
@@ -11600,10 +11615,6 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    // Cache last value for comparison.
    private BluetoothActivityEnergyInfo mLastBluetoothActivityEnergyInfo =
            new BluetoothActivityEnergyInfo(0, 0, 0, 0, 0, 0);
    /**
     * Add modem tx power to history
     * Device is said to be in high cellular transmit power when it has spent most of the transmit
@@ -11642,8 +11653,35 @@ public class BatteryStatsImpl extends BatteryStats {
        return;
    }
    private final class BluetoothActivityInfoCache {
        long idleTimeMs;
        long rxTimeMs;
        long txTimeMs;
        long energy;
        SparseLongArray uidRxBytes = new SparseLongArray();
        SparseLongArray uidTxBytes = new SparseLongArray();
        void set(BluetoothActivityEnergyInfo info) {
            idleTimeMs = info.getControllerIdleTimeMillis();
            rxTimeMs = info.getControllerRxTimeMillis();
            txTimeMs = info.getControllerTxTimeMillis();
            energy = info.getControllerEnergyUsed();
            if (info.getUidTraffic() != null) {
                for (UidTraffic traffic : info.getUidTraffic()) {
                    uidRxBytes.put(traffic.getUid(), traffic.getRxBytes());
                    uidTxBytes.put(traffic.getUid(), traffic.getTxBytes());
                }
            }
        }
    }
    private final BluetoothActivityInfoCache mLastBluetoothActivityInfo
            = new BluetoothActivityInfoCache();
    /**
     * Distribute Bluetooth energy info and network traffic to apps.
     *
     * @param info The energy information from the bluetooth controller.
     */
    public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
@@ -11658,12 +11696,13 @@ public class BatteryStatsImpl extends BatteryStats {
        mHasBluetoothReporting = true;
        final long elapsedRealtimeMs = mClocks.elapsedRealtime();
        final long rxTimeMs = info.getControllerRxTimeMillis() -
                mLastBluetoothActivityEnergyInfo.getControllerRxTimeMillis();
        final long txTimeMs = info.getControllerTxTimeMillis() -
                mLastBluetoothActivityEnergyInfo.getControllerTxTimeMillis();
        final long idleTimeMs = info.getControllerIdleTimeMillis() -
                mLastBluetoothActivityEnergyInfo.getControllerIdleTimeMillis();
        final long rxTimeMs =
                info.getControllerRxTimeMillis() - mLastBluetoothActivityInfo.rxTimeMs;
        final long txTimeMs =
                info.getControllerTxTimeMillis() - mLastBluetoothActivityInfo.txTimeMs;
        final long idleTimeMs =
                info.getControllerIdleTimeMillis() - mLastBluetoothActivityInfo.idleTimeMs;
        if (DEBUG_ENERGY) {
            Slog.d(TAG, "------ BEGIN BLE power blaming ------");
            Slog.d(TAG, "  Tx Time:    " + txTimeMs + " ms");
@@ -11735,8 +11774,8 @@ public class BatteryStatsImpl extends BatteryStats {
        }
        if (DEBUG_ENERGY) {
            Slog.d(TAG, "Left over time for traffic RX=" + leftOverRxTimeMs
                    + " TX=" + leftOverTxTimeMs);
            Slog.d(TAG, "Left over time for traffic RX=" + leftOverRxTimeMs + " TX="
                    + leftOverTxTimeMs);
        }
        //
@@ -11747,70 +11786,56 @@ public class BatteryStatsImpl extends BatteryStats {
        long totalRxBytes = 0;
        final UidTraffic[] uidTraffic = info.getUidTraffic();
        final UidTraffic[] lastUidTraffic = mLastBluetoothActivityEnergyInfo.getUidTraffic();
        final ArrayList<UidTraffic> deltaTraffic = new ArrayList<>();
        int m = 0, n = 0;
        for (; m < uidTraffic.length && n < lastUidTraffic.length; m++) {
            final UidTraffic traffic = uidTraffic[m];
            final UidTraffic lastTraffic = lastUidTraffic[n];
            if (traffic.getUid() == lastTraffic.getUid()) {
                deltaTraffic.add(new UidTraffic(traffic.getUid(),
                        traffic.getRxBytes() - lastTraffic.getRxBytes(),
                        traffic.getTxBytes() - lastTraffic.getTxBytes()));
                n++;
            }
        }
        for (; m < uidTraffic.length; m ++) {
            deltaTraffic.add(uidTraffic[m]);
        }
        for (int i = 0, j = 0; i < deltaTraffic.size(); i++) {
            final UidTraffic traffic = deltaTraffic.get(i);
        final int numUids = uidTraffic != null ? uidTraffic.length : 0;
        for (int i = 0; i < numUids; i++) {
            final UidTraffic traffic = uidTraffic[i];
            final long rxBytes = traffic.getRxBytes() - mLastBluetoothActivityInfo.uidRxBytes.get(
                    traffic.getUid());
            final long txBytes = traffic.getTxBytes() - mLastBluetoothActivityInfo.uidTxBytes.get(
                    traffic.getUid());
            // Add to the global counters.
            mNetworkByteActivityCounters[NETWORK_BT_RX_DATA].addCountLocked(
                    traffic.getRxBytes());
            mNetworkByteActivityCounters[NETWORK_BT_TX_DATA].addCountLocked(
                    traffic.getTxBytes());
            mNetworkByteActivityCounters[NETWORK_BT_RX_DATA].addCountLocked(rxBytes);
            mNetworkByteActivityCounters[NETWORK_BT_TX_DATA].addCountLocked(txBytes);
            // Add to the UID counters.
            final Uid u = getUidStatsLocked(mapUid(traffic.getUid()));
            u.noteNetworkActivityLocked(NETWORK_BT_RX_DATA, traffic.getRxBytes(), 0);
            u.noteNetworkActivityLocked(NETWORK_BT_TX_DATA, traffic.getTxBytes(), 0);
            u.noteNetworkActivityLocked(NETWORK_BT_RX_DATA, rxBytes, 0);
            u.noteNetworkActivityLocked(NETWORK_BT_TX_DATA, txBytes, 0);
            // Calculate the total traffic.
            totalTxBytes += traffic.getTxBytes();
            totalRxBytes += traffic.getRxBytes();
            totalRxBytes += rxBytes;
            totalTxBytes += txBytes;
        }
        if ((totalTxBytes != 0 || totalRxBytes != 0) &&
                (leftOverRxTimeMs != 0 || leftOverTxTimeMs != 0)) {
            for (int i = 0; i < deltaTraffic.size(); i++) {
                final UidTraffic traffic = deltaTraffic.get(i);
                final Uid u = getUidStatsLocked(mapUid(traffic.getUid()));
        if ((totalTxBytes != 0 || totalRxBytes != 0) && (leftOverRxTimeMs != 0
                || leftOverTxTimeMs != 0)) {
            for (int i = 0; i < numUids; i++) {
                final UidTraffic traffic = uidTraffic[i];
                final int uid = traffic.getUid();
                final long rxBytes =
                        traffic.getRxBytes() - mLastBluetoothActivityInfo.uidRxBytes.get(uid);
                final long txBytes =
                        traffic.getTxBytes() - mLastBluetoothActivityInfo.uidTxBytes.get(uid);
                final Uid u = getUidStatsLocked(mapUid(uid));
                final ControllerActivityCounterImpl counter =
                        u.getOrCreateBluetoothControllerActivityLocked();
                if (totalRxBytes > 0 && traffic.getRxBytes() > 0) {
                    final long timeRxMs = (leftOverRxTimeMs * traffic.getRxBytes()) / totalRxBytes;
                if (totalRxBytes > 0 && rxBytes > 0) {
                    final long timeRxMs = (leftOverRxTimeMs * rxBytes) / totalRxBytes;
                    if (DEBUG_ENERGY) {
                        Slog.d(TAG, "UID=" + traffic.getUid() + " rx_bytes=" + traffic.getRxBytes()
                                + " rx_time=" + timeRxMs);
                        Slog.d(TAG, "UID=" + uid + " rx_bytes=" + rxBytes + " rx_time=" + timeRxMs);
                    }
                    counter.getRxTimeCounter().addCountLocked(timeRxMs);
                    leftOverRxTimeMs -= timeRxMs;
                }
                if (totalTxBytes > 0 && traffic.getTxBytes() > 0) {
                    final long timeTxMs = (leftOverTxTimeMs * traffic.getTxBytes()) / totalTxBytes;
                if (totalTxBytes > 0 && txBytes > 0) {
                    final long timeTxMs = (leftOverTxTimeMs * txBytes) / totalTxBytes;
                    if (DEBUG_ENERGY) {
                        Slog.d(TAG, "UID=" + traffic.getUid() + " tx_bytes=" + traffic.getTxBytes()
                                + " tx_time=" + timeTxMs);
                        Slog.d(TAG, "UID=" + uid + " tx_bytes=" + txBytes + " tx_time=" + timeTxMs);
                    }
                    counter.getTxTimeCounters()[0].addCountLocked(timeTxMs);
                    leftOverTxTimeMs -= timeTxMs;
                }
@@ -11827,10 +11852,10 @@ public class BatteryStatsImpl extends BatteryStats {
        if (opVolt != 0) {
            // We store the power drain as mAms.
            mBluetoothActivity.getPowerCounter().addCountLocked(
                    (long) ((info.getControllerEnergyUsed() -
                            mLastBluetoothActivityEnergyInfo.getControllerEnergyUsed() )/ opVolt));
                    (long) ((info.getControllerEnergyUsed() - mLastBluetoothActivityInfo.energy)
                            / opVolt));
        }
        mLastBluetoothActivityEnergyInfo = info;
        mLastBluetoothActivityInfo.set(info);
    }
    /**
+1 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import org.junit.runners.Suite;
        KernelUidCpuActiveTimeReaderTest.class,
        KernelUidCpuClusterTimeReaderTest.class,
        KernelWakelockReaderTest.class,
        LongSamplingCounterTest.class,
        LongSamplingCounterArrayTest.class,
        PowerCalculatorTest.class,
        PowerProfileTest.class
+194 −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 static android.os.BatteryStats.STATS_SINCE_CHARGED;

import static com.android.internal.os.BatteryStatsImpl.LongSamplingCounter;
import static com.android.internal.os.BatteryStatsImpl.TimeBase;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

/**
 * Test class for {@link LongSamplingCounter}.
 *
 * To run the tests, use
 *
 * bit FrameworksCoreTests:com.android.internal.os.LongSamplingCounterTest
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LongSamplingCounterTest {

    private static final long COUNT = 1111;
    private static final long CURRENT_COUNT = 5555;

    @Mock
    private TimeBase mTimeBase;
    private LongSamplingCounter mCounter;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mCounter = new LongSamplingCounter(mTimeBase);
        Mockito.reset(mTimeBase);
    }

    @Test
    public void testReadWriteParcel() {
        final Parcel parcel = Parcel.obtain();
        updateCounts(COUNT, CURRENT_COUNT);
        mCounter.writeToParcel(parcel);
        parcel.setDataPosition(0);

        // Now clear counterArray and verify values are read from parcel correctly.
        updateCounts(0, 0);

        mCounter = new LongSamplingCounter(mTimeBase, parcel);
        assertEquals(COUNT, mCounter.mCount);
        assertEquals(CURRENT_COUNT, mCounter.mCurrentCount);
        parcel.recycle();
    }

    @Test
    public void testReadWriteSummaryParcel() {
        final Parcel parcel = Parcel.obtain();
        updateCounts(COUNT, CURRENT_COUNT);
        mCounter.writeSummaryFromParcelLocked(parcel);
        parcel.setDataPosition(0);

        // Now clear counterArray and verify values are read from parcel correctly.
        updateCounts(0, 0);

        mCounter.readSummaryFromParcelLocked(parcel);
        assertEquals(COUNT, mCounter.mCount);
        parcel.recycle();
    }

    @Test
    public void testOnTimeStarted() {
        updateCounts(COUNT, CURRENT_COUNT);
        mCounter.onTimeStarted(0, 0, 0);
        assertEquals(COUNT, mCounter.mCount);
        assertEquals(COUNT, mCounter.mUnpluggedCount);
    }

    @Test
    public void testOnTimeStopped() {
        updateCounts(COUNT, CURRENT_COUNT);
        mCounter.onTimeStopped(0, 0, 0);
        assertEquals(COUNT, mCounter.mCount);
    }

    @Test
    public void testAddCountLocked() {
        updateCounts(0, 0);
        assertEquals(0, mCounter.getCountLocked(0));
        when(mTimeBase.isRunning()).thenReturn(true);
        mCounter.addCountLocked(111);
        assertEquals(111, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(111, mCounter.mCurrentCount);
        mCounter.addCountLocked(222);
        assertEquals(333, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(333, mCounter.mCurrentCount);

        when(mTimeBase.isRunning()).thenReturn(false);
        mCounter.addCountLocked(456);
        assertEquals(333, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(789, mCounter.mCurrentCount);

        mCounter.addCountLocked(444, true);
        assertEquals(777, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(1233, mCounter.mCurrentCount);
        mCounter.addCountLocked(567, false);
        assertEquals(777, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(1800, mCounter.mCurrentCount);
    }

    @Test
    public void testUpdate() {
        updateCounts(0, 0);
        assertEquals(0, mCounter.getCountLocked(0));
        when(mTimeBase.isRunning()).thenReturn(true);
        mCounter.update(111);
        assertEquals(111, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(111, mCounter.mCurrentCount);
        mCounter.update(333);
        assertEquals(333, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(333, mCounter.mCurrentCount);

        when(mTimeBase.isRunning()).thenReturn(false);
        mCounter.update(789);
        assertEquals(333, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(789, mCounter.mCurrentCount);
        mCounter.update(100);
        assertEquals(333, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(100, mCounter.mCurrentCount);

        mCounter.update(544, true);
        assertEquals(777, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(544, mCounter.mCurrentCount);
        mCounter.update(1544, false);
        assertEquals(777, mCounter.getCountLocked(STATS_SINCE_CHARGED));
        assertEquals(1544, mCounter.mCurrentCount);
    }

    @Test
    public void testReset() {
        updateCounts(COUNT, CURRENT_COUNT);
        // Test with detachIfReset=false
        mCounter.reset(false /* detachIfReset */);
        assertEquals(0, mCounter.mCount);
        assertEquals(CURRENT_COUNT, mCounter.mCurrentCount);
        verifyZeroInteractions(mTimeBase);

        updateCounts(COUNT, CURRENT_COUNT);
        // Test with detachIfReset=true
        mCounter.reset(true /* detachIfReset */);
        assertEquals(0, mCounter.mCount);
        assertEquals(CURRENT_COUNT, mCounter.mCurrentCount);
        verify(mTimeBase).remove(mCounter);
        verifyNoMoreInteractions(mTimeBase);
    }

    @Test
    public void testDetach() {
        mCounter.detach();
        verify(mTimeBase).remove(mCounter);
        verifyNoMoreInteractions(mTimeBase);
    }

    private void updateCounts(long total, long current) {
        mCounter.mCount = total;
        mCounter.mCurrentCount = current;
    }
}
+25 −21
Original line number Diff line number Diff line
@@ -283,6 +283,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
                mUseLatestStates = true;
            }

            try {
                synchronized (mWorkerLock) {
                    if (DEBUG) {
                        Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
@@ -308,6 +309,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
                    }
                    mStats.clearPendingRemovedUids();
                }
            } catch (Exception e) {
                Slog.wtf(TAG, "Error updating external stats: ", e);
            }
        }
    };

@@ -398,7 +402,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
                if (bluetoothInfo.isValid()) {
                    mStats.updateBluetoothStateLocked(bluetoothInfo);
                } else {
                    Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
                    Slog.w(TAG, "bluetooth info is invalid: " + bluetoothInfo);
                }
            }
        }
@@ -410,7 +414,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
            if (wifiInfo.isValid()) {
                mStats.updateWifiState(extractDeltaLocked(wifiInfo));
            } else {
                Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
                Slog.w(TAG, "wifi info is invalid: " + wifiInfo);
            }
        }

@@ -418,7 +422,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
            if (modemInfo.isValid()) {
                mStats.updateMobileRadioState(modemInfo);
            } else {
                Slog.e(TAG, "modem info is invalid: " + modemInfo);
                Slog.w(TAG, "modem info is invalid: " + modemInfo);
            }
        }
    }