[Remarks] Introduce count subcommand for llvm-remarkutil. (#66214)

This tool is a generic remark counter reporting count based on specified
properties. The counter can be used to count remarks individually and
filter them based on name, type and pass or count using remark
arguments.
This commit is contained in:
Zain Jaffal 2023-10-12 16:03:39 +01:00 committed by GitHub
parent ab6a66dbec
commit 31c2cf1136
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 914 additions and 0 deletions

View File

@ -110,6 +110,80 @@ if `--use-debug-loc` is passed then the CSV will include the source path, line n
Source,Function,Count
path:line:column,foo,3
.. _count_subcommand:
count
~~~~~
..program:: llvm-remarkutil count
USAGE: :program:`llvm-remarkutil` count [*options*] <input file>
Summary
^^^^^^^
:program:`llvm-remarkutil count` counts `remarks <https://llvm.org/docs/Remarks.html>` based on specified properties.
By default the tool counts remarks based on how many occour in a source file or function or total for the generated remark file.
The tool also supports collecting count based on specific remark arguments. The specified arguments should have an integer value to be able to report a count.
The tool contains utilities to filter the remark count based on remark name, pass name, argument value and remark type.
OPTIONS
-------
.. option:: --parser=<yaml|bitstream>
Select the type of input remark parser. Required.
* ``yaml``: The tool will parse YAML remarks.
* ``bitstream``: The tool will parse bitstream remarks.
.. option:: --count-by<value>
Select option to collect remarks by.
* ``remark-name``: count how many individual remarks exist.
* ``arg``: count remarks based on specified arguments passed by --(r)args. The argument value must be a number.
.. option:: --group-by=<value>
group count of remarks by property.
* ``source``: Count will be collected per source path. Remarks with no debug location will not be counted.
* ``function``: Count is collected per function.
* ``function-with-loc``: Count is collected per function per source. Remarks with no debug location will not be counted.
* ``Total``: Report a count for the provided remark file.
.. option:: --args[=arguments]
If `count-by` is set to `arg` this flag can be used to collect from specified remark arguments represented as a comma seperated string.
The arguments must have a numeral value to be able to count remarks by
.. option:: --rargs[=arguments]
If `count-by` is set to `arg` this flag can be used to collect from specified remark arguments using regular expression.
The arguments must have a numeral value to be able to count remarks by
.. option:: --pass-name[=<string>]
Filter count by pass name.
.. option:: --rpass-name[=<string>]
Filter count by pass name using regular expressions.
.. option:: --remark-name[=<string>]
Filter count by remark name.
.. option:: --rremark-name[=<string>]
Filter count by remark name using regular expressions.
.. option:: --filter-arg-by[=<string>]
Filter count by argument value.
.. option:: --rfilter-arg-by[=<string>]
Filter count by argument value using regular expressions.
.. option:: --remark-type=<value>
Filter remarks by type with the following options.
* ``unknown``
* ``passed``
* ``missed``
* ``analysis``
* ``analysis-fp-commute``
* ``analysis-aliasing``
* ``failure``
.. _size-diff_subcommand:
size-diff

View File

@ -52,6 +52,10 @@ struct Argument {
/// Implement operator<< on Argument.
void print(raw_ostream &OS) const;
/// Return the value of argument as int.
std::optional<int> getValAsInt() const;
/// Check if the argument value can be parsed as int.
bool isValInt() const;
};
// Create wrappers for C Binding types (see CBindingWrapping.h).

View File

@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "llvm/Remarks/Remark.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/ArrayRef.h"
#include <optional>
@ -25,6 +26,16 @@ std::string Remark::getArgsAsMsg() const {
return OS.str();
}
/// Returns the value of a specified key parsed from StringRef.
std::optional<int> Argument::getValAsInt() const {
APInt KeyVal;
if (Val.getAsInteger(10, KeyVal))
return std::nullopt;
return KeyVal.getSExtValue();
}
bool Argument::isValInt() const { return getValAsInt().has_value(); }
void RemarkLocation::print(raw_ostream &OS) const {
OS << "{ "
<< "File: " << SourceFilePath << ", Line: " << SourceLine

View File

@ -1,7 +1,14 @@
RUN: llvm-remarkutil annotation-count --use-debug-loc --parser=yaml --annotation-type=remark %p/Inputs/annotation-count-with-dbg-loc.yaml | FileCheck %s
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/annotation-count-with-dbg-loc.yaml | llvm-remarkutil annotation-count --use-debug-loc --parser=bitstream --annotation-type=remark | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=function-with-loc --remark-name="AnnotationSummary" %p/Inputs/annotation-count-with-dbg-loc.yaml | FileCheck %s --check-prefix=COUNT-CHECK
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/annotation-count-with-dbg-loc.yaml | llvm-remarkutil count --parser=bitstream --count-by=arg --group-by=function-with-loc --remark-name="AnnotationSummary" | FileCheck %s --check-prefix=COUNT-CHECK
; CHECK-LABEL: Source,Function,Count
; CHECK: path/to/anno.c:1:2,func1,1
; CHECK: path/to/anno2.c:1:2,func2,2
; CHECK: path/to/anno3.c:1:2,func3,3
; COUNT-CHECK-LABEL: FuctionWithDebugLoc,count
; COUNT-CHECK: path/to/anno.c:func1,1
; COUNT-CHECK: path/to/anno2.c:func2,2
; COUNT-CHECK: path/to/anno3.c:func3,3

View File

@ -1,7 +1,14 @@
RUN: llvm-remarkutil annotation-count --parser=yaml --annotation-type=remark %p/Inputs/annotation-count.yaml | FileCheck %s
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/annotation-count.yaml | llvm-remarkutil annotation-count --parser=bitstream --annotation-type=remark | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=function --remark-name="AnnotationSummary" %p/Inputs/annotation-count.yaml | FileCheck %s --check-prefix=COUNT-CHECK
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/annotation-count.yaml | llvm-remarkutil count --parser=bitstream --count-by=arg --group-by=function --remark-name="AnnotationSummary" | FileCheck %s --check-prefix=COUNT-CHECK
; CHECK-LABEL: Function,Count
; CHECK: func1,1
; CHECK: func2,2
; CHECK: func3,3
; COUNT-CHECK-LABEL: Function,count
; COUNT-CHECK: func1,1
; COUNT-CHECK: func2,2
; COUNT-CHECK: func3,3

View File

@ -1,4 +1,6 @@
RUN: not llvm-remarkutil bitstream2yaml %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
RUN: not llvm-remarkutil instruction-count --parser=bitstream %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
RUN: not llvm-remarkutil annotation-count --parser=bitstream --annotation-type=remark %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
RUN: not llvm-remarkutil count --parser=bitstream %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
CHECK: error: Unknown magic number: expecting RMRK, got --- .

View File

@ -1,4 +1,6 @@
RUN: not llvm-remarkutil yaml2bitstream %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
RUN: not llvm-remarkutil instruction-count --parser=yaml %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
RUN: not llvm-remarkutil annotation-count --parser=yaml --annotation-type=remark %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
RUN: not llvm-remarkutil count --parser=yaml %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
CHECK: error: Type, Pass, Name or Function missing

View File

@ -0,0 +1,43 @@
--- !Analysis
Pass: generic-remarks-pass
Name: Remark
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func1
Args:
- count1: '1'
- count2: '2'
- count3: '3'
- count4: '4'
- String: ' instructions with '
- type: remark
--- !Analysis
Pass: generic-remarks-pass
Name: Remark2
DebugLoc: { File: path/to/anno2.c, Line: 1, Column: 2 }
Function: func1
Args:
- count1: '1'
- count2: '2'
- count3: '3'
- String: ' instructions with '
- type: remark
--- !Analysis
Pass: generic-remarks-pass
Name: Remark3
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func1
Args:
- count1: '1'
- String: ' instructions with '
- type: remark
--- !Analysis
Pass: generic-remarks-pass
Name: Remark
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func2
Args:
- count1: '1'
- count2: '2'
- count3: '3'
- String: ' instructions with '
- type: remark

View File

@ -0,0 +1,40 @@
--- !Analysis
Pass: generic-remarks-pass
Name: Remark
DebugLoc: { File: path/to/anno2.c, Line: 1, Column: 2 }
Function: func1
Args:
- count1: '1'
- String: ' instructions with '
- type: remark
--- !Analysis
Pass: generic-remarks-pass2
Name: Remark2
DebugLoc: { File: path/to/anno2.c, Line: 1, Column: 2 }
Function: func1
Args:
- count1: '1'
- count2: '2'
- count3: '3'
- String: ' instructions with '
- type: remark
--- !Missed
Pass: generic-remarks-pass
Name: Remark3
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func1
Args:
- count1: '1'
- String: ' instructions with '
- type: remark
--- !Passed
Pass: generic-remarks-pass
Name: Remark
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func1
Args:
- count1: '1'
- count2: '2'
- count3: '3'
- String: ' instructions with '
- type: remark

View File

@ -0,0 +1,54 @@
--- !Analysis
Pass: generic-remarks-pass
Name: Remark
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func1
Args:
- count: '1'
- String: ' instructions with '
- type: remark
--- !Missed
Pass: generic-remarks-pass
Name: Remark
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func1
Args:
- count: '3'
- String: ' instructions with '
- type: remark
--- !Passed
Pass: generic-remarks-pass
Name: Remark
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func2
Args:
- count: '3'
- String: ' instructions with '
- type: remark
--- !Analysis
Pass: generic-remarks-pass2
Name: Remark
DebugLoc: { File: path/to/anno3.c, Line: 1, Column: 2 }
Function: func1
Args:
- count: '3'
- String: ' instructions with '
- type: remark
--- !Analysis
Pass: generic-remarks-pass3
Name: Remark
DebugLoc: { File: path/to/anno.c, Line: 1, Column: 2 }
Function: func2
Args:
- count: '2'
- String: ' instructions with '
- type: remark
--- !Analysis
Pass: generic-remarks-pass4
Name: Remark
DebugLoc: { File: path/to/anno2.c, Line: 1, Column: 2 }
Function: func3
Args:
- count: '2'
- String: ' instructions with '
- type: remark

View File

@ -0,0 +1,20 @@
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=source %p/Inputs/remark-count-by.yaml | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=function %p/Inputs/remark-count-by.yaml | FileCheck %s --check-prefix=CHECKFUNC
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=function-with-loc %p/Inputs/remark-count-by.yaml | FileCheck %s --check-prefix=CHECKFUNCLOC
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=total %p/Inputs/remark-count-by.yaml | FileCheck %s --check-prefix=CHECKTOTAL
; CHECK-LABEL: Source,count1,count2,count3,count4
; CHECK: path/to/anno.c,3,4,6,4
; CHECK: path/to/anno2.c,1,2,3,0
; CHECKFUNC-LABEL: Function,count1,count2,count3,count4
; CHECKFUNC: func1,3,4,6,4
; CHECKFUNC: func2,1,2,3,0
; CHECKFUNCLOC-LABEL: FuctionWithDebugLoc,count1,count2,count3,count4
; CHECKFUNCLOC: path/to/anno.c:func1,2,2,3,4
; CHECKFUNCLOC: path/to/anno.c:func2,1,2,3,0
; CHECKFUNCLOC: path/to/anno2.c:func1,1,2,3,0
; CHECKTOTAL-LABEL: Total,count1,count2,count3,count4
; CHECKTOTAL: Total,4,6,9,4

View File

@ -0,0 +1,20 @@
RUN: llvm-remarkutil count --parser=yaml --count-by=remark-name --group-by=source %p/Inputs/remark-count-by.yaml | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --count-by=remark-name --group-by=function %p/Inputs/remark-count-by.yaml | FileCheck %s --check-prefix=CHECKFUNC
RUN: llvm-remarkutil count --parser=yaml --count-by=remark-name --group-by=function-with-loc %p/Inputs/remark-count-by.yaml | FileCheck %s --check-prefix=CHECKFUNCLOC
RUN: llvm-remarkutil count --parser=yaml --count-by=remark-name --group-by=total %p/Inputs/remark-count-by.yaml | FileCheck %s --check-prefix=CHECKTOTAL
; CHECK-LABEL: Source,Count
; CHECK: path/to/anno.c,3
; CHECK: path/to/anno2.c,1
; CHECKFUNC-LABEL: Function,Count
; CHECKFUNC: func1,3
; CHECKFUNC: func2,1
; CHECKFUNCLOC-LABEL: FuctionWithDebugLoc,Count
; CHECKFUNCLOC: path/to/anno.c:func1,2
; CHECKFUNCLOC: path/to/anno.c:func2,1
; CHECKFUNCLOC: path/to/anno2.c:func1,1
; CHECKTOTAL-LABEL: Total,Count
; CHECKTOTAL: Total,4

View File

@ -0,0 +1,10 @@
RUN: llvm-remarkutil count --parser=yaml --pass-name=generic-remarks-pass %p/Inputs/remark-filter-by.yaml | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --rpass-name=.* %p/Inputs/remark-filter-by.yaml | FileCheck %s --check-prefix=CHECKALL
; CHECK-LABEL: Source,Count
; CHECK: path/to/anno.c,2
; CHECK: path/to/anno2.c,1
; CHECKALL-LABEL: Source,Count
; CHECKALL: path/to/anno.c,2
; CHECKALL: path/to/anno2.c,2

View File

@ -0,0 +1,10 @@
RUN: llvm-remarkutil count --parser=yaml --remark-name=Remark %p/Inputs/remark-filter-by.yaml | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --rremark-name=R.* %p/Inputs/remark-filter-by.yaml | FileCheck %s --check-prefix=CHECKALL
; CHECK-LABEL: Source,Count
; CHECK: path/to/anno.c,1
; CHECK: path/to/anno2.c,1
; CHECKALL-LABEL: Source,Count
; CHECKALL: path/to/anno.c,2
; CHECKALL: path/to/anno2.c,2

View File

@ -0,0 +1,16 @@
RUN: llvm-remarkutil count --parser=yaml --remark-type=missed %p/Inputs/remark-filter-by.yaml | FileCheck %s --check-prefix=MISSED
RUN: llvm-remarkutil count --parser=yaml --remark-type=passed %p/Inputs/remark-filter-by.yaml | FileCheck %s --check-prefix=PASSED
RUN: llvm-remarkutil count --parser=yaml --remark-type=analysis %p/Inputs/remark-filter-by.yaml | FileCheck %s --check-prefix=ANALYSIS
RUN: llvm-remarkutil count --parser=yaml --remark-type=unknown %p/Inputs/remark-filter-by.yaml | FileCheck %s --check-prefix=UNKNOWN
; MISSED-LABEL: Source,Count
; MISSED: path/to/anno.c,1
; PASSED-LABEL: Source,Count
; PASSED: path/to/anno.c,1
; ANALYSIS-LABEL: Source,Count
; ANALYSIS: path/to/anno2.c,2
; UNKNOWN: Source,Count
; UNKNOWN-EMPTY:

View File

@ -0,0 +1,7 @@
RUN: llvm-remarkutil count --parser=yaml --group-by=function-with-loc %p/Inputs/remark-group-by.yaml | FileCheck %s
; CHECK-LABEL: FuctionWithDebugLoc,Count
; CHECK: path/to/anno.c:func1,2
; CHECK: path/to/anno.c:func2,2
; CHECK: path/to/anno2.c:func3,1
; CHECK: path/to/anno3.c:func1,1

View File

@ -0,0 +1,7 @@
RUN: llvm-remarkutil count --parser=yaml --group-by=function %p/Inputs/remark-group-by.yaml | FileCheck %s
; CHECK-LABEL: Function,Count
; CHECK: func1,3
; CHECK: func2,2
; CHECK: func3,1

View File

@ -0,0 +1,6 @@
RUN: llvm-remarkutil count --parser=yaml --group-by=source %p/Inputs/remark-group-by.yaml | FileCheck %s
; CHECK-LABEL: Source,Count
; CHECK: path/to/anno.c,4
; CHECK: path/to/anno2.c,1
; CHECK: path/to/anno3.c,1

View File

@ -1,9 +1,11 @@
RUN: not llvm-remarkutil yaml2bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
RUN: not llvm-remarkutil instruction-count --parser=yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
RUN: not llvm-remarkutil annotation-count --parser=yaml --annotation-type=remark %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
RUN: not llvm-remarkutil count --parser=yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
RUN: llvm-remarkutil bitstream2yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=BITSTREAM2YAML
RUN: llvm-remarkutil instruction-count --parser=bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=SIZEBITSTREAM
RUN: llvm-remarkutil annotation-count --parser=bitstream --annotation-type=remark %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=ANNOTATIONBITSTREAM
RUN: llvm-remarkutil count --parser=bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=COUNTBITSTREAM
; YAMLPARSER: error: document root is not of mapping type.
@ -15,3 +17,6 @@ RUN: llvm-remarkutil annotation-count --parser=bitstream --annotation-type=remar
; ANNOTATIONBITSTREAM-LABEL: Function,Count
; ANNOTATIONBITSTREAM-EMPTY:
; COUNTBITSTREAM-LABEL: Source,Count
; COUNTBITSTREAM-EMPTY:

View File

@ -1,7 +1,14 @@
RUN: llvm-remarkutil instruction-count --use-debug-loc --parser=yaml %p/Inputs/instruction-count-with-dbg-loc.yaml | FileCheck %s
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/instruction-count-with-dbg-loc.yaml | llvm-remarkutil instruction-count --use-debug-loc --parser=bitstream | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=function-with-loc --remark-name="InstructionCount" %p/Inputs/instruction-count-with-dbg-loc.yaml | FileCheck %s --check-prefix=COUNT-CHECK
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/instruction-count-with-dbg-loc.yaml | llvm-remarkutil count --parser=bitstream --count-by=arg --group-by=function-with-loc --remark-name="InstructionCount" | FileCheck %s --check-prefix=COUNT-CHECK
; CHECK-LABEL: Source,Function,InstructionCount
; CHECK: path/to/inst.c:1:2,func1,1
; CHECK: path/to/inst2.c:1:2,func2,2
; CHECK: path/to/inst3.c:1:2,func3,3
; COUNT-CHECK-LABEL: FuctionWithDebugLoc,NumInstructions
; COUNT-CHECK: path/to/inst.c:func1,1
; COUNT-CHECK: path/to/inst2.c:func2,2
; COUNT-CHECK: path/to/inst3.c:func3,3

View File

@ -1,7 +1,14 @@
RUN: llvm-remarkutil instruction-count --parser=yaml %p/Inputs/instruction-count.yaml | FileCheck %s
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/instruction-count.yaml | llvm-remarkutil instruction-count --parser=bitstream | FileCheck %s
RUN: llvm-remarkutil count --parser=yaml --count-by=arg --group-by=function --remark-name="InstructionCount" %p/Inputs/instruction-count.yaml | FileCheck %s --check-prefix=COUNT-CHECK
RUN: llvm-remarkutil yaml2bitstream %p/Inputs/instruction-count.yaml | llvm-remarkutil count --parser=bitstream --count-by=arg --group-by=function --remark-name="InstructionCount" | FileCheck %s --check-prefix=COUNT-CHECK
; CHECK-LABEL: Function,InstructionCount
; CHECK: func1,1
; CHECK: func2,2
; CHECK: func3,3
; COUNT-CHECK-LABEL: Function,NumInstructions
; COUNT-CHECK: func1,1
; COUNT-CHECK: func2,2
; COUNT-CHECK: func3,3

View File

@ -7,6 +7,7 @@ set(LLVM_LINK_COMPONENTS
add_llvm_tool(llvm-remarkutil
RemarkConvert.cpp
RemarkCount.cpp
RemarkCounter.cpp
RemarkSizeDiff.cpp
RemarkUtil.cpp
RemarkUtilHelpers.cpp

View File

@ -0,0 +1,337 @@
//===- RemarkCounter.cpp --------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Generic tool to count remarks based on properties
//
//===----------------------------------------------------------------------===//
#include "RemarkCounter.h"
#include "RemarkUtilRegistry.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Regex.h"
using namespace llvm;
using namespace remarks;
using namespace llvm::remarkutil;
static cl::SubCommand CountSub("count",
"Collect remarks based on specified criteria.");
INPUT_FORMAT_COMMAND_LINE_OPTIONS(CountSub)
INPUT_OUTPUT_COMMAND_LINE_OPTIONS(CountSub)
static cl::list<std::string>
Keys("args", cl::desc("Specify remark argument/s to count by."),
cl::value_desc("arguments"), cl::sub(CountSub), cl::ValueOptional);
static cl::list<std::string> RKeys(
"rargs",
cl::desc(
"Specify remark argument/s to count (accepts regular expressions)."),
cl::value_desc("arguments"), cl::sub(CountSub), cl::ValueOptional);
static cl::opt<std::string>
RemarkNameOpt("remark-name",
cl::desc("Optional remark name to filter collection by."),
cl::ValueOptional, cl::sub(CountSub));
static cl::opt<std::string>
PassNameOpt("pass-name", cl::ValueOptional,
cl::desc("Optional remark pass name to filter collection by."),
cl::sub(CountSub));
static cl::opt<std::string> RemarkFilterArgByOpt(
"filter-arg-by", cl::desc("Optional remark arg to filter collection by."),
cl::ValueOptional, cl::sub(CountSub));
static cl::opt<std::string>
RemarkNameOptRE("rremark-name",
cl::desc("Optional remark name to filter collection by "
"(accepts regular expressions)."),
cl::ValueOptional, cl::sub(CountSub));
static cl::opt<std::string>
RemarkArgFilterOptRE("rfilter-arg-by",
cl::desc("Optional remark arg to filter collection by "
"(accepts regular expressions)."),
cl::sub(CountSub), cl::ValueOptional);
static cl::opt<std::string>
PassNameOptRE("rpass-name", cl::ValueOptional,
cl::desc("Optional remark pass name to filter collection "
"by (accepts regular expressions)."),
cl::sub(CountSub));
static cl::opt<Type> RemarkTypeOpt(
"remark-type", cl::desc("Optional remark type to filter collection by."),
cl::values(clEnumValN(Type::Unknown, "unknown", "UNKOWN"),
clEnumValN(Type::Passed, "passed", "PASSED"),
clEnumValN(Type::Missed, "missed", "MISSED"),
clEnumValN(Type::Analysis, "analysis", "ANALYSIS"),
clEnumValN(Type::AnalysisFPCommute, "analysis-fp-commute",
"ANALYSIS_FP_COMMUTE"),
clEnumValN(Type::AnalysisAliasing, "analysis-aliasing",
"ANALYSIS_ALIASING"),
clEnumValN(Type::Failure, "failure", "FAILURE")),
cl::init(Type::Failure), cl::sub(CountSub));
static cl::opt<CountBy> CountByOpt(
"count-by", cl::desc("Specify the property to collect remarks by."),
cl::values(
clEnumValN(CountBy::REMARK, "remark-name",
"Counts individual remarks based on how many of the remark "
"exists."),
clEnumValN(CountBy::ARGUMENT, "arg",
"Counts based on the value each specified argument has. The "
"argument has to have a number value to be considered.")),
cl::init(CountBy::REMARK), cl::sub(CountSub));
static cl::opt<GroupBy> GroupByOpt(
"group-by", cl::desc("Specify the property to group remarks by."),
cl::values(
clEnumValN(
GroupBy::PER_SOURCE, "source",
"Display the count broken down by the filepath of each remark "
"emitted. Requires remarks to have DebugLoc information."),
clEnumValN(GroupBy::PER_FUNCTION, "function",
"Breakdown the count by function name."),
clEnumValN(
GroupBy::PER_FUNCTION_WITH_DEBUG_LOC, "function-with-loc",
"Breakdown the count by function name taking into consideration "
"the filepath info from the DebugLoc of the remark."),
clEnumValN(GroupBy::TOTAL, "total",
"Output the total number corresponding to the count for the "
"provided input file.")),
cl::init(GroupBy::PER_SOURCE), cl::sub(CountSub));
/// Look for matching argument with \p Key in \p Remark and return the parsed
/// integer value or 0 if it is has no integer value.
static unsigned getValForKey(StringRef Key, const Remark &Remark) {
auto *RemarkArg = find_if(Remark.Args, [&Key](const Argument &Arg) {
return Arg.Key == Key && Arg.isValInt();
});
if (RemarkArg == Remark.Args.end())
return 0;
return *RemarkArg->getValAsInt();
}
Error Filters::regexArgumentsValid() {
if (RemarkNameFilter && RemarkNameFilter->IsRegex)
if (auto E = checkRegex(RemarkNameFilter->FilterRE))
return E;
if (PassNameFilter && PassNameFilter->IsRegex)
if (auto E = checkRegex(PassNameFilter->FilterRE))
return E;
if (ArgFilter && ArgFilter->IsRegex)
if (auto E = checkRegex(ArgFilter->FilterRE))
return E;
return Error::success();
}
bool Filters::filterRemark(const Remark &Remark) {
if (RemarkNameFilter && !RemarkNameFilter->match(Remark.RemarkName))
return false;
if (PassNameFilter && !PassNameFilter->match(Remark.PassName))
return false;
if (RemarkTypeFilter)
return *RemarkTypeFilter == Remark.RemarkType;
if (ArgFilter) {
if (!any_of(Remark.Args,
[this](Argument Arg) { return ArgFilter->match(Arg.Val); }))
return false;
}
return true;
}
Error ArgumentCounter::getAllMatchingArgumentsInRemark(
StringRef Buffer, ArrayRef<FilterMatcher> Arguments, Filters &Filter) {
auto MaybeParser = createRemarkParser(InputFormat, Buffer);
if (!MaybeParser)
return MaybeParser.takeError();
auto &Parser = **MaybeParser;
auto MaybeRemark = Parser.next();
for (; MaybeRemark; MaybeRemark = Parser.next()) {
auto &Remark = **MaybeRemark;
// Only collect keys from remarks included in the filter.
if (!Filter.filterRemark(Remark))
continue;
for (auto &Key : Arguments) {
for (Argument Arg : Remark.Args)
if (Key.match(Arg.Key) && Arg.isValInt())
ArgumentSetIdxMap.insert({Arg.Key, ArgumentSetIdxMap.size()});
}
}
auto E = MaybeRemark.takeError();
if (!E.isA<EndOfFileError>())
return E;
consumeError(std::move(E));
return Error::success();
}
std::optional<std::string> Counter::getGroupByKey(const Remark &Remark) {
switch (GroupBy) {
case GroupBy::PER_FUNCTION:
return Remark.FunctionName.str();
case GroupBy::TOTAL:
return "Total";
case GroupBy::PER_SOURCE:
case GroupBy::PER_FUNCTION_WITH_DEBUG_LOC:
if (!Remark.Loc.has_value())
return std::nullopt;
if (GroupBy == GroupBy::PER_FUNCTION_WITH_DEBUG_LOC)
return Remark.Loc->SourceFilePath.str() + ":" + Remark.FunctionName.str();
return Remark.Loc->SourceFilePath.str();
}
}
void ArgumentCounter::collect(const Remark &Remark) {
SmallVector<unsigned, 4> Row(ArgumentSetIdxMap.size());
std::optional<std::string> GroupByKey = getGroupByKey(Remark);
// Early return if we don't have a value
if (!GroupByKey)
return;
auto GroupVal = *GroupByKey;
CountByKeysMap.insert({GroupVal, Row});
for (auto [Key, Idx] : ArgumentSetIdxMap) {
auto Count = getValForKey(Key, Remark);
CountByKeysMap[GroupVal][Idx] += Count;
}
}
void RemarkCounter::collect(const Remark &Remark) {
std::optional<std::string> Key = getGroupByKey(Remark);
if (!Key.has_value())
return;
auto Iter = CountedByRemarksMap.insert({*Key, 1});
if (!Iter.second)
Iter.first->second += 1;
}
Error ArgumentCounter::print(StringRef OutputFileName) {
auto MaybeOF =
getOutputFileWithFlags(OutputFileName, sys::fs::OF_TextWithCRLF);
if (!MaybeOF)
return MaybeOF.takeError();
auto OF = std::move(*MaybeOF);
OF->os() << groupByToStr(GroupBy) << ",";
unsigned Idx = 0;
for (auto [Key, _] : ArgumentSetIdxMap) {
OF->os() << Key;
if (Idx != ArgumentSetIdxMap.size() - 1)
OF->os() << ",";
Idx++;
}
OF->os() << "\n";
for (auto [Header, CountVector] : CountByKeysMap) {
OF->os() << Header << ",";
unsigned Idx = 0;
for (auto Count : CountVector) {
OF->os() << Count;
if (Idx != ArgumentSetIdxMap.size() - 1)
OF->os() << ",";
Idx++;
}
OF->os() << "\n";
}
return Error::success();
}
Error RemarkCounter::print(StringRef OutputFileName) {
auto MaybeOF =
getOutputFileWithFlags(OutputFileName, sys::fs::OF_TextWithCRLF);
if (!MaybeOF)
return MaybeOF.takeError();
auto OF = std::move(*MaybeOF);
OF->os() << groupByToStr(GroupBy) << ","
<< "Count\n";
for (auto [Key, Count] : CountedByRemarksMap)
OF->os() << Key << "," << Count << "\n";
OF->keep();
return Error::success();
}
Expected<Filters> getRemarkFilter() {
// Create Filter properties.
std::optional<FilterMatcher> RemarkNameFilter;
std::optional<FilterMatcher> PassNameFilter;
std::optional<FilterMatcher> RemarkArgFilter;
std::optional<Type> RemarkType;
if (!RemarkNameOpt.empty())
RemarkNameFilter = {RemarkNameOpt, false};
else if (!RemarkNameOptRE.empty())
RemarkNameFilter = {RemarkNameOptRE, true};
if (!PassNameOpt.empty())
PassNameFilter = {PassNameOpt, false};
else if (!PassNameOptRE.empty())
PassNameFilter = {PassNameOptRE, true};
if (RemarkTypeOpt != Type::Failure)
RemarkType = RemarkTypeOpt;
if (!RemarkFilterArgByOpt.empty())
RemarkArgFilter = {RemarkFilterArgByOpt, false};
else if (!RemarkArgFilterOptRE.empty())
RemarkArgFilter = {RemarkArgFilterOptRE, true};
// Create RemarkFilter.
return Filters::createRemarkFilter(std::move(RemarkNameFilter),
std::move(PassNameFilter),
std::move(RemarkArgFilter), RemarkType);
}
Error useCollectRemark(StringRef Buffer, Counter &Counter, Filters &Filter) {
// Create Parser.
auto MaybeParser = createRemarkParser(InputFormat, Buffer);
if (!MaybeParser)
return MaybeParser.takeError();
auto &Parser = **MaybeParser;
auto MaybeRemark = Parser.next();
for (; MaybeRemark; MaybeRemark = Parser.next()) {
const Remark &Remark = **MaybeRemark;
if (Filter.filterRemark(Remark))
Counter.collect(Remark);
}
if (auto E = Counter.print(OutputFileName))
return E;
auto E = MaybeRemark.takeError();
if (!E.isA<EndOfFileError>())
return E;
consumeError(std::move(E));
return Error::success();
}
static Error collectRemarks() {
// Create a parser for the user-specified input format.
auto MaybeBuf = getInputMemoryBuffer(InputFileName);
if (!MaybeBuf)
return MaybeBuf.takeError();
StringRef Buffer = (*MaybeBuf)->getBuffer();
auto MaybeFilter = getRemarkFilter();
if (!MaybeFilter)
return MaybeFilter.takeError();
auto &Filter = *MaybeFilter;
if (CountByOpt == CountBy::REMARK) {
RemarkCounter RC(GroupByOpt);
if (auto E = useCollectRemark(Buffer, RC, Filter))
return E;
} else if (CountByOpt == CountBy::ARGUMENT) {
SmallVector<FilterMatcher, 4> ArgumentsVector;
if (!Keys.empty()) {
for (auto &Key : Keys)
ArgumentsVector.push_back({Key, false});
} else if (!RKeys.empty())
for (auto Key : RKeys)
ArgumentsVector.push_back({Key, true});
else
ArgumentsVector.push_back({".*", true});
Expected<ArgumentCounter> AC = ArgumentCounter::createArgumentCounter(
GroupByOpt, ArgumentsVector, Buffer, Filter);
if (!AC)
return AC.takeError();
if (auto E = useCollectRemark(Buffer, *AC, Filter))
return E;
}
return Error::success();
}
static CommandRegistration CountReg(&CountSub, collectRemarks);

View File

@ -0,0 +1,217 @@
//===- RemarkCounter.h ----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Generic tool to count remarks based on properties
//
//===----------------------------------------------------------------------===//
#ifndef TOOLS_LLVM_REMARKCOUNTER_H
#define TOOLS_LLVM_REMARKCOUNTER_H
#include "RemarkUtilHelpers.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/Support/Regex.h"
#include <list>
namespace llvm {
namespace remarks {
/// Collect remarks by counting the existance of a remark or by looking through
/// the keys and summing through the total count.
enum class CountBy { REMARK, ARGUMENT };
/// Summarize the count by either emitting one count for the remark file, or
/// grouping the count by source file or by function name.
enum class GroupBy {
TOTAL,
PER_SOURCE,
PER_FUNCTION,
PER_FUNCTION_WITH_DEBUG_LOC
};
/// Convert \p GroupBy to a std::string.
inline std::string groupByToStr(GroupBy GroupBy) {
switch (GroupBy) {
default:
return "Total";
case GroupBy::PER_FUNCTION:
return "Function";
case GroupBy::PER_FUNCTION_WITH_DEBUG_LOC:
return "FuctionWithDebugLoc";
case GroupBy::PER_SOURCE:
return "Source";
}
}
/// Filter object which can be either a string or a regex to match with the
/// remark properties.
struct FilterMatcher {
Regex FilterRE;
std::string FilterStr;
bool IsRegex;
FilterMatcher(std::string Filter, bool IsRegex) : IsRegex(IsRegex) {
if (IsRegex)
FilterRE = Regex(Filter);
else
FilterStr = Filter;
}
bool match(StringRef StringToMatch) const {
if (IsRegex)
return FilterRE.match(StringToMatch);
return FilterStr == StringToMatch.trim().str();
}
};
/// Filter out remarks based on remark properties based on name, pass name,
/// argument and type.
struct Filters {
std::optional<FilterMatcher> RemarkNameFilter;
std::optional<FilterMatcher> PassNameFilter;
std::optional<FilterMatcher> ArgFilter;
std::optional<Type> RemarkTypeFilter;
/// Returns a filter object if all the arguments provided are valid regex
/// types otherwise return an error.
static Expected<Filters>
createRemarkFilter(std::optional<FilterMatcher> RemarkNameFilter,
std::optional<FilterMatcher> PassNameFilter,
std::optional<FilterMatcher> ArgFilter,
std::optional<Type> RemarkTypeFilter) {
Filters Filter;
Filter.RemarkNameFilter = std::move(RemarkNameFilter);
Filter.PassNameFilter = std::move(PassNameFilter);
Filter.ArgFilter = std::move(ArgFilter);
Filter.RemarkTypeFilter = std::move(RemarkTypeFilter);
if (auto E = Filter.regexArgumentsValid())
return E;
return Filter;
}
/// Returns true if \p Remark satisfies all the provided filters.
bool filterRemark(const Remark &Remark);
private:
/// Check if arguments can be parsed as valid regex types.
Error regexArgumentsValid();
};
/// Convert Regex string error to an error object.
inline Error checkRegex(const Regex &Regex) {
std::string Error;
if (!Regex.isValid(Error))
return createStringError(make_error_code(std::errc::invalid_argument),
Twine("Regex: ", Error));
return Error::success();
}
/// Abstract counter class used to define the general required methods for
/// counting a remark.
struct Counter {
GroupBy GroupBy;
Counter(){};
Counter(enum GroupBy GroupBy) : GroupBy(GroupBy) {}
/// Obtain the field for collecting remark info based on how we are
/// collecting. Remarks are grouped by FunctionName, Source, Source and
/// Function or collect by file.
std::optional<std::string> getGroupByKey(const Remark &Remark);
/// Collect count information from \p Remark organized based on \p GroupBy
/// property.
virtual void collect(const Remark &) = 0;
/// Output the final count to the file \p OutputFileName
virtual Error print(StringRef OutputFileName) = 0;
virtual ~Counter() = default;
};
/// Count remarks based on the provided \p Keys argument and summing up the
/// value for each matching key organized by source, function or reporting a
/// total for the specified remark file.
/// Reporting count grouped by source:
///
/// | source | key1 | key2 | key3 |
/// |---------------|------|------|------|
/// | path/to/file1 | 0 | 1 | 3 |
/// | path/to/file2 | 1 | 0 | 2 |
/// | path/to/file3 | 2 | 3 | 1 |
///
/// Reporting count grouped by function:
///
/// | Function | key1 | key2 | key3 |
/// |---------------|------|------|------|
/// | function1 | 0 | 1 | 3 |
/// | function2 | 1 | 0 | 2 |
/// | function3 | 2 | 3 | 1 |
struct ArgumentCounter : Counter {
/// The internal object to keep the count for the remarks. The first argument
/// corresponds to the property we are collecting for this can be either a
/// source or function. The second argument is a row of integers where each
/// item in the row is the count for a specified key.
std::map<std::string, SmallVector<unsigned, 4>> CountByKeysMap;
/// A set of all the remark argument found in the remark file. The second
/// argument is the index of each of those arguments which can be used in
/// `CountByKeysMap` to fill count information for that argument.
MapVector<StringRef, unsigned> ArgumentSetIdxMap;
/// Create an argument counter. If the provided \p Arguments represent a regex
/// vector then we need to check that the provided regular expressions are
/// valid if not we return an Error.
static Expected<ArgumentCounter>
createArgumentCounter(enum GroupBy GroupBy, ArrayRef<FilterMatcher> Arguments,
StringRef Buffer, Filters &Filter) {
ArgumentCounter AC;
AC.GroupBy = GroupBy;
for (auto &Arg : Arguments) {
if (Arg.IsRegex) {
if (auto E = checkRegex(Arg.FilterRE))
return E;
}
}
if (auto E = AC.getAllMatchingArgumentsInRemark(Buffer, Arguments, Filter))
return E;
return AC;
}
/// Update the internal count map based on the remark integer arguments that
/// correspond the the user specified argument keys to collect for.
void collect(const Remark &) override;
/// Print a CSV table consisting of an index which is specified by \p
/// `GroupBy` and can be a function name, source file name or function name
/// with the full source path and columns of user specified remark arguments
/// to collect the count for.
Error print(StringRef OutputFileName) override;
private:
/// collect all the arguments that match the list of \p Arguments provided by
/// parsing through \p Buffer of remarks and filling \p ArgumentSetIdxMap
/// acting as a row for for all the keys that we are interested in collecting
/// information for.
Error getAllMatchingArgumentsInRemark(StringRef Buffer,
ArrayRef<FilterMatcher> Arguments,
Filters &Filter);
};
/// Collect remarks based by counting the existance of individual remarks. The
/// reported table will be structured based on the provided \p GroupBy argument
/// by reporting count for functions, source or total count for the provided
/// remark file.
struct RemarkCounter : Counter {
std::map<std::string, unsigned> CountedByRemarksMap;
RemarkCounter(enum GroupBy GroupBy) : Counter(GroupBy) {}
/// Advance the internal map count broken by \p GroupBy when
/// seeing \p Remark.
void collect(const Remark &) override;
/// Print a CSV table consisting of an index which is specified by \p
/// `GroupBy` and can be a function name, source file name or function name
/// with the full source path and a counts column corresponding to the count
/// of each individual remark at th index.
Error print(StringRef OutputFileName) override;
};
} // namespace remarks
} // namespace llvm
#endif // TOOLS_LLVM_REMARKCOUNTER_H