blob: cda1bb3d57e8d36a2a106f61998b2d9d4ba8fcb6 [file] [log] [blame]
Kuan-Tung Panfbd59a02017-08-02 19:50:04 +00001/*
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
17package com.android.media.tests;
18
Kuan-Tung Panfbd59a02017-08-02 19:50:04 +000019import com.android.tradefed.config.Option;
20import com.android.tradefed.config.OptionClass;
21import com.android.tradefed.device.DeviceNotAvailableException;
22import com.android.tradefed.log.LogUtil.CLog;
23import com.android.tradefed.result.ITestInvocationListener;
jdesprez607ef1a2018-02-07 16:34:53 -080024import com.android.tradefed.result.TestDescription;
Kuan-Tung Panfbd59a02017-08-02 19:50:04 +000025import com.android.tradefed.targetprep.BuildError;
26import com.android.tradefed.targetprep.ITargetPreparer;
27import com.android.tradefed.targetprep.TargetSetupError;
28import com.android.tradefed.targetprep.TemperatureThrottlingWaiter;
29import com.android.tradefed.util.MultiMap;
30
31import org.junit.Assert;
32
33import java.util.Collections;
34import java.util.HashMap;
35import java.util.Map;
36import java.util.Set;
37import java.util.regex.Matcher;
38import java.util.regex.Pattern;
39
40/**
41 * Camera app startup test
42 *
43 * Runs CameraActivityTest to measure Camera startup time and reports the metrics.
44 */
45@OptionClass(alias = "camera-startup")
46public class CameraStartupTest extends CameraTestBase {
47
48 private static final Pattern STATS_REGEX = Pattern.compile(
49 "^(?<coldStartup>[0-9.]+)\\|(?<warmStartup>[0-9.]+)\\|(?<values>[0-9 .-]+)");
50 private static final String PREFIX_COLD_STARTUP = "Cold";
51 // all metrics are expected to be less than 10 mins and greater than 0.
52 private static final int METRICS_MAX_THRESHOLD_MS = 10 * 60 * 1000;
53 private static final int METRICS_MIN_THRESHOLD_MS = 0;
54 private static final String INVALID_VALUE = "-1";
55
56 @Option(name="num-test-runs", description="The number of test runs. A instrumentation "
57 + "test will be repeatedly executed. Then it posts the average of test results.")
58 private int mNumTestRuns = 1;
59
60 @Option(name="delay-between-test-runs", description="Time delay between multiple test runs, "
61 + "in msecs. Used to wait for device to cool down. "
62 + "Note that this will be ignored when TemperatureThrottlingWaiter is configured.")
63 private long mDelayBetweenTestRunsMs = 120 * 1000; // 2 minutes
64
65 private MultiMap<String, String> mMultipleRunMetrics = new MultiMap<String, String>();
66 private Map<String, String> mAverageMultipleRunMetrics = new HashMap<String, String>();
67 private long mTestRunsDurationMs = 0;
68
69 public CameraStartupTest() {
70 setTestPackage("com.google.android.camera");
71 setTestClass("com.android.camera.latency.CameraStartupTest");
72 setTestRunner("android.test.InstrumentationTestRunner");
73 setRuKey("CameraAppStartup");
74 setTestTimeoutMs(60 * 60 * 1000); // 1 hour
75 }
76
77 /**
78 * {@inheritDoc}
79 */
80 @Override
81 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
82 runMultipleInstrumentationTests(listener, mNumTestRuns);
83 }
84
85 private void runMultipleInstrumentationTests(ITestInvocationListener listener, int numTestRuns)
86 throws DeviceNotAvailableException {
87 Assert.assertTrue(numTestRuns > 0);
88
89 mTestRunsDurationMs = 0;
90 for (int i = 0; i < numTestRuns; ++i) {
91 CLog.v("Running multiple instrumentation tests... [%d/%d]", i + 1, numTestRuns);
92 CollectingListener singleRunListener = new CollectingListener(listener);
93 runInstrumentationTest(listener, singleRunListener);
94 mTestRunsDurationMs += getTestDurationMs();
95
96 if (singleRunListener.hasFailedTests() ||
97 singleRunListener.hasTestRunFatalError()) {
98 exitTestRunsOnError(listener, singleRunListener.getErrorMessage());
99 return;
100 }
101 if (i + 1 < numTestRuns) { // Skipping preparation on the last run
102 postSetupTestRun();
103 }
104 }
105
106 // Post the average of metrics collected in multiple instrumentation test runs.
107 postMultipleRunMetrics(listener);
108 CLog.v("multiple instrumentation tests end");
109 }
110
111 private void exitTestRunsOnError(ITestInvocationListener listener, String errorMessage) {
112 CLog.e("The instrumentation result not found. Test runs may have failed due to exceptions."
113 + " Test results will not be posted. errorMsg: %s", errorMessage);
114 listener.testRunFailed(errorMessage);
115 listener.testRunEnded(mTestRunsDurationMs, Collections.<String, String>emptyMap());
116 }
117
118 private void postMultipleRunMetrics(ITestInvocationListener listener) {
119 listener.testRunEnded(mTestRunsDurationMs, getAverageMultipleRunMetrics());
120 }
121
122 private void postSetupTestRun() throws DeviceNotAvailableException {
123 // Reboot for a cold start up of Camera application
124 CLog.d("Cold start: Rebooting...");
125 getDevice().reboot();
126
127 // Wait for device to cool down to target temperature
128 // Use TemperatureThrottlingWaiter if configured, otherwise just wait for
129 // a specific amount of time.
130 CLog.d("Cold start: Waiting for device to cool down...");
131 boolean usedTemperatureThrottlingWaiter = false;
132 for (ITargetPreparer preparer : mConfiguration.getTargetPreparers()) {
133 if (preparer instanceof TemperatureThrottlingWaiter) {
134 usedTemperatureThrottlingWaiter = true;
135 try {
136 preparer.setUp(getDevice(), null);
137 } catch (TargetSetupError e) {
138 CLog.w("No-op even when temperature is still high after wait timeout. "
139 + "error: %s", e.getMessage());
140 } catch (BuildError e) {
141 // This should not happen.
142 }
143 }
144 }
145 if (!usedTemperatureThrottlingWaiter) {
146 getRunUtil().sleep(mDelayBetweenTestRunsMs);
147 }
148 CLog.d("Device gets prepared for the next test run.");
149 }
150
151 // Call this function once at the end to get the average.
152 private Map<String, String> getAverageMultipleRunMetrics() {
153 Assert.assertTrue(mMultipleRunMetrics.size() > 0);
154
155 Set<String> keys = mMultipleRunMetrics.keySet();
156 mAverageMultipleRunMetrics.clear();
157 for (String key : keys) {
158 int sum = 0;
159 int size = 0;
160 boolean isInvalid = false;
161 for (String valueString : mMultipleRunMetrics.get(key)) {
162 int value = Integer.parseInt(valueString);
163 // If value is out of valid range, skip posting the result associated with the key
164 if (value > METRICS_MAX_THRESHOLD_MS || value < METRICS_MIN_THRESHOLD_MS) {
165 isInvalid = true;
166 break;
167 }
168 sum += value;
169 ++size;
170 }
171
172 String valueString = INVALID_VALUE;
173 if (isInvalid) {
174 CLog.w("Value is out of valid range. Key: %s ", key);
175 } else {
176 valueString = String.format("%d", (sum / size));
177 }
178 mAverageMultipleRunMetrics.put(key, valueString);
179 }
180 return mAverageMultipleRunMetrics;
181 }
182
183 /**
184 * A listener to collect the output from test run and fatal errors
185 */
186 private class CollectingListener extends DefaultCollectingListener {
187
188 public CollectingListener(ITestInvocationListener listener) {
189 super(listener);
190 }
191
192 @Override
jdesprez607ef1a2018-02-07 16:34:53 -0800193 public void handleMetricsOnTestEnded(TestDescription test, Map<String, String> testMetrics) {
Kuan-Tung Panfbd59a02017-08-02 19:50:04 +0000194 // Test metrics accumulated will be posted at the end of test run.
195 getAggregatedMetrics().putAll(parseResults(testMetrics));
196 }
197
198 @Override
199 public void handleTestRunEnded(ITestInvocationListener listener, long elapsedTime,
200 Map<String, String> runMetrics) {
201 // Do not post aggregated metrics from a single run to a dashboard. Instead, it needs
202 // to collect all metrics from multiple test runs.
203 mMultipleRunMetrics.putAll(getAggregatedMetrics());
204 }
205
206 public Map<String, String> parseResults(Map<String, String> testMetrics) {
207 // Parse activity time stats from the instrumentation result.
208 // Format : <metric_key>=<cold_startup>|<average_of_warm_startups>|<all_startups>
209 // Example:
210 // VideoStartupTimeMs=1098|1184.6|1098 1222 ... 788
211 // VideoOnCreateTimeMs=138|103.3|138 114 ... 114
212 // VideoOnResumeTimeMs=39|40.4|39 36 ... 41
213 // VideoFirstPreviewFrameTimeMs=0|0.0|0 0 ... 0
214 // CameraStartupTimeMs=2388|1045.4|2388 1109 ... 746
215 // CameraOnCreateTimeMs=574|122.7|574 124 ... 109
216 // CameraOnResumeTimeMs=610|504.6|610 543 ... 278
217 // CameraFirstPreviewFrameTimeMs=0|0.0|0 0 ... 0
218 //
219 // Then report only the first two startup time of cold startup and average warm startup.
220 Map<String, String> parsed = new HashMap<String, String>();
221 for (Map.Entry<String, String> metric : testMetrics.entrySet()) {
222 Matcher matcher = STATS_REGEX.matcher(metric.getValue());
223 String keyName = metric.getKey();
224 String coldStartupValue = INVALID_VALUE;
225 String warmStartupValue = INVALID_VALUE;
226 if (matcher.matches()) {
227 coldStartupValue = matcher.group("coldStartup");
228 warmStartupValue = matcher.group("warmStartup");
229 }
230 parsed.put(PREFIX_COLD_STARTUP + keyName, coldStartupValue);
231 parsed.put(keyName, warmStartupValue);
232 }
233 return parsed;
234 }
235 }
236}