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)
28REMARK_FILTER_COMMAND_LINE_OPTIONS(CountSub)
29
30REMARK_FILTER_SETUP_FUNC()
31
32static cl::list<std::string>
33 Keys("args", cl::desc("Specify remark argument/s to count by."),
34 cl::value_desc("arguments"), cl::sub(CountSub), cl::ValueOptional);
35static cl::list<std::string> RKeys(
36 "rargs",
37 cl::desc(
38 "Specify remark argument/s to count (accepts regular expressions)."),
39 cl::value_desc("arguments"), cl::sub(CountSub), cl::ValueOptional);
40
41static cl::opt<CountBy> CountByOpt(
42 "count-by", cl::desc("Specify the property to collect remarks by."),
43 cl::values(
44 clEnumValN(CountBy::REMARK, "remark-name",
45 "Counts individual remarks based on how many of the remark "
46 "exists."),
47 clEnumValN(CountBy::ARGUMENT, "arg",
48 "Counts based on the value each specified argument has. The "
49 "argument has to have a number value to be considered.")),
50 cl::init(Val: CountBy::REMARK), cl::sub(CountSub));
51static cl::opt<GroupBy> GroupByOpt(
52 "group-by", cl::desc("Specify the property to group remarks by."),
53 cl::values(
54 clEnumValN(
55 GroupBy::PER_SOURCE, "source",
56 "Display the count broken down by the filepath of each remark "
57 "emitted. Requires remarks to have DebugLoc information."),
58 clEnumValN(GroupBy::PER_FUNCTION, "function",
59 "Breakdown the count by function name."),
60 clEnumValN(
61 GroupBy::PER_FUNCTION_WITH_DEBUG_LOC, "function-with-loc",
62 "Breakdown the count by function name taking into consideration "
63 "the filepath info from the DebugLoc of the remark."),
64 clEnumValN(GroupBy::TOTAL, "total",
65 "Output the total number corresponding to the count for the "
66 "provided input file.")),
67 cl::init(Val: GroupBy::PER_SOURCE), cl::sub(CountSub));
68
69/// Look for matching argument with \p Key in \p Remark and return the parsed
70/// integer value or 0 if it is has no integer value.
71static unsigned getValForKey(StringRef Key, const Remark &Remark) {
72 auto *RemarkArg = find_if(Range: Remark.Args, P: [&Key](const Argument &Arg) {
73 return Arg.Key == Key && Arg.getValAsInt<unsigned>();
74 });
75 if (RemarkArg == Remark.Args.end())
76 return 0;
77 return *RemarkArg->getValAsInt<unsigned>();
78}
79
80Error ArgumentCounter::getAllMatchingArgumentsInRemark(
81 StringRef Buffer, ArrayRef<FilterMatcher> Arguments, Filters &Filter) {
82 auto MaybeParser = createRemarkParser(ParserFormat: InputFormat, Buf: Buffer);
83 if (!MaybeParser)
84 return MaybeParser.takeError();
85 auto &Parser = **MaybeParser;
86 auto MaybeRemark = Parser.next();
87 for (; MaybeRemark; MaybeRemark = Parser.next()) {
88 auto &Remark = **MaybeRemark;
89 // Only collect keys from remarks included in the filter.
90 if (!Filter.filterRemark(Remark))
91 continue;
92 for (auto &Key : Arguments) {
93 for (Argument Arg : Remark.Args)
94 if (Key.match(StringToMatch: Arg.Key) && Arg.getValAsInt<unsigned>())
95 ArgumentSetIdxMap.insert(KV: {Arg.Key, ArgumentSetIdxMap.size()});
96 }
97 }
98
99 auto E = MaybeRemark.takeError();
100 if (!E.isA<EndOfFileError>())
101 return E;
102 consumeError(Err: std::move(E));
103 return Error::success();
104}
105
106std::optional<std::string> Counter::getGroupByKey(const Remark &Remark) {
107 switch (Group) {
108 case GroupBy::PER_FUNCTION:
109 return Remark.FunctionName.str();
110 case GroupBy::TOTAL:
111 return "Total";
112 case GroupBy::PER_SOURCE:
113 case GroupBy::PER_FUNCTION_WITH_DEBUG_LOC:
114 if (!Remark.Loc.has_value())
115 return std::nullopt;
116
117 if (Group == GroupBy::PER_FUNCTION_WITH_DEBUG_LOC)
118 return Remark.Loc->SourceFilePath.str() + ":" + Remark.FunctionName.str();
119 return Remark.Loc->SourceFilePath.str();
120 }
121 llvm_unreachable("Fully covered switch above!");
122}
123
124void ArgumentCounter::collect(const Remark &Remark) {
125 SmallVector<unsigned, 4> Row(ArgumentSetIdxMap.size());
126 std::optional<std::string> GroupByKey = getGroupByKey(Remark);
127 // Early return if we don't have a value
128 if (!GroupByKey)
129 return;
130 auto GroupVal = *GroupByKey;
131 CountByKeysMap.insert(x: {GroupVal, Row});
132 for (auto [Key, Idx] : ArgumentSetIdxMap) {
133 auto Count = getValForKey(Key, Remark);
134 CountByKeysMap[GroupVal][Idx] += Count;
135 }
136}
137
138void RemarkCounter::collect(const Remark &Remark) {
139 if (std::optional<std::string> Key = getGroupByKey(Remark))
140 ++CountedByRemarksMap[*Key];
141}
142
143Error ArgumentCounter::print(StringRef OutputFileName) {
144 auto MaybeOF =
145 getOutputFileWithFlags(OutputFileName, Flags: sys::fs::OF_TextWithCRLF);
146 if (!MaybeOF)
147 return MaybeOF.takeError();
148
149 auto OF = std::move(*MaybeOF);
150 OF->os() << groupByToStr(GroupBy: Group) << ",";
151 OF->os() << llvm::interleaved(R: llvm::make_first_range(c&: ArgumentSetIdxMap), Separator: ",");
152 OF->os() << "\n";
153 for (auto [Header, CountVector] : CountByKeysMap) {
154 OF->os() << Header << ",";
155 OF->os() << llvm::interleaved(R: CountVector, Separator: ",");
156 OF->os() << "\n";
157 }
158 return Error::success();
159}
160
161Error RemarkCounter::print(StringRef OutputFileName) {
162 auto MaybeOF =
163 getOutputFileWithFlags(OutputFileName, Flags: sys::fs::OF_TextWithCRLF);
164 if (!MaybeOF)
165 return MaybeOF.takeError();
166
167 auto OF = std::move(*MaybeOF);
168 OF->os() << groupByToStr(GroupBy: Group) << ","
169 << "Count\n";
170 for (auto [Key, Count] : CountedByRemarksMap)
171 OF->os() << Key << "," << Count << "\n";
172 OF->keep();
173 return Error::success();
174}
175
176Error useCollectRemark(StringRef Buffer, Counter &Counter, Filters &Filter) {
177 // Create Parser.
178 auto MaybeParser = createRemarkParser(ParserFormat: InputFormat, Buf: Buffer);
179 if (!MaybeParser)
180 return MaybeParser.takeError();
181 auto &Parser = **MaybeParser;
182 auto MaybeRemark = Parser.next();
183 for (; MaybeRemark; MaybeRemark = Parser.next()) {
184 const Remark &Remark = **MaybeRemark;
185 if (Filter.filterRemark(Remark))
186 Counter.collect(Remark);
187 }
188
189 if (auto E = Counter.print(OutputFileName))
190 return E;
191 auto E = MaybeRemark.takeError();
192 if (!E.isA<EndOfFileError>())
193 return E;
194 consumeError(Err: std::move(E));
195 return Error::success();
196}
197
198static Error collectRemarks() {
199 // Create a parser for the user-specified input format.
200 auto MaybeBuf = getInputMemoryBuffer(InputFileName);
201 if (!MaybeBuf)
202 return MaybeBuf.takeError();
203 StringRef Buffer = (*MaybeBuf)->getBuffer();
204 auto MaybeFilter = getRemarkFilters();
205 if (!MaybeFilter)
206 return MaybeFilter.takeError();
207 auto &Filter = *MaybeFilter;
208 if (CountByOpt == CountBy::REMARK) {
209 RemarkCounter RC(GroupByOpt);
210 if (auto E = useCollectRemark(Buffer, Counter&: RC, Filter))
211 return E;
212 } else if (CountByOpt == CountBy::ARGUMENT) {
213 SmallVector<FilterMatcher, 4> ArgumentsVector;
214 if (!Keys.empty()) {
215 for (auto &Key : Keys)
216 ArgumentsVector.push_back(Elt: FilterMatcher::createExact(Filter: Key));
217 } else if (!RKeys.empty())
218 for (auto Key : RKeys) {
219 auto FM = FilterMatcher::createRE(Filter: Key, Arg: RKeys);
220 if (!FM)
221 return FM.takeError();
222 ArgumentsVector.push_back(Elt: std::move(*FM));
223 }
224 else
225 ArgumentsVector.push_back(Elt: FilterMatcher::createAny());
226
227 Expected<ArgumentCounter> AC = ArgumentCounter::createArgumentCounter(
228 Group: GroupByOpt, Arguments: ArgumentsVector, Buffer, Filter);
229 if (!AC)
230 return AC.takeError();
231 if (auto E = useCollectRemark(Buffer, Counter&: *AC, Filter))
232 return E;
233 }
234 return Error::success();
235}
236
237static CommandRegistration CountReg(&CountSub, collectRemarks);
238