diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 0614cd26785f9..696f0ffba5183 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -79,7 +79,7 @@ public class BatteryStatsHistory { private static final String TAG = "BatteryStatsHistory"; // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - private static final int VERSION = 208; + private static final int VERSION = 209; private static final String HISTORY_DIR = "battery-history"; private static final String FILE_SUFFIX = ".bin"; @@ -198,6 +198,7 @@ public class BatteryStatsHistory { private long mHistoryBaseTimeMs; private boolean mMeasuredEnergyHeaderWritten = false; private boolean mCpuUsageHeaderWritten = false; + private final VarintParceler mVarintParceler = new VarintParceler(); private byte mLastHistoryStepLevel = 0; @@ -1665,9 +1666,7 @@ public class BatteryStatsHistory { } mMeasuredEnergyHeaderWritten = true; } - for (long chargeUC : cur.measuredEnergyDetails.chargeUC) { - dest.writeLong(chargeUC); - } + mVarintParceler.writeLongArray(dest, cur.measuredEnergyDetails.chargeUC); } if (cur.cpuUsageDetails != null) { @@ -1679,9 +1678,7 @@ public class BatteryStatsHistory { mCpuUsageHeaderWritten = true; } dest.writeInt(cur.cpuUsageDetails.uid); - for (long cpuUsageMs: cur.cpuUsageDetails.cpuUsageMs) { - dest.writeLong(cpuUsageMs); - } + mVarintParceler.writeLongArray(dest, cur.cpuUsageDetails.cpuUsageMs); } } } @@ -1930,4 +1927,74 @@ public class BatteryStatsHistory { entry.getKey()); } } + + /** + * Writes/reads an array of longs into Parcel using a compact format, where small integers use + * fewer bytes. It is a bit more expensive than just writing the long into the parcel, + * but at scale saves a lot of storage and allows recording of longer battery history. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static final class VarintParceler { + /** + * Writes an array of longs into Parcel using the varint format, see + * https://developers.google.com/protocol-buffers/docs/encoding#varints + */ + public void writeLongArray(Parcel parcel, long[] values) { + int out = 0; + int shift = 0; + for (long value : values) { + boolean done = false; + while (!done) { + final byte b; + if ((value & ~0x7FL) == 0) { + b = (byte) value; + done = true; + } else { + b = (byte) (((int) value & 0x7F) | 0x80); + value >>>= 7; + } + if (shift == 32) { + parcel.writeInt(out); + shift = 0; + out = 0; + } + out |= (b & 0xFF) << shift; + shift += 8; + } + } + if (shift != 0) { + parcel.writeInt(out); + } + } + + /** + * Reads a long written with {@link #writeLongArray} + */ + public void readLongArray(Parcel parcel, long[] values) { + int in = parcel.readInt(); + int available = 4; + for (int i = 0; i < values.length; i++) { + long result = 0; + int shift; + for (shift = 0; shift < 64; shift += 7) { + if (available == 0) { + in = parcel.readInt(); + available = 4; + } + final byte b = (byte) in; + in >>= 8; + available--; + + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + values[i] = result; + break; + } + } + if (shift >= 64) { + throw new ParcelFormatException("Invalid varint format"); + } + } + } + } } diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index 2429a8803a06d..09fe1006ef8f1 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -35,6 +35,8 @@ public class BatteryStatsHistoryIterator { private final SparseArray mHistoryTags = new SparseArray<>(); private BatteryStats.MeasuredEnergyDetails mMeasuredEnergyDetails; private BatteryStats.CpuUsageDetails mCpuUsageDetails; + private final BatteryStatsHistory.VarintParceler mVarintParceler = + new BatteryStatsHistory.VarintParceler(); public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) { mBatteryStatsHistory = history; @@ -61,7 +63,7 @@ public class BatteryStatsHistoryIterator { return true; } - void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) { + private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) { int firstToken = src.readInt(); int deltaTimeToken = firstToken & BatteryStatsHistory.DELTA_TIME_MASK; cur.cmd = BatteryStats.HistoryItem.CMD_UPDATE; @@ -226,9 +228,7 @@ public class BatteryStatsHistoryIterator { throw new IllegalStateException("MeasuredEnergyDetails without a header"); } - for (int i = 0; i < mMeasuredEnergyDetails.chargeUC.length; i++) { - mMeasuredEnergyDetails.chargeUC[i] = src.readLong(); - } + mVarintParceler.readLongArray(src, mMeasuredEnergyDetails.chargeUC); cur.measuredEnergyDetails = mMeasuredEnergyDetails; } else { cur.measuredEnergyDetails = null; @@ -249,9 +249,7 @@ public class BatteryStatsHistoryIterator { } mCpuUsageDetails.uid = src.readInt(); - for (int i = 0; i < mCpuUsageDetails.cpuUsageMs.length; i++) { - mCpuUsageDetails.cpuUsageMs[i] = src.readLong(); - } + mVarintParceler.readLongArray(src, mCpuUsageDetails.cpuUsageMs); cur.cpuUsageDetails = mCpuUsageDetails; } else { cur.cpuUsageDetails = null; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index 39b0770f024db..3f5d33167ad38 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -382,4 +382,68 @@ public class BatteryStatsHistoryTest { pw.flush(); return writer.toString(); } + + @Test + public void testVarintParceler() { + long[] values = { + 0, + 1, + 42, + 0x1234, + 0x10000000, + 0x12345678, + 0x7fffffff, + 0xffffffffL, + 0x100000000000L, + 0x123456789012L, + 0x1000000000000000L, + 0x1234567890123456L, + 0x7fffffffffffffffL, + 0xffffffffffffffffL}; + + // Parcel subarrays of different lengths and assert the size of the resulting parcel + testVarintParceler(Arrays.copyOfRange(values, 0, 1), 4); // v. 8 + testVarintParceler(Arrays.copyOfRange(values, 0, 2), 4); // v. 16 + testVarintParceler(Arrays.copyOfRange(values, 0, 3), 4); // v. 24 + testVarintParceler(Arrays.copyOfRange(values, 0, 4), 8); // v. 32 + testVarintParceler(Arrays.copyOfRange(values, 0, 5), 12); // v. 40 + testVarintParceler(Arrays.copyOfRange(values, 0, 6), 16); // v. 48 + testVarintParceler(Arrays.copyOfRange(values, 0, 7), 20); // v. 56 + testVarintParceler(Arrays.copyOfRange(values, 0, 8), 28); // v. 64 + testVarintParceler(Arrays.copyOfRange(values, 0, 9), 32); // v. 72 + testVarintParceler(Arrays.copyOfRange(values, 0, 10), 40); // v. 80 + testVarintParceler(Arrays.copyOfRange(values, 0, 11), 48); // v. 88 + testVarintParceler(Arrays.copyOfRange(values, 0, 12), 60); // v. 96 + testVarintParceler(Arrays.copyOfRange(values, 0, 13), 68); // v. 104 + testVarintParceler(Arrays.copyOfRange(values, 0, 14), 76); // v. 112 + } + + private void testVarintParceler(long[] values, int expectedLength) { + BatteryStatsHistory.VarintParceler parceler = new BatteryStatsHistory.VarintParceler(); + Parcel parcel = Parcel.obtain(); + parcel.writeString("begin"); + int pos = parcel.dataPosition(); + parceler.writeLongArray(parcel, values); + int length = parcel.dataPosition() - pos; + parcel.writeString("end"); + + byte[] bytes = parcel.marshall(); + parcel.recycle(); + + parcel = Parcel.obtain(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + + assertThat(parcel.readString()).isEqualTo("begin"); + + long[] result = new long[values.length]; + parceler.readLongArray(parcel, result); + + assertThat(result).isEqualTo(values); + assertThat(length).isEqualTo(expectedLength); + + assertThat(parcel.readString()).isEqualTo("end"); + + parcel.recycle(); + } }