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