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