aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/process.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-23 19:22:08 +0100
committerGitHub Enterprise <[email protected]>2026-03-23 19:22:08 +0100
commit440ef03df8d8bba4432126f36168c1f7631c18dc (patch)
tree07d4bd4446a11589c9a842255bf37c25aaded74b /src/zencore/process.cpp
parentMerge branch 'de/v5.7.25-hotpatch' (#880) (diff)
downloadzen-440ef03df8d8bba4432126f36168c1f7631c18dc.tar.xz
zen-440ef03df8d8bba4432126f36168c1f7631c18dc.zip
Cross-platform process metrics support (#887)
- **Cross-platform `GetProcessMetrics`**: Implement Linux (`/proc/{pid}/stat`, `/proc/{pid}/statm`, `/proc/{pid}/status`) and macOS (`proc_pidinfo(PROC_PIDTASKINFO)`) support for CPU times and memory metrics. Fix Windows to populate the `MemoryBytes` field (was always 0). All platforms now set `MemoryBytes = WorkingSetSize`. - **`ProcessMetricsTracker`**: Experimental utility class (`zenutil`) that periodically samples resource usage for a set of tracked child processes. Supports both a dedicated background thread and an ASIO steady_timer mode. Computes delta-based CPU usage percentage across samples, with batched sampling (8 processes per tick) to limit per-cycle overhead. - **`ProcessHandle` documentation**: Add Doxygen comments to all public methods describing platform-specific behavior. - **Cleanup**: Remove unused `ZEN_RUN_TESTS` macro (inlined at its single call site in `zenserver/main.cpp`), remove dead `#if 0` thread-shutdown workaround block. - **Minor fixes**: Use `HttpClientAccessToken` constructor in hordeclient instead of setting private members directly. Log ASIO version at startup and include it in the server settings list.
Diffstat (limited to 'src/zencore/process.cpp')
-rw-r--r--src/zencore/process.cpp152
1 files changed, 148 insertions, 4 deletions
diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp
index 47289a37b..dcb8b2422 100644
--- a/src/zencore/process.cpp
+++ b/src/zencore/process.cpp
@@ -1839,11 +1839,137 @@ GetProcessMetrics(const ProcessHandle& Handle, ProcessMetrics& OutMetrics)
OutMetrics.PeakWorkingSetSize = MemCounters.PeakWorkingSetSize;
OutMetrics.PagefileUsage = MemCounters.PagefileUsage;
OutMetrics.PeakPagefileUsage = MemCounters.PeakPagefileUsage;
+ OutMetrics.MemoryBytes = MemCounters.WorkingSetSize;
}
-#else
- // TODO: implement for Linux and Mac
- ZEN_UNUSED(Handle);
- ZEN_UNUSED(OutMetrics);
+#elif ZEN_PLATFORM_LINUX
+
+ const pid_t Pid = static_cast<pid_t>(Handle.Pid());
+
+ // Read CPU times from /proc/{pid}/stat
+ {
+ char Path[64];
+ snprintf(Path, sizeof(Path), "/proc/%d/stat", static_cast<int>(Pid));
+
+ char Buf[256];
+ int Fd = open(Path, O_RDONLY);
+ if (Fd >= 0)
+ {
+ ssize_t Len = read(Fd, Buf, sizeof(Buf) - 1);
+ close(Fd);
+
+ if (Len > 0)
+ {
+ Buf[Len] = '\0';
+
+ // Skip past "pid (name) " — find last ')' to handle names containing spaces or parens
+ const char* P = strrchr(Buf, ')');
+ if (P)
+ {
+ P += 2; // skip ') '
+
+ // Fields after (name): 0:state 1:ppid ... 11:utime 12:stime
+ unsigned long UTime = 0;
+ unsigned long STime = 0;
+ if (sscanf(P, "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &UTime, &STime) == 2)
+ {
+ static const long ClkTck = std::max(sysconf(_SC_CLK_TCK), 1L);
+ OutMetrics.KernelTimeMs = STime * 1000 / ClkTck;
+ OutMetrics.UserTimeMs = UTime * 1000 / ClkTck;
+ }
+ }
+ }
+ }
+ }
+
+ // Read memory metrics from /proc/{pid}/statm (values in pages)
+ {
+ char Path[64];
+ snprintf(Path, sizeof(Path), "/proc/%d/statm", static_cast<int>(Pid));
+
+ char Buf[128];
+ int Fd = open(Path, O_RDONLY);
+ if (Fd >= 0)
+ {
+ ssize_t Len = read(Fd, Buf, sizeof(Buf) - 1);
+ close(Fd);
+
+ if (Len > 0)
+ {
+ Buf[Len] = '\0';
+
+ // Fields: size resident shared text lib data dt
+ unsigned long VmSize = 0;
+ unsigned long Resident = 0;
+ if (sscanf(Buf, "%lu %lu", &VmSize, &Resident) == 2)
+ {
+ static const long PageSize = sysconf(_SC_PAGESIZE);
+ OutMetrics.WorkingSetSize = Resident * PageSize;
+ OutMetrics.PagefileUsage = VmSize * PageSize;
+ }
+ }
+ }
+ }
+
+ // Read peak RSS from /proc/{pid}/status (VmHWM line)
+ {
+ char Path[64];
+ snprintf(Path, sizeof(Path), "/proc/%d/status", static_cast<int>(Pid));
+
+ char Buf[2048];
+ int Fd = open(Path, O_RDONLY);
+ if (Fd >= 0)
+ {
+ ssize_t Len = read(Fd, Buf, sizeof(Buf) - 1);
+ close(Fd);
+
+ if (Len > 0)
+ {
+ Buf[Len] = '\0';
+
+ const char* VmHWM = strstr(Buf, "VmHWM:");
+ if (VmHWM)
+ {
+ unsigned long PeakRssKb = 0;
+ if (sscanf(VmHWM + 6, "%lu", &PeakRssKb) == 1)
+ {
+ OutMetrics.PeakWorkingSetSize = PeakRssKb * 1024;
+ }
+ }
+
+ const char* VmPeak = strstr(Buf, "VmPeak:");
+ if (VmPeak)
+ {
+ unsigned long PeakVmKb = 0;
+ if (sscanf(VmPeak + 7, "%lu", &PeakVmKb) == 1)
+ {
+ OutMetrics.PeakPagefileUsage = PeakVmKb * 1024;
+ }
+ }
+ }
+ }
+ }
+
+ OutMetrics.MemoryBytes = OutMetrics.WorkingSetSize;
+
+#elif ZEN_PLATFORM_MAC
+
+ const pid_t Pid = static_cast<pid_t>(Handle.Pid());
+
+ struct proc_taskinfo Info;
+ if (proc_pidinfo(Pid, PROC_PIDTASKINFO, 0, &Info, sizeof(Info)) > 0)
+ {
+ // pti_total_user and pti_total_system are in nanoseconds
+ OutMetrics.UserTimeMs = Info.pti_total_user / 1'000'000;
+ OutMetrics.KernelTimeMs = Info.pti_total_system / 1'000'000;
+
+ OutMetrics.WorkingSetSize = Info.pti_resident_size;
+ OutMetrics.PeakWorkingSetSize = Info.pti_resident_size; // macOS doesn't track peak RSS directly
+ OutMetrics.PagefileUsage = Info.pti_virtual_size;
+ OutMetrics.PeakPagefileUsage = Info.pti_virtual_size;
+ }
+
+ OutMetrics.MemoryBytes = OutMetrics.WorkingSetSize;
+
#endif
}
@@ -1885,6 +2011,24 @@ TEST_CASE("FindProcess")
}
}
+TEST_CASE("GetProcessMetrics")
+{
+ ProcessHandle Handle;
+ Handle.Initialize(GetCurrentProcessId());
+ REQUIRE(Handle.IsValid());
+
+ ProcessMetrics Metrics;
+ GetProcessMetrics(Handle, Metrics);
+
+ // The current process should have non-zero memory usage
+ CHECK(Metrics.WorkingSetSize > 0);
+ CHECK(Metrics.MemoryBytes > 0);
+ CHECK(Metrics.MemoryBytes == Metrics.WorkingSetSize);
+
+ // CPU time should be non-zero for a running test process
+ CHECK((Metrics.UserTimeMs + Metrics.KernelTimeMs) > 0);
+}
+
TEST_CASE("BuildArgV")
{
const char* Words[] = {"one", "two", "three", "four", "five"};