mirror of
https://github.com/aristocratos/btop.git
synced 2024-09-28 22:21:35 +02:00
Add rudimentary, fullscreen single-GPU NVML utilization graph
This commit is contained in:
parent
ac17f34580
commit
d522a91ef4
8
Makefile
8
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
|
||||
|
||||
@ -210,6 +212,8 @@ info:
|
||||
@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"
|
||||
|
||||
|
@ -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.
|
||||
|
35
src/btop.cpp
35
src/btop.cpp
@ -187,7 +187,7 @@ void term_resize(bool force) {
|
||||
}
|
||||
else return;
|
||||
|
||||
static const array<string, 4> all_boxes = {"cpu", "mem", "net", "proc"};
|
||||
static const array<string, 5> 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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ namespace Config {
|
||||
|
||||
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
|
||||
const vector<string> valid_graph_symbols_def = { "default", "braille", "block", "tty" };
|
||||
const vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
|
||||
const vector<string> valid_boxes = { "cpu", "mem", "net", "proc", "gpu" };
|
||||
const vector<string> temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" };
|
||||
|
||||
extern vector<string> current_boxes;
|
||||
|
@ -28,6 +28,7 @@ tab-size = 4
|
||||
#include <btop_tools.hpp>
|
||||
#include <btop_input.hpp>
|
||||
#include <btop_menu.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<string, 4> boxes = {"cpu", "mem", "net", "proc"};
|
||||
static const array<string, 5> boxes = {"cpu", "mem", "net", "proc", "gpu"};
|
||||
Config::toggle_box(boxes.at(std::stoi(key) - 1));
|
||||
Draw::calcSizes();
|
||||
Runner::run("all", false, true);
|
||||
|
@ -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 + ' ')
|
||||
|
@ -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<long long> gpu_percent = {};
|
||||
//deque<long long> temp;
|
||||
//long long temp_max = 0;
|
||||
//array<float, 3> 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 <gpu> as source
|
||||
string draw(const gpu_info& gpu, bool force_redraw, bool data_same);
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ tab-size = 4
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <arpa/inet.h> // for inet_ntop()
|
||||
#include <nvml.h>
|
||||
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user