Added Net::collect(), Net::draw() and input mappings for net

This commit is contained in:
aristocratos 2021-08-15 23:20:55 +02:00
parent ee073f6ae5
commit 7ab765b575
10 changed files with 416 additions and 98 deletions

View file

@ -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)

View file

@ -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::cpu_info> cpu;
future<Mem::mem_info> mem;
future<Net::net_info> net;
future<Cpu::cpu_info&> cpu;
future<Mem::mem_info&> mem;
future<Net::net_info&> net;
future<vector<Proc::proc_info>> proc;
//* Start collection functions for all boxes in async threads and draw in this thread when finished

View file

@ -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<string, int> 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<string, int> intsTmp;
vector<string> 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())

View file

@ -39,6 +39,7 @@ namespace Config {
extern unordered_flat_map<string, int> intsTmp;
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
const vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
extern vector<string> current_boxes;

View file

@ -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<string, Draw::Graph> 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") + "<b " + Theme::c("title")
+ uresize(selected_iface, 10) + Theme::c("hi_fg") + " n>" + 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);

View file

@ -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;
}
}
}

View file

@ -18,20 +18,29 @@ tab-size = 4
#if defined(__linux__)
// #define _GNU_SOURCE
#include <fstream>
#include <ranges>
#include <cmath>
#include <unistd.h>
#include <numeric>
#include <tuple>
#include <regex>
#include <sys/statvfs.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <linux/if_link.h>
#include <net/if.h>
#include <linux/rtnetlink.h>
#include <btop_shared.hpp>
#include <btop_config.hpp>
#include <btop_tools.hpp>
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<string, net_info> current_net;
net_info empty_net = {};
vector<string> interfaces;
string selected_iface;
int errors = 0;
unordered_flat_map<string, uint64_t> graph_max = { {"download", {}}, {"upload", {}} };
unordered_flat_map<string, array<int, 2>> 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<size_t, p_cache> 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

View file

@ -25,6 +25,7 @@ tab-size = 4
#include <deque>
#include <robin_hood.h>
#include <array>
#include <ifaddrs.h>
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 <cpu> 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<string, 4> mem_names = {"used", "available", "cached", "free"};
const array<string, 2> 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 <mem> 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<string> interfaces;
extern bool rescale;
extern unordered_flat_map<string, uint64_t> 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<string, deque<long long>> bandwidth;
unordered_flat_map<string, net_stat> stat;
string ip_addr;
unordered_flat_map<string, deque<long long>> bandwidth = { {"download", {}}, {"upload", {}} };
unordered_flat_map<string, net_stat> stat = { {"download", {}}, {"upload", {}} };
string ipv4 = "", ipv6 = "";
bool connected = false;
};
extern unordered_flat_map<string, net_info> 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 <net> 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<int> 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<int> detailed_pid;

View file

@ -32,7 +32,7 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_tools.hpp>
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<int, 2> {
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);

View file

@ -20,6 +20,7 @@ tab-size = 4
#include <string>
#include <vector>
#include <array>
#include <regex>
#include <atomic>
#include <filesystem>
@ -29,7 +30,7 @@ tab-size = 4
#include <tuple>
#include <pthread.h>
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<int, 2>;
//* Check for a valid tty, save terminal options and set new options
bool init();