| 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 | REMARK_FILTER_COMMAND_LINE_OPTIONS(CountSub) |
| 29 | |
| 30 | REMARK_FILTER_SETUP_FUNC() |
| 31 | |
| 32 | static 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); |
| 35 | static 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 | |
| 41 | static 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)); |
| 51 | static 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. |
| 71 | static unsigned (StringRef Key, const Remark &) { |
| 72 | auto * = 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 | |
| 80 | Error ArgumentCounter::( |
| 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 = Parser.next(); |
| 87 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
| 88 | auto & = **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 | |
| 106 | std::optional<std::string> Counter::(const 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 | |
| 124 | void ArgumentCounter::(const 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 | |
| 138 | void RemarkCounter::(const Remark &) { |
| 139 | if (std::optional<std::string> Key = getGroupByKey(Remark)) |
| 140 | ++CountedByRemarksMap[*Key]; |
| 141 | } |
| 142 | |
| 143 | Error ArgumentCounter::(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 | |
| 161 | Error RemarkCounter::(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 | |
| 176 | Error (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 = Parser.next(); |
| 183 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
| 184 | const 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 | |
| 198 | static Error () { |
| 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 | |
| 237 | static CommandRegistration CountReg(&CountSub, collectRemarks); |
| 238 | |