1 /* 2 * Copyright (C) 2010 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.tradefed.testtype; 18 19 import com.android.ddmlib.FileListingService; 20 import com.android.ddmlib.Log; 21 import com.android.tradefed.config.Option; 22 import com.android.tradefed.config.Option.Importance; 23 import com.android.tradefed.config.OptionClass; 24 import com.android.tradefed.device.DeviceNotAvailableException; 25 import com.android.tradefed.device.IFileEntry; 26 import com.android.tradefed.device.ITestDevice; 27 import com.android.tradefed.result.ITestInvocationListener; 28 import com.android.tradefed.util.proto.TfMetricProtoUtil; 29 30 import java.util.HashMap; 31 import java.util.Map; 32 import java.util.concurrent.TimeUnit; 33 34 /** 35 * A Test that runs a native stress test executable on given device. 36 * <p/> 37 * It uses {@link NativeStressTestParser} to parse out number of iterations completed and report 38 * those results to the {@link ITestInvocationListener}s. 39 */ 40 @OptionClass(alias = "native-stress") 41 public class NativeStressTest implements IDeviceTest, IRemoteTest { 42 43 private static final String LOG_TAG = "NativeStressTest"; 44 static final String DEFAULT_TEST_PATH = "data/nativestresstest"; 45 46 // The metrics key names to report to listeners 47 // TODO: these key names are temporary 48 static final String AVG_ITERATION_TIME_KEY = "avg-iteration-time"; 49 static final String ITERATION_KEY = "iterations"; 50 51 private ITestDevice mDevice = null; 52 53 @Option(name = "native-stress-device-path", 54 description="The path on the device where native stress tests are located.") 55 private String mDeviceTestPath = DEFAULT_TEST_PATH; 56 57 @Option(name = "stress-module-name", 58 description="The name of the native test module to run. " + 59 "If not specified all tests in --native-stress-device-path will be run.") 60 private String mTestModule = null; 61 62 @Option(name = "iterations", 63 description="The number of stress test iterations per run.", 64 importance = Importance.IF_UNSET) 65 private Integer mNumIterations = null; 66 67 @Option(name = "runs", 68 description="The number of stress test runs to perform.") 69 private int mNumRuns = 1; 70 71 @Option(name = "max-iteration-time", description = 72 "The maximum time to allow for one stress test iteration in ms.") 73 private int mMaxIterationTime = 5 * 60 * 1000; 74 75 // TODO: consider sharing code with {@link GTest} 76 77 /** 78 * {@inheritDoc} 79 */ 80 @Override 81 public void setDevice(ITestDevice device) { 82 mDevice = device; 83 } 84 85 /** 86 * {@inheritDoc} 87 */ 88 @Override 89 public ITestDevice getDevice() { 90 return mDevice; 91 } 92 93 /** 94 * Set the Android native stress test module to run. 95 * 96 * @param moduleName The name of the native test module to run 97 */ 98 public void setModuleName(String moduleName) { 99 mTestModule = moduleName; 100 } 101 102 /** 103 * Get the Android native test module to run. 104 * 105 * @return the name of the native test module to run, or null if not set 106 */ 107 public String getModuleName() { 108 return mTestModule; 109 } 110 111 /** 112 * Set the number of iterations to execute per run 113 */ 114 void setNumIterations(int iterations) { 115 mNumIterations = iterations; 116 } 117 118 /** 119 * Set the number of runs to execute 120 */ 121 void setNumRuns(int runs) { 122 mNumRuns = runs; 123 } 124 125 /** 126 * Gets the path where native stress tests live on the device. 127 * 128 * @return The path on the device where the native tests live. 129 */ 130 private String getTestPath() { 131 StringBuilder testPath = new StringBuilder(mDeviceTestPath); 132 if (mTestModule != null) { 133 testPath.append(FileListingService.FILE_SEPARATOR); 134 testPath.append(mTestModule); 135 } 136 return testPath.toString(); 137 } 138 139 /** 140 * Executes all native stress tests in a folder as well as in all subfolders recursively. 141 * 142 * @param rootEntry The root folder to begin searching for native tests 143 * @param testDevice The device to run tests on 144 * @param listener the run listener 145 * @throws DeviceNotAvailableException 146 */ 147 private void doRunAllTestsInSubdirectory( 148 IFileEntry rootEntry, ITestDevice testDevice, ITestInvocationListener listener) 149 throws DeviceNotAvailableException { 150 151 if (rootEntry.isDirectory()) { 152 // recursively run tests in all subdirectories 153 for (IFileEntry childEntry : rootEntry.getChildren(true)) { 154 doRunAllTestsInSubdirectory(childEntry, testDevice, listener); 155 } 156 } else { 157 // assume every file is a valid stress test binary. 158 // use name of file as run name 159 NativeStressTestParser resultParser = createResultParser(rootEntry.getName()); 160 String fullPath = rootEntry.getFullEscapedPath(); 161 Log.i(LOG_TAG, String.format("Running native stress test %s on %s", fullPath, 162 mDevice.getSerialNumber())); 163 // force file to be executable 164 testDevice.executeShellCommand(String.format("chmod 755 %s", fullPath)); 165 int startIteration = 0; 166 int endIteration = mNumIterations - 1; 167 long startTime = System.currentTimeMillis(); 168 listener.testRunStarted(resultParser.getRunName(), 0); 169 try { 170 for (int i = 0; i < mNumRuns; i++) { 171 Log.i(LOG_TAG, String.format("Running %s for %d iterations", 172 rootEntry.getName(), mNumIterations)); 173 // -s is start iteration, -e means end iteration 174 // use maxShellOutputResponseTime to enforce the max iteration time 175 // it won't be exact, but should be close 176 testDevice.executeShellCommand(String.format("%s -s %d -e %d", fullPath, 177 startIteration, endIteration), resultParser, 178 mMaxIterationTime, TimeUnit.MILLISECONDS, 0); 179 // iteration count is also used as a random seed value, so want use different 180 // values for each run 181 startIteration += mNumIterations; 182 endIteration += mNumIterations; 183 } 184 // TODO: is catching exceptions, and reporting testRunFailed necessary? 185 } finally { 186 reportTestCompleted(startTime, listener, resultParser); 187 } 188 189 } 190 } 191 192 private void reportTestCompleted( 193 long startTime, ITestInvocationListener listener, NativeStressTestParser parser) { 194 final long elapsedTime = System.currentTimeMillis() - startTime; 195 int iterationsComplete = parser.getIterationsCompleted(); 196 float avgIterationTime = iterationsComplete > 0 ? elapsedTime / iterationsComplete : 0; 197 Map<String, String> metricMap = new HashMap<String, String>(2); 198 Log.i(LOG_TAG, String.format( 199 "Stress test %s is finished. Num iterations %d, avg time %f ms", 200 parser.getRunName(), iterationsComplete, avgIterationTime)); 201 metricMap.put(ITERATION_KEY, Integer.toString(iterationsComplete)); 202 metricMap.put(AVG_ITERATION_TIME_KEY, Float.toString(avgIterationTime)); 203 listener.testRunEnded(elapsedTime, TfMetricProtoUtil.upgradeConvert(metricMap)); 204 } 205 206 /** 207 * Factory method for creating a {@link NativeStressTestParser} that parses test output 208 * <p/> 209 * Exposed so unit tests can mock. 210 * 211 * @param runName 212 * @return a {@link NativeStressTestParser} 213 */ 214 NativeStressTestParser createResultParser(String runName) { 215 return new NativeStressTestParser(runName); 216 } 217 218 /** 219 * {@inheritDoc} 220 */ 221 @Override 222 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 223 if (mDevice == null) { 224 throw new IllegalArgumentException("Device has not been set"); 225 } 226 if (mNumIterations == null || mNumIterations <= 0) { 227 throw new IllegalArgumentException("number of iterations has not been set"); 228 } 229 230 String testPath = getTestPath(); 231 IFileEntry nativeTestDirectory = mDevice.getFileEntry(testPath); 232 if (nativeTestDirectory == null) { 233 Log.w(LOG_TAG, String.format("Could not find native stress test directory %s in %s!", 234 testPath, mDevice.getSerialNumber())); 235 return; 236 } 237 doRunAllTestsInSubdirectory(nativeTestDirectory, mDevice, listener); 238 } 239 } 240