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 |