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