Chromium Code Reviews| 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 |