• 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;
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 &lt;metadata&gt; 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