OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2016 the V8 project authors. All rights reserved. | 2 # Copyright 2016 the V8 project authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Script for merging sancov files in parallel. | 6 """Script for merging sancov files in parallel. |
7 | 7 |
8 The sancov files are expected | 8 When merging test runner output, the sancov files are expected |
9 to be located in one directory with the file-name pattern: | 9 to be located in one directory with the file-name pattern: |
10 <executable name>.test.<id>.sancov | 10 <executable name>.test.<id>.sancov |
11 | 11 |
12 For each executable, this script writes a new file: | 12 For each executable, this script writes a new file: |
13 <executable name>.result.sancov | 13 <executable name>.result.sancov |
14 | 14 |
15 When --swarming-output-dir is specified, this script will merge the result | |
16 files found there into the coverage folder. | |
17 | |
15 The sancov tool is expected to be in the llvm compiler-rt third-party | 18 The sancov tool is expected to be in the llvm compiler-rt third-party |
16 directory. It's not checked out by default and must be added as a custom deps: | 19 directory. It's not checked out by default and must be added as a custom deps: |
17 'v8/third_party/llvm/projects/compiler-rt': | 20 'v8/third_party/llvm/projects/compiler-rt': |
18 'https://chromium.googlesource.com/external/llvm.org/compiler-rt.git' | 21 'https://chromium.googlesource.com/external/llvm.org/compiler-rt.git' |
19 """ | 22 """ |
20 | 23 |
21 import argparse | 24 import argparse |
22 import logging | 25 import logging |
23 import math | 26 import math |
24 import os | 27 import os |
(...skipping 15 matching lines...) Expand all Loading... | |
40 BASE_DIR, 'third_party', 'llvm', 'projects', 'compiler-rt', | 43 BASE_DIR, 'third_party', 'llvm', 'projects', 'compiler-rt', |
41 'lib', 'sanitizer_common', 'scripts', 'sancov.py') | 44 'lib', 'sanitizer_common', 'scripts', 'sancov.py') |
42 | 45 |
43 # Number of cpus. | 46 # Number of cpus. |
44 CPUS = cpu_count() | 47 CPUS = cpu_count() |
45 | 48 |
46 # Regexp to find sancov file as output by the v8 test runner. Also grabs the | 49 # Regexp to find sancov file as output by the v8 test runner. Also grabs the |
47 # executable name in group 1. | 50 # executable name in group 1. |
48 SANCOV_FILE_RE = re.compile(r'^(.*)\.test\.\d+\.sancov$') | 51 SANCOV_FILE_RE = re.compile(r'^(.*)\.test\.\d+\.sancov$') |
49 | 52 |
53 # Regexp to find sancov result files as returned from swarming. | |
54 SANCOV_RESULTS_FILE_RE = re.compile(r'^.*\.result\.sancov$') | |
55 | |
50 | 56 |
51 def merge(args): | 57 def merge(args): |
52 """Merge several sancov files into one. | 58 """Merge several sancov files into one. |
53 | 59 |
54 Called trough multiprocessing pool. The args are expected to unpack to: | 60 Called trough multiprocessing pool. The args are expected to unpack to: |
55 keep: Option if source and intermediate sancov files should be kept. | 61 keep: Option if source and intermediate sancov files should be kept. |
56 coverage_dir: Folder where to find the sancov files. | 62 coverage_dir: Folder where to find the sancov files. |
57 executable: Name of the executable whose sancov files should be merged. | 63 executable: Name of the executable whose sancov files should be merged. |
58 index: A number to be put into the intermediate result file name. | 64 index: A number to be put into the intermediate result file name. |
59 If None, this is a final result. | 65 If None, this is a final result. |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
103 buckets = [files[i:i+n] for i in xrange(0, len(files), n)] | 109 buckets = [files[i:i+n] for i in xrange(0, len(files), n)] |
104 | 110 |
105 # Inputs for multiprocessing. List of tuples containing: | 111 # Inputs for multiprocessing. List of tuples containing: |
106 # Keep-files option, base path, executable name, index of bucket, | 112 # Keep-files option, base path, executable name, index of bucket, |
107 # list of files. | 113 # list of files. |
108 inputs.extend([(keep, coverage_dir, executable, i, b) | 114 inputs.extend([(keep, coverage_dir, executable, i, b) |
109 for i, b in enumerate(buckets)]) | 115 for i, b in enumerate(buckets)]) |
110 return inputs | 116 return inputs |
111 | 117 |
112 | 118 |
113 def merge_parallel(inputs): | 119 def merge_parallel(inputs, merge_fun=merge): |
Michael Hablich
2016/03/10 14:39:08
nit: I would simply call it merge_function ... I w
Michael Achenbach
2016/03/10 14:41:45
Laziness. fun is commonly used as abbreviation for
| |
114 """Process several merge jobs in parallel.""" | 120 """Process several merge jobs in parallel.""" |
115 pool = Pool(CPUS) | 121 pool = Pool(CPUS) |
116 try: | 122 try: |
117 return pool.map(merge, inputs) | 123 return pool.map(merge_fun, inputs) |
118 finally: | 124 finally: |
119 pool.close() | 125 pool.close() |
120 | 126 |
121 | 127 |
122 def main(): | 128 def merge_test_runner_output(options): |
123 parser = argparse.ArgumentParser() | |
124 parser.add_argument('--coverage-dir', required=True, | |
125 help='Path to the sancov output files.') | |
126 parser.add_argument('--keep', default=False, action='store_true', | |
127 help='Keep sancov output files after merging.') | |
128 options = parser.parse_args() | |
129 | |
130 # Check if folder with coverage output exists. | |
131 assert (os.path.exists(options.coverage_dir) and | |
132 os.path.isdir(options.coverage_dir)) | |
133 | |
134 # Map executable names to their respective sancov files. | 129 # Map executable names to their respective sancov files. |
135 file_map = {} | 130 file_map = {} |
136 for f in os.listdir(options.coverage_dir): | 131 for f in os.listdir(options.coverage_dir): |
137 match = SANCOV_FILE_RE.match(f) | 132 match = SANCOV_FILE_RE.match(f) |
138 if match: | 133 if match: |
139 file_map.setdefault(match.group(1), []).append(f) | 134 file_map.setdefault(match.group(1), []).append(f) |
140 | 135 |
141 inputs = generate_inputs( | 136 inputs = generate_inputs( |
142 options.keep, options.coverage_dir, file_map, CPUS) | 137 options.keep, options.coverage_dir, file_map, CPUS) |
143 | 138 |
144 logging.info('Executing %d merge jobs in parallel for %d executables.' % | 139 logging.info('Executing %d merge jobs in parallel for %d executables.' % |
145 (len(inputs), len(file_map))) | 140 (len(inputs), len(file_map))) |
146 | 141 |
147 results = merge_parallel(inputs) | 142 results = merge_parallel(inputs) |
148 | 143 |
149 # Map executable names to intermediate bucket result files. | 144 # Map executable names to intermediate bucket result files. |
150 file_map = {} | 145 file_map = {} |
151 for executable, f in results: | 146 for executable, f in results: |
152 file_map.setdefault(executable, []).append(f) | 147 file_map.setdefault(executable, []).append(f) |
153 | 148 |
154 # Merge the bucket results for each executable. | 149 # Merge the bucket results for each executable. |
155 # The final result has index None, so no index will appear in the | 150 # The final result has index None, so no index will appear in the |
156 # file name. | 151 # file name. |
157 inputs = [(options.keep, options.coverage_dir, executable, None, files) | 152 inputs = [(options.keep, options.coverage_dir, executable, None, files) |
158 for executable, files in file_map.iteritems()] | 153 for executable, files in file_map.iteritems()] |
159 | 154 |
160 logging.info('Merging %d intermediate results.' % len(inputs)) | 155 logging.info('Merging %d intermediate results.' % len(inputs)) |
161 | 156 |
162 merge_parallel(inputs) | 157 merge_parallel(inputs) |
158 | |
159 | |
160 def merge_two(args): | |
161 """Merge two sancov files. | |
162 | |
163 Called trough multiprocessing pool. The args are expected to unpack to: | |
164 swarming_output_dir: Folder where to find the new file. | |
165 coverage_dir: Folder where to find the existing file. | |
166 f: File name of the file to be merged. | |
167 """ | |
168 swarming_output_dir, coverage_dir, f = args | |
169 input_file = os.path.join(swarming_output_dir, f) | |
170 output_file = os.path.join(coverage_dir, f) | |
171 process = subprocess.Popen( | |
172 [SANCOV_TOOL, 'merge', input_file, output_file], | |
173 stdout=subprocess.PIPE, | |
174 stderr=subprocess.PIPE, | |
175 ) | |
176 output, _ = process.communicate() | |
177 assert process.returncode == 0 | |
178 with open(output_file, "wb") as f: | |
179 f.write(output) | |
180 | |
181 | |
182 def merge_swarming_output(options): | |
183 # Iterate sancov files from swarming. | |
184 files = [] | |
185 for f in os.listdir(options.swarming_output_dir): | |
186 match = SANCOV_RESULTS_FILE_RE.match(f) | |
187 if match: | |
188 if os.path.exists(os.path.join(options.coverage_dir, f)): | |
189 # If the same file already exists, we'll merge the data. | |
190 files.append(f) | |
191 else: | |
192 # No file yet? Just move it. | |
193 os.rename(os.path.join(options.swarming_output_dir, f), | |
194 os.path.join(options.coverage_dir, f)) | |
195 | |
196 inputs = [(options.swarming_output_dir, options.coverage_dir, f) | |
197 for f in files] | |
198 | |
199 logging.info('Executing %d merge jobs in parallel.' % len(inputs)) | |
tandrii(chromium)
2016/03/10 15:40:06
nit: s/%/,
because logging does formatting for you
Michael Achenbach
2016/03/10 16:12:33
Ah right, didn't pay attention. Will clean up all
| |
200 merge_parallel(inputs, merge_two) | |
201 | |
202 | |
203 def main(): | |
204 parser = argparse.ArgumentParser() | |
205 parser.add_argument('--coverage-dir', required=True, | |
206 help='Path to the sancov output files.') | |
207 parser.add_argument('--keep', default=False, action='store_true', | |
208 help='Keep sancov output files after merging.') | |
209 parser.add_argument('--swarming-output-dir', | |
210 help='Folder containing a results shard from swarming.') | |
211 options = parser.parse_args() | |
212 | |
213 # Check if folder with coverage output exists. | |
214 assert (os.path.exists(options.coverage_dir) and | |
215 os.path.isdir(options.coverage_dir)) | |
216 | |
217 if options.swarming_output_dir: | |
218 # Check if folder with swarming output exists. | |
219 assert (os.path.exists(options.swarming_output_dir) and | |
220 os.path.isdir(options.swarming_output_dir)) | |
221 merge_swarming_output(options) | |
222 else: | |
223 merge_test_runner_output(options) | |
224 | |
163 return 0 | 225 return 0 |
164 | 226 |
165 | 227 |
166 if __name__ == '__main__': | 228 if __name__ == '__main__': |
167 sys.exit(main()) | 229 sys.exit(main()) |
OLD | NEW |