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 android.os.BatteryStats;
19 import android.util.SparseArray;
20 
21 import com.android.internal.os.BatteryStatsHistory;
22 import com.android.internal.os.BatteryStatsHistoryIterator;
23 
24 import java.util.function.Consumer;
25 
26 /**
27  * Power stats aggregator. It reads through portions of battery stats history, finds
28  * relevant items (state changes, power stats etc) and produces one or more
29  * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history.
30  */
31 public class PowerStatsAggregator {
32     private static final long UNINITIALIZED = -1;
33     private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
34     private final BatteryStatsHistory mHistory;
35     private final SparseArray<PowerStatsProcessor> mProcessors = new SparseArray<>();
36     private AggregatedPowerStats mStats;
37     private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
38     private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
39 
PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig, BatteryStatsHistory history)40     public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
41             BatteryStatsHistory history) {
42         mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
43         mHistory = history;
44         for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig :
45                 aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) {
46             PowerStatsProcessor processor = powerComponentsConfig.getProcessor();
47             mProcessors.put(powerComponentsConfig.getPowerComponentId(), processor);
48         }
49     }
50 
getConfig()51     AggregatedPowerStatsConfig getConfig() {
52         return mAggregatedPowerStatsConfig;
53     }
54 
55     /**
56      * Iterates of the battery history and aggregates power stats between the specified times.
57      * The start and end are specified in the battery-stats monotonic time, which is the
58      * adjusted elapsed time found in HistoryItem.time.
59      * <p>
60      * The aggregated stats are sent to the consumer. One aggregation pass may produce
61      * multiple sets of aggregated stats if there was an incompatible change that occurred in the
62      * middle of the recorded battery history.
63      * <p>
64      * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
65      * the stats in the <code>accept</code> method and never cache it.
66      */
aggregatePowerStats(long startTimeMs, long endTimeMs, Consumer<AggregatedPowerStats> consumer)67     public void aggregatePowerStats(long startTimeMs, long endTimeMs,
68             Consumer<AggregatedPowerStats> consumer) {
69         synchronized (this) {
70             if (mStats == null) {
71                 mStats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
72             }
73 
74             start(mStats, startTimeMs);
75 
76             boolean clockUpdateAdded = false;
77             long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED;
78             long lastTime = 0;
79             int lastStates = 0xFFFFFFFF;
80             int lastStates2 = 0xFFFFFFFF;
81             try (BatteryStatsHistoryIterator iterator = mHistory.iterate(startTimeMs, endTimeMs)) {
82                 while (iterator.hasNext()) {
83                     BatteryStats.HistoryItem item = iterator.next();
84 
85                     if (!clockUpdateAdded) {
86                         mStats.addClockUpdate(item.time, item.currentTime);
87                         if (baseTime == UNINITIALIZED) {
88                             baseTime = item.time;
89                         }
90                         clockUpdateAdded = true;
91                     } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
92                                || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
93                         mStats.addClockUpdate(item.time, item.currentTime);
94                     }
95 
96                     lastTime = item.time;
97 
98                     int batteryState =
99                             (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
100                                     ? AggregatedPowerStatsConfig.POWER_STATE_OTHER
101                                     : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
102                     if (batteryState != mCurrentBatteryState) {
103                         mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState,
104                                 item.time);
105                         mCurrentBatteryState = batteryState;
106                     }
107 
108                     int screenState =
109                             (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
110                                     ? AggregatedPowerStatsConfig.SCREEN_STATE_ON
111                                     : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
112                     if (screenState != mCurrentScreenState) {
113                         mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState,
114                                 item.time);
115                         mCurrentScreenState = screenState;
116                     }
117 
118                     if ((item.states
119                             & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES)
120                             != lastStates
121                             || (item.states2
122                             & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES2)
123                             != lastStates2) {
124                         mStats.noteStateChange(item);
125                         lastStates = item.states
126                                 & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES;
127                         lastStates2 = item.states2
128                                 & BatteryStats.HistoryItem.IMPORTANT_FOR_POWER_STATS_STATES2;
129                     }
130 
131                     if (item.processStateChange != null) {
132                         mStats.setUidState(item.processStateChange.uid,
133                                 AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
134                                 item.processStateChange.processState, item.time);
135                     }
136 
137                     if (item.powerStats != null) {
138                         if (!mStats.isCompatible(item.powerStats)) {
139                             if (lastTime > baseTime) {
140                                 mStats.setDuration(lastTime - baseTime);
141                                 finish(mStats, lastTime);
142                                 consumer.accept(mStats);
143                             }
144                             mStats.reset();
145                             mStats.addClockUpdate(item.time, item.currentTime);
146                             baseTime = lastTime = item.time;
147                         }
148                         mStats.addPowerStats(item.powerStats, item.time);
149                     }
150                 }
151             }
152             if (lastTime > baseTime) {
153                 mStats.setDuration(lastTime - baseTime);
154                 finish(mStats, lastTime);
155                 consumer.accept(mStats);
156             }
157 
158             mStats.reset();     // to free up memory
159         }
160     }
161 
start(AggregatedPowerStats stats, long timestampMs)162     private void start(AggregatedPowerStats stats, long timestampMs) {
163         for (int i = 0; i < mProcessors.size(); i++) {
164             PowerComponentAggregatedPowerStats component =
165                     stats.getPowerComponentStats(mProcessors.keyAt(i));
166             if (component != null) {
167                 mProcessors.valueAt(i).start(component, timestampMs);
168             }
169         }
170     }
171 
finish(AggregatedPowerStats stats, long timestampMs)172     private void finish(AggregatedPowerStats stats, long timestampMs) {
173         for (int i = 0; i < mProcessors.size(); i++) {
174             PowerComponentAggregatedPowerStats component =
175                     stats.getPowerComponentStats(mProcessors.keyAt(i));
176             if (component != null) {
177                 mProcessors.valueAt(i).finish(component, timestampMs);
178             }
179         }
180     }
181 
182     /**
183      * Reset to prepare for a new aggregation session.
184      */
reset()185     public void reset() {
186         synchronized (this) {
187             mStats = null;
188         }
189     }
190 }
191