diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6ffc483..1e20a0e 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -264,6 +264,7 @@ namespace Config { {"net_upload", 100}, {"detailed_pid", 0}, {"selected_pid", 0}, + {"selected_depth", 0}, {"proc_start", 0}, {"proc_selected", 0}, {"proc_last_selected", 0}, @@ -472,6 +473,7 @@ namespace Config { strings.at("selected_name") = Proc::selected_name; ints.at("proc_start") = Proc::start; ints.at("proc_selected") = Proc::selected; + ints.at("selected_depth") = Proc::selected_depth; } for (auto& item : stringsTmp) { diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 5bea6cb..2af3d61 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -1022,7 +1022,7 @@ namespace Proc { int x, y, width = 20, height; int start, selected, select_max; bool shown = true, redraw = true; - int selected_pid = 0; + int selected_pid = 0, selected_depth = 0; string selected_name; unordered_flat_map p_graphs; unordered_flat_map p_wide_cmd; @@ -1345,6 +1345,7 @@ namespace Proc { if (is_selected) { selected_pid = (int)p.pid; selected_name = p.name; + selected_depth = p.depth; } //? Update graphs for processes with above 0.0% cpu usage, delete if below 0.1% 10x times diff --git a/src/btop_input.cpp b/src/btop_input.cpp index d21feec..13ed1e2 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -354,7 +354,16 @@ namespace Input { const auto& current_selection = Config::getI("proc_selected"); if (current_selection == line - y - 1) { redraw = true; - goto proc_mouse_enter; + if (Config::getB("proc_tree")) { + const int x_pos = col - Proc::x; + const int offset = Config::getI("selected_depth") * 3; + if (x_pos > offset and x_pos < 4 + offset) { + process("space"); + return; + } + } + process("enter"); + return; } else if (current_selection == 0 or line - y - 1 == 0) redraw = true; @@ -380,7 +389,6 @@ namespace Input { keep_going = true; } else if (key == "enter") { - proc_mouse_enter: if (Config::getI("proc_selected") == 0 and not Config::getB("show_detailed")) { return; } diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp new file mode 100644 index 0000000..0cafdfb --- /dev/null +++ b/src/btop_shared.cpp @@ -0,0 +1,174 @@ +/* Copyright 2021 Aristocratos (jakob@qvantnet.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +indent = tab +tab-size = 4 +*/ + +#include + +#include +#include + +using std::string_literals::operator""s; +namespace rng = std::ranges; +using namespace Tools; + + +namespace Proc { + void proc_sorter(vector& proc_vec, string sorting, const bool reverse, const bool tree) { + if (reverse) { + switch (v_index(sort_vector, sorting)) { + case 0: rng::stable_sort(proc_vec, rng::less{}, &proc_info::pid); break; + case 1: rng::stable_sort(proc_vec, rng::less{}, &proc_info::name); break; + case 2: rng::stable_sort(proc_vec, rng::less{}, &proc_info::cmd); break; + case 3: rng::stable_sort(proc_vec, rng::less{}, &proc_info::threads); break; + case 4: rng::stable_sort(proc_vec, rng::less{}, &proc_info::user); break; + case 5: rng::stable_sort(proc_vec, rng::less{}, &proc_info::mem); break; + case 6: rng::stable_sort(proc_vec, rng::less{}, &proc_info::cpu_p); break; + case 7: rng::stable_sort(proc_vec, rng::less{}, &proc_info::cpu_c); break; + } + } + else { + switch (v_index(sort_vector, sorting)) { + case 0: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::pid); break; + case 1: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::name); break; + case 2: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::cmd); break; + case 3: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::threads); break; + case 4: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::user); break; + case 5: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::mem); break; + case 6: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::cpu_p); break; + case 7: rng::stable_sort(proc_vec, rng::greater{}, &proc_info::cpu_c); break; + } + } + + //* 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 < proc_vec.size(); i++) { + if (i <= 5 and proc_vec.at(i).cpu_p > max) + max = proc_vec.at(i).cpu_p; + else if (i == 6) + target = (max > 30.0) ? max : 10.0; + if (i == offset and proc_vec.at(i).cpu_p > 30.0) + offset++; + else if (proc_vec.at(i).cpu_p > target) { + rotate(proc_vec.begin() + offset, proc_vec.begin() + i, proc_vec.begin() + i + 1); + if (++x > 10) break; + } + } + } + } + + void tree_sort(vector& proc_vec, const string& sorting, const bool reverse, int& c_index, const int index_max, const bool collapsed) { + if (proc_vec.size() > 1) { + if (reverse) { + switch (v_index(sort_vector, sorting)) { + case 3: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().threads < b.entry.get().threads; }); break; + case 5: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().mem < b.entry.get().mem; }); break; + case 6: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().cpu_p < b.entry.get().cpu_p; }); break; + case 7: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().cpu_c < b.entry.get().cpu_c; }); break; + } + } + else { + switch (v_index(sort_vector, sorting)) { + case 3: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().threads > b.entry.get().threads; }); break; + case 5: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().mem > b.entry.get().mem; }); break; + case 6: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().cpu_p > b.entry.get().cpu_p; }); break; + case 7: rng::stable_sort(proc_vec, [](const auto& a, const auto& b) { return a.entry.get().cpu_c > b.entry.get().cpu_c; }); break; + } + } + } + + for (auto& r : proc_vec) { + r.entry.get().tree_index = (collapsed or r.entry.get().filtered ? index_max : c_index++); + if (not r.children.empty()) { + tree_sort(r.children, sorting, reverse, c_index, (collapsed or r.entry.get().collapsed or r.entry.get().tree_index == (size_t)index_max)); + } + } + } + + void _tree_gen(proc_info& cur_proc, vector& in_procs, vector& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found, const bool no_update, const bool should_filter) { + auto cur_pos = out_procs.size(); + bool filtering = false; + + //? If filtering, include children of matching processes + if (not found and (should_filter or not filter.empty())) { + if (not s_contains(std::to_string(cur_proc.pid), filter) + and not s_contains(cur_proc.name, filter) + and not s_contains(cur_proc.cmd, filter) + and not s_contains(cur_proc.user, filter)) { + filtering = true; + cur_proc.filtered = true; + filter_found++; + } + else { + found = true; + cur_depth = 0; + } + } + else if (cur_proc.filtered) cur_proc.filtered = false; + + cur_proc.depth = cur_depth; + + //? Set tree index position for process if not filtered out or currently in a collapsed sub-tree + out_procs.push_back({ cur_proc, {} }); + if (not collapsed and not filtering) { + cur_proc.tree_index = out_procs.size() - 1; + + //? 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((size_t)0, std::min(cmd_view.find(' '), cmd_view.size())); + cmd_view = cmd_view.substr(std::min(cmd_view.find_last_of('/') + 1, cmd_view.size())); + cur_proc.short_cmd = (string)cmd_view; + } + } + else { + cur_proc.tree_index = in_procs.size(); + } + + //? Recursive iteration over all children + int children = 0; + for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) { + if (collapsed and not filtering) { + cur_proc.filtered = true; + } + children++; + + _tree_gen(p, in_procs, out_procs.back().children, cur_depth + 1, (collapsed or cur_proc.collapsed), filter, found, no_update, should_filter); + + if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) { + //auto& parent = cur_proc; + cur_proc.cpu_p += p.cpu_p; + cur_proc.cpu_c += p.cpu_c; + cur_proc.mem += p.mem; + cur_proc.threads += p.threads; + filter_found++; + p.filtered = true; + } + } + if (collapsed or filtering) { + return; + } + + //? Add tree terminator symbol if it's the last child in a sub-tree + if (children > 0 and out_procs.back().children.back().entry.get().prefix.size() >= 8 and not out_procs.back().children.back().entry.get().prefix.ends_with("]─")) + out_procs.back().children.back().entry.get().prefix.replace(out_procs.back().children.back().entry.get().prefix.size() - 8, 8, " └─ "); + + //? Add collapse/expand symbols if process have any children + out_procs.at(cur_pos).entry.get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); + } + +} \ No newline at end of file diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index caef8bf..f61cdbb 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -131,7 +131,7 @@ namespace Mem { struct disk_info { std::filesystem::path dev; string name; - string fstype; + string fstype = ""; std::filesystem::path stat = ""; int64_t total = 0, used = 0, free = 0; int used_percent = 0, free_percent = 0; @@ -199,7 +199,7 @@ namespace Proc { extern bool shown, redraw; extern int select_max; extern atomic detailed_pid; - extern int selected_pid, start, selected, collapse, expand; + extern int selected_pid, start, selected, collapse, expand, filter_found, selected_depth; extern string selected_name; //? Contains the valid sorting options for processes @@ -268,4 +268,18 @@ namespace Proc { //* Draw contents of proc box using as data source string draw(const vector& plist, const bool force_redraw=false, const bool data_same=false); + + struct tree_proc { + std::reference_wrapper entry; + vector children; + }; + + //* Sort vector of proc_info's + void proc_sorter(vector& proc_vec, string sorting, const bool reverse, const bool tree = false); + + //* Recursive sort of process tree + void tree_sort(vector& proc_vec, const string& sorting, const bool reverse, int& c_index, const int index_max, const bool collapsed = false); + + //* Generate process tree list + void _tree_gen(proc_info& cur_proc, vector& in_procs, vector& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false, const bool no_update=false, const bool should_filter=false); } diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 7c6c07c..9e5be73 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -1007,65 +1007,6 @@ namespace Proc { detail_container detailed; - //* Generate process tree list - void _tree_gen(proc_info &cur_proc, vector &in_procs, vector> &out_procs, int cur_depth, const bool collapsed, const string &filter, bool found = false, const bool no_update = false, const bool should_filter = false) { - auto cur_pos = out_procs.size(); - bool filtering = false; - - //? If filtering, include children of matching processes - if (not found and (should_filter or not filter.empty())) { - if (not s_contains(std::to_string(cur_proc.pid), filter) and not s_contains(cur_proc.name, filter) and not s_contains(cur_proc.cmd, filter) and not s_contains(cur_proc.user, filter)) { - filtering = true; - cur_proc.filtered = true; - filter_found++; - } else { - found = true; - cur_depth = 0; - } - } else if (cur_proc.filtered) - cur_proc.filtered = false; - - //? Set tree index position for process if not filtered out or currently in a collapsed sub-tree - if (not collapsed and not filtering) { - out_procs.push_back(std::ref(cur_proc)); - cur_proc.tree_index = out_procs.size() - 1; - //? 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((size_t)0, min(cmd_view.find(' '), cmd_view.size())); - cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size())); - cur_proc.short_cmd = (string)cmd_view; - } - } else { - cur_proc.tree_index = in_procs.size(); - } - - //? Recursive iteration over all children - int children = 0; - for (auto &p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) { - if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) { - out_procs.back().get().cpu_p += p.cpu_p; - out_procs.back().get().mem += p.mem; - out_procs.back().get().threads += p.threads; - filter_found++; - } - if (collapsed and not filtering) { - cur_proc.filtered = true; - } else - children++; - _tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter); - } - if (collapsed or filtering) - return; - - //? Add tree terminator symbol if it's the last child in a sub-tree - if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─")) - out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ "); - - //? Add collapse/expand symbols if process have any children - out_procs.at(cur_pos).get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); - } - string get_status(char s) { if (s & SRUN) return "Running"; if (s & SSLEEP) return "Sleeping"; @@ -1156,6 +1097,8 @@ namespace Proc { const int cmult = (per_core) ? Shared::coreCount : 1; bool got_detailed = false; + static vector found; + vector> cpu_time(Shared::coreCount); size_t size = sizeof(long) * CPUSTATES * Shared::coreCount; if (sysctlbyname("kern.cp_times", &cpu_time[0], &size, NULL, 0) == -1) { @@ -1175,7 +1118,7 @@ namespace Proc { //* ---------------------------------------------Collection start---------------------------------------------- should_filter = true; - vector found; + found.clear(); struct timeval currentTime; gettimeofday(¤tTime, NULL); const double timeNow = currentTime.tv_sec + (currentTime.tv_usec / 1'000'000); @@ -1206,6 +1149,7 @@ namespace Proc { if (no_cache) { if (kproc->ki_comm == NULL or kproc->ki_comm == "idle"s) { current_procs.pop_back(); + found.pop_back(); continue; } new_proc.name = kproc->ki_comm; @@ -1249,116 +1193,111 @@ namespace Proc { if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) { got_detailed = true; } - - // //? Clear dead processes from current_procs - 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, current_procs); - } else if (show_detailed and not got_detailed and detailed.status != "Dead") { - detailed.status = "Dead"; - redraw = true; - } - - old_cputimes = cputimes; } + + //? Clear dead processes from current_procs + 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, current_procs); + } else if (show_detailed and not got_detailed and detailed.status != "Dead") { + detailed.status = "Dead"; + redraw = true; + } + + old_cputimes = cputimes; + } //* ---------------------------------------------Collection done----------------------------------------------- - //* Sort processes - if (sorted_change or not no_update) { - if (reverse) { - switch (v_index(sort_vector, sorting)) { - case 0: rng::stable_sort(current_procs, rng::less{}, &proc_info::pid); break; - case 1: rng::stable_sort(current_procs, rng::less{}, &proc_info::name); break; - case 2: rng::stable_sort(current_procs, rng::less{}, &proc_info::cmd); break; - case 3: rng::stable_sort(current_procs, rng::less{}, &proc_info::threads); break; - case 4: rng::stable_sort(current_procs, rng::less{}, &proc_info::user); break; - case 5: rng::stable_sort(current_procs, rng::less{}, &proc_info::mem); break; - case 6: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_p); break; - case 7: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_c); break; - } - } else { - switch (v_index(sort_vector, sorting)) { - case 0: rng::stable_sort(current_procs, rng::greater{}, &proc_info::pid); break; - case 1: rng::stable_sort(current_procs, rng::greater{}, &proc_info::name); break; - case 2: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cmd); break; - case 3: rng::stable_sort(current_procs, rng::greater{}, &proc_info::threads); break; - case 4: rng::stable_sort(current_procs, rng::greater{}, &proc_info::user); break; - case 5: rng::stable_sort(current_procs, rng::greater{}, &proc_info::mem); break; - case 6: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_p); break; - case 7: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_c); break; - } - } - - //* 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 < 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 current_procs.at(i).cpu_p > 30.0) - offset++; - 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 if (should_filter) { filter_found = 0; - for (auto &p : current_procs) { + for (auto& p : current_procs) { if (not tree and not filter.empty()) { - if (not s_contains_ic(to_string(p.pid), filter) and not s_contains_ic(p.name, filter) and not s_contains_ic(p.cmd, filter) and not s_contains_ic(p.user, filter)) { - p.filtered = true; - filter_found++; - } else { - p.filtered = false; + if (not s_contains_ic(to_string(p.pid), filter) + and not s_contains_ic(p.name, filter) + and not s_contains_ic(p.cmd, filter) + and not s_contains_ic(p.user, filter)) { + p.filtered = true; + filter_found++; + } + else { + p.filtered = false; + } } - } else { + else { p.filtered = false; } } } + //* Sort processes + if (sorted_change or not no_update) { + proc_sorter(current_procs, sorting, reverse, tree); + } + //* Generate tree view if enabled if (tree and (not no_update or should_filter or sorted_change)) { + bool locate_selection = false; 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; - } else if (collapse > -1) { + } + else if (collapse > -1) { collapser->collapsed = true; - } else if (expand > -1) { + } + else if (expand > -1) { collapser->collapsed = false; } + if (Config::ints.at("proc_selected") > 0) locate_selection = true; } collapse = expand = -1; } if (should_filter or not filter.empty()) filter_found = 0; - vector> tree_procs; + vector tree_procs; tree_procs.reserve(current_procs.size()); + for (auto& p : current_procs) { + if (not v_contains(found, p.ppid)) p.ppid = 0; + } + //? Stable sort to retain selected sorting among processes with the same parent - rng::stable_sort(current_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 (auto &p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { + for (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, false, no_update, should_filter); } + //? Recursive sort over tree structure to account for collapsed processes in the tree + int index = 0; + tree_sort(tree_procs, sorting, reverse, index, current_procs.size()); + + //? Add tree begin symbol to first item if childless + if (tree_procs.front().children.empty()) + tree_procs.front().entry.get().prefix.replace(tree_procs.front().entry.get().prefix.size() - 8, 8, " ┌─ "); + + //? Add tree terminator symbol to last item if childless + if (tree_procs.back().children.empty()) + tree_procs.back().entry.get().prefix.replace(tree_procs.back().entry.get().prefix.size() - 8, 8, " └─ "); + //? Final sort based on tree index - rng::stable_sort(current_procs, rng::less{}, &proc_info::tree_index); + rng::sort(current_procs, rng::less{}, & proc_info::tree_index); + + //? Move current selection/view to the selected process when collapsing/expanding in the tree + if (locate_selection) { + int loc = rng::find(current_procs, Proc::selected_pid, &proc_info::pid)->tree_index; + if (Config::ints.at("proc_start") >= loc or Config::ints.at("proc_start") <= loc - Proc::select_max) + Config::ints.at("proc_start") = max(0, loc - 1); + Config::ints.at("proc_selected") = loc - Config::ints.at("proc_start") + 1; + } } numpids = (int)current_procs.size() - filter_found; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 0c71397..5d63508 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -1307,69 +1307,6 @@ namespace Proc { constexpr size_t KTHREADD = 2; static robin_hood::unordered_set kernels_procs = {KTHREADD}; - //* Generate process tree list - void _tree_gen(proc_info& cur_proc, vector& in_procs, vector>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false, const bool no_update=false, const bool should_filter=false) { - auto cur_pos = out_procs.size(); - bool filtering = false; - - //? If filtering, include children of matching processes - if (not found and (should_filter or not filter.empty())) { - if (not s_contains(std::to_string(cur_proc.pid), filter) - and not s_contains(cur_proc.name, filter) - and not s_contains(cur_proc.cmd, filter) - and not s_contains(cur_proc.user, filter)) { - filtering = true; - cur_proc.filtered = true; - filter_found++; - } - else { - found = true; - cur_depth = 0; - } - } - else if (cur_proc.filtered) cur_proc.filtered = false; - - //? Set tree index position for process if not filtered out or currently in a collapsed sub-tree - if (not collapsed and not filtering) { - out_procs.push_back(std::ref(cur_proc)); - cur_proc.tree_index = out_procs.size() - 1; - //? 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((size_t)0, min(cmd_view.find(' '), cmd_view.size())); - cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size())); - cur_proc.short_cmd = (string)cmd_view; - } - } - else { - cur_proc.tree_index = in_procs.size(); - } - - //? Recursive iteration over all children - int children = 0; - for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) { - if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) { - out_procs.back().get().cpu_p += p.cpu_p; - out_procs.back().get().mem += p.mem; - out_procs.back().get().threads += p.threads; - filter_found++; - } - if (collapsed and not filtering) { - cur_proc.filtered = true; - } - else children++; - _tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter); - } - if (collapsed or filtering) return; - - //? Add tree terminator symbol if it's the last child in a sub-tree - if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─")) - out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ "); - - //? Add collapse/expand symbols if process have any children - out_procs.at(cur_pos).get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); - } - //* Get detailed info for selected process void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { fs::path pid_path = Shared::procPath / std::to_string(pid); @@ -1488,6 +1425,8 @@ namespace Proc { string long_string; string short_str; + static vector found; + const double uptime = system_uptime(); const int cmult = (per_core) ? Shared::coreCount : 1; @@ -1502,10 +1441,11 @@ namespace Proc { //* ---------------------------------------------Collection start---------------------------------------------- else { should_filter = true; + found.clear(); //? First make sure kernel proc cache is cleared. if (should_filter_kernel and ++proc_clear_count >= 256) { - //? Clearing the cache is used in the event of a pid wrap around. + //? Clearing the cache is used in the event of a pid wrap around. //? In that event processes that acquire old kernel pids would also be filtered out so we need to manually clean the cache every now and then. kernels_procs.clear(); kernels_procs.emplace(KTHREADD); @@ -1547,7 +1487,6 @@ namespace Proc { pread.close(); //? Iterate over all pids in /proc - vector found; for (const auto& d: fs::directory_iterator(Shared::procPath)) { if (Runner::stopping) return current_procs; @@ -1557,7 +1496,7 @@ namespace Proc { if (not isdigit(pid_str[0])) continue; const size_t pid = stoul(pid_str); - + if (should_filter_kernel and kernels_procs.contains(pid)) { continue; } @@ -1694,7 +1633,7 @@ namespace Proc { catch (const std::out_of_range&) { continue; } pread.close(); - + if (should_filter_kernel and new_proc.ppid == KTHREADD) { kernels_procs.emplace(new_proc.pid); found.pop_back(); @@ -1743,50 +1682,6 @@ namespace Proc { } //* ---------------------------------------------Collection done----------------------------------------------- - //* Sort processes - if (sorted_change or not no_update) { - if (reverse) { - switch (v_index(sort_vector, sorting)) { - case 0: rng::stable_sort(current_procs, rng::less{}, &proc_info::pid); break; - case 1: rng::stable_sort(current_procs, rng::less{}, &proc_info::name); break; - case 2: rng::stable_sort(current_procs, rng::less{}, &proc_info::cmd); break; - case 3: rng::stable_sort(current_procs, rng::less{}, &proc_info::threads); break; - case 4: rng::stable_sort(current_procs, rng::less{}, &proc_info::user); break; - case 5: rng::stable_sort(current_procs, rng::less{}, &proc_info::mem); break; - case 6: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_p); break; - case 7: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_c); break; - } - } else { - switch (v_index(sort_vector, sorting)) { - case 0: rng::stable_sort(current_procs, rng::greater{}, &proc_info::pid); break; - case 1: rng::stable_sort(current_procs, rng::greater{}, &proc_info::name); break; - case 2: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cmd); break; - case 3: rng::stable_sort(current_procs, rng::greater{}, &proc_info::threads); break; - case 4: rng::stable_sort(current_procs, rng::greater{}, &proc_info::user); break; - case 5: rng::stable_sort(current_procs, rng::greater{}, &proc_info::mem); break; - case 6: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_p); break; - case 7: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_c); break; - } - } - - //* 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 < 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 current_procs.at(i).cpu_p > 30.0) - offset++; - 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 if (should_filter) { filter_found = 0; @@ -1809,8 +1704,14 @@ namespace Proc { } } + //* Sort processes + if (sorted_change or not no_update) { + proc_sorter(current_procs, sorting, reverse, tree); + } + //* Generate tree view if enabled if (tree and (not no_update or should_filter or sorted_change)) { + bool locate_selection = false; 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()) { @@ -1823,24 +1724,49 @@ namespace Proc { else if (expand > -1) { collapser->collapsed = false; } + if (Config::ints.at("proc_selected") > 0) locate_selection = true; } collapse = expand = -1; } if (should_filter or not filter.empty()) filter_found = 0; - vector> tree_procs; + vector tree_procs; tree_procs.reserve(current_procs.size()); + for (auto& p : current_procs) { + if (not v_contains(found, p.ppid)) p.ppid = 0; + } + //? Stable sort to retain selected sorting among processes with the same parent - rng::stable_sort(current_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 (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, false, no_update, should_filter); } + //? Recursive sort over tree structure to account for collapsed processes in the tree + int index = 0; + tree_sort(tree_procs, sorting, reverse, index, current_procs.size()); + + //? Add tree begin symbol to first item if childless + if (tree_procs.front().children.empty()) + tree_procs.front().entry.get().prefix.replace(tree_procs.front().entry.get().prefix.size() - 8, 8, " ┌─ "); + + //? Add tree terminator symbol to last item if childless + if (tree_procs.back().children.empty()) + tree_procs.back().entry.get().prefix.replace(tree_procs.back().entry.get().prefix.size() - 8, 8, " └─ "); + //? Final sort based on tree index - rng::stable_sort(current_procs, rng::less{}, &proc_info::tree_index); + rng::sort(current_procs, rng::less{}, & proc_info::tree_index); + + //? Move current selection/view to the selected process when collapsing/expanding in the tree + if (locate_selection) { + int loc = rng::find(current_procs, Proc::selected_pid, &proc_info::pid)->tree_index; + if (Config::ints.at("proc_start") >= loc or Config::ints.at("proc_start") <= loc - Proc::select_max) + Config::ints.at("proc_start") = max(0, loc - 1); + Config::ints.at("proc_selected") = loc - Config::ints.at("proc_start") + 1; + } } numpids = (int)current_procs.size() - filter_found; diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index c9dbcfe..d7877a1 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -1046,65 +1046,6 @@ namespace Proc { detail_container detailed; - //* Generate process tree list - void _tree_gen(proc_info &cur_proc, vector &in_procs, vector> &out_procs, int cur_depth, const bool collapsed, const string &filter, bool found = false, const bool no_update = false, const bool should_filter = false) { - auto cur_pos = out_procs.size(); - bool filtering = false; - - //? If filtering, include children of matching processes - if (not found and (should_filter or not filter.empty())) { - if (not s_contains(std::to_string(cur_proc.pid), filter) and not s_contains(cur_proc.name, filter) and not s_contains(cur_proc.cmd, filter) and not s_contains(cur_proc.user, filter)) { - filtering = true; - cur_proc.filtered = true; - filter_found++; - } else { - found = true; - cur_depth = 0; - } - } else if (cur_proc.filtered) - cur_proc.filtered = false; - - //? Set tree index position for process if not filtered out or currently in a collapsed sub-tree - if (not collapsed and not filtering) { - out_procs.push_back(std::ref(cur_proc)); - cur_proc.tree_index = out_procs.size() - 1; - //? 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((size_t)0, min(cmd_view.find(' '), cmd_view.size())); - cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size())); - cur_proc.short_cmd = (string)cmd_view; - } - } else { - cur_proc.tree_index = in_procs.size(); - } - - //? Recursive iteration over all children - int children = 0; - for (auto &p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) { - if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) { - out_procs.back().get().cpu_p += p.cpu_p; - out_procs.back().get().mem += p.mem; - out_procs.back().get().threads += p.threads; - filter_found++; - } - if (collapsed and not filtering) { - cur_proc.filtered = true; - } else - children++; - _tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter); - } - if (collapsed or filtering) - return; - - //? Add tree terminator symbol if it's the last child in a sub-tree - if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─")) - out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ "); - - //? Add collapse/expand symbols if process have any children - out_procs.at(cur_pos).get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); - } - string get_status(char s) { if (s & SRUN) return "Running"; if (s & SSLEEP) return "Sleeping"; @@ -1184,6 +1125,8 @@ namespace Proc { const int cmult = (per_core) ? Shared::coreCount : 1; bool got_detailed = false; + static vector found; + //* Use pids from last update if only changing filter, sorting or tree options if (no_update and not current_procs.empty()) { if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, current_procs); @@ -1211,7 +1154,7 @@ namespace Proc { should_filter = true; int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; - vector found; + found.clear(); size_t size = 0; const auto timeNow = time_micros(); @@ -1323,50 +1266,6 @@ namespace Proc { //* ---------------------------------------------Collection done----------------------------------------------- - //* Sort processes - if (sorted_change or not no_update) { - if (reverse) { - switch (v_index(sort_vector, sorting)) { - case 0: rng::stable_sort(current_procs, rng::less{}, &proc_info::pid); break; - case 1: rng::stable_sort(current_procs, rng::less{}, &proc_info::name); break; - case 2: rng::stable_sort(current_procs, rng::less{}, &proc_info::cmd); break; - case 3: rng::stable_sort(current_procs, rng::less{}, &proc_info::threads); break; - case 4: rng::stable_sort(current_procs, rng::less{}, &proc_info::user); break; - case 5: rng::stable_sort(current_procs, rng::less{}, &proc_info::mem); break; - case 6: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_p); break; - case 7: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_c); break; - } - } else { - switch (v_index(sort_vector, sorting)) { - case 0: rng::stable_sort(current_procs, rng::greater{}, &proc_info::pid); break; - case 1: rng::stable_sort(current_procs, rng::greater{}, &proc_info::name); break; - case 2: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cmd); break; - case 3: rng::stable_sort(current_procs, rng::greater{}, &proc_info::threads); break; - case 4: rng::stable_sort(current_procs, rng::greater{}, &proc_info::user); break; - case 5: rng::stable_sort(current_procs, rng::greater{}, &proc_info::mem); break; - case 6: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_p); break; - case 7: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_c); break; - } - } - - //* 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 < 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 current_procs.at(i).cpu_p > 30.0) - offset++; - 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 if (should_filter) { filter_found = 0; @@ -1384,36 +1283,69 @@ namespace Proc { } } + //* Sort processes + if (sorted_change or not no_update) { + proc_sorter(current_procs, sorting, reverse, tree); + } + //* Generate tree view if enabled if (tree and (not no_update or should_filter or sorted_change)) { + bool locate_selection = false; 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; - } else if (collapse > -1) { + } + else if (collapse > -1) { collapser->collapsed = true; - } else if (expand > -1) { + } + else if (expand > -1) { collapser->collapsed = false; } + if (Config::ints.at("proc_selected") > 0) locate_selection = true; } collapse = expand = -1; } if (should_filter or not filter.empty()) filter_found = 0; - vector> tree_procs; + vector tree_procs; tree_procs.reserve(current_procs.size()); + for (auto& p : current_procs) { + if (not v_contains(found, p.ppid)) p.ppid = 0; + } + //? Stable sort to retain selected sorting among processes with the same parent - rng::stable_sort(current_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 (auto &p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { + for (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, false, no_update, should_filter); } + //? Recursive sort over tree structure to account for collapsed processes in the tree + int index = 0; + tree_sort(tree_procs, sorting, reverse, index, current_procs.size()); + + //? Add tree begin symbol to first item if childless + if (tree_procs.front().children.empty()) + tree_procs.front().entry.get().prefix.replace(tree_procs.front().entry.get().prefix.size() - 8, 8, " ┌─ "); + + //? Add tree terminator symbol to last item if childless + if (tree_procs.back().children.empty()) + tree_procs.back().entry.get().prefix.replace(tree_procs.back().entry.get().prefix.size() - 8, 8, " └─ "); + //? Final sort based on tree index - rng::stable_sort(current_procs, rng::less{}, &proc_info::tree_index); + rng::sort(current_procs, rng::less{}, & proc_info::tree_index); + + //? Move current selection/view to the selected process when collapsing/expanding in the tree + if (locate_selection) { + int loc = rng::find(current_procs, Proc::selected_pid, &proc_info::pid)->tree_index; + if (Config::ints.at("proc_start") >= loc or Config::ints.at("proc_start") <= loc - Proc::select_max) + Config::ints.at("proc_start") = max(0, loc - 1); + Config::ints.at("proc_selected") = loc - Config::ints.at("proc_start") + 1; + } } numpids = (int)current_procs.size() - filter_found;