From ba481d042cf340542bf642283a25a1faf5ec421b Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sun, 13 Jun 2021 23:12:11 +0200 Subject: [PATCH] Added processes tree view --- btop.cpp | 53 +++++++++++++++++++---------- src/btop_config.h | 41 +++++++++++------------ src/btop_linux.h | 85 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 119 insertions(+), 60 deletions(-) diff --git a/btop.cpp b/btop.cpp index fdeadf5..feb15ab 100644 --- a/btop.cpp +++ b/btop.cpp @@ -120,9 +120,9 @@ void clean_quit(int sig){ Term::restore(); if (!Global::debuginit) cout << Term::normal_screen << Term::show_cursor << flush; } - if (Global::debug) Logger::debug("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time)); Global::quitting = true; Config::write(); + Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time)); if (sig != -1) exit(sig); } @@ -256,18 +256,16 @@ int main(int argc, char **argv){ } } - //? Read config file if present + //? Config init { vector load_errors; Config::load(Config::conf_file, load_errors); if (Global::debug) Logger::loglevel = 4; else Logger::loglevel = v_index(Logger::log_levels, Config::getS("log_level")); - if (Logger::loglevel == 4) Logger::debug("Starting with logger set to debug."); + Logger::info("Log level set to " + Config::getS("log_level") + "."); - if (!load_errors.empty()) { - for (auto& err_str : load_errors) Logger::error(err_str); - } + for (auto& err_str : load_errors) Logger::warning(err_str); } if (!string(getenv("LANG")).ends_with("UTF-8") && !string(getenv("LANG")).ends_with("utf-8")) { @@ -501,8 +499,6 @@ int main(int argc, char **argv){ list avgtimes = {0}; uint timer = 2000; bool filtering = false; - bool reversing = false; - int sortint = Proc::sort_map["cpu lazy"]; vector greyscale; string filter; string filter_cur; @@ -515,13 +511,13 @@ int main(int argc, char **argv){ } string pbox = Draw::createBox({.x = 1, .y = 10, .width = Term::width, .height = Term::height - 16, .line_color = Theme::c("proc_box"), .title = "testbox", .title2 = "below", .fill = false, .num = 7}); - pbox += Mv::r(1) + rjust("Pid:", 8) + " " + ljust("Program:", 16) + " " + ljust("Command:", Term::width - 70) + " Threads: " + - ljust("User:", 10) + " " + rjust("MemB", 5) + " " + rjust("Cpu%", 14) + "\n" + Mv::save; + pbox += Mv::r(1) + Theme::c("title") + Fx::b + rjust("Pid:", 8) + " " + ljust("Program:", 16) + " " + ljust("Command:", Term::width - 70) + " Threads: " + + ljust("User:", 10) + " " + rjust("MemB", 5) + " " + rjust("Cpu%", 14) + "\n" + Fx::reset + Mv::save; while (key != "q") { timestamp = time_micros(); tsl = time_ms() + timer; - auto plist = Proc::collect(Proc::sort_array[sortint], reversing, filter, Config::getB("proc_per_core")); + auto plist = Proc::collect(); timestamp2 = time_micros(); timestamp = timestamp2 - timestamp; ostring.clear(); @@ -530,14 +526,27 @@ int main(int argc, char **argv){ ostring = Mv::u(2) + Mv::l(Term::width) + Mv::r(12) + trans("Filter: " + filter + (filtering ? Fx::bl + "█" + Fx::reset : "")) + Mv::l(Term::width) + trans(rjust("Per core: " + (Config::getB("proc_per_core") ? "On "s : "Off"s) + " Sorting: " - + string(Proc::sort_array[sortint]), Term::width - 3)) + + string(Config::getS("proc_sorting")), Term::width - 3)) + Mv::restore; for (auto& p : plist){ - ostring += Mv::r(1) + greyscale[lc] + rjust(to_string(p.pid), 8) + " " + ljust(p.name, 16) + " " + ljust(p.cmd, Term::width - 66, true) + " " + if (!Config::getB("proc_tree")) { + ostring += Mv::r(1) + greyscale[lc] + rjust(to_string(p.pid), 8) + " " + ljust(p.name, 16) + " " + ljust(p.cmd, Term::width - 66, true) + " " + rjust(to_string(p.threads), 5) + " " + ljust(p.user, 10) + " " + rjust(floating_humanizer(p.mem, true), 5) + string(11, ' ') + (p.cpu_p < 10 || p.cpu_p >= 100 ? rjust(to_string(p.cpu_p), 3) + " " : rjust(to_string(p.cpu_p), 4)) + "\n"; + } + else { + string cmd_cond; + if (!p.cmd.empty()) { + cmd_cond = p.cmd.substr(0, std::min(p.cmd.find(' '), p.cmd.size())); + cmd_cond = cmd_cond.substr(std::min(cmd_cond.find_last_of('/') + 1, cmd_cond.size())); + } + ostring += Mv::r(1) + greyscale[lc] + ljust(p.prefix + to_string(p.pid) + " " + p.name + " " + (!cmd_cond.empty() && cmd_cond != p.name ? "(" + cmd_cond + ")" : ""), Term::width - 40, true) + " " + + rjust(to_string(p.threads), 5) + " " + ljust(p.user, 10) + " " + rjust(floating_humanizer(p.mem, true), 5) + string(11, ' ') + + (p.cpu_p < 10 || p.cpu_p >= 100 ? rjust(to_string(p.cpu_p), 3) + " " : rjust(to_string(p.cpu_p), 4)) + + "\n"; + } if (lc++ > Term::height - 21) break; } @@ -562,15 +571,25 @@ int main(int argc, char **argv){ else if (key == "space") filter.push_back(' '); else if (ulen(key) == 1 ) filter.append(key); else { key.clear(); continue; } + if (filter != Config::getS("proc_filter")) Config::set("proc_filter", filter); break; } else if (key == "q") break; - else if (key == "left") { if (--sortint < 0) sortint = (int)Proc::sort_array.size() - 1; } - else if (key == "right") { if (++sortint > (int)Proc::sort_array.size() - 1) sortint = 0; } + else if (key == "left") { + int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting")); + if (--cur_i < 0) cur_i = Proc::sort_vector.size() - 1; + Config::set("proc_sorting", Proc::sort_vector.at(cur_i)); + } + else if (key == "right") { + int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting")); + if (++cur_i > (int)Proc::sort_vector.size() - 1) cur_i = 0; + Config::set("proc_sorting", Proc::sort_vector.at(cur_i)); + } else if (key == "f") filtering = true; - else if (key == "r") reversing = !reversing; + else if (key == "t") Config::flip("proc_tree"); + else if (key == "r") Config::flip("proc_reversed"); else if (key == "c") Config::flip("proc_per_core"); - else if (key == "delete") filter.clear(); + else if (key == "delete") { filter.clear(); Config::set("proc_filter", filter); } else continue; break; } diff --git a/src/btop_config.h b/src/btop_config.h index afd8159..3ab70e6 100644 --- a/src/btop_config.h +++ b/src/btop_config.h @@ -56,7 +56,6 @@ namespace Config { "#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly."}, {"proc_reversed", "#* Reverse sorting order, True or False."}, {"proc_tree", "#* Show processes as a tree."}, - {"tree_depth", "#* Which depth the tree view should auto collapse processes at."}, {"proc_colors", "#* Use the cpu graph colors in the process list."}, {"proc_gradient", "#* Use a darkening gradient in the process list."}, {"proc_per_core", "#* If process cpu usage should be of the core it's running on or usage of the total available cpu power."}, @@ -115,7 +114,8 @@ namespace Config { {"net_download", "10M"}, {"net_upload", "10M"}, {"net_iface", ""}, - {"log_level", "WARNING"} + {"log_level", "WARNING"}, + {"proc_filter", ""} }; unordered_flat_map stringsTmp; @@ -154,7 +154,6 @@ namespace Config { unordered_flat_map ints = { {"update_ms", 2000}, {"proc_update_mult", 2}, - {"tree_depth", 3} }; unordered_flat_map intsTmp; @@ -215,26 +214,24 @@ namespace Config { //* Unlock config and write any cached values to config void unlock(){ atomic_wait_set(writelock); - if (stringsTmp.size() > 0) { - for (auto& item : stringsTmp){ - strings.at(item.first) = item.second; - } - stringsTmp.clear(); + + for (auto& item : stringsTmp){ + strings.at(item.first) = item.second; } - if (intsTmp.size() > 0) { - for (auto& item : intsTmp){ - ints.at(item.first) = item.second; - } - intsTmp.clear(); + stringsTmp.clear(); + + for (auto& item : intsTmp){ + ints.at(item.first) = item.second; } - if (boolsTmp.size() > 0) { - for (auto& item : boolsTmp){ - bools.at(item.first) = item.second; - } - boolsTmp.clear(); + intsTmp.clear(); + + for (auto& item : boolsTmp){ + bools.at(item.first) = item.second; } - writelock = false; + boolsTmp.clear(); + locked = false; + writelock = false; } //* Load the config file from disk @@ -247,9 +244,9 @@ namespace Config { } std::ifstream cread(conf_file); if (cread.good()) { - unordered_flat_map valid_names; + vector valid_names; for (auto &n : descriptions) - valid_names[n[0]] = 0; + valid_names.push_back(n[0]); string v_string; getline(cread, v_string, '\n'); if (!v_string.ends_with(Global::Version)) @@ -262,7 +259,7 @@ namespace Config { } string name, value; getline(cread, name, '='); - if (!valid_names.contains(name)) { + if (!v_contains(valid_names, name)) { cread.ignore(SSmax, '\n'); continue; } diff --git a/src/btop_linux.h b/src/btop_linux.h index 6e68b30..626d1cc 100644 --- a/src/btop_linux.h +++ b/src/btop_linux.h @@ -41,7 +41,7 @@ tab-size = 4 using std::string, std::vector, std::array, std::ifstream, std::atomic, std::numeric_limits, std::streamsize; -using std::cout, std::flush, std::endl; +using std::cout, std::flush, std::endl, std::string_literals::operator""s; namespace fs = std::filesystem; using namespace Tools; @@ -64,7 +64,7 @@ namespace Proc { uint64_t cpu_t = 0, cpu_s = 0; string prefix = ""; size_t depth = 0; - bool collapsed = false; + int collapsed = -1; }; unordered_flat_map cache; unordered_flat_map uid_user; @@ -82,7 +82,7 @@ namespace Proc { atomic stop (false); atomic collecting (false); atomic drawing (false); - array sort_array = { + vector sort_vector = { "pid", "name", "command", @@ -92,7 +92,6 @@ namespace Proc { "cpu direct", "cpu lazy", }; - unordered_flat_map sort_map; //* Container for process information struct proc_info { @@ -108,12 +107,49 @@ namespace Proc { string prefix = ""; }; + //* Generate process tree list + void _tree_gen(proc_info& cur_proc, vector& in_procs, vector& out_procs, int cur_depth=0, bool collapsed=false){ + auto cur_pos = out_procs.size(); + if (!collapsed) + out_procs.push_back(cur_proc); + int children = 0; + for (auto& p : in_procs) { + if (p.ppid == (int)cur_proc.pid) { + children++; + if (collapsed) { + out_procs.back().cpu_p += p.cpu_p; + out_procs.back().mem += p.mem; + out_procs.back().threads += p.threads; + _tree_gen(p, in_procs, out_procs, cur_depth + 1, collapsed); + } + else _tree_gen(p, in_procs, out_procs, cur_depth + 1, (cache.at(cur_proc.pid).collapsed == 1)); + } + } + if (collapsed) return; + + if (out_procs.size() > cur_pos + 1 && !out_procs.back().prefix.ends_with("] ")) { + std::string_view n_prefix = out_procs.back().prefix; + n_prefix.remove_suffix(8); + out_procs.back().prefix = (string)n_prefix + " └─ "; + } + + string prefix = " ├─ "; + if (children > 0) prefix = (cache.at(cur_proc.pid).collapsed == 1) ? "[+] " : "[-] "; + + out_procs.at(cur_pos).prefix = " │ "s * cur_depth + prefix; + } + vector current_procs; //* Collects and sorts process information from /proc, saves to and returns reference to Proc::current_procs; - auto& collect(string sorting="pid", bool reverse=false, string filter="", bool per_core=true, bool tree=false){ + auto& collect(){ atomic_wait_set(collecting); + auto& sorting = Config::getS("proc_sorting"); + auto& reverse = Config::getB("proc_reversed"); + auto& filter = Config::getS("proc_filter"); + auto& per_core = Config::getB("proc_per_core"); + auto& tree = Config::getB("proc_tree"); ifstream pread; auto uptime = system_uptime(); vector procs; @@ -305,20 +341,19 @@ namespace Proc { //* Sort processes - std::ranges::sort(procs, [sortint = sort_map.at(sorting), &reverse](proc_info& a, proc_info& b) { - switch (sortint) { - case 0: return (reverse) ? a.pid < b.pid : a.pid > b.pid; - case 1: return (reverse) ? a.name < b.name : a.name > b.name; - case 2: return (reverse) ? a.cmd < b.cmd : a.cmd > b.cmd; - case 3: return (reverse) ? a.threads < b.threads : a.threads > b.threads; - case 4: return (reverse) ? a.user < b.user : a.user > b.user; - case 5: return (reverse) ? a.mem < b.mem : a.mem > b.mem; - case 6: return (reverse) ? a.cpu_p < b.cpu_p : a.cpu_p > b.cpu_p; - case 7: return (reverse) ? a.cpu_c < b.cpu_c : a.cpu_c > b.cpu_c; - } - return false; + std::ranges::sort(procs, [sortint = v_index(sort_vector, sorting), &reverse](proc_info& a, proc_info& b) { + switch (sortint) { + case 0: return (reverse) ? a.pid < b.pid : a.pid > b.pid; + case 1: return (reverse) ? a.name < b.name : a.name > b.name; + case 2: return (reverse) ? a.cmd < b.cmd : a.cmd > b.cmd; + case 3: return (reverse) ? a.threads < b.threads : a.threads > b.threads; + case 4: return (reverse) ? a.user < b.user : a.user > b.user; + case 5: return (reverse) ? a.mem < b.mem : a.mem > b.mem; + case 6: return (reverse) ? a.cpu_p < b.cpu_p : a.cpu_p > b.cpu_p; + case 7: return (reverse) ? a.cpu_c < b.cpu_c : a.cpu_c > b.cpu_c; } - ); + return false; + }); //* When using "cpu lazy" sorting push processes with high cpu usage to the front regardless of cumulative usage if (sorting == "cpu lazy" && !reverse) { @@ -331,6 +366,17 @@ namespace Proc { } } + //* Generate tree view if enabled + if (tree) { + auto min_ppid = std::ranges::min(procs, [](proc_info& a, proc_info& b) { return a.ppid < b.ppid; }).ppid; + vector tree_procs; + for (auto& p : procs) { + if (p.ppid == min_ppid) _tree_gen(p, procs, tree_procs); + } + procs.swap(tree_procs); + } + + //* Clear dead processes from cache at a regular interval if (++counter >= 10000 || ((int)cache.size() > npids + 100)) { counter = 0; @@ -373,9 +419,6 @@ namespace Proc { clk_tck = 100; Logger::warning("Could not get system clocks per second. Defaulting to 100, processes cpu usage might be incorrect."); } - - uint i = 0; - for (auto& item : sort_array) sort_map[item] = i++; } }