1 | //===- RemarkCounter.cpp --------------------------------------------------===// |
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 | // Generic tool to count remarks based on properties |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "RemarkCounter.h" |
14 | #include "RemarkUtilRegistry.h" |
15 | #include "llvm/Support/CommandLine.h" |
16 | #include "llvm/Support/InterleavedRange.h" |
17 | #include "llvm/Support/Regex.h" |
18 | |
19 | using namespace llvm; |
20 | using namespace remarks; |
21 | using namespace llvm::remarkutil; |
22 | |
23 | static cl::SubCommand CountSub("count" , |
24 | "Collect remarks based on specified criteria." ); |
25 | |
26 | INPUT_FORMAT_COMMAND_LINE_OPTIONS(CountSub) |
27 | INPUT_OUTPUT_COMMAND_LINE_OPTIONS(CountSub) |
28 | |
29 | static cl::list<std::string> |
30 | Keys("args" , cl::desc("Specify remark argument/s to count by." ), |
31 | cl::value_desc("arguments" ), cl::sub(CountSub), cl::ValueOptional); |
32 | static cl::list<std::string> RKeys( |
33 | "rargs" , |
34 | cl::desc( |
35 | "Specify remark argument/s to count (accepts regular expressions)." ), |
36 | cl::value_desc("arguments" ), cl::sub(CountSub), cl::ValueOptional); |
37 | static cl::opt<std::string> |
38 | ("remark-name" , |
39 | cl::desc("Optional remark name to filter collection by." ), |
40 | cl::ValueOptional, cl::sub(CountSub)); |
41 | static cl::opt<std::string> |
42 | PassNameOpt("pass-name" , cl::ValueOptional, |
43 | cl::desc("Optional remark pass name to filter collection by." ), |
44 | cl::sub(CountSub)); |
45 | |
46 | static cl::opt<std::string> ( |
47 | "filter-arg-by" , cl::desc("Optional remark arg to filter collection by." ), |
48 | cl::ValueOptional, cl::sub(CountSub)); |
49 | static cl::opt<std::string> |
50 | ("rremark-name" , |
51 | cl::desc("Optional remark name to filter collection by " |
52 | "(accepts regular expressions)." ), |
53 | cl::ValueOptional, cl::sub(CountSub)); |
54 | static cl::opt<std::string> |
55 | ("rfilter-arg-by" , |
56 | cl::desc("Optional remark arg to filter collection by " |
57 | "(accepts regular expressions)." ), |
58 | cl::sub(CountSub), cl::ValueOptional); |
59 | static cl::opt<std::string> |
60 | PassNameOptRE("rpass-name" , cl::ValueOptional, |
61 | cl::desc("Optional remark pass name to filter collection " |
62 | "by (accepts regular expressions)." ), |
63 | cl::sub(CountSub)); |
64 | static cl::opt<Type> ( |
65 | "remark-type" , cl::desc("Optional remark type to filter collection by." ), |
66 | cl::values(clEnumValN(Type::Unknown, "unknown" , "UNKOWN" ), |
67 | clEnumValN(Type::Passed, "passed" , "PASSED" ), |
68 | clEnumValN(Type::Missed, "missed" , "MISSED" ), |
69 | clEnumValN(Type::Analysis, "analysis" , "ANALYSIS" ), |
70 | clEnumValN(Type::AnalysisFPCommute, "analysis-fp-commute" , |
71 | "ANALYSIS_FP_COMMUTE" ), |
72 | clEnumValN(Type::AnalysisAliasing, "analysis-aliasing" , |
73 | "ANALYSIS_ALIASING" ), |
74 | clEnumValN(Type::Failure, "failure" , "FAILURE" )), |
75 | cl::init(Val: Type::Failure), cl::sub(CountSub)); |
76 | static cl::opt<CountBy> CountByOpt( |
77 | "count-by" , cl::desc("Specify the property to collect remarks by." ), |
78 | cl::values( |
79 | clEnumValN(CountBy::REMARK, "remark-name" , |
80 | "Counts individual remarks based on how many of the remark " |
81 | "exists." ), |
82 | clEnumValN(CountBy::ARGUMENT, "arg" , |
83 | "Counts based on the value each specified argument has. The " |
84 | "argument has to have a number value to be considered." )), |
85 | cl::init(Val: CountBy::REMARK), cl::sub(CountSub)); |
86 | static cl::opt<GroupBy> GroupByOpt( |
87 | "group-by" , cl::desc("Specify the property to group remarks by." ), |
88 | cl::values( |
89 | clEnumValN( |
90 | GroupBy::PER_SOURCE, "source" , |
91 | "Display the count broken down by the filepath of each remark " |
92 | "emitted. Requires remarks to have DebugLoc information." ), |
93 | clEnumValN(GroupBy::PER_FUNCTION, "function" , |
94 | "Breakdown the count by function name." ), |
95 | clEnumValN( |
96 | GroupBy::PER_FUNCTION_WITH_DEBUG_LOC, "function-with-loc" , |
97 | "Breakdown the count by function name taking into consideration " |
98 | "the filepath info from the DebugLoc of the remark." ), |
99 | clEnumValN(GroupBy::TOTAL, "total" , |
100 | "Output the total number corresponding to the count for the " |
101 | "provided input file." )), |
102 | cl::init(Val: GroupBy::PER_SOURCE), cl::sub(CountSub)); |
103 | |
104 | /// Look for matching argument with \p Key in \p Remark and return the parsed |
105 | /// integer value or 0 if it is has no integer value. |
106 | static unsigned (StringRef Key, const Remark &) { |
107 | auto * = find_if(Range: Remark.Args, P: [&Key](const Argument &Arg) { |
108 | return Arg.Key == Key && Arg.isValInt(); |
109 | }); |
110 | if (RemarkArg == Remark.Args.end()) |
111 | return 0; |
112 | return *RemarkArg->getValAsInt(); |
113 | } |
114 | |
115 | bool Filters::(const Remark &) { |
116 | if (RemarkNameFilter && !RemarkNameFilter->match(StringToMatch: Remark.RemarkName)) |
117 | return false; |
118 | if (PassNameFilter && !PassNameFilter->match(StringToMatch: Remark.PassName)) |
119 | return false; |
120 | if (RemarkTypeFilter) |
121 | return *RemarkTypeFilter == Remark.RemarkType; |
122 | if (ArgFilter) { |
123 | if (!any_of(Range: Remark.Args, |
124 | P: [this](Argument Arg) { return ArgFilter->match(StringToMatch: Arg.Val); })) |
125 | return false; |
126 | } |
127 | return true; |
128 | } |
129 | |
130 | Error ArgumentCounter::( |
131 | StringRef Buffer, ArrayRef<FilterMatcher> Arguments, Filters &Filter) { |
132 | auto MaybeParser = createRemarkParser(ParserFormat: InputFormat, Buf: Buffer); |
133 | if (!MaybeParser) |
134 | return MaybeParser.takeError(); |
135 | auto &Parser = **MaybeParser; |
136 | auto = Parser.next(); |
137 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
138 | auto & = **MaybeRemark; |
139 | // Only collect keys from remarks included in the filter. |
140 | if (!Filter.filterRemark(Remark)) |
141 | continue; |
142 | for (auto &Key : Arguments) { |
143 | for (Argument Arg : Remark.Args) |
144 | if (Key.match(StringToMatch: Arg.Key) && Arg.isValInt()) |
145 | ArgumentSetIdxMap.insert(KV: {Arg.Key, ArgumentSetIdxMap.size()}); |
146 | } |
147 | } |
148 | |
149 | auto E = MaybeRemark.takeError(); |
150 | if (!E.isA<EndOfFileError>()) |
151 | return E; |
152 | consumeError(Err: std::move(E)); |
153 | return Error::success(); |
154 | } |
155 | |
156 | std::optional<std::string> Counter::(const Remark &) { |
157 | switch (Group) { |
158 | case GroupBy::PER_FUNCTION: |
159 | return Remark.FunctionName.str(); |
160 | case GroupBy::TOTAL: |
161 | return "Total" ; |
162 | case GroupBy::PER_SOURCE: |
163 | case GroupBy::PER_FUNCTION_WITH_DEBUG_LOC: |
164 | if (!Remark.Loc.has_value()) |
165 | return std::nullopt; |
166 | |
167 | if (Group == GroupBy::PER_FUNCTION_WITH_DEBUG_LOC) |
168 | return Remark.Loc->SourceFilePath.str() + ":" + Remark.FunctionName.str(); |
169 | return Remark.Loc->SourceFilePath.str(); |
170 | } |
171 | llvm_unreachable("Fully covered switch above!" ); |
172 | } |
173 | |
174 | void ArgumentCounter::(const Remark &) { |
175 | SmallVector<unsigned, 4> Row(ArgumentSetIdxMap.size()); |
176 | std::optional<std::string> GroupByKey = getGroupByKey(Remark); |
177 | // Early return if we don't have a value |
178 | if (!GroupByKey) |
179 | return; |
180 | auto GroupVal = *GroupByKey; |
181 | CountByKeysMap.insert(x: {GroupVal, Row}); |
182 | for (auto [Key, Idx] : ArgumentSetIdxMap) { |
183 | auto Count = getValForKey(Key, Remark); |
184 | CountByKeysMap[GroupVal][Idx] += Count; |
185 | } |
186 | } |
187 | |
188 | void RemarkCounter::(const Remark &) { |
189 | if (std::optional<std::string> Key = getGroupByKey(Remark)) |
190 | ++CountedByRemarksMap[*Key]; |
191 | } |
192 | |
193 | Error ArgumentCounter::(StringRef OutputFileName) { |
194 | auto MaybeOF = |
195 | getOutputFileWithFlags(OutputFileName, Flags: sys::fs::OF_TextWithCRLF); |
196 | if (!MaybeOF) |
197 | return MaybeOF.takeError(); |
198 | |
199 | auto OF = std::move(*MaybeOF); |
200 | OF->os() << groupByToStr(GroupBy: Group) << "," ; |
201 | OF->os() << llvm::interleaved(R: llvm::make_first_range(c&: ArgumentSetIdxMap), Separator: "," ); |
202 | OF->os() << "\n" ; |
203 | for (auto [Header, CountVector] : CountByKeysMap) { |
204 | OF->os() << Header << "," ; |
205 | OF->os() << llvm::interleaved(R: CountVector, Separator: "," ); |
206 | OF->os() << "\n" ; |
207 | } |
208 | return Error::success(); |
209 | } |
210 | |
211 | Error RemarkCounter::(StringRef OutputFileName) { |
212 | auto MaybeOF = |
213 | getOutputFileWithFlags(OutputFileName, Flags: sys::fs::OF_TextWithCRLF); |
214 | if (!MaybeOF) |
215 | return MaybeOF.takeError(); |
216 | |
217 | auto OF = std::move(*MaybeOF); |
218 | OF->os() << groupByToStr(GroupBy: Group) << "," |
219 | << "Count\n" ; |
220 | for (auto [Key, Count] : CountedByRemarksMap) |
221 | OF->os() << Key << "," << Count << "\n" ; |
222 | OF->keep(); |
223 | return Error::success(); |
224 | } |
225 | |
226 | Expected<Filters> () { |
227 | // Create Filter properties. |
228 | auto = |
229 | FilterMatcher::createExactOrRE(ExactArg: RemarkNameOpt, REArg: RemarkNameOptRE); |
230 | if (!MaybeRemarkNameFilter) |
231 | return MaybeRemarkNameFilter.takeError(); |
232 | |
233 | auto MaybePassNameFilter = |
234 | FilterMatcher::createExactOrRE(ExactArg: PassNameOpt, REArg: PassNameOptRE); |
235 | if (!MaybePassNameFilter) |
236 | return MaybePassNameFilter.takeError(); |
237 | |
238 | auto = FilterMatcher::createExactOrRE( |
239 | ExactArg: RemarkFilterArgByOpt, REArg: RemarkArgFilterOptRE); |
240 | if (!MaybeRemarkArgFilter) |
241 | return MaybeRemarkArgFilter.takeError(); |
242 | |
243 | std::optional<Type> ; |
244 | if (RemarkTypeOpt != Type::Failure) |
245 | RemarkType = RemarkTypeOpt; |
246 | |
247 | // Create RemarkFilter. |
248 | return Filters{.RemarkNameFilter: std::move(*MaybeRemarkNameFilter), |
249 | .PassNameFilter: std::move(*MaybePassNameFilter), |
250 | .ArgFilter: std::move(*MaybeRemarkArgFilter), .RemarkTypeFilter: RemarkType}; |
251 | } |
252 | |
253 | Error (StringRef Buffer, Counter &Counter, Filters &Filter) { |
254 | // Create Parser. |
255 | auto MaybeParser = createRemarkParser(ParserFormat: InputFormat, Buf: Buffer); |
256 | if (!MaybeParser) |
257 | return MaybeParser.takeError(); |
258 | auto &Parser = **MaybeParser; |
259 | auto = Parser.next(); |
260 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
261 | const Remark & = **MaybeRemark; |
262 | if (Filter.filterRemark(Remark)) |
263 | Counter.collect(Remark); |
264 | } |
265 | |
266 | if (auto E = Counter.print(OutputFileName)) |
267 | return E; |
268 | auto E = MaybeRemark.takeError(); |
269 | if (!E.isA<EndOfFileError>()) |
270 | return E; |
271 | consumeError(Err: std::move(E)); |
272 | return Error::success(); |
273 | } |
274 | |
275 | static Error () { |
276 | // Create a parser for the user-specified input format. |
277 | auto MaybeBuf = getInputMemoryBuffer(InputFileName); |
278 | if (!MaybeBuf) |
279 | return MaybeBuf.takeError(); |
280 | StringRef Buffer = (*MaybeBuf)->getBuffer(); |
281 | auto MaybeFilter = getRemarkFilter(); |
282 | if (!MaybeFilter) |
283 | return MaybeFilter.takeError(); |
284 | auto &Filter = *MaybeFilter; |
285 | if (CountByOpt == CountBy::REMARK) { |
286 | RemarkCounter RC(GroupByOpt); |
287 | if (auto E = useCollectRemark(Buffer, Counter&: RC, Filter)) |
288 | return E; |
289 | } else if (CountByOpt == CountBy::ARGUMENT) { |
290 | SmallVector<FilterMatcher, 4> ArgumentsVector; |
291 | if (!Keys.empty()) { |
292 | for (auto &Key : Keys) |
293 | ArgumentsVector.push_back(Elt: FilterMatcher::createExact(Filter: Key)); |
294 | } else if (!RKeys.empty()) |
295 | for (auto Key : RKeys) { |
296 | auto FM = FilterMatcher::createRE(Filter: Key, Arg: RKeys); |
297 | if (!FM) |
298 | return FM.takeError(); |
299 | ArgumentsVector.push_back(Elt: std::move(*FM)); |
300 | } |
301 | else |
302 | ArgumentsVector.push_back(Elt: FilterMatcher::createAny()); |
303 | |
304 | Expected<ArgumentCounter> AC = ArgumentCounter::createArgumentCounter( |
305 | Group: GroupByOpt, Arguments: ArgumentsVector, Buffer, Filter); |
306 | if (!AC) |
307 | return AC.takeError(); |
308 | if (auto E = useCollectRemark(Buffer, Counter&: *AC, Filter)) |
309 | return E; |
310 | } |
311 | return Error::success(); |
312 | } |
313 | |
314 | static CommandRegistration CountReg(&CountSub, collectRemarks); |
315 | |