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