mirror of
https://github.com/aristocratos/btop.git
synced 2024-05-21 04:43:36 +12:00
Fixed memory leak and greatly reduced general memory usage
This commit is contained in:
parent
7ab765b575
commit
8bd97b4f24
325
src/btop.cpp
325
src/btop.cpp
|
@ -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 *) ¤t_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();
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue