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:
1
Makefile
1
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user