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.annotation.Nullable; 23 import android.util.IndentingPrintWriter; 24 import android.util.Slog; 25 import android.util.TimeUtils; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.modules.utils.TypedXmlPullParser; 29 import com.android.modules.utils.TypedXmlSerializer; 30 31 import com.google.android.collect.Sets; 32 33 import org.xmlpull.v1.XmlPullParser; 34 import org.xmlpull.v1.XmlPullParserException; 35 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.io.OutputStream; 39 import java.io.StringWriter; 40 import java.nio.charset.StandardCharsets; 41 import java.time.Instant; 42 import java.time.ZoneId; 43 import java.time.format.DateTimeFormatter; 44 import java.util.ArrayList; 45 import java.util.Comparator; 46 import java.util.List; 47 import java.util.Set; 48 49 /** 50 * Contains power stats of various kinds, aggregated over a time span. 51 */ 52 public class PowerStatsSpan { 53 private static final String TAG = "PowerStatsStore"; 54 55 /** 56 * Increment VERSION when the XML format of the store changes. Also, update 57 * {@link #isCompatibleXmlFormat} to return true for all legacy versions 58 * that are compatible with the new one. 59 */ 60 private static final int VERSION = 2; 61 62 private static final String XML_TAG_METADATA = "metadata"; 63 private static final String XML_ATTR_ID = "id"; 64 private static final String XML_ATTR_VERSION = "version"; 65 private static final String XML_TAG_TIMEFRAME = "timeframe"; 66 private static final String XML_ATTR_MONOTONIC = "monotonic"; 67 private static final String XML_ATTR_START_TIME = "start"; 68 private static final String XML_ATTR_DURATION = "duration"; 69 private static final String XML_TAG_SECTION = "section"; 70 private static final String XML_ATTR_SECTION_TYPE = "type"; 71 72 private static final DateTimeFormatter DATE_FORMAT = 73 DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); 74 75 static class TimeFrame { 76 public final long startMonotonicTime; 77 @CurrentTimeMillisLong 78 public final long startTime; 79 @DurationMillisLong 80 public final long duration; 81 TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime, @DurationMillisLong long duration)82 TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime, 83 @DurationMillisLong long duration) { 84 this.startMonotonicTime = startMonotonicTime; 85 this.startTime = startTime; 86 this.duration = duration; 87 } 88 write(TypedXmlSerializer serializer)89 void write(TypedXmlSerializer serializer) throws IOException { 90 serializer.startTag(null, XML_TAG_TIMEFRAME); 91 serializer.attributeLong(null, XML_ATTR_START_TIME, startTime); 92 serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime); 93 serializer.attributeLong(null, XML_ATTR_DURATION, duration); 94 serializer.endTag(null, XML_TAG_TIMEFRAME); 95 } 96 read(TypedXmlPullParser parser)97 static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException { 98 return new TimeFrame( 99 parser.getAttributeLong(null, XML_ATTR_MONOTONIC), 100 parser.getAttributeLong(null, XML_ATTR_START_TIME), 101 parser.getAttributeLong(null, XML_ATTR_DURATION)); 102 } 103 104 /** 105 * Prints the contents of this TimeFrame. 106 */ dump(IndentingPrintWriter pw)107 public void dump(IndentingPrintWriter pw) { 108 StringBuilder sb = new StringBuilder(); 109 sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime))) 110 .append(" (monotonic=").append(startMonotonicTime).append(") ") 111 .append(" duration="); 112 String durationString = TimeUtils.formatDuration(duration); 113 if (durationString.startsWith("+")) { 114 sb.append(durationString.substring(1)); 115 } else { 116 sb.append(durationString); 117 } 118 pw.print(sb); 119 } 120 } 121 122 static class Metadata { 123 static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId); 124 125 private final long mId; 126 private final List<TimeFrame> mTimeFrames = new ArrayList<>(); 127 private final List<String> mSections = new ArrayList<>(); 128 Metadata(long id)129 Metadata(long id) { 130 mId = id; 131 } 132 getId()133 public long getId() { 134 return mId; 135 } 136 getTimeFrames()137 public List<TimeFrame> getTimeFrames() { 138 return mTimeFrames; 139 } 140 getSections()141 public List<String> getSections() { 142 return mSections; 143 } 144 addTimeFrame(TimeFrame timeFrame)145 void addTimeFrame(TimeFrame timeFrame) { 146 mTimeFrames.add(timeFrame); 147 } 148 addSection(String sectionType)149 void addSection(String sectionType) { 150 // The number of sections per span is small, so there is no need to use a Set 151 if (!mSections.contains(sectionType)) { 152 mSections.add(sectionType); 153 } 154 } 155 write(TypedXmlSerializer serializer)156 void write(TypedXmlSerializer serializer) throws IOException { 157 serializer.startTag(null, XML_TAG_METADATA); 158 serializer.attributeLong(null, XML_ATTR_ID, mId); 159 serializer.attributeInt(null, XML_ATTR_VERSION, VERSION); 160 for (TimeFrame timeFrame : mTimeFrames) { 161 timeFrame.write(serializer); 162 } 163 for (String section : mSections) { 164 serializer.startTag(null, XML_TAG_SECTION); 165 serializer.attribute(null, XML_ATTR_SECTION_TYPE, section); 166 serializer.endTag(null, XML_TAG_SECTION); 167 } 168 serializer.endTag(null, XML_TAG_METADATA); 169 } 170 171 /** 172 * Reads just the header of the XML file containing metadata. 173 * Returns null if the file does not contain a compatible <metadata> element. 174 */ 175 @Nullable read(TypedXmlPullParser parser)176 public static Metadata read(TypedXmlPullParser parser) 177 throws IOException, XmlPullParserException { 178 Metadata metadata = null; 179 int eventType = parser.getEventType(); 180 while (eventType != XmlPullParser.END_DOCUMENT 181 && !(eventType == XmlPullParser.END_TAG 182 && parser.getName().equals(XML_TAG_METADATA))) { 183 if (eventType == XmlPullParser.START_TAG) { 184 String tagName = parser.getName(); 185 if (tagName.equals(XML_TAG_METADATA)) { 186 int version = parser.getAttributeInt(null, XML_ATTR_VERSION); 187 if (!isCompatibleXmlFormat(version)) { 188 Slog.e(TAG, 189 "Incompatible version " + version + "; expected " + VERSION); 190 return null; 191 } 192 193 long id = parser.getAttributeLong(null, XML_ATTR_ID); 194 metadata = new Metadata(id); 195 } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) { 196 metadata.addTimeFrame(TimeFrame.read(parser)); 197 } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) { 198 metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE)); 199 } 200 } 201 eventType = parser.next(); 202 } 203 return metadata; 204 } 205 206 /** 207 * Prints the metadata. 208 */ dump(IndentingPrintWriter pw)209 public void dump(IndentingPrintWriter pw) { 210 dump(pw, true); 211 } 212 dump(IndentingPrintWriter pw, boolean includeSections)213 void dump(IndentingPrintWriter pw, boolean includeSections) { 214 pw.print("Span "); 215 if (mTimeFrames.size() > 0) { 216 mTimeFrames.get(0).dump(pw); 217 pw.println(); 218 } 219 220 // Sometimes, when the wall clock is adjusted in the middle of a stats session, 221 // we will have more than one time frame. 222 for (int i = 1; i < mTimeFrames.size(); i++) { 223 TimeFrame timeFrame = mTimeFrames.get(i); 224 pw.print(" "); // Aligned below "Span " 225 timeFrame.dump(pw); 226 pw.println(); 227 } 228 229 if (includeSections) { 230 pw.increaseIndent(); 231 for (String section : mSections) { 232 pw.print("section", section); 233 pw.println(); 234 } 235 pw.decreaseIndent(); 236 } 237 } 238 239 @Override toString()240 public String toString() { 241 StringWriter sw = new StringWriter(); 242 IndentingPrintWriter ipw = new IndentingPrintWriter(sw); 243 ipw.print("id", mId); 244 for (int i = 0; i < mTimeFrames.size(); i++) { 245 TimeFrame timeFrame = mTimeFrames.get(i); 246 ipw.print("timeframe=["); 247 timeFrame.dump(ipw); 248 ipw.print("] "); 249 } 250 for (String section : mSections) { 251 ipw.print("section", section); 252 } 253 ipw.flush(); 254 return sw.toString().trim(); 255 } 256 } 257 258 /** 259 * Contains a specific type of aggregate power stats. The contents type is determined by 260 * the section type. 261 */ 262 public abstract static class Section { 263 private final String mType; 264 Section(String type)265 Section(String type) { 266 mType = type; 267 } 268 269 /** 270 * Returns the section type, which determines the type of data stored in the corresponding 271 * section of {@link PowerStatsSpan} 272 */ getType()273 public String getType() { 274 return mType; 275 } 276 write(TypedXmlSerializer serializer)277 abstract void write(TypedXmlSerializer serializer) throws IOException; 278 279 /** 280 * Prints the section type. 281 */ dump(IndentingPrintWriter ipw)282 public void dump(IndentingPrintWriter ipw) { 283 ipw.println(mType); 284 } 285 } 286 287 /** 288 * A universal XML parser for {@link PowerStatsSpan.Section}'s. It is aware of all 289 * supported section types as well as their corresponding XML formats. 290 */ 291 public interface SectionReader { 292 /** 293 * Reads the contents of the section using the parser. The type of the object 294 * read and the corresponding XML format are determined by the section type. 295 */ read(String sectionType, TypedXmlPullParser parser)296 Section read(String sectionType, TypedXmlPullParser parser) 297 throws IOException, XmlPullParserException; 298 } 299 300 private final Metadata mMetadata; 301 private final List<Section> mSections = new ArrayList<>(); 302 PowerStatsSpan(long id)303 public PowerStatsSpan(long id) { 304 this(new Metadata(id)); 305 } 306 PowerStatsSpan(Metadata metadata)307 private PowerStatsSpan(Metadata metadata) { 308 mMetadata = metadata; 309 } 310 getMetadata()311 public Metadata getMetadata() { 312 return mMetadata; 313 } 314 getId()315 public long getId() { 316 return mMetadata.mId; 317 } 318 addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime, @DurationMillisLong long duration)319 void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime, 320 @DurationMillisLong long duration) { 321 mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration)); 322 } 323 addSection(Section section)324 void addSection(Section section) { 325 mMetadata.addSection(section.getType()); 326 mSections.add(section); 327 } 328 329 @NonNull getSections()330 public List<Section> getSections() { 331 return mSections; 332 } 333 isCompatibleXmlFormat(int version)334 private static boolean isCompatibleXmlFormat(int version) { 335 return version == VERSION; 336 } 337 338 /** 339 * Creates an XML file containing the persistent state of the power stats span. 340 */ 341 @VisibleForTesting writeXml(OutputStream out, TypedXmlSerializer serializer)342 public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { 343 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 344 serializer.startDocument(null, true); 345 mMetadata.write(serializer); 346 for (Section section : mSections) { 347 serializer.startTag(null, XML_TAG_SECTION); 348 serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType); 349 section.write(serializer); 350 serializer.endTag(null, XML_TAG_SECTION); 351 } 352 serializer.endDocument(); 353 } 354 355 @Nullable read(InputStream in, TypedXmlPullParser parser, SectionReader sectionReader, String... sectionTypes)356 static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser, 357 SectionReader sectionReader, String... sectionTypes) 358 throws IOException, XmlPullParserException { 359 Set<String> neededSections = Sets.newArraySet(sectionTypes); 360 boolean selectSections = !neededSections.isEmpty(); 361 parser.setInput(in, StandardCharsets.UTF_8.name()); 362 363 Metadata metadata = Metadata.read(parser); 364 if (metadata == null) { 365 return null; 366 } 367 368 PowerStatsSpan span = new PowerStatsSpan(metadata); 369 boolean skipSection = false; 370 int nestingLevel = 0; 371 int eventType = parser.getEventType(); 372 while (eventType != XmlPullParser.END_DOCUMENT) { 373 if (skipSection) { 374 if (eventType == XmlPullParser.END_TAG 375 && parser.getName().equals(XML_TAG_SECTION)) { 376 nestingLevel--; 377 if (nestingLevel == 0) { 378 skipSection = false; 379 } 380 } else if (eventType == XmlPullParser.START_TAG 381 && parser.getName().equals(XML_TAG_SECTION)) { 382 nestingLevel++; 383 } 384 } else if (eventType == XmlPullParser.START_TAG) { 385 String tag = parser.getName(); 386 if (tag.equals(XML_TAG_SECTION)) { 387 String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE); 388 if (!selectSections || neededSections.contains(sectionType)) { 389 Section section = sectionReader.read(sectionType, parser); 390 if (section == null) { 391 if (selectSections) { 392 throw new XmlPullParserException( 393 "Unsupported PowerStatsStore section type: " + sectionType); 394 } else { 395 section = new Section(sectionType) { 396 @Override 397 public void dump(IndentingPrintWriter ipw) { 398 ipw.println("Unsupported PowerStatsStore section type: " 399 + sectionType); 400 } 401 402 @Override 403 void write(TypedXmlSerializer serializer) { 404 } 405 }; 406 } 407 } 408 span.addSection(section); 409 } else { 410 skipSection = true; 411 } 412 } else if (tag.equals(XML_TAG_METADATA)) { 413 Metadata.read(parser); 414 } 415 } 416 eventType = parser.next(); 417 } 418 return span; 419 } 420 421 /** 422 * Prints the contents of this power stats span. 423 */ dump(IndentingPrintWriter ipw)424 public void dump(IndentingPrintWriter ipw) { 425 mMetadata.dump(ipw, /* includeSections */ false); 426 for (Section section : mSections) { 427 ipw.increaseIndent(); 428 ipw.println(section.mType); 429 section.dump(ipw); 430 ipw.decreaseIndent(); 431 } 432 } 433 } 434