xref: /freebsd/contrib/llvm-project/llvm/tools/llvm-remarkutil/RemarkCounter.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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