From 9ce52ad83a54932495d494c95e976fe3164388c4 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 20 Nov 2022 19:53:45 +0100 Subject: [PATCH] Linux/macOS: Implement overlay CPU/memory statistics (#480) --- src/Cafe/HW/Latte/Core/LatteOverlay.cpp | 109 +++++------------- .../HW/Latte/Core/LattePerformanceMonitor.cpp | 9 +- src/util/CMakeLists.txt | 23 +++- src/util/Fiber/FiberUnix.cpp | 3 - src/util/Fiber/FiberWin.cpp | 3 - src/util/MemMapper/MemMapperUnix.cpp | 3 - src/util/MemMapper/MemMapperWin.cpp | 4 - src/util/SystemInfo/SystemInfo.cpp | 34 ++++++ src/util/SystemInfo/SystemInfo.h | 17 +++ src/util/SystemInfo/SystemInfoLinux.cpp | 48 ++++++++ src/util/SystemInfo/SystemInfoMac.cpp | 62 ++++++++++ src/util/SystemInfo/SystemInfoStub.cpp | 21 ++++ src/util/SystemInfo/SystemInfoUnix.cpp | 15 +++ src/util/SystemInfo/SystemInfoWin.cpp | 69 +++++++++++ 14 files changed, 322 insertions(+), 98 deletions(-) create mode 100644 src/util/SystemInfo/SystemInfo.cpp create mode 100644 src/util/SystemInfo/SystemInfo.h create mode 100644 src/util/SystemInfo/SystemInfoLinux.cpp create mode 100644 src/util/SystemInfo/SystemInfoMac.cpp create mode 100644 src/util/SystemInfo/SystemInfoStub.cpp create mode 100644 src/util/SystemInfo/SystemInfoUnix.cpp create mode 100644 src/util/SystemInfo/SystemInfoWin.cpp diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp index 21b70d25..441ec169 100644 --- a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp @@ -12,30 +12,16 @@ #include "imgui/imgui_extension.h" #include "input/InputManager.h" +#include "util/SystemInfo/SystemInfo.h" #include -#if BOOST_OS_WINDOWS -#include -#include -#pragma comment(lib, "ntdll.lib") -#endif - struct OverlayStats { OverlayStats() {}; int processor_count = 1; - - // cemu cpu stats - uint64_t last_cpu{}, kernel{}, user{}; - - // global cpu stats - struct ProcessorTime - { - uint64_t idle{}, kernel{}, user{}; - }; - + ProcessorTime processor_time_cemu; std::vector processor_times; double fps{}; @@ -562,19 +548,38 @@ void LatteOverlay_render(bool pad_view) } } - void LatteOverlay_init() { -#if BOOST_OS_WINDOWS - SYSTEM_INFO sys_info; - GetSystemInfo(&sys_info); - g_state.processor_count = sys_info.dwNumberOfProcessors; + g_state.processor_count = GetProcessorCount(); g_state.processor_times.resize(g_state.processor_count); g_state.cpu_per_core.resize(g_state.processor_count); -#else - g_state.processor_count = 1; -#endif +} + +static void UpdateStats_CemuCpu() +{ + ProcessorTime now; + QueryProcTime(now); + + double cpu = ProcessorTime::Compare(g_state.processor_time_cemu, now); + cpu /= g_state.processor_count; + + g_state.cpu_usage = cpu * 100; + g_state.processor_time_cemu = now; +} + +static void UpdateStats_CpuPerCore() +{ + std::vector now(g_state.processor_count); + QueryCoreTimes(g_state.processor_count, now); + + for (int32_t i = 0; i < g_state.processor_count; ++i) + { + double cpu = ProcessorTime::Compare(g_state.processor_times[i], now[i]); + + g_state.cpu_per_core[i] = cpu * 100; + g_state.processor_times[i] = now[i]; + } } void LatteOverlay_updateStats(double fps, sint32 drawcalls) @@ -584,61 +589,11 @@ void LatteOverlay_updateStats(double fps, sint32 drawcalls) g_state.fps = fps; g_state.draw_calls_per_frame = drawcalls; - -#if BOOST_OS_WINDOWS - // update cemu cpu - FILETIME ftime, fkernel, fuser; - LARGE_INTEGER now, kernel, user; - GetSystemTimeAsFileTime(&ftime); - now.LowPart = ftime.dwLowDateTime; - now.HighPart = ftime.dwHighDateTime; - - GetProcessTimes(GetCurrentProcess(), &ftime, &ftime, &fkernel, &fuser); - kernel.LowPart = fkernel.dwLowDateTime; - kernel.HighPart = fkernel.dwHighDateTime; - - user.LowPart = fuser.dwLowDateTime; - user.HighPart = fuser.dwHighDateTime; - - double percent = (kernel.QuadPart - g_state.kernel) + (user.QuadPart - g_state.user); - percent /= (now.QuadPart - g_state.last_cpu); - percent /= g_state.processor_count; - g_state.cpu_usage = percent * 100; - g_state.last_cpu = now.QuadPart; - g_state.user = user.QuadPart; - g_state.kernel = kernel.QuadPart; - - // update cpu per core - std::vector sppi(g_state.processor_count); - if (NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, sppi.data(), sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * g_state.processor_count, nullptr))) - { - for (sint32 i = 0; i < g_state.processor_count; ++i) - { - const uint64 kernel_diff = sppi[i].KernelTime.QuadPart - g_state.processor_times[i].kernel; - const uint64 user_diff = sppi[i].UserTime.QuadPart - g_state.processor_times[i].user; - const uint64 idle_diff = sppi[i].IdleTime.QuadPart - g_state.processor_times[i].idle; - - const auto total = kernel_diff + user_diff; // kernel time already includes idletime - const double cpu = total == 0 ? 0 : (1.0 - ((double)idle_diff / total)) * 100.0; - - g_state.cpu_per_core[i] = cpu; - //total_cpu += cpu; - - g_state.processor_times[i].idle = sppi[i].IdleTime.QuadPart; - g_state.processor_times[i].kernel = sppi[i].KernelTime.QuadPart; - g_state.processor_times[i].user = sppi[i].UserTime.QuadPart; - } - - //total_cpu /= g_state.processor_count; - //g_state.cpu_usage = total_cpu; - } + UpdateStats_CemuCpu(); + UpdateStats_CpuPerCore(); // update ram - PROCESS_MEMORY_COUNTERS pmc{}; - pmc.cb = sizeof(pmc); - GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)); - g_state.ram_usage = (pmc.WorkingSetSize / 1000) / 1000; -#endif + g_state.ram_usage = (QueryRamUsage() / 1000) / 1000; // update vram g_renderer->GetVRAMInfo(g_state.vramUsage, g_state.vramTotal); diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp index 7104dbd9..714758e9 100644 --- a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp @@ -98,7 +98,10 @@ void LattePerformanceMonitor_frameEnd() performanceMonitor.cycle[nextCycleIndex].recompilerLeaveCount = 0; performanceMonitor.cycle[nextCycleIndex].threadLeaveCount = 0; performanceMonitor.cycleIndex = nextCycleIndex; - + + // next update in 1 second + performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate = GetTickCount(); + if (isFirstUpdate) { LatteOverlay_updateStats(0.0, 0); @@ -109,8 +112,6 @@ void LattePerformanceMonitor_frameEnd() LatteOverlay_updateStats(fps, drawCallCounter / elapsedFrames); gui_updateWindowTitles(false, false, fps); } - // next update in 1 second - performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate = GetTickCount(); // prevent hibernation and screen saver/monitor off #if BOOST_OS_WINDOWS @@ -124,4 +125,4 @@ void LattePerformanceMonitor_frameBegin() { performanceMonitor.vk.numDrawBarriersPerFrame.reset(); performanceMonitor.vk.numBeginRenderpassPerFrame.reset(); -} \ No newline at end of file +} diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 06c8f857..5af88176 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -16,8 +16,6 @@ add_library(CemuUtil DXGIWrapper/DXGIWrapper.h EventService.h Fiber/Fiber.h - Fiber/FiberUnix.cpp - Fiber/FiberWin.cpp helpers/ClassWrapper.h helpers/ConcurrentQueue.h helpers/enum_array.hpp @@ -50,8 +48,8 @@ add_library(CemuUtil math/vector2.h math/vector3.h MemMapper/MemMapper.h - MemMapper/MemMapperUnix.cpp - MemMapper/MemMapperWin.cpp + SystemInfo/SystemInfo.cpp + SystemInfo/SystemInfo.h ThreadPool/ThreadPool.cpp ThreadPool/ThreadPool.h tinyxml2/tinyxml2.cpp @@ -71,6 +69,23 @@ add_library(CemuUtil Zir/Passes/ZpIRRegisterAllocator.cpp ) +if(WIN32) + target_sources(CemuUtil PRIVATE Fiber/FiberWin.cpp) + target_sources(CemuUtil PRIVATE MemMapper/MemMapperWin.cpp) + target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoWin.cpp) +elseif(UNIX) + target_sources(CemuUtil PRIVATE Fiber/FiberUnix.cpp) + target_sources(CemuUtil PRIVATE MemMapper/MemMapperUnix.cpp) + target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoUnix.cpp) + if(NOT APPLE) + target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoLinux.cpp) + else() + target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoMac.cpp) + endif() +else() + target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoStub.cpp) +endif() + set_property(TARGET CemuUtil PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuUtil PUBLIC "../") diff --git a/src/util/Fiber/FiberUnix.cpp b/src/util/Fiber/FiberUnix.cpp index c2ced28b..7d3bf05a 100644 --- a/src/util/Fiber/FiberUnix.cpp +++ b/src/util/Fiber/FiberUnix.cpp @@ -1,5 +1,4 @@ #include "Fiber.h" -#if BOOST_OS_LINUX || BOOST_OS_MACOS #include thread_local Fiber* sCurrentFiber{}; @@ -52,5 +51,3 @@ void* Fiber::GetFiberPrivateData() { return sCurrentFiber->m_privateData; } - -#endif \ No newline at end of file diff --git a/src/util/Fiber/FiberWin.cpp b/src/util/Fiber/FiberWin.cpp index af7df700..ae5da517 100644 --- a/src/util/Fiber/FiberWin.cpp +++ b/src/util/Fiber/FiberWin.cpp @@ -1,5 +1,4 @@ #include "Fiber.h" -#if BOOST_OS_WINDOWS #include thread_local Fiber* sCurrentFiber{}; @@ -39,5 +38,3 @@ void* Fiber::GetFiberPrivateData() { return sCurrentFiber->m_privateData; } - -#endif \ No newline at end of file diff --git a/src/util/MemMapper/MemMapperUnix.cpp b/src/util/MemMapper/MemMapperUnix.cpp index 0ea52aa0..0ade291d 100644 --- a/src/util/MemMapper/MemMapperUnix.cpp +++ b/src/util/MemMapper/MemMapperUnix.cpp @@ -1,6 +1,5 @@ #include "util/MemMapper/MemMapper.h" -#if BOOST_OS_LINUX || BOOST_OS_MACOS #include #include @@ -65,5 +64,3 @@ namespace MemMapper } }; - -#endif \ No newline at end of file diff --git a/src/util/MemMapper/MemMapperWin.cpp b/src/util/MemMapper/MemMapperWin.cpp index 5c07949f..0e8d3496 100644 --- a/src/util/MemMapper/MemMapperWin.cpp +++ b/src/util/MemMapper/MemMapperWin.cpp @@ -1,7 +1,5 @@ #include "util/MemMapper/MemMapper.h" -#if BOOST_OS_WINDOWS - #include namespace MemMapper @@ -63,5 +61,3 @@ namespace MemMapper } }; - -#endif \ No newline at end of file diff --git a/src/util/SystemInfo/SystemInfo.cpp b/src/util/SystemInfo/SystemInfo.cpp new file mode 100644 index 00000000..afda4e8b --- /dev/null +++ b/src/util/SystemInfo/SystemInfo.cpp @@ -0,0 +1,34 @@ +#include "util/SystemInfo/SystemInfo.h" + +uint64 ProcessorTime::work() +{ + return user + kernel; +} + +uint64 ProcessorTime::total() +{ + return idle + user + kernel; +} + +double ProcessorTime::Compare(ProcessorTime &last, ProcessorTime &now) +{ + auto dwork = now.work() - last.work(); + auto dtotal = now.total() - last.total(); + + return (double)dwork / dtotal; +} + +uint32 GetProcessorCount() +{ + return std::thread::hardware_concurrency(); +} + +void QueryProcTime(ProcessorTime &out) +{ + uint64 now, user, kernel; + QueryProcTime(now, user, kernel); + + out.idle = now - (user + kernel); + out.kernel = kernel; + out.user = user; +} \ No newline at end of file diff --git a/src/util/SystemInfo/SystemInfo.h b/src/util/SystemInfo/SystemInfo.h new file mode 100644 index 00000000..72ce98db --- /dev/null +++ b/src/util/SystemInfo/SystemInfo.h @@ -0,0 +1,17 @@ +#pragma once + +struct ProcessorTime +{ + uint64 idle{}, kernel{}, user{}; + + uint64 work(); + uint64 total(); + + static double Compare(ProcessorTime &last, ProcessorTime &now); +}; + +uint32 GetProcessorCount(); +uint64 QueryRamUsage(); +void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel); +void QueryProcTime(ProcessorTime &out); +void QueryCoreTimes(uint32 count, std::vector& out); diff --git a/src/util/SystemInfo/SystemInfoLinux.cpp b/src/util/SystemInfo/SystemInfoLinux.cpp new file mode 100644 index 00000000..a84b4eeb --- /dev/null +++ b/src/util/SystemInfo/SystemInfoLinux.cpp @@ -0,0 +1,48 @@ +#include "util/SystemInfo/SystemInfo.h" + +#include + +uint64 QueryRamUsage() +{ + static long page_size = sysconf(_SC_PAGESIZE); + if (page_size == -1) + { + return 0; + } + + std::ifstream file("/proc/self/statm"); + if (file) + { + file.ignore(std::numeric_limits::max(), ' '); + uint64 pages; + file >> pages; + + return pages * page_size; + } + return 0; +} + +void QueryCoreTimes(uint32 count, std::vector& out) +{ + std::ifstream file("/proc/stat"); + if (file) + { + file.ignore(std::numeric_limits::max(), '\n'); + + for (auto i = 0; i < out.size(); ++i) + { + uint64 user, nice, kernel, idle; + file.ignore(std::numeric_limits::max(), ' '); + file >> user >> nice >> kernel >> idle; + file.ignore(std::numeric_limits::max(), '\n'); + + out[i].idle = idle; + out[i].kernel = kernel; + out[i].user = user + nice; + } + } + else + { + for (auto i = 0; i < count; ++i) out[i] = { }; + } +} diff --git a/src/util/SystemInfo/SystemInfoMac.cpp b/src/util/SystemInfo/SystemInfoMac.cpp new file mode 100644 index 00000000..608f1ffc --- /dev/null +++ b/src/util/SystemInfo/SystemInfoMac.cpp @@ -0,0 +1,62 @@ +#include "util/SystemInfo/SystemInfo.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +// borrowed from https://en.wikichip.org/wiki/resident_set_size#OS_X +uint64 QueryRamUsage() +{ + mach_task_basic_info info; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS) + return info.resident_size; + return 0; +} + +// apple official documentation is non-existsent. +// based on https://github.com/giampaolo/psutil/blob/master/psutil/_psutil_osx.c#L623 +void QueryCoreTimes(uint32 count, std::vector& out) +{ + // initialize default + for (auto i = 0; i < out.size(); ++i) + { + out[i] = {}; + } + + natural_t cpu_count; + processor_info_array_t info_array; + mach_msg_type_number_t info_count; + kern_return_t error; + + mach_port_t host_port = mach_host_self(); + error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info_array, &info_count); + mach_port_deallocate(mach_task_self(), host_port); + + if (error != KERN_SUCCESS) + return; + + processor_cpu_load_info_data_t* cpuLoad = (processor_cpu_load_info_data_t*) info_array; + + for (auto i = 0; i < cpu_count; ++i) + { + uint64 system = cpuLoad[i].cpu_ticks[CPU_STATE_SYSTEM]; + uint64 user = cpuLoad[i].cpu_ticks[CPU_STATE_USER] + cpuLoad[i].cpu_ticks[CPU_STATE_NICE]; + uint64 idle = cpuLoad[i].cpu_ticks[CPU_STATE_IDLE]; + + out[i].idle = idle; + out[i].kernel = system; + out[i].user = user; + } + + int ret = vm_deallocate(mach_task_self(), (vm_address_t) info_array, + info_count * sizeof(int)); + if (ret != KERN_SUCCESS) + cemuLog_force("vm_deallocate() failed"); +} diff --git a/src/util/SystemInfo/SystemInfoStub.cpp b/src/util/SystemInfo/SystemInfoStub.cpp new file mode 100644 index 00000000..b6d1fc84 --- /dev/null +++ b/src/util/SystemInfo/SystemInfoStub.cpp @@ -0,0 +1,21 @@ +#include "util/SystemInfo/SystemInfo.h" + +uint64 QueryRamUsage() +{ + return 0; +} + +void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel) +{ + out_now = 0; + out_user = 0; + out_kernel = 0; +} + +void QueryCoreTimes(uint32 count, std::vector& out) +{ + for (auto i = 0; i < out.size(); ++i) + { + out[i] = { }; + } +} diff --git a/src/util/SystemInfo/SystemInfoUnix.cpp b/src/util/SystemInfo/SystemInfoUnix.cpp new file mode 100644 index 00000000..87bd9f8c --- /dev/null +++ b/src/util/SystemInfo/SystemInfoUnix.cpp @@ -0,0 +1,15 @@ +#include "util/SystemInfo/SystemInfo.h" + +#include + +void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel) +{ + struct tms time_info; + clock_t clock_now = times(&time_info); + clock_t clock_user = time_info.tms_utime; + clock_t clock_kernel = time_info.tms_stime; + out_now = static_cast(clock_now); + out_user = static_cast(clock_user); + out_kernel = static_cast(clock_kernel); +} + diff --git a/src/util/SystemInfo/SystemInfoWin.cpp b/src/util/SystemInfo/SystemInfoWin.cpp new file mode 100644 index 00000000..137e11b7 --- /dev/null +++ b/src/util/SystemInfo/SystemInfoWin.cpp @@ -0,0 +1,69 @@ +#include "util/SystemInfo/SystemInfo.h" + +#include +#include +#pragma comment(lib, "ntdll.lib") + +uint64 QueryRamUsage() +{ + PROCESS_MEMORY_COUNTERS pmc{}; + pmc.cb = sizeof(pmc); + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) + { + return pmc.WorkingSetSize; + } + else + { + return 0; + } +} + +void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel) +{ + FILETIME ftime, fkernel, fuser; + LARGE_INTEGER now, kernel, user; + GetSystemTimeAsFileTime(&ftime); + now.LowPart = ftime.dwLowDateTime; + now.HighPart = ftime.dwHighDateTime; + + if (GetProcessTimes(GetCurrentProcess(), &ftime, &ftime, &fkernel, &fuser)) + { + kernel.LowPart = fkernel.dwLowDateTime; + kernel.HighPart = fkernel.dwHighDateTime; + + user.LowPart = fuser.dwLowDateTime; + user.HighPart = fuser.dwHighDateTime; + + out_now = now.QuadPart; + out_user = user.QuadPart; + out_kernel = kernel.QuadPart; + } + else + { + out_now = 0; + out_user = 0; + out_kernel = 0; + } +} + +void QueryCoreTimes(uint32 count, std::vector& out) +{ + std::vector sppi(count); + if (NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, sppi.data(), sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * count, nullptr))) + { + for (auto i = 0; i < out.size(); ++i) + { + out[i].idle = sppi[i].IdleTime.QuadPart; + out[i].kernel = sppi[i].KernelTime.QuadPart; + out[i].kernel -= out[i].idle; + out[i].user = sppi[i].UserTime.QuadPart; + } + } + else + { + for (auto i = 0; i < count; ++i) + { + out[i] = { }; + } + } +}