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 RemarkNameOpt("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> RemarkFilterArgByOpt(
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 RemarkNameOptRE("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 RemarkArgFilterOptRE("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> RemarkTypeOpt(
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(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(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(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.
getValForKey(StringRef Key,const Remark & Remark)106 static unsigned getValForKey(StringRef Key, const Remark &Remark) {
107 auto *RemarkArg = find_if(Remark.Args, [&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
filterRemark(const Remark & Remark)115 bool Filters::filterRemark(const Remark &Remark) {
116 if (RemarkNameFilter && !RemarkNameFilter->match(Remark.RemarkName))
117 return false;
118 if (PassNameFilter && !PassNameFilter->match(Remark.PassName))
119 return false;
120 if (RemarkTypeFilter)
121 return *RemarkTypeFilter == Remark.RemarkType;
122 if (ArgFilter) {
123 if (!any_of(Remark.Args,
124 [this](Argument Arg) { return ArgFilter->match(Arg.Val); }))
125 return false;
126 }
127 return true;
128 }
129
getAllMatchingArgumentsInRemark(StringRef Buffer,ArrayRef<FilterMatcher> Arguments,Filters & Filter)130 Error ArgumentCounter::getAllMatchingArgumentsInRemark(
131 StringRef Buffer, ArrayRef<FilterMatcher> Arguments, Filters &Filter) {
132 auto MaybeParser = createRemarkParser(InputFormat, Buffer);
133 if (!MaybeParser)
134 return MaybeParser.takeError();
135 auto &Parser = **MaybeParser;
136 auto MaybeRemark = Parser.next();
137 for (; MaybeRemark; MaybeRemark = Parser.next()) {
138 auto &Remark = **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(Arg.Key) && Arg.isValInt())
145 ArgumentSetIdxMap.insert({Arg.Key, ArgumentSetIdxMap.size()});
146 }
147 }
148
149 auto E = MaybeRemark.takeError();
150 if (!E.isA<EndOfFileError>())
151 return E;
152 consumeError(std::move(E));
153 return Error::success();
154 }
155
getGroupByKey(const Remark & Remark)156 std::optional<std::string> Counter::getGroupByKey(const Remark &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
collect(const Remark & Remark)174 void ArgumentCounter::collect(const Remark &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({GroupVal, Row});
182 for (auto [Key, Idx] : ArgumentSetIdxMap) {
183 auto Count = getValForKey(Key, Remark);
184 CountByKeysMap[GroupVal][Idx] += Count;
185 }
186 }
187
collect(const Remark & Remark)188 void RemarkCounter::collect(const Remark &Remark) {
189 if (std::optional<std::string> Key = getGroupByKey(Remark))
190 ++CountedByRemarksMap[*Key];
191 }
192
print(StringRef OutputFileName)193 Error ArgumentCounter::print(StringRef OutputFileName) {
194 auto MaybeOF =
195 getOutputFileWithFlags(OutputFileName, sys::fs::OF_TextWithCRLF);
196 if (!MaybeOF)
197 return MaybeOF.takeError();
198
199 auto OF = std::move(*MaybeOF);
200 OF->os() << groupByToStr(Group) << ",";
201 OF->os() << llvm::interleaved(llvm::make_first_range(ArgumentSetIdxMap), ",");
202 OF->os() << "\n";
203 for (auto [Header, CountVector] : CountByKeysMap) {
204 OF->os() << Header << ",";
205 OF->os() << llvm::interleaved(CountVector, ",");
206 OF->os() << "\n";
207 }
208 return Error::success();
209 }
210
print(StringRef OutputFileName)211 Error RemarkCounter::print(StringRef OutputFileName) {
212 auto MaybeOF =
213 getOutputFileWithFlags(OutputFileName, sys::fs::OF_TextWithCRLF);
214 if (!MaybeOF)
215 return MaybeOF.takeError();
216
217 auto OF = std::move(*MaybeOF);
218 OF->os() << groupByToStr(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
getRemarkFilter()226 Expected<Filters> getRemarkFilter() {
227 // Create Filter properties.
228 auto MaybeRemarkNameFilter =
229 FilterMatcher::createExactOrRE(RemarkNameOpt, RemarkNameOptRE);
230 if (!MaybeRemarkNameFilter)
231 return MaybeRemarkNameFilter.takeError();
232
233 auto MaybePassNameFilter =
234 FilterMatcher::createExactOrRE(PassNameOpt, PassNameOptRE);
235 if (!MaybePassNameFilter)
236 return MaybePassNameFilter.takeError();
237
238 auto MaybeRemarkArgFilter = FilterMatcher::createExactOrRE(
239 RemarkFilterArgByOpt, RemarkArgFilterOptRE);
240 if (!MaybeRemarkArgFilter)
241 return MaybeRemarkArgFilter.takeError();
242
243 std::optional<Type> RemarkType;
244 if (RemarkTypeOpt != Type::Failure)
245 RemarkType = RemarkTypeOpt;
246
247 // Create RemarkFilter.
248 return Filters{std::move(*MaybeRemarkNameFilter),
249 std::move(*MaybePassNameFilter),
250 std::move(*MaybeRemarkArgFilter), RemarkType};
251 }
252
useCollectRemark(StringRef Buffer,Counter & Counter,Filters & Filter)253 Error useCollectRemark(StringRef Buffer, Counter &Counter, Filters &Filter) {
254 // Create Parser.
255 auto MaybeParser = createRemarkParser(InputFormat, Buffer);
256 if (!MaybeParser)
257 return MaybeParser.takeError();
258 auto &Parser = **MaybeParser;
259 auto MaybeRemark = Parser.next();
260 for (; MaybeRemark; MaybeRemark = Parser.next()) {
261 const Remark &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(std::move(E));
272 return Error::success();
273 }
274
collectRemarks()275 static Error collectRemarks() {
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, 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(FilterMatcher::createExact(Key));
294 } else if (!RKeys.empty())
295 for (auto Key : RKeys) {
296 auto FM = FilterMatcher::createRE(Key, RKeys);
297 if (!FM)
298 return FM.takeError();
299 ArgumentsVector.push_back(std::move(*FM));
300 }
301 else
302 ArgumentsVector.push_back(FilterMatcher::createAny());
303
304 Expected<ArgumentCounter> AC = ArgumentCounter::createArgumentCounter(
305 GroupByOpt, ArgumentsVector, Buffer, Filter);
306 if (!AC)
307 return AC.takeError();
308 if (auto E = useCollectRemark(Buffer, *AC, Filter))
309 return E;
310 }
311 return Error::success();
312 }
313
314 static CommandRegistration CountReg(&CountSub, collectRemarks);
315