summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/os/MultiStateStats.java4
-rw-r--r--core/java/com/android/internal/os/PowerStats.java55
-rw-r--r--core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java16
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java7
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStats.java147
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java22
-rw-r--r--services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java29
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java4
-rw-r--r--services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java232
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsAggregator.java226
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java5
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java232
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java4
13 files changed, 952 insertions, 31 deletions
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
index ad1507177731..f971849987dd 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -41,6 +41,10 @@ public class MultiStateStats {
this.mTracked = tracked;
this.mLabels = labels;
}
+
+ public boolean isTracked() {
+ return mTracked;
+ }
}
/**
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 8b0e814c87aa..8f66d1f9365c 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -21,12 +21,14 @@ import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.Bundle;
import android.os.Parcel;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
import java.util.Arrays;
+import java.util.Objects;
/**
* Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
@@ -74,16 +76,16 @@ public final class PowerStats {
* Extra parameters specific to the power component, e.g. the availability of power
* monitors.
*/
- public final Bundle extras;
+ public final PersistableBundle extras;
public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
- int statsArrayLength, int uidStatsArrayLength, @NonNull Bundle extras) {
+ int statsArrayLength, int uidStatsArrayLength, @NonNull PersistableBundle extras) {
this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
statsArrayLength, uidStatsArrayLength, extras);
}
public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
- int uidStatsArrayLength, Bundle extras) {
+ int uidStatsArrayLength, PersistableBundle extras) {
if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
@@ -104,11 +106,11 @@ public final class PowerStats {
*/
public void writeSummaryToParcel(Parcel parcel) {
int firstWord = ((PARCEL_FORMAT_VERSION << PARCEL_FORMAT_VERSION_SHIFT)
- & PARCEL_FORMAT_VERSION_MASK)
- | ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
- & STATS_ARRAY_LENGTH_MASK)
- | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
- & UID_STATS_ARRAY_LENGTH_MASK);
+ & PARCEL_FORMAT_VERSION_MASK)
+ | ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
+ & STATS_ARRAY_LENGTH_MASK)
+ | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
+ & UID_STATS_ARRAY_LENGTH_MASK);
parcel.writeInt(firstWord);
parcel.writeInt(powerComponentId);
parcel.writeString(name);
@@ -125,7 +127,7 @@ public final class PowerStats {
int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
if (version != PARCEL_FORMAT_VERSION) {
Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
- + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
+ + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
return null;
}
int statsArrayLength =
@@ -134,23 +136,42 @@ public final class PowerStats {
(firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
int powerComponentId = parcel.readInt();
String name = parcel.readString();
- Bundle extras = parcel.readBundle(PowerStats.class.getClassLoader());
+ PersistableBundle extras = parcel.readPersistableBundle();
return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
extras);
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Descriptor)) return false;
+ Descriptor that = (Descriptor) o;
+ return powerComponentId == that.powerComponentId
+ && statsArrayLength == that.statsArrayLength
+ && uidStatsArrayLength == that.uidStatsArrayLength
+ && Objects.equals(name, that.name)
+ && extras.size() == that.extras.size() // Unparcel the Parcel if not yet
+ && Bundle.kindofEquals(extras,
+ that.extras); // Since the Parcel is now unparceled, do a deep comparison
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(powerComponentId);
+ }
+
+ @Override
public String toString() {
if (extras != null) {
extras.size(); // Unparcel
}
return "PowerStats.Descriptor{"
- + "powerComponentId=" + powerComponentId
- + ", name='" + name + '\''
- + ", statsArrayLength=" + statsArrayLength
- + ", uidStatsArrayLength=" + uidStatsArrayLength
- + ", extras=" + extras
- + '}';
+ + "powerComponentId=" + powerComponentId
+ + ", name='" + name + '\''
+ + ", statsArrayLength=" + statsArrayLength
+ + ", uidStatsArrayLength=" + uidStatsArrayLength
+ + ", extras=" + extras
+ + '}';
}
}
@@ -254,7 +275,7 @@ public final class PowerStats {
}
if (parcel.dataPosition() != endPos) {
Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
- + ", actual length: " + (parcel.dataPosition() - startPos));
+ + ", actual length: " + (parcel.dataPosition() - startPos));
return null;
}
return stats;
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index ee04383107e2..29da2319adc2 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -19,8 +19,8 @@ package com.android.internal.os;
import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
-import android.os.Bundle;
import android.os.Parcel;
+import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -39,7 +39,7 @@ public class PowerStatsTest {
@Before
public void setup() {
mRegistry = new PowerStats.DescriptorRegistry();
- Bundle extras = new Bundle();
+ PersistableBundle extras = new PersistableBundle();
extras.putBoolean("hasPowerMonitor", true);
mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, 2, extras);
mRegistry.register(mDescriptor);
@@ -52,8 +52,8 @@ public class PowerStatsTest {
stats.stats[0] = 10;
stats.stats[1] = 20;
stats.stats[2] = 30;
- stats.uidStats.put(42, new long[] {40, 50});
- stats.uidStats.put(99, new long[] {60, 70});
+ stats.uidStats.put(42, new long[]{40, 50});
+ stats.uidStats.put(99, new long[]{60, 70});
Parcel parcel = Parcel.obtain();
mDescriptor.writeSummaryToParcel(parcel);
@@ -74,10 +74,10 @@ public class PowerStatsTest {
PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
assertThat(newStats.durationMs).isEqualTo(1234);
- assertThat(newStats.stats).isEqualTo(new long[] {10, 20, 30});
+ assertThat(newStats.stats).isEqualTo(new long[]{10, 20, 30});
assertThat(newStats.uidStats.size()).isEqualTo(2);
- assertThat(newStats.uidStats.get(42)).isEqualTo(new long[] {40, 50});
- assertThat(newStats.uidStats.get(99)).isEqualTo(new long[] {60, 70});
+ assertThat(newStats.uidStats.get(42)).isEqualTo(new long[]{40, 50});
+ assertThat(newStats.uidStats.get(99)).isEqualTo(new long[]{60, 70});
String end = newParcel.readString();
assertThat(end).isEqualTo("END");
@@ -86,7 +86,7 @@ public class PowerStatsTest {
@Test
public void parceling_unrecognizedPowerComponent() {
PowerStats stats = new PowerStats(
- new PowerStats.Descriptor(777, "luck", 3, 2, new Bundle()));
+ new PowerStats.Descriptor(777, "luck", 3, 2, new PersistableBundle()));
stats.durationMs = 1234;
Parcel parcel = Parcel.obtain();
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b0f30d6875ae..610b8af48cd1 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -2631,6 +2631,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.dumpStatsSample(pw);
}
+ private void dumpAggregatedStats(PrintWriter pw) {
+ mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+ }
+
private void dumpMeasuredEnergyStats(PrintWriter pw) {
// Wait for the completion of pending works if there is any
awaitCompletion();
@@ -2875,6 +2879,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("--sample".equals(arg)) {
dumpStatsSample(pw);
return;
+ } else if ("--aggregated".equals(arg)) {
+ dumpAggregatedStats(pw);
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
new file mode 100644
index 000000000000..6cc9d0a54607
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.server.power.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.os.UserHandle;
+import android.text.format.DateFormat;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.os.PowerStats;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class represents aggregated power stats for a variety of power components (CPU, WiFi,
+ * etc) covering a specific period of power usage history.
+ */
+class AggregatedPowerStats {
+ private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
+
+ @CurrentTimeMillisLong
+ private long mStartTime;
+
+ @DurationMillisLong
+ private long mDurationMs;
+
+ AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
+ this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+ }
+
+ void setStartTime(@CurrentTimeMillisLong long startTime) {
+ mStartTime = startTime;
+ }
+
+ @CurrentTimeMillisLong
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ void setDuration(long durationMs) {
+ mDurationMs = durationMs;
+ }
+
+ @DurationMillisLong
+ public long getDuration() {
+ return mDurationMs;
+ }
+
+ PowerComponentAggregatedPowerStats getPowerComponentStats(int powerComponentId) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId) {
+ return stats;
+ }
+ }
+ return null;
+ }
+
+ void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.setState(stateId, state, time);
+ }
+ }
+
+ void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ long time) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.setUidState(uid, stateId, state, time);
+ }
+ }
+
+ boolean isCompatible(PowerStats powerStats) {
+ int powerComponentId = powerStats.descriptor.powerComponentId;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId && !stats.isCompatible(powerStats)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void addPowerStats(PowerStats powerStats, long time) {
+ int powerComponentId = powerStats.descriptor.powerComponentId;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId) {
+ stats.addPowerStats(powerStats, time);
+ }
+ }
+ }
+
+ void reset() {
+ mStartTime = 0;
+ mDurationMs = 0;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.reset();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.print("Start time: ");
+ ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
+ ipw.print(" duration: ");
+ ipw.print(mDurationMs);
+ ipw.println();
+
+ ipw.println("Device");
+ ipw.increaseIndent();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.dumpDevice(ipw);
+ }
+ ipw.decreaseIndent();
+
+ Set<Integer> uids = new HashSet<>();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.collectUids(uids);
+ }
+
+ Integer[] allUids = uids.toArray(new Integer[uids.size()]);
+ Arrays.sort(allUids);
+ for (int uid : allUids) {
+ ipw.println(UserHandle.formatUid(uid));
+ ipw.increaseIndent();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.dumpUid(ipw, uid);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index c5f879548210..613f18982b86 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -283,6 +283,7 @@ public class BatteryStatsImpl extends BatteryStats {
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private final PowerStatsAggregator mPowerStatsAggregator;
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -1747,6 +1748,7 @@ public class BatteryStatsImpl extends BatteryStats {
mEnergyConsumerRetriever = null;
mUserInfoProvider = null;
mCpuPowerStatsCollector = null;
+ mPowerStatsAggregator = null;
}
private void init(Clock clock) {
@@ -10947,6 +10949,18 @@ public class BatteryStatsImpl extends BatteryStats {
mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
+ PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
+ builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ .trackDeviceStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN)
+ .trackUidStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN,
+ PowerStatsAggregator.STATE_PROCESS_STATE);
+
+ mPowerStatsAggregator = builder.build();
+
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -15694,6 +15708,14 @@ public class BatteryStatsImpl extends BatteryStats {
mCpuPowerStatsCollector.collectAndDump(pw);
}
+ /**
+ * Aggregates power stats between the specified times and prints them.
+ */
+ public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
+ mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
+ stats-> stats.dump(pw));
+ }
+
private final Runnable mWriteAsyncRunnable = () -> {
synchronized (BatteryStatsImpl.this) {
writeSyncLocked();
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
new file mode 100644
index 000000000000..5b3fe064d79a
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.server.power.stats;
+
+import android.os.BatteryConsumer;
+
+import com.android.internal.os.MultiStateStats;
+
+class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
+
+ CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
+ MultiStateStats.States[] uidStates) {
+ super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 020cd91c51c1..376ca897fbd1 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -17,8 +17,8 @@
package com.android.server.power.stats;
import android.os.BatteryConsumer;
-import android.os.Bundle;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.util.SparseArray;
import com.android.internal.annotations.Keep;
@@ -74,7 +74,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
mCpuPowerStats = new PowerStats(
new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 0, mUidStatsSize,
- new Bundle()));
+ new PersistableBundle()));
}
/**
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
new file mode 100644
index 000000000000..686268fea22b
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2023 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.server.power.stats;
+
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
+
+import java.util.Collection;
+
+/**
+ * Aggregated power stats for a specific power component (e.g. CPU, WiFi, etc). This class
+ * treats stats as arrays of nonspecific longs. Subclasses contain specific logic to interpret those
+ * longs and use them for calculations such as power attribution. They may use meta-data supplied
+ * as part of the {@link PowerStats.Descriptor}.
+ */
+class PowerComponentAggregatedPowerStats {
+ public final int powerComponentId;
+ private final MultiStateStats.States[] mDeviceStateConfig;
+ private final MultiStateStats.States[] mUidStateConfig;
+ private final int[] mDeviceStates;
+ private final long[] mDeviceStateTimestamps;
+
+ private MultiStateStats.Factory mStatsFactory;
+ private MultiStateStats.Factory mUidStatsFactory;
+ private PowerStats.Descriptor mPowerStatsDescriptor;
+ private MultiStateStats mDeviceStats;
+ private final SparseArray<UidStats> mUidStats = new SparseArray<>();
+
+ private static class UidStats {
+ public int[] states;
+ public long[] stateTimestampMs;
+ public MultiStateStats stats;
+ }
+
+ PowerComponentAggregatedPowerStats(int powerComponentId,
+ MultiStateStats.States[] deviceStates,
+ MultiStateStats.States[] uidStates) {
+ this.powerComponentId = powerComponentId;
+ mDeviceStateConfig = deviceStates;
+ mUidStateConfig = uidStates;
+ mDeviceStates = new int[mDeviceStateConfig.length];
+ mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
+ }
+
+ void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ mDeviceStates[stateId] = state;
+ mDeviceStateTimestamps[stateId] = time;
+
+ if (mDeviceStateConfig[stateId].isTracked()) {
+ if (mDeviceStats != null || createDeviceStats()) {
+ mDeviceStats.setState(stateId, state, time);
+ }
+ }
+
+ if (mUidStateConfig[stateId].isTracked()) {
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.stats != null || createUidStats(uidStats)) {
+ uidStats.stats.setState(stateId, state, time);
+ }
+ }
+ }
+ }
+
+ void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ long time) {
+ if (!mUidStateConfig[stateId].isTracked()) {
+ return;
+ }
+
+ UidStats uidStats = getUidStats(uid);
+ uidStats.states[stateId] = state;
+ uidStats.stateTimestampMs[stateId] = time;
+
+ if (uidStats.stats != null || createUidStats(uidStats)) {
+ uidStats.stats.setState(stateId, state, time);
+ }
+ }
+
+ boolean isCompatible(PowerStats powerStats) {
+ return mPowerStatsDescriptor == null || mPowerStatsDescriptor.equals(powerStats.descriptor);
+ }
+
+ void addPowerStats(PowerStats powerStats, long timestampMs) {
+ mPowerStatsDescriptor = powerStats.descriptor;
+
+ if (mDeviceStats == null) {
+ if (mStatsFactory == null) {
+ mStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
+ mUidStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
+ }
+
+ createDeviceStats();
+ }
+
+ mDeviceStats.increment(powerStats.stats, timestampMs);
+
+ for (int i = powerStats.uidStats.size() - 1; i >= 0; i--) {
+ int uid = powerStats.uidStats.keyAt(i);
+ PowerComponentAggregatedPowerStats.UidStats uidStats = getUidStats(uid);
+ if (uidStats.stats == null) {
+ createUidStats(uidStats);
+ }
+ uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs);
+ }
+ }
+
+ void reset() {
+ mPowerStatsDescriptor = null;
+ mStatsFactory = null;
+ mUidStatsFactory = null;
+ mDeviceStats = null;
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ mUidStats.valueAt(i).stats = null;
+ }
+ }
+
+ private UidStats getUidStats(int uid) {
+ // TODO(b/292247660): map isolated and sandbox UIDs
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new UidStats();
+ uidStats.states = new int[mUidStateConfig.length];
+ uidStats.stateTimestampMs = new long[mUidStateConfig.length];
+ mUidStats.put(uid, uidStats);
+ }
+ return uidStats;
+ }
+
+ void collectUids(Collection<Integer> uids) {
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ if (mUidStats.valueAt(i).stats != null) {
+ uids.add(mUidStats.keyAt(i));
+ }
+ }
+ }
+
+ boolean getDeviceStats(long[] outValues, int[] deviceStates) {
+ if (deviceStates.length != mDeviceStateConfig.length) {
+ throw new IllegalArgumentException(
+ "Invalid number of tracked states: " + deviceStates.length
+ + " expected: " + mDeviceStateConfig.length);
+ }
+ if (mDeviceStats != null) {
+ mDeviceStats.getStats(outValues, deviceStates);
+ return true;
+ }
+ return false;
+ }
+
+ boolean getUidStats(long[] outValues, int uid, int[] uidStates) {
+ if (uidStates.length != mUidStateConfig.length) {
+ throw new IllegalArgumentException(
+ "Invalid number of tracked states: " + uidStates.length
+ + " expected: " + mUidStateConfig.length);
+ }
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats != null && uidStats.stats != null) {
+ uidStats.stats.getStats(outValues, uidStates);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean createDeviceStats() {
+ if (mStatsFactory == null) {
+ return false;
+ }
+
+ mDeviceStats = mStatsFactory.create();
+ for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
+ mDeviceStats.setState(stateId, mDeviceStates[stateId],
+ mDeviceStateTimestamps[stateId]);
+ }
+ return true;
+ }
+
+ private boolean createUidStats(UidStats uidStats) {
+ if (mUidStatsFactory == null) {
+ return false;
+ }
+
+ uidStats.stats = mUidStatsFactory.create();
+ for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
+ uidStats.stats.setState(stateId, mDeviceStates[stateId],
+ mDeviceStateTimestamps[stateId]);
+ }
+ for (int stateId = mDeviceStateConfig.length; stateId < mUidStateConfig.length; stateId++) {
+ uidStats.stats.setState(stateId, uidStats.states[stateId],
+ uidStats.stateTimestampMs[stateId]);
+ }
+ return true;
+ }
+
+ void dumpDevice(IndentingPrintWriter ipw) {
+ if (mDeviceStats != null) {
+ ipw.println(mPowerStatsDescriptor.name);
+ ipw.increaseIndent();
+ mDeviceStats.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+
+ void dumpUid(IndentingPrintWriter ipw, int uid) {
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats != null && uidStats.stats != null) {
+ ipw.println(mPowerStatsDescriptor.name);
+ ipw.increaseIndent();
+ uidStats.stats.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
new file mode 100644
index 000000000000..6a1c1da93163
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2023 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.server.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MultiStateStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+class PowerStatsAggregator {
+ public static final int STATE_POWER = 0;
+ public static final int STATE_SCREEN = 1;
+ public static final int STATE_PROCESS_STATE = 2;
+
+ @IntDef({
+ STATE_POWER,
+ STATE_SCREEN,
+ STATE_PROCESS_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TrackedState {
+ }
+
+ static final int POWER_STATE_BATTERY = 0;
+ static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
+ static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+ static final int SCREEN_STATE_ON = 0;
+ static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
+ static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+ static final String[] STATE_LABELS_PROCESS_STATE;
+
+ static {
+ String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+ procStateLabels[i] = BatteryConsumer.processStateToString(i);
+ }
+ STATE_LABELS_PROCESS_STATE = procStateLabels;
+ }
+
+ private final BatteryStatsHistory mHistory;
+ private final AggregatedPowerStats mStats;
+
+ private PowerStatsAggregator(BatteryStatsHistory history,
+ AggregatedPowerStats aggregatedPowerStats) {
+ mHistory = history;
+ mStats = aggregatedPowerStats;
+ }
+
+ /**
+ * Iterates of the battery history and aggregates power stats between the specified times.
+ * The start and end are specified in the battery-stats monotonic time, which is the
+ * adjusted elapsed time found in HistoryItem.time.
+ * <p>
+ * The aggregated stats are sent to the consumer. One aggregation pass may produce
+ * multiple sets of aggregated stats if there was an incompatible change that occurred in the
+ * middle of the recorded battery history.
+ * <p>
+ * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
+ * the stats in the <code>accept</code> method and never cache it.
+ */
+ void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+ Consumer<AggregatedPowerStats> consumer) {
+ mStats.reset();
+
+ int currentBatteryState = POWER_STATE_BATTERY;
+ int currentScreenState = SCREEN_STATE_OTHER;
+ long baseTime = -1;
+ long lastTime = 0;
+ try (BatteryStatsHistoryIterator iterator =
+ mHistory.copy().iterate(startTimeMs, endTimeMs)) {
+ while (iterator.hasNext()) {
+ BatteryStats.HistoryItem item = iterator.next();
+
+ if (baseTime < 0) {
+ mStats.setStartTime(item.currentTime);
+ baseTime = item.time;
+ }
+
+ lastTime = item.time;
+
+ int batteryState =
+ (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
+ ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+ if (batteryState != currentBatteryState) {
+ mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+ currentBatteryState = batteryState;
+ }
+
+ int screenState =
+ (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
+ ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+ if (screenState != currentScreenState) {
+ mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+ currentScreenState = screenState;
+ }
+
+ if (item.processStateChange != null) {
+ mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+ item.processStateChange.processState, item.time);
+ }
+
+ if (item.powerStats != null) {
+ if (!mStats.isCompatible(item.powerStats)) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ mStats.reset();
+ mStats.setStartTime(item.currentTime);
+ baseTime = lastTime = item.time;
+ }
+ mStats.addPowerStats(item.powerStats, item.time);
+ }
+ }
+ }
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ }
+
+ static class Builder {
+ static class PowerComponentAggregateStatsBuilder {
+ private final int mPowerComponentId;
+ private @TrackedState int[] mTrackedDeviceStates;
+ private @TrackedState int[] mTrackedUidStates;
+
+ PowerComponentAggregateStatsBuilder(int powerComponentId) {
+ this.mPowerComponentId = powerComponentId;
+ }
+
+ public PowerComponentAggregateStatsBuilder trackDeviceStates(
+ @TrackedState int... states) {
+ mTrackedDeviceStates = states;
+ return this;
+ }
+
+ public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
+ mTrackedUidStates = states;
+ return this;
+ }
+
+ private PowerComponentAggregatedPowerStats build() {
+ MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
+ new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
+ PowerStatsAggregator.STATE_LABELS_POWER),
+ new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
+ PowerStatsAggregator.STATE_LABELS_SCREEN),
+ };
+
+ MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
+ new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
+ PowerStatsAggregator.STATE_LABELS_POWER),
+ new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
+ PowerStatsAggregator.STATE_LABELS_SCREEN),
+ new MultiStateStats.States(
+ isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+ PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
+ };
+
+ switch (mPowerComponentId) {
+ case BatteryConsumer.POWER_COMPONENT_CPU:
+ return new CpuAggregatedPowerStats(deviceStates, uidStates);
+ default:
+ return new PowerComponentAggregatedPowerStats(mPowerComponentId,
+ deviceStates, uidStates);
+ }
+ }
+
+ private boolean isTracked(int[] trackedStates, int state) {
+ if (trackedStates == null) {
+ return false;
+ }
+
+ for (int trackedState : trackedStates) {
+ if (trackedState == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final BatteryStatsHistory mHistory;
+ private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
+ new ArrayList<>();
+
+ Builder(BatteryStatsHistory history) {
+ mHistory = history;
+ }
+
+ PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
+ PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
+ powerComponentId);
+ mPowerComponents.add(builder);
+ return builder;
+ }
+
+ PowerStatsAggregator build() {
+ return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
+ mPowerComponents.stream()
+ .map(PowerComponentAggregateStatsBuilder::build)
+ .toArray(PowerComponentAggregatedPowerStats[]::new)));
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 1d86b88235c0..f22296a6261c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -28,8 +28,8 @@ import android.os.BatteryConsumer;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
-import android.os.Bundle;
import android.os.Parcel;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.NetworkRegistrationInfo;
@@ -387,7 +387,8 @@ public class BatteryStatsHistoryTest {
@Test
public void recordPowerStats() {
- PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, 2, new Bundle());
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, 2,
+ new PersistableBundle());
PowerStats powerStats = new PowerStats(descriptor);
powerStats.durationMs = 100;
powerStats.stats[0] = 200;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
new file mode 100644
index 000000000000..47de44324ae1
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2023 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PowerStatsAggregatorTest {
+ private static final int TEST_POWER_COMPONENT = 77;
+ private static final int TEST_UID = 42;
+
+ private final MockClock mClock = new MockClock();
+ private long mStartTime;
+ private BatteryStatsHistory mHistory;
+ private PowerStatsAggregator mAggregator;
+ private int mAggregatedStatsCount;
+
+ @Before
+ public void setup() throws ParseException {
+ mHistory = new BatteryStatsHistory(32, 1024,
+ mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
+ mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
+ .parse("2008-09-23 08:00").getTime();
+ mClock.currentTime = mStartTime;
+
+ PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
+ builder.trackPowerComponent(TEST_POWER_COMPONENT)
+ .trackDeviceStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN)
+ .trackUidStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN,
+ PowerStatsAggregator.STATE_PROCESS_STATE);
+ mAggregator = builder.build();
+ }
+
+ @Test
+ public void stateUpdates() {
+ mHistory.forceRecordAllHistory();
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
+ mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+
+ advance(1000);
+
+ PowerStats.Descriptor descriptor =
+ new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{10000};
+ powerStats.uidStats.put(TEST_UID, new long[]{1234});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 90, /* plugged */ false);
+ mHistory.recordStateStopEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+
+ advance(1000);
+
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND);
+
+ advance(3000);
+
+ powerStats.stats = new long[]{20000};
+ powerStats.uidStats.put(TEST_UID, new long[]{4444});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ assertThat(mAggregatedStatsCount++).isEqualTo(0);
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getDuration()).isEqualTo(5000);
+
+ long[] values = new long[1];
+
+ PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats(
+ TEST_POWER_COMPONENT);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{10000});
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{20000});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1234});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1111});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{3333});
+ });
+ }
+
+ @Test
+ public void incompatiblePowerStats() {
+ mHistory.forceRecordAllHistory();
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+
+ advance(1000);
+
+ PowerStats.Descriptor descriptor =
+ new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{10000};
+ powerStats.uidStats.put(TEST_UID, new long[]{1234});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 90, /* plugged */ false);
+
+ advance(1000);
+
+ descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ PersistableBundle.forPair("something", "changed"));
+ powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{20000};
+ powerStats.uidStats.put(TEST_UID, new long[]{4444});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ advance(1000);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
+
+ mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ long[] values = new long[1];
+
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT);
+
+ if (mAggregatedStatsCount == 0) {
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getDuration()).isEqualTo(2000);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{10000});
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1234});
+ } else if (mAggregatedStatsCount == 1) {
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+ assertThat(stats.getDuration()).isEqualTo(1000);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{20000});
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{4444});
+ } else {
+ fail();
+ }
+ mAggregatedStatsCount++;
+ });
+ }
+
+ private void advance(long durationMs) {
+ mClock.realtime += durationMs;
+ mClock.uptime += durationMs;
+ mClock.currentTime += durationMs;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index f02164d49a55..330f698277f8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -18,10 +18,10 @@ package com.android.server.power.stats;
import static com.google.common.truth.Truth.assertThat;
-import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -50,7 +50,7 @@ public class PowerStatsCollectorTest {
mMockClock) {
@Override
protected PowerStats collectStats() {
- return new PowerStats(new PowerStats.Descriptor(0, 0, 0, new Bundle()));
+ return new PowerStats(new PowerStats.Descriptor(0, 0, 0, new PersistableBundle()));
}
};
mCollector.addConsumer(stats -> mCollectedStats = stats);