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; 17 18 import static com.android.server.power.stats.MultiStateStats.STATE_DOES_NOT_EXIST; 19 import static com.android.server.power.stats.MultiStateStats.States.findTrackedStateByName; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.os.BatteryStats; 24 import android.util.Log; 25 26 import com.android.internal.os.PowerStats; 27 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collections; 31 import java.util.List; 32 33 /* 34 * The power estimation algorithm used by PowerStatsProcessor can roughly be 35 * described like this: 36 * 37 * 1. Estimate power usage for each state combination (e.g. power-battery/screen-on) using 38 * a metric such as CPU time-in-state. 39 * 40 * 2. Combine estimates obtain in step 1, aggregating across states that are *not* tracked 41 * per UID. 42 * 43 * 2. For each UID, compute the proportion of the combined estimates in each state 44 * and attribute the corresponding portion of the total power estimate in that state to the UID. 45 */ 46 abstract class PowerStatsProcessor { 47 private static final String TAG = "PowerStatsProcessor"; 48 49 private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0; 50 start(PowerComponentAggregatedPowerStats stats, long timestampMs)51 void start(PowerComponentAggregatedPowerStats stats, long timestampMs) { 52 } 53 noteStateChange(PowerComponentAggregatedPowerStats stats, BatteryStats.HistoryItem item)54 void noteStateChange(PowerComponentAggregatedPowerStats stats, 55 BatteryStats.HistoryItem item) { 56 } 57 addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats, long timestampMs)58 void addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats, 59 long timestampMs) { 60 stats.addPowerStats(powerStats, timestampMs); 61 } 62 finish(PowerComponentAggregatedPowerStats stats, long timestampMs)63 abstract void finish(PowerComponentAggregatedPowerStats stats, long timestampMs); 64 65 protected static class PowerEstimationPlan { 66 private final AggregatedPowerStatsConfig.PowerComponent mConfig; 67 public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>(); 68 public List<CombinedDeviceStateEstimate> combinedDeviceStateEstimations = new ArrayList<>(); 69 public List<UidStateEstimate> uidStateEstimates = new ArrayList<>(); 70 PowerEstimationPlan(AggregatedPowerStatsConfig.PowerComponent config)71 public PowerEstimationPlan(AggregatedPowerStatsConfig.PowerComponent config) { 72 mConfig = config; 73 addDeviceStateEstimations(); 74 combineDeviceStateEstimations(); 75 addUidStateEstimations(); 76 } 77 addDeviceStateEstimations()78 private void addDeviceStateEstimations() { 79 MultiStateStats.States[] config = mConfig.getDeviceStateConfig(); 80 int[][] deviceStateCombinations = getAllTrackedStateCombinations(config); 81 for (int[] deviceStateCombination : deviceStateCombinations) { 82 deviceStateEstimations.add( 83 new DeviceStateEstimation(config, deviceStateCombination)); 84 } 85 } 86 combineDeviceStateEstimations()87 private void combineDeviceStateEstimations() { 88 MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig(); 89 MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig(); 90 MultiStateStats.States[] deviceStatesTrackedPerUid = 91 new MultiStateStats.States[deviceStateConfig.length]; 92 93 for (int i = 0; i < deviceStateConfig.length; i++) { 94 if (!deviceStateConfig[i].isTracked()) { 95 continue; 96 } 97 98 int index = findTrackedStateByName(uidStateConfig, deviceStateConfig[i].getName()); 99 if (index != STATE_DOES_NOT_EXIST && uidStateConfig[index].isTracked()) { 100 deviceStatesTrackedPerUid[i] = deviceStateConfig[i]; 101 } 102 } 103 104 combineDeviceStateEstimationsRecursively(deviceStateConfig, deviceStatesTrackedPerUid, 105 new int[deviceStateConfig.length], 0); 106 } 107 combineDeviceStateEstimationsRecursively( MultiStateStats.States[] deviceStateConfig, MultiStateStats.States[] deviceStatesTrackedPerUid, int[] stateValues, int state)108 private void combineDeviceStateEstimationsRecursively( 109 MultiStateStats.States[] deviceStateConfig, 110 MultiStateStats.States[] deviceStatesTrackedPerUid, int[] stateValues, int state) { 111 if (state >= deviceStateConfig.length) { 112 DeviceStateEstimation dse = getDeviceStateEstimate(stateValues); 113 CombinedDeviceStateEstimate cdse = getCombinedDeviceStateEstimate( 114 deviceStatesTrackedPerUid, stateValues); 115 if (cdse == null) { 116 cdse = new CombinedDeviceStateEstimate(deviceStatesTrackedPerUid, stateValues); 117 combinedDeviceStateEstimations.add(cdse); 118 } 119 cdse.deviceStateEstimations.add(dse); 120 return; 121 } 122 123 if (deviceStateConfig[state].isTracked()) { 124 for (int stateValue = 0; 125 stateValue < deviceStateConfig[state].getLabels().length; 126 stateValue++) { 127 stateValues[state] = stateValue; 128 combineDeviceStateEstimationsRecursively(deviceStateConfig, 129 deviceStatesTrackedPerUid, stateValues, state + 1); 130 } 131 } else { 132 combineDeviceStateEstimationsRecursively(deviceStateConfig, 133 deviceStatesTrackedPerUid, stateValues, state + 1); 134 } 135 } 136 addUidStateEstimations()137 private void addUidStateEstimations() { 138 MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig(); 139 MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig(); 140 MultiStateStats.States[] uidStatesTrackedForDevice = 141 new MultiStateStats.States[uidStateConfig.length]; 142 MultiStateStats.States[] uidStatesNotTrackedForDevice = 143 new MultiStateStats.States[uidStateConfig.length]; 144 145 for (int i = 0; i < uidStateConfig.length; i++) { 146 if (!uidStateConfig[i].isTracked()) { 147 continue; 148 } 149 150 int index = findTrackedStateByName(deviceStateConfig, uidStateConfig[i].getName()); 151 if (index != STATE_DOES_NOT_EXIST && deviceStateConfig[index].isTracked()) { 152 uidStatesTrackedForDevice[i] = uidStateConfig[i]; 153 } else { 154 uidStatesNotTrackedForDevice[i] = uidStateConfig[i]; 155 } 156 } 157 158 @AggregatedPowerStatsConfig.TrackedState 159 int[][] uidStateCombinations = getAllTrackedStateCombinations(uidStateConfig); 160 for (int[] stateValues : uidStateCombinations) { 161 CombinedDeviceStateEstimate combined = 162 getCombinedDeviceStateEstimate(uidStatesTrackedForDevice, stateValues); 163 if (combined == null) { 164 // This is not supposed to be possible 165 Log.wtf(TAG, "Mismatch in UID and combined device states: " 166 + concatLabels(uidStatesTrackedForDevice, stateValues)); 167 continue; 168 } 169 UidStateEstimate uidStateEstimate = getUidStateEstimate(combined); 170 if (uidStateEstimate == null) { 171 uidStateEstimate = new UidStateEstimate(combined, uidStatesNotTrackedForDevice); 172 uidStateEstimates.add(uidStateEstimate); 173 } 174 uidStateEstimate.proportionalEstimates.add( 175 new UidStateProportionalEstimate(stateValues)); 176 } 177 } 178 179 @Override toString()180 public String toString() { 181 StringBuilder sb = new StringBuilder(); 182 sb.append("Step 1. Compute device-wide power estimates for state combinations:\n"); 183 for (DeviceStateEstimation deviceStateEstimation : deviceStateEstimations) { 184 sb.append(" ").append(deviceStateEstimation.id).append("\n"); 185 } 186 sb.append("Step 2. Combine device-wide estimates that are untracked per UID:\n"); 187 boolean any = false; 188 for (CombinedDeviceStateEstimate cdse : combinedDeviceStateEstimations) { 189 if (cdse.deviceStateEstimations.size() <= 1) { 190 continue; 191 } 192 any = true; 193 sb.append(" ").append(cdse.id).append(": "); 194 for (int i = 0; i < cdse.deviceStateEstimations.size(); i++) { 195 if (i != 0) { 196 sb.append(" + "); 197 } 198 sb.append(cdse.deviceStateEstimations.get(i).id); 199 } 200 sb.append("\n"); 201 } 202 if (!any) { 203 sb.append(" N/A\n"); 204 } 205 sb.append("Step 3. Proportionally distribute power estimates to UIDs:\n"); 206 for (UidStateEstimate uidStateEstimate : uidStateEstimates) { 207 sb.append(" ").append(uidStateEstimate.combinedDeviceStateEstimate.id) 208 .append("\n among: "); 209 for (int i = 0; i < uidStateEstimate.proportionalEstimates.size(); i++) { 210 UidStateProportionalEstimate uspe = 211 uidStateEstimate.proportionalEstimates.get(i); 212 if (i != 0) { 213 sb.append(", "); 214 } 215 sb.append(concatLabels(uidStateEstimate.states, uspe.stateValues)); 216 } 217 sb.append("\n"); 218 } 219 return sb.toString(); 220 } 221 222 @Nullable getDeviceStateEstimate( @ggregatedPowerStatsConfig.TrackedState int[] stateValues)223 public DeviceStateEstimation getDeviceStateEstimate( 224 @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { 225 String label = concatLabels(mConfig.getDeviceStateConfig(), stateValues); 226 for (int i = 0; i < deviceStateEstimations.size(); i++) { 227 DeviceStateEstimation deviceStateEstimation = this.deviceStateEstimations.get(i); 228 if (deviceStateEstimation.id.equals(label)) { 229 return deviceStateEstimation; 230 } 231 } 232 return null; 233 } 234 getCombinedDeviceStateEstimate( MultiStateStats.States[] deviceStates, @AggregatedPowerStatsConfig.TrackedState int[] stateValues)235 public CombinedDeviceStateEstimate getCombinedDeviceStateEstimate( 236 MultiStateStats.States[] deviceStates, 237 @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { 238 String label = concatLabels(deviceStates, stateValues); 239 for (int i = 0; i < combinedDeviceStateEstimations.size(); i++) { 240 CombinedDeviceStateEstimate cdse = combinedDeviceStateEstimations.get(i); 241 if (cdse.id.equals(label)) { 242 return cdse; 243 } 244 } 245 return null; 246 } 247 getUidStateEstimate(CombinedDeviceStateEstimate combined)248 public UidStateEstimate getUidStateEstimate(CombinedDeviceStateEstimate combined) { 249 for (int i = 0; i < uidStateEstimates.size(); i++) { 250 UidStateEstimate uidStateEstimate = uidStateEstimates.get(i); 251 if (uidStateEstimate.combinedDeviceStateEstimate == combined) { 252 return uidStateEstimate; 253 } 254 } 255 return null; 256 } 257 resetIntermediates()258 public void resetIntermediates() { 259 for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) { 260 deviceStateEstimations.get(i).intermediates = null; 261 } 262 for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) { 263 deviceStateEstimations.get(i).intermediates = null; 264 } 265 for (int i = uidStateEstimates.size() - 1; i >= 0; i--) { 266 UidStateEstimate uidStateEstimate = uidStateEstimates.get(i); 267 List<UidStateProportionalEstimate> proportionalEstimates = 268 uidStateEstimate.proportionalEstimates; 269 for (int j = proportionalEstimates.size() - 1; j >= 0; j--) { 270 proportionalEstimates.get(j).intermediates = null; 271 } 272 } 273 } 274 } 275 276 protected static class DeviceStateEstimation { 277 public final String id; 278 @AggregatedPowerStatsConfig.TrackedState 279 public final int[] stateValues; 280 public Object intermediates; 281 DeviceStateEstimation(MultiStateStats.States[] config, @AggregatedPowerStatsConfig.TrackedState int[] stateValues)282 public DeviceStateEstimation(MultiStateStats.States[] config, 283 @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { 284 id = concatLabels(config, stateValues); 285 this.stateValues = stateValues; 286 } 287 } 288 289 protected static class CombinedDeviceStateEstimate { 290 public final String id; 291 public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>(); 292 public Object intermediates; 293 CombinedDeviceStateEstimate(MultiStateStats.States[] config, @AggregatedPowerStatsConfig.TrackedState int[] stateValues)294 public CombinedDeviceStateEstimate(MultiStateStats.States[] config, 295 @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { 296 id = concatLabels(config, stateValues); 297 } 298 } 299 300 protected static class UidStateEstimate { 301 public final MultiStateStats.States[] states; 302 public CombinedDeviceStateEstimate combinedDeviceStateEstimate; 303 public List<UidStateProportionalEstimate> proportionalEstimates = new ArrayList<>(); 304 UidStateEstimate(CombinedDeviceStateEstimate combined, MultiStateStats.States[] states)305 public UidStateEstimate(CombinedDeviceStateEstimate combined, 306 MultiStateStats.States[] states) { 307 combinedDeviceStateEstimate = combined; 308 this.states = states; 309 } 310 } 311 312 protected static class UidStateProportionalEstimate { 313 @AggregatedPowerStatsConfig.TrackedState 314 public final int[] stateValues; 315 public Object intermediates; 316 UidStateProportionalEstimate( @ggregatedPowerStatsConfig.TrackedState int[] stateValues)317 protected UidStateProportionalEstimate( 318 @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { 319 this.stateValues = stateValues; 320 } 321 } 322 323 @NonNull concatLabels(MultiStateStats.States[] config, @AggregatedPowerStatsConfig.TrackedState int[] stateValues)324 private static String concatLabels(MultiStateStats.States[] config, 325 @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { 326 List<String> labels = new ArrayList<>(); 327 for (int state = 0; state < config.length; state++) { 328 if (config[state] != null && config[state].isTracked()) { 329 labels.add(config[state].getName() 330 + "=" + config[state].getLabels()[stateValues[state]]); 331 } 332 } 333 Collections.sort(labels); 334 return labels.toString(); 335 } 336 337 @AggregatedPowerStatsConfig.TrackedState getAllTrackedStateCombinations(MultiStateStats.States[] states)338 private static int[][] getAllTrackedStateCombinations(MultiStateStats.States[] states) { 339 List<int[]> combinations = new ArrayList<>(); 340 MultiStateStats.States.forEachTrackedStateCombination(states, stateValues -> { 341 combinations.add(Arrays.copyOf(stateValues, stateValues.length)); 342 }); 343 return combinations.toArray(new int[combinations.size()][0]); 344 } 345 uCtoMah(long chargeUC)346 public static double uCtoMah(long chargeUC) { 347 return chargeUC * MILLIAMPHOUR_PER_MICROCOULOMB; 348 } 349 } 350