Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 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 | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Script for merging sancov files in parallel. | |
| 7 | |
| 8 The sancov files are expected | |
| 9 to be located in one directory with the file-name pattern: | |
| 10 <executable name>.test.<id>.sancov | |
| 11 | |
| 12 For each executable, this script writes a new file: | |
| 13 <executable name>.result.sancov | |
| 14 | |
| 15 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: | |
| 17 'v8/third_party/llvm/projects/compiler-rt': | |
| 18 'https://chromium.googlesource.com/external/llvm.org/compiler-rt.git' | |
| 19 """ | |
| 20 | |
| 21 import argparse | |
| 22 import math | |
| 23 import os | |
| 24 import re | |
| 25 import subprocess | |
| 26 import sys | |
| 27 | |
| 28 from multiprocessing import Pool, cpu_count | |
| 29 | |
| 30 | |
| 31 # V8 checkout directory. | |
| 32 BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname( | |
| 33 os.path.abspath(__file__)))) | |
| 34 | |
| 35 # The sancov tool location. | |
| 36 SANCOV_TOOL = os.path.join( | |
| 37 BASE_DIR, 'third_party', 'llvm', 'projects', 'compiler-rt', | |
| 38 'lib', 'sanitizer_common', 'scripts', 'sancov.py') | |
| 39 | |
| 40 # Number of cpus. | |
| 41 CPUS = cpu_count() | |
| 42 | |
| 43 # Regexp to find sancov file as output by the v8 test runner. Also grabs the | |
| 44 # executable name in group 1. | |
| 45 SANCOV_FILE_RE = re.compile(r'^(.*)\.test\.\d+\.sancov$') | |
| 46 | |
| 47 | |
| 48 def merge(args): | |
| 49 """Merge several sancov files into one. | |
| 50 | |
| 51 Called trough multiprocessing pool. The args are expected to unpack to: | |
| 52 keep: Option if source and intermediate sancov files should be kept. | |
| 53 coverage_dir: Folder where to find the sancov files. | |
| 54 executable: Name of the executable whose sancov files should be merged. | |
| 55 index: A number to be put into the intermediate result file name. | |
| 56 If None, this is a final result. | |
| 57 bucket: The list of sancov files to be merged. | |
| 58 Returns: A tuple with the executable name and the result file name. | |
| 59 """ | |
| 60 keep, coverage_dir, executable, index, bucket = args | |
| 61 process = subprocess.Popen( | |
| 62 [SANCOV_TOOL, 'merge'] + bucket, | |
| 63 stdout=subprocess.PIPE, | |
| 64 stderr=subprocess.PIPE, | |
| 65 cwd=coverage_dir, | |
| 66 ) | |
| 67 output, _ = process.communicate() | |
|
kjellander_chromium
2016/03/08 04:48:33
what about process.returncode?
Michael Achenbach
2016/03/08 10:05:46
Good point! Added asserts to not let a bad return
| |
| 68 if index is not None: | |
| 69 # This is an intermediate result, add the bucket index to the file name. | |
| 70 result_file_name = '%s.result.%d.sancov' % (executable, index) | |
| 71 else: | |
| 72 # This is the final result without bucket index. | |
| 73 result_file_name = '%s.result.sancov' % executable | |
| 74 with open(os.path.join(coverage_dir, result_file_name), "wb") as f: | |
| 75 f.write(output) | |
| 76 if not keep: | |
| 77 for f in bucket: | |
| 78 os.remove(os.path.join(coverage_dir, f)) | |
| 79 return executable, result_file_name | |
| 80 | |
| 81 | |
| 82 def generate_inputs(keep, coverage_dir, file_map, cpus): | |
| 83 """Generate inputs for multiprocessed merging. | |
| 84 | |
| 85 Splits the sancov files into several buckets, so that each bucket can be | |
| 86 merged in a separate process. We have only few executables in total with | |
| 87 mostly lots of associated files. In the general case, with many executables | |
| 88 we might need to avoid splitting buckets of executables with few files. | |
| 89 | |
| 90 Returns: List of args as expected by merge above. | |
| 91 """ | |
| 92 inputs = [] | |
| 93 for executable, files in file_map.iteritems(): | |
| 94 # What's the bucket size for distributing files for merging? E.g. with | |
| 95 # 2 cpus and 9 files we want bucket size 5. | |
| 96 n = max(2, int(math.ceil(len(files) / float(cpus)))) | |
| 97 | |
| 98 # Chop files into buckets. | |
| 99 buckets = [files[i:i+n] for i in xrange(0, len(files), n)] | |
| 100 | |
| 101 # Inputs for multiprocessing. List of tuples containing: | |
| 102 # Keep-files option, base path, executable name, index of bucket, | |
| 103 # list of files. | |
| 104 inputs.extend([(keep, coverage_dir, executable, i, b) | |
| 105 for i, b in enumerate(buckets)]) | |
| 106 return inputs | |
| 107 | |
| 108 | |
| 109 def merge_parallel(inputs): | |
| 110 """Process several merge jobs in parallel.""" | |
| 111 pool = Pool(CPUS) | |
| 112 try: | |
| 113 return pool.map(merge, inputs) | |
| 114 finally: | |
| 115 pool.close() | |
| 116 | |
| 117 | |
| 118 def main(): | |
| 119 parser = argparse.ArgumentParser() | |
| 120 parser.add_argument('--coverage-dir', required=True, | |
| 121 help='Path to the sancov output files.') | |
| 122 parser.add_argument('--keep', default=False, action='store_true', | |
| 123 help='Keep sancov output files after merging.') | |
| 124 options = parser.parse_args() | |
| 125 | |
| 126 # Check if folder with coverage output exists. | |
| 127 assert (os.path.exists(options.coverage_dir) and | |
| 128 os.path.isdir(options.coverage_dir)) | |
| 129 | |
| 130 # Map executable names to their respective sancov files. | |
| 131 file_map = {} | |
| 132 for f in os.listdir(options.coverage_dir): | |
| 133 match = SANCOV_FILE_RE.match(f) | |
| 134 if match: | |
| 135 file_map.setdefault(match.group(1), []).append(f) | |
| 136 | |
| 137 inputs = generate_inputs( | |
| 138 options.keep, options.coverage_dir, file_map, CPUS) | |
| 139 | |
| 140 results = merge_parallel(inputs) | |
| 141 | |
| 142 # Map executable names to intermediate bucket result files. | |
| 143 file_map = {} | |
| 144 for executable, f in results: | |
| 145 file_map.setdefault(executable, []).append(f) | |
| 146 | |
| 147 # Merge the bucket results for each executable. | |
| 148 # The final result has index None, so no index will appear in the | |
| 149 # file name. | |
| 150 inputs = [(options.keep, options.coverage_dir, executable, None, files) | |
| 151 for executable, files in file_map.iteritems()] | |
| 152 merge_parallel(inputs) | |
| 153 return 0 | |
| 154 | |
| 155 | |
| 156 if __name__ == '__main__': | |
| 157 sys.exit(main()) | |
| OLD | NEW |