From 7ab765b57500bc3243a2627f29a8460eae0f84d9 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sun, 15 Aug 2021 23:20:55 +0200 Subject: [PATCH] Added Net::collect(), Net::draw() and input mappings for net --- Makefile | 22 ++-- src/btop.cpp | 12 ++- src/btop_config.cpp | 21 ++-- src/btop_config.hpp | 1 + src/btop_draw.cpp | 100 +++++++++++++++--- src/btop_input.cpp | 50 +++++++++ src/btop_linux.cpp | 252 ++++++++++++++++++++++++++++++++++++-------- src/btop_shared.hpp | 30 ++++-- src/btop_tools.cpp | 20 +++- src/btop_tools.hpp | 6 +- 10 files changed, 416 insertions(+), 98 deletions(-) diff --git a/Makefile b/Makefile index fbbfc20..520162e 100644 --- a/Makefile +++ b/Makefile @@ -2,16 +2,24 @@ PREFIX ?= /usr/local DOCDIR ?= $(PREFIX)/share/btop/doc #Compiler and Linker -CXX := g++ +CXX ?= g++ -#Try to make sure we are using GCC/G++ version 11 or later -CXX_VERSION = $(shell $(CXX) -dumpversion) -ifneq ($(shell test $(CXX_VERSION) -ge 11; echo $$?),0) - ifneq ($(shell command -v g++-11),) - CXX := g++-11 +#Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10 +ifneq ($(CXX),g++-10) + CXX_VERSION = $(shell $(CXX) -dumpfullversion -dumpversion | cut -f1 -d"." || echo 0) + ifneq ($(shell test $(CXX_VERSION) -ge 11; echo $$?),0) + ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0) + override CXX = g++-11 + endif endif endif +#Only enable fcf-protection if on x86 +ARCH = $(shell uname -p) +ifeq ($(ARCH),x86_64) + ADDFLAGS = -fcf-protection +endif + #The Target Binary Program TARGET := btop @@ -28,7 +36,7 @@ OBJEXT := o REQFLAGS := -std=c++20 WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic -pedantic-errors -Wfatal-errors OPTFLAGS := -O2 -ftree-loop-vectorize -override LDCXXFLAGS += -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection -fcf-protection -flto +override LDCXXFLAGS += -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection -flto $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) INC := -I$(INCDIR) -I$(SRCDIR) diff --git a/src/btop.cpp b/src/btop.cpp index 213d24e..40974b1 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -68,7 +68,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "0.0.30"; + const string Version = "0.5.0"; int coreCount; string banner; @@ -151,10 +151,12 @@ void term_resize(bool force) { if (rez_state > 1) return; Global::resized = true; Runner::stop(); + auto min_size = Term::get_min_size(Config::getS("shown_boxes")); + while (not force) { sleep_ms(100); if (rez_state != Global::resizing) rez_state = --Global::resizing; - else if (not Term::refresh()) break; + else if (not Term::refresh() and Term::width >= min_size[0] and Term::height >= min_size[1]) break; } Input::interrupt = true; @@ -334,9 +336,9 @@ namespace Runner { output.clear(); - future cpu; - future mem; - future net; + future cpu; + future mem; + future net; future> proc; //* Start collection functions for all boxes in async threads and draw in this thread when finished diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 0853627..450ef0e 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -65,11 +65,8 @@ namespace Config { {"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."}, - {"proc_update_mult", "#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n" - "#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)."}, - {"proc_sorting", "#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu responsive\",\n" - "#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly."}, + "#* \"cpu lazy\" sorts top process over time (easier to follow), \"cpu responsive\" updates top process directly."}, {"proc_reversed", "#* Reverse sorting order, True or False."}, @@ -147,15 +144,13 @@ namespace Config { {"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n" "#* Example: \"/mnt/media:100 /:20 /boot:1\"."}, - {"net_download", "#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"."}, + {"net_download", "#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False."}, {"net_upload", ""}, - {"net_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."}, + {"net_auto", "#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."}, - {"net_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."}, - - {"net_color_fixed", "#* If the network graphs color gradient should scale to bandwidth usage or auto scale, bandwidth usage is based on \"net_download\" and \"net_upload\" values."}, + {"net_sync", "#* Sync the auto scaling for download and upload to whichever currently has the highest scale."}, {"net_iface", "#* Starts with the Network Interface specified here."}, @@ -183,8 +178,6 @@ namespace Config { {"custom_cpu_name", ""}, {"disks_filter", ""}, {"io_graph_speeds", ""}, - {"net_download", "10M"}, - {"net_upload", "10M"}, {"net_iface", ""}, {"log_level", "WARNING"}, {"proc_filter", ""}, @@ -221,7 +214,6 @@ namespace Config { {"show_io_stat", true}, {"io_mode", false}, {"io_graph_combined", false}, - {"net_color_fixed", false}, {"net_auto", true}, {"net_sync", false}, {"show_battery", true}, @@ -235,7 +227,8 @@ namespace Config { unordered_flat_map ints = { {"update_ms", 2000}, - {"proc_update_mult", 2}, + {"net_download", 100}, + {"net_upload", 100}, {"detailed_pid", 0}, {"selected_pid", 0}, {"proc_start", 0}, @@ -244,8 +237,6 @@ namespace Config { }; unordered_flat_map intsTmp; - vector valid_boxes = { "cpu", "mem", "net", "proc" }; - bool _locked(const string& name) { atomic_wait(writelock); if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end()) diff --git a/src/btop_config.hpp b/src/btop_config.hpp index 0b3df6f..35d13df 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -39,6 +39,7 @@ namespace Config { extern unordered_flat_map intsTmp; const vector valid_graph_symbols = { "braille", "block", "tty" }; + const vector valid_boxes = { "cpu", "mem", "net", "proc" }; extern vector current_boxes; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index be7a190..71fd2a1 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -376,7 +376,7 @@ namespace Draw { namespace Cpu { int width_p = 100, height_p = 32; - int min_w = 60, min_h = 8; + int min_width = 60, min_height = 8; int x = 1, y = 1, width, height; int b_columns, b_column_size; int b_x, b_y, b_width, b_height; @@ -541,7 +541,7 @@ namespace Cpu { namespace Mem { int width_p = 45, height_p = 38; - int min_w = 36, min_h = 10; + int min_width = 36, min_height = 10; int x = 1, y, width, height; int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter; int disks_io_h = 0; @@ -695,6 +695,7 @@ namespace Mem { divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Symbols::div_right + Mv::l(disks_width - 1); if (io_mode) { for (const auto& mount : mem.disks_order) { + if (not disks.contains(mount)) continue; if (cy > height - 3) break; const auto& disk = disks.at(mount); if (disk.io_read.empty()) continue; @@ -728,6 +729,7 @@ namespace Mem { } else { for (const auto& mount : mem.disks_order) { + if (not disks.contains(mount)) continue; if (cy > height - 3) break; const auto& disk = disks.at(mount); auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); @@ -749,7 +751,7 @@ namespace Mem { } out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' ' - + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 7)); + + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { @@ -774,22 +776,92 @@ namespace Mem { namespace Net { int width_p = 45, height_p = 30; - int min_w = 3, min_h = 6; + int min_width = 36, min_height = 6; int x = 1, y, width, height; int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; - int graph_height; bool shown = true, redraw = true; + string old_ip; + unordered_flat_map graphs; string box; string draw(const net_info& net, const bool force_redraw, const bool data_same) { if (Runner::stopping) return ""; - (void)net; - (void)data_same; - string out = Mv::to(0, 0); - if (redraw or force_redraw) { - redraw = false; - out += box; + if (force_redraw) redraw = true; + auto& net_sync = Config::getB("net_sync"); + auto& net_auto = Config::getB("net_auto"); + auto& tty_mode = Config::getB("tty_mode"); + auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc")); + string ip_addr = (net.ipv4.empty() ? net.ipv6 : net.ipv4); + // if (ip_addr.ends_with(selected_iface)) ip_addr.resize(ip_addr.size() - selected_iface.size()); + if (old_ip != ip_addr) { + old_ip = ip_addr; + redraw = true; } + string out; + out.reserve(width * height); + const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; + const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; + const int i_size = min((int)selected_iface.size(), 10); + const long long down_max = (net_auto ? graph_max.at("download") : (long long)(Config::getI("net_download") << 20) / 8); + const long long up_max = (net_auto ? graph_max.at("upload") : (long long)(Config::getI("net_upload") << 20) / 8); + + //* Redraw elements not needed to be updated every cycle + if (redraw) { + out = box; + //? Graphs + graphs["download"] = Draw::Graph{width - b_width - 2, u_graph_height, "download", net.bandwidth.at("download"), graph_symbol, false, true, down_max}; + graphs["upload"] = Draw::Graph{width - b_width - 2, d_graph_height, "upload", net.bandwidth.at("upload"), graph_symbol, true, true, up_max}; + + //? Interface selector and buttons + + out += Mv::to(y, x+width - i_size - 10) + title_left + Fx::b + Theme::c("hi_fg") + "" + title_right + + Mv::to(y, x+width - i_size - 16) + title_left + Theme::c("hi_fg") + (net.stat.at("download").offset + net.stat.at("upload").offset > 0 ? Fx::b : "") + 'z' + + Theme::c("title") + "ero" + title_right; + Input::mouse_mappings["b"] = {y, x+width - i_size - 9, 1, 3}; + Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; + Input::mouse_mappings["z"] = {y, x+width - i_size - 15, 1, 4}; + if (width - i_size - 20 > 6) { + out += Mv::to(y, x+width - i_size - 21) + title_left + Theme::c("hi_fg") + (net_auto ? Fx::b : "") + 'a' + Theme::c("title") + "uto" + title_right; + Input::mouse_mappings["a"] = {y, x+width - i_size - 20, 1, 4}; + } + if (width - i_size - 20 > 13) { + out += Mv::to(y, x+width - i_size - 27) + title_left + Theme::c("title") + (net_sync ? Fx::b : "") + 's' + Theme::c("hi_fg") + + 'y' + Theme::c("title") + "nc" + title_right; + Input::mouse_mappings["y"] = {y, x+width - i_size - 26, 1, 4}; + } + } + + //? IP or device address + if (not ip_addr.empty() and width - i_size - 35 - ip_addr.size() > 0) { + out += Mv::to(y, x + 8) + title_left + Theme::c("title") + Fx::b + ip_addr + title_right; + } + + //? Graphs and stats + int cy = 0; + for (const string dir : {"download", "upload"}) { + out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(net.bandwidth.at(dir), redraw or data_same or not net.connected) + + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + + floating_humanizer((dir == "upload" ? up_max : down_max), true); + const string speed = floating_humanizer(net.stat.at(dir).speed, false, 0, false, true); + const string speed_bits = (b_width >= 20 ? floating_humanizer(net.stat.at(dir).speed, false, 0, true, true) : ""); + const string top = floating_humanizer(net.stat.at(dir).top, false, 0, true, true); + const string total = floating_humanizer(net.stat.at(dir).total); + const string symbol = (dir == "upload" ? "▲" : "▼"); + out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : ""); + cy += (b_height == 3 ? 2 : 1); + if (b_height >= 6) { + out += Mv::to(b_y+1+cy, b_x+1) + symbol + ' ' + "Top: " + rjust('(' + top, (b_width >= 20 ? 17 : 9)) + ')'; + cy++; + } + if (b_height >= 4) { + out += Mv::to(b_y+1+cy, b_x+1) + symbol + ' ' + "Total: " + rjust(total, (b_width >= 20 ? 16 : 8)); + cy += (b_height > 2 and b_height % 2 ? 2 : 1); + } + } + + + redraw = false; return out + Fx::reset; } @@ -797,7 +869,7 @@ namespace Net { namespace Proc { int width_p = 55, height_p = 68; - int min_w = 44, min_h = 16; + int min_width = 44, min_height = 16; int x, y, width, height; int start, selected, select_max; bool shown = true, redraw = true; @@ -1357,8 +1429,8 @@ namespace Draw { graph_height = 0; if (show_disks) { - disk_meter = max(0, width - mem_width - 23); - if (disks_width < 25) disk_meter += 10; + disk_meter = max(-14, width - mem_width - 23); + if (disks_width < 25) disk_meter += 14; } box = createBox(x, y, width, height, Theme::c("mem_box"), true, "mem", "", 2); diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 7b7d2e4..61ad42a 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -383,12 +383,62 @@ namespace Input { if (key == "i") { Config::flip("io_mode"); } + else keep_going = true; if (not keep_going) { Runner::run("mem", no_update, redraw); return; } } + + //? Input actions for net box + if (Net::shown) { + bool keep_going = false; + bool no_update = true; + bool redraw = true; + + if (is_in(key, "b", "n")) { + atomic_wait(Runner::active); + int c_index = v_index(Net::interfaces, Net::selected_iface); + if (c_index != (int)Net::interfaces.size()) { + if (key == "b") { + if (--c_index < 0) c_index = Net::interfaces.size() - 1; + } + else if (key == "n") { + if (++c_index == (int)Net::interfaces.size()) c_index = 0; + } + Net::selected_iface = Net::interfaces.at(c_index); + Net::rescale = true; + } + } + else if (key == "y") { + Config::flip("net_sync"); + Net::rescale = true; + } + else if (key == "a") { + Config::flip("net_auto"); + Net::rescale = true; + } + else if (key == "z") { + atomic_wait(Runner::active); + auto& ndev = Net::current_net.at(Net::selected_iface); + if (ndev.stat.at("download").offset + ndev.stat.at("upload").offset > 0) { + ndev.stat.at("download").offset = 0; + ndev.stat.at("upload").offset = 0; + } + else { + ndev.stat.at("download").offset = ndev.stat.at("download").last; + ndev.stat.at("upload").offset = ndev.stat.at("upload").last; + } + no_update = false; + } + else keep_going = true; + + if (not keep_going) { + Runner::run("net", no_update, redraw); + return; + } + } } diff --git a/src/btop_linux.cpp b/src/btop_linux.cpp index 9333a8b..b2a48a0 100644 --- a/src/btop_linux.cpp +++ b/src/btop_linux.cpp @@ -18,20 +18,29 @@ tab-size = 4 #if defined(__linux__) +// #define _GNU_SOURCE #include #include #include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize, std::round, std::max, std::min, - std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; + std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater, std::tuple, std::make_tuple; namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; @@ -133,6 +142,9 @@ namespace Shared { //? Init for namespace Mem Mem::collect(); + //? Init for namespace Net + Net::collect(); + } } @@ -438,7 +450,7 @@ namespace Cpu { return core_map; } - auto collect(const bool no_update) -> cpu_info { + auto collect(const 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; @@ -539,7 +551,7 @@ namespace Mem { mem_info current_mem {}; - auto collect(const bool no_update) -> mem_info { + auto collect(const bool no_update) -> mem_info& { if (Runner::stopping or no_update) return current_mem; auto& show_swap = Config::getB("show_swap"); auto& swap_disk = Config::getB("swap_disk"); @@ -702,11 +714,11 @@ namespace Mem { } //? Remove disks no longer mounted or filtered out if (swap_disk and has_swap) found.push_back("swap"); - for (auto i = disks.begin(); i != disks.end();) { - if (not v_contains(found, i->first)) - i = disks.erase(i); + for (auto it = disks.begin(); it != disks.end();) { + if (not v_contains(found, it->first)) + it = disks.erase(it); else - i++; + it++; } } else @@ -784,11 +796,173 @@ namespace Mem { } namespace Net { - net_info current_net; + unordered_flat_map current_net; + net_info empty_net = {}; + vector interfaces; + string selected_iface; + int errors = 0; + unordered_flat_map graph_max = { {"download", {}}, {"upload", {}} }; + unordered_flat_map> max_count = { {"download", {}}, {"upload", {}} }; + bool rescale = true; - auto collect(const bool no_update) -> net_info { - (void)no_update; - return current_net; + //* RAII wrapper for getifaddrs + class getifaddr_wrapper { + struct ifaddrs* ifaddr; + public: + int status; + getifaddr_wrapper() { status = getifaddrs(&ifaddr); } + ~getifaddr_wrapper() { freeifaddrs(ifaddr); } + auto operator()() -> struct ifaddrs* { return ifaddr; } + }; + + auto collect(const bool no_update) -> net_info& { + auto& net = current_net; + auto& config_iface = Config::getS("net_iface"); + auto& net_sync = Config::getB("net_sync"); + auto& net_auto = Config::getB("net_auto"); + + if (not no_update and errors < 3) { + //? Get interface list using getifaddrs() wrapper + getifaddr_wrapper if_wrap = {}; + if (if_wrap.status != 0) { + errors++; + Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); + return empty_net; + } + int family = 0; + char ip[NI_MAXHOST]; + interfaces.clear(); + string ipv4, ipv6; + + //? Iteration over all items in getifaddrs() list + for (auto* ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) continue; + family = ifa->ifa_addr->sa_family; + const auto& iface = ifa->ifa_name; + + //? Get IPv4 address + if (family == AF_INET) { + if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) + net[iface].ipv4 = ip; + } + //? Get IPv6 address + else if (family == AF_INET6) { + if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) + net[iface].ipv6 = ip; + } + + //? Update available interfaces vector and get status of interface + if (not v_contains(interfaces, iface)) { + interfaces.push_back(iface); + net[iface].connected = (ifa->ifa_flags & IFF_RUNNING); + } + } + + //? Get total recieved and transmitted bytes + device address if no ip was found + for (const auto& iface : interfaces) { + if (net.at(iface).ipv4.empty() and net.at(iface).ipv6.empty()) + net.at(iface).ipv4 = readfile("/sys/class/net/" + iface + "/address"); + + for (const string dir : {"download", "upload"}) { + const fs::path sys_file = "/sys/class/net/" + iface + "/statistics/" + (dir == "download" ? "rx_bytes" : "tx_bytes"); + auto& saved_stat = net.at(iface).stat.at(dir); + auto& bandwidth = net.at(iface).bandwidth.at(dir); + + const uint64_t val = max(stoul(readfile(sys_file, "0")), saved_stat.last); + + //? Update speed, total and top values + saved_stat.speed = val - (saved_stat.last == 0 ? val : saved_stat.last); + if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed; + if (saved_stat.offset > val) saved_stat.offset = 0; + saved_stat.total = val - saved_stat.offset; + saved_stat.last = val; + + //? Add values to graph + bandwidth.push_back(saved_stat.speed); + if (cmp_greater(bandwidth.size(), Term::width.load() * 2)) bandwidth.pop_front(); + + //? Set counters for auto scaling + if (net_auto and selected_iface == iface) { + if (saved_stat.speed > graph_max[dir]) { + ++max_count[dir][0]; + if (max_count[dir][1] > 0) --max_count[dir][1]; + } + else if (graph_max[dir] > 10 << 10 and saved_stat.speed < graph_max[dir] / 10) { + ++max_count[dir][1]; + if (max_count[dir][0] > 0) --max_count[dir][0]; + } + + } + } + } + + //? Clean up net map if needed + if (net.size() > interfaces.size()) { + for (auto it = net.begin(); it != net.end();) { + if (not v_contains(interfaces, it->first)) + it = net.erase(it); + else + it++; + } + } + } + + //? Return empty net_info struct if no interfaces was found + if (net.empty()) + return empty_net; + + //? Find an interface to display if selected isn't set or valid + if (selected_iface.empty() or not v_contains(interfaces, selected_iface)) { + max_count["download"][0] = max_count["download"][1] = max_count["upload"][0] = max_count["upload"][1] = 0; + redraw = true; + if (net_auto) rescale = true; + if (not config_iface.empty() and v_contains(interfaces, config_iface)) selected_iface = config_iface; + else { + //? Sort interfaces by total upload + download bytes + auto sorted_interfaces = interfaces; + rng::sort(sorted_interfaces, [&](const auto& a, const auto& b){ + return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total, + net.at(b).stat["download"].total + net.at(b).stat["upload"].total); + }); + selected_iface.clear(); + //? Try to set to a connected interface + for (const auto& iface : sorted_interfaces) { + if (net.at(iface).connected) selected_iface = iface; + break; + } + //? If no interface is connected set to first available + if (selected_iface.empty()) selected_iface = sorted_interfaces.at(0); + } + } + + //? Calculate max scale for graphs if needed + if (net_auto) { + bool sync = false; + for (const auto& dir: {"download", "upload"}) { + for (const auto& sel : {0, 1}) { + if (rescale or max_count[dir][sel] >= 5) { + const uint64_t avg_speed = (net[selected_iface].bandwidth[dir].size() > 5 + ? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0) / 5 + : net[selected_iface].stat[dir].speed); + graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), 10ul << 10); + max_count[dir][0] = max_count[dir][1] = 0; + redraw = true; + if (net_sync) sync = true; + break; + } + } + //? Sync download/upload graphs if enabled + if (sync) { + const auto other = (string(dir) == "upload" ? "download" : "upload"); + graph_max[other] = graph_max[dir]; + max_count[other][0] = max_count[other][1] = 0; + break; + } + } + } + + rescale = false; + return net.at(selected_iface); } } @@ -1091,29 +1265,24 @@ namespace Proc { getline(pread, short_str, ' '); switch (x-offset) { - case 3: { //? Process state + case 3: //? Process state new_proc.state = short_str.at(0); continue; - } - case 4: { //? Parent pid + case 4: //? Parent pid new_proc.ppid = stoull(short_str); next_x = 14; continue; - } - case 14: { //? Process utime + case 14: //? Process utime cpu_t = stoull(short_str); continue; - } - case 15: { //? Process stime + case 15: //? Process stime cpu_t += stoull(short_str); next_x = 19; continue; - } - case 19: { //? Nice value + case 19: //? Nice value new_proc.p_nice = stoull(short_str); continue; - } - case 20: { //? Number of threads + case 20: //? Number of threads new_proc.threads = stoull(short_str); if (cache.at(new_proc.pid).cpu_s == 0) { next_x = 22; @@ -1122,21 +1291,17 @@ namespace Proc { else next_x = 24; continue; - } - case 22: { //? Save cpu seconds to cache if missing + case 22: //? Save cpu seconds to cache if missing cache.at(new_proc.pid).cpu_s = stoull(short_str); next_x = 24; continue; - } - case 24: { //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x) + case 24: //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x) new_proc.mem = stoull(short_str) * Shared::pageSize; next_x = 39; continue; - } - case 39: { //? CPU number last executed on + case 39: //? CPU number last executed on new_proc.cpu_n = stoull(short_str); goto stat_loop_done; - } } } @@ -1170,13 +1335,12 @@ namespace Proc { //? Clear dead processes from cache at a regular interval if (++counter >= 1000 or (cache.size() > procs.size() + 100)) { counter = 0; - unordered_flat_map r_cache; - r_cache.reserve(procs.size()); - rng::for_each(procs, [&r_cache](const auto &p) { - if (cache.contains(p.pid)) - r_cache[p.pid] = cache.at(p.pid); - }); - cache = std::move(r_cache); + for (auto it = cache.begin(); it != cache.end();) { + if (rng::find(procs, it->first, &proc_info::pid) == procs.end()) + it = cache.erase(it); + else + it++; + } } //? Update the details info box for process if active @@ -1207,14 +1371,14 @@ namespace Proc { //* Sort processes const auto cmp = [&reverse](const auto &a, const auto &b) { return (reverse ? a < b : a > b); }; switch (v_index(sort_vector, sorting)) { - case 0: { rng::sort(procs, cmp, &proc_info::pid); break; } - case 1: { rng::sort(procs, cmp, &proc_info::name); break; } - case 2: { rng::sort(procs, cmp, &proc_info::cmd); break; } - case 3: { rng::sort(procs, cmp, &proc_info::threads); break; } - case 4: { rng::sort(procs, cmp, &proc_info::user); break; } - case 5: { rng::sort(procs, cmp, &proc_info::mem); break; } - case 6: { rng::sort(procs, cmp, &proc_info::cpu_p); break; } - case 7: { rng::sort(procs, cmp, &proc_info::cpu_c); break; } + case 0: rng::sort(procs, cmp, &proc_info::pid); break; + case 1: rng::sort(procs, cmp, &proc_info::name); break; + case 2: rng::sort(procs, cmp, &proc_info::cmd); break; + case 3: rng::sort(procs, cmp, &proc_info::threads); break; + case 4: rng::sort(procs, cmp, &proc_info::user); break; + case 5: rng::sort(procs, cmp, &proc_info::mem); break; + case 6: rng::sort(procs, cmp, &proc_info::cpu_p); break; + case 7: rng::sort(procs, cmp, &proc_info::cpu_c); break; } //* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index f8e4c7c..af2d236 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -25,6 +25,7 @@ tab-size = 4 #include #include #include +#include using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array; @@ -69,7 +70,7 @@ namespace Shared { namespace Cpu { extern string box; - extern int x, y, width, height; + extern int x, y, width, height, min_width, min_height; extern bool shown, redraw, got_sensors, cpu_temp_only; extern string cpuName, cpuHz; @@ -94,7 +95,7 @@ namespace Cpu { }; //* Collect cpu stats and temperatures - auto collect(const bool no_update=false) -> cpu_info; + auto collect(const bool no_update=false) -> cpu_info&; //* Draw contents of cpu box using as source string draw(const cpu_info& cpu, const bool force_redraw=false, const bool data_same=false); @@ -102,7 +103,7 @@ namespace Cpu { namespace Mem { extern string box; - extern int x, y, width, height; + extern int x, y, width, height, min_width, min_height; extern bool has_swap, shown, redraw; const array mem_names = {"used", "available", "cached", "free"}; const array swap_names = {"swap_used", "swap_free"}; @@ -129,7 +130,7 @@ namespace Mem { }; //* Collect mem & disks stats - auto collect(const bool no_update=false) -> mem_info; + auto collect(const bool no_update=false) -> mem_info&; //* Draw contents of mem box using as source string draw(const mem_info& mem, const bool force_redraw=false, const bool data_same=false); @@ -137,21 +138,28 @@ namespace Mem { namespace Net { extern string box; - extern int x, y, width, height; + extern int x, y, width, height, min_width, min_height; extern bool shown, redraw; + extern string selected_iface; + extern vector interfaces; + extern bool rescale; + extern unordered_flat_map graph_max; struct net_stat { - uint64_t speed = 0, top = 0, total = 0; + uint64_t speed = 0, top = 0, total = 0, last = 0, offset = 0; }; struct net_info { - unordered_flat_map> bandwidth; - unordered_flat_map stat; - string ip_addr; + unordered_flat_map> bandwidth = { {"download", {}}, {"upload", {}} }; + unordered_flat_map stat = { {"download", {}}, {"upload", {}} }; + string ipv4 = "", ipv6 = ""; + bool connected = false; }; + extern unordered_flat_map current_net; + //* Collect net upload/download stats - auto collect(const bool no_update=false) -> net_info; + auto collect(const bool no_update=false) -> net_info&; //* Draw contents of net box using as source string draw(const net_info& net, const bool force_redraw=false, const bool data_same=false); @@ -161,7 +169,7 @@ namespace Proc { extern atomic numpids; extern string box; - extern int x, y, width, height; + extern int x, y, width, height, min_width, min_height; extern bool shown, redraw; extern int select_max; extern atomic detailed_pid; diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 019848f..bcac979 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -32,7 +32,7 @@ tab-size = 4 #include #include -using std::string_view, std::array, std::max, std::floor, std::to_string, std::cin, std::cout, std::flush, robin_hood::unordered_flat_map; +using std::string_view, std::max, std::floor, std::to_string, std::cin, std::cout, std::flush, robin_hood::unordered_flat_map; namespace fs = std::filesystem; namespace rng = std::ranges; @@ -82,6 +82,24 @@ namespace Term { return false; } + auto get_min_size(const string& boxes) -> array { + const bool cpu = boxes.find("cpu") != string::npos; + const bool mem = boxes.find("mem") != string::npos; + const bool net = boxes.find("net") != string::npos; + const bool proc = boxes.find("proc") != string::npos; + int width = 0; + if (mem) width = Mem::min_width; + else if (net) width = Mem::min_width; + width += (proc ? Proc::min_width : 0); + if (cpu and width < Cpu::min_width) width = Cpu::min_width; + + int height = (cpu ? Cpu::min_height : 0); + if (proc) height += Proc::min_height; + else height += (mem ? Mem::min_height : 0) + (net ? Net::min_height : 0); + + return { width, height }; + } + bool init() { if (not initialized) { initialized = (bool)isatty(STDIN_FILENO); diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 05e3ee0..a0d72ea 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -20,6 +20,7 @@ tab-size = 4 #include #include +#include #include #include #include @@ -29,7 +30,7 @@ tab-size = 4 #include #include -using std::string, std::vector, std::atomic, std::to_string, std::regex, std::tuple; +using std::string, std::vector, std::atomic, std::to_string, std::regex, std::tuple, std::array; //? ------------------------------------------------- NAMESPACES ------------------------------------------------------ @@ -114,6 +115,9 @@ namespace Term { //* Returns true if terminal has been resized and updates width and height bool refresh(); + //* Returns an array with the lowest possible width, height with current box config + auto get_min_size(const string& boxes) -> array; + //* Check for a valid tty, save terminal options and set new options bool init();