1 /*
2  * Copyright (C) 2014 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.compatibility.common.util;
18 
19 import org.xmlpull.v1.XmlPullParser;
20 import org.xmlpull.v1.XmlPullParserException;
21 import org.xmlpull.v1.XmlPullParserFactory;
22 import org.xmlpull.v1.XmlSerializer;
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.IOException;
27 import java.io.Serializable;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31 
32 /**
33  * Utility class to add results to the report.
34  */
35 public class ReportLog implements Serializable {
36 
37     private static final String ENCODING = "UTF-8";
38     private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
39 
40     // XML constants
41     private static final String METRIC_TAG = "Metric";
42     private static final String MESSAGE_ATTR = "message";
43     private static final String SCORETYPE_ATTR = "score_type";
44     private static final String SCOREUNIT_ATTR = "score_unit";
45     private static final String SOURCE_ATTR = "source";
46     private static final String SUMMARY_TAG = "Summary";
47     private static final String VALUE_TAG = "Value";
48     private static final String DEFAULT_NAME = "default";
49 
50     protected Metric mSummary;
51     protected String mReportLogName;
52     protected String mStreamName;
53 
54     public static class Metric implements Serializable {
55         private static final int MAX_SOURCE_LENGTH = 200;
56         private static final int MAX_MESSAGE_LENGTH = 200;
57         private static final int MAX_NUM_VALUES = 1000;
58         String mSource;
59         String mMessage;
60         double[] mValues;
61         ResultType mType;
62         ResultUnit mUnit;
63 
Metric(String source, String message, double value, ResultType type, ResultUnit unit)64         Metric(String source, String message, double value, ResultType type, ResultUnit unit) {
65             this(source, message, new double[] { value }, type, unit);
66         }
67 
68         /**
69          * Creates a metric array to be included in the report. Each object has a message
70          * describing its values and enums to interpret them. In addition, each result also includes
71          * class, method and line number information about the test which added this result which is
72          * collected by looking at the stack trace.
73          *
74          * @param message A string describing the values
75          * @param values An array of the values
76          * @param type Represents how to interpret the values (eg. A lower score is better)
77          * @param unit Represents the unit in which the values are (eg. Milliseconds)
78          */
Metric(String source, String message, double[] values, ResultType type, ResultUnit unit)79         Metric(String source, String message, double[] values, ResultType type, ResultUnit unit) {
80             int sourceLength = source.length();
81             if (sourceLength > MAX_SOURCE_LENGTH) {
82                 // Substring to the end
83                 mSource = source.substring(sourceLength - MAX_SOURCE_LENGTH);
84             } else {
85                 mSource = source;
86             }
87             int messageLength = message.length();
88             if (messageLength > MAX_MESSAGE_LENGTH) {
89                 // Substring from the start
90                 mMessage = message.substring(0, MAX_MESSAGE_LENGTH);
91             } else {
92                 mMessage = message;
93             }
94             int valuesLength = values.length;
95             if (valuesLength > MAX_NUM_VALUES) {
96                 // Subarray from the start
97                 mValues = Arrays.copyOf(values, MAX_NUM_VALUES);
98             } else {
99                 mValues = values;
100             }
101             mType = type;
102             mUnit = unit;
103         }
104 
getSource()105         public String getSource() {
106             return mSource;
107         }
108 
getMessage()109         public String getMessage() {
110             return mMessage;
111         }
112 
getValues()113         public double[] getValues() {
114             return mValues;
115         }
116 
getType()117         public ResultType getType() {
118             return mType;
119         }
120 
getUnit()121         public ResultUnit getUnit() {
122             return mUnit;
123         }
124 
serialize(XmlSerializer serializer)125         void serialize(XmlSerializer serializer)
126                 throws IllegalArgumentException, IllegalStateException, IOException {
127             serializer.startTag(null, METRIC_TAG);
128             serializer.attribute(null, SOURCE_ATTR, getSource());
129             serializer.attribute(null, MESSAGE_ATTR, getMessage());
130             serializer.attribute(null, SCORETYPE_ATTR, getType().toReportString());
131             serializer.attribute(null, SCOREUNIT_ATTR, getUnit().toReportString());
132             for (double d : getValues()) {
133                 serializer.startTag(null, VALUE_TAG);
134                 serializer.text(Double.toString(d));
135                 serializer.endTag(null, VALUE_TAG);
136             }
137             serializer.endTag(null, METRIC_TAG);
138         }
139 
parse(XmlPullParser parser)140         static Metric parse(XmlPullParser parser)
141                 throws XmlPullParserException, IOException {
142             parser.require(XmlPullParser.START_TAG, null, METRIC_TAG);
143             String source = parser.getAttributeValue(null, SOURCE_ATTR);
144             String message = parser.getAttributeValue(null, MESSAGE_ATTR);
145             ResultType type = ResultType.parseReportString(
146                     parser.getAttributeValue(null, SCORETYPE_ATTR));
147             ResultUnit unit = ResultUnit.parseReportString(
148                     parser.getAttributeValue(null, SCOREUNIT_ATTR));
149             List<String> valuesList = new ArrayList<>();
150             while (parser.nextTag() == XmlPullParser.START_TAG) {
151                 parser.require(XmlPullParser.START_TAG, null, VALUE_TAG);
152                 valuesList.add(parser.nextText());
153                 parser.require(XmlPullParser.END_TAG, null, VALUE_TAG);
154             }
155             int length = valuesList.size();
156             double[] values = new double[length];
157             for (int i = 0; i < length; i++) {
158                 values[i] = Double.parseDouble(valuesList.get(i));
159             }
160             parser.require(XmlPullParser.END_TAG, null, METRIC_TAG);
161             return new Metric(source, message, values, type, unit);
162         }
163     }
164 
ReportLog()165     public ReportLog() {
166         mReportLogName = DEFAULT_NAME;
167     }
168 
ReportLog(String reportLogName, String streamName)169     public ReportLog(String reportLogName, String streamName) {
170         mReportLogName = reportLogName;
171         mStreamName = streamName;
172     }
173 
174     /**
175      * Adds a double array of metrics to the report.
176      */
addValues(String message, double[] values, ResultType type, ResultUnit unit)177     public void addValues(String message, double[] values, ResultType type, ResultUnit unit) {
178         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
179     }
180 
181     /**
182      * Adds a double array of metrics to the report.
183      */
addValues(String source, String message, double[] values, ResultType type, ResultUnit unit)184     public void addValues(String source, String message, double[] values, ResultType type,
185             ResultUnit unit) {
186         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
187     }
188 
189     /**
190      * Adds a double metric to the report.
191      */
addValue(String message, double value, ResultType type, ResultUnit unit)192     public void addValue(String message, double value, ResultType type, ResultUnit unit) {
193         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
194     }
195 
196     /**
197      * Adds a double metric to the report.
198      */
addValue(String source, String message, double value, ResultType type, ResultUnit unit)199     public void addValue(String source, String message, double value, ResultType type,
200             ResultUnit unit) {
201         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
202     }
203 
204     /**
205      * Adds an int metric to the report.
206      */
addValue(String message, int value, ResultType type, ResultUnit unit)207     public void addValue(String message, int value, ResultType type, ResultUnit unit) {
208         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
209     }
210 
211     /**
212      * Adds a long metric to the report.
213      */
addValue(String message, long value, ResultType type, ResultUnit unit)214     public void addValue(String message, long value, ResultType type, ResultUnit unit) {
215         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
216     }
217 
218     /**
219      * Adds a float metric to the report.
220      */
addValue(String message, float value, ResultType type, ResultUnit unit)221     public void addValue(String message, float value, ResultType type, ResultUnit unit) {
222         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
223     }
224 
225     /**
226      * Adds a boolean metric to the report.
227      */
addValue(String message, boolean value, ResultType type, ResultUnit unit)228     public void addValue(String message, boolean value, ResultType type, ResultUnit unit) {
229         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
230     }
231 
232     /**
233      * Adds a String metric to the report.
234      */
addValue(String message, String value, ResultType type, ResultUnit unit)235     public void addValue(String message, String value, ResultType type, ResultUnit unit) {
236         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
237     }
238 
239     /**
240      * Adds an int array of metrics to the report.
241      */
addValues(String message, int[] values, ResultType type, ResultUnit unit)242     public void addValues(String message, int[] values, ResultType type, ResultUnit unit) {
243         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
244     }
245 
246     /**
247      * Adds a long array of metrics to the report.
248      */
addValues(String message, long[] values, ResultType type, ResultUnit unit)249     public void addValues(String message, long[] values, ResultType type, ResultUnit unit) {
250         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
251     }
252 
253     /**
254      * Adds a float array of metrics to the report.
255      */
addValues(String message, float[] values, ResultType type, ResultUnit unit)256     public void addValues(String message, float[] values, ResultType type, ResultUnit unit) {
257         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
258     }
259 
260     /**
261      * Adds a boolean array of metrics to the report.
262      */
addValues(String message, boolean[] values, ResultType type, ResultUnit unit)263     public void addValues(String message, boolean[] values, ResultType type, ResultUnit unit) {
264         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
265     }
266 
267     /**
268      * Adds a String List of metrics to the report.
269      */
addValues(String message, List<String> values, ResultType type, ResultUnit unit)270     public void addValues(String message, List<String> values, ResultType type, ResultUnit unit) {
271         // Do nothing. Subclasses may implement using InfoStore to write metrics to files.
272     }
273 
274     /**
275      * @param elem
276      */
setSummary(Metric elem)277     /* package */ void setSummary(Metric elem) {
278         mSummary = elem;
279     }
280 
281     /**
282      * Sets the double metric summary of the report.
283      *
284      * NOTE: messages over {@value Metric#MAX_MESSAGE_LENGTH} chars will be trimmed.
285      */
setSummary(String message, double value, ResultType type, ResultUnit unit)286     public void setSummary(String message, double value, ResultType type, ResultUnit unit) {
287         setSummary(new Metric(Stacktrace.getTestCallerClassMethodNameLineNumber(), message, value,
288                 type, unit));
289     }
290 
getSummary()291     public Metric getSummary() {
292         return mSummary;
293     }
294 
295     /**
296      * Serializes a given {@link ReportLog} to a String.
297      * @throws XmlPullParserException
298      * @throws IOException
299      * @throws IllegalStateException
300      * @throws IllegalArgumentException
301      */
serialize(ReportLog reportlog)302     public static String serialize(ReportLog reportlog) throws XmlPullParserException,
303             IllegalArgumentException, IllegalStateException, IOException {
304         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
305         XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
306         serializer.setOutput(byteArrayOutputStream, ENCODING);
307         serializer.startDocument(ENCODING, true);
308         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
309         serialize(serializer, reportlog);
310         serializer.endDocument();
311         return byteArrayOutputStream.toString(ENCODING);
312     }
313 
314     /**
315      * Serializes a given {@link ReportLog} to XML.
316      * @param serializer
317      * @param reportLog
318      * @throws IOException
319      */
serialize(XmlSerializer serializer, ReportLog reportLog)320     public static void serialize(XmlSerializer serializer, ReportLog reportLog)
321             throws IOException {
322         if (reportLog == null) {
323             throw new IllegalArgumentException("Metrics reports was null");
324         }
325         Metric summary = reportLog.getSummary();
326         // Summary is optional. Details are not included in result report.
327         if (summary != null) {
328             serializer.startTag(null, SUMMARY_TAG);
329             summary.serialize(serializer);
330             serializer.endTag(null, SUMMARY_TAG);
331         }
332     }
333 
334     /**
335      * Parses a {@link ReportLog} from the given string.
336      * @throws XmlPullParserException
337      * @throws IOException
338      */
parse(String result)339     public static ReportLog parse(String result) throws XmlPullParserException, IOException {
340         if (result == null){
341             throw new IllegalArgumentException("Metrics string was null");
342         }
343         if (result.trim().isEmpty()) {
344             // Empty report.
345             return new ReportLog();
346         }
347         XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
348         XmlPullParser parser = factory.newPullParser();
349         parser.setInput(new ByteArrayInputStream(result.getBytes(ENCODING)), ENCODING);
350         try {
351             parser.nextTag();
352         } catch (XmlPullParserException e) {
353             // Empty Report.
354             return new ReportLog();
355         }
356         return parse(parser);
357     }
358 
359     /**
360      * Parses a {@link ReportLog} from the given XML parser.
361      * @param parser
362      * @throws IOException
363      * @throws XmlPullParserException
364      */
parse(XmlPullParser parser)365     public static ReportLog parse(XmlPullParser parser) throws XmlPullParserException, IOException {
366         parser.require(XmlPullParser.START_TAG, null, SUMMARY_TAG);
367         parser.nextTag();
368         ReportLog report = new ReportLog();
369         report.setSummary(Metric.parse(parser));
370         parser.nextTag();
371         parser.require(XmlPullParser.END_TAG, null, SUMMARY_TAG);
372         return report;
373     }
374 }
375