diff --git a/Makefile b/Makefile index 69100ef..14d2c5c 100644 --- a/Makefile +++ b/Makefile @@ -164,8 +164,10 @@ override REQFLAGS := -std=c++20 WARNFLAGS := -Wall -Wextra -pedantic OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO) LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS) -override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) -override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) +GPUCXXFLAGS := -I/opt/cuda/include # TODO: there has to be a better way to link NVML than hardcoded dirs +GPULDFLAGS := -L/usr/lib64 -lnvidia-ml +override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) $(GPUCXXFLAGS) +override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) $(GPULDFLAGS) INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR) SU_USER := root @@ -202,16 +204,18 @@ all: $(PRE) directories btop info: @printf " $(BANNER)\n" - @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" - @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" - @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n" - @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n" - @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n" - @printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n" - @printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n" - @printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n" - @printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDCXX)\n" - @printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDLD)\n" + @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" + @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" + @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n" + @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n" + @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n" + @printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n" + @printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n" + @printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n" + @printf "\033[1;92mGPUCXXFLAGS \033[1;94m:| \033[0m$(GPUCXXFLAGS)\n" + @printf "\033[1;92mGPULDFLAGS \033[1;94m:| \033[0m$(GPULDFLAGS)\n" + @printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDCXX)\n" + @printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDLD)\n" info-quiet: @sleep 0.1 2>/dev/null || true diff --git a/README.md b/README.md index 5966dc2..5c56eea 100644 --- a/README.md +++ b/README.md @@ -655,7 +655,7 @@ graph_symbol_net = "default" # Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". graph_symbol_proc = "default" -#* Manually set which boxes to show. Available values are "cpu mem net proc", separate values with whitespace. +#* Manually set which boxes to show. Available values are "cpu mem net proc gpu", separate values with whitespace. shown_boxes = "proc cpu mem net" #* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs. diff --git a/src/btop.cpp b/src/btop.cpp index 552adae..f97e448 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -187,7 +187,7 @@ void term_resize(bool force) { } else return; - static const array all_boxes = {"cpu", "mem", "net", "proc"}; + static const array all_boxes = {"cpu", "mem", "net", "proc", "gpu"}; Global::resized = true; if (Runner::active) Runner::stop(); Term::refresh(); @@ -583,6 +583,27 @@ namespace Runner { throw std::runtime_error("Proc:: -> " + string{e.what()}); } } + + //? GPU + if (v_contains(conf.boxes, "gpu")) { + try { + if (Global::debug) debug_timer("gpu", collect_begin); + + //? Start collect + auto gpu = Gpu::collect(conf.no_update); + + if (Global::debug) debug_timer("gpu", draw_begin); + + //? Draw box + if (not pause_output) output += Gpu::draw(gpu, conf.force_redraw, conf.no_update); + + if (Global::debug) debug_timer("gpu", draw_done); + } + catch (const std::exception& e) { + throw std::runtime_error("Gpu:: -> " + string{e.what()}); + } + } + } catch (const std::exception& e) { Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()}; @@ -613,8 +634,9 @@ namespace Runner { "{mv3}{hiFg}2 {mainFg}| Show MEM box" "{mv4}{hiFg}3 {mainFg}| Show NET box" "{mv5}{hiFg}4 {mainFg}| Show PROC box" - "{mv6}{hiFg}esc {mainFg}| Show menu" - "{mv7}{hiFg}q {mainFg}| Quit", + "{mv6}{hiFg}5 {mainFg}| Show GPU box" + "{mv7}{hiFg}esc {mainFg}| Show menu" + "{mv8}{hiFg}q {mainFg}| Quit", "banner"_a = Draw::banner_gen(y, 0, true), "titleFg"_a = Theme::c("title"), "b"_a = Fx::b, "hiFg"_a = Theme::c("hi_fg"), "mainFg"_a = Theme::c("main_fg"), "mv1"_a = Mv::to(y+6, x), @@ -622,8 +644,9 @@ namespace Runner { "mv3"_a = Mv::to(y+9, x), "mv4"_a = Mv::to(y+10, x), "mv5"_a = Mv::to(y+11, x), - "mv6"_a = Mv::to(y+12, x-2), - "mv7"_a = Mv::to(y+13, x) + "mv6"_a = Mv::to(y+12, x), + "mv7"_a = Mv::to(y+13, x-2), + "mv8"_a = Mv::to(y+14, x) ); } output += empty_bg; @@ -637,7 +660,7 @@ namespace Runner { "post"_a = Theme::c("main_fg") + Fx::ub ); static auto loc = std::locale(std::locale::classic(), new MyNumPunct); - for (const string name : {"cpu", "mem", "net", "proc", "total"}) { + for (const string name : {"cpu", "mem", "net", "proc", "gpu", "total"}) { if (not debug_times.contains(name)) debug_times[name] = {0,0}; const auto& [time_collect, time_draw] = debug_times.at(name); if (name == "total") output += Fx::b; diff --git a/src/btop_config.cpp b/src/btop_config.cpp index b4bdec2..495af23 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -78,7 +78,7 @@ namespace Config { {"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, - {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."}, + {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc gpu\", separate values with whitespace."}, {"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."}, @@ -316,7 +316,7 @@ namespace Config { validError = "Malformatted preset in config value presets!"; return false; } - if (not is_in(vals.at(0), "cpu", "mem", "net", "proc")) { + if (not is_in(vals.at(0), "cpu", "mem", "net", "proc", "gpu")) { validError = "Invalid box name in config value presets!"; return false; } diff --git a/src/btop_config.hpp b/src/btop_config.hpp index bbebc9b..46e6a4c 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -42,7 +42,7 @@ namespace Config { const vector valid_graph_symbols = { "braille", "block", "tty" }; const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; - const vector valid_boxes = { "cpu", "mem", "net", "proc" }; + const vector valid_boxes = { "cpu", "mem", "net", "proc", "gpu" }; const vector temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" }; extern vector current_boxes; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 2352b09..5fb10d0 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -28,6 +28,7 @@ tab-size = 4 #include #include #include +#include using std::array; using std::clamp; @@ -1575,6 +1576,62 @@ namespace Proc { } +namespace Gpu { + int width_p = 100, height_p = 32; + int min_width = 60, min_height = 8; + int x = 1, y = 1, width = 20, height; + int b_columns, b_column_size; + int b_x, b_y, b_width, b_height; + bool shown = true, redraw = true, mid_line = false; + int graph_height; + Draw::Graph graph_upper; + string box; + + string draw(const gpu_info& gpu, bool force_redraw, bool data_same) { + if (Runner::stopping) return ""; + if (force_redraw) redraw = true; + auto tty_mode = Config::getB("tty_mode"); + auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_cpu")); // TODO graph_symbol_gpu + auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); + string out; + out.reserve(width * height); + + //* Redraw elements not needed to be updated every cycle + if (redraw) { + out += box; + graph_height = height-2; + //out += Gpu::Nvml::initialized ? "NVML initialized" : "NVML not initialized"; + graph_upper = Draw::Graph{x + width - b_width - 3, graph_height, "cpu", gpu.gpu_percent, graph_symbol, false, true}; // TODO cpu -> gpu + } + //out += " " + std::to_string(gpu.gpu_percent.back()) + "%"; + + try { + //? Gpu graphs + out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(gpu.gpu_percent, (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)); + + /*out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back()) + + Theme::g("cpu").at(clamp(cpu.cpu_percent.at("total").back(), 0ll, 100ll)) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; + if (show_temps) { + const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); + if (b_column_size > 1 or b_columns > 1) + out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color + + temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw); + out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; + }*/ + out += Theme::c("div_line") + Symbols::v_line; + + } catch (const std::exception& e) { + throw std::runtime_error("graphs: " + string{e.what()}); + } + + redraw = false; + return out + Fx::reset; + } +} + namespace Draw { void calcSizes() { atomic_wait(Runner::active); @@ -1588,6 +1645,7 @@ namespace Draw { Mem::box.clear(); Net::box.clear(); Proc::box.clear(); + Gpu::box.clear(); Global::clock.clear(); Global::overlay.clear(); Runner::pause_output = false; @@ -1598,16 +1656,17 @@ namespace Draw { Input::mouse_mappings.clear(); - Cpu::x = Mem::x = Net::x = Proc::x = 1; - Cpu::y = Mem::y = Net::y = Proc::y = 1; - Cpu::width = Mem::width = Net::width = Proc::width = 0; - Cpu::height = Mem::height = Net::height = Proc::height = 0; - Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = true; + Cpu::x = Mem::x = Net::x = Proc::y = Gpu::x = 1; + Cpu::y = Mem::y = Net::y = Proc::y = Gpu::y = 1; + Cpu::width = Mem::width = Net::width = Proc::width = Gpu::width = 0; + Cpu::height = Mem::height = Net::height = Proc::height = Gpu::height = 0; + Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = Gpu::redraw = true; Cpu::shown = s_contains(boxes, "cpu"); Mem::shown = s_contains(boxes, "mem"); Net::shown = s_contains(boxes, "net"); Proc::shown = s_contains(boxes, "proc"); + Gpu::shown = s_contains(boxes, "gpu"); //* Calculate and draw cpu box outlines if (Cpu::shown) { @@ -1739,5 +1798,15 @@ namespace Draw { select_max = height - 3; box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4); } + + //* Calculate and draw gpu box outlines + if (Gpu::shown) { + using namespace Gpu; + width = Term::width; + height = Term::height; + x = 1; + y = 1; + box = createBox(x, y, width, height, Theme::c("cpu_box"), true, "gpu", "", 5); // TODO gpu_box + } } } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 3a09152..9113a42 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -260,10 +260,10 @@ namespace Input { Menu::show(Menu::Menus::Options); return; } - else if (is_in(key, "1", "2", "3", "4")) { + else if (is_in(key, "1", "2", "3", "4", "5")) { atomic_wait(Runner::active); Config::current_preset = -1; - static const array boxes = {"cpu", "mem", "net", "proc"}; + static const array boxes = {"cpu", "mem", "net", "proc", "gpu"}; Config::toggle_box(boxes.at(std::stoi(key) - 1)); Draw::calcSizes(); Runner::run("all", false, true); diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index a29a1c7..07ecf0a 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -1263,7 +1263,7 @@ namespace Menu { //? Category buttons out += Mv::to(y+7, x+4); - for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) { + for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc", "gpu"}) { out += Fx::b + (i == selected_cat ? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']' : Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ') diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 51d8f3d..8c26c71 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -313,3 +313,26 @@ 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; + + struct gpu_info { + deque gpu_percent = {}; + //deque temp; + //long long temp_max = 0; + //array load_avg; + }; + + namespace Nvml { + extern bool initialized; + } + + //* Collect gpu stats and temperatures + auto collect(bool no_update = false) -> gpu_info&; + + //* Draw contents of gpu box using as source + string draw(const gpu_info& gpu, bool force_redraw, bool data_same); +} diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 2156115..521dd48 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -28,6 +28,7 @@ tab-size = 4 #include #include #include // for inet_ntop() +#include #if !(defined(STATIC_BUILD) && defined(__GLIBC__)) @@ -93,6 +94,19 @@ namespace Mem { double old_uptime; } +namespace Gpu { + gpu_info current_gpu; + unsigned int device_count; + nvmlDevice_t device; + + //? NVIDIA data collection + namespace Nvml { + bool initialized = false; + bool init(); + bool shutdown(); + } +} + namespace Shared { fs::path procPath, passwd_path; @@ -152,6 +166,9 @@ namespace Shared { Mem::old_uptime = system_uptime(); Mem::collect(); + //? Init for namespace Gpu + Gpu::Nvml::init(); + } } @@ -2073,3 +2090,75 @@ namespace Tools { throw std::runtime_error("Failed get uptime from from " + string{Shared::procPath} + "/uptime"); } } + +namespace Gpu { + //? NVIDIA + namespace Nvml { + bool init() { + if (initialized) {return false;} + + nvmlReturn_t result = nvmlInit(); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("Failed to initialize NVML, NVIDIA GPUs will not be detected: ") + nvmlErrorString(result)); + return false; + } + + result = nvmlDeviceGetCount(&device_count); + if (result != NVML_SUCCESS) { + Logger::error(std::string("Failed to get NVML device count: ") + nvmlErrorString(result)); + return false; + } + + result = nvmlDeviceGetHandleByIndex(0, &device); // TODO: multi-GPU support + if (result != NVML_SUCCESS) { + Logger::error(std::string("Failed to get NVML device handle: ") + nvmlErrorString(result)); + return false; + } + + initialized = true; + return true; + } + bool shutdown() { + if (!initialized) {return false;} + + nvmlReturn_t result = nvmlShutdown(); + if (NVML_SUCCESS == result) { + initialized = true; + } else Logger::warning(std::string("Failed to shutdown NVML: ") + nvmlErrorString(result)); + return !initialized; + } + } + // TODO: AMD + // TODO: Intel + + //? Collect data from GPU-specific libraries + auto collect(bool no_update) -> gpu_info& { + if (Runner::stopping or (no_update and not current_gpu.gpu_percent.empty())) return current_gpu; + auto& gpu = current_gpu; + + //if (Config::getB("show_gpu_freq")) + // TODO gpuHz = get_gpuHz(); + + //? Get GPU utilization + if (Nvml::initialized) { + nvmlUtilization_t utilization; + nvmlReturn_t result = nvmlDeviceGetUtilizationRates(device, &utilization); + if (result != NVML_SUCCESS) { + throw std::runtime_error(std::string("Failed to get GPU utilization: ") + nvmlErrorString(result)); + } + + //? Total usage of gpu + 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(); + } + + /*if (Config::getB("check_temp")) { + + } + */ + + return gpu; + } +}