diff --git a/CMakeLists.txt b/CMakeLists.txt index dbd570b3edf52..f988c2ab7f632 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,11 @@ option(SWIFT_INCLUDE_DOCS "Create targets for building docs." TRUE) +set(SWIFT_ANALYZE_CODE_COVERAGE FALSE CACHE STRING + "Build Swift with code coverage instrumenting enabled [FALSE, NOT-MERGED, MERGED]") +set_property(CACHE SWIFT_ANALYZE_CODE_COVERAGE PROPERTY + STRINGS FALSE "NOT-MERGED" "MERGED") + set(SWIFT_VERSION "3.0" CACHE STRING "The user-visible version of the Swift compiler") set(SWIFT_VENDOR "" CACHE STRING diff --git a/cmake/modules/AddSwift.cmake b/cmake/modules/AddSwift.cmake index ba717f0cc0bdb..50b2d5c23228e 100644 --- a/cmake/modules/AddSwift.cmake +++ b/cmake/modules/AddSwift.cmake @@ -64,7 +64,7 @@ function(_add_variant_c_compile_link_flags "-m${SWIFT_SDK_${sdk}_VERSION_MIN_NAME}-version-min=${SWIFT_SDK_${sdk}_DEPLOYMENT_VERSION}") if(analyze_code_coverage) - list(APPEND result "-fprofile-instr-generate=swift-%p.profraw" + list(APPEND result "-fprofile-instr-generate" "-fcoverage-mapping") endif() endif() @@ -111,7 +111,7 @@ function(_add_variant_c_compile_flags endif() if(analyze_code_coverage) - list(APPEND result "-fprofile-instr-generate=swift-%p.profraw" + list(APPEND result "-fprofile-instr-generate" "-fcoverage-mapping") endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 019523540c9d0..bb03809d1dd06 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,6 +117,7 @@ if(PYTHONINTERP_FOUND) set(TEST_MODES optimize_none optimize optimize_unchecked) + foreach(SDK ${SWIFT_SDKS}) foreach(ARCH ${SWIFT_SDK_${SDK}_ARCHITECTURES}) foreach(TEST_MODE ${TEST_MODES}) @@ -196,11 +197,27 @@ if(PYTHONINTERP_FOUND) "${CMAKE_CURRENT_SOURCE_DIR}/../validation-test/lit.site.cfg.in" "${validation_test_bin_dir}/lit.site.cfg" "validation-test${VARIANT_SUFFIX}.lit.site.cfg") + set(profdata_merge_worker + "${CMAKE_CURRENT_SOURCE_DIR}/../utils/profdata_merge/main.py") + + if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") + set(command_profdata_merge_start + COMMAND "${PYTHON_EXECUTABLE}" "${profdata_merge_worker}" start + -o "${swift_test_results_dir}" + -l "${swift_test_results_dir}/profdata_merge.log") + set(command_profdata_merge_stop + COMMAND "${PYTHON_EXECUTABLE}" "${profdata_merge_worker}" stop) + else() + set(command_profdata_merge_start) + set(command_profdata_merge_stop) + endif() add_custom_target("check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} ${command_clean_test_results_dir} + ${command_profdata_merge_start} COMMAND ${lit_command} "${test_bin_dir}" + ${command_profdata_merge_stop} DEPENDS ${test_dependencies} COMMENT "Running Swift tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) @@ -208,7 +225,9 @@ if(PYTHONINTERP_FOUND) add_custom_target("check-swift-validation${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} ${command_clean_test_results_dir} + ${command_profdata_merge_start} COMMAND ${lit_command} "${validation_test_bin_dir}" + ${command_profdata_merge_stop} DEPENDS ${test_dependencies} ${validation_test_dependencies} COMMENT "Running Swift validation tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) @@ -216,10 +235,14 @@ if(PYTHONINTERP_FOUND) add_custom_target("check-swift-all${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} ${command_clean_test_results_dir} + ${command_profdata_merge_start} COMMAND ${lit_command} "${validation_test_bin_dir}" "${test_bin_dir}" + ${command_profdata_merge_stop} DEPENDS ${test_dependencies} ${validation_test_dependencies} COMMENT "Running all Swift tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) + + endforeach() endforeach() endforeach() @@ -245,6 +268,7 @@ if(PYTHONINTERP_FOUND) add_custom_target(check-swift-all${test_mode_target_suffix} DEPENDS "check-swift-all${test_mode_target_suffix}${SWIFT_PRIMARY_VARIANT_SUFFIX}") + endforeach() endif() diff --git a/test/Unit/lit.site.cfg.in b/test/Unit/lit.site.cfg.in index d174b190e6032..e1e0395602305 100644 --- a/test/Unit/lit.site.cfg.in +++ b/test/Unit/lit.site.cfg.in @@ -8,5 +8,7 @@ config.build_mode = lit_config.params.get('build_mode', "@LLVM_BUILD_MODE@") config.swift_obj_root = "@SWIFT_BINARY_DIR@" config.target_triple = "@TARGET_TRIPLE@" +config.coverage_mode = "@SWIFT_ANALYZE_CODE_COVERAGE@" + # Let the main config do the real work. lit_config.load_config(config, "@SWIFT_SOURCE_DIR@/test/Unit/lit.cfg") diff --git a/test/lit.cfg b/test/lit.cfg index b0b6a1a81b25c..7fc74ad30cdfa 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -24,7 +24,10 @@ import re import subprocess import sys import tempfile +import socket +import glob +import lit import lit.formats import lit.util @@ -129,6 +132,43 @@ if config.test_exec_root is None: ### +class SwiftTest(lit.formats.ShTest, object): + def __init__(self, coverage_mode=None, execute_external=True): + super(SwiftTest, self).__init__(execute_external=execute_external) + if coverage_mode == "FALSE": + self.coverage_mode = None + else: + self.coverage_mode = coverage_mode + + def profdir_for_test(self, test): + _, tmp_base = lit.TestRunner.getTempPaths(test) + return tmp_base + ".profdir" + + def before_test(self, test, litConfig): + if self.coverage_mode: + profdir = self.profdir_for_test(test) + if not os.path.exists(profdir): + os.makedirs(profdir) + + test.config.environment["LLVM_PROFILE_FILE"] = \ + os.path.join(profdir, "swift-%p.profraw") + + def after_test(self, test, litConfig, result): + if self.coverage_mode == "MERGED": + profdir = self.profdir_for_test(test) + files = glob.glob(os.path.join(profdir, "swift-*.profraw")) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', 12400)) + sock.send("\n".join(files)) + sock.close() + return result + + + def execute(self, test, litConfig): + self.before_test(test, litConfig) + result = super(SwiftTest, self).execute(test, litConfig) + return self.after_test(test, litConfig, result) + # name: The name of this test suite. config.name = 'Swift' @@ -138,7 +178,7 @@ if platform.system() == 'Darwin': config.environment['TOOLCHAINS'] = 'default' # testFormat: The test format to use to interpret tests. -config.test_format = lit.formats.ShTest(execute_external=True) +config.test_format = SwiftTest(coverage_mode=config.coverage_mode) # suffixes: A list of file extensions to treat as test files. config.suffixes = ['.swift', '.ll', '.sil', '.gyb', '.m'] diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in index cd569075595fe..a410eaf75e16b 100644 --- a/test/lit.site.cfg.in +++ b/test/lit.site.cfg.in @@ -18,6 +18,8 @@ config.variant_sdk = "@VARIANT_SDK@" config.swiftlib_dir = "@LIT_SWIFTLIB_DIR@" config.darwin_xcrun_toolchain = "@SWIFT_DARWIN_XCRUN_TOOLCHAIN@" +config.coverage_mode = "@SWIFT_ANALYZE_CODE_COVERAGE@" + if "@SWIFT_ASAN_BUILD@" == "TRUE": config.available_features.add("asan") else: @@ -42,11 +44,6 @@ if "@SWIFT_OPTIMIZED@" == "TRUE": if "@SWIFT_HAVE_WORKING_STD_REGEX@" == "FALSE": config.available_features.add('broken_std_regex') -if "@SWIFT_ANALYZE_CODE_COVERAGE@" == "TRUE": - lit_config.useValgrind = True - lit_config.valgrindArgs = [os.path.join(config.swift_src_root, - "utils/use_profdir.py")] - # Let the main config do the real work. if config.test_exec_root is None: config.test_exec_root = os.path.dirname(os.path.realpath(__file__)) diff --git a/tools/SourceKit/CMakeLists.txt b/tools/SourceKit/CMakeLists.txt index aa1de0f2ed8e4..bf127bc549794 100644 --- a/tools/SourceKit/CMakeLists.txt +++ b/tools/SourceKit/CMakeLists.txt @@ -241,7 +241,7 @@ macro(add_sourcekit_executable name) if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET "${name}" APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() endif() diff --git a/tools/SourceKit/tools/complete-test/CMakeLists.txt b/tools/SourceKit/tools/complete-test/CMakeLists.txt index 1ee0c2e695bec..2986840c0ed29 100644 --- a/tools/SourceKit/tools/complete-test/CMakeLists.txt +++ b/tools/SourceKit/tools/complete-test/CMakeLists.txt @@ -17,7 +17,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET complete-test APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt index 20542027dbc19..ab0969f21e1b6 100644 --- a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt @@ -11,7 +11,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-repl APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt index ba470ff72a917..ed6a88607434b 100644 --- a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt @@ -25,7 +25,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-test APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 073d7a6241362..d629f9ecbfb9a 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -29,7 +29,7 @@ function(add_swift_unittest test_dirname) if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET "${test_dirname}" APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() endfunction() diff --git a/utils/build-script b/utils/build-script index 08dfc13358593..115e687da9107 100755 --- a/utils/build-script +++ b/utils/build-script @@ -512,11 +512,11 @@ also build for Apple watchos, but disallow tests that require an watchOS device" action="store_true") parser.add_argument("--swift-analyze-code-coverage", - help="enable code coverage analysis in Swift", - action="store_const", - const=True, - dest="swift_analyze_code_coverage", - default=False) + help="""enable code coverage analysis in Swift (false, not-merged, + merged).""", + choices=["false", "not-merged", "merged"], + default="false", # so CMake can see the inert mode as a false value + dest="swift_analyze_code_coverage") parser.add_argument("--build-subdir", help=""" @@ -726,7 +726,7 @@ the number of parallel build jobs to use""", swift_build_dir_label = args.swift_build_variant if args.swift_assertions: swift_build_dir_label += "Assert" - if args.swift_analyze_code_coverage: + if args.swift_analyze_code_coverage != "false": swift_build_dir_label += "Coverage" swift_stdlib_build_dir_label = args.swift_stdlib_build_variant diff --git a/utils/build-script-impl b/utils/build-script-impl index e71c313b3046a..13e82cf51e2e2 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -71,7 +71,7 @@ KNOWN_SETTINGS=( llvm-enable-assertions "1" "enable assertions in LLVM and Clang" swift-build-type "Debug" "the CMake build variant for Swift" swift-enable-assertions "1" "enable assertions in Swift" - swift-analyze-code-coverage "0" "enable code coverage analysis in Swift" + swift-analyze-code-coverage "not-merged" "Code coverage analysis mode for Swift (false, not-merged, merged). Defaults to false if the argument is not present, and not-merged if the argument is present without a modifier." swift-stdlib-build-type "Debug" "the CMake build variant for Swift" swift-stdlib-enable-assertions "1" "enable assertions in Swift" lldb-build-type "Debug" "the CMake build variant for LLDB" @@ -1600,7 +1600,7 @@ for deployment_target in "${HOST_TARGET}" "${CROSS_TOOLS_DEPLOYMENT_TARGETS[@]}" -DCMAKE_CXX_FLAGS="$(swift_c_flags ${deployment_target})" -DCMAKE_BUILD_TYPE:STRING="${SWIFT_BUILD_TYPE}" -DLLVM_ENABLE_ASSERTIONS:BOOL=$(true_false "${SWIFT_ENABLE_ASSERTIONS}") - -DSWIFT_ANALYZE_CODE_COVERAGE:BOOL=$(true_false "${SWIFT_ANALYZE_CODE_COVERAGE}") + -DSWIFT_ANALYZE_CODE_COVERAGE:STRING=$(toupper "${SWIFT_ANALYZE_CODE_COVERAGE}") -DSWIFT_STDLIB_BUILD_TYPE:STRING="${SWIFT_STDLIB_BUILD_TYPE}" -DSWIFT_STDLIB_ASSERTIONS:BOOL=$(true_false "${SWIFT_STDLIB_ENABLE_ASSERTIONS}") -DSWIFT_NATIVE_LLVM_TOOLS_PATH:STRING="${native_llvm_tools_path}" diff --git a/utils/profdata_merge/README.md b/utils/profdata_merge/README.md new file mode 100644 index 0000000000000..2c5a3fcff2b56 --- /dev/null +++ b/utils/profdata_merge/README.md @@ -0,0 +1,17 @@ +# Profdata Merge + +Because LLVM's instrumented builds produce a `profraw` file every time a +they are executed, the Swift test suite, when run with coverage enabled, +produces thousands of 30MB profile files. + +When build-script is run with `--swift-analyze-code-coverage merged`, this +module will get called before the tests run and spin up a small server that +runs alongside the test suite. The server accepts filenames that it receives +after each test directory is finished executing, and feeds them to 10 worker +processes which concurrently merge those files together. Doing so means the +test results directory doesn't balloon in size, as the files are merged together +and removed often. + +Once the test suite is finished, it sends a crafted message to the server that +tells it to merge any files left in the queue, then merges all the workers +together into one final file. diff --git a/utils/profdata_merge/__init__.py b/utils/profdata_merge/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/utils/profdata_merge/config.py b/utils/profdata_merge/config.py new file mode 100644 index 0000000000000..b727239b2fc56 --- /dev/null +++ b/utils/profdata_merge/config.py @@ -0,0 +1,27 @@ +# utils/profdata_merge/config.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the data structure that transforms arguments into usable +# values + +import tempfile +import os + + +class Config(): + """A class to store configuration information specified by command-line + arguments.""" + def __init__(self, out_dir, no_remove_files): + self.out_dir = out_dir + self.tmp_dir = tempfile.mkdtemp() + self.pid_file_path = os.path.join(self.out_dir, + "profdata_merge_worker.pid") + self.final_profdata_path = os.path.join(self.out_dir, "swift.profdata") + self.remove_files = not no_remove_files diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py new file mode 100755 index 0000000000000..96c1bede542c8 --- /dev/null +++ b/utils/profdata_merge/main.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +# utils/profdata_merge/main.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This module is used to prevent profile data filling up available disk space +# by listening for profile data and merging them into a universal profdata +# file while tests are executing. +# This file invokes the runner after parsing arguments. + +from __future__ import print_function +import sys +import argparse +import tempfile +import logging + +import runner + +SERVER_ADDRESS = ('localhost', 12400) +TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" + +if __name__ == "__main__": + if sys.platform != "darwin": + sys.exit("Error: The profile data merge worker requires OS X.") + + parser = argparse.ArgumentParser() + parser.add_argument("-l", "--log-file", + help="The file to write logs in debug mode.") + + subparsers = parser.add_subparsers() + + start = subparsers.add_parser("start") + start.add_argument("-d", "--debug", + help="Run in foreground and report status.", + action="store_true") + start.add_argument("-o", "--output-dir", + help=("The directory to write the PID file" + + "and final profdata file."), + default=tempfile.gettempdir()) + start.add_argument("--no-remove", + action="store_true", + help="Don't remove profraw files after merging them.") + start.set_defaults(func=runner.start_server) + + stop = subparsers.add_parser("stop") + stop.set_defaults(func=runner.stop_server) + + args = parser.parse_args() + + log_args = {'level': logging.DEBUG} + if args.log_file: + log_args['filename'] = args.log_file + + logging.basicConfig(**log_args) + + args.func(args) diff --git a/utils/profdata_merge/process.py b/utils/profdata_merge/process.py new file mode 100644 index 0000000000000..f7608a4e72916 --- /dev/null +++ b/utils/profdata_merge/process.py @@ -0,0 +1,78 @@ +# utils/profdata_merge/process.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the worker processes that watch a file queue for files and +# merge profile data in parallel. + +from multiprocessing import Process +import pipes +import os +import subprocess +import logging + + +class ProfdataMergerProcess(Process): + def __init__(self, config, file_queue): + super(ProfdataMergerProcess, self).__init__() + self.config = config + self.file_queue = file_queue + self.filename_buffer = [] + self.profdata_path = os.path.join(config.tmp_dir, + "%s.profdata" % self.name) + self.profdata_tmp_path = self.profdata_path + ".copy" + + def report(self, msg, level=logging.INFO): + """Convenience method for reporting status from the workers.""" + logging.log(level, "[%s]: %s" % (self.name, msg)) + + def merge_file_buffer(self): + """Merge all files in this worker's buffer and clear them. + This method makes a copy of the working merge progress, then + calls llvm-cov merge with up to 10 filenames, plus the current + in-progress merge.""" + if not self.filename_buffer: + self.report("no files to merge...") + return + if os.path.exists(self.profdata_path): + os.rename(self.profdata_path, self.profdata_tmp_path) + self.filename_buffer.append(self.profdata_tmp_path) + cleaned_files = ' '.join(pipes.quote(f) for f in self.filename_buffer) + # FIXME: This doesn't necessarily always line up with the version + # of clang++ used to build the binaries. + llvm_cmd = ("xcrun llvm-profdata merge -o %s %s" + % (self.profdata_path, cleaned_files)) + self.report(llvm_cmd) + ret = subprocess.call(llvm_cmd, shell=True) + if ret != 0: + self.report("llvm profdata command failed -- Exited with code %d" + % ret, level=logging.ERROR) + if self.config.remove_files: + for f in self.filename_buffer: + if os.path.exists(f): + os.remove(f) + self.filename_buffer = [] + + def run(self): + """Blocks and waits for the file queue so it can fill its buffer and + execute merges. If it finds None in the queue, then it knows to stop + waiting for the queue, merge its current buffer, and kill itself""" + while True: + filename = self.file_queue.get() + self.report("received filename: %s" % filename) + if filename is None: + self.report("received sentinel; merging...") + self.merge_file_buffer() + self.file_queue.task_done() + break + self.filename_buffer.append(filename) + self.report("Adding %s to filename_buffer." % filename) + if len(self.filename_buffer) >= 10: + self.merge_file_buffer() + self.file_queue.task_done() diff --git a/utils/profdata_merge/runner.py b/utils/profdata_merge/runner.py new file mode 100644 index 0000000000000..708260d324026 --- /dev/null +++ b/utils/profdata_merge/runner.py @@ -0,0 +1,87 @@ +# utils/profdata_merge/runner.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the main subroutines that invoke or stop the merge worker. + +import shutil +import os +import socket +import sys +import logging + +from multiprocessing import JoinableQueue +from process import ProfdataMergerProcess +from server import ProfdataServer +from main import SERVER_ADDRESS, TESTS_FINISHED_SENTINEL +from config import Config + + +def run_server(config): + pid = os.getpid() + if os.path.exists(config.pid_file_path): + with open(config.pid_file_path) as pidfile: + pid = pidfile.read() + logging.error(("existing process found with pid %s." + + "Ensure there are no other test runners running," + + "and delete the file at %s") % + (pid, config.pid_file_path)) + return + + with open(config.pid_file_path, "w") as pidfile: + pidfile.write(str(pid)) + + file_queue = JoinableQueue() + + processes = [ProfdataMergerProcess(config, file_queue) for _ in range(10)] + for p in processes: + p.start() + + server = ProfdataServer(file_queue) + server.serve_forever() + + for p in processes: + # force each merge worker to gracefully exit + file_queue.put(None) + + for p in processes: + logging.info("waiting for %s to finish..." % p.name) + p.join() + + # now that all workers have completed, merge all their files + merge_final = ProfdataMergerProcess(config, file_queue) + merge_final.profdata_path = config.final_profdata_path + for p in processes: + if os.path.exists(p.profdata_path): + logging.info("merging " + p.profdata_path + "...") + merge_final.filename_buffer.append(p.profdata_path) + merge_final.merge_file_buffer() + + +def stop_server(args): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(SERVER_ADDRESS) + sock.send(TESTS_FINISHED_SENTINEL) + sock.close() + + +def start_server(args): + config = Config(args.output_dir, args.no_remove) + if not args.debug: + pid = os.fork() + if pid != 0: + # kill the parent process we forked from. + sys.exit(0) + try: + run_server(config) + finally: + if os.path.exists(config.pid_file_path): + os.remove(config.pid_file_path) + if os.path.exists(config.tmp_dir): + shutil.rmtree(config.tmp_dir, ignore_errors=True) diff --git a/utils/profdata_merge/server.py b/utils/profdata_merge/server.py new file mode 100644 index 0000000000000..80cb48bf6cc06 --- /dev/null +++ b/utils/profdata_merge/server.py @@ -0,0 +1,60 @@ +# utils/profdata_merge/server.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the server and handler definitions that pass files to +# the merge worker processes. + +import SocketServer +import thread +import logging + +from main import SERVER_ADDRESS, TESTS_FINISHED_SENTINEL + + +class ProfdataTCPHandler(SocketServer.StreamRequestHandler): + + def report(self, msg): + """Convenience method for reporting status from the workers.""" + logging.info("[ProfdataTCPHandler]: %s" % msg) + + def handle(self): + """Receive a newline-separated list of filenames from a TCP connection + and add them to the shared merge queue, where the workers will + execute llvm-profdata merge commands.""" + data = self.rfile.read() + self.report("received data (length %d): %s" % (len(data), repr(data))) + + # Stop once we receive the sentinel + if data.startswith(TESTS_FINISHED_SENTINEL): + self.report("received sentinel; killing server...") + self.finish() + self.connection.close() + + def kill_server(server): + server.shutdown() + + # must be killed on another thread, or else deadlock + thread.start_new_thread(kill_server, (self.server,)) + else: + # Add all the files to the queue + for f in data.splitlines(): + f = f.strip() + if f in self.server.files_merged: + return + self.server.files_merged.add(f) + self.server.file_queue.put(f) + + +class ProfdataServer(SocketServer.TCPServer, object): + def __init__(self, file_queue): + super(ProfdataServer, self).__init__(SERVER_ADDRESS, + ProfdataTCPHandler) + self.file_queue = file_queue + self.files_merged = set() diff --git a/utils/use_profdir.py b/utils/use_profdir.py deleted file mode 100755 index 59d05fa9c3bf0..0000000000000 --- a/utils/use_profdir.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python - -# utils/use_profdir.py -# -# This source file is part of the Swift.org open source project -# -# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors -# Licensed under Apache License v2.0 with Runtime Library Exception -# -# See http://swift.org/LICENSE.txt for license information -# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors - -# This script is used to help prevent profile data clobbering during code -# coverage profiling. - -import sys -import subprocess -import os -import string -import random - -def random_string(N): - """Return a random ascii_uppercase + digits string of length `N`""" - return ''.join(random.choice(string.ascii_uppercase + string.digits) - for _ in range(N)) - -def main(): - # Grab passed in bash command - cmd = sys.argv[1:] - - # Search arguments for test identifiers - script_files = [f for f in cmd if f.endswith('.script')] - gtests = [f.split('=')[1] for f in cmd if f.startswith('--gtest_filter')] - - # Generate directory name using first test identifier, defaulting to - # random characters if no test identifier can be found - if script_files: - profdir = script_files[0] + '.profdir' - elif gtests: - profdir = os.path.join(os.path.dirname(cmd[0]), gtests[0] + '.profdir') - else: - profdir = random_string(12) + '.profdir' - - # Create the directory using the generated name - try: - os.makedirs(profdir) - except OSError as e: - ERROR_FILE_EXISTS = 17 - if e.errno != ERROR_FILE_EXISTS: - raise - - # cd into the new directory and execute the passed in command - previous_cwd = os.getcwd() - os.chdir(profdir) - try: - return_code = subprocess.call(cmd) - finally: - os.chdir(previous_cwd) - - return return_code - -if __name__ == '__main__': - exit(main()) diff --git a/validation-test/lit.site.cfg.in b/validation-test/lit.site.cfg.in index c4fd908003f3e..072917fa5a1b7 100644 --- a/validation-test/lit.site.cfg.in +++ b/validation-test/lit.site.cfg.in @@ -16,6 +16,8 @@ config.variant_triple = "@VARIANT_TRIPLE@" config.variant_sdk = "@VARIANT_SDK@" config.darwin_xcrun_toolchain = "@SWIFT_DARWIN_XCRUN_TOOLCHAIN@" +config.coverage_mode = "@SWIFT_ANALYZE_CODE_COVERAGE@" + if "@SWIFT_ASAN_BUILD@" == "TRUE": config.available_features.add("asan")