xref: /llvm-project/llvm/utils/prepare-code-coverage-artifact.py (revision 64425caba29e62b42dfa7e03a47a37d9ee9c47aa)
1#!/usr/bin/env python
2
3from __future__ import print_function
4
5"""Prepare a code coverage artifact.
6
7- Collate raw profiles into one indexed profile.
8- Generate html reports for the given binaries.
9
10Caution: The positional arguments to this script must be specified before any
11optional arguments, such as --restrict.
12"""
13
14import argparse
15import glob
16import os
17import subprocess
18import sys
19
20
21def merge_raw_profiles(host_llvm_profdata, profile_data_dir, preserve_profiles):
22    print(":: Merging raw profiles...", end="")
23    sys.stdout.flush()
24    raw_profiles = glob.glob(os.path.join(profile_data_dir, "*.profraw"))
25    manifest_path = os.path.join(profile_data_dir, "profiles.manifest")
26    profdata_path = os.path.join(profile_data_dir, "Coverage.profdata")
27    with open(manifest_path, "w") as manifest:
28        manifest.write("\n".join(raw_profiles))
29    subprocess.check_call(
30        [
31            host_llvm_profdata,
32            "merge",
33            "-sparse",
34            "-f",
35            manifest_path,
36            "-o",
37            profdata_path,
38        ]
39    )
40    if not preserve_profiles:
41        for raw_profile in raw_profiles:
42            os.remove(raw_profile)
43    os.remove(manifest_path)
44    print("Done!")
45    return profdata_path
46
47
48def prepare_html_report(
49    host_llvm_cov, profile, report_dir, binaries, restricted_dirs, compilation_dir
50):
51    print(":: Preparing html report for {0}...".format(binaries), end="")
52    sys.stdout.flush()
53    objects = []
54    for i, binary in enumerate(binaries):
55        if i == 0:
56            objects.append(binary)
57        else:
58            objects.extend(("-object", binary))
59    invocation = (
60        [host_llvm_cov, "show"]
61        + objects
62        + [
63            "-format",
64            "html",
65            "-instr-profile",
66            profile,
67            "-o",
68            report_dir,
69            "-show-line-counts-or-regions",
70            "-show-directory-coverage",
71            "-Xdemangler",
72            "c++filt",
73            "-Xdemangler",
74            "-n",
75        ]
76        + restricted_dirs
77    )
78    if compilation_dir:
79        invocation += ["-compilation-dir=" + compilation_dir]
80    subprocess.check_call(invocation)
81    with open(os.path.join(report_dir, "summary.txt"), "wb") as Summary:
82        subprocess.check_call(
83            [host_llvm_cov, "report"]
84            + objects
85            + ["-instr-profile", profile]
86            + restricted_dirs,
87            stdout=Summary,
88        )
89    print("Done!")
90
91
92def prepare_html_reports(
93    host_llvm_cov,
94    profdata_path,
95    report_dir,
96    binaries,
97    unified_report,
98    restricted_dirs,
99    compilation_dir,
100):
101    if unified_report:
102        prepare_html_report(
103            host_llvm_cov,
104            profdata_path,
105            report_dir,
106            binaries,
107            restricted_dirs,
108            compilation_dir,
109        )
110    else:
111        for binary in binaries:
112            binary_report_dir = os.path.join(report_dir, os.path.basename(binary))
113            prepare_html_report(
114                host_llvm_cov,
115                profdata_path,
116                binary_report_dir,
117                [binary],
118                restricted_dirs,
119                compilation_dir,
120            )
121
122
123if __name__ == "__main__":
124    parser = argparse.ArgumentParser(description=__doc__)
125    parser.add_argument("host_llvm_profdata", help="Path to llvm-profdata")
126    parser.add_argument("host_llvm_cov", help="Path to llvm-cov")
127    parser.add_argument(
128        "profile_data_dir", help="Path to the directory containing the raw profiles"
129    )
130    parser.add_argument(
131        "report_dir", help="Path to the output directory for html reports"
132    )
133    parser.add_argument(
134        "binaries",
135        metavar="B",
136        type=str,
137        nargs="*",
138        help="Path to an instrumented binary",
139    )
140    parser.add_argument(
141        "--only-merge",
142        action="store_true",
143        help="Only merge raw profiles together, skip report " "generation",
144    )
145    parser.add_argument(
146        "--preserve-profiles", help="Do not delete raw profiles", action="store_true"
147    )
148    parser.add_argument(
149        "--use-existing-profdata", help="Specify an existing indexed profile to use"
150    )
151    parser.add_argument(
152        "--unified-report",
153        action="store_true",
154        help="Emit a unified report for all binaries",
155    )
156    parser.add_argument(
157        "--restrict",
158        metavar="R",
159        type=str,
160        nargs="*",
161        default=[],
162        help="Restrict the reporting to the given source paths"
163        " (must be specified after all other positional arguments)",
164    )
165    parser.add_argument(
166        "-C",
167        "--compilation-dir",
168        type=str,
169        default="",
170        help="The compilation directory of the binary",
171    )
172    args = parser.parse_args()
173
174    if args.use_existing_profdata and args.only_merge:
175        print("--use-existing-profdata and --only-merge are incompatible")
176        exit(1)
177
178    if args.use_existing_profdata:
179        profdata_path = args.use_existing_profdata
180    else:
181        profdata_path = merge_raw_profiles(
182            args.host_llvm_profdata, args.profile_data_dir, args.preserve_profiles
183        )
184
185    if not len(args.binaries):
186        print("No binaries specified, no work to do!")
187        exit(1)
188
189    if not args.only_merge:
190        prepare_html_reports(
191            args.host_llvm_cov,
192            profdata_path,
193            args.report_dir,
194            args.binaries,
195            args.unified_report,
196            args.restrict,
197            args.compilation_dir,
198        )
199