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;
18 
19 import android.annotation.CurrentTimeMillisLong;
20 import android.annotation.DurationMillisLong;
21 import android.annotation.NonNull;
22 import android.os.BatteryStats;
23 import android.os.UserHandle;
24 import android.text.format.DateFormat;
25 import android.util.IndentingPrintWriter;
26 import android.util.Slog;
27 import android.util.TimeUtils;
28 
29 import com.android.internal.os.PowerStats;
30 import com.android.modules.utils.TypedXmlPullParser;
31 import com.android.modules.utils.TypedXmlSerializer;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 
36 import java.io.IOException;
37 import java.io.StringWriter;
38 import java.text.SimpleDateFormat;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Date;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Set;
45 import java.util.TimeZone;
46 
47 /**
48  * This class represents aggregated power stats for a variety of power components (CPU, WiFi,
49  * etc) covering a specific period of power usage history.
50  */
51 class AggregatedPowerStats {
52     private static final String TAG = "AggregatedPowerStats";
53     private static final int MAX_CLOCK_UPDATES = 100;
54     private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats";
55 
56     private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
57 
58     static class ClockUpdate {
59         public long monotonicTime;
60         @CurrentTimeMillisLong public long currentTime;
61     }
62 
63     private final List<ClockUpdate> mClockUpdates = new ArrayList<>();
64 
65     @DurationMillisLong
66     private long mDurationMs;
67 
AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig)68     AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
69         List<AggregatedPowerStatsConfig.PowerComponent> configs =
70                 aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
71         mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
72         for (int i = 0; i < configs.size(); i++) {
73             mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(this, configs.get(i));
74         }
75     }
76 
77     /**
78      * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change,
79      * there may be multiple clock updates in one set of aggregated stats.
80      *
81      * @param monotonicTime monotonic time in milliseconds, see
82      * {@link com.android.internal.os.MonotonicClock}
83      * @param currentTime   current time in milliseconds, see {@link System#currentTimeMillis()}
84      */
addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime)85     void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
86         ClockUpdate clockUpdate = new ClockUpdate();
87         clockUpdate.monotonicTime = monotonicTime;
88         clockUpdate.currentTime = currentTime;
89         if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
90             mClockUpdates.add(clockUpdate);
91         } else {
92             Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
93                         + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
94             mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
95         }
96     }
97 
98     /**
99      * Start time according to {@link com.android.internal.os.MonotonicClock}
100      */
getStartTime()101     long getStartTime() {
102         if (mClockUpdates.isEmpty()) {
103             return 0;
104         } else {
105             return mClockUpdates.get(0).monotonicTime;
106         }
107     }
108 
getClockUpdates()109     List<ClockUpdate> getClockUpdates() {
110         return mClockUpdates;
111     }
112 
setDuration(long durationMs)113     void setDuration(long durationMs) {
114         mDurationMs = durationMs;
115     }
116 
117     @DurationMillisLong
getDuration()118     public long getDuration() {
119         return mDurationMs;
120     }
121 
getPowerComponentStats(int powerComponentId)122     PowerComponentAggregatedPowerStats getPowerComponentStats(int powerComponentId) {
123         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
124             if (stats.powerComponentId == powerComponentId) {
125                 return stats;
126             }
127         }
128         return null;
129     }
130 
setDeviceState(@ggregatedPowerStatsConfig.TrackedState int stateId, int state, long time)131     void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
132             long time) {
133         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
134             stats.setState(stateId, state, time);
135         }
136     }
137 
setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time)138     void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
139             long time) {
140         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
141             stats.setUidState(uid, stateId, state, time);
142         }
143     }
144 
isCompatible(PowerStats powerStats)145     boolean isCompatible(PowerStats powerStats) {
146         int powerComponentId = powerStats.descriptor.powerComponentId;
147         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
148             if (stats.powerComponentId == powerComponentId && !stats.isCompatible(powerStats)) {
149                 return false;
150             }
151         }
152         return true;
153     }
154 
addPowerStats(PowerStats powerStats, long time)155     void addPowerStats(PowerStats powerStats, long time) {
156         int powerComponentId = powerStats.descriptor.powerComponentId;
157         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
158             if (stats.powerComponentId == powerComponentId) {
159                 stats.getConfig().getProcessor().addPowerStats(stats, powerStats, time);
160             }
161         }
162     }
163 
noteStateChange(BatteryStats.HistoryItem item)164     public void noteStateChange(BatteryStats.HistoryItem item) {
165         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
166             stats.getConfig().getProcessor().noteStateChange(stats, item);
167         }
168     }
169 
reset()170     void reset() {
171         mClockUpdates.clear();
172         mDurationMs = 0;
173         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
174             stats.reset();
175         }
176     }
177 
writeXml(TypedXmlSerializer serializer)178     public void writeXml(TypedXmlSerializer serializer) throws IOException {
179         serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS);
180         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
181             stats.writeXml(serializer);
182         }
183         serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS);
184         serializer.flush();
185     }
186 
187     @NonNull
createFromXml( TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)188     public static AggregatedPowerStats createFromXml(
189             TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)
190             throws XmlPullParserException, IOException {
191         AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
192         boolean inElement = false;
193         boolean skipToEnd = false;
194         int eventType = parser.getEventType();
195         while (eventType != XmlPullParser.END_DOCUMENT
196                    && !(eventType == XmlPullParser.END_TAG
197                         && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) {
198             if (!skipToEnd && eventType == XmlPullParser.START_TAG) {
199                 switch (parser.getName()) {
200                     case XML_TAG_AGGREGATED_POWER_STATS:
201                         inElement = true;
202                         break;
203                     case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT:
204                         if (!inElement) {
205                             break;
206                         }
207 
208                         int powerComponentId = parser.getAttributeInt(null,
209                                 PowerComponentAggregatedPowerStats.XML_ATTR_ID);
210                         for (PowerComponentAggregatedPowerStats powerComponent :
211                                 stats.mPowerComponentStats) {
212                             if (powerComponent.powerComponentId == powerComponentId) {
213                                 if (!powerComponent.readFromXml(parser)) {
214                                     skipToEnd = true;
215                                 }
216                                 break;
217                             }
218                         }
219                         break;
220                 }
221             }
222             eventType = parser.next();
223         }
224         return stats;
225     }
226 
dump(IndentingPrintWriter ipw)227     void dump(IndentingPrintWriter ipw) {
228         StringBuilder sb = new StringBuilder();
229         long baseTime = 0;
230         for (int i = 0; i < mClockUpdates.size(); i++) {
231             ClockUpdate clockUpdate = mClockUpdates.get(i);
232             sb.setLength(0);
233             if (i == 0) {
234                 baseTime = clockUpdate.monotonicTime;
235                 sb.append("Start time: ")
236                         .append(formatDateTime(clockUpdate.currentTime))
237                         .append(" (")
238                         .append(baseTime)
239                         .append(") duration: ")
240                         .append(mDurationMs);
241                 ipw.println(sb);
242             } else {
243                 sb.setLength(0);
244                 sb.append("Clock update:  ");
245                 TimeUtils.formatDuration(
246                         clockUpdate.monotonicTime - baseTime, sb,
247                         TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
248                 sb.append(" ").append(formatDateTime(clockUpdate.currentTime));
249                 ipw.increaseIndent();
250                 ipw.println(sb);
251                 ipw.decreaseIndent();
252             }
253         }
254 
255         ipw.println("Device");
256         ipw.increaseIndent();
257         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
258             stats.dumpDevice(ipw);
259         }
260         ipw.decreaseIndent();
261 
262         Set<Integer> uids = new HashSet<>();
263         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
264             stats.collectUids(uids);
265         }
266 
267         Integer[] allUids = uids.toArray(new Integer[uids.size()]);
268         Arrays.sort(allUids);
269         for (int uid : allUids) {
270             ipw.println(UserHandle.formatUid(uid));
271             ipw.increaseIndent();
272             for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
273                 stats.dumpUid(ipw, uid);
274             }
275             ipw.decreaseIndent();
276         }
277     }
278 
formatDateTime(long timeInMillis)279     private static String formatDateTime(long timeInMillis) {
280         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
281         format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT"));
282         return format.format(new Date(timeInMillis));
283     }
284 
285     @Override
toString()286     public String toString() {
287         StringWriter sw = new StringWriter();
288         dump(new IndentingPrintWriter(sw));
289         return sw.toString();
290     }
291 }
292