From 3726c90c7a0b7fc40d349690634e2cb40a150eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hor=C3=A1=C4=8Dek?= Date: Thu, 12 Nov 2020 22:27:35 +0100 Subject: [PATCH] Add cpu_power --- README.md | 2 +- bin/MangoHud.conf | 1 + src/cpu.cpp | 294 +++++++++++++++++++++++++++++++++++++++++ src/cpu.h | 71 ++++++++++ src/hud_elements.cpp | 10 +- src/overlay.cpp | 2 + src/overlay_params.cpp | 1 + src/overlay_params.h | 1 + src/vulkan.cpp | 2 + 9 files changed, 382 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0781683c..d44e4b01 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ Parameters that are enabled by default have to be explicitly disabled. These (cu | `log_interval` | Change the default log interval, `100` is default | | `vulkan_driver` | Displays used vulkan driver, radv/amdgpu-pro/amdvlk | | `gpu_name` | Displays GPU name from pci.ids | -| `gpu_power` | Display GPU draw in watts | +| `cpu_power`
`gpu_power` | Display CPU/GPU draw in watts | | `engine_version` | Display OpenGL or vulkan and vulkan-based render engine's version | | `permit_upload` | Allow uploading of logs to Flightlessmango.com | | `upload_log` | Change keybind for uploading log | diff --git a/bin/MangoHud.conf b/bin/MangoHud.conf index 4eba14eb..a8be6995 100644 --- a/bin/MangoHud.conf +++ b/bin/MangoHud.conf @@ -19,6 +19,7 @@ ### Display the current CPU information cpu_stats # cpu_temp +# cpu_power # cpu_text = "CPU" ### Display the current GPU information diff --git a/src/cpu.cpp b/src/cpu.cpp index 6799b1b3..6d106e54 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -240,6 +240,113 @@ bool CPUStats::UpdateCpuTemp() { return ret; } +static bool get_cpu_power_k10temp(CPUPowerData* cpuPowerData, int& power) { + CPUPowerData_k10temp* powerData_k10temp = (CPUPowerData_k10temp*)cpuPowerData; + + if (!powerData_k10temp->coreVoltageFile || !powerData_k10temp->coreCurrentFile || !powerData_k10temp->socVoltageFile || !powerData_k10temp->socCurrentFile) + return false; + + rewind(powerData_k10temp->coreVoltageFile); + rewind(powerData_k10temp->coreCurrentFile); + rewind(powerData_k10temp->socVoltageFile); + rewind(powerData_k10temp->socCurrentFile); + + fflush(powerData_k10temp->coreVoltageFile); + fflush(powerData_k10temp->coreCurrentFile); + fflush(powerData_k10temp->socVoltageFile); + fflush(powerData_k10temp->socCurrentFile); + + int coreVoltage, coreCurrent; + int socVoltage, socCurrent; + + if (fscanf(powerData_k10temp->coreVoltageFile, "%d", &coreVoltage) != 1) + return false; + if (fscanf(powerData_k10temp->coreCurrentFile, "%d", &coreCurrent) != 1) + return false; + if (fscanf(powerData_k10temp->socVoltageFile, "%d", &socVoltage) != 1) + return false; + if (fscanf(powerData_k10temp->socCurrentFile, "%d", &socCurrent) != 1) + return false; + + power = (coreVoltage * coreCurrent + socVoltage * socCurrent) / 1000000; + + return true; +} + +static bool get_cpu_power_zenpower(CPUPowerData* cpuPowerData, int& power) { + CPUPowerData_zenpower* powerData_zenpower = (CPUPowerData_zenpower*)cpuPowerData; + + if (!powerData_zenpower->corePowerFile || !powerData_zenpower->socPowerFile) + return false; + + rewind(powerData_zenpower->corePowerFile); + rewind(powerData_zenpower->socPowerFile); + + fflush(powerData_zenpower->corePowerFile); + fflush(powerData_zenpower->socPowerFile); + + int corePower, socPower; + + if (fscanf(powerData_zenpower->corePowerFile, "%d", &corePower) != 1) + return false; + if (fscanf(powerData_zenpower->socPowerFile, "%d", &socPower) != 1) + return false; + + power = (corePower + socPower) / 1000000; + + return true; +} + +static bool get_cpu_power_rapl(CPUPowerData* cpuPowerData, int& power) { + CPUPowerData_rapl* powerData_rapl = (CPUPowerData_rapl*)cpuPowerData; + + if (!powerData_rapl->energyCounterFile) + return false; + + rewind(powerData_rapl->energyCounterFile); + fflush(powerData_rapl->energyCounterFile); + + int energyCounterValue = 0; + if (fscanf(powerData_rapl->energyCounterFile, "%d", &energyCounterValue) != 1) + return false; + + Clock::time_point now = Clock::now(); + Clock::duration timeDiff = now - powerData_rapl->lastCounterValueTime; + int energyCounterDiff = energyCounterValue - powerData_rapl->lastCounterValue; + + power = (int)((float)energyCounterDiff / (float)timeDiff.count() * 1000); + + powerData_rapl->lastCounterValue = energyCounterValue; + powerData_rapl->lastCounterValueTime = now; + + return true; +} + +bool CPUStats::UpdateCpuPower() { + if(!m_cpuPowerData) + return false; + + int power = 0; + + switch(m_cpuPowerData->source) { + case CPU_POWER_K10TEMP: + if (!get_cpu_power_k10temp(m_cpuPowerData.get(), power)) return false; + break; + case CPU_POWER_ZENPOWER: + if (!get_cpu_power_zenpower(m_cpuPowerData.get(), power)) return false; + break; + case CPU_POWER_RAPL: + if (!get_cpu_power_rapl(m_cpuPowerData.get(), power)) return false; + break; + default: + return false; + } + + m_cpuDataTotal.power = power; + + return true; +} + static bool find_temp_input(const std::string path, std::string& input, const std::string& name) { auto files = ls(path.c_str(), "temp", LS_FILES); @@ -321,4 +428,191 @@ bool CPUStats::GetCpuFile() { return true; } +static bool find_voltage_input(const std::string path, std::string& input, const std::string& name) +{ + auto files = ls(path.c_str(), "in", LS_FILES); + for (auto& file : files) { + if (!ends_with(file, "_label")) + continue; + + auto label = read_line(path + "/" + file); + if (label != name) + continue; + + auto uscore = file.find_first_of("_"); + if (uscore != std::string::npos) { + file.erase(uscore, std::string::npos); + input = path + "/" + file + "_input"; + return true; + } + } + return false; +} + +static bool find_current_input(const std::string path, std::string& input, const std::string& name) +{ + auto files = ls(path.c_str(), "curr", LS_FILES); + for (auto& file : files) { + if (!ends_with(file, "_label")) + continue; + + auto label = read_line(path + "/" + file); + if (label != name) + continue; + + auto uscore = file.find_first_of("_"); + if (uscore != std::string::npos) { + file.erase(uscore, std::string::npos); + input = path + "/" + file + "_input"; + return true; + } + } + return false; +} + +static bool find_power_input(const std::string path, std::string& input, const std::string& name) +{ + auto files = ls(path.c_str(), "power", LS_FILES); + for (auto& file : files) { + if (!ends_with(file, "_label")) + continue; + + auto label = read_line(path + "/" + file); + if (label != name) + continue; + + auto uscore = file.find_first_of("_"); + if (uscore != std::string::npos) { + file.erase(uscore, std::string::npos); + input = path + "/" + file + "_input"; + return true; + } + } + return false; +} + +CPUPowerData_k10temp* init_cpu_power_data_k10temp(const std::string path) { + CPUPowerData_k10temp* powerData = new CPUPowerData_k10temp(); + + std::string coreVoltageInput, coreCurrentInput; + std::string socVoltageInput, socCurrentInput; + + if(!find_voltage_input(path, coreVoltageInput, "Vcore")) goto error; + if(!find_current_input(path, coreCurrentInput, "Icore")) goto error; + if(!find_voltage_input(path, socVoltageInput, "Vsoc")) goto error; + if(!find_current_input(path, socCurrentInput, "Isoc")) goto error; + +#ifndef NDEBUG + std::cerr << "hwmon: using input: " << coreVoltageInput << std::endl; + std::cerr << "hwmon: using input: " << coreCurrentInput << std::endl; + std::cerr << "hwmon: using input: " << socVoltageInput << std::endl; + std::cerr << "hwmon: using input: " << socCurrentInput << std::endl; +#endif + + powerData->coreVoltageFile = fopen(coreVoltageInput.c_str(), "r"); + powerData->coreCurrentFile = fopen(coreCurrentInput.c_str(), "r"); + powerData->socVoltageFile = fopen(socVoltageInput.c_str(), "r"); + powerData->socCurrentFile = fopen(socCurrentInput.c_str(), "r"); + goto success; + +error: + delete powerData; + return nullptr; + +success: + return powerData; +} + +CPUPowerData_zenpower* init_cpu_power_data_zenpower(const std::string path) { + CPUPowerData_zenpower* powerData = new CPUPowerData_zenpower(); + + std::string corePowerInput, socPowerInput; + + if(!find_power_input(path, corePowerInput, "SVI2_P_Core")) goto error; + if(!find_power_input(path, socPowerInput, "SVI2_P_SoC")) goto error; + +#ifndef NDEBUG + std::cerr << "hwmon: using input: " << corePowerInput << std::endl; + std::cerr << "hwmon: using input: " << socPowerInput << std::endl; +#endif + + powerData->corePowerFile = fopen(corePowerInput.c_str(), "r"); + powerData->socPowerFile = fopen(socPowerInput.c_str(), "r"); + goto success; + +error: + delete powerData; + return nullptr; + +success: + return powerData; +} + +CPUPowerData_rapl* init_cpu_power_data_rapl(const std::string path) { + CPUPowerData_rapl* powerData = new CPUPowerData_rapl(); + + std::string energyCounterPath = path + "/energy_uj"; + if (!file_exists(energyCounterPath)) goto error; + + powerData->energyCounterFile = fopen(energyCounterPath.c_str(), "r"); + goto success; + +error: + delete powerData; + return nullptr; + +success: + return powerData; +} + +bool CPUStats::InitCpuPowerData() { + if(m_cpuPowerData != nullptr) + return true; + + std::string name, path; + std::string hwmon = "/sys/class/hwmon/"; + + CPUPowerData* cpuPowerData = nullptr; + + auto dirs = ls(hwmon.c_str()); + for (auto& dir : dirs) { + path = hwmon + dir; + name = read_line(path + "/name"); +#ifndef NDEBUG + std::cerr << "hwmon: sensor name: " << name << std::endl; +#endif + if (name == "k10temp") { + cpuPowerData = (CPUPowerData*)init_cpu_power_data_k10temp(path); + break; + } else if (name == "zenpower") { + cpuPowerData = (CPUPowerData*)init_cpu_power_data_zenpower(path); + break; + } + } + + if (!cpuPowerData) { + std::string powercap = "/sys/class/powercap/"; + auto powercap_dirs = ls(powercap.c_str()); + for (auto& dir : powercap_dirs) { + path = powercap + dir; + name = read_line(path + "/name"); +#ifndef NDEBUG + std::cerr << "powercap: name: " << name << std::endl; +#endif + if (name == "package-0") { + cpuPowerData = (CPUPowerData*)init_cpu_power_data_rapl(path); + break; + } + } + } + + if(cpuPowerData == nullptr) { + std::cerr << "MANGOHUD: Failed to initialize CPU power data" << std::endl; + return false; + } + + m_cpuPowerData.reset(cpuPowerData); + return true; +} + CPUStats cpuStats; diff --git a/src/cpu.h b/src/cpu.h index 29124b22..53943acf 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -5,6 +5,9 @@ #include #include #include +#include + +#include "timing.hpp" typedef struct CPUData_ { unsigned long long int totalTime; @@ -36,8 +39,73 @@ typedef struct CPUData_ { int mhz; int temp; int cpu_mhz; + int power; } CPUData; +enum { + CPU_POWER_K10TEMP, + CPU_POWER_ZENPOWER, + CPU_POWER_RAPL +}; + +struct CPUPowerData { + int source; +}; + +struct CPUPowerData_k10temp : public CPUPowerData { + CPUPowerData_k10temp() { + this->source = CPU_POWER_K10TEMP; + }; + + ~CPUPowerData_k10temp() { + if(this->coreVoltageFile) + fclose(this->coreVoltageFile); + if(this->coreCurrentFile) + fclose(this->coreCurrentFile); + if(this->socVoltageFile) + fclose(this->socVoltageFile); + if(this->socCurrentFile) + fclose(this->socCurrentFile); + }; + + FILE* coreVoltageFile; + FILE* coreCurrentFile; + FILE* socVoltageFile; + FILE* socCurrentFile; +}; + +struct CPUPowerData_zenpower : public CPUPowerData { + CPUPowerData_zenpower() { + this->source = CPU_POWER_ZENPOWER; + }; + + ~CPUPowerData_zenpower() { + if(this->corePowerFile) + fclose(this->corePowerFile); + if(this->socPowerFile) + fclose(this->socPowerFile); + }; + + FILE* corePowerFile; + FILE* socPowerFile; +}; + +struct CPUPowerData_rapl : public CPUPowerData { + CPUPowerData_rapl() { + this->source = CPU_POWER_RAPL; + this->lastCounterValueTime = Clock::now(); + }; + + ~CPUPowerData_rapl() { + if(this->energyCounterFile) + fclose(this->energyCounterFile); + }; + + FILE* energyCounterFile; + int lastCounterValue; + Clock::time_point lastCounterValueTime; +}; + class CPUStats { public: @@ -52,7 +120,9 @@ public: bool UpdateCPUData(); bool UpdateCoreMhz(); bool UpdateCpuTemp(); + bool UpdateCpuPower(); bool GetCpuFile(); + bool InitCpuPowerData(); double GetCPUPeriod() { return m_cpuPeriod; } const std::vector& GetCPUData() const { @@ -70,6 +140,7 @@ private: bool m_updatedCPUs = false; // TODO use caching or just update? bool m_inited = false; FILE *m_cpuTempFile = nullptr; + std::unique_ptr m_cpuPowerData; }; extern CPUStats cpuStats; diff --git a/src/hud_elements.cpp b/src/hud_elements.cpp index 1dd071a2..a03631c0 100644 --- a/src/hud_elements.cpp +++ b/src/hud_elements.cpp @@ -115,7 +115,7 @@ void HudElements::cpu_stats(){ ImGui::SameLine(0, 1.0f); ImGui::Text("°C"); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_cpu_mhz]) + if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_cpu_mhz] || HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_cpu_power]) ImGui::TableNextRow(); if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_cpu_mhz]){ ImGui::TableNextCell(); @@ -125,6 +125,14 @@ void HudElements::cpu_stats(){ ImGui::Text("MHz"); ImGui::PopFont(); } + if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_cpu_power]){ + ImGui::TableNextCell(); + right_aligned_text(HUDElements.sw_stats->colors.text, HUDElements.ralign_width, "%i", cpuStats.GetCPUDataTotal().power); + ImGui::SameLine(0, 1.0f); + ImGui::PushFont(HUDElements.sw_stats->font1); + ImGui::Text("W"); + ImGui::PopFont(); + } } } diff --git a/src/overlay.cpp b/src/overlay.cpp index 6569478c..a03015fa 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -26,6 +26,8 @@ void update_hw_info(struct swapchain_stats& sw_stats, struct overlay_params& par cpuStats.UpdateCoreMhz(); if (params.enabled[OVERLAY_PARAM_ENABLED_cpu_temp] || logger->is_active()) cpuStats.UpdateCpuTemp(); + if (params.enabled[OVERLAY_PARAM_ENABLED_cpu_power]) + cpuStats.UpdateCpuPower(); #endif } if (params.enabled[OVERLAY_PARAM_ENABLED_gpu_stats] || logger->is_active()) { diff --git a/src/overlay_params.cpp b/src/overlay_params.cpp index 6a8a3fb4..68ea1e81 100644 --- a/src/overlay_params.cpp +++ b/src/overlay_params.cpp @@ -485,6 +485,7 @@ parse_overlay_config(struct overlay_params *params, params->enabled[OVERLAY_PARAM_ENABLED_frame_timing] = true; params->enabled[OVERLAY_PARAM_ENABLED_core_load] = false; params->enabled[OVERLAY_PARAM_ENABLED_cpu_temp] = false; + params->enabled[OVERLAY_PARAM_ENABLED_cpu_power] = false; params->enabled[OVERLAY_PARAM_ENABLED_gpu_temp] = false; params->enabled[OVERLAY_PARAM_ENABLED_cpu_stats] = true; params->enabled[OVERLAY_PARAM_ENABLED_gpu_stats] = true; diff --git a/src/overlay_params.h b/src/overlay_params.h index d3da03d1..46f0793d 100644 --- a/src/overlay_params.h +++ b/src/overlay_params.h @@ -29,6 +29,7 @@ typedef unsigned long KeySym; OVERLAY_PARAM_BOOL(frame_timing) \ OVERLAY_PARAM_BOOL(core_load) \ OVERLAY_PARAM_BOOL(cpu_temp) \ + OVERLAY_PARAM_BOOL(cpu_power) \ OVERLAY_PARAM_BOOL(gpu_temp) \ OVERLAY_PARAM_BOOL(cpu_stats) \ OVERLAY_PARAM_BOOL(gpu_stats) \ diff --git a/src/vulkan.cpp b/src/vulkan.cpp index 98b96fbd..3c2c8b5c 100644 --- a/src/vulkan.cpp +++ b/src/vulkan.cpp @@ -466,6 +466,8 @@ void init_cpu_stats(overlay_params& params) && enabled[OVERLAY_PARAM_ENABLED_cpu_stats]; enabled[OVERLAY_PARAM_ENABLED_cpu_temp] = cpuStats.GetCpuFile() && enabled[OVERLAY_PARAM_ENABLED_cpu_temp]; + enabled[OVERLAY_PARAM_ENABLED_cpu_power] = cpuStats.InitCpuPowerData() + && enabled[OVERLAY_PARAM_ENABLED_cpu_power]; #endif }