From 22a463976d107a1e5849621e2a11f3e1071ed884 Mon Sep 17 00:00:00 2001 From: romner Date: Thu, 18 May 2023 16:07:05 +0200 Subject: [PATCH] Add GPU info to CPU panel --- src/btop.cpp | 25 +++++-- src/btop_config.cpp | 4 +- src/btop_draw.cpp | 134 ++++++++++++++++++++++++++++++------- src/btop_shared.hpp | 111 +++++++++++++++--------------- src/linux/btop_collect.cpp | 56 ++++++++++++---- 5 files changed, 228 insertions(+), 102 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index f081d5d..7966f39 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -499,6 +499,20 @@ namespace Runner { //* Run collection and draw functions for all boxes try { + //? GPU data collection + vector gpus; + if ( + v_contains(conf.boxes, "gpu") + or Config::getS("cpu_graph_lower") == "default" + or Config::getS("cpu_graph_lower").rfind("gpu-", 0) == 0 + or Config::getS("cpu_graph_upper").rfind("gpu-", 0) == 0 + or true + ) { + if (Global::debug) debug_timer("gpu", collect_begin); + gpus = Gpu::collect(conf.no_update); + } + auto& gpus_ref = gpus; + //? CPU if (v_contains(conf.boxes, "cpu")) { try { @@ -518,7 +532,7 @@ namespace Runner { if (Global::debug) debug_timer("cpu", draw_begin); //? Draw box - if (not pause_output) output += Cpu::draw(cpu, conf.force_redraw, conf.no_update); + if (not pause_output) output += Cpu::draw(cpu, gpus_ref, conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("cpu", draw_done); } @@ -527,18 +541,15 @@ namespace Runner { } } - //? GPU + //? GPU // TODO detailed panel if (v_contains(conf.boxes, "gpu")) { try { - if (Global::debug) debug_timer("gpu", collect_begin); - - //? Start collect - auto gpus = Gpu::collect(conf.no_update); if (Global::debug) debug_timer("gpu", draw_begin); //? Draw box - if (not pause_output and not Gpu::gpu_names.empty()) output += Gpu::draw(gpus, conf.force_redraw, conf.no_update); + if (not pause_output and not Gpu::gpu_names.empty()) + output += Gpu::draw(gpus_ref, conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("gpu", draw_done); } diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 495af23..3890207 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -203,8 +203,8 @@ namespace Config { {"graph_symbol_net", "default"}, {"graph_symbol_proc", "default"}, {"proc_sorting", "cpu lazy"}, - {"cpu_graph_upper", "total"}, - {"cpu_graph_lower", "total"}, + {"cpu_graph_upper", "default"}, // set to "total" in btop_collect.cpp + {"cpu_graph_lower", "default"}, // set to "total" or "gpu-totals" in btop_collect.cpp {"cpu_sensor", "Auto"}, {"selected_battery", "Auto"}, {"cpu_core_map", ""}, diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 4baf9dd..9cb22df 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -495,17 +495,21 @@ 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; - int graph_up_height; long unsigned int lavg_str_len = 0; + int graph_up_height, graph_low_height; + int graph_up_width, graph_low_width; bool shown = true, redraw = true, mid_line = false; string box; - Draw::Graph graph_upper; - Draw::Graph graph_lower; + vector graphs_upper; + vector graphs_lower; Draw::Meter cpu_meter; + vector gpu_meters; vector core_graphs; vector temp_graphs; + vector gpu_temp_graphs; + vector gpu_mem_graphs; - string draw(const cpu_info& cpu, bool force_redraw, bool data_same) { + string draw(const cpu_info& cpu, const vector& gpus, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; if (force_redraw) redraw = true; bool show_temps = (Config::getB("check_temp") and got_sensors); @@ -531,8 +535,8 @@ namespace Cpu { //* Redraw elements not needed to be updated every cycle if (redraw) { mid_line = (not single_graph and graph_up_field != graph_lo_field); - graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0 ? 1 : 0)); - const int graph_low_height = height - 2 - graph_up_height - (mid_line ? 1 : 0); + 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; out += box; @@ -549,17 +553,56 @@ namespace Cpu { Input::mouse_mappings["+"] = {button_y, x + width - 5, 1, 2}; //? Graphs & meters - graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", cpu.cpu_percent.at(graph_up_field), graph_symbol, false, true}; + const int graph_default_width = x + width - b_width - 3; + + auto init_graphs = [&](vector& graphs, const int graph_height, int& graph_width, const string& graph_field, bool invert) { + if (graph_field == "gpu-totals") { + graphs.resize(gpus.size()); + gpu_temp_graphs.resize(gpus.size()); + gpu_mem_graphs.resize(gpus.size()); + gpu_meters.resize(gpus.size()); + graph_width = graph_default_width/(int)gpus.size() - (int)gpus.size() + 1 + graph_default_width%gpus.size(); + for (unsigned long i = 0;;) { + auto& gpu = gpus[i]; auto& graph = graphs[i]; + + //? GPU graphs/meters + gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpu.mem_used_percent, graph_symbol }; + gpu_meters[i] = Draw::Meter{ b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size(), "cpu" }; + if (++i < gpus.size()) + graph = Draw::Graph{graph_width, graph_height, "cpu", gpu.gpu_percent, graph_symbol, invert, true}; + else { + graph = Draw::Graph{ + graph_width + graph_default_width%graph_width - (int)gpus.size() + 1, + graph_height, "cpu", gpu.gpu_percent, graph_symbol, invert, true + }; + break; + } + } + } else if (graph_field == "gpu-average") { + graphs.resize(1); + graph_width = graph_default_width; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", Gpu::average_gpu_percent, graph_symbol, invert, true }; + gpu_temp_graphs.resize(gpus.size()); + gpu_mem_graphs.resize(gpus.size()); + gpu_meters.resize(gpus.size()); + for (unsigned long i = 0; i < gpus.size(); ++i) { + gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].mem_used_percent, graph_symbol }; + gpu_meters[i] = Draw::Meter{ b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size(), "cpu" }; + } + } else { + graphs.resize(1); + graph_width = graph_default_width; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", cpu.cpu_percent.at(graph_field), graph_symbol, invert, true }; + } + }; + + init_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field, false); + if (not single_graph) + init_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field, Config::getB("cpu_invert_lower")); + cpu_meter = Draw::Meter{b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11), "cpu"}; - if (not single_graph) { - graph_lower = Draw::Graph{ - x + width - b_width - 3, - graph_low_height, "cpu", - cpu.cpu_percent.at(graph_lo_field), - graph_symbol, - Config::getB("cpu_invert_lower"), true - }; - } 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") @@ -627,10 +670,29 @@ namespace Cpu { } try { - //? Cpu graphs - out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(cpu.cpu_percent.at(graph_up_field), (data_same or redraw)); - if (not single_graph) - out += Mv::to( y + graph_up_height + 1 + (mid_line ? 1 : 0), x + 1) + graph_lower(cpu.cpu_percent.at(graph_lo_field), (data_same or redraw)); + //? Cpu/Gpu graphs + out += Fx::ub + Mv::to(y + 1, x + 1); + auto draw_graphs = [&](vector& graphs, const int graph_height, const int graph_width, const string& graph_field) { + if (graph_field == "gpu-totals") + for (unsigned long i = 0;;) { + out += graphs[i](gpus[i].gpu_percent, (data_same or redraw)); + if (gpus.size() > 1) { + auto i_str = to_string(i); + out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str + + Mv::d(graph_height/2) + Mv::r(graph_width - 1 - (graph_width > 5)*4 - i_str.size()); + } + if (++i < graphs.size()) + out += Theme::c("div_line") + (Symbols::v_line + Mv::l(1) + Mv::u(1))*graph_height + Mv::r(1) + Mv::d(1); + else break; + } + else out += graphs[0]((graph_field == "gpu-average" ? Gpu::average_gpu_percent : cpu.cpu_percent.at(graph_field)), (data_same or redraw)); + }; + + draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); + if (not single_graph) { + out += Mv::to(y + graph_up_height + 1 + mid_line, x + 1); + draw_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field); + } //? Uptime if (Config::getB("show_uptime")) { @@ -718,9 +780,26 @@ namespace Cpu { } else { lavg_str_len = lavg_str.length(); } - out += Mv::to(b_y + b_height - 2, b_x + cx + 1) + Theme::c("main_fg") + lavg_str; + out += Mv::to(b_y + b_height - 2 - gpus.size(), b_x + cx + 1) + Theme::c("main_fg") + lavg_str; } + //? Gpu brief info + if (graph_lo_field.rfind("gpu-", 0) or graph_up_field.rfind("gpu-", 0)) + for (unsigned long i = 0; i < gpus.size(); ++i) { + out += Mv::to(b_y + b_height - 1 - gpus.size() + i, b_x + 1) + + Theme::c("main_fg") + Fx::b + "GPU " + to_string(i) + ' ' + gpu_meters[i](gpus[i].gpu_percent.back()) + + Theme::g("cpu").at(gpus[i].gpu_percent.back()) + rjust(to_string(gpus[i].gpu_percent.back()), 4) + Theme::c("main_fg") + '%'; + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(gpus[i].mem_used_percent.back()) + + gpu_mem_graphs[i](gpus[i].mem_used_percent, data_same or redraw) + Theme::c("main_fg") + + rjust(floating_humanizer(gpus[i].mem_used, true), 5) + Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true); + if (show_temps) { + const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale); + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll)) + + gpu_temp_graphs[i](gpus[i].temp, data_same or redraw) + + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; + } + } + redraw = false; return out + Fx::reset; } @@ -1752,18 +1831,25 @@ namespace Draw { //* Calculate and draw cpu box outlines if (Cpu::shown) { using namespace Cpu; - bool show_temp = (Config::getB("check_temp") and got_sensors); + const bool gpus_shown_in_cpu_panel = ( + Config::getS("cpu_graph_lower") == "default" + or Config::getS("cpu_graph_lower").rfind("gpu-", 0) == 0 + or Config::getS("cpu_graph_upper").rfind("gpu-", 0) == 0 + ); + const int gpus_height_offset = Gpu::gpu_names.size()*gpus_shown_in_cpu_panel; + const bool show_temp = (Config::getB("check_temp") and got_sensors); width = round((double)Term::width * width_p / 100); if (Gpu::shown and not (Mem::shown or Net::shown or Proc::shown)) { height = Term::height/2; } else { height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p/(Gpu::shown+1) + Gpu::shown*5) / 100)); } + if (height <= Term::height-2) height += gpus_height_offset; x = 1; y = cpu_bottom ? Term::height - height + 1 : 1; - b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5))); + b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - gpus_height_offset*(height <= Term::height-2) - 5))); if (b_columns * (21 + 12 * show_temp) < width - (width / 3)) { b_column_size = 2; b_width = (21 + 12 * show_temp) * b_columns - (b_columns - 1); @@ -1781,7 +1867,7 @@ namespace Draw { } if (b_column_size == 0) b_width = (8 + 6 * show_temp) * b_columns + 1; - b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4); + b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4 + (int)gpus_height_offset - gpus_shown_in_cpu_panel); b_x = x + width - b_width - 1; b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1; diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 589536b..6b6d928 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -86,6 +86,61 @@ namespace Shared { } +namespace Gpu { + extern string box; + extern int x, y, width, height, min_width, min_height; + extern bool shown, redraw; + extern vector gpu_names; + extern deque average_gpu_percent; + + const array mem_names { "used"s, "free"s }; + + //* Container for process information // TODO + /*struct proc_info { + unsigned int pid; + unsigned long long mem; + };*/ + + //* Per-device container for GPU info + struct gpu_info { + deque gpu_percent = {0}; + unsigned int gpu_clock_speed = 0; // MHz + + deque pwr_percent = {0}; + long long pwr_usage = 0; // mW + long long pwr_max_usage = 255000; + long long pwr_state = 32; + + deque temp = {0}; + long long temp_max = 110; + + long long mem_total = 0; + long long mem_used = 0; + deque mem_used_percent = {0}; + deque mem_utilization_percent = {0}; // TODO: properly handle GPUs that can't report some stats + long long mem_clock_speed = 0; // MHz + + long long pcie_tx = 0; // KB/s + long long pcie_rx = 0; + + // vector graphics_processes = {}; // TODO + // vector compute_processes = {}; + }; + + namespace Nvml { + extern bool shutdown(); + } + namespace Rsmi { + extern bool shutdown(); + } + + //* Collect gpu stats and temperatures + auto collect(bool no_update = false) -> vector&; + + //* Draw contents of gpu box using as source + string draw(const vector& gpus, bool force_redraw, bool data_same); +} + namespace Cpu { extern string box; extern int x, y, width, height, min_width, min_height; @@ -119,7 +174,7 @@ namespace Cpu { auto collect(bool no_update = false) -> cpu_info&; //* Draw contents of cpu box using as source - string draw(const cpu_info& cpu, bool force_redraw = false, bool data_same = false); + string draw(const cpu_info& cpu, const vector& gpu, bool force_redraw = false, bool data_same = false); //* Parse /proc/cpu info for mapping of core ids auto get_core_mapping() -> unordered_flat_map; @@ -313,57 +368,3 @@ namespace Proc { int cur_depth, bool collapsed, const string& filter, bool found = false, bool no_update = false, bool should_filter = false); } - -namespace Gpu { - extern string box; - extern int x, y, width, height, min_width, min_height; - extern bool shown, redraw; - extern vector gpu_names; - - const array mem_names { "used"s, "free"s }; - - //* Container for process information // TODO - /*struct proc_info { - unsigned int pid; - unsigned long long mem; - };*/ - - //* Per-device container for GPU info - struct gpu_info { - deque gpu_percent = {0}; - unsigned int gpu_clock_speed = 0; // MHz - - deque pwr_percent = {0}; - long long pwr_usage = 0; // mW - long long pwr_max_usage = 255000; - long long pwr_state = 32; - - deque temp = {0}; - long long temp_max = 110; - - long long mem_total = 0; - long long mem_used = 0; - deque mem_used_percent = {0}; - deque mem_utilization_percent = {0}; // TODO: properly handle GPUs that can't report some stats - long long mem_clock_speed = 0; // MHz - - long long pcie_tx = 0; // KB/s - long long pcie_rx = 0; - - // vector graphics_processes = {}; // TODO - // vector compute_processes = {}; - }; - - namespace Nvml { - extern bool shutdown(); - } - namespace Rsmi { - extern bool shutdown(); - } - - //* Collect gpu stats and temperatures - auto collect(bool no_update = false) -> vector&; - - //* Draw contents of gpu box using as source - string draw(const vector& gpus, bool force_redraw, bool data_same); -} diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index bb47aeb..31d9438 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -94,6 +94,7 @@ namespace Cpu { namespace Gpu { vector gpus; vector gpu_names; + deque average_gpu_percent = {}; //? NVIDIA data collection namespace Nvml { @@ -172,13 +173,24 @@ namespace Shared { } Cpu::core_mapping = Cpu::get_core_mapping(); + //? Init for namespace Gpu + Gpu::Nvml::init(); + Gpu::Rsmi::init(); + if (not Gpu::gpu_names.empty()) { + Cpu::available_fields.push_back("gpu-totals"); + Cpu::available_fields.push_back("gpu-average"); + if (Config::strings.at("cpu_graph_lower") == "default") + Config::strings.at("cpu_graph_lower") = "gpu-totals"; + } + //? Init for namespace Mem Mem::old_uptime = system_uptime(); Mem::collect(); - //? Init for namespace Gpu - Gpu::Nvml::init(); - Gpu::Rsmi::init(); + if (Config::strings.at("cpu_graph_upper") == "default") + Config::strings.at("cpu_graph_upper") = "total"; + if (Config::strings.at("cpu_graph_lower") == "default") + Config::strings.at("cpu_graph_lower") = "total"; } } @@ -870,7 +882,13 @@ namespace Gpu { result = nvmlDeviceGetName(devices[i], name, NVML_DEVICE_NAME_BUFFER_SIZE); if (result != NVML_SUCCESS) Logger::error(std::string("NVML: Failed to get device name: ") + nvmlErrorString(result)); - else gpu_names[i] = string(name); + else { + gpu_names[i] = string(name); + for (const auto& brand : {"NVIDIA", "Nvidia", "AMD", "Amd", "Intel", "(R)", "(TM)"}) { + gpu_names[i] = s_replace(gpu_names[i], brand, ""); + } + gpu_names[i] = trim(gpu_names[i]); + } //? Power usage unsigned int max_power; @@ -1150,18 +1168,28 @@ namespace Gpu { Nvml::collect(gpus.data()); // raw pointer to vector data, size == Nvml::device_count Rsmi::collect(gpus.data() + Nvml::device_count); // size = Rsmi::device_count - //* Trim vectors if there are more values than needed for graphs + //* Calculate average usage + long long avg = 0; for (auto& gpu : gpus) { - //? GPU & memory utilization - while (cmp_greater(gpu.gpu_percent.size(), width * 2)) gpu.gpu_percent.pop_front(); - while (cmp_greater(gpu.mem_utilization_percent.size(), width)) gpu.mem_utilization_percent.pop_front(); - //? Power usage - while (cmp_greater(gpu.pwr_percent.size(), width)) gpu.pwr_percent.pop_front(); - //? Temperature - while (cmp_greater(gpu.temp.size(), 18)) gpu.temp.pop_front(); - //? Memory usage - while (cmp_greater(gpu.mem_used_percent.size(), width/2)) gpu.mem_used_percent.pop_front(); + avg += gpu.gpu_percent.back(); + + //* Trim vectors if there are more values than needed for graphs + if (width != 0) { + //? GPU & memory utilization + while (cmp_greater(gpu.gpu_percent.size(), width * 2)) gpu.gpu_percent.pop_front(); + while (cmp_greater(gpu.mem_utilization_percent.size(), width)) gpu.mem_utilization_percent.pop_front(); + //? Power usage + while (cmp_greater(gpu.pwr_percent.size(), width)) gpu.pwr_percent.pop_front(); + //? Temperature + while (cmp_greater(gpu.temp.size(), 18)) gpu.temp.pop_front(); + //? Memory usage + while (cmp_greater(gpu.mem_used_percent.size(), width/2)) gpu.mem_used_percent.pop_front(); + } } + average_gpu_percent.push_back(avg / gpus.size()); + + if (width != 0) + while (cmp_greater(average_gpu_percent.size(), width * 2)) average_gpu_percent.pop_front(); return gpus; }