• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.power.stats.processor;
18 
19 import android.annotation.CurrentTimeMillisLong;
20 import android.annotation.DurationMillisLong;
21 import android.annotation.NonNull;
22 import android.os.BatteryConsumer;
23 import android.os.BatteryStats;
24 import android.os.PersistableBundle;
25 import android.os.UserHandle;
26 import android.text.format.DateFormat;
27 import android.util.IndentingPrintWriter;
28 import android.util.IntArray;
29 import android.util.Slog;
30 import android.util.SparseArray;
31 import android.util.SparseBooleanArray;
32 import android.util.TimeUtils;
33 
34 import com.android.internal.os.PowerStats;
35 import com.android.modules.utils.TypedXmlPullParser;
36 import com.android.modules.utils.TypedXmlSerializer;
37 import com.android.server.power.stats.processor.AggregatedPowerStatsConfig.PowerComponent;
38 
39 import org.xmlpull.v1.XmlPullParser;
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 import java.io.IOException;
43 import java.io.StringWriter;
44 import java.text.SimpleDateFormat;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Date;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Set;
51 import java.util.TimeZone;
52 
53 /**
54  * This class represents aggregated power stats for a variety of power components (CPU, WiFi,
55  * etc) covering a specific period of power usage history.
56  */
57 class AggregatedPowerStats {
58     private static final String TAG = "AggregatedPowerStats";
59     private static final int MAX_CLOCK_UPDATES = 100;
60     private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats";
61 
62     private final AggregatedPowerStatsConfig mConfig;
63     private final SparseArray<PowerComponentAggregatedPowerStats> mPowerComponentStats;
64     private final PowerComponentAggregatedPowerStats mGenericPowerComponent;
65 
66     static class ClockUpdate {
67         public long monotonicTime;
68         @CurrentTimeMillisLong
69         public long currentTime;
70     }
71 
72     private final List<ClockUpdate> mClockUpdates = new ArrayList<>();
73 
74     @DurationMillisLong
75     private long mDurationMs;
76 
AggregatedPowerStats(@onNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig)77     AggregatedPowerStats(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
78         this(aggregatedPowerStatsConfig, new SparseBooleanArray());
79     }
80 
AggregatedPowerStats(@onNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig, @NonNull SparseBooleanArray enabledComponents)81     AggregatedPowerStats(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
82             @NonNull SparseBooleanArray enabledComponents) {
83         mConfig = aggregatedPowerStatsConfig;
84         List<PowerComponent> configs =
85                 aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
86         mPowerComponentStats = new SparseArray<>(configs.size());
87         for (int i = 0; i < configs.size(); i++) {
88             PowerComponent powerComponent = configs.get(i);
89             if (enabledComponents.get(powerComponent.getPowerComponentId(), true)) {
90                 mPowerComponentStats.put(powerComponent.getPowerComponentId(),
91                         new PowerComponentAggregatedPowerStats(this, powerComponent));
92             }
93         }
94         mGenericPowerComponent = createGenericPowerComponent();
95         mPowerComponentStats.put(BatteryConsumer.POWER_COMPONENT_ANY, mGenericPowerComponent);
96     }
97 
createGenericPowerComponent()98     private PowerComponentAggregatedPowerStats createGenericPowerComponent() {
99         PowerComponent config = new PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY);
100         config.trackDeviceStates(
101                         AggregatedPowerStatsConfig.STATE_POWER,
102                         AggregatedPowerStatsConfig.STATE_SCREEN)
103                 .trackUidStates(
104                         AggregatedPowerStatsConfig.STATE_POWER,
105                         AggregatedPowerStatsConfig.STATE_SCREEN,
106                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
107         PowerComponentAggregatedPowerStats stats =
108                 new PowerComponentAggregatedPowerStats(this, config);
109         stats.setPowerStatsDescriptor(
110                 new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_ANY, 0, null, 0, 0,
111                         new PersistableBundle()));
112         return stats;
113     }
114 
115     /**
116      * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change,
117      * there may be multiple clock updates in one set of aggregated stats.
118      *
119      * @param monotonicTime monotonic time in milliseconds, see
120      *                      {@link com.android.internal.os.MonotonicClock}
121      * @param currentTime   current time in milliseconds, see {@link System#currentTimeMillis()}
122      */
addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime)123     boolean addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
124         ClockUpdate clockUpdate = new ClockUpdate();
125         clockUpdate.monotonicTime = monotonicTime;
126         clockUpdate.currentTime = currentTime;
127         if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
128             mClockUpdates.add(clockUpdate);
129             return true;
130         } else {
131             Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
132                     + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
133             mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
134             return false;
135         }
136     }
137 
138     /**
139      * Start time according to {@link com.android.internal.os.MonotonicClock}
140      */
getStartTime()141     long getStartTime() {
142         if (mClockUpdates.isEmpty()) {
143             return 0;
144         } else {
145             return mClockUpdates.get(0).monotonicTime;
146         }
147     }
148 
149     // TODO - DO NOT SUBMIT public
getClockUpdates()150     public List<ClockUpdate> getClockUpdates() {
151         return mClockUpdates;
152     }
153 
setDuration(long durationMs)154     void setDuration(long durationMs) {
155         mDurationMs = durationMs;
156     }
157 
158     @DurationMillisLong
getDuration()159     public long getDuration() {
160         return mDurationMs;
161     }
162 
getPowerComponentStats()163     List<PowerComponentAggregatedPowerStats> getPowerComponentStats() {
164         List<PowerComponentAggregatedPowerStats> list = new ArrayList<>(
165                 mPowerComponentStats.size());
166         for (int i = 0; i < mPowerComponentStats.size(); i++) {
167             PowerComponentAggregatedPowerStats stats = mPowerComponentStats.valueAt(i);
168             if (stats != mGenericPowerComponent) {
169                 list.add(stats);
170             }
171         }
172         return list;
173     }
174 
getPowerComponentStats(int powerComponentId)175     PowerComponentAggregatedPowerStats getPowerComponentStats(int powerComponentId) {
176         return mPowerComponentStats.get(powerComponentId);
177     }
178 
start(long timestampMs)179     void start(long timestampMs) {
180         for (int i = 0; i < mPowerComponentStats.size(); i++) {
181             mPowerComponentStats.valueAt(i).start(timestampMs);
182         }
183     }
184 
setDeviceState(@ggregatedPowerStatsConfig.TrackedState int stateId, int state, long time)185     void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
186             long time) {
187         for (int i = 0; i < mPowerComponentStats.size(); i++) {
188             mPowerComponentStats.valueAt(i).setState(stateId, state, time);
189         }
190     }
191 
setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time)192     void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
193             long time) {
194         for (int i = 0; i < mPowerComponentStats.size(); i++) {
195             mPowerComponentStats.valueAt(i).setUidState(uid, stateId, state, time);
196         }
197     }
198 
isCompatible(PowerStats powerStats)199     boolean isCompatible(PowerStats powerStats) {
200         int powerComponentId = powerStats.descriptor.powerComponentId;
201         PowerComponentAggregatedPowerStats stats = mPowerComponentStats.get(powerComponentId);
202         return stats != null && stats.isCompatible(powerStats);
203     }
204 
addPowerStats(PowerStats powerStats, long time)205     void addPowerStats(PowerStats powerStats, long time) {
206         int powerComponentId = powerStats.descriptor.powerComponentId;
207         PowerComponentAggregatedPowerStats stats = mPowerComponentStats.get(powerComponentId);
208         if (stats == null) {
209             PowerComponent powerComponent = mConfig.createPowerComponent(powerComponentId);
210             if (powerComponent == null) {
211                 return;
212             }
213 
214             stats = new PowerComponentAggregatedPowerStats(this, powerComponent);
215             stats.setPowerStatsDescriptor(powerStats.descriptor);
216             stats.copyStatesFrom(mGenericPowerComponent);
217             stats.start(time);
218             mPowerComponentStats.put(powerComponentId, stats);
219         }
220 
221         stats.addPowerStats(powerStats, time);
222     }
223 
noteStateChange(BatteryStats.HistoryItem item)224     public void noteStateChange(BatteryStats.HistoryItem item) {
225         for (int i = 0; i < mPowerComponentStats.size(); i++) {
226             mPowerComponentStats.valueAt(i).noteStateChange(item);
227         }
228     }
229 
noteBatteryLevel(int batteryLevel, int batteryChargeUah, long timestampMs)230     public void noteBatteryLevel(int batteryLevel, int batteryChargeUah, long timestampMs) {
231         for (int i = 0; i < mPowerComponentStats.size(); i++) {
232             mPowerComponentStats.valueAt(i).noteBatteryLevel(batteryLevel, batteryChargeUah,
233                     timestampMs);
234         }
235     }
236 
finish(long timestampMs)237     void finish(long timestampMs) {
238         for (int i = 0; i < mPowerComponentStats.size(); i++) {
239             PowerComponentAggregatedPowerStats component = mPowerComponentStats.valueAt(i);
240             component.finish(timestampMs);
241         }
242     }
243 
reset()244     void reset() {
245         mClockUpdates.clear();
246         mDurationMs = 0;
247         for (int i = 0; i < mPowerComponentStats.size(); i++) {
248             mPowerComponentStats.valueAt(i).reset();
249         }
250     }
251 
writeXml(TypedXmlSerializer serializer)252     public void writeXml(TypedXmlSerializer serializer) throws IOException {
253         serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS);
254         for (int i = 0; i < mPowerComponentStats.size(); i++) {
255             PowerComponentAggregatedPowerStats stats = mPowerComponentStats.valueAt(i);
256             if (stats != mGenericPowerComponent) {
257                 stats.writeXml(serializer);
258             }
259         }
260         serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS);
261         serializer.flush();
262     }
263 
264     @NonNull
createFromXml( TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)265     public static AggregatedPowerStats createFromXml(
266             TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)
267             throws XmlPullParserException, IOException {
268         AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
269         boolean inElement = false;
270         boolean skipToEnd = false;
271         int eventType = parser.getEventType();
272         while (eventType != XmlPullParser.END_DOCUMENT
273                    && !(eventType == XmlPullParser.END_TAG
274                         && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) {
275             if (!skipToEnd && eventType == XmlPullParser.START_TAG) {
276                 switch (parser.getName()) {
277                     case XML_TAG_AGGREGATED_POWER_STATS:
278                         inElement = true;
279                         break;
280                     case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT: {
281                         if (!inElement) {
282                             break;
283                         }
284 
285                         int powerComponentId = parser.getAttributeInt(null,
286                                 PowerComponentAggregatedPowerStats.XML_ATTR_ID);
287 
288                         PowerComponentAggregatedPowerStats
289                                 powerComponentStats =
290                                 stats.getPowerComponentStats(powerComponentId);
291                         if (powerComponentStats == null) {
292                             PowerComponent powerComponent =
293                                     aggregatedPowerStatsConfig.createPowerComponent(
294                                             powerComponentId);
295                             if (powerComponent != null) {
296                                 powerComponentStats = new PowerComponentAggregatedPowerStats(stats,
297                                         powerComponent);
298                                 stats.mPowerComponentStats.put(powerComponentId,
299                                         powerComponentStats);
300                             }
301                         }
302                         if (powerComponentStats != null) {
303                             if (!powerComponentStats.readFromXml(parser)) {
304                                 skipToEnd = true;
305                             }
306                         }
307                         break;
308                     }
309                 }
310             }
311             eventType = parser.next();
312         }
313         return stats;
314     }
315 
dump(IndentingPrintWriter ipw)316     void dump(IndentingPrintWriter ipw) {
317         StringBuilder sb = new StringBuilder();
318         long baseTime = 0;
319         for (int i = 0; i < mClockUpdates.size(); i++) {
320             ClockUpdate clockUpdate = mClockUpdates.get(i);
321             sb.setLength(0);
322             if (i == 0) {
323                 baseTime = clockUpdate.monotonicTime;
324                 sb.append("Start time: ")
325                         .append(formatDateTime(clockUpdate.currentTime))
326                         .append(" (")
327                         .append(baseTime)
328                         .append(") duration: ")
329                         .append(mDurationMs);
330                 ipw.println(sb);
331             } else {
332                 sb.setLength(0);
333                 sb.append("Clock update:  ");
334                 TimeUtils.formatDuration(
335                         clockUpdate.monotonicTime - baseTime, sb,
336                         TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
337                 sb.append(" ").append(formatDateTime(clockUpdate.currentTime));
338                 ipw.increaseIndent();
339                 ipw.println(sb);
340                 ipw.decreaseIndent();
341             }
342         }
343 
344         ipw.println("Device");
345         ipw.increaseIndent();
346         for (int i = 0; i < mPowerComponentStats.size(); i++) {
347             mPowerComponentStats.valueAt(i).dumpDevice(ipw);
348         }
349         ipw.decreaseIndent();
350 
351         Set<Integer> uids = new HashSet<>();
352         for (int i = 0; i < mPowerComponentStats.size(); i++) {
353             IntArray activeUids = mPowerComponentStats.valueAt(i).getActiveUids();
354             for (int j = activeUids.size() - 1; j >= 0; j--) {
355                 uids.add(activeUids.get(j));
356             }
357         }
358 
359         Integer[] allUids = uids.toArray(new Integer[uids.size()]);
360         Arrays.sort(allUids);
361         for (int uid : allUids) {
362             ipw.println(UserHandle.formatUid(uid));
363             ipw.increaseIndent();
364             for (int i = 0; i < mPowerComponentStats.size(); i++) {
365                 mPowerComponentStats.valueAt(i).dumpUid(ipw, uid);
366             }
367             ipw.decreaseIndent();
368         }
369     }
370 
formatDateTime(long timeInMillis)371     private static String formatDateTime(long timeInMillis) {
372         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
373         format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT"));
374         return format.format(new Date(timeInMillis));
375     }
376 
377     @Override
toString()378     public String toString() {
379         StringWriter sw = new StringWriter();
380         dump(new IndentingPrintWriter(sw));
381         return sw.toString();
382     }
383 }
384