Fixed memory leak and greatly reduced general memory usage

This commit is contained in:
aristocratos 2021-08-17 22:33:21 +02:00
parent 7ab765b575
commit 8bd97b4f24
9 changed files with 440 additions and 349 deletions

View file

@ -109,7 +109,7 @@ void argumentParser(const int& argc, char **argv) {
<< " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
<< " +t, --tty_off force (OFF) tty mode\n"
<< " --utf-foce force start even if no UTF-8 locale was detected"
<< " --debug start with loglevel set to DEBUG, overriding value set in config\n"
<< " --debug start in debug mode with loglevel set to DEBUG\n"
<< endl;
exit(0);
}
@ -168,6 +168,8 @@ void clean_quit(int sig) {
if (Global::quitting) return;
Global::quitting = true;
Runner::stop();
if (pthread_join(Runner::runner_id, NULL) != 0)
Logger::error("Failed to join _runner thread!");
if (not Global::exit_error_msg.empty()) {
sig = 1;
@ -273,30 +275,35 @@ namespace Runner {
atomic<bool> active (false);
atomic<bool> stopping (false);
atomic<bool> waiting (false);
atomic<bool> do_work (false);
string output;
sigset_t mask;
pthread_t runner_id;
pthread_mutex_t mtx;
const unordered_flat_map<string, uint_fast8_t> box_bits = {
{"proc", 0b0000'0001},
{"mem", 0b0000'0100},
{"net", 0b0001'0000},
{"net", 0b0000'0100},
{"mem", 0b0001'0000},
{"cpu", 0b0100'0000},
};
enum bit_pos {
proc_present, proc_running,
mem_present, mem_running,
net_present, net_running,
mem_present, mem_running,
cpu_present, cpu_running
};
const uint_fast8_t proc_done = 0b0000'0011;
const uint_fast8_t mem_done = 0b0000'1100;
const uint_fast8_t net_done = 0b0011'0000;
const uint_fast8_t net_done = 0b0000'1100;
const uint_fast8_t mem_done = 0b0011'0000;
const uint_fast8_t cpu_done = 0b1100'0000;
string debug_bg;
unordered_flat_map<string, array<uint64_t, 2>> debug_times;
struct runner_conf {
bitset<8> box_mask;
bool no_update;
@ -308,10 +315,8 @@ namespace Runner {
struct runner_conf current_conf;
//? ------------------------------- Secondary thread: async launcher and drawing ----------------------------------
void * _runner(void * confptr) {
struct runner_conf *conf;
conf = (struct runner_conf *) confptr;
void * _runner(void * _) {
(void)_;
//? Block all signals in this thread to avoid deadlock from any signal handlers trying to stop this thread
pthread_sigmask(SIG_BLOCK, &mask, NULL);
@ -324,126 +329,182 @@ namespace Runner {
stopping = true;
}
if (active or stopping or Global::resized) {
pthread_exit(NULL);
}
while (not Global::quitting) {
atomic_wait(do_work, false);
do_work = false;
if (stopping or Global::resized) {
continue;
}
//? Secondary atomic lock used for signaling status to main thread
atomic_lock lck(active);
auto& conf = current_conf;
//! DEBUG stats
auto timestamp = time_micros();
//? Secondary atomic lock used for signaling status to main thread
atomic_lock lck(active);
output.clear();
//! DEBUG stats
if (Global::debug) {
debug_times.clear();
debug_times["total"] = {0, 0};
}
future<Cpu::cpu_info&> cpu;
future<Mem::mem_info&> mem;
future<Net::net_info&> net;
future<vector<Proc::proc_info>> proc;
output.clear();
//* Start collection functions for all boxes in async threads and draw in this thread when finished
//? Starting order below based on mean time to finish
try {
while (conf->box_mask.count() > 0) {
if (stopping) break;
//? PROC
if (conf->box_mask.test(proc_present)) {
if (not conf->box_mask.test(proc_running)) {
proc = async(Proc::collect, conf->no_update);
conf->box_mask.set(proc_running);
//* Start collection functions for all boxes in async threads and draw in this thread when finished
//? Starting order below based on mean time to finish
try {
future<Cpu::cpu_info&> cpu;
future<Mem::mem_info&> mem;
future<Net::net_info&> net;
future<vector<Proc::proc_info>&> proc;
while (conf.box_mask.count() > 0) {
if (stopping) break;
//? PROC
if (conf.box_mask.test(proc_present)) {
if (not conf.box_mask.test(proc_running)) {
if (Global::debug) debug_times["proc"].at(0) = time_micros();
proc = async(Proc::collect, conf.no_update);
conf.box_mask.set(proc_running);
}
else if (not proc.valid())
throw std::runtime_error("Proc::collect() future not valid.");
else if (proc.wait_for(std::chrono::microseconds(10)) == future_status::ready) {
try {
if (Global::debug) {
debug_times["proc"].at(1) = time_micros();
debug_times["proc"].at(0) = debug_times["proc"].at(1) - debug_times["proc"].at(0);
debug_times["total"].at(0) += debug_times["proc"].at(0);
}
output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["proc"].at(1) = time_micros() - debug_times["proc"].at(1);
debug_times["total"].at(1) += debug_times["proc"].at(1);
}
}
catch (const std::exception& e) {
throw std::runtime_error("Proc:: -> " + (string)e.what());
}
conf.box_mask ^= proc_done;
}
}
else if (not proc.valid())
throw std::runtime_error("Proc::collect() future not valid.");
//? NET
if (conf.box_mask.test(net_present)) {
if (not conf.box_mask.test(net_running)) {
if (Global::debug) debug_times["net"].at(0) = time_micros();
net = async(Net::collect, conf.no_update);
conf.box_mask.set(net_running);
}
else if (not net.valid())
throw std::runtime_error("Net::collect() future not valid.");
else if (proc.wait_for(std::chrono::microseconds(100)) == future_status::ready) {
try {
output += Proc::draw(proc.get(), conf->force_redraw, conf->no_update);
else if (net.wait_for(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["net"].at(1) = time_micros();
debug_times["net"].at(0) = debug_times["net"].at(1) - debug_times["net"].at(0);
debug_times["total"].at(0) += debug_times["net"].at(0);
}
output += Net::draw(net.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["net"].at(1) = time_micros() - debug_times["net"].at(1);
debug_times["total"].at(1) += debug_times["net"].at(1);
}
}
catch (const std::exception& e) {
throw std::runtime_error("Net:: -> " + (string)e.what());
}
conf.box_mask ^= net_done;
}
catch (const std::exception& e) {
throw std::runtime_error("Proc:: -> " + (string)e.what());
}
//? MEM
if (conf.box_mask.test(mem_present)) {
if (not conf.box_mask.test(mem_running)) {
if (Global::debug) debug_times["mem"].at(0) = time_micros();
mem = async(Mem::collect, conf.no_update);
conf.box_mask.set(mem_running);
}
conf->box_mask ^= proc_done;
}
}
//? MEM
if (conf->box_mask.test(mem_present)) {
if (not conf->box_mask.test(mem_running)) {
mem = async(Mem::collect, conf->no_update);
conf->box_mask.set(mem_running);
}
else if (not mem.valid())
throw std::runtime_error("Mem::collect() future not valid.");
else if (not mem.valid())
throw std::runtime_error("Mem::collect() future not valid.");
else if (mem.wait_for(ZeroSec) == future_status::ready) {
try {
output += Mem::draw(mem.get(), conf->force_redraw, conf->no_update);
else if (mem.wait_for(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["mem"].at(1) = time_micros();
debug_times["mem"].at(0) = debug_times["mem"].at(1) - debug_times["mem"].at(0);
debug_times["total"].at(0) += debug_times["mem"].at(0);
}
output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["mem"].at(1) = time_micros() - debug_times["mem"].at(1);
debug_times["total"].at(1) += debug_times["mem"].at(1);
}
}
catch (const std::exception& e) {
throw std::runtime_error("Mem:: -> " + (string)e.what());
}
conf.box_mask ^= mem_done;
}
catch (const std::exception& e) {
throw std::runtime_error("Mem:: -> " + (string)e.what());
}
//? CPU
if (conf.box_mask.test(cpu_present)) {
if (not conf.box_mask.test(cpu_running)) {
if (Global::debug) debug_times["cpu"].at(0) = time_micros();
cpu = async(Cpu::collect, conf.no_update);
conf.box_mask.set(cpu_running);
}
conf->box_mask ^= mem_done;
}
}
//? NET
if (conf->box_mask.test(net_present)) {
if (not conf->box_mask.test(net_running)) {
net = async(Net::collect, conf->no_update);
conf->box_mask.set(net_running);
}
else if (not net.valid())
throw std::runtime_error("Net::collect() future not valid.");
else if (not cpu.valid())
throw std::runtime_error("Cpu::collect() future not valid.");
else if (net.wait_for(ZeroSec) == future_status::ready) {
try {
output += Net::draw(net.get(), conf->force_redraw, conf->no_update);
else if (cpu.wait_for(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["cpu"].at(1) = time_micros();
debug_times["cpu"].at(0) = debug_times["cpu"].at(1) - debug_times["cpu"].at(0);
debug_times["total"].at(0) += debug_times["cpu"].at(0);
}
output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["cpu"].at(1) = time_micros() - debug_times["cpu"].at(1);
debug_times["total"].at(1) += debug_times["cpu"].at(1);
}
}
catch (const std::exception& e) {
throw std::runtime_error("Cpu:: -> " + (string)e.what());
}
conf.box_mask ^= cpu_done;
}
catch (const std::exception& e) {
throw std::runtime_error("Net:: -> " + (string)e.what());
}
conf->box_mask ^= net_done;
}
}
//? CPU
if (conf->box_mask.test(cpu_present)) {
if (not conf->box_mask.test(cpu_running)) {
cpu = async(Cpu::collect, conf->no_update);
conf->box_mask.set(cpu_running);
}
else if (not cpu.valid())
throw std::runtime_error("Cpu::collect() future not valid.");
else if (cpu.wait_for(ZeroSec) == future_status::ready) {
try {
output += Cpu::draw(cpu.get(), conf->force_redraw, conf->no_update);
}
catch (const std::exception& e) {
throw std::runtime_error("Cpu:: -> " + (string)e.what());
}
conf->box_mask ^= cpu_done;
}
}
}
}
catch (const std::exception& e) {
Global::exit_error_msg = "Exception in runner thread -> " + (string)e.what();
Global::thread_exception = true;
Input::interrupt = true;
stopping = true;
}
catch (const std::exception& e) {
Global::exit_error_msg = "Exception in runner thread -> " + (string)e.what();
Global::thread_exception = true;
Input::interrupt = true;
stopping = true;
}
if (stopping) {
pthread_exit(NULL);
if (stopping) {
continue;
}
//! DEBUG stats -->
if (Global::debug) {
output += debug_bg + Theme::c("title") + Fx::b + ljust(" Box", 9) + ljust("Collect μs", 12, true) + ljust("Draw μs", 9, true) + Theme::c("main_fg") + Fx::ub;
for (const string name : {"cpu", "mem", "net", "proc", "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;
output += Mv::l(29) + Mv::d(1) + ljust(name, 8) + ljust(to_string(time_collect), 12) + ljust(to_string(time_draw), 9);
}
}
//? If overlay isn't empty, print output without color and then print overlay on top
cout << Term::sync_start << (conf.overlay.empty()
? output + conf.clock
: Theme::c("inactive_fg") + Fx::ub + Fx::uncolor(output + conf.clock) + conf.overlay)
<< Term::sync_end << flush;
}
//? If overlay isn't empty, print output without color and then print overlay on top
cout << Term::sync_start << (conf->overlay.empty()
? output + conf->clock
: Theme::c("inactive_fg") + Fx::uncolor(output + conf->clock) + conf->overlay)
<< Term::sync_end << flush;
//! DEBUG stats -->
cout << Fx::reset << Mv::to(1, 20) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
pthread_exit(NULL);
}
//? ------------------------------------------ Secondary thread end -----------------------------------------------
@ -476,31 +537,32 @@ namespace Runner {
current_conf = {box_mask, no_update, force_redraw, Global::overlay, Global::clock};
pthread_t runner_id;
if (pthread_create(&runner_id, NULL, &_runner, (void *) &current_conf) != 0)
throw std::runtime_error("Failed to create _runner thread!");
if (pthread_detach(runner_id) != 0)
throw std::runtime_error("Failed to detach _runner thread!");
do_work = true;
atomic_notify(do_work);
for (int i = 0; not active and i < 10; i++) sleep_ms(1);
}
}
//* Stops any secondary thread running
//* Stops any work being done in runner thread and checks for thread errors
void stop() {
stopping = true;
int ret = pthread_mutex_trylock(&mtx);
if (ret == EOWNERDEAD or ret == ENOTRECOVERABLE) {
if (ret != EBUSY and not Global::quitting) {
if (active) active = false;
Global::exit_error_msg = "Runner thread died unexpectedly!";
if (not Global::quitting) exit(1);
exit(1);
}
else if (ret == EBUSY) {
if (not active) {
do_work = true;
atomic_notify(do_work);
sleep_ms(1);
}
else {
atomic_wait(active);
}
}
else if (ret == EBUSY)
atomic_wait(active);
else if (ret == 0)
pthread_mutex_unlock(&mtx);
sleep_ms(1);
stopping = false;
}
@ -575,7 +637,10 @@ int main(int argc, char **argv) {
if (Config::current_boxes.empty()) Config::check_boxes(Config::getS("shown_boxes"));
Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor")));
if (Global::debug) Logger::set("DEBUG");
if (Global::debug) {
Logger::set("DEBUG");
Logger::debug("Starting in DEBUG mode!");
}
else Logger::set(Config::getS("log_level"));
Logger::info("Logger set to " + Config::getS("log_level"));
@ -641,6 +706,16 @@ int main(int argc, char **argv) {
Theme::updateThemes();
Theme::setTheme();
//? Start runner thread
if (pthread_create(&Runner::runner_id, NULL, &Runner::_runner, NULL) != 0) {
Global::exit_error_msg = "Failed to create _runner thread!";
exit(1);
}
if (Global::debug) {
Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug");
}
//? Create the btop++ banner
banner_gen();

View file

@ -306,10 +306,11 @@ namespace Config {
}
void toggle_box(const string& box) {
if (not v_contains(current_boxes, box))
auto box_pos = rng::find(current_boxes, box);
if (box_pos == current_boxes.end())
current_boxes.push_back(box);
else
current_boxes.erase(rng::find(current_boxes, box));
current_boxes.erase(box_pos);
string new_boxes;
if (not current_boxes.empty()) {

View file

@ -279,8 +279,8 @@ namespace Draw {
//? Horizontal iteration over values in <data>
for (const int& i : iota(data_offset, (int)data.size())) {
if (tty_mode and mult and i % 2 != 0) continue;
else if (not tty_mode and mult) current = not current;
// if (tty_mode and mult and i % 2 != 0) continue;
if (not tty_mode and mult) current = not current;
if (i < 0) {
data_value = 0;
last = 0;
@ -332,16 +332,17 @@ namespace Draw {
Graph::Graph(int width, int height, const string& color_gradient, const deque<long long>& data, const string& symbol, bool invert, bool no_zero, long long max_value, long long offset)
: width(width), height(height), color_gradient(color_gradient), invert(invert), no_zero(no_zero), offset(offset) {
if (Config::getB("tty_mode") or symbol == "tty") { tty_mode = true; this->symbol = "tty"; }
if (Config::getB("tty_mode") or symbol == "tty") this->symbol = "tty";
else if (symbol != "default") this->symbol = symbol;
else this->symbol = Config::getS("graph_symbol");
if (this->symbol == "tty") tty_mode = true;
if (max_value == 0 and offset > 0) max_value = 100;
this->max_value = max_value;
const int value_width = ceil((double)data.size() / 2);
int data_offset = (value_width > width) ? data.size() - width * 2 : 0;
const int value_width = (tty_mode ? data.size() : ceil((double)data.size() / 2));
int data_offset = (value_width > width) ? data.size() - width * (tty_mode ? 1 : 2) : 0;
if ((data.size() - data_offset) % 2 != 0) {
if (not tty_mode and (data.size() - data_offset) % 2 != 0) {
data_offset--;
}
@ -377,7 +378,7 @@ namespace Draw {
namespace Cpu {
int width_p = 100, height_p = 32;
int min_width = 60, min_height = 8;
int x = 1, y = 1, width, height;
int x = 1, y = 1, width = 20, height;
int b_columns, b_column_size;
int b_x, b_y, b_width, b_height;
int graph_up_height;
@ -542,7 +543,7 @@ namespace Cpu {
namespace Mem {
int width_p = 45, height_p = 38;
int min_width = 36, min_height = 10;
int x = 1, y, width, height;
int x = 1, y, width = 20, height;
int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter;
int disks_io_h = 0;
bool shown = true, redraw = true;
@ -580,6 +581,7 @@ namespace Mem {
//? Mem graphs and meters
for (const auto& name : mem_names) {
if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name)};
else
@ -777,7 +779,7 @@ namespace Mem {
namespace Net {
int width_p = 45, height_p = 30;
int min_width = 36, min_height = 6;
int x = 1, y, width, height;
int x = 1, y, width = 20, height;
int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height;
bool shown = true, redraw = true;
string old_ip;
@ -792,7 +794,6 @@ namespace Net {
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;
@ -809,6 +810,7 @@ namespace Net {
if (redraw) {
out = box;
//? Graphs
graphs.clear();
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};
@ -870,7 +872,7 @@ namespace Net {
namespace Proc {
int width_p = 55, height_p = 68;
int min_width = 44, min_height = 16;
int x, y, width, height;
int x, y, width = 20, height;
int start, selected, select_max;
bool shown = true, redraw = true;
int selected_pid = 0;
@ -992,8 +994,8 @@ namespace Proc {
//? Create cpu and mem graphs if process is alive
if (alive) {
detailed_cpu_graph = {dgraph_width - 1, 7, "cpu", detailed.cpu_percent, graph_symbol, false, true};
detailed_mem_graph = {d_width / 3, 1, "", detailed.mem_bytes, graph_symbol, false, false, detailed.first_mem};
detailed_cpu_graph = Draw::Graph{dgraph_width - 1, 7, "cpu", detailed.cpu_percent, graph_symbol, false, true};
detailed_mem_graph = Draw::Graph{d_width / 3, 1, "", detailed.mem_bytes, graph_symbol, false, false, detailed.first_mem};
}
//? Draw structure of details box
@ -1189,7 +1191,7 @@ namespace Proc {
//* Iteration over processes
int lc = 0;
for (int n=0; auto& p : plist) {
if (n++ < start) continue;
if (p.filtered or n++ < start) continue;
bool is_selected = (lc + 1 == selected);
if (is_selected) selected_pid = (int)p.pid;
@ -1197,7 +1199,7 @@ namespace Proc {
const bool has_graph = p_counters.contains(p.pid);
if ((p.cpu_p > 0 and not has_graph) or (not data_same and has_graph)) {
if (not has_graph) {
p_graphs[p.pid] = {5, 1, "", {}, graph_symbol};
p_graphs[p.pid] = Draw::Graph{5, 1, "", {}, graph_symbol};
p_counters[p.pid] = 0;
}
else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) {
@ -1251,7 +1253,7 @@ namespace Proc {
}
//? Tree view line
else {
string prefix_pid = p.prefix + to_string(p.pid);
const string prefix_pid = p.prefix + to_string(p.pid);
int width_left = tree_size;
out += Mv::to(y+2+lc, x+1) + g_color + uresize(prefix_pid, width_left) + ' ';
width_left -= ulen(prefix_pid);
@ -1259,9 +1261,9 @@ namespace Proc {
out += c_color + uresize(p.name, width_left - 1) + end + ' ';
width_left -= (ulen(p.name) + 1);
}
if (width_left > 7 and not p.cmd.empty()) {
out += g_color + uresize(p.cmd, width_left - 1, true) + ' ';
width_left -= (ulen(p.cmd, true) + 1);
if (width_left > 7 and (not p.short_cmd.empty() or p.short_cmd == p.name)) {
out += g_color + '(' + uresize(p.short_cmd, width_left - 3, true) + ") ";
width_left -= (ulen(p.short_cmd, true) + 3);
}
out += string(max(0, width_left), ' ');
}
@ -1312,6 +1314,8 @@ namespace Proc {
else
++element;
}
p_graphs.compact();
p_counters.compact();
}
if (selected == 0 and selected_pid != 0) selected_pid = 0;
@ -1323,6 +1327,7 @@ namespace Proc {
namespace Draw {
void calcSizes() {
atomic_wait(Runner::active);
auto& boxes = Config::getS("shown_boxes");
auto& cpu_bottom = Config::getB("cpu_bottom");
auto& mem_below_net = Config::getB("mem_below_net");

View file

@ -32,44 +32,43 @@ using namespace Tools;
namespace rng = std::ranges;
namespace Input {
namespace {
//* Map for translating key codes to readable values
const unordered_flat_map<string, string> Key_escapes = {
{"\033", "escape"},
{"\n", "enter"},
{" ", "space"},
{"\x7f", "backspace"},
{"\x08", "backspace"},
{"[A", "up"},
{"OA", "up"},
{"[B", "down"},
{"OB", "down"},
{"[D", "left"},
{"OD", "left"},
{"[C", "right"},
{"OC", "right"},
{"[2~", "insert"},
{"[3~", "delete"},
{"[H", "home"},
{"[F", "end"},
{"[5~", "page_up"},
{"[6~", "page_down"},
{"\t", "tab"},
{"[Z", "shift_tab"},
{"OP", "f1"},
{"OQ", "f2"},
{"OR", "f3"},
{"OS", "f4"},
{"[15~", "f5"},
{"[17~", "f6"},
{"[18~", "f7"},
{"[19~", "f8"},
{"[20~", "f9"},
{"[21~", "f10"},
{"[23~", "f11"},
{"[24~", "f12"}
};
}
//* Map for translating key codes to readable values
const unordered_flat_map<string, string> Key_escapes = {
{"\033", "escape"},
{"\n", "enter"},
{" ", "space"},
{"\x7f", "backspace"},
{"\x08", "backspace"},
{"[A", "up"},
{"OA", "up"},
{"[B", "down"},
{"OB", "down"},
{"[D", "left"},
{"OD", "left"},
{"[C", "right"},
{"OC", "right"},
{"[2~", "insert"},
{"[3~", "delete"},
{"[H", "home"},
{"[F", "end"},
{"[5~", "page_up"},
{"[6~", "page_down"},
{"\t", "tab"},
{"[Z", "shift_tab"},
{"OP", "f1"},
{"OQ", "f2"},
{"OR", "f3"},
{"OS", "f4"},
{"[15~", "f5"},
{"[17~", "f6"},
{"[18~", "f7"},
{"[19~", "f8"},
{"[20~", "f9"},
{"[21~", "f10"},
{"[23~", "f11"},
{"[24~", "f12"}
};
std::atomic<bool> interrupt (false);
array<int, 2> mouse_pos;
@ -156,8 +155,10 @@ namespace Input {
else if (ulen(key) > 1)
key.clear();
history.push_back(key);
history.pop_front();
if (not key.empty()) {
history.push_back(key);
history.pop_front();
}
}
return key;
}
@ -171,7 +172,6 @@ namespace Input {
void clear() {
if (cin.rdbuf()->in_avail() > 0) cin.ignore(SSmax);
history.clear();
}
void process(const string& key) {
@ -184,6 +184,7 @@ namespace Input {
if (not filtering) {
bool keep_going = false;
if (is_in(key, "1", "2", "3", "4")) {
atomic_wait(Runner::active);
static const array<string, 4> boxes = {"cpu", "mem", "net", "proc"};
Config::toggle_box(boxes.at(std::stoi(key) - 1));
term_resize(true);

View file

@ -18,29 +18,23 @@ 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::tuple, std::make_tuple;
std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater;
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
@ -496,7 +490,7 @@ namespace Cpu {
cpu.cpu_percent.at("total").push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll));
//? Reduce size if there are more values than needed for graph
if (cmp_greater(cpu.cpu_percent.at("total").size(), Term::width * 2)) cpu.cpu_percent.at("total").pop_front();
while (cmp_greater(cpu.cpu_percent.at("total").size(), width * 2)) cpu.cpu_percent.at("total").pop_front();
//? Populate cpu.cpu_percent with all fields from stat
for (int ii = 0; const auto& val : times) {
@ -504,7 +498,7 @@ namespace Cpu {
cpu_old.at(time_names.at(ii)) = val;
//? Reduce size if there are more values than needed for graph
if (cmp_greater(cpu.cpu_percent.at(time_names.at(ii)).size(), Term::width * 2)) cpu.cpu_percent.at(time_names.at(ii)).pop_front();
while (cmp_greater(cpu.cpu_percent.at(time_names.at(ii)).size(), width * 2)) cpu.cpu_percent.at(time_names.at(ii)).pop_front();
if (++ii == 10) break;
}
@ -543,10 +537,10 @@ namespace Cpu {
namespace Mem {
bool has_swap = false;
bool disks_fail = false;
vector<string> fstab;
fs::file_time_type fstab_time;
int disk_ios = 0;
vector<string> last_found;
mem_info current_mem {};
@ -602,24 +596,21 @@ namespace Mem {
//? Calculate percentages
for (const auto& name : mem_names) {
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / Shared::totalMem));
while (cmp_greater(mem.percent.at(name).size(), width * 2)) mem.percent.at(name).pop_front();
}
if (show_swap and mem.stats.at("swap_total") > 0) {
for (const auto& name : swap_names) {
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total")));
while (cmp_greater(mem.percent.at(name).size(), width * 2)) mem.percent.at(name).pop_front();
}
has_swap = true;
}
else
has_swap = false;
//? Remove values beyond whats needed for graph creation
for (auto& [ignored, deq] : mem.percent) {
if (cmp_greater(deq.size(), Term::width * 2)) deq.pop_front();
}
//? Get disks stats
if (show_disks and not disks_fail) {
if (show_disks) {
try {
auto& disks_filter = Config::getS("disks_filter");
bool filter_exclude = false;
@ -674,9 +665,10 @@ namespace Mem {
}
//? Get mounts from /etc/mtab or /proc/self/mounts
vector<string> found;
diskread.open((fs::exists("/etc/mtab") ? fs::path("/etc/mtab") : Shared::procPath / "self/mounts"));
if (diskread.good()) {
vector<string> found;
found.reserve(last_found.size());
string dev, mountpoint, fstype;
while (not diskread.eof()) {
std::error_code ec;
@ -693,6 +685,7 @@ namespace Mem {
or (use_fstab and v_contains(fstab, mountpoint))
or (not use_fstab and only_physical and v_contains(fstypes, fstype))) {
found.push_back(mountpoint);
if (not v_contains(last_found, mountpoint)) redraw = true;
//? Save mountpoint, name, dev path and path to /sys/block stat file
if (not disks.contains(mountpoint)) {
@ -701,7 +694,7 @@ namespace Mem {
if (disks.at(mountpoint).name.empty()) disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint);
string devname = disks.at(mountpoint).dev.filename();
while (devname.size() >= 2) {
if (fs::exists("/sys/block/" + devname + "/stat")) {
if (fs::exists("/sys/block/" + devname + "/stat", ec) and access(string("/sys/block/" + devname + "/stat").c_str(), R_OK) == 0) {
disks.at(mountpoint).stat = "/sys/block/" + devname + "/stat";
break;
}
@ -720,6 +713,7 @@ namespace Mem {
else
it++;
}
last_found = std::move(found);
}
else
throw std::runtime_error("Failed to get mounts from /etc/mtab and /proc/self/mounts");
@ -727,7 +721,7 @@ namespace Mem {
//? Get disk/partition stats
for (auto& [mountpoint, disk] : disks) {
if (not fs::exists(mountpoint)) continue;
if (std::error_code ec; not fs::exists(mountpoint, ec)) continue;
struct statvfs vfs;
if (statvfs(mountpoint.c_str(), &vfs) < 0) {
Logger::warning("Failed to get disk/partition stats with statvfs() for: " + mountpoint);
@ -752,14 +746,14 @@ namespace Mem {
disks.at("swap").used_percent = mem.percent.at("swap_used").back();
disks.at("swap").free_percent = mem.percent.at("swap_free").back();
}
for (const auto& name : found)
for (const auto& name : last_found)
if (not is_in(name, "/", "swap")) mem.disks_order.push_back(name);
//? Get disks IO
int64_t sectors_read, sectors_write;
disk_ios = 0;
for (auto& [ignored, disk] : disks) {
if (disk.stat.empty()) continue;
if (disk.stat.empty() or access(disk.stat.c_str(), R_OK) != 0) continue;
diskread.open(disk.stat);
if (diskread.good()) {
disk_ios++;
@ -770,7 +764,7 @@ namespace Mem {
else
disk.io_read.push_back(max(0l, (sectors_read - disk.old_io.at(0)) * 512));
disk.old_io.at(0) = sectors_read;
if (cmp_greater(disk.io_read.size(), Term::width * 2)) disk.io_read.pop_front();
while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front();
for (int i = 0; i < 3; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); }
diskread >> sectors_write;
@ -779,14 +773,13 @@ namespace Mem {
else
disk.io_write.push_back(max(0l, (sectors_write - disk.old_io.at(1)) * 512));
disk.old_io.at(1) = sectors_write;
if (cmp_greater(disk.io_write.size(), Term::width * 2)) disk.io_write.pop_front();
while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front();
}
diskread.close();
}
}
catch (const std::exception& e) {
Logger::warning("Error in Mem::collect() : " + (string)e.what());
disks_fail = true;
}
}
@ -823,7 +816,7 @@ namespace Net {
if (not no_update and errors < 3) {
//? Get interface list using getifaddrs() wrapper
getifaddr_wrapper if_wrap = {};
getifaddr_wrapper if_wrap {};
if (if_wrap.status != 0) {
errors++;
Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status));
@ -879,7 +872,7 @@ namespace Net {
//? Add values to graph
bandwidth.push_back(saved_stat.speed);
if (cmp_greater(bandwidth.size(), Term::width.load() * 2)) bandwidth.pop_front();
while (cmp_greater(bandwidth.size(), width * 2)) bandwidth.pop_front();
//? Set counters for auto scaling
if (net_auto and selected_iface == iface) {
@ -904,6 +897,7 @@ namespace Net {
else
it++;
}
net.compact();
}
}
@ -967,27 +961,17 @@ namespace Net {
}
namespace Proc {
namespace {
struct p_cache {
string name, cmd, user;
size_t name_offset;
uint64_t cpu_t = 0, cpu_s = 0;
string prefix = "";
size_t depth = 0;
bool collapsed = false;
};
vector<proc_info> current_procs;
unordered_flat_map<size_t, p_cache> cache;
unordered_flat_map<string, string> uid_user;
fs::file_time_type passwd_time;
uint64_t cputimes;
vector<proc_info> current_procs;
unordered_flat_map<string, string> uid_user;
int counter = 0;
}
fs::file_time_type passwd_time;
uint64_t cputimes;
int collapse = -1, expand = -1;
uint64_t old_cputimes = 0;
atomic<int> numpids = 0;
int filter_found = 0;
detail_container detailed;
@ -1013,14 +997,12 @@ namespace Proc {
//? Add process to vector if not filtered out or currently in a collapsed sub-tree
if (not collapsed and not filtering) {
out_procs.push_back(cur_proc);
//? Try to find name of the binary file and append to program name if not the same (replacing cmd string)
if (std::string_view cmd_view = cur_proc.cmd; not cmd_view.empty() and not cmd_view.starts_with('(')) {
//? Try to find name of the binary file and append to program name if not the same
if (cur_proc.short_cmd.empty() and not cur_proc.cmd.empty()) {
std::string_view cmd_view = cur_proc.cmd;
cmd_view = cmd_view.substr(0, min(cmd_view.find(' '), cmd_view.size()));
cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
if (cmd_view == cur_proc.name)
out_procs.back().cmd.clear();
else
out_procs.back().cmd = '(' + (string)cmd_view + ')';
out_procs.back().short_cmd = (string)cmd_view;
}
}
@ -1033,7 +1015,7 @@ namespace Proc {
out_procs.back().threads += p.threads;
}
else children++;
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cache.at(cur_proc.pid).collapsed), filter, found);
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found);
}
if (collapsed or filtering) return;
@ -1042,7 +1024,7 @@ namespace Proc {
out_procs.back().prefix.replace(out_procs.back().prefix.size() - 8, 8, " └─ ");
//? Add collapse/expand symbols if process have any children
out_procs.at(cur_pos).prefix = ""s * cur_depth + (children > 0 ? (cache.at(cur_proc.pid).collapsed ? "[+]─" : "[-]─") : " ├─ ");
out_procs.at(cur_pos).prefix = ""s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ ");
}
//* Get detailed info for selected process
@ -1062,14 +1044,17 @@ namespace Proc {
//? Update cpu percent deque for process cpu graph
if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount;
detailed.cpu_percent.push_back(round(detailed.entry.cpu_p));
if (cmp_greater(detailed.cpu_percent.size(), Term::width.load())) detailed.cpu_percent.pop_front();
while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front();
//? Process runtime
detailed.elapsed = sec_to_dhms(uptime - (cache.at(pid).cpu_s / Shared::clkTck));
detailed.elapsed = sec_to_dhms(uptime - (detailed.entry.cpu_s / Shared::clkTck));
if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3);
//? Get parent process name
if (detailed.parent.empty() and cache.contains(detailed.entry.ppid)) detailed.parent = cache.at(detailed.entry.ppid).name;
if (detailed.parent.empty()) {
auto p_entry = rng::find(procs, detailed.entry.ppid, &proc_info::pid);
if (p_entry != procs.end()) detailed.parent = p_entry->name;
}
//? Expand process status from single char to explanative string
detailed.status = (proc_states.contains(detailed.entry.state)) ? proc_states.at(detailed.entry.state) : "Unknown";
@ -1111,7 +1096,7 @@ namespace Proc {
redraw = true;
}
if (cmp_greater(detailed.mem_bytes.size(), Term::width.load())) detailed.mem_bytes.pop_front();
while (cmp_greater(detailed.mem_bytes.size(), width)) detailed.mem_bytes.pop_front();
//? Get bytes read and written from proc/[pid]/io
if (fs::exists(pid_path / "io")) {
@ -1140,7 +1125,7 @@ namespace Proc {
}
//* Collects and sorts process information from /proc
auto collect(const bool no_update) -> vector<proc_info> {
auto collect(const bool no_update) -> vector<proc_info>& {
const auto& sorting = Config::getS("proc_sorting");
const auto& reverse = Config::getB("proc_reversed");
const auto& filter = Config::getS("proc_filter");
@ -1151,16 +1136,16 @@ namespace Proc {
ifstream pread;
string long_string;
string short_str;
filter_found = 0;
const double uptime = system_uptime();
vector<proc_info> procs;
procs.reserve(current_procs.size() + 10);
const int cmult = (per_core) ? Shared::coreCount : 1;
bool got_detailed = false;
//* Use pids from last update if only changing filter, sorting or tree options
if (no_update and not cache.empty()) {
procs = current_procs;
if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, round(uptime), procs);
if (no_update and not current_procs.empty()) {
if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, round(uptime), current_procs);
}
//* ---------------------------------------------Collection start----------------------------------------------
else {
@ -1196,31 +1181,43 @@ namespace Proc {
pread.close();
//? Iterate over all pids in /proc
vector<size_t> found;
for (const auto& d: fs::directory_iterator(Shared::procPath)) {
if (Runner::stopping)
return procs;
return current_procs;
if (pread.is_open()) pread.close();
const string pid_str = d.path().filename();
if (not isdigit(pid_str[0])) continue;
proc_info new_proc (stoul(pid_str));
const size_t pid = stoul(pid_str);
found.push_back(pid);
//? Cache program name, command and username
if (not cache.contains(new_proc.pid)) {
string name, cmd, user;
//? Check if pid already exists
auto find_old = rng::find(current_procs, pid, &proc_info::pid);
bool no_cache = false;
if (find_old == current_procs.end()) {
current_procs.push_back({pid});
find_old = current_procs.end() - 1;
no_cache = true;
}
auto& new_proc = *find_old;
//? Get program name, command and username
if (no_cache) {
pread.open(d.path() / "comm");
if (not pread.good()) continue;
getline(pread, name);
getline(pread, new_proc.name);
pread.close();
size_t name_offset = rng::count(name, ' ');
new_proc.name_offset = rng::count(new_proc.name, ' ');
pread.open(d.path() / "cmdline");
if (not pread.good()) continue;
long_string.clear();
while(getline(pread, long_string, '\0')) cmd += long_string + ' ';
while(getline(pread, long_string, '\0')) new_proc.cmd += long_string + ' ';
pread.close();
if (not cmd.empty()) cmd.pop_back();
if (not new_proc.cmd.empty()) new_proc.cmd.pop_back();
pread.open(d.path() / "status");
if (not pread.good()) continue;
@ -1237,26 +1234,20 @@ namespace Proc {
}
}
pread.close();
user = (uid_user.contains(uid)) ? uid_user.at(uid) : uid;
cache[new_proc.pid] = {name, cmd, user, name_offset};
new_proc.user = (uid_user.contains(uid)) ? uid_user.at(uid) : uid;
}
new_proc.name = cache.at(new_proc.pid).name;
new_proc.cmd = cache.at(new_proc.pid).cmd;
new_proc.user = cache.at(new_proc.pid).user;
//? Parse /proc/[pid]/stat
pread.open(d.path() / "stat");
if (not pread.good()) continue;
//? Check cached value for whitespace characters in name and set offset to get correct fields from stat file
const auto& offset = cache.at(new_proc.pid).name_offset;
const auto& offset = new_proc.name_offset;
short_str.clear();
size_t x = 0, next_x = 3;
uint64_t cpu_t = 0;
try {
for (;;) {
while (x < 40) {
while (pread.good() and ++x < next_x + offset) {
pread.ignore(SSmax, ' ');
}
@ -1284,15 +1275,15 @@ namespace Proc {
continue;
case 20: //? Number of threads
new_proc.threads = stoull(short_str);
if (cache.at(new_proc.pid).cpu_s == 0) {
if (new_proc.cpu_s == 0) {
next_x = 22;
cache.at(new_proc.pid).cpu_t = cpu_t;
new_proc.cpu_t = cpu_t;
}
else
next_x = 24;
continue;
case 22: //? Save cpu seconds to cache if missing
cache.at(new_proc.pid).cpu_s = stoull(short_str);
case 22: //? Get cpu seconds if missing
new_proc.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)
@ -1301,135 +1292,150 @@ namespace Proc {
continue;
case 39: //? CPU number last executed on
new_proc.cpu_n = stoull(short_str);
goto stat_loop_done;
x++;
break;
}
}
}
catch (const std::invalid_argument&) { continue; }
catch (const std::out_of_range&) { continue; }
stat_loop_done:
pread.close();
if (x-offset < 24) continue;
//? Process cpu usage since last update
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache.at(new_proc.pid).cpu_t) / max(1ul, cputimes - old_cputimes)) / 10.0;
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - new_proc.cpu_t) / max(1ul, cputimes - old_cputimes)) / 10.0;
//? Process cumulative cpu usage since process start
new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - cache.at(new_proc.pid).cpu_s);
new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - new_proc.cpu_s);
//? Update cache with latest cpu times
cache.at(new_proc.pid).cpu_t = cpu_t;
new_proc.cpu_t = cpu_t;
if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
got_detailed = true;
}
//? Push process to vector
procs.push_back(new_proc);
}
//? Clear dead processes from cache at a regular interval
if (++counter >= 1000 or (cache.size() > procs.size() + 100)) {
counter = 0;
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++;
}
}
// //? Clear dead processes from cache at a regular interval
// if (++counter >= 1000 or (cache.size() > found.size() + 100)) {
// counter = 0;
// for (auto it = cache.begin(); it != cache.end();) {
// if (not v_contains(found, it->first))
// it = cache.erase(it);
// else
// it++;
// }
// cache.compact();
// }
//? Clear dead processes from list
auto eraser = rng::remove_if(current_procs, [&](const auto& element){ return not v_contains(found, element.pid); });
current_procs.erase(eraser.begin(), eraser.end());
//? Update the details info box for process if active
if (show_detailed and got_detailed) {
_collect_details(detailed_pid, round(uptime), procs);
_collect_details(detailed_pid, round(uptime), current_procs);
}
else if (show_detailed and not got_detailed and detailed.status != "Dead") {
detailed.status = "Dead";
redraw = true;
}
old_cputimes = cputimes;
current_procs = procs;
// current_procs.clear();
// current_procs = procs;
}
//* ---------------------------------------------Collection done-----------------------------------------------
//* Match filter if defined
if (not tree and not filter.empty()) {
const auto filtered = rng::remove_if(procs, [&filter](const auto& p) {
return (not s_contains(to_string(p.pid), filter)
and not s_contains(p.name, filter)
and not s_contains(p.cmd, filter)
and not s_contains(p.user, filter));
});
procs.erase(filtered.begin(), filtered.end());
}
//* 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(current_procs, rng::greater{}, &proc_info::pid); break;
case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break;
case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break;
case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break;
case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break;
case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break;
case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break;
case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break;
}
if (reverse) rng::reverse(current_procs);
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
if (not tree and not reverse and sorting == "cpu lazy") {
double max = 10.0, target = 30.0;
for (size_t i = 0, x = 0, offset = 0; i < procs.size(); i++) {
if (i <= 5 and procs.at(i).cpu_p > max)
max = procs.at(i).cpu_p;
for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) {
if (i <= 5 and current_procs.at(i).cpu_p > max)
max = current_procs.at(i).cpu_p;
else if (i == 6)
target = (max > 30.0) ? max : 10.0;
if (i == offset and procs.at(i).cpu_p > 30.0)
if (i == offset and current_procs.at(i).cpu_p > 30.0)
offset++;
else if (procs.at(i).cpu_p > target) {
rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
else if (current_procs.at(i).cpu_p > target) {
rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1);
if (++x > 10) break;
}
}
}
//* Match filter if defined
for (auto& p : current_procs) {
if (not tree and not filter.empty()) {
if (not s_contains(to_string(p.pid), filter)
and not s_contains(p.name, filter)
and not s_contains(p.cmd, filter)
and not s_contains(p.user, filter)) {
p.filtered = true;
filter_found++;
}
}
else if (not tree) {
p.filtered = false;
}
}
//* Generate tree view if enabled
if (tree) {
if (collapse > -1 and collapse == expand) {
if (cache.contains(collapse)) cache.at(collapse).collapsed = not cache.at(collapse).collapsed;
collapse = expand = -1;
}
else if (collapse > -1) {
if (cache.contains(collapse)) cache.at(collapse).collapsed = true;
collapse = -1;
}
else if (expand > -1) {
if (cache.contains(expand)) cache.at(expand).collapsed = false;
expand = -1;
if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) {
auto collapser = rng::find(current_procs, find_pid, &proc_info::pid);
if (collapser != current_procs.end()) {
if (collapse == expand) {
collapser->collapsed = not collapser->collapsed;
collapse = expand = -1;
}
else if (collapse > -1) {
collapser->collapsed = true;
collapse = -1;
}
else if (expand > -1) {
collapser->collapsed = false;
expand = -1;
}
}
}
vector<proc_info> tree_procs;
tree_procs.reserve(procs.size());
tree_procs.reserve(current_procs.size());
//? Stable sort to retain selected sorting among processes with the same parent
rng::stable_sort(procs, rng::less{}, &proc_info::ppid);
rng::stable_sort(current_procs, rng::less{}, &proc_info::ppid);
//? Start recursive iteration over processes with the lowest shared parent pids
for (const auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
_tree_gen(p, procs, tree_procs, 0, false, filter);
for (const auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
_tree_gen(p, current_procs, tree_procs, 0, false, filter);
}
procs = std::move(tree_procs);
//procs.clear();
current_procs = std::move(tree_procs);
}
numpids = (int)procs.size();
numpids = (not filter.empty() ? filter_found : (int)current_procs.size());
return procs;
return current_procs;
}
}

View file

@ -48,6 +48,7 @@ namespace Runner {
extern atomic<bool> active;
extern atomic<bool> reading;
extern atomic<bool> stopping;
extern pthread_t runner_id;
void run(const string& box="", const bool no_update=false, const bool force_redraw=false);
void stop();
@ -206,13 +207,16 @@ namespace Proc {
struct proc_info {
size_t pid = 0;
string name = "", cmd = "";
size_t threads = 0;
string short_cmd = "";
size_t threads = 0, name_offset = 0;
string user = "";
uint64_t mem = 0;
double cpu_p = 0.0, cpu_c = 0.0;
char state = '0';
uint64_t cpu_n = 0, p_nice = 0, ppid = 0;
uint64_t cpu_n = 0, p_nice = 0, ppid = 0, cpu_s = 0, cpu_t = 0;
string prefix = "";
size_t depth = 0;
bool collapsed = false, filtered = false;
};
//* Container for process info box
@ -230,7 +234,7 @@ namespace Proc {
extern detail_container detailed;
//* Collect and sort process information from /proc
auto collect(const bool no_update=false) -> vector<proc_info>;
auto collect(const bool no_update=false) -> vector<proc_info>&;
//* Update current selection and view, returns -1 if no change otherwise the current selection
int selection(const string& cmd_key);

View file

@ -98,7 +98,7 @@ namespace Theme {
{ "selected_bg", "\x1b[41m" },
{ "selected_fg", "\x1b[97m" },
{ "inactive_fg", "\x1b[90m" },
{ "graph_text", "\x1b[37m" },
{ "graph_text", "\x1b[90m" },
{ "meter_bg", "\x1b[90m" },
{ "proc_misc", "\x1b[92m" },
{ "cpu_box", "\x1b[32m" },

View file

@ -120,8 +120,6 @@ namespace Term {
void restore() {
if (initialized) {
echo(true);
linebuffered(true);
tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush;
initialized = false;
@ -274,9 +272,9 @@ namespace Tools {
else if (out.size() >= 2) out.resize(out.size() - 2);
}
if (shorten) {
if (out.find('.') != string::npos) out = to_string((int)round(stof(out)));
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;}
out.push_back(units[start][0]);
if (out.find('.') != string::npos) out = to_string((int)round(stof(out)));
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;}
out.push_back(units[start][0]);
}
else out += " " + units[start];
@ -383,6 +381,7 @@ namespace Logger {
else logfile.clear();
}
catch (const std::exception& e) {
logfile.clear();
throw std::runtime_error("Exception in Logger::log_write() : " + (string)e.what());
}
}

View file

@ -132,10 +132,10 @@ namespace Tools {
constexpr auto ZeroSec = std::chrono::seconds(0);
extern atomic<int> active_locks;
//* Return number of UTF8 characters in a string (counts UTF-8 characters with a width > 1 as 2 characters)
//* Return number of UTF8 characters in a string (wide=true counts UTF-8 characters with a width > 1 as 2 characters)
inline size_t ulen(const string& str, const bool wide=false) {
return std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; })
+ (wide ? std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) > 0xef); }) : 0);
+ (wide ? std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) > 0xef); }) : 0);
}
//* Resize a string consisting of UTF8 characters (only reduces size)
@ -183,7 +183,7 @@ namespace Tools {
//* Compare <first> with all following values
template<typename First, typename ... T>
inline bool is_in(const First& first, const T& ... t) {
return ((first == t) || ...);
return ((first == t) or ...);
}
//* Return current time since epoch in seconds
@ -203,12 +203,12 @@ namespace Tools {
//* Check if a string is a valid bool value
inline bool isbool(const string& str) {
return (str == "true") or (str == "false") or (str == "True") or (str == "False");
return is_in(str, "true", "false", "True", "False");
}
//* Convert string to bool, returning any value not equal to "true" or "True" as false
inline bool stobool(const string& str) {
return (str == "true" or str == "True");
return is_in(str, "true", "True");
}
//* Check if a string is a valid integer value