From 7b8ceb5d2dc26c924c4a24c8b22bbb10c440e85f Mon Sep 17 00:00:00 2001 From: Ethan Evans Date: Tue, 12 Aug 2025 23:34:49 -0700 Subject: [PATCH] libutil, libexpr: #10542 abstract over getrusage for getting cpuTime stat and implement windows version Update src/libutil/windows/current-process.cc Prefer `nullptr` over `NULL` Co-authored-by: Sergei Zimmerman Update src/libutil/unix/current-process.cc Prefer C++ type casts Co-authored-by: Sergei Zimmerman Update src/libutil/windows/current-process.cc Prefer C++ type casts Co-authored-by: Sergei Zimmerman Update src/libutil/unix/current-process.cc Don't allocate exception Co-authored-by: Sergei Zimmerman --- src/libexpr/eval.cc | 18 ++-------- .../include/nix/util/current-process.hh | 6 ++++ src/libutil/unix/current-process.cc | 23 ++++++++++++ src/libutil/unix/meson.build | 1 + src/libutil/windows/current-process.cc | 35 +++++++++++++++++++ src/libutil/windows/meson.build | 1 + 6 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 src/libutil/unix/current-process.cc create mode 100644 src/libutil/windows/current-process.cc diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 293b05953..81a9afe63 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -21,6 +21,7 @@ #include "nix/fetchers/fetch-to-store.hh" #include "nix/fetchers/tarball.hh" #include "nix/fetchers/input-cache.hh" +#include "nix/util/current-process.hh" #include "parser-tab.hh" @@ -37,10 +38,6 @@ #include #include -#ifndef _WIN32 // TODO use portable implementation -# include -#endif - #include "nix/util/strings-inline.hh" using json = nlohmann::json; @@ -2888,11 +2885,8 @@ void EvalState::maybePrintStats() void EvalState::printStatistics() { -#ifndef _WIN32 // TODO use portable implementation - struct rusage buf; - getrusage(RUSAGE_SELF, &buf); - float cpuTime = buf.ru_utime.tv_sec + ((float) buf.ru_utime.tv_usec / 1000000); -#endif + std::chrono::microseconds cpuTimeDuration = getCpuUserTime(); + float cpuTime = std::chrono::duration_cast>(cpuTimeDuration).count(); uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *); uint64_t bLists = nrListElems * sizeof(Value *); @@ -2914,18 +2908,12 @@ void EvalState::printStatistics() if (outPath != "-") fs.open(outPath, std::fstream::out); json topObj = json::object(); -#ifndef _WIN32 // TODO implement topObj["cpuTime"] = cpuTime; -#endif topObj["time"] = { -#ifndef _WIN32 // TODO implement {"cpu", cpuTime}, -#endif #if NIX_USE_BOEHMGC {GC_is_incremental_mode() ? "gcNonIncremental" : "gc", gcFullOnlyTime}, -# ifndef _WIN32 // TODO implement {GC_is_incremental_mode() ? "gcNonIncrementalFraction" : "gcFraction", gcFullOnlyTime / cpuTime}, -# endif #endif }; topObj["envs"] = { diff --git a/src/libutil/include/nix/util/current-process.hh b/src/libutil/include/nix/util/current-process.hh index 364493137..c4a952581 100644 --- a/src/libutil/include/nix/util/current-process.hh +++ b/src/libutil/include/nix/util/current-process.hh @@ -2,6 +2,7 @@ ///@file #include +#include #ifndef _WIN32 # include @@ -11,6 +12,11 @@ namespace nix { +/** + * Get the current process's user space CPU time. + */ +std::chrono::microseconds getCpuUserTime(); + /** * If cgroups are active, attempt to calculate the number of CPUs available. * If cgroups are unavailable or if cpu.max is set to "max", return 0. diff --git a/src/libutil/unix/current-process.cc b/src/libutil/unix/current-process.cc new file mode 100644 index 000000000..eaa2424ab --- /dev/null +++ b/src/libutil/unix/current-process.cc @@ -0,0 +1,23 @@ +#include "nix/util/current-process.hh" +#include "nix/util/error.hh" +#include + +#include + +namespace nix { + +std::chrono::microseconds getCpuUserTime() +{ + struct rusage buf; + + if (getrusage(RUSAGE_SELF, &buf) != 0) { + throw SysError("failed to get CPU time"); + } + + std::chrono::seconds seconds(buf.ru_utime.tv_sec); + std::chrono::microseconds microseconds(buf.ru_utime.tv_usec); + + return seconds + microseconds; +} + +} // namespace nix diff --git a/src/libutil/unix/meson.build b/src/libutil/unix/meson.build index 13bb380b4..8f89b65ab 100644 --- a/src/libutil/unix/meson.build +++ b/src/libutil/unix/meson.build @@ -49,6 +49,7 @@ config_unix_priv_h = configure_file( sources += config_unix_priv_h sources += files( + 'current-process.cc', 'environment-variables.cc', 'file-descriptor.cc', 'file-path.cc', diff --git a/src/libutil/windows/current-process.cc b/src/libutil/windows/current-process.cc new file mode 100644 index 000000000..4bc866bb3 --- /dev/null +++ b/src/libutil/windows/current-process.cc @@ -0,0 +1,35 @@ +#include "nix/util/current-process.hh" +#include "nix/util/windows-error.hh" +#include + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include + +namespace nix { + +std::chrono::microseconds getCpuUserTime() +{ + FILETIME creationTime; + FILETIME exitTime; + FILETIME kernelTime; + FILETIME userTime; + + if (!GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)) { + auto lastError = GetLastError(); + throw windows::WinError(lastError, "failed to get CPU time"); + } + + ULARGE_INTEGER uLargeInt; + uLargeInt.LowPart = userTime.dwLowDateTime; + uLargeInt.HighPart = userTime.dwHighDateTime; + + // FILETIME stores units of 100 nanoseconds. + // Dividing by 10 gives microseconds. + std::chrono::microseconds microseconds(uLargeInt.QuadPart / 10); + + return microseconds; +} + +} // namespace nix +#endif // ifdef _WIN32 diff --git a/src/libutil/windows/meson.build b/src/libutil/windows/meson.build index 0c1cec49c..fb4de2017 100644 --- a/src/libutil/windows/meson.build +++ b/src/libutil/windows/meson.build @@ -1,4 +1,5 @@ sources += files( + 'current-process.cc', 'environment-variables.cc', 'file-descriptor.cc', 'file-path.cc',