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