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
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 06230c1..674fe85 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -137,7 +137,7 @@
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 @@
}
}
+ @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 @@
@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());
}
- void addCountLocked(long count, boolean isRunning) {
- if (isRunning) {
- mCount += count;
+ public void addCountLocked(long count, boolean isRunning) {
+ update(mCurrentCount + count, isRunning);
+ }
+
+ public void update(long count) {
+ update(count, mTimeBase.isRunning());
+ }
+
+ public void update(long count, boolean isRunning) {
+ if (count < mCurrentCount) {
+ mCurrentCount = 0;
}
+ if (isRunning) {
+ 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 @@
}
}
- 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 @@
}
}
- // 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 @@
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 @@
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 @@
}
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 @@
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);
+ 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(traffic.getUid()));
+ 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 @@
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);
}
/**
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index f5fe80c..94492ba 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -43,6 +43,7 @@
KernelUidCpuActiveTimeReaderTest.class,
KernelUidCpuClusterTimeReaderTest.class,
KernelWakelockReaderTest.class,
+ LongSamplingCounterTest.class,
LongSamplingCounterArrayTest.class,
PowerCalculatorTest.class,
PowerProfileTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterTest.java
new file mode 100644
index 0000000..853bf8a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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;
+ }
+}