Index: clang-tidy/ClangTidy.h =================================================================== --- clang-tidy/ClangTidy.h +++ clang-tidy/ClangTidy.h @@ -228,7 +228,9 @@ const tooling::CompilationDatabase &Compilations, ArrayRef InputFiles, llvm::IntrusiveRefCntPtr BaseFS, - bool EnableCheckProfile = false); + bool EnableCheckProfile = false, + llvm::StringRef StoreCheckProfile = StringRef(), + llvm::StringRef StoreCheckProfileElide = StringRef()); // FIXME: This interface will need to be significantly extended to be useful. // FIXME: Implement confidence levels for displaying/fixing errors. Index: clang-tidy/ClangTidy.cpp =================================================================== --- clang-tidy/ClangTidy.cpp +++ clang-tidy/ClangTidy.cpp @@ -362,7 +362,8 @@ std::unique_ptr Profiling; if (Context.getEnableProfiling()) { - Profiling = llvm::make_unique(); + Profiling = llvm::make_unique( + Context.getStoreProfileFilename()); FinderOptions.CheckProfiling.emplace(Profiling->Records); } @@ -483,7 +484,8 @@ const CompilationDatabase &Compilations, ArrayRef InputFiles, llvm::IntrusiveRefCntPtr BaseFS, - bool EnableCheckProfile) { + bool EnableCheckProfile, llvm::StringRef StoreCheckProfile, + llvm::StringRef StoreCheckProfileElide) { ClangTool Tool(Compilations, InputFiles, std::make_shared(), BaseFS); @@ -524,6 +526,7 @@ Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter); Tool.appendArgumentsAdjuster(PluginArgumentsRemover); Context.setEnableProfiling(EnableCheckProfile); + Context.setStoreProfile(StoreCheckProfile, StoreCheckProfileElide); ClangTidyDiagnosticConsumer DiagConsumer(Context); Index: clang-tidy/ClangTidyDiagnosticConsumer.h =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.h +++ clang-tidy/ClangTidyDiagnosticConsumer.h @@ -168,6 +168,10 @@ void setEnableProfiling(bool Profile); bool getEnableProfiling() const { return Profile; } + /// \brief Control storage of profile date. + void setStoreProfile(StringRef ProfilePrefix, StringRef PrefixElide); + llvm::Optional getStoreProfileFilename() const; + /// \brief Should be called when starting to process new translation unit. void setCurrentBuildDirectory(StringRef BuildDirectory) { CurrentBuildDirectory = BuildDirectory; @@ -209,6 +213,8 @@ llvm::DenseMap CheckNamesByDiagnosticID; bool Profile; + std::string ProfilePrefix; + std::string ProfilePrefixElide; }; /// \brief A diagnostic consumer that turns each \c Diagnostic into a Index: clang-tidy/ClangTidyDiagnosticConsumer.cpp =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -23,6 +23,7 @@ #include "clang/Frontend/DiagnosticRenderer.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/Path.h" #include #include using namespace clang; @@ -235,6 +236,28 @@ void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; } +void ClangTidyContext::setStoreProfile(StringRef Prefix, StringRef Elide) { + ProfilePrefix = Prefix; + ProfilePrefixElide = Elide; +} + +llvm::Optional ClangTidyContext::getStoreProfileFilename() const { + if (ProfilePrefix.empty()) + return llvm::None; + + SmallString<256> OutputFile(CurrentFile); + + // If the current file starts with ProfilePrefixElide, drop that prefix. + llvm::sys::path::replace_path_prefix(OutputFile, ProfilePrefixElide, ""); + + // Now, append the current file name (after possibly eliding prefix) + // to the output prefix. + SmallString<256> FinalName(ProfilePrefix); + llvm::sys::path::append(FinalName, OutputFile); + + return Twine(FinalName + ".csv").str(); +} + bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const { assert(CheckFilter != nullptr); return CheckFilter->contains(CheckName); Index: clang-tidy/ClangTidyProfiling.h =================================================================== --- clang-tidy/ClangTidyProfiling.h +++ clang-tidy/ClangTidyProfiling.h @@ -10,10 +10,12 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H +#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include #include #include @@ -21,17 +23,28 @@ namespace tidy { class ClangTidyProfiling { + llvm::Optional StoreCSVTo; + // Time is first to allow for sorting by it. std::vector> Timers; + + // The Total needs to be here, not in preprocess(). llvm::TimeRecord Total; void preprocess(); - void printProfileData(llvm::raw_ostream &OS) const; + void printUserFriendlyTable(llvm::raw_ostream &OS) const; + void printCSVTable(llvm::raw_ostream &OS) const; + + void storeProfileData() const; public: llvm::StringMap Records; + ClangTidyProfiling() = default; + + ClangTidyProfiling(llvm::Optional StoreCSVTo); + ~ClangTidyProfiling(); }; Index: clang-tidy/ClangTidyProfiling.cpp =================================================================== --- clang-tidy/ClangTidyProfiling.cpp +++ clang-tidy/ClangTidyProfiling.cpp @@ -9,27 +9,41 @@ #include "ClangTidyProfiling.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include +#include #define DEBUG_TYPE "clang-tidy-profiling" +namespace { +static const char *const TotalStr = "Total"; +} + namespace clang { namespace tidy { void ClangTidyProfiling::preprocess() { + assert(Timers.empty() && "Don't preprocess twice"); + // Convert from a insertion-friendly map to sort-friendly vector. - Timers.clear(); - Timers.reserve(Records.size()); + Timers.reserve(1UL + Records.size()); for (const auto &P : Records) { Timers.emplace_back(P.getValue(), P.getKey()); Total += P.getValue(); } - assert(Timers.size() == Records.size() && "Size mismatch after processing"); // We want the measurements to be sorted by decreasing time spent. - llvm::sort(Timers.begin(), Timers.end()); + llvm::sort(Timers.rbegin(), Timers.rend()); + + // And the total at the end. + Timers.emplace_back(Total, TotalStr); + assert(Timers.size() == 1UL + Records.size() && "Size mismatch afterwards"); } -void ClangTidyProfiling::printProfileData(llvm::raw_ostream &OS) const { +void ClangTidyProfiling::printUserFriendlyTable(llvm::raw_ostream &OS) const { std::string Line = "===" + std::string(73, '-') + "===\n"; OS << Line; @@ -45,20 +59,79 @@ OS << " --- Name ---\n"; // Loop through all of the timing data, printing it out. - for (auto I = Timers.rbegin(), E = Timers.rend(); I != E; ++I) { - I->first.print(Total, OS); - OS << I->second << '\n'; + for (const auto &I : Timers) { + I.first.print(Total, OS); + OS << I.second << '\n'; } - Total.print(Total, OS); - OS << "Total\n"; OS << Line << "\n"; OS.flush(); } +void ClangTidyProfiling::printCSVTable(llvm::raw_ostream &OS) const { + int Column = 0; + auto comma = [&Column, &OS]() { + if (Column) + OS << ','; // FIXME: can ',' be a decimal separator? + ++Column; + }; + auto printColumn = [comma, &OS](bool print, const char *str) { + if (!print) + return; + comma(); + OS << "\""; + OS << str; + OS << "\""; + }; + printColumn(Total.getUserTime(), "User Time"); + printColumn(Total.getSystemTime(), "System Time"); + printColumn(Total.getProcessTime(), "User+System"); + printColumn(true, "Wall Time"); + printColumn(Total.getMemUsed(), "Mem"); + printColumn(true, "Name"); + OS << "\n"; + + // Loop through all of the timing data, printing it out. + for (const auto &I : Timers) { + I.first.printCSV(Total, OS); + comma(); + OS << "\"" << I.second << "\"\n"; + } + + OS.flush(); +} + +void ClangTidyProfiling::storeProfileData() const { + assert(StoreCSVTo.hasValue() && "We should have a filename."); + + llvm::SmallString<256> OutputDirectory(*StoreCSVTo); + llvm::sys::path::remove_filename(OutputDirectory); + if (std::error_code EC = llvm::sys::fs::create_directories(OutputDirectory)) { + llvm::errs() << "Unable to create output directory '" << OutputDirectory + << "': " << EC.message() << "\n"; + return; + } + + std::error_code EC; + llvm::raw_fd_ostream OS(*StoreCSVTo, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Error opening output file: " << EC.message() << "\n"; + return; + } + + printCSVTable(OS); +} + +ClangTidyProfiling::ClangTidyProfiling(llvm::Optional StoreCSV) + : StoreCSVTo(std::move(StoreCSV)) {} + ClangTidyProfiling::~ClangTidyProfiling() { preprocess(); - printProfileData(llvm::errs()); + + printUserFriendlyTable(llvm::errs()); + + if (StoreCSVTo.hasValue()) + storeProfileData(); } } // namespace tidy Index: clang-tidy/tool/ClangTidyMain.cpp =================================================================== --- clang-tidy/tool/ClangTidyMain.cpp +++ clang-tidy/tool/ClangTidyMain.cpp @@ -181,6 +181,24 @@ cl::init(false), cl::cat(ClangTidyCategory)); +static cl::opt StoreCheckProfile("store-check-profile", + cl::desc(R"( +When -enable-check-profile option is enabled, +this option controls the prefix where these +per-TU profiles are stored, as CSV. +By default reports are only printed +in tabulated format to stderr.)"), + cl::value_desc("prefix"), + cl::cat(ClangTidyCategory)); + +static cl::opt StoreCheckProfileElidePrefix( + "store-check-profile-elide-prefix", cl::desc(R"( +When specified, this prefix will be elided +from the source file name, before prepending +it with the prefix specified by -store-check-profile. +)"), + cl::value_desc("prefix"), cl::cat(ClangTidyCategory)); + static cl::opt ExportFixes("export-fixes", cl::desc(R"( YAML file to store suggested fixes in. The stored fixes can be applied to the input source @@ -323,6 +341,26 @@ if (!OptionsProvider) return 1; + auto processPrefix = [](std::string &input, SmallVectorImpl &dest) { + dest.clear(); + if (input.empty()) + return; + SmallString<256> ScratchPath(input); + if (std::error_code EC = llvm::sys::fs::make_absolute(ScratchPath)) { + llvm::errs() << "Can't make absolute path from " << ScratchPath << ": " + << EC.message() << "\n"; + } + if (std::error_code EC = llvm::sys::fs::real_path(ScratchPath, dest)) { + llvm::errs() << "Can't make real path from " << ScratchPath << ": " + << EC.message() << "\n"; + } + }; + + SmallString<256> ProfilePrefix; + processPrefix(StoreCheckProfile, ProfilePrefix); + SmallString<256> ProfileElidePrefix; + processPrefix(StoreCheckProfileElidePrefix, ProfileElidePrefix); + StringRef FileName("dummy"); auto PathList = OptionsParser.getSourcePathList(); if (!PathList.empty()) { @@ -392,7 +430,7 @@ ClangTidyContext Context(std::move(OwningOptionsProvider)); runClangTidy(Context, OptionsParser.getCompilations(), PathList, BaseFS, - EnableCheckProfile); + EnableCheckProfile, ProfilePrefix, ProfileElidePrefix); ArrayRef Errors = Context.getErrors(); bool FoundErrors = llvm::find_if(Errors, [](const ClangTidyError &E) { return E.DiagLevel == ClangTidyError::Error; Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -57,6 +57,8 @@ Improvements to clang-tidy -------------------------- +- clang-tidy learned to store checks profiling info as CSV files. + - New module `abseil` for checks related to the `Abseil `_ library. Index: docs/clang-tidy/index.rst =================================================================== --- docs/clang-tidy/index.rst +++ docs/clang-tidy/index.rst @@ -99,114 +99,127 @@ .. code-block:: console - $ clang-tidy -help + $ clang-tidy --help USAGE: clang-tidy [options] [... ] OPTIONS: Generic Options: - -help - Display available options (-help-hidden for more) - -help-list - Display list of available options (-help-list-hidden for more) - -version - Display the version of this program + -help - Display available options (-help-hidden for more) + -help-list - Display list of available options (-help-list-hidden for more) + -version - Display the version of this program clang-tidy options: - -checks= - - Comma-separated list of globs with optional '-' - prefix. Globs are processed in order of - appearance in the list. Globs without '-' - prefix add checks with matching names to the - set, globs with the '-' prefix remove checks - with matching names from the set of enabled - checks. This option's value is appended to the - value of the 'Checks' option in .clang-tidy - file, if any. - -config= - - Specifies a configuration in YAML/JSON format: - -config="{Checks: '*', - CheckOptions: [{key: x, - value: y}]}" - When the value is empty, clang-tidy will - attempt to find a file named .clang-tidy for - each source file in its parent directories. - -dump-config - - Dumps configuration in the YAML format to - stdout. This option can be used along with a - file name (and '--' if the file is outside of a - project with configured compilation database). - The configuration used for this file will be - printed. - Use along with -checks=* to include - configuration of all checks. - -enable-check-profile - - Enable per-check timing profiles, and print a - report to stderr. - -explain-config - - For each enabled check explains, where it is - enabled, i.e. in clang-tidy binary, command - line or a specific configuration file. - -export-fixes= - - YAML file to store suggested fixes in. The - stored fixes can be applied to the input source - code with clang-apply-replacements. - -extra-arg= - Additional argument to append to the compiler command line - -extra-arg-before= - Additional argument to prepend to the compiler command line - -fix - - Apply suggested fixes. Without -fix-errors - clang-tidy will bail out if any compilation - errors were found. - -fix-errors - - Apply suggested fixes even if compilation - errors were found. If compiler errors have - attached fix-its, clang-tidy will apply them as - well. - -format-style= - - Style for formatting code around applied fixes: - - 'none' (default) turns off formatting - - 'file' (literally 'file', not a placeholder) - uses .clang-format file in the closest parent - directory - - '{ }' specifies options inline, e.g. - -format-style='{BasedOnStyle: llvm, IndentWidth: 8}' - - 'llvm', 'google', 'webkit', 'mozilla' - See clang-format documentation for the up-to-date - information about formatting styles and options. - This option overrides the 'FormatStyle` option in - .clang-tidy file, if any. - -header-filter= - - Regular expression matching the names of the - headers to output diagnostics from. Diagnostics - from the main file of each translation unit are - always displayed. - Can be used together with -line-filter. - This option overrides the 'HeaderFilter' option - in .clang-tidy file, if any. - -line-filter= - - List of files with line ranges to filter the - warnings. Can be used together with - -header-filter. The format of the list is a - JSON array of objects: - [ - {"name":"file1.cpp","lines":[[1,3],[5,7]]}, - {"name":"file2.h"} - ] - -list-checks - - List all enabled checks and exit. Use with - -checks=* to list all available checks. - -p= - Build path - -quiet - - Run clang-tidy in quiet mode. This suppresses - printing statistics about ignored warnings and - warnings treated as errors if the respective - options are specified. - -system-headers - Display the errors from system headers. - -warnings-as-errors= - - Upgrades warnings to errors. Same format as - '-checks'. - This option's value is appended to the value of - the 'WarningsAsErrors' option in .clang-tidy - file, if any. + -checks= - + Comma-separated list of globs with optional '-' + prefix. Globs are processed in order of + appearance in the list. Globs without '-' + prefix add checks with matching names to the + set, globs with the '-' prefix remove checks + with matching names from the set of enabled + checks. This option's value is appended to the + value of the 'Checks' option in .clang-tidy + file, if any. + -config= - + Specifies a configuration in YAML/JSON format: + -config="{Checks: '*', + CheckOptions: [{key: x, + value: y}]}" + When the value is empty, clang-tidy will + attempt to find a file named .clang-tidy for + each source file in its parent directories. + -dump-config - + Dumps configuration in the YAML format to + stdout. This option can be used along with a + file name (and '--' if the file is outside of a + project with configured compilation database). + The configuration used for this file will be + printed. + Use along with -checks=* to include + configuration of all checks. + -enable-check-profile - + Enable per-check timing profiles, and print a + report to stderr. + -explain-config - + For each enabled check explains, where it is + enabled, i.e. in clang-tidy binary, command + line or a specific configuration file. + -export-fixes= - + YAML file to store suggested fixes in. The + stored fixes can be applied to the input source + code with clang-apply-replacements. + -extra-arg= - Additional argument to append to the compiler command line + -extra-arg-before= - Additional argument to prepend to the compiler command line + -fix - + Apply suggested fixes. Without -fix-errors + clang-tidy will bail out if any compilation + errors were found. + -fix-errors - + Apply suggested fixes even if compilation + errors were found. If compiler errors have + attached fix-its, clang-tidy will apply them as + well. + -format-style= - + Style for formatting code around applied fixes: + - 'none' (default) turns off formatting + - 'file' (literally 'file', not a placeholder) + uses .clang-format file in the closest parent + directory + - '{ }' specifies options inline, e.g. + -format-style='{BasedOnStyle: llvm, IndentWidth: 8}' + - 'llvm', 'google', 'webkit', 'mozilla' + See clang-format documentation for the up-to-date + information about formatting styles and options. + This option overrides the 'FormatStyle` option in + .clang-tidy file, if any. + -header-filter= - + Regular expression matching the names of the + headers to output diagnostics from. Diagnostics + from the main file of each translation unit are + always displayed. + Can be used together with -line-filter. + This option overrides the 'HeaderFilter' option + in .clang-tidy file, if any. + -line-filter= - + List of files with line ranges to filter the + warnings. Can be used together with + -header-filter. The format of the list is a + JSON array of objects: + [ + {"name":"file1.cpp","lines":[[1,3],[5,7]]}, + {"name":"file2.h"} + ] + -list-checks - + List all enabled checks and exit. Use with + -checks=* to list all available checks. + -p= - Build path + -quiet - + Run clang-tidy in quiet mode. This suppresses + printing statistics about ignored warnings and + warnings treated as errors if the respective + options are specified. + -store-check-profile= - + When -enable-check-profile option is enabled, + this option controls the prefix where these + per-TU profiles are stored, as CSV. + By default reports are only printed + in tabulated format to stderr. + -store-check-profile-elide-prefix= - + When specified, this prefix will be elided + from the source file name, before prepending + it with the prefix specified by -store-check-profile. + -system-headers - Display the errors from system headers. + -vfsoverlay= - + Overlay the virtual filesystem described by file + over the real file system. + -warnings-as-errors= - + Upgrades warnings to errors. Same format as + '-checks'. + This option's value is appended to the value of + the 'WarningsAsErrors' option in .clang-tidy + file, if any. -p is used to read a compile command database. @@ -739,3 +752,69 @@ all changes in a temporary directory and applies them. Passing ``-format`` will run clang-format over changed lines. + +On checks profiling +------------------- + +:program:`clang-tidy` can collect per-check profiling info, and output it +for each processed source file (translation unit). + +To enable profiling info collection, use ``-enable-check-profile`` argument. +The timings will be outputted to the ``stderr`` as a table. Example output: + +.. code-block:: console + + $ clang-tidy -enable-check-profile -checks=-*,readability-function-size source.cpp + ===-------------------------------------------------------------------------=== + ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- + 0.8842 (100.0%) 0.1203 (100.0%) 1.0045 (100.0%) 1.0032 (100.0%) readability-function-size + 0.8842 (100.0%) 0.1203 (100.0%) 1.0045 (100.0%) 1.0032 (100.0%) Total + ===-------------------------------------------------------------------------=== + +It can also store that data as CSV files for further processing. Example output: + +.. code-block:: console + + $ clang-tidy -enable-check-profile -store-check-profile=. -store-check-profile-elide-prefix=. -checks=-*,readability-function-size source.cpp 2>&1 >/dev/null + $ cat .csv + "User Time","System Time","User+System","Wall Time","Name" + 8.8420499999973945e-01,1.2026799999999757e-01,1.0044729999997370e+00,1.0031676292419434e+00,"readability-function-size" + 8.8420499999973945e-01,1.2026799999999757e-01,1.0044729999997370e+00,1.0031676292419434e+00,"Total" + +There are two arguments that control profile storage: + +* ``-store-check-profile=`` + + This option controls the prefix where these per-TU profiles are stored as CSV. + If the prefix is not an absolute path, it is considered to be relative to the + directory from where you have run :program:`clang-tidy`. All ``.`` and ``..`` + patterns in the path are collapsed, and symlinks are resolved. + + Example: + Let's suppose you have a source file named ``example.cpp``, located in + ``/source`` directory. + + * If you specify ``-store-check-profile=/tmp``, then the profile will be saved + to ``/tmp/source/example.cpp.csv`` + + * If you run :program:`clang-tidy` from within ``/foo`` directory, and specify + ``-store-check-profile=.``, then the profile will still be saved to + ``/foo/source/example.cpp.csv`` +* ``-store-check-profile-elide-prefix=`` + + When specified, this prefix will be elided from the source file name, + before prepending it with the prefix specified by ``-store-check-profile``. + If the prefix is not an absolute path, it is considered to be relative to the + directory from where you have run :program:`clang-tidy`. All ``.`` and ``..`` + patterns in the path are collapsed, and symlinks are resolved. + + Example: + Let's suppose you have a source file named ``example.cpp``, located in + ``/source`` directory. + + * If you specify ``-store-check-profile=/tmp -store-check-profile-elide-prefix=/source`` + , then the profile will be saved to ``/tmp/example.cpp.csv`` + + * If you run :program:`clang-tidy` from within ``/source`` directory, and + specify ``-store-check-profile=/foo -store-check-profile-elide-prefix=.``, + then the profile will be saved to ``/foo/example.cpp.csv`` Index: test/clang-tidy/clang-tidy-store-check-profile-one-tu.cpp =================================================================== --- /dev/null +++ test/clang-tidy/clang-tidy-store-check-profile-one-tu.cpp @@ -0,0 +1,17 @@ +// RUN: clang-tidy -enable-check-profile -checks='-*,readability-function-size' -store-check-profile=%T -store-check-profile-elide-prefix=%s %s 2>&1 | FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' -check-prefix=CHECK-CONSOLE %s +// RUN: FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' -input-file=%T/.csv -check-prefix=CHECK-FILE %s + +// CHECK-CONSOLE: ===-------------------------------------------------------------------------=== +// CHECK-CONSOLE-NEXT: {{.*}} --- Name --- +// CHECK-CONSOLE-NEXT: {{.*}} readability-function-size +// CHECK-CONSOLE-NEXT: {{.*}} Total +// CHECK-CONSOLE-NEXT: ===-------------------------------------------------------------------------=== + +// CHECK-FILE: {{.*}}"Wall Time","Name" +// CHECK-FILE-NEXT: {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}},"readability-function-size" +// CHECK-FILE-NEXT: {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}},"Total" + +class A { + A() {} + ~A() {} +};