diff --git a/llvm/include/llvm/Support/Program.h b/llvm/include/llvm/Support/Program.h --- a/llvm/include/llvm/Support/Program.h +++ b/llvm/include/llvm/Support/Program.h @@ -18,6 +18,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Config/llvm-config.h" #include "llvm/Support/ErrorOr.h" +#include #include namespace llvm { @@ -52,6 +53,13 @@ ProcessInfo(); }; + /// This struct encapsulates information about a process execution. + struct ProcessStatistics { + std::chrono::microseconds TotalTime; + std::chrono::microseconds UserTime; + uint64_t PeakMemory = 0; + }; + /// Find the first executable file \p Name in \p Paths. /// /// This does not perform hashing as a shell would but instead stats each PATH @@ -116,10 +124,14 @@ ///< string instance in which error messages will be returned. If the ///< string is non-empty upon return an error occurred while invoking the ///< program. - bool *ExecutionFailed = nullptr); + bool *ExecutionFailed = nullptr, + Optional *ProcStat = nullptr ///< If non-zero, provides + /// a pointer to a structure in which process execution statistics will be + /// stored. + ); /// Similar to ExecuteAndWait, but returns immediately. - /// @returns The \see ProcessInfo of the newly launced process. + /// @returns The \see ProcessInfo of the newly launched process. /// \note On Microsoft Windows systems, users will need to either call /// \see Wait until the process finished execution or win32 CloseHandle() API /// on ProcessInfo.ProcessHandle to avoid memory leaks. @@ -182,18 +194,21 @@ /// \note Users of this function should always check the ReturnCode member of /// the \see ProcessInfo returned from this function. ProcessInfo Wait( - const ProcessInfo &PI, ///< The child process that should be waited on. + const ProcessInfo &PI, ///< The child process that should be waited on. unsigned SecondsToWait, ///< If non-zero, this specifies the amount of ///< time to wait for the child process to exit. If the time expires, the ///< child is killed and this function returns. If zero, this function ///< will perform a non-blocking wait on the child process. bool WaitUntilTerminates, ///< If true, ignores \p SecondsToWait and waits ///< until child has terminated. - std::string *ErrMsg = nullptr ///< If non-zero, provides a pointer to a + std::string *ErrMsg = nullptr, ///< If non-zero, provides a pointer to a ///< string instance in which error messages will be returned. If the ///< string is non-empty upon return an error occurred while invoking the ///< program. - ); + Optional *ProcStat = nullptr ///< If non-zero, provides + /// a pointer to a structure in which process execution statistics will be + /// stored. + ); #if defined(_WIN32) /// Given a list of command line arguments, quote and escape them as necessary diff --git a/llvm/lib/Support/Program.cpp b/llvm/lib/Support/Program.cpp --- a/llvm/lib/Support/Program.cpp +++ b/llvm/lib/Support/Program.cpp @@ -31,14 +31,16 @@ Optional> Env, ArrayRef> Redirects, unsigned SecondsToWait, unsigned MemoryLimit, - std::string *ErrMsg, bool *ExecutionFailed) { + std::string *ErrMsg, bool *ExecutionFailed, + Optional *ProcStat) { assert(Redirects.empty() || Redirects.size() == 3); ProcessInfo PI; if (Execute(PI, Program, Args, Env, Redirects, MemoryLimit, ErrMsg)) { if (ExecutionFailed) *ExecutionFailed = false; - ProcessInfo Result = Wait( - PI, SecondsToWait, /*WaitUntilTerminates=*/SecondsToWait == 0, ErrMsg); + ProcessInfo Result = + Wait(PI, SecondsToWait, /*WaitUntilTerminates=*/SecondsToWait == 0, + ErrMsg, ProcStat); return Result.ReturnCode; } diff --git a/llvm/lib/Support/Unix/Program.inc b/llvm/lib/Support/Unix/Program.inc --- a/llvm/lib/Support/Unix/Program.inc +++ b/llvm/lib/Support/Unix/Program.inc @@ -332,7 +332,8 @@ namespace llvm { ProcessInfo sys::Wait(const ProcessInfo &PI, unsigned SecondsToWait, - bool WaitUntilTerminates, std::string *ErrMsg) { + bool WaitUntilTerminates, std::string *ErrMsg, + Optional *ProcStat) { struct sigaction Act, Old; assert(PI.Pid && "invalid pid to wait on, process not started?"); @@ -355,9 +356,12 @@ // Parent process: Wait for the child process to terminate. int status; ProcessInfo WaitResult; + rusage Info; + if (ProcStat) + ProcStat->reset(); do { - WaitResult.Pid = waitpid(ChildPid, &status, WaitPidOptions); + WaitResult.Pid = wait4(ChildPid, &status, WaitPidOptions, &Info); } while (WaitUntilTerminates && WaitResult.Pid == -1 && errno == EINTR); if (WaitResult.Pid != PI.Pid) { @@ -395,6 +399,13 @@ sigaction(SIGALRM, &Old, nullptr); } + if (ProcStat) { + std::chrono::microseconds UserT = toDuration(Info.ru_utime); + std::chrono::microseconds KernelT = toDuration(Info.ru_stime); + uint64_t PeakMemory = static_cast(Info.ru_maxrss); + *ProcStat = ProcessStatistics{UserT + KernelT, UserT, PeakMemory}; + } + // Return the proper exit status. Detect error conditions // so we can return -1 for them and set ErrMsg informatively. int result = 0; diff --git a/llvm/lib/Support/Windows/Program.inc b/llvm/lib/Support/Windows/Program.inc --- a/llvm/lib/Support/Windows/Program.inc +++ b/llvm/lib/Support/Windows/Program.inc @@ -10,14 +10,15 @@ // //===----------------------------------------------------------------------===// -#include "llvm/Support/Windows/WindowsSupport.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Windows/WindowsSupport.h" #include "llvm/Support/WindowsError.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -390,7 +391,8 @@ } ProcessInfo sys::Wait(const ProcessInfo &PI, unsigned SecondsToWait, - bool WaitUntilChildTerminates, std::string *ErrMsg) { + bool WaitUntilChildTerminates, std::string *ErrMsg, + Optional *ProcStat) { assert(PI.Pid && "invalid pid to wait on, process not started?"); assert((PI.Process && PI.Process != INVALID_HANDLE_VALUE) && "invalid process handle to wait on, process not started?"); @@ -401,6 +403,8 @@ milliSecondsToWait = SecondsToWait * 1000; ProcessInfo WaitResult = PI; + if (ProcStat) + ProcStat->reset(); DWORD WaitStatus = WaitForSingleObject(PI.Process, milliSecondsToWait); if (WaitStatus == WAIT_TIMEOUT) { if (SecondsToWait) { @@ -421,6 +425,22 @@ } } + // Get process execution statistics. + if (ProcStat) { + FILETIME CreationTime, ExitTime, KernelTime, UserTime; + PROCESS_MEMORY_COUNTERS MemInfo; + if (GetProcessTimes(PI.Process, &CreationTime, &ExitTime, &KernelTime, + &UserTime) && + GetProcessMemoryInfo(PI.Process, &MemInfo, sizeof(MemInfo))) { + auto UserT = std::chrono::duration_cast( + toDuration(UserTime)); + auto KernelT = std::chrono::duration_cast( + toDuration(KernelTime)); + uint64_t PeakMemory = MemInfo.PeakPagefileUsage / 1024; + *ProcStat = ProcessStatistics{UserT + KernelT, UserT, PeakMemory}; + } + } + // Get its exit status. DWORD status; BOOL rc = GetExitCodeProcess(PI.Process, &status); diff --git a/llvm/unittests/Support/ProgramTest.cpp b/llvm/unittests/Support/ProgramTest.cpp --- a/llvm/unittests/Support/ProgramTest.cpp +++ b/llvm/unittests/Support/ProgramTest.cpp @@ -336,4 +336,30 @@ ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); } +TEST_F(ProgramEnvTest, TestExecuteAndWaitStatistics) { + using namespace llvm::sys; + + if (getenv("LLVM_PROGRAM_TEST_STATISTICS")) + exit(0); + + std::string Executable = + sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); + StringRef argv[] = { + Executable, "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitStatistics"}; + + // Add LLVM_PROGRAM_TEST_STATISTICS to the environment of the child. + addEnvVar("LLVM_PROGRAM_TEST_STATISTICS=1"); + + std::string Error; + bool ExecutionFailed; + Optional ProcStat; + int RetCode = ExecuteAndWait(Executable, argv, getEnviron(), {}, 0, 0, &Error, + &ExecutionFailed, &ProcStat); + ASSERT_EQ(0, RetCode); + ASSERT_TRUE(ProcStat); + ASSERT_GT(ProcStat->PeakMemory, 0); + ASSERT_GE(ProcStat->UserTime, std::chrono::microseconds(0)); + ASSERT_GE(ProcStat->TotalTime, ProcStat->UserTime); +} + } // end anonymous namespace