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