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 |