| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.media.tests; |
| |
| import com.android.ddmlib.testrunner.TestIdentifier; |
| import com.android.tradefed.config.OptionClass; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.result.ITestInvocationListener; |
| import com.android.tradefed.util.FileUtil; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableMultimap; |
| |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlPullParserFactory; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * This test invocation runs android.hardware.camera2.cts.PerformanceTest - Camera2 API use case |
| * performance KPIs (Key Performance Indicator), such as camera open time, session creation time, |
| * shutter lag etc. The KPI data will be parsed and reported. |
| */ |
| @OptionClass(alias = "camera-framework") |
| public class CameraPerformanceTest extends CameraTestBase { |
| |
| private static final String TEST_CAMERA_LAUNCH = "testCameraLaunch"; |
| private static final String TEST_SINGLE_CAPTURE = "testSingleCapture"; |
| private static final String TEST_REPROCESSING_LATENCY = "testReprocessingLatency"; |
| private static final String TEST_REPROCESSING_THROUGHPUT = "testReprocessingThroughput"; |
| |
| // KPIs to be reported. The key is test methods and the value is KPIs in the method. |
| private final ImmutableMultimap<String, String> mReportingKpis = |
| new ImmutableMultimap.Builder<String, String>() |
| .put(TEST_CAMERA_LAUNCH, "Camera launch time") |
| .put(TEST_CAMERA_LAUNCH, "Camera start preview time") |
| .put(TEST_SINGLE_CAPTURE, "Camera capture result latency") |
| .put(TEST_REPROCESSING_LATENCY, "YUV reprocessing shot to shot latency") |
| .put(TEST_REPROCESSING_LATENCY, "opaque reprocessing shot to shot latency") |
| .put(TEST_REPROCESSING_THROUGHPUT, "YUV reprocessing capture latency") |
| .put(TEST_REPROCESSING_THROUGHPUT, "opaque reprocessing capture latency") |
| .build(); |
| |
| // JSON format keymap, key is test method name and the value is stream name in Json file |
| private static final ImmutableMap<String, String> METHOD_JSON_KEY_MAP = |
| new ImmutableMap.Builder<String, String>() |
| .put(TEST_CAMERA_LAUNCH, "test_camera_launch") |
| .put(TEST_SINGLE_CAPTURE, "test_single_capture") |
| .put(TEST_REPROCESSING_LATENCY, "test_reprocessing_latency") |
| .put(TEST_REPROCESSING_THROUGHPUT, "test_reprocessing_throughput") |
| .build(); |
| |
| private <E extends Number> double getAverage(List<E> list) { |
| double sum = 0; |
| int size = list.size(); |
| for (E num : list) { |
| sum += num.doubleValue(); |
| } |
| if (size == 0) { |
| return 0.0; |
| } |
| return (sum / size); |
| } |
| |
| public CameraPerformanceTest() { |
| // Set up the default test info. But this is subject to be overwritten by options passed |
| // from commands. |
| setTestPackage("android.camera.cts"); |
| setTestClass("android.hardware.camera2.cts.PerformanceTest"); |
| setTestRunner("android.support.test.runner.AndroidJUnitRunner"); |
| setRuKey("camera_framework_performance"); |
| setTestTimeoutMs(10 * 60 * 1000); // 10 mins |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { |
| runInstrumentationTest(listener, new CollectingListener(listener)); |
| } |
| |
| /** |
| * A listener to collect the output from test run and fatal errors |
| */ |
| private class CollectingListener extends DefaultCollectingListener { |
| |
| public CollectingListener(ITestInvocationListener listener) { |
| super(listener); |
| } |
| |
| @Override |
| public void handleMetricsOnTestEnded(TestIdentifier test, Map<String, String> testMetrics) { |
| // Pass the test name for a key in the aggregated metrics, because |
| // it is used to generate the key of the final metrics to post at the end of test run. |
| for (Map.Entry<String, String> metric : testMetrics.entrySet()) { |
| getAggregatedMetrics().put(test.getTestName(), metric.getValue()); |
| } |
| } |
| |
| @Override |
| public void handleTestRunEnded( |
| ITestInvocationListener listener, |
| long elapsedTime, |
| Map<String, String> runMetrics) { |
| // Report metrics at the end of test run. |
| Map<String, String> result = parseResult(getAggregatedMetrics()); |
| listener.testRunEnded(getTestDurationMs(), result); |
| } |
| } |
| |
| /** |
| * Parse Camera Performance KPIs results and then put them all together to post the final |
| * report. |
| * |
| * @return a {@link HashMap} that contains pairs of kpiName and kpiValue |
| */ |
| private Map<String, String> parseResult(Map<String, String> metrics) { |
| |
| // if json report exists, return the parse results |
| CtsJsonResultParser ctsJsonResultParser = new CtsJsonResultParser(); |
| |
| if (ctsJsonResultParser.isJsonFileExist()) { |
| return ctsJsonResultParser.parse(); |
| } |
| |
| Map<String, String> resultsAll = new HashMap<String, String>(); |
| |
| CtsResultParserBase parser; |
| for (Map.Entry<String, String> metric : metrics.entrySet()) { |
| String testMethod = metric.getKey(); |
| String testResult = metric.getValue(); |
| CLog.d("test name %s", testMethod); |
| CLog.d("test result %s", testResult); |
| // Probe which result parser should be used. |
| if (shouldUseCtsXmlResultParser(testResult)) { |
| parser = new CtsXmlResultParser(); |
| } else { |
| parser = new CtsDelimitedResultParser(); |
| } |
| |
| // Get pairs of { KPI name, KPI value } from stdout that each test outputs. |
| // Assuming that a device has both the front and back cameras, parser will return |
| // 2 KPIs in HashMap. For an example of testCameraLaunch, |
| // { |
| // ("Camera 0 Camera launch time", "379.2"), |
| // ("Camera 1 Camera launch time", "272.8"), |
| // } |
| Map<String, String> testKpis = parser.parse(testResult, testMethod); |
| for (String k : testKpis.keySet()) { |
| if (resultsAll.containsKey(k)) { |
| throw new RuntimeException( |
| String.format("KPI name (%s) conflicts with the existing names.", k)); |
| } |
| } |
| parser.clear(); |
| |
| // Put each result together to post the final result |
| resultsAll.putAll(testKpis); |
| } |
| return resultsAll; |
| } |
| |
| public boolean shouldUseCtsXmlResultParser(String result) { |
| final String XML_DECLARATION = "<?xml"; |
| return (result.startsWith(XML_DECLARATION) |
| || result.startsWith(XML_DECLARATION.toUpperCase())); |
| } |
| |
| /** Data class of CTS test results for Camera framework performance test */ |
| public static class CtsMetric { |
| String testMethod; // "testSingleCapture" |
| String source; // "android.hardware.camera2.cts.PerformanceTest#testSingleCapture:327" |
| // or "testSingleCapture" (just test method name) |
| String message; // "Camera 0: Camera capture latency" |
| String type; // "lower_better" |
| String unit; // "ms" |
| String value; // "691.0" (is an average of 736.0 688.0 679.0 667.0 686.0) |
| String schemaKey; // RU schema key = message (+ testMethodName if needed), derived |
| |
| // eg. "android.hardware.camera2.cts.PerformanceTest#testSingleCapture:327" |
| public static final Pattern SOURCE_REGEX = |
| Pattern.compile("^(?<package>[a-zA-Z\\d\\._$]+)#(?<method>[a-zA-Z\\d_$]+)(:\\d+)?"); |
| // eg. "Camera 0: Camera capture latency" |
| public static final Pattern MESSAGE_REGEX = |
| Pattern.compile("^Camera\\s+(?<cameraId>\\d+):\\s+(?<kpiName>.*)"); |
| |
| CtsMetric( |
| String testMethod, |
| String source, |
| String message, |
| String type, |
| String unit, |
| String value) { |
| this.testMethod = testMethod; |
| this.source = source; |
| this.message = message; |
| this.type = type; |
| this.unit = unit; |
| this.value = value; |
| this.schemaKey = getRuSchemaKeyName(message); |
| } |
| |
| public boolean matches(String testMethod, String kpiName) { |
| return (this.testMethod.equals(testMethod) && this.message.endsWith(kpiName)); |
| } |
| |
| public String getRuSchemaKeyName(String message) { |
| // Note 1: The key shouldn't contain ":" for side by side report. |
| String schemaKey = message.replace(":", ""); |
| // Note 2: Two tests testReprocessingLatency & testReprocessingThroughput have the |
| // same metric names to report results. To make the report key name distinct, |
| // the test name is added as prefix for these tests for them. |
| final String[] TEST_NAMES_AS_PREFIX = { |
| "testReprocessingLatency", "testReprocessingThroughput" |
| }; |
| for (String testName : TEST_NAMES_AS_PREFIX) { |
| if (testMethod.endsWith(testName)) { |
| schemaKey = String.format("%s_%s", testName, schemaKey); |
| break; |
| } |
| } |
| return schemaKey; |
| } |
| |
| public String getTestMethodNameInSource(String source) { |
| Matcher m = SOURCE_REGEX.matcher(source); |
| if (!m.matches()) { |
| return source; |
| } |
| return m.group("method"); |
| } |
| } |
| |
| /** |
| * Base class of CTS test result parser. This is inherited to two derived parsers, |
| * {@link CtsDelimitedResultParser} for legacy delimiter separated format and |
| * {@link CtsXmlResultParser} for XML typed format introduced since NYC. |
| */ |
| public abstract class CtsResultParserBase { |
| |
| protected CtsMetric mSummary; |
| protected List<CtsMetric> mDetails = new ArrayList<>(); |
| |
| /** |
| * Parse Camera Performance KPIs result first, then leave the only KPIs that matter. |
| * |
| * @param result String to be parsed |
| * @param testMethod test method name used to leave the only metric that matters |
| * @return a {@link HashMap} that contains kpiName and kpiValue |
| */ |
| public abstract Map<String, String> parse(String result, String testMethod); |
| |
| protected Map<String, String> filter(List<CtsMetric> metrics, String testMethod) { |
| Map<String, String> filtered = new HashMap<String, String>(); |
| for (CtsMetric metric : metrics) { |
| for (String kpiName : mReportingKpis.get(testMethod)) { |
| // Post the data only when it matches with the given methods and KPI names. |
| if (metric.matches(testMethod, kpiName)) { |
| filtered.put(metric.schemaKey, metric.value); |
| } |
| } |
| } |
| return filtered; |
| } |
| |
| protected void setSummary(CtsMetric summary) { |
| mSummary = summary; |
| } |
| |
| protected void addDetail(CtsMetric detail) { |
| mDetails.add(detail); |
| } |
| |
| protected List<CtsMetric> getDetails() { |
| return mDetails; |
| } |
| |
| void clear() { |
| mSummary = null; |
| mDetails.clear(); |
| } |
| } |
| |
| /** |
| * Parses the camera performance test generated by the underlying instrumentation test and |
| * returns it to test runner for later reporting. |
| * |
| * <p>TODO(liuyg): Rename this class to not reference CTS. |
| * |
| * <p>Format: (summary message)| |(type)|(unit)|(value) ++++ |
| * (source)|(message)|(type)|(unit)|(value)... +++ ... |
| * |
| * <p>Example: Camera launch average time for Camera 1| |lower_better|ms|586.6++++ |
| * android.hardware.camera2.cts.PerformanceTest#testCameraLaunch:171| Camera 0: Camera open |
| * time|lower_better|ms|74.0 100.0 70.0 67.0 82.0 +++ |
| * android.hardware.camera2.cts.PerformanceTest#testCameraLaunch:171| Camera 0: Camera configure |
| * stream time|lower_better|ms|9.0 5.0 5.0 8.0 5.0 ... |
| * |
| * <p>See also com.android.cts.util.ReportLog for the format detail. |
| */ |
| public class CtsDelimitedResultParser extends CtsResultParserBase { |
| private static final String LOG_SEPARATOR = "\\+\\+\\+"; |
| private static final String SUMMARY_SEPARATOR = "\\+\\+\\+\\+"; |
| private final Pattern mSummaryRegex = |
| Pattern.compile( |
| "^(?<message>[^|]+)\\| \\|(?<type>[^|]+)\\|(?<unit>[^|]+)\\|(?<value>[0-9 .]+)"); |
| private final Pattern mDetailRegex = |
| Pattern.compile( |
| "^(?<source>[^|]+)\\|(?<message>[^|]+)\\|(?<type>[^|]+)\\|(?<unit>[^|]+)\\|" |
| + "(?<values>[0-9 .]+)"); |
| |
| @Override |
| public Map<String, String> parse(String result, String testMethod) { |
| parseToCtsMetrics(result, testMethod); |
| parseToCtsMetrics(result, testMethod); |
| return filter(getDetails(), testMethod); |
| } |
| |
| void parseToCtsMetrics(String result, String testMethod) { |
| // Split summary and KPIs from stdout passes as parameter. |
| String[] output = result.split(SUMMARY_SEPARATOR); |
| if (output.length != 2) { |
| throw new RuntimeException("Value not in the correct format"); |
| } |
| Matcher summaryMatcher = mSummaryRegex.matcher(output[0].trim()); |
| |
| // Parse summary. |
| // Example: "Camera launch average time for Camera 1| |lower_better|ms|586.6++++" |
| if (summaryMatcher.matches()) { |
| setSummary( |
| new CtsMetric( |
| testMethod, |
| null, |
| summaryMatcher.group("message"), |
| summaryMatcher.group("type"), |
| summaryMatcher.group("unit"), |
| summaryMatcher.group("value"))); |
| } else { |
| // Fall through since the summary is not posted as results. |
| CLog.w("Summary not in the correct format"); |
| } |
| |
| // Parse KPIs. |
| // Example: "android.hardware.camera2.cts.PerformanceTest#testCameraLaunch:171|Camera 0: |
| // Camera open time|lower_better|ms|74.0 100.0 70.0 67.0 82.0 +++" |
| String[] details = output[1].split(LOG_SEPARATOR); |
| for (String detail : details) { |
| Matcher detailMatcher = mDetailRegex.matcher(detail.trim()); |
| if (detailMatcher.matches()) { |
| // get average of kpi values |
| List<Double> values = new ArrayList<>(); |
| for (String value : detailMatcher.group("values").split("\\s+")) { |
| values.add(Double.parseDouble(value)); |
| } |
| String kpiValue = String.format("%.1f", getAverage(values)); |
| addDetail( |
| new CtsMetric( |
| testMethod, |
| detailMatcher.group("source"), |
| detailMatcher.group("message"), |
| detailMatcher.group("type"), |
| detailMatcher.group("unit"), |
| kpiValue)); |
| } else { |
| throw new RuntimeException("KPI not in the correct format"); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Parses the CTS test results in a XML format introduced since NYC. |
| * Format: |
| * <Summary> |
| * <Metric source="android.hardware.camera2.cts.PerformanceTest#testSingleCapture:327" |
| * message="Camera capture result average latency for all cameras " |
| * score_type="lower_better" |
| * score_unit="ms" |
| * <Value>353.9</Value> |
| * </Metric> |
| * </Summary> |
| * <Detail> |
| * <Metric source="android.hardware.camera2.cts.PerformanceTest#testSingleCapture:303" |
| * message="Camera 0: Camera capture latency" |
| * score_type="lower_better" |
| * score_unit="ms"> |
| * <Value>335.0</Value> |
| * <Value>302.0</Value> |
| * <Value>316.0</Value> |
| * </Metric> |
| * </Detail> |
| * } |
| * See also com.android.compatibility.common.util.ReportLog for the format detail. |
| */ |
| public class CtsXmlResultParser extends CtsResultParserBase { |
| private static final String ENCODING = "UTF-8"; |
| // XML constants |
| private static final String DETAIL_TAG = "Detail"; |
| private static final String METRIC_TAG = "Metric"; |
| private static final String MESSAGE_ATTR = "message"; |
| private static final String SCORETYPE_ATTR = "score_type"; |
| private static final String SCOREUNIT_ATTR = "score_unit"; |
| private static final String SOURCE_ATTR = "source"; |
| private static final String SUMMARY_TAG = "Summary"; |
| private static final String VALUE_TAG = "Value"; |
| private String mTestMethod; |
| |
| @Override |
| public Map<String, String> parse(String result, String testMethod) { |
| try { |
| mTestMethod = testMethod; |
| XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); |
| XmlPullParser parser = factory.newPullParser(); |
| parser.setInput(new ByteArrayInputStream(result.getBytes(ENCODING)), ENCODING); |
| parser.nextTag(); |
| parse(parser); |
| return filter(getDetails(), testMethod); |
| } catch (XmlPullParserException | IOException e) { |
| throw new RuntimeException("Failed to parse results in XML.", e); |
| } |
| } |
| |
| /** |
| * Parses a {@link CtsMetric} from the given XML parser. |
| * |
| * @param parser |
| * @throws IOException |
| * @throws XmlPullParserException |
| */ |
| private void parse(XmlPullParser parser) throws XmlPullParserException, IOException { |
| parser.require(XmlPullParser.START_TAG, null, SUMMARY_TAG); |
| parser.nextTag(); |
| setSummary(parseToCtsMetrics(parser)); |
| parser.nextTag(); |
| parser.require(XmlPullParser.END_TAG, null, SUMMARY_TAG); |
| parser.nextTag(); |
| if (parser.getName().equals(DETAIL_TAG)) { |
| while (parser.nextTag() == XmlPullParser.START_TAG) { |
| addDetail(parseToCtsMetrics(parser)); |
| } |
| parser.require(XmlPullParser.END_TAG, null, DETAIL_TAG); |
| } |
| } |
| |
| CtsMetric parseToCtsMetrics(XmlPullParser parser) |
| throws IOException, XmlPullParserException { |
| parser.require(XmlPullParser.START_TAG, null, METRIC_TAG); |
| String source = parser.getAttributeValue(null, SOURCE_ATTR); |
| String message = parser.getAttributeValue(null, MESSAGE_ATTR); |
| String type = parser.getAttributeValue(null, SCORETYPE_ATTR); |
| String unit = parser.getAttributeValue(null, SCOREUNIT_ATTR); |
| List<Double> values = new ArrayList<>(); |
| while (parser.nextTag() == XmlPullParser.START_TAG) { |
| parser.require(XmlPullParser.START_TAG, null, VALUE_TAG); |
| values.add(Double.parseDouble(parser.nextText())); |
| parser.require(XmlPullParser.END_TAG, null, VALUE_TAG); |
| } |
| String kpiValue = String.format("%.1f", getAverage(values)); |
| parser.require(XmlPullParser.END_TAG, null, METRIC_TAG); |
| return new CtsMetric(mTestMethod, source, message, type, unit, kpiValue); |
| } |
| } |
| |
| /* |
| * Parse the Json report from the Json String |
| * "test_single_capture": |
| * {"camera_id":"0","camera_capture_latency":[264.0,229.0,229.0,237.0,234.0], |
| * "camera_capture_result_latency":[230.0,197.0,196.0,204.0,202.0]}," |
| * "test_reprocessing_latency": |
| * {"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing", |
| * "capture_message":"shot to shot latency","latency":[102.0,101.0,99.0,99.0,100.0,101.0], |
| * "camera_reprocessing_shot_to_shot_average_latency":100.33333333333333}, |
| * |
| * TODO: move this to a seperate class |
| */ |
| public class CtsJsonResultParser { |
| |
| // report json file set in |
| // cts/tools/cts-tradefed/res/config/cts-preconditions.xml |
| private static final String JSON_RESULT_FILE = |
| "/sdcard/report-log-files/CtsCameraTestCases.reportlog.json"; |
| private static final String CAMERA_ID_KEY = "camera_id"; |
| private static final String AVERAGE_LATENCY_KEY = "average_latency"; |
| private static final String REPROCESS_TYPE_KEY = "reprocess_type"; |
| private static final String CAPTURE_MESSAGE_KEY = "capture_message"; |
| private static final String LATENCY_KEY = "latency"; |
| |
| public Map<String, String> parse() { |
| |
| Map<String, String> metrics = new HashMap<>(); |
| |
| String jsonString = getFormatedJsonReportFromFile(); |
| if (null == jsonString) { |
| throw new RuntimeException("Get null json report string."); |
| } |
| |
| Map<String, List<Double>> metricsData = new HashMap<>(); |
| |
| try { |
| JSONObject jsonObject = new JSONObject(jsonString); |
| |
| for (String testMethod : METHOD_JSON_KEY_MAP.keySet()) { |
| |
| JSONArray jsonArray = |
| (JSONArray) jsonObject.get(METHOD_JSON_KEY_MAP.get(testMethod)); |
| |
| switch (testMethod) { |
| case TEST_REPROCESSING_THROUGHPUT: |
| case TEST_REPROCESSING_LATENCY: |
| for (int i = 0; i < jsonArray.length(); i++) { |
| JSONObject element = jsonArray.getJSONObject(i); |
| |
| // create a kpiKey from camera id, |
| // reprocess type and capture message |
| String cameraId = element.getString(CAMERA_ID_KEY); |
| String reprocessType = element.getString(REPROCESS_TYPE_KEY); |
| String captureMessage = element.getString(CAPTURE_MESSAGE_KEY); |
| String kpiKey = |
| String.format( |
| "%s_Camera %s %s %s", |
| testMethod, |
| cameraId, |
| reprocessType, |
| captureMessage); |
| |
| // read the data array from json object |
| JSONArray jsonDataArray = element.getJSONArray(LATENCY_KEY); |
| if (!metricsData.containsKey(kpiKey)) { |
| List<Double> list = new ArrayList<>(); |
| metricsData.put(kpiKey, list); |
| } |
| for (int j = 0; j < jsonDataArray.length(); j++) { |
| metricsData.get(kpiKey).add(jsonDataArray.getDouble(j)); |
| } |
| } |
| break; |
| case TEST_SINGLE_CAPTURE: |
| case TEST_CAMERA_LAUNCH: |
| for (int i = 0; i < jsonArray.length(); i++) { |
| JSONObject element = jsonArray.getJSONObject(i); |
| |
| String cameraid = element.getString(CAMERA_ID_KEY); |
| for (String kpiName : mReportingKpis.get(testMethod)) { |
| |
| // the json key is all lower case |
| String jsonKey = kpiName.toLowerCase().replace(" ", "_"); |
| String kpiKey = |
| String.format("Camera %s %s", cameraid, kpiName); |
| if (!metricsData.containsKey(kpiKey)) { |
| List<Double> list = new ArrayList<>(); |
| metricsData.put(kpiKey, list); |
| } |
| JSONArray jsonDataArray = element.getJSONArray(jsonKey); |
| for (int j = 0; j < jsonDataArray.length(); j++) { |
| metricsData.get(kpiKey).add(jsonDataArray.getDouble(j)); |
| } |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } catch (JSONException e) { |
| CLog.w("JSONException: %s in string %s", e.getMessage(), jsonString); |
| } |
| |
| // take the average of all data for reporting |
| for (String kpiKey : metricsData.keySet()) { |
| String kpiValue = String.format("%.1f", getAverage(metricsData.get(kpiKey))); |
| metrics.put(kpiKey, kpiValue); |
| } |
| return metrics; |
| } |
| |
| public boolean isJsonFileExist() { |
| try { |
| return getDevice().doesFileExist(JSON_RESULT_FILE); |
| } catch (DeviceNotAvailableException e) { |
| throw new RuntimeException("Failed to check json report file on device.", e); |
| } |
| } |
| |
| /* |
| * read json report file on the device |
| */ |
| private String getFormatedJsonReportFromFile() { |
| String jsonString = null; |
| try { |
| // pull the json report file from device |
| File outputFile = FileUtil.createTempFile("json", ".txt"); |
| getDevice().pullFile(JSON_RESULT_FILE, outputFile); |
| jsonString = reformatJsonString(FileUtil.readStringFromFile(outputFile)); |
| } catch (IOException e) { |
| CLog.w("Couldn't parse the output json log file: ", e); |
| } catch (DeviceNotAvailableException e) { |
| CLog.w("Could not pull file: %s, error: %s", JSON_RESULT_FILE, e); |
| } |
| return jsonString; |
| } |
| |
| // Reformat the json file to remove duplicate keys |
| private String reformatJsonString(String jsonString) { |
| |
| final String TEST_METRICS_PATTERN = "\\\"([a-z0-9_]*)\\\":(\\{[^{}]*\\})"; |
| StringBuilder newJsonBuilder = new StringBuilder(); |
| // Create map of stream names and json objects. |
| HashMap<String, List<String>> jsonMap = new HashMap<>(); |
| Pattern p = Pattern.compile(TEST_METRICS_PATTERN); |
| Matcher m = p.matcher(jsonString); |
| while (m.find()) { |
| String key = m.group(1); |
| String value = m.group(2); |
| if (!jsonMap.containsKey(key)) { |
| jsonMap.put(key, new ArrayList<String>()); |
| } |
| jsonMap.get(key).add(value); |
| } |
| // Rewrite json string as arrays. |
| newJsonBuilder.append("{"); |
| boolean firstLine = true; |
| for (String key : jsonMap.keySet()) { |
| if (!firstLine) { |
| newJsonBuilder.append(","); |
| } else { |
| firstLine = false; |
| } |
| newJsonBuilder.append("\"").append(key).append("\":["); |
| boolean firstValue = true; |
| for (String stream : jsonMap.get(key)) { |
| if (!firstValue) { |
| newJsonBuilder.append(","); |
| } else { |
| firstValue = false; |
| } |
| newJsonBuilder.append(stream); |
| } |
| newJsonBuilder.append("]"); |
| } |
| newJsonBuilder.append("}"); |
| return newJsonBuilder.toString(); |
| } |
| } |
| } |