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