1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.power.stats.processor; 17 18 import android.annotation.NonNull; 19 import android.os.BatteryConsumer; 20 import android.os.BatteryStats; 21 import android.util.SparseBooleanArray; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.internal.os.BatteryStatsHistory; 25 import com.android.internal.os.BatteryStatsHistoryIterator; 26 import com.android.internal.os.MonotonicClock; 27 28 import java.util.function.Consumer; 29 30 /** 31 * Power stats aggregator. It reads through portions of battery stats history, finds 32 * relevant items (state changes, power stats etc) and produces one or more 33 * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history. 34 */ 35 public class PowerStatsAggregator { 36 private static final long UNINITIALIZED = -1; 37 private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; 38 private final SparseBooleanArray mEnabledComponents = 39 new SparseBooleanArray(BatteryConsumer.POWER_COMPONENT_COUNT + 10); 40 private AggregatedPowerStats mStats; 41 private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; 42 private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; 43 44 @VisibleForTesting PowerStatsAggregator()45 public PowerStatsAggregator() { 46 this(new AggregatedPowerStatsConfig()); 47 } 48 PowerStatsAggregator(@onNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig)49 PowerStatsAggregator(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { 50 mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig; 51 } 52 getConfig()53 AggregatedPowerStatsConfig getConfig() { 54 return mAggregatedPowerStatsConfig; 55 } 56 57 /** 58 * Marks the power component as enabled for PowerStats aggregation 59 */ 60 @VisibleForTesting setPowerComponentEnabled(int powerComponentId, boolean enabled)61 public void setPowerComponentEnabled(int powerComponentId, boolean enabled) { 62 synchronized (this) { 63 if (mStats != null) { 64 mStats = null; 65 } 66 mEnabledComponents.put(powerComponentId, enabled); 67 } 68 } 69 70 /** 71 * Iterates of the battery history and aggregates power stats between the specified times. 72 * The start and end are specified in the battery-stats monotonic time, which is the 73 * adjusted elapsed time found in HistoryItem.time. 74 * <p> 75 * The aggregated stats are sent to the consumer. One aggregation pass may produce 76 * multiple sets of aggregated stats if there was an incompatible change that occurred in the 77 * middle of the recorded battery history. 78 * <p> 79 * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume 80 * the stats in the <code>accept</code> method and never cache it. 81 */ aggregatePowerStats(BatteryStatsHistory history, long startTimeMs, long endTimeMs, Consumer<AggregatedPowerStats> consumer)82 public void aggregatePowerStats(BatteryStatsHistory history, long startTimeMs, long endTimeMs, 83 Consumer<AggregatedPowerStats> consumer) { 84 synchronized (this) { 85 if (mStats == null) { 86 mStats = new AggregatedPowerStats(mAggregatedPowerStatsConfig, mEnabledComponents); 87 } 88 89 boolean startedSession = false; 90 long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED; 91 long lastTime = 0; 92 int lastStates = 0xFFFFFFFF; 93 int lastStates2 = 0xFFFFFFFF; 94 int lastBatteryLevel = 0; 95 try (BatteryStatsHistoryIterator iterator = history.iterate(startTimeMs, endTimeMs)) { 96 while (iterator.hasNext()) { 97 BatteryStats.HistoryItem item = iterator.next(); 98 99 if (!startedSession) { 100 mStats.start(item.time); 101 if (!mStats.addClockUpdate(item.time, item.currentTime)) { 102 break; 103 } 104 if (baseTime == UNINITIALIZED) { 105 baseTime = item.time; 106 } 107 startedSession = true; 108 } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME 109 || item.cmd == BatteryStats.HistoryItem.CMD_RESET) { 110 if (!mStats.addClockUpdate(item.time, item.currentTime)) { 111 break; 112 } 113 } 114 115 lastTime = item.time; 116 117 if (item.cmd == BatteryStats.HistoryItem.CMD_UPDATE 118 && item.batteryLevel != lastBatteryLevel) { 119 mStats.noteBatteryLevel(item.batteryLevel, item.batteryChargeUah, 120 item.time); 121 lastBatteryLevel = item.batteryLevel; 122 } 123 124 int batteryState = 125 (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0 126 ? AggregatedPowerStatsConfig.POWER_STATE_OTHER 127 : AggregatedPowerStatsConfig.POWER_STATE_BATTERY; 128 if (batteryState != mCurrentBatteryState) { 129 mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState, 130 item.time); 131 mCurrentBatteryState = batteryState; 132 } 133 134 int screenState = 135 (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0 136 ? AggregatedPowerStatsConfig.SCREEN_STATE_ON 137 : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; 138 if (screenState != mCurrentScreenState) { 139 mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState, 140 item.time); 141 mCurrentScreenState = screenState; 142 } 143 144 if ((item.states 145 & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES) 146 != lastStates 147 || (item.states2 148 & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES2) 149 != lastStates2) { 150 mStats.noteStateChange(item); 151 lastStates = item.states 152 & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES; 153 lastStates2 = item.states2 154 & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES2; 155 } 156 157 if (item.processStateChange != null) { 158 mStats.setUidState(item.processStateChange.uid, 159 AggregatedPowerStatsConfig.STATE_PROCESS_STATE, 160 item.processStateChange.processState, item.time); 161 } 162 163 if (item.powerStats != null) { 164 if (!mStats.isCompatible(item.powerStats)) { 165 if (lastTime > baseTime) { 166 mStats.setDuration(lastTime - baseTime); 167 mStats.finish(lastTime); 168 consumer.accept(mStats); 169 } 170 mStats.reset(); 171 if (!mStats.addClockUpdate(item.time, item.currentTime)) { 172 break; 173 } 174 baseTime = lastTime = item.time; 175 } 176 mStats.addPowerStats(item.powerStats, item.time); 177 } 178 } 179 } 180 if (startedSession) { 181 if (endTimeMs != MonotonicClock.UNDEFINED) { 182 lastTime = endTimeMs; 183 } 184 if (lastTime > baseTime) { 185 mStats.setDuration(lastTime - baseTime); 186 mStats.finish(lastTime); 187 consumer.accept(mStats); 188 } 189 } 190 191 mStats.reset(); // to free up memory 192 } 193 } 194 195 /** 196 * Reset to prepare for a new aggregation session. 197 */ reset()198 public void reset() { 199 synchronized (this) { 200 mStats = null; 201 } 202 } 203 } 204