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.framework.tests; 18 19 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; 20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 21 import com.android.tradefed.build.IBuildInfo; 22 import com.android.tradefed.config.Option; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.device.ITestDevice; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.result.FileInputStreamSource; 27 import com.android.tradefed.result.ITestInvocationListener; 28 import com.android.tradefed.result.LogDataType; 29 import com.android.tradefed.testtype.IBuildReceiver; 30 import com.android.tradefed.testtype.IDeviceTest; 31 import com.android.tradefed.testtype.IRemoteTest; 32 import com.android.tradefed.util.CommandResult; 33 import com.android.tradefed.util.CommandStatus; 34 import com.android.tradefed.util.FileUtil; 35 import com.android.tradefed.util.RunUtil; 36 37 import java.io.File; 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.List; 41 42 /** 43 * Runs a series of automated use cases and collects loaded class information in order to generate a 44 * list of preloaded classes based on the input thresholds. 45 */ 46 public class PreloadedClassesTest implements IRemoteTest, IDeviceTest, IBuildReceiver { 47 private static final String JUNIT_RUNNER = "android.support.test.runner.AndroidJUnitRunner"; 48 // Preload tool commands 49 private static final String TOOL_CMD = "java -cp %s com.android.preload.Main --seq %s %s"; 50 private static final String SCAN_ALL_CMD = "scan-all"; 51 private static final String COMPUTE_CMD = "comp %d %s"; 52 private static final String EXPORT_CMD = "export %s"; 53 private static final String IMPORT_CMD = "import %s"; 54 // Large, common timeouts 55 private static final long SCAN_TIMEOUT_MS = 5 * 60 * 1000; 56 private static final long COMPUTE_TIMEOUT_MS = 60 * 1000; 57 58 @Option( 59 name = "package", 60 description = "Instrumentation package for use case automation.", 61 mandatory = true) 62 private String mPackage = null; 63 64 @Option( 65 name = "test-case", 66 description = "List of use cases to exercise from the package.", 67 mandatory = true) 68 private List<String> mTestCases = new ArrayList<>(); 69 70 @Option(name = "preload-tool", description = "Overridden location of the preload JAR file.") 71 private String mPreloadToolJarPath = null; 72 73 @Option( 74 name = "threshold", 75 description = "List of thresholds for computing preloaded classes.", 76 mandatory = true) 77 private List<String> mThresholds = new ArrayList<>(); 78 79 @Option( 80 name = "quit-on-error", 81 description = "Quits if errors are encountered anywhere in the process.", 82 mandatory = false) 83 private boolean mQuitOnError = false; 84 85 private ITestDevice mDevice; 86 private IBuildInfo mBuildInfo; 87 private List<File> mExportFiles = new ArrayList<>(); 88 89 /** {@inheritDoc} */ 90 @Override run(ITestInvocationListener listener)91 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 92 // Download preload tool, if not supplied 93 if (mPreloadToolJarPath == null) { 94 File preload = mBuildInfo.getFile("preload2.jar"); 95 if (preload != null && preload.exists()) { 96 mPreloadToolJarPath = preload.getAbsolutePath(); 97 } else { 98 CLog.e("Unable to find the preload tool."); 99 } 100 } else { 101 CLog.v("Using alternative preload tool path, %s", mPreloadToolJarPath); 102 } 103 104 IRemoteAndroidTestRunner runner = 105 new RemoteAndroidTestRunner(mPackage, JUNIT_RUNNER, getDevice().getIDevice()); 106 107 for (String testCaseIdentifier : mTestCases) { 108 // Run an individual use case 109 runner.addInstrumentationArg("class", testCaseIdentifier); 110 getDevice().runInstrumentationTests(runner, listener); 111 // Scan loaded classes and export 112 File outfile = scanAndExportClasses(); 113 if (outfile != null) { 114 mExportFiles.add(outfile); 115 } else { 116 String msg = String.format("Failed to find outfile after %s", testCaseIdentifier); 117 if (mQuitOnError) { 118 throw new RuntimeException(msg); 119 } else { 120 CLog.e(msg + ". Continuing anyway..."); 121 } 122 } 123 } 124 125 try { 126 // Consider each threshold input 127 for (String thresholdStr : mThresholds) { 128 int threshold = 0; 129 try { 130 threshold = Integer.parseInt(thresholdStr); 131 } catch (NumberFormatException e) { 132 if (mQuitOnError) { 133 throw e; 134 } else { 135 CLog.e("Failed to parse threshold: %s", thresholdStr); 136 CLog.e(e); 137 continue; 138 } 139 } 140 // Generate the corresponding preloaded classes 141 File classes = writePreloadedClasses(threshold); 142 if (classes != null) { 143 try (FileInputStreamSource stream = new FileInputStreamSource(classes)) { 144 String name = String.format("preloaded-classes-threshold-%s", thresholdStr); 145 listener.testLog(name, LogDataType.TEXT, stream); 146 } 147 // Clean up after uploading 148 FileUtil.deleteFile(classes); 149 } else { 150 String msg = 151 String.format( 152 "Failed to generate classes file for threshold, %s", 153 thresholdStr); 154 if (mQuitOnError) { 155 throw new RuntimeException(msg); 156 } else { 157 CLog.e(msg + ". Continuing anyway..."); 158 } 159 } 160 } 161 } finally { 162 // Clean up temporary export files. 163 for (File f : mExportFiles) { 164 FileUtil.deleteFile(f); 165 } 166 } 167 } 168 169 /** 170 * Calls the preload tool to pull and scan heap profiles and to generate and export the list of 171 * loaded Java classes. 172 * 173 * @return {@link File} containing the loaded Java classes 174 */ scanAndExportClasses()175 private File scanAndExportClasses() { 176 File temp = null; 177 try { 178 temp = FileUtil.createTempFile("scanned", ".txt"); 179 } catch (IOException e) { 180 CLog.e("Failed while creating temp file."); 181 CLog.e(e); 182 return null; 183 } 184 // Construct the command 185 String exportCmd = String.format(EXPORT_CMD, temp.getAbsolutePath()); 186 String actionCmd = String.format("%s %s", SCAN_ALL_CMD, exportCmd); 187 String[] fullCmd = constructPreloadCommand(actionCmd); 188 CommandResult result = RunUtil.getDefault().runTimedCmd(SCAN_TIMEOUT_MS, fullCmd); 189 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 190 return temp; 191 } else { 192 // Clean up the temp file 193 FileUtil.deleteFile(temp); 194 // Log and return the failure 195 CLog.e("Error scanning: %s", result.getStderr()); 196 return null; 197 } 198 } 199 200 /** 201 * Calls the preload tool to import the previously exported files and to generate the list of 202 * preloaded classes based on the threshold input. 203 * 204 * @return {@link File} containing the generated list of preloaded classes 205 */ writePreloadedClasses(int threshold)206 private File writePreloadedClasses(int threshold) { 207 File temp = null; 208 try { 209 temp = FileUtil.createTempFile("preloaded-classes", ".txt"); 210 } catch (IOException e) { 211 CLog.e("Failed while creating temp file."); 212 CLog.e(e); 213 return null; 214 } 215 // Construct the command 216 String actionCmd = ""; 217 for (File f : mExportFiles) { 218 String importCmd = String.format(IMPORT_CMD, f.getAbsolutePath()); 219 actionCmd += importCmd + " "; 220 } 221 actionCmd += String.format(COMPUTE_CMD, threshold, temp.getAbsolutePath()); 222 String[] fullCmd = constructPreloadCommand(actionCmd); 223 CommandResult result = RunUtil.getDefault().runTimedCmd(COMPUTE_TIMEOUT_MS, fullCmd); 224 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 225 return temp; 226 } else { 227 // Clean up the temp file 228 FileUtil.deleteFile(temp); 229 // Log and return the failure 230 CLog.e("Error computing classes: %s", result.getStderr()); 231 return null; 232 } 233 } 234 constructPreloadCommand(String command)235 private String[] constructPreloadCommand(String command) { 236 return String.format(TOOL_CMD, mPreloadToolJarPath, getDevice().getSerialNumber(), command) 237 .split(" "); 238 } 239 240 @Override setDevice(ITestDevice device)241 public void setDevice(ITestDevice device) { 242 mDevice = device; 243 } 244 245 @Override getDevice()246 public ITestDevice getDevice() { 247 return mDevice; 248 } 249 250 @Override setBuild(IBuildInfo buildInfo)251 public void setBuild(IBuildInfo buildInfo) { 252 mBuildInfo = buildInfo; 253 } 254 } 255