mirror of
https://github.com/aristocratos/btop.git
synced 2024-05-18 19:33:03 +12:00
Added processes tree view
This commit is contained in:
parent
c4b55c7dfd
commit
ba481d042c
53
btop.cpp
53
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<string> 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<uint64_t> avgtimes = {0};
|
||||
uint timer = 2000;
|
||||
bool filtering = false;
|
||||
bool reversing = false;
|
||||
int sortint = Proc::sort_map["cpu lazy"];
|
||||
vector<string> 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;
|
||||
}
|
||||
|
|
|
@ -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<string, string> stringsTmp;
|
||||
|
||||
|
@ -154,7 +154,6 @@ namespace Config {
|
|||
unordered_flat_map<string, int> ints = {
|
||||
{"update_ms", 2000},
|
||||
{"proc_update_mult", 2},
|
||||
{"tree_depth", 3}
|
||||
};
|
||||
unordered_flat_map<string, int> 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<string, int> valid_names;
|
||||
vector<string> 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;
|
||||
}
|
||||
|
|
|
@ -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<uint, p_cache> cache;
|
||||
unordered_flat_map<string, string> uid_user;
|
||||
|
@ -82,7 +82,7 @@ namespace Proc {
|
|||
atomic<bool> stop (false);
|
||||
atomic<bool> collecting (false);
|
||||
atomic<bool> drawing (false);
|
||||
array<string, 8> sort_array = {
|
||||
vector<string> sort_vector = {
|
||||
"pid",
|
||||
"name",
|
||||
"command",
|
||||
|
@ -92,7 +92,6 @@ namespace Proc {
|
|||
"cpu direct",
|
||||
"cpu lazy",
|
||||
};
|
||||
unordered_flat_map<string, int> 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<proc_info>& in_procs, vector<proc_info>& 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<proc_info> 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<proc_info> 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<proc_info> 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++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue