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