diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -747,6 +747,51 @@ translated from debug annotations. That translation can be lossy, which results in some remarks having no location information. +Options to Emit Resource Consumption Reports +-------------------------------------------- + +These are options that report execution time and consumed memory of different +compilations steps. + +.. option:: -fproc-stat-report= + + This option requests driver to print used memory and execution time of each + compilation step. The ``clang`` driver during execution calls different tools, + like compiler, assembler, linker etc. With this option the driver reports + total execution time, the execution time spent in user mode and peak memory + usage of each the called tool. Value of the option specifies where the report + is sent to. If it specifies a regular file, the data are saved to this file in + CSV format: + +.. code-block:: console + + $ clang -fproc-stat-report=abc foo.c + $ cat abc + clang-11,"/tmp/foo-123456.o",92000,84000,87536 + ld,"a.out",900,8000,53568 + + The data on each row represent: + + * file name of the tool executable, + * output file name in quotes, + * total execution time in microseconds, + * execution time in user mode in microseconds, + * peak memory usage in Kb. + + It is possible to specify this option without any value. In this case statistics + is printed on standard output in human readable format: + +.. code-block:: console + + $ clang -fproc-stat-report foo.c + clang-11: output=/tmp/foo-855a8e.o, total=68.000 ms, user=60.000 ms, mem=86920 Kb + ld: output=a.out, total=8.000 ms, user=4.000 ms, mem=52320 Kb + + The report file specified in the option is locked for write, so this option + can be used to collect statistics in parallel builds. The report file is not + cleared, new data is appended to it, thus making posible to accumulate build + statistics. + Other Options ------------- Clang options that don't fit neatly into other categories. diff --git a/clang/include/clang/Driver/Job.h b/clang/include/clang/Driver/Job.h --- a/clang/include/clang/Driver/Job.h +++ b/clang/include/clang/Driver/Job.h @@ -16,6 +16,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator.h" #include "llvm/Option/Option.h" +#include "llvm/Support/Program.h" #include #include #include @@ -73,6 +74,9 @@ /// See Command::setEnvironment std::vector Environment; + /// Information on executable run provided by OS. + mutable Optional ProcStat; + /// When a response file is needed, we try to put most arguments in an /// exclusive file, while others remains as regular command line arguments. /// This functions fills a vector with the regular command line arguments, @@ -139,6 +143,10 @@ return OutputFilenames; } + Optional getProcessStatistics() const { + return ProcStat; + } + /// Print a command argument, and optionally quote it. static void printArg(llvm::raw_ostream &OS, StringRef Arg, bool Quote); diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1910,6 +1910,10 @@ def ftime_trace_granularity_EQ : Joined<["-"], "ftime-trace-granularity=">, Group, HelpText<"Minimum time granularity (in microseconds) traced by time profiler">, Flags<[CC1Option, CoreOption]>; +def fproc_stat_report : Joined<["-"], "fproc-stat-report">, Group, + Flags<[DriverOption]>, HelpText<"Print subprocess statistics">; +def fproc_stat_report_EQ : Joined<["-"], "fproc-stat-report=">, Group, + Flags<[DriverOption]>, HelpText<"Save subprocess statistics to the given file">; def ftlsmodel_EQ : Joined<["-"], "ftls-model=">, Group, Flags<[CC1Option]>; def ftrapv : Flag<["-"], "ftrapv">, Group, Flags<[CC1Option]>, HelpText<"Trap on integer overflow">; diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -3762,11 +3762,70 @@ /*TargetDeviceOffloadKind*/ Action::OFK_None); } - // If we have more than one job, then disable integrated-cc1 for now. - if (C.getJobs().size() > 1) + StringRef StatReportFile; + bool PrintProcessStat = false; + if (const Arg *A = C.getArgs().getLastArg(options::OPT_fproc_stat_report_EQ)) + StatReportFile = A->getValue(); + if (C.getArgs().hasArg(options::OPT_fproc_stat_report)) + PrintProcessStat = true; + + // If we have more than one job, then disable integrated-cc1 for now. Do this + // also when we need to report process execution statistics. + if (C.getJobs().size() > 1 || !StatReportFile.empty() || PrintProcessStat) for (auto &J : C.getJobs()) J.InProcess = false; + if (!StatReportFile.empty() || PrintProcessStat) { + C.setPostCallback([=](const Command &Cmd, int Res) { + Optional ProcStat + = Cmd.getProcessStatistics(); + if (ProcStat) { + if (PrintProcessStat) { + using namespace llvm; + // Human readable output. + outs() << sys::path::filename(Cmd.getExecutable()) << ": " + << "output="; + if (Cmd.getOutputFilenames().empty()) + outs() << "\"\""; + else + outs() << Cmd.getOutputFilenames().front(); + outs() << ", total=" + << format("%.3f", ProcStat->TotalTime.count() / 1000.) << " ms" + << ", user=" + << format("%.3f", ProcStat->UserTime.count() / 1000.) << " ms" + << ", mem=" << ProcStat->PeakMemory << " Kb\n"; + } + if (!StatReportFile.empty()) { + // CSV format. + std::string Buffer; + llvm::raw_string_ostream Out(Buffer); + Out << llvm::sys::path::filename(Cmd.getExecutable()) << ','; + if (Cmd.getOutputFilenames().empty()) + Out << "\"\""; + else + Command::printArg(Out, Cmd.getOutputFilenames().front(), true); + Out << ',' + << ProcStat->TotalTime.count() << ',' + << ProcStat->UserTime.count() << ',' + << ProcStat->PeakMemory << '\n'; + Out.flush(); + std::error_code EC; + llvm::raw_fd_ostream OS(StatReportFile, EC, llvm::sys::fs::OF_Append); + if (!EC) { + if (auto L = OS.tryToLock()) + OS << Buffer; + else + handleAllErrors(std::move(L.takeError()), + [&](llvm::ErrorInfoBase &EIB) { + llvm::errs() << "ERROR: Cannot lock file " << StatReportFile + << "n"; + }); + } + } + } + }); + } + // If the user passed -Qunused-arguments or there were errors, don't warn // about any unused arguments. if (Diags.hasErrorOccurred() || diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp --- a/clang/lib/Driver/Job.cpp +++ b/clang/lib/Driver/Job.cpp @@ -370,8 +370,8 @@ auto Args = llvm::toStringRefArray(Argv.data()); return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects, - /*secondsToWait*/ 0, - /*memoryLimit*/ 0, ErrMsg, ExecutionFailed); + /*secondsToWait*/ 0, /*memoryLimit*/ 0, ErrMsg, ExecutionFailed, + &ProcStat); } CC1Command::CC1Command(const Action &Source, const Tool &Creator, diff --git a/clang/test/Driver/report-stat.c b/clang/test/Driver/report-stat.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/report-stat.c @@ -0,0 +1,6 @@ +// RUN: %clang -c -fproc-stat-report %s | FileCheck %s +// CHECK: clang{{.*}}: output={{.*}}.o, total={{[0-9.]+}} ms, user={{[0-9.]+}} ms, mem={{[0-9]+}} Kb + +// RUN: %clang -c -fproc-stat-report=%t %s +// RUN: cat %t | FileCheck --check-prefix=CSV %s +// CSV: clang{{.*}},"{{.*}}.o",{{[0-9]+}},{{[0-9]+}},{{[0-9]+}}