• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.media.tests;
18 
19 import com.android.tradefed.config.OptionClass;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 import com.android.tradefed.invoker.TestInformation;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.result.ITestInvocationListener;
24 import com.android.tradefed.result.TestDescription;
25 import com.android.tradefed.util.FileUtil;
26 import com.android.tradefed.util.proto.TfMetricProtoUtil;
27 
28 import com.google.common.collect.ImmutableMap;
29 import com.google.common.collect.ImmutableMultimap;
30 
31 import org.json.JSONArray;
32 import org.json.JSONException;
33 import org.json.JSONObject;
34 import org.xmlpull.v1.XmlPullParser;
35 import org.xmlpull.v1.XmlPullParserException;
36 import org.xmlpull.v1.XmlPullParserFactory;
37 
38 import java.io.ByteArrayInputStream;
39 import java.io.File;
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.regex.Matcher;
46 import java.util.regex.Pattern;
47 
48 /**
49  * This test invocation runs android.hardware.camera2.cts.PerformanceTest - Camera2 API use case
50  * performance KPIs (Key Performance Indicator), such as camera open time, session creation time,
51  * shutter lag etc. The KPI data will be parsed and reported.
52  */
53 @OptionClass(alias = "camera-framework")
54 public class CameraPerformanceTest extends CameraTestBase {
55 
56     private static final String TEST_CAMERA_LAUNCH = "testCameraLaunch";
57     private static final String TEST_SINGLE_CAPTURE = "testSingleCapture";
58     private static final String TEST_REPROCESSING_LATENCY = "testReprocessingLatency";
59     private static final String TEST_SINGLE_CAPTURE_JPEG_R = "testSingleCaptureJpegR";
60     private static final String TEST_REPROCESSING_THROUGHPUT = "testReprocessingThroughput";
61 
62     // KPIs to be reported. The key is test methods and the value is KPIs in the method.
63     private final ImmutableMultimap<String, String> mReportingKpis =
64             new ImmutableMultimap.Builder<String, String>()
65                     .put(TEST_CAMERA_LAUNCH, "Camera launch time")
66                     .put(TEST_CAMERA_LAUNCH, "Camera start preview time")
67                     .put(TEST_CAMERA_LAUNCH, "Camera camera close time")
68                     .put(TEST_SINGLE_CAPTURE, "Camera capture result latency")
69                     .put(TEST_SINGLE_CAPTURE_JPEG_R, "Camera capture latency jpeg r")
70                     .put(TEST_REPROCESSING_LATENCY, "YUV reprocessing shot to shot latency")
71                     .put(TEST_REPROCESSING_LATENCY, "opaque reprocessing shot to shot latency")
72                     .put(TEST_REPROCESSING_THROUGHPUT, "YUV reprocessing capture latency")
73                     .put(TEST_REPROCESSING_THROUGHPUT, "opaque reprocessing capture latency")
74                     .build();
75 
76     // JSON format keymap, key is test method name and the value is stream name in Json file
77     private static final ImmutableMap<String, String> METHOD_JSON_KEY_MAP =
78             new ImmutableMap.Builder<String, String>()
79                     .put(TEST_CAMERA_LAUNCH, "test_camera_launch")
80                     .put(TEST_SINGLE_CAPTURE, "test_single_capture")
81                     .put(TEST_SINGLE_CAPTURE_JPEG_R, "test_single_capture_jpeg_r")
82                     .put(TEST_REPROCESSING_LATENCY, "test_reprocessing_latency")
83                     .put(TEST_REPROCESSING_THROUGHPUT, "test_reprocessing_throughput")
84                     .build();
85 
getAverage(List<E> list)86     private <E extends Number> double getAverage(List<E> list) {
87         double sum = 0;
88         int size = list.size();
89         for (E num : list) {
90             sum += num.doubleValue();
91         }
92         if (size == 0) {
93             return 0.0;
94         }
95         return (sum / size);
96     }
97 
CameraPerformanceTest()98     public CameraPerformanceTest() {
99         // Set up the default test info. But this is subject to be overwritten by options passed
100         // from commands.
101         setTestPackage("android.camera.cts");
102         setTestClass("android.hardware.camera2.cts.PerformanceTest");
103         setTestRunner("androidx.test.runner.AndroidJUnitRunner");
104         setRuKey("camera_framework_performance");
105         setTestTimeoutMs(10 * 60 * 1000); // 10 mins
106         setIsolatedStorageFlag(false);
107     }
108 
109     /** {@inheritDoc} */
110     @Override
run(TestInformation testInfo, ITestInvocationListener listener)111     public void run(TestInformation testInfo, ITestInvocationListener listener)
112             throws DeviceNotAvailableException {
113         runInstrumentationTest(testInfo, listener, new CollectingListener(listener));
114     }
115 
116     /**
117      * A listener to collect the output from test run and fatal errors
118      */
119     private class CollectingListener extends CameraTestMetricsCollectionListener.DefaultCollectingListener {
120 
CollectingListener(ITestInvocationListener listener)121         public CollectingListener(ITestInvocationListener listener) {
122             super(listener);
123         }
124 
125         @Override
handleMetricsOnTestEnded(TestDescription test, Map<String, String> testMetrics)126         public void handleMetricsOnTestEnded(TestDescription test, Map<String, String> testMetrics) {
127             // Pass the test name for a key in the aggregated metrics, because
128             // it is used to generate the key of the final metrics to post at the end of test run.
129             for (Map.Entry<String, String> metric : testMetrics.entrySet()) {
130                 getAggregatedMetrics().put(test.getTestName(), metric.getValue());
131             }
132         }
133 
134         @Override
handleTestRunEnded( ITestInvocationListener listener, long elapsedTime, Map<String, String> runMetrics)135         public void handleTestRunEnded(
136                 ITestInvocationListener listener,
137                 long elapsedTime,
138                 Map<String, String> runMetrics) {
139             // Report metrics at the end of test run.
140             Map<String, String> result = parseResult(getAggregatedMetrics());
141             listener.testRunEnded(getTestDurationMs(), TfMetricProtoUtil.upgradeConvert(result));
142         }
143     }
144 
145     /**
146      * Parse Camera Performance KPIs results and then put them all together to post the final
147      * report.
148      *
149      * @return a {@link HashMap} that contains pairs of kpiName and kpiValue
150      */
parseResult(Map<String, String> metrics)151     private Map<String, String> parseResult(Map<String, String> metrics) {
152 
153         // if json report exists, return the parse results
154         CtsJsonResultParser ctsJsonResultParser = new CtsJsonResultParser();
155 
156         if (ctsJsonResultParser.isJsonFileExist()) {
157             return ctsJsonResultParser.parse();
158         }
159 
160         Map<String, String> resultsAll = new HashMap<String, String>();
161 
162         CtsResultParserBase parser;
163         for (Map.Entry<String, String> metric : metrics.entrySet()) {
164             String testMethod = metric.getKey();
165             String testResult = metric.getValue();
166             CLog.d("test name %s", testMethod);
167             CLog.d("test result %s", testResult);
168             // Probe which result parser should be used.
169             if (shouldUseCtsXmlResultParser(testResult)) {
170                 parser = new CtsXmlResultParser();
171             } else {
172                 parser = new CtsDelimitedResultParser();
173             }
174 
175             // Get pairs of { KPI name, KPI value } from stdout that each test outputs.
176             // Assuming that a device has both the front and back cameras, parser will return
177             // 2 KPIs in HashMap. For an example of testCameraLaunch,
178             //   {
179             //     ("Camera 0 Camera launch time", "379.2"),
180             //     ("Camera 1 Camera launch time", "272.8"),
181             //   }
182             Map<String, String> testKpis = parser.parse(testResult, testMethod);
183             for (String k : testKpis.keySet()) {
184                 if (resultsAll.containsKey(k)) {
185                     throw new RuntimeException(
186                             String.format("KPI name (%s) conflicts with the existing names.", k));
187                 }
188             }
189             parser.clear();
190 
191             // Put each result together to post the final result
192             resultsAll.putAll(testKpis);
193         }
194         return resultsAll;
195     }
196 
shouldUseCtsXmlResultParser(String result)197     public boolean shouldUseCtsXmlResultParser(String result) {
198         final String XML_DECLARATION = "<?xml";
199         return (result.startsWith(XML_DECLARATION)
200                 || result.startsWith(XML_DECLARATION.toUpperCase()));
201     }
202 
203     /** Data class of CTS test results for Camera framework performance test */
204     public static class CtsMetric {
205         String testMethod;  // "testSingleCapture"
206         String source;      // "android.hardware.camera2.cts.PerformanceTest#testSingleCapture:327"
207         // or "testSingleCapture" (just test method name)
208         String message; // "Camera 0: Camera capture latency"
209         String type; // "lower_better"
210         String unit;        // "ms"
211         String value;       // "691.0" (is an average of 736.0 688.0 679.0 667.0 686.0)
212         String schemaKey;   // RU schema key = message (+ testMethodName if needed), derived
213 
214         // eg. "android.hardware.camera2.cts.PerformanceTest#testSingleCapture:327"
215         public static final Pattern SOURCE_REGEX =
216                 Pattern.compile("^(?<package>[a-zA-Z\\d\\._$]+)#(?<method>[a-zA-Z\\d_$]+)(:\\d+)?");
217         // eg. "Camera 0: Camera capture latency"
218         public static final Pattern MESSAGE_REGEX =
219                 Pattern.compile("^Camera\\s+(?<cameraId>\\d+):\\s+(?<kpiName>.*)");
220 
CtsMetric( String testMethod, String source, String message, String type, String unit, String value)221         CtsMetric(
222                 String testMethod,
223                 String source,
224                 String message,
225                 String type,
226                 String unit,
227                 String value) {
228             this.testMethod = testMethod;
229             this.source = source;
230             this.message = message;
231             this.type = type;
232             this.unit = unit;
233             this.value = value;
234             this.schemaKey = getRuSchemaKeyName(message);
235         }
236 
matches(String testMethod, String kpiName)237         public boolean matches(String testMethod, String kpiName) {
238             return (this.testMethod.equals(testMethod) && this.message.endsWith(kpiName));
239         }
240 
getRuSchemaKeyName(String message)241         public String getRuSchemaKeyName(String message) {
242             // Note 1: The key shouldn't contain ":" for side by side report.
243             String schemaKey = message.replace(":", "");
244             // Note 2: Two tests testReprocessingLatency & testReprocessingThroughput have the
245             // same metric names to report results. To make the report key name distinct,
246             // the test name is added as prefix for these tests for them.
247             final String[] TEST_NAMES_AS_PREFIX = {
248                 "testReprocessingLatency", "testReprocessingThroughput"
249             };
250             for (String testName : TEST_NAMES_AS_PREFIX) {
251                 if (testMethod.endsWith(testName)) {
252                     schemaKey = String.format("%s_%s", testName, schemaKey);
253                     break;
254                 }
255             }
256             return schemaKey;
257         }
258 
getTestMethodNameInSource(String source)259         public String getTestMethodNameInSource(String source) {
260             Matcher m = SOURCE_REGEX.matcher(source);
261             if (!m.matches()) {
262                 return source;
263             }
264             return m.group("method");
265         }
266     }
267 
268     /**
269      * Base class of CTS test result parser. This is inherited to two derived parsers,
270      * {@link CtsDelimitedResultParser} for legacy delimiter separated format and
271      * {@link CtsXmlResultParser} for XML typed format introduced since NYC.
272      */
273     public abstract class CtsResultParserBase {
274 
275         protected CtsMetric mSummary;
276         protected List<CtsMetric> mDetails = new ArrayList<>();
277 
278         /**
279          * Parse Camera Performance KPIs result first, then leave the only KPIs that matter.
280          *
281          * @param result String to be parsed
282          * @param testMethod test method name used to leave the only metric that matters
283          * @return a {@link HashMap} that contains kpiName and kpiValue
284          */
parse(String result, String testMethod)285         public abstract Map<String, String> parse(String result, String testMethod);
286 
filter(List<CtsMetric> metrics, String testMethod)287         protected Map<String, String> filter(List<CtsMetric> metrics, String testMethod) {
288             Map<String, String> filtered = new HashMap<String, String>();
289             for (CtsMetric metric : metrics) {
290                 for (String kpiName : mReportingKpis.get(testMethod)) {
291                     // Post the data only when it matches with the given methods and KPI names.
292                     if (metric.matches(testMethod, kpiName)) {
293                         filtered.put(metric.schemaKey, metric.value);
294                     }
295                 }
296             }
297             return filtered;
298         }
299 
setSummary(CtsMetric summary)300         protected void setSummary(CtsMetric summary) {
301             mSummary = summary;
302         }
303 
addDetail(CtsMetric detail)304         protected void addDetail(CtsMetric detail) {
305             mDetails.add(detail);
306         }
307 
getDetails()308         protected List<CtsMetric> getDetails() {
309             return mDetails;
310         }
311 
clear()312         void clear() {
313             mSummary = null;
314             mDetails.clear();
315         }
316     }
317 
318     /**
319      * Parses the camera performance test generated by the underlying instrumentation test and
320      * returns it to test runner for later reporting.
321      *
322      * <p>TODO(liuyg): Rename this class to not reference CTS.
323      *
324      * <p>Format: (summary message)| |(type)|(unit)|(value) ++++
325      * (source)|(message)|(type)|(unit)|(value)... +++ ...
326      *
327      * <p>Example: Camera launch average time for Camera 1| |lower_better|ms|586.6++++
328      * android.hardware.camera2.cts.PerformanceTest#testCameraLaunch:171| Camera 0: Camera open
329      * time|lower_better|ms|74.0 100.0 70.0 67.0 82.0 +++
330      * android.hardware.camera2.cts.PerformanceTest#testCameraLaunch:171| Camera 0: Camera configure
331      * stream time|lower_better|ms|9.0 5.0 5.0 8.0 5.0 ...
332      *
333      * <p>See also com.android.cts.util.ReportLog for the format detail.
334      */
335     public class CtsDelimitedResultParser extends CtsResultParserBase {
336         private static final String LOG_SEPARATOR = "\\+\\+\\+";
337         private static final String SUMMARY_SEPARATOR = "\\+\\+\\+\\+";
338         private final Pattern mSummaryRegex =
339                 Pattern.compile(
340                         "^(?<message>[^|]+)\\| \\|(?<type>[^|]+)\\|(?<unit>[^|]+)\\|(?<value>[0-9 .]+)");
341         private final Pattern mDetailRegex =
342                 Pattern.compile(
343                         "^(?<source>[^|]+)\\|(?<message>[^|]+)\\|(?<type>[^|]+)\\|(?<unit>[^|]+)\\|"
344                                 + "(?<values>[0-9 .]+)");
345 
346         @Override
parse(String result, String testMethod)347         public Map<String, String> parse(String result, String testMethod) {
348             parseToCtsMetrics(result, testMethod);
349             parseToCtsMetrics(result, testMethod);
350             return filter(getDetails(), testMethod);
351         }
352 
parseToCtsMetrics(String result, String testMethod)353         void parseToCtsMetrics(String result, String testMethod) {
354             // Split summary and KPIs from stdout passes as parameter.
355             String[] output = result.split(SUMMARY_SEPARATOR);
356             if (output.length != 2) {
357                 throw new RuntimeException("Value not in the correct format");
358             }
359             Matcher summaryMatcher = mSummaryRegex.matcher(output[0].trim());
360 
361             // Parse summary.
362             // Example: "Camera launch average time for Camera 1| |lower_better|ms|586.6++++"
363             if (summaryMatcher.matches()) {
364                 setSummary(
365                         new CtsMetric(
366                                 testMethod,
367                                 null,
368                                 summaryMatcher.group("message"),
369                                 summaryMatcher.group("type"),
370                                 summaryMatcher.group("unit"),
371                                 summaryMatcher.group("value")));
372             } else {
373                 // Fall through since the summary is not posted as results.
374                 CLog.w("Summary not in the correct format");
375             }
376 
377             // Parse KPIs.
378             // Example: "android.hardware.camera2.cts.PerformanceTest#testCameraLaunch:171|Camera 0:
379             // Camera open time|lower_better|ms|74.0 100.0 70.0 67.0 82.0 +++"
380             String[] details = output[1].split(LOG_SEPARATOR);
381             for (String detail : details) {
382                 Matcher detailMatcher = mDetailRegex.matcher(detail.trim());
383                 if (detailMatcher.matches()) {
384                     // get average of kpi values
385                     List<Double> values = new ArrayList<>();
386                     for (String value : detailMatcher.group("values").split("\\s+")) {
387                         values.add(Double.parseDouble(value));
388                     }
389                     String kpiValue = String.format("%.1f", getAverage(values));
390                     addDetail(
391                             new CtsMetric(
392                                     testMethod,
393                                     detailMatcher.group("source"),
394                                     detailMatcher.group("message"),
395                                     detailMatcher.group("type"),
396                                     detailMatcher.group("unit"),
397                                     kpiValue));
398                 } else {
399                     throw new RuntimeException("KPI not in the correct format");
400                 }
401             }
402         }
403     }
404 
405     /**
406      * Parses the CTS test results in a XML format introduced since NYC.
407      * Format:
408      *   <Summary>
409      *       <Metric source="android.hardware.camera2.cts.PerformanceTest#testSingleCapture:327"
410      *               message="Camera capture result average latency for all cameras "
411      *               score_type="lower_better"
412      *               score_unit="ms"
413      *           <Value>353.9</Value>
414      *       </Metric>
415      *   </Summary>
416      *   <Detail>
417      *       <Metric source="android.hardware.camera2.cts.PerformanceTest#testSingleCapture:303"
418      *               message="Camera 0: Camera capture latency"
419      *               score_type="lower_better"
420      *               score_unit="ms">
421      *           <Value>335.0</Value>
422      *           <Value>302.0</Value>
423      *           <Value>316.0</Value>
424      *       </Metric>
425      *   </Detail>
426      *  }
427      * See also com.android.compatibility.common.util.ReportLog for the format detail.
428      */
429     public class CtsXmlResultParser extends CtsResultParserBase {
430         private static final String ENCODING = "UTF-8";
431         // XML constants
432         private static final String DETAIL_TAG = "Detail";
433         private static final String METRIC_TAG = "Metric";
434         private static final String MESSAGE_ATTR = "message";
435         private static final String SCORETYPE_ATTR = "score_type";
436         private static final String SCOREUNIT_ATTR = "score_unit";
437         private static final String SOURCE_ATTR = "source";
438         private static final String SUMMARY_TAG = "Summary";
439         private static final String VALUE_TAG = "Value";
440         private String mTestMethod;
441 
442         @Override
parse(String result, String testMethod)443         public Map<String, String> parse(String result, String testMethod) {
444             try {
445                 mTestMethod = testMethod;
446                 XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
447                 XmlPullParser parser = factory.newPullParser();
448                 parser.setInput(new ByteArrayInputStream(result.getBytes(ENCODING)), ENCODING);
449                 parser.nextTag();
450                 parse(parser);
451                 return filter(getDetails(), testMethod);
452             } catch (XmlPullParserException | IOException e) {
453                 throw new RuntimeException("Failed to parse results in XML.", e);
454             }
455         }
456 
457         /**
458          * Parses a {@link CtsMetric} from the given XML parser.
459          *
460          * @param parser
461          * @throws IOException
462          * @throws XmlPullParserException
463          */
parse(XmlPullParser parser)464         private void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
465             parser.require(XmlPullParser.START_TAG, null, SUMMARY_TAG);
466             parser.nextTag();
467             setSummary(parseToCtsMetrics(parser));
468             parser.nextTag();
469             parser.require(XmlPullParser.END_TAG, null, SUMMARY_TAG);
470             parser.next();
471             int eventType = parser.getEventType();
472             if (eventType != XmlPullParser.END_DOCUMENT) {
473                 if (parser.getName().equals(DETAIL_TAG)) {
474                     while (parser.nextTag() == XmlPullParser.START_TAG) {
475                         addDetail(parseToCtsMetrics(parser));
476                     }
477                     parser.require(XmlPullParser.END_TAG, null, DETAIL_TAG);
478                 }
479             }
480         }
481 
parseToCtsMetrics(XmlPullParser parser)482         CtsMetric parseToCtsMetrics(XmlPullParser parser)
483                 throws IOException, XmlPullParserException {
484             parser.require(XmlPullParser.START_TAG, null, METRIC_TAG);
485             String source = parser.getAttributeValue(null, SOURCE_ATTR);
486             String message = parser.getAttributeValue(null, MESSAGE_ATTR);
487             String type = parser.getAttributeValue(null, SCORETYPE_ATTR);
488             String unit = parser.getAttributeValue(null, SCOREUNIT_ATTR);
489             List<Double> values = new ArrayList<>();
490             while (parser.nextTag() == XmlPullParser.START_TAG) {
491                 parser.require(XmlPullParser.START_TAG, null, VALUE_TAG);
492                 values.add(Double.parseDouble(parser.nextText()));
493                 parser.require(XmlPullParser.END_TAG, null, VALUE_TAG);
494             }
495             String kpiValue = String.format("%.1f", getAverage(values));
496             parser.require(XmlPullParser.END_TAG, null, METRIC_TAG);
497             return new CtsMetric(mTestMethod, source, message, type, unit, kpiValue);
498         }
499     }
500 
501     /*
502      * Parse the Json report from the Json String
503      * "test_single_capture":
504      * {"camera_id":"0","camera_capture_latency":[264.0,229.0,229.0,237.0,234.0],
505      * "camera_capture_result_latency":[230.0,197.0,196.0,204.0,202.0]},"
506      * "test_reprocessing_latency":
507      * {"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing",
508      * "capture_message":"shot to shot latency","latency":[102.0,101.0,99.0,99.0,100.0,101.0],
509      * "camera_reprocessing_shot_to_shot_average_latency":100.33333333333333},
510      *
511      * TODO: move this to a seperate class
512      */
513     public class CtsJsonResultParser {
514 
515         // report json file set in
516         // cts/tools/cts-tradefed/res/config/cts-preconditions.xml
517         private static final String JSON_RESULT_FILE =
518                 "/sdcard/report-log-files/CtsCameraTestCases.reportlog.json";
519         private static final String CAMERA_ID_KEY = "camera_id";
520         private static final String REPROCESS_TYPE_KEY = "reprocess_type";
521         private static final String CAPTURE_MESSAGE_KEY = "capture_message";
522         private static final String LATENCY_KEY = "latency";
523 
parse()524         public Map<String, String> parse() {
525 
526             Map<String, String> metrics = new HashMap<>();
527 
528             String jsonString = getFormatedJsonReportFromFile();
529             if (null == jsonString) {
530                 throw new RuntimeException("Get null json report string.");
531             }
532 
533             Map<String, List<Double>> metricsData = new HashMap<>();
534 
535             try {
536                 JSONObject jsonObject = new JSONObject(jsonString);
537 
538                 for (String testMethod : METHOD_JSON_KEY_MAP.keySet()) {
539 
540                     JSONArray jsonArray =
541                             (JSONArray) jsonObject.get(METHOD_JSON_KEY_MAP.get(testMethod));
542 
543                     switch (testMethod) {
544                         case TEST_REPROCESSING_THROUGHPUT:
545                         case TEST_REPROCESSING_LATENCY:
546                             for (int i = 0; i < jsonArray.length(); i++) {
547                                 JSONObject element = jsonArray.getJSONObject(i);
548 
549                                 // create a kpiKey from camera id,
550                                 // reprocess type and capture message
551                                 String cameraId = element.getString(CAMERA_ID_KEY);
552                                 String reprocessType = element.getString(REPROCESS_TYPE_KEY);
553                                 String captureMessage = element.getString(CAPTURE_MESSAGE_KEY);
554                                 String kpiKey =
555                                         String.format(
556                                                 "%s_Camera %s %s %s",
557                                                 testMethod,
558                                                 cameraId,
559                                                 reprocessType,
560                                                 captureMessage);
561 
562                                 // read the data array from json object
563                                 JSONArray jsonDataArray = element.getJSONArray(LATENCY_KEY);
564                                 if (!metricsData.containsKey(kpiKey)) {
565                                     List<Double> list = new ArrayList<>();
566                                     metricsData.put(kpiKey, list);
567                                 }
568                                 for (int j = 0; j < jsonDataArray.length(); j++) {
569                                     metricsData.get(kpiKey).add(jsonDataArray.getDouble(j));
570                                 }
571                             }
572                             break;
573                         case TEST_SINGLE_CAPTURE:
574                         case TEST_SINGLE_CAPTURE_JPEG_R:
575                         case TEST_CAMERA_LAUNCH:
576                             for (int i = 0; i < jsonArray.length(); i++) {
577                                 JSONObject element = jsonArray.getJSONObject(i);
578 
579                                 String cameraid = element.getString(CAMERA_ID_KEY);
580                                 for (String kpiName : mReportingKpis.get(testMethod)) {
581 
582                                     // the json key is all lower case
583                                     String jsonKey = kpiName.toLowerCase().replace(" ", "_");
584                                     String kpiKey =
585                                             String.format("Camera %s %s", cameraid, kpiName);
586                                     if (!metricsData.containsKey(kpiKey)) {
587                                         List<Double> list = new ArrayList<>();
588                                         metricsData.put(kpiKey, list);
589                                     }
590                                     JSONArray jsonDataArray = element.getJSONArray(jsonKey);
591                                     for (int j = 0; j < jsonDataArray.length(); j++) {
592                                         metricsData.get(kpiKey).add(jsonDataArray.getDouble(j));
593                                     }
594                                 }
595                             }
596                             break;
597                         default:
598                             break;
599                     }
600                 }
601             } catch (JSONException e) {
602                 CLog.w("JSONException: %s in string %s", e.getMessage(), jsonString);
603             }
604 
605             // take the average of all data for reporting
606             for (String kpiKey : metricsData.keySet()) {
607                 String kpiValue = String.format("%.1f", getAverage(metricsData.get(kpiKey)));
608                 metrics.put(kpiKey, kpiValue);
609             }
610             return metrics;
611         }
612 
isJsonFileExist()613         public boolean isJsonFileExist() {
614             try {
615                 return getDevice().doesFileExist(JSON_RESULT_FILE);
616             } catch (DeviceNotAvailableException e) {
617                 throw new RuntimeException("Failed to check json report file on device.", e);
618             }
619         }
620 
621         /*
622          * read json report file on the device
623          */
getFormatedJsonReportFromFile()624         private String getFormatedJsonReportFromFile() {
625             String jsonString = null;
626             try {
627                 // pull the json report file from device
628                 File outputFile = FileUtil.createTempFile("json", ".txt");
629                 getDevice().pullFile(JSON_RESULT_FILE, outputFile);
630                 jsonString = reformatJsonString(FileUtil.readStringFromFile(outputFile));
631             } catch (IOException e) {
632                 CLog.w("Couldn't parse the output json log file: ", e);
633             } catch (DeviceNotAvailableException e) {
634                 CLog.w("Could not pull file: %s, error: %s", JSON_RESULT_FILE, e);
635             }
636             return jsonString;
637         }
638 
639         // Reformat the json file to remove duplicate keys
reformatJsonString(String jsonString)640         private String reformatJsonString(String jsonString) {
641 
642             final String TEST_METRICS_PATTERN = "\\\"([a-z0-9_]*)\\\":(\\{[^{}]*\\})";
643             StringBuilder newJsonBuilder = new StringBuilder();
644             // Create map of stream names and json objects.
645             HashMap<String, List<String>> jsonMap = new HashMap<>();
646             Pattern p = Pattern.compile(TEST_METRICS_PATTERN);
647             Matcher m = p.matcher(jsonString);
648             while (m.find()) {
649                 String key = m.group(1);
650                 String value = m.group(2);
651                 if (!jsonMap.containsKey(key)) {
652                     jsonMap.put(key, new ArrayList<String>());
653                 }
654                 jsonMap.get(key).add(value);
655             }
656             // Rewrite json string as arrays.
657             newJsonBuilder.append("{");
658             boolean firstLine = true;
659             for (String key : jsonMap.keySet()) {
660                 if (!firstLine) {
661                     newJsonBuilder.append(",");
662                 } else {
663                     firstLine = false;
664                 }
665                 newJsonBuilder.append("\"").append(key).append("\":[");
666                 boolean firstValue = true;
667                 for (String stream : jsonMap.get(key)) {
668                     if (!firstValue) {
669                         newJsonBuilder.append(",");
670                     } else {
671                         firstValue = false;
672                     }
673                     newJsonBuilder.append(stream);
674                 }
675                 newJsonBuilder.append("]");
676             }
677             newJsonBuilder.append("}");
678             return newJsonBuilder.toString();
679         }
680     }
681 }
682