1 /* 2 * Copyright (C) 2016 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.device.IFileEntry; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 24 import com.android.tradefed.result.ITestInvocationListener; 25 import com.android.tradefed.result.TestDescription; 26 import com.android.tradefed.util.FileUtil; 27 import com.android.tradefed.util.proto.TfMetricProtoUtil; 28 29 import java.io.BufferedReader; 30 import java.io.File; 31 import java.io.FileReader; 32 import java.io.IOException; 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 39 /** 40 * Camera2 framework stress test 41 * This is a test invocation that runs stress tests against Camera2 framework (API & HAL) to 42 * isolate stability issues from Camera application. 43 * This invocation uses a Camera2InstrumentationTestRunner to run a set of 44 * Camera framework stress tests. 45 */ 46 @OptionClass(alias = "camera2-framework-stress") 47 public class Camera2FrameworkStressTest extends CameraTestBase { 48 49 // Keys in instrumentation test metrics 50 private static final String RESULT_DIR = 51 "/storage/emulated/0/Android/sandbox/com.android.mediaframeworktest/camera-out"; 52 private static final String RESULT_FILE_FORMAT = RESULT_DIR + "fwk-stress_camera_%s.txt"; 53 private static final Pattern RESULT_FILE_REGEX = Pattern.compile( 54 "^fwk-stress_camera_(?<id>.+).txt"); 55 private static final String KEY_NUM_ATTEMPTS = "numAttempts"; 56 private static final String KEY_ITERATION = "iteration"; 57 Camera2FrameworkStressTest()58 public Camera2FrameworkStressTest() { 59 // Note that default value in constructor will be overridden by the passing option from 60 // a command line. 61 setTestPackage("com.android.mediaframeworktest"); 62 setTestRunner("com.android.mediaframeworktest.Camera2InstrumentationTestRunner"); 63 setRuKey("CameraFrameworkStress"); 64 setTestTimeoutMs(2 * 60 * 60 * 1000); // 2 hours 65 setLogcatOnFailure(true); 66 } 67 68 /** 69 * {@inheritDoc} 70 */ 71 @Override run(ITestInvocationListener listener)72 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 73 runInstrumentationTest(listener, new CollectingListener(listener)); 74 } 75 76 /** 77 * A listener to collect the output from test run and fatal errors 78 */ 79 private class CollectingListener extends DefaultCollectingListener { 80 CollectingListener(ITestInvocationListener listener)81 public CollectingListener(ITestInvocationListener listener) { 82 super(listener); 83 } 84 85 @Override handleMetricsOnTestEnded(TestDescription test, Map<String, String> testMetrics)86 public void handleMetricsOnTestEnded(TestDescription test, Map<String, String> testMetrics) { 87 if (testMetrics == null) { 88 return; // No-op if there is nothing to post. 89 } 90 for (Map.Entry<String, String> metric : testMetrics.entrySet()) { 91 getAggregatedMetrics().put(metric.getKey(), metric.getValue()); 92 } 93 } 94 95 @Override testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)96 public void testEnded( 97 TestDescription test, long endTime, HashMap<String, Metric> testMetrics) { 98 if (hasTestRunFatalError()) { 99 CLog.v("The instrumentation result not found. Fall back to get the metrics from a " 100 + "log file. errorMsg: %s", getCollectingListener().getErrorMessage()); 101 } 102 103 // For stress test, parse the metrics from a log file. 104 testMetrics = TfMetricProtoUtil.upgradeConvert(parseLog(test.getTestName())); 105 super.testEnded(test, endTime, testMetrics); 106 } 107 108 // Return null if failed to parse the result file or the test didn't even start. parseLog(String testName)109 private Map<String, String> parseLog(String testName) { 110 Map<String, String> postMetrics = new HashMap<String, String>(); 111 String resultFilePath; 112 try { 113 for (String cameraId : getCameraIdList(RESULT_DIR)) { 114 File outputFile = FileUtil.createTempFile("fwk-stress", ".txt"); 115 resultFilePath = getResultFilePath(cameraId); 116 getDevice().pullFile(resultFilePath, outputFile); 117 if (outputFile == null) { 118 throw new DeviceNotAvailableException(String.format("Failed to pull the " 119 + "result file: %s", resultFilePath), 120 getDevice().getSerialNumber()); 121 } 122 123 BufferedReader reader = new BufferedReader(new FileReader(outputFile)); 124 String line; 125 Map<String, String> resultMap = new HashMap<String, String>(); 126 127 // Parse results from log file that contain the key-value pairs. 128 // eg. "numAttempts=10|iteration=9[|cameraId=0]" 129 try { 130 while ((line = reader.readLine()) != null) { 131 String[] pairs = line.split("\\|"); 132 for (String pair : pairs) { 133 String[] keyValue = pair.split("="); 134 // Each should be a pair of key and value. 135 String key = keyValue[0].trim(); 136 String value = keyValue[1].trim(); 137 resultMap.put(key, value); 138 } 139 } 140 } finally { 141 reader.close(); 142 } 143 144 // Fail if a stress test doesn't start. 145 if (0 == Integer.parseInt(resultMap.get(KEY_NUM_ATTEMPTS))) { 146 CLog.w("Failed to start stress tests. test setup configured incorrectly?"); 147 return null; 148 } 149 // Post the number of iterations only with the key name associated with 150 // test name and camera ID. 151 String keyName = testName + "_" + cameraId; 152 postMetrics.put(keyName, resultMap.get(KEY_ITERATION)); 153 } 154 } catch (IOException e) { 155 CLog.w("Couldn't parse the output log file"); 156 CLog.e(e); 157 } catch (DeviceNotAvailableException e) { 158 CLog.w("Could not pull file: %s, error:", RESULT_DIR); 159 CLog.e(e); 160 } catch (NumberFormatException e) { 161 CLog.w("Could not find the key in file: %s, error:", KEY_NUM_ATTEMPTS); 162 CLog.e(e); 163 } 164 return postMetrics; 165 } 166 } 167 getCameraIdList(String resultDir)168 private ArrayList<String> getCameraIdList(String resultDir) throws DeviceNotAvailableException { 169 // The result files are created per each camera ID 170 ArrayList<String> cameraIds = new ArrayList<>(); 171 IFileEntry dirEntry = getDevice().getFileEntry(resultDir); 172 if (dirEntry != null) { 173 for (IFileEntry file : dirEntry.getChildren(false)) { 174 String fileName = file.getName(); 175 Matcher matcher = RESULT_FILE_REGEX.matcher(fileName); 176 if (matcher.matches()) { 177 cameraIds.add(matcher.group("id")); 178 } 179 } 180 } 181 182 if (cameraIds.isEmpty()) { 183 CLog.w("No camera ID is found in %s. The resultToFile instrumentation argument is set" 184 + " to false?", resultDir); 185 } 186 return cameraIds; 187 } 188 getResultFilePath(String cameraId)189 private String getResultFilePath(String cameraId) { 190 return String.format(RESULT_FILE_FORMAT, cameraId); 191 } 192 } 193