From db0514fa325eecd3c3e8ff1357e2cde60c8da3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 16 Aug 2025 17:10:13 +0200 Subject: [PATCH] feat: display CPU power draw & fix GPU+load avg overwriting core info This change was based on YuriiShkrobut's initial PR from here: https://github.com/aristocratos/btop/pull/595 Co-authored-by: Yurii Shkrobut --- Makefile | 1 + README.md | 3 ++ src/btop_config.cpp | 3 ++ src/btop_draw.cpp | 57 +++++++++++++++++++++--------------- src/btop_menu.cpp | 7 +++++ src/btop_shared.hpp | 3 +- src/freebsd/btop_collect.cpp | 2 +- src/linux/btop_collect.cpp | 57 ++++++++++++++++++++++++++++++++++++ src/netbsd/btop_collect.cpp | 2 +- src/openbsd/btop_collect.cpp | 2 +- src/osx/btop_collect.cpp | 2 +- 11 files changed, 111 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 2681aa7..ff89984 100644 --- a/Makefile +++ b/Makefile @@ -334,6 +334,7 @@ setcap: @printf "\033[1;97mFile: $(DESTDIR)$(PREFIX)/bin/btop\n" @printf "\033[1;92mSetting capabilities...\033[0m\n" @setcap cap_perfmon=+ep $(DESTDIR)$(PREFIX)/bin/btop + @setcap cap_dac_read_search=+ep $(DESTDIR)$(PREFIX)/bin/btop # With 'rm -v' user will see what files (if any) got removed uninstall: diff --git a/README.md b/README.md index 8cd52ca..256384f 100644 --- a/README.md +++ b/README.md @@ -1402,6 +1402,9 @@ cpu_bottom = False #* Shows the system uptime in the CPU box. show_uptime = True +#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo. +show_cpu_watts = True + #* Show cpu temperature. check_temp = True diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 60a4efb..086c3b2 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -132,6 +132,8 @@ namespace Config { {"show_uptime", "#* Shows the system uptime in the CPU box."}, + {"show_cpu_watts", "#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo."}, + {"check_temp", "#* Show cpu temperature."}, {"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."}, @@ -281,6 +283,7 @@ namespace Config { {"cpu_single_graph", false}, {"cpu_bottom", false}, {"show_uptime", true}, + {"show_cpu_watts", true}, {"check_temp", true}, {"show_coretemp", true}, {"show_cpu_freq", true}, diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 11ad57d..cacccfa 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -519,7 +519,8 @@ namespace Cpu { int x = 1, y = 1, width = 20, height; int b_columns, b_column_size; int b_x, b_y, b_width, b_height; - long unsigned int lavg_str_len = 0; + float max_observed_pwr = 1.0f; + int graph_up_height, graph_low_height; int graph_up_width, graph_low_width; int gpu_meter_width; @@ -538,6 +539,7 @@ namespace Cpu { if (Runner::stopping) return ""; if (force_redraw) redraw = true; bool show_temps = (Config::getB("check_temp") and got_sensors); + bool show_watts = (Config::getB("show_cpu_watts") and supports_watts); auto single_graph = Config::getB("cpu_single_graph"); bool hide_cores = show_temps and (cpu_temp_only or not Config::getB("show_coretemp")); const int extra_width = (hide_cores ? max(6, 6 * b_column_size) : 0); @@ -584,7 +586,6 @@ namespace Cpu { graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0)); graph_low_height = height - 2 - graph_up_height - mid_line; const int button_y = cpu_bottom ? y + height - 1 : y; - lavg_str_len = 0; out += box; //? Buttons on title @@ -681,7 +682,12 @@ namespace Cpu { } #endif - cpu_meter = Draw::Meter{b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11), "cpu"}; + int cpu_meter_width = b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11); + if (show_watts) { + cpu_meter_width -= 6; + } + + cpu_meter = Draw::Meter{cpu_meter_width, "cpu"}; if (mid_line) { out += Mv::to(y + graph_up_height + 1, x) + Fx::ub + Theme::c("cpu_box") + Symbols::div_left + Theme::c("div_line") @@ -815,10 +821,26 @@ namespace Cpu { + temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } + + if (show_watts) { + string cwatts = fmt::format(" {:>4.{}f}", cpu.usage_watts, cpu.usage_watts < 10.0f ? 2 : cpu.usage_watts < 100.0f ? 1 : 0); + string cwatts_post = "W"; + + max_observed_pwr = max(max_observed_pwr, cpu.usage_watts); + out += Theme::g("cached").at(clamp(cpu.usage_watts / max_observed_pwr * 100.0f, 0.0f, 100.0f)) + cwatts + Theme::c("main_fg") + cwatts_post; + } + out += Theme::c("div_line") + Symbols::v_line; } catch (const std::exception& e) { throw std::runtime_error("graphs, clock, meter : " + string{e.what()}); } + int max_row = b_height - 3; // Subtracting one extra row for the load average (and power if enabled) + int n_gpus_to_show = 0; + #ifdef GPU_SUPPORT + n_gpus_to_show = show_gpu ? (gpus.size() - (gpu_always ? 0 : Gpu::shown)) : 0; + #endif + max_row -= n_gpus_to_show; + //? Core text and graphs int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3); if (Shared::coreCount >= 100) core_width++; @@ -843,7 +865,7 @@ namespace Cpu { out += Theme::c("div_line") + Symbols::v_line; - if ((++cy > ceil((double)Shared::coreCount / b_columns) or cy == b_height - 2) and n != Shared::coreCount - 1) { + if ((++cy > ceil((double)Shared::coreCount / b_columns) or cy == max_row) and n != Shared::coreCount - 1) { if (++cc >= b_columns) break; cy = 1; cx = (b_width / b_columns) * cc; } @@ -851,28 +873,17 @@ namespace Cpu { //? Load average if (cy < b_height - 1 and cc <= b_columns) { - string lavg_pre; - int sep = 1; - if (b_column_size == 2 and show_temps) { lavg_pre = "Load AVG: "; sep = 3; } - else if (b_column_size == 2 or (b_column_size == 1 and show_temps)) { lavg_pre = "LAV:"; } - else if (b_column_size == 1 or (b_column_size == 0 and show_temps)) { lavg_pre = "L "; } - string lavg; + cy = b_height - 2 - n_gpus_to_show; + + string load_avg_pre = "Load avg:"; + string load_avg; + for (const auto& val : cpu.load_avg) { - lavg += string(sep, ' ') + (lavg_pre.size() < 3 ? fmt::format("{:.0f}", val) : fmt::format("{:.2f}", val)); + load_avg += fmt::format(" {:.2f}", val); } - string lavg_str = lavg_pre + lavg; - if (lavg_str_len > lavg_str.length()) { - lavg_str += string(lavg_str_len - lavg_str.length(), ' '); - } else { - lavg_str_len = lavg_str.length(); - } - #ifdef GPU_SUPPORT - cy = b_height - 2 - (show_gpu ? (gpus.size() - (gpu_always ? 0 : Gpu::shown)) : 0); - #else - cy = b_height - 2; - #endif - out += Mv::to(b_y + cy, b_x + cx + 1) + Theme::c("main_fg") + lavg_str; + int len = load_avg_pre.size() + load_avg.size(); + out += Mv::to(b_y + cy, b_x + 1) + string(max(b_width - len - 2, 0), ' ') + Theme::c("main_fg") + Fx::b + load_avg_pre + Fx::ub + load_avg; } #ifdef GPU_SUPPORT diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 3645f86..730925c 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -510,6 +510,13 @@ namespace Menu { "\"/uptime\" in the formatting.", "", "True or False."}, + {"show_cpu_watts", + "Shows the CPU power consumption in watts.", + "", + "Requires running `make setcap` or", + "`make setuid` or running with sudo.", + "", + "True or False."}, }, #ifdef GPU_SUPPORT { diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 0c1ff38..c6e537b 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -199,7 +199,7 @@ namespace Gpu { namespace Cpu { extern string box; extern int x, y, width, height, min_width, min_height; - extern bool shown, redraw, got_sensors, cpu_temp_only, has_battery; + extern bool shown, redraw, got_sensors, cpu_temp_only, has_battery, supports_watts; extern string cpuName, cpuHz; extern vector available_fields; extern vector available_sensors; @@ -223,6 +223,7 @@ namespace Cpu { vector> temp; long long temp_max = 0; array load_avg; + float usage_watts = 0; }; //* Collect cpu stats and temperatures diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 1d31d69..063b23f 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -78,7 +78,7 @@ namespace Cpu { vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; - bool got_sensors = false, cpu_temp_only = false; + bool got_sensors = false, cpu_temp_only = false, supports_watts = false; //* Populate found_sensors map bool get_sensors(); diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index eae701c..c687b64 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -78,6 +78,18 @@ using namespace std::literals; // for operator""s using namespace std::chrono_literals; //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- +namespace +{ + +long long get_monotonicTimeUSec() +{ + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return time.tv_sec * 1000000 + time.tv_nsec / 1000; +} + +} + namespace Cpu { vector core_old_totals; vector core_old_idles; @@ -87,6 +99,7 @@ namespace Cpu { fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; bool got_sensors{}; bool cpu_temp_only{}; + bool supports_watts = true; //* Populate found_sensors map bool get_sensors(); @@ -873,6 +886,47 @@ namespace Cpu { return {percent, watts, seconds, status}; } + long long get_cpuConsumptionUJoules() + { + long long consumption = -1; + const auto rapl_power_usage_path = "/sys/class/powercap/intel-rapl:0/energy_uj"; + std::ifstream file(rapl_power_usage_path); + if(file.good()) + { + file >> consumption; + } + return consumption; + } + + float get_cpuConsumptionWatts() + { + static long long previous_usage = 0; + static long long previous_timestamp = 0; + + if (previous_usage == 0) + { + previous_usage = get_cpuConsumptionUJoules(); + previous_timestamp = get_monotonicTimeUSec(); + supports_watts = (previous_usage > 0); + return 0; + } + + if (!supports_watts) + { + return -1; + } + + auto current_timestamp = get_monotonicTimeUSec(); + auto current_usage = get_cpuConsumptionUJoules(); + + auto watts = (float)(current_usage - previous_usage) / (float)(current_timestamp - previous_timestamp); + + previous_timestamp = current_timestamp; + previous_usage = current_usage; + + return watts; + } + auto collect(bool no_update) -> cpu_info& { if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) return current_cpu; auto& cpu = current_cpu; @@ -1010,6 +1064,9 @@ namespace Cpu { if (Config::getB("show_battery") and has_battery) current_bat = get_battery(); + if (Config::getB("show_cpu_watts") and supports_watts) + current_cpu.usage_watts = get_cpuConsumptionWatts(); + return cpu; } } diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index 89ca564..d778439 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -84,7 +84,7 @@ namespace Cpu { vector available_fields = {"total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; - bool got_sensors = false, cpu_temp_only = false; + bool got_sensors = false, cpu_temp_only = false, supports_watts = false; //* Populate found_sensors map bool get_sensors(); diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index 27d6cb4..5a2ed81 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -83,7 +83,7 @@ namespace Cpu { vector available_fields = {"total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; - bool got_sensors = false, cpu_temp_only = false; + bool got_sensors = false, cpu_temp_only = false, supports_watts = false; //* Populate found_sensors map bool get_sensors(); diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index a77f920..c7ecb5c 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -77,7 +77,7 @@ namespace Cpu { vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; - bool got_sensors = false, cpu_temp_only = false; + bool got_sensors = false, cpu_temp_only = false, supports_watts = false; int core_offset = 0; //* Populate found_sensors map