1 /* 2 * Copyright (C) 2020 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.device.metric; 18 19 import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.CLANG; 20 21 import static com.google.common.base.Verify.verifyNotNull; 22 23 import com.android.tradefed.build.IBuildInfo; 24 import com.android.tradefed.config.IConfiguration; 25 import com.android.tradefed.config.IConfigurationReceiver; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.ITestDevice; 28 import com.android.tradefed.invoker.IInvocationContext; 29 import com.android.tradefed.log.LogUtil.CLog; 30 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 31 import com.android.tradefed.result.FileInputStreamSource; 32 import com.android.tradefed.result.ITestInvocationListener; 33 import com.android.tradefed.result.LogDataType; 34 import com.android.tradefed.testtype.coverage.CoverageOptions; 35 import com.android.tradefed.util.AdbRootElevator; 36 import com.android.tradefed.util.ClangProfileIndexer; 37 import com.android.tradefed.util.FileUtil; 38 import com.android.tradefed.util.IRunUtil; 39 import com.android.tradefed.util.NativeCodeCoverageFlusher; 40 import com.android.tradefed.util.RunUtil; 41 import com.android.tradefed.util.TarUtil; 42 import com.android.tradefed.util.ZipUtil; 43 44 import com.google.common.annotations.VisibleForTesting; 45 import com.google.common.base.Strings; 46 47 import java.io.BufferedOutputStream; 48 import java.io.File; 49 import java.io.FileOutputStream; 50 import java.io.IOException; 51 import java.io.OutputStream; 52 import java.util.HashMap; 53 import java.util.HashSet; 54 import java.util.Map; 55 import java.util.Set; 56 import java.util.concurrent.TimeUnit; 57 58 /** 59 * A {@link com.android.tradefed.device.metric.BaseDeviceMetricCollector} that will pull Clang 60 * coverage measurements off of the device and log them as test artifacts. 61 */ 62 public final class ClangCodeCoverageCollector extends BaseDeviceMetricCollector 63 implements IConfigurationReceiver { 64 // Finds .profraw files and compresses those files only. Stores the full 65 // path of the file on the device. 66 private static final String ZIP_CLANG_FILES_COMMAND_FORMAT = 67 "find %s -name '*.profraw' | tar -czf - -T - 2>/dev/null"; 68 69 // Deletes .profraw files in the directory. 70 private static final String DELETE_COVERAGE_FILES_COMMAND_FORMAT = 71 "find %s -name '*.profraw' -delete"; 72 73 private IConfiguration mConfiguration; 74 private IRunUtil mRunUtil = RunUtil.getDefault(); 75 // Timeout for pulling coverage measurements from the device, in milliseconds. 76 private long mTimeoutMilli = 20 * 60 * 1000; 77 private File mLlvmProfileTool; 78 79 private NativeCodeCoverageFlusher mFlusher; 80 81 @Override extraInit(IInvocationContext context, ITestInvocationListener listener)82 public void extraInit(IInvocationContext context, ITestInvocationListener listener) 83 throws DeviceNotAvailableException { 84 super.extraInit(context, listener); 85 setDisableReceiver(false); 86 87 verifyNotNull(mConfiguration); 88 setCoverageOptions(mConfiguration.getCoverageOptions()); 89 90 if (isClangCoverageEnabled() 91 && mConfiguration.getCoverageOptions().shouldResetCoverageBeforeTest()) { 92 for (ITestDevice device : getRealDevices()) { 93 // Clear coverage measurements on the device. 94 try (AdbRootElevator adbRoot = new AdbRootElevator(device)) { 95 getCoverageFlusher(device).deleteCoverageMeasurements(); 96 } 97 } 98 } 99 } 100 101 @Override setConfiguration(IConfiguration configuration)102 public void setConfiguration(IConfiguration configuration) { 103 mConfiguration = configuration; 104 } 105 106 @Override rebootEnded(ITestDevice device)107 public void rebootEnded(ITestDevice device) throws DeviceNotAvailableException { 108 if (isClangCoverageEnabled() 109 && mConfiguration.getCoverageOptions().shouldResetCoverageBeforeTest()) { 110 getCoverageFlusher(device).deleteCoverageMeasurements(); 111 } 112 } 113 114 @VisibleForTesting setRunUtil(IRunUtil runUtil)115 public void setRunUtil(IRunUtil runUtil) { 116 mRunUtil = runUtil; 117 if (mFlusher != null) { 118 mFlusher.setRunUtil(runUtil); 119 } 120 } 121 122 @Override onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)123 public void onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) 124 throws DeviceNotAvailableException { 125 if (!isClangCoverageEnabled()) { 126 return; 127 } 128 129 for (ITestDevice device : getRealDevices()) { 130 try (AdbRootElevator adbRoot = new AdbRootElevator(device)) { 131 if (mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) { 132 getCoverageFlusher(device).forceCoverageFlush(); 133 } 134 logCoverageMeasurement(device, generateMeasurementFileName()); 135 } catch (IOException e) { 136 throw new RuntimeException(e); 137 } 138 } 139 } 140 141 /** Generate the .profdata file prefix in format "$moduleName_MODULE_$runName". */ generateMeasurementFileName()142 private String generateMeasurementFileName() { 143 String moduleName = Strings.nullToEmpty(getModuleName()); 144 if (moduleName.length() > 0) { 145 moduleName += "_MODULE_"; 146 } 147 return moduleName + getRunName().replace(' ', '_'); 148 } 149 150 /** 151 * Logs Clang coverage measurements from the device. 152 * 153 * @param runName name used in the log file 154 * @throws DeviceNotAvailableException 155 * @throws IOException 156 */ logCoverageMeasurement(ITestDevice device, String runName)157 private void logCoverageMeasurement(ITestDevice device, String runName) 158 throws DeviceNotAvailableException, IOException { 159 Map<String, File> untarDirs = new HashMap<>(); 160 File profileTool = null; 161 File indexedProfileFile = null; 162 try { 163 Set<String> rawProfileFiles = new HashSet<>(); 164 for (String devicePath : mConfiguration.getCoverageOptions().getDeviceCoveragePaths()) { 165 File coverageTarGz = FileUtil.createTempFile("clang_coverage", ".tar.gz"); 166 167 try { 168 // Compress coverage measurements on the device before streaming to the host. 169 try (OutputStream out = 170 new BufferedOutputStream(new FileOutputStream(coverageTarGz))) { 171 device.executeShellV2Command( 172 String.format( 173 ZIP_CLANG_FILES_COMMAND_FORMAT, devicePath), // Command 174 null, // File pipe as input 175 out, // OutputStream to write to 176 mTimeoutMilli, // Timeout in milliseconds 177 TimeUnit.MILLISECONDS, // Timeout units 178 1); // Retry count 179 } 180 181 File untarDir = TarUtil.extractTarGzipToTemp(coverageTarGz, "clang_coverage"); 182 untarDirs.put(devicePath, untarDir); 183 rawProfileFiles.addAll( 184 FileUtil.findFiles( 185 untarDir, 186 mConfiguration.getCoverageOptions().getProfrawFilter())); 187 } catch (IOException e) { 188 CLog.e("Failed to pull Clang coverage data from %s", devicePath); 189 CLog.e(e); 190 } finally { 191 FileUtil.deleteFile(coverageTarGz); 192 } 193 } 194 195 if (rawProfileFiles.isEmpty()) { 196 CLog.i("No Clang code coverage measurements found."); 197 return; 198 } 199 200 CLog.i("Received %d Clang code coverage measurements.", rawProfileFiles.size()); 201 202 ClangProfileIndexer indexer = new ClangProfileIndexer(getProfileTool(), mRunUtil); 203 204 // Create the output file. 205 indexedProfileFile = 206 FileUtil.createTempFile(runName + "_clang_runtime_coverage", ".profdata"); 207 indexer.index(rawProfileFiles, indexedProfileFile); 208 209 try (FileInputStreamSource source = 210 new FileInputStreamSource(indexedProfileFile, true)) { 211 testLog(runName + "_clang_runtime_coverage", LogDataType.CLANG_COVERAGE, source); 212 } 213 } finally { 214 // Delete coverage files on the device. 215 for (String devicePath : mConfiguration.getCoverageOptions().getDeviceCoveragePaths()) { 216 device.executeShellCommand( 217 String.format(DELETE_COVERAGE_FILES_COMMAND_FORMAT, devicePath)); 218 } 219 for (File untarDir : untarDirs.values()) { 220 FileUtil.recursiveDelete(untarDir); 221 } 222 FileUtil.recursiveDelete(mLlvmProfileTool); 223 FileUtil.deleteFile(indexedProfileFile); 224 } 225 } 226 227 /** 228 * Creates a {@link NativeCodeCoverageFlusher} if one does not already exist. 229 * 230 * @return a NativeCodeCoverageFlusher 231 */ getCoverageFlusher(ITestDevice device)232 private NativeCodeCoverageFlusher getCoverageFlusher(ITestDevice device) { 233 if (mFlusher == null) { 234 verifyNotNull(mConfiguration); 235 mFlusher = new NativeCodeCoverageFlusher(device, mConfiguration.getCoverageOptions()); 236 mFlusher.setRunUtil(mRunUtil); 237 } 238 return mFlusher; 239 } 240 isClangCoverageEnabled()241 private boolean isClangCoverageEnabled() { 242 return mConfiguration != null 243 && mConfiguration.getCoverageOptions().isCoverageEnabled() 244 && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(CLANG); 245 } 246 247 /** 248 * Retrieves the profile tool and dependencies from the build, and extracts them. 249 * 250 * @return the directory containing the profile tool and dependencies 251 */ getProfileTool()252 private File getProfileTool() throws IOException { 253 // If llvm-profdata-path was set in the Configuration, pass it through. Don't save the path 254 // locally since the parent process is responsible for cleaning it up. 255 File configurationTool = mConfiguration.getCoverageOptions().getLlvmProfdataPath(); 256 if (configurationTool != null) { 257 return configurationTool; 258 } 259 if (mLlvmProfileTool != null && mLlvmProfileTool.exists()) { 260 return mLlvmProfileTool; 261 } 262 263 // Otherwise, try to download llvm-profdata.zip from the build and cache it. 264 File profileToolZip = null; 265 for (IBuildInfo info : getBuildInfos()) { 266 if (info.getFile("llvm-profdata.zip") != null) { 267 profileToolZip = info.getFile("llvm-profdata.zip"); 268 mLlvmProfileTool = ZipUtil.extractZipToTemp(profileToolZip, "llvm-profdata"); 269 return mLlvmProfileTool; 270 } 271 } 272 return mLlvmProfileTool; 273 } 274 setCoverageOptions(CoverageOptions coverageOptions)275 private void setCoverageOptions(CoverageOptions coverageOptions) { 276 mTimeoutMilli = coverageOptions.getPullTimeout(); 277 } 278 } 279