11cb0ef41Sopenharmony_ci#!/usr/bin/env python 21cb0ef41Sopenharmony_ci# Copyright 2016 the V8 project authors. All rights reserved. 31cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be 41cb0ef41Sopenharmony_ci# found in the LICENSE file. 51cb0ef41Sopenharmony_ci 61cb0ef41Sopenharmony_ci"""Script for merging sancov files in parallel. 71cb0ef41Sopenharmony_ci 81cb0ef41Sopenharmony_ciWhen merging test runner output, the sancov files are expected 91cb0ef41Sopenharmony_cito be located in one directory with the file-name pattern: 101cb0ef41Sopenharmony_ci<executable name>.test.<id>.<attempt>.sancov 111cb0ef41Sopenharmony_ci 121cb0ef41Sopenharmony_ciFor each executable, this script writes a new file: 131cb0ef41Sopenharmony_ci<executable name>.result.sancov 141cb0ef41Sopenharmony_ci 151cb0ef41Sopenharmony_ciWhen --swarming-output-dir is specified, this script will merge the result 161cb0ef41Sopenharmony_cifiles found there into the coverage folder. 171cb0ef41Sopenharmony_ci 181cb0ef41Sopenharmony_ciThe sancov tool is expected to be in the llvm compiler-rt third-party 191cb0ef41Sopenharmony_cidirectory. It's not checked out by default and must be added as a custom deps: 201cb0ef41Sopenharmony_ci'v8/third_party/llvm/projects/compiler-rt': 211cb0ef41Sopenharmony_ci 'https://chromium.googlesource.com/external/llvm.org/compiler-rt.git' 221cb0ef41Sopenharmony_ci""" 231cb0ef41Sopenharmony_ci 241cb0ef41Sopenharmony_ciimport argparse 251cb0ef41Sopenharmony_ciimport logging 261cb0ef41Sopenharmony_ciimport math 271cb0ef41Sopenharmony_ciimport os 281cb0ef41Sopenharmony_ciimport re 291cb0ef41Sopenharmony_ciimport subprocess 301cb0ef41Sopenharmony_ciimport sys 311cb0ef41Sopenharmony_ci 321cb0ef41Sopenharmony_cifrom multiprocessing import Pool, cpu_count 331cb0ef41Sopenharmony_ci 341cb0ef41Sopenharmony_ci 351cb0ef41Sopenharmony_cilogging.basicConfig(level=logging.INFO) 361cb0ef41Sopenharmony_ci 371cb0ef41Sopenharmony_ci# V8 checkout directory. 381cb0ef41Sopenharmony_ciBASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname( 391cb0ef41Sopenharmony_ci os.path.abspath(__file__)))) 401cb0ef41Sopenharmony_ci 411cb0ef41Sopenharmony_ci# The sancov tool location. 421cb0ef41Sopenharmony_ciSANCOV_TOOL = os.path.join( 431cb0ef41Sopenharmony_ci BASE_DIR, 'third_party', 'llvm', 'projects', 'compiler-rt', 441cb0ef41Sopenharmony_ci 'lib', 'sanitizer_common', 'scripts', 'sancov.py') 451cb0ef41Sopenharmony_ci 461cb0ef41Sopenharmony_ci# Number of cpus. 471cb0ef41Sopenharmony_ciCPUS = cpu_count() 481cb0ef41Sopenharmony_ci 491cb0ef41Sopenharmony_ci# Regexp to find sancov file as output by the v8 test runner. Also grabs the 501cb0ef41Sopenharmony_ci# executable name in group 1. 511cb0ef41Sopenharmony_ciSANCOV_FILE_RE = re.compile(r'^(.*)\.test\.\d+\.\d+\.sancov$') 521cb0ef41Sopenharmony_ci 531cb0ef41Sopenharmony_ci# Regexp to find sancov result files as returned from swarming. 541cb0ef41Sopenharmony_ciSANCOV_RESULTS_FILE_RE = re.compile(r'^.*\.result\.sancov$') 551cb0ef41Sopenharmony_ci 561cb0ef41Sopenharmony_ci 571cb0ef41Sopenharmony_cidef merge(args): 581cb0ef41Sopenharmony_ci """Merge several sancov files into one. 591cb0ef41Sopenharmony_ci 601cb0ef41Sopenharmony_ci Called trough multiprocessing pool. The args are expected to unpack to: 611cb0ef41Sopenharmony_ci keep: Option if source and intermediate sancov files should be kept. 621cb0ef41Sopenharmony_ci coverage_dir: Folder where to find the sancov files. 631cb0ef41Sopenharmony_ci executable: Name of the executable whose sancov files should be merged. 641cb0ef41Sopenharmony_ci index: A number to be put into the intermediate result file name. 651cb0ef41Sopenharmony_ci If None, this is a final result. 661cb0ef41Sopenharmony_ci bucket: The list of sancov files to be merged. 671cb0ef41Sopenharmony_ci Returns: A tuple with the executable name and the result file name. 681cb0ef41Sopenharmony_ci """ 691cb0ef41Sopenharmony_ci keep, coverage_dir, executable, index, bucket = args 701cb0ef41Sopenharmony_ci process = subprocess.Popen( 711cb0ef41Sopenharmony_ci [SANCOV_TOOL, 'merge'] + bucket, 721cb0ef41Sopenharmony_ci stdout=subprocess.PIPE, 731cb0ef41Sopenharmony_ci stderr=subprocess.PIPE, 741cb0ef41Sopenharmony_ci cwd=coverage_dir, 751cb0ef41Sopenharmony_ci ) 761cb0ef41Sopenharmony_ci output, _ = process.communicate() 771cb0ef41Sopenharmony_ci assert process.returncode == 0 781cb0ef41Sopenharmony_ci if index is not None: 791cb0ef41Sopenharmony_ci # This is an intermediate result, add the bucket index to the file name. 801cb0ef41Sopenharmony_ci result_file_name = '%s.result.%d.sancov' % (executable, index) 811cb0ef41Sopenharmony_ci else: 821cb0ef41Sopenharmony_ci # This is the final result without bucket index. 831cb0ef41Sopenharmony_ci result_file_name = '%s.result.sancov' % executable 841cb0ef41Sopenharmony_ci with open(os.path.join(coverage_dir, result_file_name), "wb") as f: 851cb0ef41Sopenharmony_ci f.write(output) 861cb0ef41Sopenharmony_ci if not keep: 871cb0ef41Sopenharmony_ci for f in bucket: 881cb0ef41Sopenharmony_ci os.remove(os.path.join(coverage_dir, f)) 891cb0ef41Sopenharmony_ci return executable, result_file_name 901cb0ef41Sopenharmony_ci 911cb0ef41Sopenharmony_ci 921cb0ef41Sopenharmony_cidef generate_inputs(keep, coverage_dir, file_map, cpus): 931cb0ef41Sopenharmony_ci """Generate inputs for multiprocessed merging. 941cb0ef41Sopenharmony_ci 951cb0ef41Sopenharmony_ci Splits the sancov files into several buckets, so that each bucket can be 961cb0ef41Sopenharmony_ci merged in a separate process. We have only few executables in total with 971cb0ef41Sopenharmony_ci mostly lots of associated files. In the general case, with many executables 981cb0ef41Sopenharmony_ci we might need to avoid splitting buckets of executables with few files. 991cb0ef41Sopenharmony_ci 1001cb0ef41Sopenharmony_ci Returns: List of args as expected by merge above. 1011cb0ef41Sopenharmony_ci """ 1021cb0ef41Sopenharmony_ci inputs = [] 1031cb0ef41Sopenharmony_ci for executable, files in file_map.iteritems(): 1041cb0ef41Sopenharmony_ci # What's the bucket size for distributing files for merging? E.g. with 1051cb0ef41Sopenharmony_ci # 2 cpus and 9 files we want bucket size 5. 1061cb0ef41Sopenharmony_ci n = max(2, int(math.ceil(len(files) / float(cpus)))) 1071cb0ef41Sopenharmony_ci 1081cb0ef41Sopenharmony_ci # Chop files into buckets. 1091cb0ef41Sopenharmony_ci buckets = [files[i:i+n] for i in range(0, len(files), n)] 1101cb0ef41Sopenharmony_ci 1111cb0ef41Sopenharmony_ci # Inputs for multiprocessing. List of tuples containing: 1121cb0ef41Sopenharmony_ci # Keep-files option, base path, executable name, index of bucket, 1131cb0ef41Sopenharmony_ci # list of files. 1141cb0ef41Sopenharmony_ci inputs.extend([(keep, coverage_dir, executable, i, b) 1151cb0ef41Sopenharmony_ci for i, b in enumerate(buckets)]) 1161cb0ef41Sopenharmony_ci return inputs 1171cb0ef41Sopenharmony_ci 1181cb0ef41Sopenharmony_ci 1191cb0ef41Sopenharmony_cidef merge_parallel(inputs, merge_fun=merge): 1201cb0ef41Sopenharmony_ci """Process several merge jobs in parallel.""" 1211cb0ef41Sopenharmony_ci pool = Pool(CPUS) 1221cb0ef41Sopenharmony_ci try: 1231cb0ef41Sopenharmony_ci return pool.map(merge_fun, inputs) 1241cb0ef41Sopenharmony_ci finally: 1251cb0ef41Sopenharmony_ci pool.close() 1261cb0ef41Sopenharmony_ci 1271cb0ef41Sopenharmony_ci 1281cb0ef41Sopenharmony_cidef merge_test_runner_output(options): 1291cb0ef41Sopenharmony_ci # Map executable names to their respective sancov files. 1301cb0ef41Sopenharmony_ci file_map = {} 1311cb0ef41Sopenharmony_ci for f in os.listdir(options.coverage_dir): 1321cb0ef41Sopenharmony_ci match = SANCOV_FILE_RE.match(f) 1331cb0ef41Sopenharmony_ci if match: 1341cb0ef41Sopenharmony_ci file_map.setdefault(match.group(1), []).append(f) 1351cb0ef41Sopenharmony_ci 1361cb0ef41Sopenharmony_ci inputs = generate_inputs( 1371cb0ef41Sopenharmony_ci options.keep, options.coverage_dir, file_map, CPUS) 1381cb0ef41Sopenharmony_ci 1391cb0ef41Sopenharmony_ci logging.info('Executing %d merge jobs in parallel for %d executables.' % 1401cb0ef41Sopenharmony_ci (len(inputs), len(file_map))) 1411cb0ef41Sopenharmony_ci 1421cb0ef41Sopenharmony_ci results = merge_parallel(inputs) 1431cb0ef41Sopenharmony_ci 1441cb0ef41Sopenharmony_ci # Map executable names to intermediate bucket result files. 1451cb0ef41Sopenharmony_ci file_map = {} 1461cb0ef41Sopenharmony_ci for executable, f in results: 1471cb0ef41Sopenharmony_ci file_map.setdefault(executable, []).append(f) 1481cb0ef41Sopenharmony_ci 1491cb0ef41Sopenharmony_ci # Merge the bucket results for each executable. 1501cb0ef41Sopenharmony_ci # The final result has index None, so no index will appear in the 1511cb0ef41Sopenharmony_ci # file name. 1521cb0ef41Sopenharmony_ci inputs = [(options.keep, options.coverage_dir, executable, None, files) 1531cb0ef41Sopenharmony_ci for executable, files in file_map.iteritems()] 1541cb0ef41Sopenharmony_ci 1551cb0ef41Sopenharmony_ci logging.info('Merging %d intermediate results.' % len(inputs)) 1561cb0ef41Sopenharmony_ci 1571cb0ef41Sopenharmony_ci merge_parallel(inputs) 1581cb0ef41Sopenharmony_ci 1591cb0ef41Sopenharmony_ci 1601cb0ef41Sopenharmony_cidef merge_two(args): 1611cb0ef41Sopenharmony_ci """Merge two sancov files. 1621cb0ef41Sopenharmony_ci 1631cb0ef41Sopenharmony_ci Called trough multiprocessing pool. The args are expected to unpack to: 1641cb0ef41Sopenharmony_ci swarming_output_dir: Folder where to find the new file. 1651cb0ef41Sopenharmony_ci coverage_dir: Folder where to find the existing file. 1661cb0ef41Sopenharmony_ci f: File name of the file to be merged. 1671cb0ef41Sopenharmony_ci """ 1681cb0ef41Sopenharmony_ci swarming_output_dir, coverage_dir, f = args 1691cb0ef41Sopenharmony_ci input_file = os.path.join(swarming_output_dir, f) 1701cb0ef41Sopenharmony_ci output_file = os.path.join(coverage_dir, f) 1711cb0ef41Sopenharmony_ci process = subprocess.Popen( 1721cb0ef41Sopenharmony_ci [SANCOV_TOOL, 'merge', input_file, output_file], 1731cb0ef41Sopenharmony_ci stdout=subprocess.PIPE, 1741cb0ef41Sopenharmony_ci stderr=subprocess.PIPE, 1751cb0ef41Sopenharmony_ci ) 1761cb0ef41Sopenharmony_ci output, _ = process.communicate() 1771cb0ef41Sopenharmony_ci assert process.returncode == 0 1781cb0ef41Sopenharmony_ci with open(output_file, "wb") as f: 1791cb0ef41Sopenharmony_ci f.write(output) 1801cb0ef41Sopenharmony_ci 1811cb0ef41Sopenharmony_ci 1821cb0ef41Sopenharmony_cidef merge_swarming_output(options): 1831cb0ef41Sopenharmony_ci # Iterate sancov files from swarming. 1841cb0ef41Sopenharmony_ci files = [] 1851cb0ef41Sopenharmony_ci for f in os.listdir(options.swarming_output_dir): 1861cb0ef41Sopenharmony_ci match = SANCOV_RESULTS_FILE_RE.match(f) 1871cb0ef41Sopenharmony_ci if match: 1881cb0ef41Sopenharmony_ci if os.path.exists(os.path.join(options.coverage_dir, f)): 1891cb0ef41Sopenharmony_ci # If the same file already exists, we'll merge the data. 1901cb0ef41Sopenharmony_ci files.append(f) 1911cb0ef41Sopenharmony_ci else: 1921cb0ef41Sopenharmony_ci # No file yet? Just move it. 1931cb0ef41Sopenharmony_ci os.rename(os.path.join(options.swarming_output_dir, f), 1941cb0ef41Sopenharmony_ci os.path.join(options.coverage_dir, f)) 1951cb0ef41Sopenharmony_ci 1961cb0ef41Sopenharmony_ci inputs = [(options.swarming_output_dir, options.coverage_dir, f) 1971cb0ef41Sopenharmony_ci for f in files] 1981cb0ef41Sopenharmony_ci 1991cb0ef41Sopenharmony_ci logging.info('Executing %d merge jobs in parallel.' % len(inputs)) 2001cb0ef41Sopenharmony_ci merge_parallel(inputs, merge_two) 2011cb0ef41Sopenharmony_ci 2021cb0ef41Sopenharmony_ci 2031cb0ef41Sopenharmony_cidef main(): 2041cb0ef41Sopenharmony_ci parser = argparse.ArgumentParser() 2051cb0ef41Sopenharmony_ci parser.add_argument('--coverage-dir', required=True, 2061cb0ef41Sopenharmony_ci help='Path to the sancov output files.') 2071cb0ef41Sopenharmony_ci parser.add_argument('--keep', default=False, action='store_true', 2081cb0ef41Sopenharmony_ci help='Keep sancov output files after merging.') 2091cb0ef41Sopenharmony_ci parser.add_argument('--swarming-output-dir', 2101cb0ef41Sopenharmony_ci help='Folder containing a results shard from swarming.') 2111cb0ef41Sopenharmony_ci options = parser.parse_args() 2121cb0ef41Sopenharmony_ci 2131cb0ef41Sopenharmony_ci # Check if folder with coverage output exists. 2141cb0ef41Sopenharmony_ci assert (os.path.exists(options.coverage_dir) and 2151cb0ef41Sopenharmony_ci os.path.isdir(options.coverage_dir)) 2161cb0ef41Sopenharmony_ci 2171cb0ef41Sopenharmony_ci if options.swarming_output_dir: 2181cb0ef41Sopenharmony_ci # Check if folder with swarming output exists. 2191cb0ef41Sopenharmony_ci assert (os.path.exists(options.swarming_output_dir) and 2201cb0ef41Sopenharmony_ci os.path.isdir(options.swarming_output_dir)) 2211cb0ef41Sopenharmony_ci merge_swarming_output(options) 2221cb0ef41Sopenharmony_ci else: 2231cb0ef41Sopenharmony_ci merge_test_runner_output(options) 2241cb0ef41Sopenharmony_ci 2251cb0ef41Sopenharmony_ci return 0 2261cb0ef41Sopenharmony_ci 2271cb0ef41Sopenharmony_ci 2281cb0ef41Sopenharmony_ciif __name__ == '__main__': 2291cb0ef41Sopenharmony_ci sys.exit(main()) 230