aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/system.cpp
diff options
context:
space:
mode:
authorLiam Mitchell <[email protected]>2026-03-09 19:06:36 -0700
committerLiam Mitchell <[email protected]>2026-03-09 19:06:36 -0700
commitd1abc50ee9d4fb72efc646e17decafea741caa34 (patch)
treee4288e00f2f7ca0391b83d986efcb69d3ba66a83 /src/zencore/system.cpp
parentAllow requests with invalid content-types unless specified in command line or... (diff)
parentupdated chunk–block analyser (#818) (diff)
downloadzen-d1abc50ee9d4fb72efc646e17decafea741caa34.tar.xz
zen-d1abc50ee9d4fb72efc646e17decafea741caa34.zip
Merge branch 'main' into lm/restrict-content-type
Diffstat (limited to 'src/zencore/system.cpp')
-rw-r--r--src/zencore/system.cpp407
1 files changed, 370 insertions, 37 deletions
diff --git a/src/zencore/system.cpp b/src/zencore/system.cpp
index b9ac3bdee..141450b84 100644
--- a/src/zencore/system.cpp
+++ b/src/zencore/system.cpp
@@ -4,15 +4,20 @@
#include <zencore/compactbinarybuilder.h>
#include <zencore/except.h>
+#include <zencore/fmtutils.h>
#include <zencore/memory/memory.h>
#include <zencore/string.h>
+#include <mutex>
+
#if ZEN_PLATFORM_WINDOWS
# include <zencore/windows.h>
ZEN_THIRD_PARTY_INCLUDES_START
# include <iphlpapi.h>
# include <winsock2.h>
+# include <pdh.h>
+# pragma comment(lib, "pdh.lib")
ZEN_THIRD_PARTY_INCLUDES_END
#elif ZEN_PLATFORM_LINUX
# include <sys/utsname.h>
@@ -65,55 +70,73 @@ GetSystemMetrics()
// Determine physical core count
- DWORD BufferSize = 0;
- BOOL Result = GetLogicalProcessorInformation(nullptr, &BufferSize);
- if (int32_t Error = GetLastError(); Error != ERROR_INSUFFICIENT_BUFFER)
{
- ThrowSystemError(Error, "Failed to get buffer size for logical processor information");
- }
+ DWORD BufferSize = 0;
+ BOOL Result = GetLogicalProcessorInformationEx(RelationAll, nullptr, &BufferSize);
+ if (int32_t Error = GetLastError(); Error != ERROR_INSUFFICIENT_BUFFER)
+ {
+ ThrowSystemError(Error, "Failed to get buffer size for logical processor information");
+ }
- PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)Memory::Alloc(BufferSize);
+ PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX Buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)Memory::Alloc(BufferSize);
- Result = GetLogicalProcessorInformation(Buffer, &BufferSize);
- if (!Result)
- {
- Memory::Free(Buffer);
- throw std::runtime_error("Failed to get logical processor information");
- }
-
- DWORD ProcessorPkgCount = 0;
- DWORD ProcessorCoreCount = 0;
- DWORD ByteOffset = 0;
- while (ByteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= BufferSize)
- {
- const SYSTEM_LOGICAL_PROCESSOR_INFORMATION& Slpi = Buffer[ByteOffset / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)];
- if (Slpi.Relationship == RelationProcessorCore)
+ Result = GetLogicalProcessorInformationEx(RelationAll, Buffer, &BufferSize);
+ if (!Result)
{
- ProcessorCoreCount++;
+ Memory::Free(Buffer);
+ throw std::runtime_error("Failed to get logical processor information");
}
- else if (Slpi.Relationship == RelationProcessorPackage)
+
+ DWORD ProcessorPkgCount = 0;
+ DWORD ProcessorCoreCount = 0;
+ DWORD LogicalProcessorCount = 0;
+
+ BYTE* Ptr = reinterpret_cast<BYTE*>(Buffer);
+ BYTE* const End = Ptr + BufferSize;
+ while (Ptr < End)
{
- ProcessorPkgCount++;
+ const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX& Slpi = *reinterpret_cast<const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(Ptr);
+ if (Slpi.Relationship == RelationProcessorCore)
+ {
+ ++ProcessorCoreCount;
+
+ // Count logical processors (threads) across all processor groups for this core.
+ // Each core entry lists one GROUP_AFFINITY per group it spans; each set bit
+ // in the Mask represents one logical processor (HyperThreading sibling).
+ for (WORD g = 0; g < Slpi.Processor.GroupCount; ++g)
+ {
+ LogicalProcessorCount += static_cast<DWORD>(__popcnt64(Slpi.Processor.GroupMask[g].Mask));
+ }
+ }
+ else if (Slpi.Relationship == RelationProcessorPackage)
+ {
+ ++ProcessorPkgCount;
+ }
+ Ptr += Slpi.Size;
}
- ByteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
- }
- Metrics.CoreCount = ProcessorCoreCount;
- Metrics.CpuCount = ProcessorPkgCount;
+ Metrics.CoreCount = ProcessorCoreCount;
+ Metrics.CpuCount = ProcessorPkgCount;
+ Metrics.LogicalProcessorCount = LogicalProcessorCount;
- Memory::Free(Buffer);
+ Memory::Free(Buffer);
+ }
// Query memory status
- MEMORYSTATUSEX MemStatus{.dwLength = sizeof(MEMORYSTATUSEX)};
- GlobalMemoryStatusEx(&MemStatus);
+ {
+ MEMORYSTATUSEX MemStatus{.dwLength = sizeof(MEMORYSTATUSEX)};
+ GlobalMemoryStatusEx(&MemStatus);
+
+ Metrics.SystemMemoryMiB = MemStatus.ullTotalPhys / 1024 / 1024;
+ Metrics.AvailSystemMemoryMiB = MemStatus.ullAvailPhys / 1024 / 1024;
+ Metrics.VirtualMemoryMiB = MemStatus.ullTotalVirtual / 1024 / 1024;
+ Metrics.AvailVirtualMemoryMiB = MemStatus.ullAvailVirtual / 1024 / 1024;
+ Metrics.PageFileMiB = MemStatus.ullTotalPageFile / 1024 / 1024;
+ Metrics.AvailPageFileMiB = MemStatus.ullAvailPageFile / 1024 / 1024;
+ }
- Metrics.SystemMemoryMiB = MemStatus.ullTotalPhys / 1024 / 1024;
- Metrics.AvailSystemMemoryMiB = MemStatus.ullAvailPhys / 1024 / 1024;
- Metrics.VirtualMemoryMiB = MemStatus.ullTotalVirtual / 1024 / 1024;
- Metrics.AvailVirtualMemoryMiB = MemStatus.ullAvailVirtual / 1024 / 1024;
- Metrics.PageFileMiB = MemStatus.ullTotalPageFile / 1024 / 1024;
- Metrics.AvailPageFileMiB = MemStatus.ullAvailPageFile / 1024 / 1024;
+ Metrics.UptimeSeconds = GetTickCount64() / 1000;
return Metrics;
}
@@ -206,6 +229,17 @@ GetSystemMetrics()
Metrics.VirtualMemoryMiB = Metrics.SystemMemoryMiB;
Metrics.AvailVirtualMemoryMiB = Metrics.AvailSystemMemoryMiB;
+ // System uptime
+ if (FILE* UptimeFile = fopen("/proc/uptime", "r"))
+ {
+ double UptimeSec = 0;
+ if (fscanf(UptimeFile, "%lf", &UptimeSec) == 1)
+ {
+ Metrics.UptimeSeconds = static_cast<uint64_t>(UptimeSec);
+ }
+ fclose(UptimeFile);
+ }
+
// Parse /proc/meminfo for swap/page file information
Metrics.PageFileMiB = 0;
Metrics.AvailPageFileMiB = 0;
@@ -298,12 +332,35 @@ GetSystemMetrics()
Metrics.PageFileMiB = SwapUsage.xsu_total / 1024 / 1024;
Metrics.AvailPageFileMiB = (SwapUsage.xsu_total - SwapUsage.xsu_used) / 1024 / 1024;
+ // System uptime via boot time
+ {
+ struct timeval BootTime
+ {
+ };
+ Size = sizeof(BootTime);
+ if (sysctlbyname("kern.boottime", &BootTime, &Size, nullptr, 0) == 0)
+ {
+ Metrics.UptimeSeconds = static_cast<uint64_t>(time(nullptr) - BootTime.tv_sec);
+ }
+ }
+
return Metrics;
}
#else
# error "Unknown platform"
#endif
+ExtendedSystemMetrics
+ApplyReportingOverrides(ExtendedSystemMetrics Metrics)
+{
+ if (g_FakeCpuCount)
+ {
+ Metrics.CoreCount = g_FakeCpuCount;
+ Metrics.LogicalProcessorCount = g_FakeCpuCount;
+ }
+ return Metrics;
+}
+
SystemMetrics
GetSystemMetricsForReporting()
{
@@ -318,12 +375,281 @@ GetSystemMetricsForReporting()
return Sm;
}
+///////////////////////////////////////////////////////////////////////////
+// SystemMetricsTracker
+///////////////////////////////////////////////////////////////////////////
+
+// Per-platform CPU sampling helper. Called with m_Mutex held.
+
+#if ZEN_PLATFORM_WINDOWS || ZEN_PLATFORM_LINUX
+
+// Samples CPU usage by reading /proc/stat. Used natively on Linux and as a
+// Wine fallback on Windows (where /proc/stat is accessible via the Z: drive).
+struct ProcStatCpuSampler
+{
+ const char* Path = "/proc/stat";
+ unsigned long PrevUser = 0;
+ unsigned long PrevNice = 0;
+ unsigned long PrevSystem = 0;
+ unsigned long PrevIdle = 0;
+ unsigned long PrevIoWait = 0;
+ unsigned long PrevIrq = 0;
+ unsigned long PrevSoftIrq = 0;
+
+ explicit ProcStatCpuSampler(const char* InPath = "/proc/stat") : Path(InPath) {}
+
+ float Sample()
+ {
+ float CpuUsage = 0.0f;
+
+ if (FILE* Stat = fopen(Path, "r"))
+ {
+ char Line[256];
+ unsigned long User, Nice, System, Idle, IoWait, Irq, SoftIrq;
+
+ if (fgets(Line, sizeof(Line), Stat))
+ {
+ if (sscanf(Line, "cpu %lu %lu %lu %lu %lu %lu %lu", &User, &Nice, &System, &Idle, &IoWait, &Irq, &SoftIrq) == 7)
+ {
+ unsigned long TotalDelta = (User + Nice + System + Idle + IoWait + Irq + SoftIrq) -
+ (PrevUser + PrevNice + PrevSystem + PrevIdle + PrevIoWait + PrevIrq + PrevSoftIrq);
+ unsigned long IdleDelta = Idle - PrevIdle;
+
+ if (TotalDelta > 0)
+ {
+ CpuUsage = 100.0f * (TotalDelta - IdleDelta) / TotalDelta;
+ }
+
+ PrevUser = User;
+ PrevNice = Nice;
+ PrevSystem = System;
+ PrevIdle = Idle;
+ PrevIoWait = IoWait;
+ PrevIrq = Irq;
+ PrevSoftIrq = SoftIrq;
+ }
+ }
+ fclose(Stat);
+ }
+
+ return CpuUsage;
+ }
+};
+
+#endif
+
+#if ZEN_PLATFORM_WINDOWS
+
+struct CpuSampler
+{
+ PDH_HQUERY QueryHandle = nullptr;
+ PDH_HCOUNTER CounterHandle = nullptr;
+ bool HasPreviousSample = false;
+ bool IsWine = false;
+ ProcStatCpuSampler ProcStat{"Z:\\proc\\stat"};
+
+ CpuSampler()
+ {
+ IsWine = zen::windows::IsRunningOnWine();
+
+ if (!IsWine)
+ {
+ if (PdhOpenQueryW(nullptr, 0, &QueryHandle) == ERROR_SUCCESS)
+ {
+ if (PdhAddEnglishCounterW(QueryHandle, L"\\Processor(_Total)\\% Processor Time", 0, &CounterHandle) != ERROR_SUCCESS)
+ {
+ CounterHandle = nullptr;
+ }
+ }
+ }
+ }
+
+ ~CpuSampler()
+ {
+ if (QueryHandle)
+ {
+ PdhCloseQuery(QueryHandle);
+ }
+ }
+
+ float Sample()
+ {
+ if (IsWine)
+ {
+ return ProcStat.Sample();
+ }
+
+ if (!QueryHandle || !CounterHandle)
+ {
+ return 0.0f;
+ }
+
+ PdhCollectQueryData(QueryHandle);
+
+ if (!HasPreviousSample)
+ {
+ HasPreviousSample = true;
+ return 0.0f;
+ }
+
+ PDH_FMT_COUNTERVALUE CounterValue;
+ if (PdhGetFormattedCounterValue(CounterHandle, PDH_FMT_DOUBLE, nullptr, &CounterValue) == ERROR_SUCCESS)
+ {
+ return static_cast<float>(CounterValue.doubleValue);
+ }
+
+ return 0.0f;
+ }
+};
+
+#elif ZEN_PLATFORM_LINUX
+
+struct CpuSampler
+{
+ ProcStatCpuSampler ProcStat;
+
+ float Sample() { return ProcStat.Sample(); }
+};
+
+#elif ZEN_PLATFORM_MAC
+
+struct CpuSampler
+{
+ unsigned long PrevTotalTicks = 0;
+ unsigned long PrevIdleTicks = 0;
+
+ float Sample()
+ {
+ float CpuUsage = 0.0f;
+
+ host_cpu_load_info_data_t CpuLoad;
+ mach_msg_type_number_t Count = sizeof(CpuLoad) / sizeof(natural_t);
+ if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&CpuLoad, &Count) == KERN_SUCCESS)
+ {
+ unsigned long TotalTicks = 0;
+ for (int i = 0; i < CPU_STATE_MAX; ++i)
+ {
+ TotalTicks += CpuLoad.cpu_ticks[i];
+ }
+ unsigned long IdleTicks = CpuLoad.cpu_ticks[CPU_STATE_IDLE];
+
+ unsigned long TotalDelta = TotalTicks - PrevTotalTicks;
+ unsigned long IdleDelta = IdleTicks - PrevIdleTicks;
+
+ if (TotalDelta > 0 && PrevTotalTicks > 0)
+ {
+ CpuUsage = 100.0f * (TotalDelta - IdleDelta) / TotalDelta;
+ }
+
+ PrevTotalTicks = TotalTicks;
+ PrevIdleTicks = IdleTicks;
+ }
+
+ return CpuUsage;
+ }
+};
+
+#endif
+
+struct SystemMetricsTracker::Impl
+{
+ using Clock = std::chrono::steady_clock;
+
+ std::mutex Mutex;
+ CpuSampler Sampler;
+ float CachedCpuPercent = 0.0f;
+ Clock::time_point NextSampleTime = Clock::now();
+ std::chrono::milliseconds MinInterval;
+
+ explicit Impl(std::chrono::milliseconds InMinInterval) : MinInterval(InMinInterval) {}
+
+ float SampleCpu()
+ {
+ const auto Now = Clock::now();
+ if (Now >= NextSampleTime)
+ {
+ CachedCpuPercent = Sampler.Sample();
+ NextSampleTime = Now + MinInterval;
+ }
+ return CachedCpuPercent;
+ }
+};
+
+SystemMetricsTracker::SystemMetricsTracker(std::chrono::milliseconds MinInterval) : m_Impl(std::make_unique<Impl>(MinInterval))
+{
+}
+
+SystemMetricsTracker::~SystemMetricsTracker() = default;
+
+ExtendedSystemMetrics
+SystemMetricsTracker::Query()
+{
+ ExtendedSystemMetrics Metrics;
+ static_cast<SystemMetrics&>(Metrics) = GetSystemMetrics();
+
+ std::lock_guard Lock(m_Impl->Mutex);
+ Metrics.CpuUsagePercent = m_Impl->SampleCpu();
+ return Metrics;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
std::string_view
GetOperatingSystemName()
{
return ZEN_PLATFORM_NAME;
}
+std::string
+GetOperatingSystemVersion()
+{
+#if ZEN_PLATFORM_WINDOWS
+ // Use RtlGetVersion to avoid the compatibility shim that GetVersionEx applies
+ using RtlGetVersionFn = LONG(WINAPI*)(PRTL_OSVERSIONINFOW);
+ RTL_OSVERSIONINFOW OsVer{.dwOSVersionInfoSize = sizeof(OsVer)};
+ if (auto Fn = (RtlGetVersionFn)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetVersion"))
+ {
+ Fn(&OsVer);
+ }
+ return fmt::format("Windows {}.{} Build {}", OsVer.dwMajorVersion, OsVer.dwMinorVersion, OsVer.dwBuildNumber);
+#elif ZEN_PLATFORM_LINUX
+ struct utsname Info
+ {
+ };
+ if (uname(&Info) == 0)
+ {
+ return fmt::format("{} {}", Info.sysname, Info.release);
+ }
+ return "Linux";
+#elif ZEN_PLATFORM_MAC
+ char OsVersion[64] = "";
+ size_t Size = sizeof(OsVersion);
+ if (sysctlbyname("kern.osproductversion", OsVersion, &Size, nullptr, 0) == 0)
+ {
+ return fmt::format("macOS {}", OsVersion);
+ }
+ return "macOS";
+#endif
+}
+
+std::string_view
+GetRuntimePlatformName()
+{
+#if ZEN_PLATFORM_WINDOWS
+ if (zen::windows::IsRunningOnWine())
+ {
+ return "wine"sv;
+ }
+ return "windows"sv;
+#elif ZEN_PLATFORM_LINUX
+ return "linux"sv;
+#elif ZEN_PLATFORM_MAC
+ return "macos"sv;
+#else
+ return "unknown"sv;
+#endif
+}
+
std::string_view
GetCpuName()
{
@@ -340,7 +666,14 @@ Describe(const SystemMetrics& Metrics, CbWriter& Writer)
Writer << "cpu_count" << Metrics.CpuCount << "core_count" << Metrics.CoreCount << "lp_count" << Metrics.LogicalProcessorCount
<< "total_memory_mb" << Metrics.SystemMemoryMiB << "avail_memory_mb" << Metrics.AvailSystemMemoryMiB << "total_virtual_mb"
<< Metrics.VirtualMemoryMiB << "avail_virtual_mb" << Metrics.AvailVirtualMemoryMiB << "total_pagefile_mb" << Metrics.PageFileMiB
- << "avail_pagefile_mb" << Metrics.AvailPageFileMiB;
+ << "avail_pagefile_mb" << Metrics.AvailPageFileMiB << "uptime_seconds" << Metrics.UptimeSeconds;
+}
+
+void
+Describe(const ExtendedSystemMetrics& Metrics, CbWriter& Writer)
+{
+ Describe(static_cast<const SystemMetrics&>(Metrics), Writer);
+ Writer << "cpu_usage_percent" << Metrics.CpuUsagePercent;
}
} // namespace zen