diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index b9d3a30ecd952..25d5fae12191e 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -967,14 +967,15 @@ public class BatteryStatsImpl extends BatteryStats { } } + @VisibleForTesting public static class LongSamplingCounterArray extends LongCounterArray implements TimeBaseObs { final TimeBase mTimeBase; - long[] mCounts; - long[] mLoadedCounts; - long[] mUnpluggedCounts; - long[] mPluggedCounts; + public long[] mCounts; + public long[] mLoadedCounts; + public long[] mUnpluggedCounts; + public long[] mPluggedCounts; - LongSamplingCounterArray(TimeBase timeBase, Parcel in) { + private LongSamplingCounterArray(TimeBase timeBase, Parcel in) { mTimeBase = timeBase; mPluggedCounts = in.createLongArray(); mCounts = copyArray(mPluggedCounts, mCounts); @@ -983,12 +984,12 @@ public class BatteryStatsImpl extends BatteryStats { timeBase.add(this); } - LongSamplingCounterArray(TimeBase timeBase) { + public LongSamplingCounterArray(TimeBase timeBase) { mTimeBase = timeBase; timeBase.add(this); } - public void writeToParcel(Parcel out) { + private void writeToParcel(Parcel out) { out.writeLongArray(mCounts); out.writeLongArray(mLoadedCounts); out.writeLongArray(mUnpluggedCounts); @@ -1024,7 +1025,7 @@ public class BatteryStatsImpl extends BatteryStats { + " mPluggedCounts=" + Arrays.toString(mPluggedCounts)); } - void addCountLocked(long[] counts) { + public void addCountLocked(long[] counts) { if (counts == null) { return; } @@ -1039,7 +1040,7 @@ public class BatteryStatsImpl extends BatteryStats { /** * Clear state of this counter. */ - void reset(boolean detachIfReset) { + public void reset(boolean detachIfReset) { fillArray(mCounts, 0); fillArray(mLoadedCounts, 0); fillArray(mPluggedCounts, 0); @@ -1049,21 +1050,60 @@ public class BatteryStatsImpl extends BatteryStats { } } - void detach() { + public void detach() { mTimeBase.remove(this); } - void writeSummaryFromParcelLocked(Parcel out) { + private void writeSummaryToParcelLocked(Parcel out) { out.writeLongArray(mCounts); } - void readSummaryFromParcelLocked(Parcel in) { + private void readSummaryFromParcelLocked(Parcel in) { mCounts = in.createLongArray(); mLoadedCounts = copyArray(mCounts, mLoadedCounts); mUnpluggedCounts = copyArray(mCounts, mUnpluggedCounts); mPluggedCounts = copyArray(mCounts, mPluggedCounts); } + public static void writeToParcel(Parcel out, LongSamplingCounterArray counterArray) { + if (counterArray != null) { + out.writeInt(1); + counterArray.writeToParcel(out); + } else { + out.writeInt(0); + } + } + + public static LongSamplingCounterArray readFromParcel(Parcel in, TimeBase timeBase) { + if (in.readInt() != 0) { + return new LongSamplingCounterArray(timeBase, in); + } else { + return null; + } + } + + public static void writeSummaryToParcelLocked(Parcel out, + LongSamplingCounterArray counterArray) { + if (counterArray != null) { + out.writeInt(1); + counterArray.writeSummaryToParcelLocked(out); + } else { + out.writeInt(0); + } + } + + public static LongSamplingCounterArray readSummaryFromParcelLocked(Parcel in, + TimeBase timeBase) { + if (in.readInt() != 0) { + final LongSamplingCounterArray counterArray + = new LongSamplingCounterArray(timeBase); + counterArray.readSummaryFromParcelLocked(in); + return counterArray; + } else { + return null; + } + } + private void fillArray(long[] a, long val) { if (a != null) { Arrays.fill(a, val); @@ -6927,18 +6967,8 @@ public class BatteryStatsImpl extends BatteryStats { out.writeInt(0); } - if (mCpuFreqTimeMs != null) { - out.writeInt(1); - mCpuFreqTimeMs.writeToParcel(out); - } else { - out.writeInt(0); - } - if (mScreenOffCpuFreqTimeMs != null) { - out.writeInt(1); - mScreenOffCpuFreqTimeMs.writeToParcel(out); - } else { - out.writeInt(0); - } + LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs); + LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs); if (mMobileRadioApWakeupCount != null) { out.writeInt(1); @@ -7187,17 +7217,9 @@ public class BatteryStatsImpl extends BatteryStats { mCpuClusterSpeed = null; } - if (in.readInt() != 0) { - mCpuFreqTimeMs = new LongSamplingCounterArray(mBsi.mOnBatteryTimeBase, in); - } else { - mCpuFreqTimeMs = null; - } - if (in.readInt() != 0) { - mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray( - mBsi.mOnBatteryScreenOffTimeBase, in); - } else { - mScreenOffCpuFreqTimeMs = null; - } + mCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(in, mBsi.mOnBatteryTimeBase); + mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel( + in, mBsi.mOnBatteryScreenOffTimeBase); if (in.readInt() != 0) { mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in); @@ -11355,19 +11377,10 @@ public class BatteryStatsImpl extends BatteryStats { u.mCpuClusterSpeed = null; } - if (in.readInt() != 0) { - u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase); - u.mCpuFreqTimeMs.readSummaryFromParcelLocked(in); - } else { - u.mCpuFreqTimeMs = null; - } - if (in.readInt() != 0) { - u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray( - mOnBatteryScreenOffTimeBase); - u.mScreenOffCpuFreqTimeMs.readSummaryFromParcelLocked(in); - } else { - u.mScreenOffCpuFreqTimeMs = null; - } + u.mCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked( + in, mOnBatteryTimeBase); + u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked( + in, mOnBatteryScreenOffTimeBase); if (in.readInt() != 0) { u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase); @@ -11765,18 +11778,8 @@ public class BatteryStatsImpl extends BatteryStats { out.writeInt(0); } - if (u.mCpuFreqTimeMs != null) { - out.writeInt(1); - u.mCpuFreqTimeMs.writeSummaryFromParcelLocked(out); - } else { - out.writeInt(0); - } - if (u.mScreenOffCpuFreqTimeMs != null) { - out.writeInt(1); - u.mScreenOffCpuFreqTimeMs.writeSummaryFromParcelLocked(out); - } else { - out.writeInt(0); - } + LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs); + LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs); if (u.mMobileRadioApWakeupCount != null) { out.writeInt(1); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java index ad8221b470ae0..620acaecd0101 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java @@ -18,6 +18,7 @@ package com.android.internal.os; 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.support.test.filters.SmallTest; @@ -27,6 +28,7 @@ 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; import java.io.BufferedReader; @@ -70,18 +72,70 @@ public class KernelUidCpuFreqTimeReaderTest { times[i][j] = uids[i] * freqs[j] * 10; } } - final String[] uidsTimesLines = getUidTimesLines(uids, times); - final String[] lines = new String[uidsTimesLines.length + 1]; - System.arraycopy(uidsTimesLines, 0, lines, 0, uidsTimesLines.length); - lines[uidsTimesLines.length] = null; when(mBufferedReader.readLine()) - .thenReturn(getFreqsLine(freqs), lines); + .thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, times)); mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback); verify(mCallback).onCpuFreqs(freqs); for (int i = 0; i < uids.length; ++i) { verify(mCallback).onUidCpuFreqTime(uids[i], times[i]); } verifyNoMoreInteractions(mCallback); + + // Verify that a second call will only return deltas. + Mockito.reset(mCallback, mBufferedReader); + final long[][] newTimes1 = new long[uids.length][freqs.length]; + for (int i = 0; i < uids.length; ++i) { + for (int j = 0; j < freqs.length; ++j) { + newTimes1[i][j] = (times[i][j] + uids[i] + freqs[j]) * 10; + } + } + when(mBufferedReader.readLine()) + .thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, newTimes1)); + mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback); + verify(mCallback).onCpuFreqs(freqs); + for (int i = 0; i < uids.length; ++i) { + verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i])); + } + verifyNoMoreInteractions(mCallback); + + // Verify that calling with a null callback doesn't result in any crashes + Mockito.reset(mCallback, mBufferedReader); + final long[][] newTimes2 = new long[uids.length][freqs.length]; + for (int i = 0; i < uids.length; ++i) { + for (int j = 0; j < freqs.length; ++j) { + newTimes2[i][j] = (newTimes1[i][j] + uids[i] * freqs[j]) * 10; + } + } + when(mBufferedReader.readLine()) + .thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, newTimes2)); + mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, null); + verifyZeroInteractions(mCallback); + + // Verify that the readDelta call will only return deltas when + // the previous call had null callback. + Mockito.reset(mCallback, mBufferedReader); + final long[][] newTimes3 = new long[uids.length][freqs.length]; + for (int i = 0; i < uids.length; ++i) { + for (int j = 0; j < freqs.length; ++j) { + newTimes3[i][j] = (newTimes2[i][j] * (uids[i] + freqs[j])) * 10; + } + } + when(mBufferedReader.readLine()) + .thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, newTimes3)); + mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback); + verify(mCallback).onCpuFreqs(freqs); + for (int i = 0; i < uids.length; ++i) { + verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes3[i], newTimes2[i])); + } + verifyNoMoreInteractions(mCallback); + } + + private long[] subtract(long[] a1, long[] a2) { + long[] val = new long[a1.length]; + for (int i = 0; i < val.length; ++i) { + val[i] = a1[i] - a2[i]; + } + return val; } private String getFreqsLine(long[] freqs) { @@ -94,7 +148,7 @@ public class KernelUidCpuFreqTimeReaderTest { } private String[] getUidTimesLines(int[] uids, long[][] times) { - final String[] lines = new String[uids.length]; + final String[] lines = new String[uids.length + 1]; final StringBuilder sb = new StringBuilder(); for (int i = 0; i < uids.length; ++i) { sb.setLength(0); @@ -104,6 +158,7 @@ public class KernelUidCpuFreqTimeReaderTest { } lines[i] = sb.toString(); } + lines[uids.length] = null; return lines; } } diff --git a/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterArrayTest.java b/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterArrayTest.java new file mode 100644 index 0000000000000..4a23f408e745c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/LongSamplingCounterArrayTest.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2017 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.LongSamplingCounterArray; +import static com.android.internal.os.BatteryStatsImpl.TimeBase; + +import static org.junit.Assert.assertTrue; +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; + +import java.util.Arrays; + +/** + * Test class for {@link BatteryStatsImpl.LongSamplingCounterArray}. + * + * To run the tests, use + * + * runtest -c com.android.internal.os.LongSamplingCounterArrayTest frameworks-core + * + * or the following steps: + * + * Build: m FrameworksCoreTests + * Install: adb install -r \ + * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk + * Run: adb shell am instrument -e class com.android.internal.os.LongSamplingCounterArrayTest -w \ + * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LongSamplingCounterArrayTest { + + private static final long[] COUNTS = {1111, 2222, 3333, 4444}; + private static final long[] LOADED_COUNTS = {5555, 6666, 7777, 8888}; + private static final long[] PLUGGED_COUNTS = {9999, 11111, 22222, 33333}; + private static final long[] UNPLUGGED_COUNTS = {44444, 55555, 66666, 77777}; + private static final long[] ZEROES = {0, 0, 0, 0}; + + @Mock private TimeBase mTimeBase; + private LongSamplingCounterArray mCounterArray; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mCounterArray = new LongSamplingCounterArray(mTimeBase); + Mockito.reset(mTimeBase); + } + + @Test + public void testReadWriteParcel() { + final Parcel parcel = Parcel.obtain(); + initializeCounterArrayWithDefaultValues(); + LongSamplingCounterArray.writeToParcel(parcel, mCounterArray); + parcel.setDataPosition(0); + + // Now clear counterArray and verify values are read from parcel correctly. + updateCounts(null, null, null, null); + mCounterArray = LongSamplingCounterArray.readFromParcel(parcel, mTimeBase); + assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts, + "Unexpected unpluggedCounts"); + parcel.recycle(); + } + + @Test + public void testReadWriteSummaryParcel() { + final Parcel parcel = Parcel.obtain(); + initializeCounterArrayWithDefaultValues(); + LongSamplingCounterArray.writeSummaryToParcelLocked(parcel, mCounterArray); + parcel.setDataPosition(0); + + // Now clear counterArray and verify values are read from parcel correctly. + updateCounts(null, null, null, null); + mCounterArray = LongSamplingCounterArray.readSummaryFromParcelLocked(parcel, mTimeBase); + assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(COUNTS, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts"); + parcel.recycle(); + } + + @Test + public void testOnTimeStarted() { + initializeCounterArrayWithDefaultValues(); + mCounterArray.onTimeStarted(0, 0, 0); + assertArrayEquals(PLUGGED_COUNTS, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(PLUGGED_COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(PLUGGED_COUNTS, mCounterArray.mUnpluggedCounts, + "Unexpected unpluggedCounts"); + } + + @Test + public void testOnTimeStopped() { + initializeCounterArrayWithDefaultValues(); + mCounterArray.onTimeStopped(0, 0, 0); + assertArrayEquals(COUNTS, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts, + "Unexpected unpluggedCounts"); + } + + @Test + public void testGetCountsLocked() { + initializeCounterArrayWithDefaultValues(); + + when(mTimeBase.isRunning()).thenReturn(false); + long[] actualVal = mCounterArray.getCountsLocked(STATS_SINCE_CHARGED); + long[] expectedVal = PLUGGED_COUNTS; + assertArrayEquals(expectedVal, actualVal, "Unexpected values"); + + when(mTimeBase.isRunning()).thenReturn(true); + actualVal = mCounterArray.getCountsLocked(STATS_SINCE_CHARGED); + expectedVal = COUNTS; + assertArrayEquals(expectedVal, actualVal, "Unexpected values"); + } + + @Test + public void testAddCountLocked() { + final long[] deltas = {123, 234, 345, 456}; + mCounterArray.addCountLocked(deltas); + assertArrayEquals(deltas, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(null, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(null, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(null, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts"); + + initializeCounterArrayWithDefaultValues(); + final long[] newCounts = new long[deltas.length]; + for (int i = 0; i < deltas.length; ++i) { + newCounts[i] = COUNTS[i] + deltas[i]; + } + mCounterArray.addCountLocked(deltas); + assertArrayEquals(newCounts, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(LOADED_COUNTS, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(PLUGGED_COUNTS, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(UNPLUGGED_COUNTS, mCounterArray.mUnpluggedCounts, + "Unexpected unpluggedCounts"); + } + + @Test + public void testReset() { + initializeCounterArrayWithDefaultValues(); + // Test with detachIfReset=false + mCounterArray.reset(false /* detachIfReset */); + assertArrayEquals(ZEROES, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(ZEROES, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(ZEROES, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(ZEROES, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts"); + verifyZeroInteractions(mTimeBase); + + initializeCounterArrayWithDefaultValues(); + // Test with detachIfReset=true + mCounterArray.reset(true /* detachIfReset */); + assertArrayEquals(ZEROES, mCounterArray.mCounts, "Unexpected counts"); + assertArrayEquals(ZEROES, mCounterArray.mLoadedCounts, "Unexpected loadedCounts"); + assertArrayEquals(ZEROES, mCounterArray.mPluggedCounts, "Unexpected pluggedCounts"); + assertArrayEquals(ZEROES, mCounterArray.mUnpluggedCounts, "Unexpected unpluggedCounts"); + verify(mTimeBase).remove(mCounterArray); + verifyNoMoreInteractions(mTimeBase); + } + + @Test + public void testDetach() { + mCounterArray.detach(); + verify(mTimeBase).remove(mCounterArray); + verifyNoMoreInteractions(mTimeBase); + } + + private void initializeCounterArrayWithDefaultValues() { + updateCounts(COUNTS, LOADED_COUNTS, PLUGGED_COUNTS, UNPLUGGED_COUNTS); + } + + private void assertArrayEquals(long[] expected, long[] actual, String msg) { + assertTrue(msg + ", expected: " + Arrays.toString(expected) + + ", actual: " + Arrays.toString(actual), Arrays.equals(expected, actual)); + } + + private void updateCounts(long[] counts, long[] loadedCounts, + long[] pluggedCounts, long[] unpluggedCounts) { + mCounterArray.mCounts = counts; + mCounterArray.mLoadedCounts = loadedCounts; + mCounterArray.mPluggedCounts = pluggedCounts; + mCounterArray.mUnpluggedCounts = unpluggedCounts; + } +}