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