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
19using namespace llvm;
20using namespace remarks;
21using namespace llvm::remarkutil;
22
23static cl::SubCommand CountSub("count",
24 "Collect remarks based on specified criteria.");
25
26INPUT_FORMAT_COMMAND_LINE_OPTIONS(CountSub)
27INPUT_OUTPUT_COMMAND_LINE_OPTIONS(CountSub)
28
29static 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);
32static 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);
37static cl::opt<std::string>
38 RemarkNameOpt("remark-name",
39 cl::desc("Optional remark name to filter collection by."),
40 cl::ValueOptional, cl::sub(CountSub));
41static 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
46static cl::opt<std::string> RemarkFilterArgByOpt(
47 "filter-arg-by", cl::desc("Optional remark arg to filter collection by."),
48 cl::ValueOptional, cl::sub(CountSub));
49static 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));
54static 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);
59static 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));
64static 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(Val: Type::Failure), cl::sub(CountSub));
76static 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));
86static 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.
106static unsigned getValForKey(StringRef Key, const Remark &Remark) {
107 auto *RemarkArg = 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
115bool Filters::filterRemark(const Remark &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
130Error ArgumentCounter::getAllMatchingArgumentsInRemark(
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 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(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
156std::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
174void 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(x: {GroupVal, Row});
182 for (auto [Key, Idx] : ArgumentSetIdxMap) {
183 auto Count = getValForKey(Key, Remark);
184 CountByKeysMap[GroupVal][Idx] += Count;
185 }
186}
187
188void RemarkCounter::collect(const Remark &Remark) {
189 if (std::optional<std::string> Key = getGroupByKey(Remark))
190 ++CountedByRemarksMap[*Key];
191}
192
193Error ArgumentCounter::print(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
211Error RemarkCounter::print(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
226Expected<Filters> getRemarkFilter() {
227 // Create Filter properties.
228 auto MaybeRemarkNameFilter =
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 MaybeRemarkArgFilter = FilterMatcher::createExactOrRE(
239 ExactArg: RemarkFilterArgByOpt, REArg: 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{.RemarkNameFilter: std::move(*MaybeRemarkNameFilter),
249 .PassNameFilter: std::move(*MaybePassNameFilter),
250 .ArgFilter: std::move(*MaybeRemarkArgFilter), .RemarkTypeFilter: RemarkType};
251}
252
253Error useCollectRemark(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 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(Err: std::move(E));
272 return Error::success();
273}
274
275static 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, 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
314static CommandRegistration CountReg(&CountSub, collectRemarks);
315