From 95b32283083ca2b74824a94ce97561b323ea1d17 Mon Sep 17 00:00:00 2001 From: romner Date: Sat, 13 May 2023 19:41:51 +0200 Subject: [PATCH] Improve GPU side panel --- src/btop_draw.cpp | 81 ++++++++++++++++---------- src/btop_shared.hpp | 23 ++++++-- src/linux/btop_collect.cpp | 113 +++++++++++++++++++++++++++---------- 3 files changed, 155 insertions(+), 62 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index ff7ccee..1137fa1 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -1586,9 +1586,10 @@ namespace Gpu { int graph_height; Draw::Graph graph_upper; Draw::Graph temp_graph; + Draw::Graph mem_used_graph; + Draw::Graph mem_util_graph; Draw::Meter gpu_meter; - Draw::Meter mem_meter; - unordered_flat_map mem_graphs; + Draw::Meter pwr_meter; string box; string draw(const gpu_info& gpu, bool force_redraw, bool data_same) { @@ -1607,51 +1608,73 @@ namespace Gpu { out += box; graph_upper = Draw::Graph{x + width - b_width - 3, height-2, "cpu", gpu.gpu_percent, graph_symbol, false, true}; // TODO cpu -> gpu - gpu_meter = Draw::Meter{b_width - (show_temps ? 27 : 11), "cpu"}; - temp_graph = Draw::Graph{9, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23}; + gpu_meter = Draw::Meter{b_width - (show_temps ? 24 : 11), "cpu"}; + temp_graph = Draw::Graph{6, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23}; - mem_meter = Draw::Meter{b_width - 27, "used"}; - for (const auto& name : mem_names) { - mem_graphs[name] = Draw::Graph{b_width/2 - (name == "used" ? 1 : 2), 3, name, gpu.mem_percent.at(name), graph_symbol}; - } + pwr_meter = Draw::Meter{b_width - 24, "cached"}; + + mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% I/O + mem_used_graph = Draw::Graph{b_width/2 - 2, 4, "used", gpu.mem_used_percent, graph_symbol}; } - //out += " " + std::to_string(gpu.gpu_percent.back()) + "%"; //? Core text and graphs try { - //? Gpu graph & meter + //? Gpu graph, clock speed & meter out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(gpu.gpu_percent, (data_same or redraw)); + + if (Config::getB("show_cpu_freq")) { // TODO show_gpu_freq + string clock_speed_string = to_string(gpu.gpu_clock_speed); + out += Mv::to(b_y, b_x + b_width - 12) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; + } + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(gpu.gpu_percent.back()) + Theme::g("cpu").at(gpu.gpu_percent.back()) + rjust(to_string(gpu.gpu_percent.back()), 4) + Theme::c("main_fg") + '%'; //? Temperature graph if (show_temps) { const auto [temp, unit] = celsius_to(gpu.temp.back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(gpu.temp.back() * 100 / gpu.temp_max, 0ll, 100ll)); - out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpu.temp.back() * 100 / gpu.temp_max, 0ll, 100ll)) + temp_graph(gpu.temp, data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; - //? Memory usage meter - out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "MEM " + mem_meter(gpu.mem_percent.at("used").back()) - + Theme::g("used").at(gpu.mem_percent.at("used").back()) + rjust(to_string(gpu.mem_percent.at("used").back()), 4) + Theme::c("main_fg") + '%' - + Fx::b + " Total:" + rjust(floating_humanizer(gpu.mem_total), 9); + //? Power usage meter, power state + out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(gpu.pwr_percent.back()) + + Theme::g("cached").at(gpu.pwr_percent.back()) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W' + + " P-state: " + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(gpu.pwr_state) + to_string(gpu.pwr_state); - //? Memory usage graphs - out += Mv::to(b_y + 4, b_x + 1); - for (const auto& name : mem_names) { - out += mem_graphs[name](gpu.mem_percent.at(name), (data_same or redraw)) + Mv::u(2) + Mv::r(1); + //? Memory section header & clock speed + string used_memory_string = floating_humanizer(gpu.mem_used); + out += Mv::to(b_y + 3, b_x) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Fx::b + Theme::c("title") + "vram" + + Theme::c("div_line") + Fx::ub + Symbols::title_right + Symbols::h_line*(b_width/2-8) + Symbols::div_up + Symbols::h_line + + Theme::c("title") + "Used:" + Theme::c("div_line") + Symbols::h_line*(b_width/2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right; + if (Config::getB("show_cpu_freq")) { // TODO show_gpu_freq + string clock_speed_string = to_string(gpu.mem_clock_speed); + out += Mv::to(b_y + 3, b_x + b_width/2 - 11) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; } - //? Memory usage borders // TODO, there's gotta be a more elegant way to do this... - out += Mv::to(b_y + 3, b_x) + Theme::c("div_line") + Symbols::div_left+Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line") - + Symbols::h_line*(b_width/2-15) + Theme::c("title") + floating_humanizer(gpu.mem_stats.at("used")) + Theme::c("div_line") + Symbols::h_line; - out += Symbols::div_up + Symbols::h_line + Theme::c("title") + "Free:" + Theme::c("div_line") - + Symbols::h_line*(b_width/2-17) + Theme::c("title") + floating_humanizer(gpu.mem_stats.at("free")) + Theme::c("div_line") + Symbols::h_line + Symbols::div_right; - out += Mv::to(b_y + 7, b_x) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line*(b_width/2-1) + (Mv::u(1) + Symbols::v_line + Mv::l(1))*3 - + Mv::d(3) + Symbols::div_down + Symbols::h_line*(b_width/2-2) + Symbols::div_right; + //? Memory usage borders + out += Mv::to(b_y + 5, b_x) + Theme::c("div_line") + Symbols::div_left+Symbols::h_line + Theme::c("title") + "I/O:" + Theme::c("div_line") + Symbols::h_line*(b_width/2-6) + + Symbols::div_right + Mv::u(1)+Mv::l(1) + Symbols::v_line + Mv::l(1)+Mv::d(2) + (Symbols::v_line + Mv::l(1)+Mv::d(1))*2; + + //? Total memory usage + out += Mv::to(b_y + 4, b_x + 2) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub; + + //? Memory usage graphs & percentage + out += Mv::to(b_y+6, b_x+1) + Theme::c("inactive_fg") + mem_util_graph(gpu.mem_utilization_percent, (data_same or redraw)) + + Mv::u(3) + Mv::r(1) + mem_used_graph(gpu.mem_used_percent, (data_same or redraw)); + out += Mv::to(b_y+6, b_x+1) + rjust(to_string(gpu.mem_utilization_percent.back()), 3) + '%' + Mv::u(2) + Mv::r(b_width/2-3) + rjust(to_string(gpu.mem_used_percent.back()), 3) + '%'; + + //? Processes section header + out += Mv::to(b_y+8, b_x) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Theme::c("main_fg") + Fx::b + "gpu-proc" + Fx::ub + Theme::c("div_line") + + Symbols::title_right + Symbols::h_line*(b_width/2-12) + Symbols::div_down + Symbols::h_line*(b_width/2-2) + Symbols::div_right; + + //? PCIe link throughput + out += Mv::to(b_y + b_height - 1, b_x+2) + Symbols::title_left_down + Theme::c("main_fg") + "TX: " + floating_humanizer(gpu.pcie_tx, 0, 1, 0, 1) + Theme::c("div_line") + + Symbols::title_right_down + Symbols::title_left_down + Theme::c("main_fg") + "RX: " + floating_humanizer(gpu.pcie_rx, 0, 1, 0, 1) + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*10; } catch (const std::exception& e) { throw std::runtime_error("graphs: " + string{e.what()}); @@ -1837,13 +1860,13 @@ namespace Draw { x = 1; y = 1; box = createBox(x, y, width, height, Theme::c("cpu_box"), true, "gpu", "", 5); // TODO gpu_box - b_width = width/2; // TODO + b_width = width/2 - width%2; // TODO b_height = height-2; b_x = x + width - b_width - 1; b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1; - box += createBox(b_x, b_y, b_width, b_height, "", false, "test"); + box += createBox(b_x, b_y, b_width, b_height, "", false, gpu_name); } } } diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index e392ff9..5c121be 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -318,16 +318,31 @@ namespace Gpu { extern string box; extern int x, y, width, height, min_width, min_height; extern bool shown, redraw; + extern string gpu_name; const array mem_names { "used"s, "free"s }; struct gpu_info { deque gpu_percent = {}; - deque temp; - long long temp_max = 0; - unordered_flat_map mem_stats = {{"used", 0}, {"free", 0}}; - unordered_flat_map> mem_percent = {{"used", {}}, {"free", {}}}; + unsigned int gpu_clock_speed = 0; // MHz + + deque pwr_percent = {}; + unsigned int pwr_usage = 0; // mW + unsigned int pwr_max_usage = 300000; + unsigned int pwr_state = 32; + + deque temp = {}; + long long temp_max = 100; + long long mem_total = 0; + long long mem_used = 0; + deque mem_used_percent = {}; + long long mem_utilization = 0; + deque mem_utilization_percent = {}; + unsigned int mem_clock_speed = 0; // MHz + + unsigned int pcie_tx = 0; // KB/s + unsigned int pcie_rx = 0; }; namespace Nvml { diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 5a4d4c4..b40ab73 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -97,6 +97,7 @@ namespace Mem { namespace Gpu { gpu_info current_gpu; unsigned int device_count; + string gpu_name; //? NVIDIA data collection namespace Nvml { @@ -2093,7 +2094,7 @@ namespace Tools { namespace Gpu { //? NVIDIA - namespace Nvml { + namespace Nvml { // TODO: multi-GPU support bool init() { if (initialized) {return false;} @@ -2103,13 +2104,17 @@ namespace Gpu { return false; } - result = nvmlDeviceGetCount(&device_count); + //? Device count + unsigned int nvml_count; + result = nvmlDeviceGetCount(&nvml_count); if (result != NVML_SUCCESS) { Logger::error(std::string("NVML: Failed to get device count: ") + nvmlErrorString(result)); return false; } + device_count += nvml_count; - result = nvmlDeviceGetHandleByIndex(0, &device); // TODO: multi-GPU support + //? Device Handle + result = nvmlDeviceGetHandleByIndex(0, &device); if (result != NVML_SUCCESS) { Logger::error(std::string("NVML: Failed to get device handle: ") + nvmlErrorString(result)); return false; @@ -2117,16 +2122,30 @@ namespace Gpu { initialized = true; + //? Device name + char name[NVML_DEVICE_NAME_BUFFER_SIZE]; + result = nvmlDeviceGetName(device, name, NVML_DEVICE_NAME_BUFFER_SIZE); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get device name: ") + nvmlErrorString(result)); + } else {gpu_name = string(name);} + + //? Power usage + result = nvmlDeviceGetPowerManagementLimit(device, ¤t_gpu.pwr_max_usage); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get maximum GPU power draw, defaulting to 300W: ") + nvmlErrorString(result)); + } + //? Get temp_max - unsigned int temp_max; + unsigned int temp_max = 100; result = nvmlDeviceGetTemperatureThreshold(device, NVML_TEMPERATURE_THRESHOLD_SHUTDOWN, &temp_max); if (result != NVML_SUCCESS) { - Logger::error(std::string("NVML: Failed to get maximum GPU temperature: ") + nvmlErrorString(result)); + Logger::error(std::string("NVML: Failed to get maximum GPU temperature, defaulting to 100: ") + nvmlErrorString(result)); return false; } current_gpu.temp_max = (long long)temp_max; return true; } + bool shutdown() { if (!initialized) {return false;} @@ -2136,31 +2155,60 @@ namespace Gpu { } else Logger::warning(std::string("Failed to shutdown NVML: ") + nvmlErrorString(result)); return !initialized; } + bool collect(gpu_info& gpu) { if (!initialized) return false; - //? Get GPU utilization + //? GPU & memory utilization nvmlUtilization_t utilization; nvmlReturn_t result = nvmlDeviceGetUtilizationRates(device, &utilization); if (result != NVML_SUCCESS) { Logger::error(std::string("NVML: Failed to get GPU utilization: ") + nvmlErrorString(result)); - return false; + } else { + gpu.gpu_percent.push_back((long long)utilization.gpu); + gpu.mem_utilization_percent.push_back((long long)utilization.memory); + //? Reduce size if there are more values than needed for graph + 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(); } - gpu.gpu_percent.push_back((long long)utilization.gpu); - //? Reduce size if there are more values than needed for graph - while (cmp_greater(gpu.gpu_percent.size(), width * 2)) gpu.gpu_percent.pop_front(); - - //? GPU temp + + //? Clock speeds + result = nvmlDeviceGetClockInfo(device, NVML_CLOCK_GRAPHICS, ¤t_gpu.gpu_clock_speed); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get GPU clock speed: ") + nvmlErrorString(result)); + } + result = nvmlDeviceGetClockInfo(device, NVML_CLOCK_MEM, ¤t_gpu.mem_clock_speed); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get VRAM clock speed: ") + nvmlErrorString(result)); + } + + //? Power usage & state + result = nvmlDeviceGetPowerUsage(device, ¤t_gpu.pwr_usage); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get GPU power usage: ") + nvmlErrorString(result)); + } else { + current_gpu.pwr_percent.push_back(clamp((long long)round((double)current_gpu.pwr_usage * 100.0 / (double)current_gpu.pwr_max_usage), 0ll, 100ll)); + } + + nvmlPstates_t pState; + result = nvmlDeviceGetPowerState(device, &pState); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get GPU power state: ") + nvmlErrorString(result)); + } else { + current_gpu.pwr_state = static_cast(pState); + } + + //? GPU temperature if (Config::getB("check_temp")) { unsigned int temp; nvmlReturn_t result = nvmlDeviceGetTemperature(device, NVML_TEMPERATURE_GPU, &temp); if (result != NVML_SUCCESS) { Logger::error(std::string("NVML: Failed to get GPU temperature: ") + nvmlErrorString(result)); - return false; - } - gpu.temp.push_back((long long)temp); - //? Reduce size if there are more values than needed for graph - while (cmp_greater(gpu.temp.size(), 18)) gpu.temp.pop_front(); + } else { + gpu.temp.push_back((long long)temp); + //? Reduce size if there are more values than needed for graph + while (cmp_greater(gpu.temp.size(), 18)) gpu.temp.pop_front(); + } } //? Memory info @@ -2168,20 +2216,27 @@ namespace Gpu { result = nvmlDeviceGetMemoryInfo(device, &memory); if (result != NVML_SUCCESS) { Logger::error(std::string("NVML: Failed to get VRAM info: ") + nvmlErrorString(result)); - return false; + } else { + gpu.mem_total = memory.total; + gpu.mem_used = memory.used; + //gpu.mem_free = memory.free; + + auto used_percent = (long long)round((double)memory.used * 100.0 / (double)memory.total); + gpu.mem_used_percent.push_back(used_percent); + + //? Reduce size if there are more values than needed for graphs + while (cmp_greater(gpu.mem_used_percent.size(), width/2)) gpu.mem_used_percent.pop_front(); } - gpu.mem_total = memory.total; - gpu.mem_stats.at("used") = memory.used; - gpu.mem_stats.at("free") = memory.free; - - auto used_percent = (long long)round((double)memory.used * 100.0 / (double)memory.total); - gpu.mem_percent.at("used").push_back(used_percent); - gpu.mem_percent.at("free").push_back(100-used_percent); - - //? Reduce size if there are more values than needed for graphs - while (cmp_greater(gpu.mem_percent.at("used").size(), width/2)) gpu.mem_percent.at("used").pop_front(); - while (cmp_greater(gpu.mem_percent.at("free").size(), width/2)) gpu.mem_percent.at("free").pop_front(); + //? PCIe link speeds + result = nvmlDeviceGetPcieThroughput(device, NVML_PCIE_UTIL_TX_BYTES, ¤t_gpu.pcie_tx); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get PCIe TX throughput: ") + nvmlErrorString(result)); + } + result = nvmlDeviceGetPcieThroughput(device, NVML_PCIE_UTIL_RX_BYTES, ¤t_gpu.pcie_rx); + if (result != NVML_SUCCESS) { + Logger::error(std::string("NVML: Failed to get PCIe RX throughput: ") + nvmlErrorString(result)); + } return true; }