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 <yurii.shkrobut@gmail.com>
This commit is contained in:
Thomas Müller
2025-08-16 17:10:13 +02:00
parent 4f5abbb3eb
commit db0514fa32
11 changed files with 111 additions and 28 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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},

View File

@@ -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

View File

@@ -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
{

View File

@@ -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<string> available_fields;
extern vector<string> available_sensors;
@@ -223,6 +223,7 @@ namespace Cpu {
vector<deque<long long>> temp;
long long temp_max = 0;
array<double, 3> load_avg;
float usage_watts = 0;
};
//* Collect cpu stats and temperatures

View File

@@ -78,7 +78,7 @@ namespace Cpu {
vector<string> available_fields = {"Auto", "total"};
vector<string> 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();

View File

@@ -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<long long> core_old_totals;
vector<long long> 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;
}
}

View File

@@ -84,7 +84,7 @@ namespace Cpu {
vector<string> available_fields = {"total"};
vector<string> 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();

View File

@@ -83,7 +83,7 @@ namespace Cpu {
vector<string> available_fields = {"total"};
vector<string> 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();

View File

@@ -77,7 +77,7 @@ namespace Cpu {
vector<string> available_fields = {"Auto", "total"};
vector<string> 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