From 0a9728f87e41e2956cfd47de97c8f57e41b13aaa Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Wed, 3 Jan 2024 09:16:48 +1100 Subject: [PATCH] twister: support parallel coverage with lcov >=2.0 lcov 2.0 added support for processing coverage data in parallel, which provides a large speedup when processing many files, at the cost of some additional overhead. When running the Chrome EC tests with coverage, parallel reporting on a 36C72T machine reduces the time spent generating coverage reports by 40 minutes (from approximately 1 hour to 20 minutes total runtime), at the cost of about 3x greater CPU time overall (assumed to be overhead for parallel processing, likely from spawning much larger numbers of subprocesses). The level of lcov parallelism is taken from the --jobs option passed to twister, allowing lcov to choose if unspecified. Signed-off-by: Peter Marheine --- scripts/pylib/twister/twisterlib/coverage.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/pylib/twister/twisterlib/coverage.py b/scripts/pylib/twister/twisterlib/coverage.py index fbc04e9c7c3..556f5b359ba 100644 --- a/scripts/pylib/twister/twisterlib/coverage.py +++ b/scripts/pylib/twister/twisterlib/coverage.py @@ -31,9 +31,9 @@ class CoverageTool: self.output_formats = None @staticmethod - def factory(tool): + def factory(tool, jobs=None): if tool == 'lcov': - t = Lcov() + t = Lcov(jobs) elif tool == 'gcovr': t = Gcovr() else: @@ -141,11 +141,12 @@ class CoverageTool: class Lcov(CoverageTool): - def __init__(self): + def __init__(self, jobs=None): super().__init__() self.ignores = [] self.output_formats = "lcov,html" self.version = self.get_version() + self.jobs = jobs def get_version(self): try: @@ -192,13 +193,22 @@ class Lcov(CoverageTool): def run_lcov(self, args, coveragelog): if self.is_lcov_v2: branch_coverage = "branch_coverage=1" + if self.jobs is None: + # Default: --parallel=0 will autodetect appropriate parallelism + parallel = ["--parallel", "0"] + elif self.jobs == 1: + # Serial execution requested, don't parallelize at all + parallel = [] + else: + parallel = ["--parallel", str(self.jobs)] else: branch_coverage = "lcov_branch_coverage=1" + parallel = [] cmd = [ "lcov", "--gcov-tool", self.gcov_tool, "--rc", branch_coverage, - ] + args + ] + parallel + args return self.run_command(cmd, coveragelog) def _generate(self, outdir, coveragelog): @@ -364,7 +374,7 @@ def run_coverage(testplan, options): logger.info("Generating coverage files...") logger.info(f"Using gcov tool: {gcov_tool}") - coverage_tool = CoverageTool.factory(options.coverage_tool) + coverage_tool = CoverageTool.factory(options.coverage_tool, jobs=options.jobs) coverage_tool.gcov_tool = gcov_tool coverage_tool.base_dir = os.path.abspath(options.coverage_basedir) # Apply output format default