/Users/buildslave/jenkins/workspace/clang-stage2-coverage-R/llvm/tools/llvm-cov/CoverageExporterJson.cpp
Line | Count | Source |
1 | | //===- CoverageExporterJson.cpp - Code coverage export --------------------===// |
2 | | // |
3 | | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | | // See https://llvm.org/LICENSE.txt for license information. |
5 | | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | | // |
7 | | //===----------------------------------------------------------------------===// |
8 | | // |
9 | | // This file implements export of code coverage data to JSON. |
10 | | // |
11 | | //===----------------------------------------------------------------------===// |
12 | | |
13 | | //===----------------------------------------------------------------------===// |
14 | | // |
15 | | // The json code coverage export follows the following format |
16 | | // Root: dict => Root Element containing metadata |
17 | | // -- Data: array => Homogeneous array of one or more export objects |
18 | | // -- Export: dict => Json representation of one CoverageMapping |
19 | | // -- Files: array => List of objects describing coverage for files |
20 | | // -- File: dict => Coverage for a single file |
21 | | // -- Segments: array => List of Segments contained in the file |
22 | | // -- Segment: dict => Describes a segment of the file with a counter |
23 | | // -- Expansions: array => List of expansion records |
24 | | // -- Expansion: dict => Object that descibes a single expansion |
25 | | // -- CountedRegion: dict => The region to be expanded |
26 | | // -- TargetRegions: array => List of Regions in the expansion |
27 | | // -- CountedRegion: dict => Single Region in the expansion |
28 | | // -- Summary: dict => Object summarizing the coverage for this file |
29 | | // -- LineCoverage: dict => Object summarizing line coverage |
30 | | // -- FunctionCoverage: dict => Object summarizing function coverage |
31 | | // -- RegionCoverage: dict => Object summarizing region coverage |
32 | | // -- Functions: array => List of objects describing coverage for functions |
33 | | // -- Function: dict => Coverage info for a single function |
34 | | // -- Filenames: array => List of filenames that the function relates to |
35 | | // -- Summary: dict => Object summarizing the coverage for the entire binary |
36 | | // -- LineCoverage: dict => Object summarizing line coverage |
37 | | // -- FunctionCoverage: dict => Object summarizing function coverage |
38 | | // -- InstantiationCoverage: dict => Object summarizing inst. coverage |
39 | | // -- RegionCoverage: dict => Object summarizing region coverage |
40 | | // |
41 | | //===----------------------------------------------------------------------===// |
42 | | |
43 | | #include "CoverageExporterJson.h" |
44 | | #include "CoverageReport.h" |
45 | | #include "llvm/ADT/Optional.h" |
46 | | #include "llvm/ADT/StringRef.h" |
47 | | #include "llvm/Support/JSON.h" |
48 | | #include "llvm/Support/ThreadPool.h" |
49 | | #include "llvm/Support/Threading.h" |
50 | | #include <algorithm> |
51 | | #include <mutex> |
52 | | #include <utility> |
53 | | |
54 | | /// The semantic version combined as a string. |
55 | 10 | #define LLVM_COVERAGE_EXPORT_JSON_STR "2.0.0" |
56 | | |
57 | | /// Unique type identifier for JSON coverage export. |
58 | 10 | #define LLVM_COVERAGE_EXPORT_JSON_TYPE_STR "llvm.coverage.json.export" |
59 | | |
60 | | using namespace llvm; |
61 | | |
62 | | namespace { |
63 | | |
64 | 218 | json::Array renderSegment(const coverage::CoverageSegment &Segment) { |
65 | 218 | return json::Array({Segment.Line, Segment.Col, int64_t(Segment.Count), |
66 | 218 | Segment.HasCount, Segment.IsRegionEntry}); |
67 | 218 | } |
68 | | |
69 | 191 | json::Array renderRegion(const coverage::CountedRegion &Region) { |
70 | 191 | return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd, |
71 | 191 | Region.ColumnEnd, int64_t(Region.ExecutionCount), |
72 | 191 | Region.FileID, Region.ExpandedFileID, |
73 | 191 | int64_t(Region.Kind)}); |
74 | 191 | } |
75 | | |
76 | 36 | json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) { |
77 | 36 | json::Array RegionArray; |
78 | 36 | for (const auto &Region : Regions) |
79 | 190 | RegionArray.push_back(renderRegion(Region)); |
80 | 36 | return RegionArray; |
81 | 36 | } |
82 | | |
83 | 1 | json::Object renderExpansion(const coverage::ExpansionRecord &Expansion) { |
84 | 1 | return json::Object( |
85 | 1 | {{"filenames", json::Array(Expansion.Function.Filenames)}, |
86 | 1 | // Mark the beginning and end of this expansion in the source file. |
87 | 1 | {"source_region", renderRegion(Expansion.Region)}, |
88 | 1 | // Enumerate the coverage information for the expansion. |
89 | 1 | {"target_regions", renderRegions(Expansion.Function.CountedRegions)}}); |
90 | 1 | } |
91 | | |
92 | 34 | json::Object renderSummary(const FileCoverageSummary &Summary) { |
93 | 34 | return json::Object( |
94 | 34 | {{"lines", |
95 | 34 | json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())}, |
96 | 34 | {"covered", int64_t(Summary.LineCoverage.getCovered())}, |
97 | 34 | {"percent", Summary.LineCoverage.getPercentCovered()}})}, |
98 | 34 | {"functions", |
99 | 34 | json::Object( |
100 | 34 | {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())}, |
101 | 34 | {"covered", int64_t(Summary.FunctionCoverage.getExecuted())}, |
102 | 34 | {"percent", Summary.FunctionCoverage.getPercentCovered()}})}, |
103 | 34 | {"instantiations", |
104 | 34 | json::Object( |
105 | 34 | {{"count", |
106 | 34 | int64_t(Summary.InstantiationCoverage.getNumFunctions())}, |
107 | 34 | {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())}, |
108 | 34 | {"percent", Summary.InstantiationCoverage.getPercentCovered()}})}, |
109 | 34 | {"regions", |
110 | 34 | json::Object( |
111 | 34 | {{"count", int64_t(Summary.RegionCoverage.getNumRegions())}, |
112 | 34 | {"covered", int64_t(Summary.RegionCoverage.getCovered())}, |
113 | 34 | {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() - |
114 | 34 | Summary.RegionCoverage.getCovered())}, |
115 | 34 | {"percent", Summary.RegionCoverage.getPercentCovered()}})}}); |
116 | 34 | } |
117 | | |
118 | | json::Array renderFileExpansions(const coverage::CoverageData &FileCoverage, |
119 | 22 | const FileCoverageSummary &FileReport) { |
120 | 22 | json::Array ExpansionArray; |
121 | 22 | for (const auto &Expansion : FileCoverage.getExpansions()) |
122 | 1 | ExpansionArray.push_back(renderExpansion(Expansion)); |
123 | 22 | return ExpansionArray; |
124 | 22 | } |
125 | | |
126 | | json::Array renderFileSegments(const coverage::CoverageData &FileCoverage, |
127 | 23 | const FileCoverageSummary &FileReport) { |
128 | 23 | json::Array SegmentArray; |
129 | 23 | for (const auto &Segment : FileCoverage) |
130 | 218 | SegmentArray.push_back(renderSegment(Segment)); |
131 | 23 | return SegmentArray; |
132 | 23 | } |
133 | | |
134 | | json::Object renderFile(const coverage::CoverageMapping &Coverage, |
135 | | const std::string &Filename, |
136 | | const FileCoverageSummary &FileReport, |
137 | 24 | const CoverageViewOptions &Options) { |
138 | 24 | json::Object File({{"filename", Filename}}); |
139 | 24 | if (!Options.ExportSummaryOnly) { |
140 | 22 | // Calculate and render detailed coverage information for given file. |
141 | 22 | auto FileCoverage = Coverage.getCoverageForFile(Filename); |
142 | 22 | File["segments"] = renderFileSegments(FileCoverage, FileReport); |
143 | 22 | if (!Options.SkipExpansions) { |
144 | 22 | File["expansions"] = renderFileExpansions(FileCoverage, FileReport); |
145 | 22 | } |
146 | 22 | } |
147 | 24 | File["summary"] = renderSummary(FileReport); |
148 | 24 | return File; |
149 | 24 | } |
150 | | |
151 | | json::Array renderFiles(const coverage::CoverageMapping &Coverage, |
152 | | ArrayRef<std::string> SourceFiles, |
153 | | ArrayRef<FileCoverageSummary> FileReports, |
154 | 10 | const CoverageViewOptions &Options) { |
155 | 10 | auto NumThreads = Options.NumThreads; |
156 | 10 | if (NumThreads == 0) { |
157 | 8 | NumThreads = std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(), |
158 | 8 | unsigned(SourceFiles.size()))); |
159 | 8 | } |
160 | 10 | ThreadPool Pool(NumThreads); |
161 | 10 | json::Array FileArray; |
162 | 10 | std::mutex FileArrayMutex; |
163 | 10 | |
164 | 34 | for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I24 ) { |
165 | 24 | auto &SourceFile = SourceFiles[I]; |
166 | 24 | auto &FileReport = FileReports[I]; |
167 | 24 | Pool.async([&] { |
168 | 24 | auto File = renderFile(Coverage, SourceFile, FileReport, Options); |
169 | 24 | { |
170 | 24 | std::lock_guard<std::mutex> Lock(FileArrayMutex); |
171 | 24 | FileArray.push_back(std::move(File)); |
172 | 24 | } |
173 | 24 | }); |
174 | 24 | } |
175 | 10 | Pool.wait(); |
176 | 10 | return FileArray; |
177 | 10 | } |
178 | | |
179 | | json::Array renderFunctions( |
180 | 8 | const iterator_range<coverage::FunctionRecordIterator> &Functions) { |
181 | 8 | json::Array FunctionArray; |
182 | 8 | for (const auto &F : Functions) |
183 | 35 | FunctionArray.push_back( |
184 | 35 | json::Object({{"name", F.Name}, |
185 | 35 | {"count", int64_t(F.ExecutionCount)}, |
186 | 35 | {"regions", renderRegions(F.CountedRegions)}, |
187 | 35 | {"filenames", json::Array(F.Filenames)}})); |
188 | 8 | return FunctionArray; |
189 | 8 | } |
190 | | |
191 | | } // end anonymous namespace |
192 | | |
193 | 9 | void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) { |
194 | 9 | std::vector<std::string> SourceFiles; |
195 | 24 | for (StringRef SF : Coverage.getUniqueSourceFiles()) { |
196 | 24 | if (!IgnoreFilters.matchesFilename(SF)) |
197 | 21 | SourceFiles.emplace_back(SF); |
198 | 24 | } |
199 | 9 | renderRoot(SourceFiles); |
200 | 9 | } |
201 | | |
202 | 10 | void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) { |
203 | 10 | FileCoverageSummary Totals = FileCoverageSummary("Totals"); |
204 | 10 | auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals, |
205 | 10 | SourceFiles, Options); |
206 | 10 | auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options); |
207 | 10 | // Sort files in order of their names. |
208 | 10 | std::sort(Files.begin(), Files.end(), |
209 | 31 | [](const json::Value &A, const json::Value &B) { |
210 | 31 | const json::Object *ObjA = A.getAsObject(); |
211 | 31 | const json::Object *ObjB = B.getAsObject(); |
212 | 31 | assert(ObjA != nullptr && "Value A was not an Object"); |
213 | 31 | assert(ObjB != nullptr && "Value B was not an Object"); |
214 | 31 | const StringRef FilenameA = ObjA->getString("filename").getValue(); |
215 | 31 | const StringRef FilenameB = ObjB->getString("filename").getValue(); |
216 | 31 | return FilenameA.compare(FilenameB) < 0; |
217 | 31 | }); |
218 | 10 | auto Export = json::Object( |
219 | 10 | {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}}); |
220 | 10 | // Skip functions-level information if necessary. |
221 | 10 | if (!Options.ExportSummaryOnly && !Options.SkipFunctions9 ) |
222 | 8 | Export["functions"] = renderFunctions(Coverage.getCoveredFunctions()); |
223 | 10 | |
224 | 10 | auto ExportArray = json::Array({std::move(Export)}); |
225 | 10 | |
226 | 10 | OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR}, |
227 | 10 | {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR}, |
228 | 10 | {"data", std::move(ExportArray)}}); |
229 | 10 | } |