| 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 | ("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> ( |
| 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 | ("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 | ("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> ( |
| 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)); |
| 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(Val: 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(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. |
| 106 | static unsigned (StringRef Key, const Remark &) { |
| 107 | auto * = 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 | |
| 115 | bool Filters::(const 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 | |
| 130 | Error ArgumentCounter::( |
| 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 = Parser.next(); |
| 137 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
| 138 | auto & = **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 | |
| 156 | std::optional<std::string> Counter::(const 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 | |
| 174 | void ArgumentCounter::(const 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 | |
| 188 | void RemarkCounter::(const Remark &) { |
| 189 | if (std::optional<std::string> Key = getGroupByKey(Remark)) |
| 190 | ++CountedByRemarksMap[*Key]; |
| 191 | } |
| 192 | |
| 193 | Error ArgumentCounter::(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 | |
| 211 | Error RemarkCounter::(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 | |
| 226 | Expected<Filters> () { |
| 227 | // Create Filter properties. |
| 228 | auto = |
| 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 = FilterMatcher::createExactOrRE( |
| 239 | ExactArg: RemarkFilterArgByOpt, REArg: RemarkArgFilterOptRE); |
| 240 | if (!MaybeRemarkArgFilter) |
| 241 | return MaybeRemarkArgFilter.takeError(); |
| 242 | |
| 243 | std::optional<Type> ; |
| 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 | |
| 253 | Error (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 = Parser.next(); |
| 260 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
| 261 | const 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 | |
| 275 | static Error () { |
| 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 | |
| 314 | static CommandRegistration CountReg(&CountSub, collectRemarks); |
| 315 | |