• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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