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