diff options
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | snap/gui/btop.desktop | 11 | ||||
-rw-r--r-- | snap/gui/icon.svg | 111 | ||||
-rw-r--r-- | snap/snapcraft.yaml | 10 | ||||
-rw-r--r-- | src/btop.cpp | 11 | ||||
-rw-r--r-- | src/btop_config.cpp | 8 | ||||
-rw-r--r-- | src/btop_draw.cpp | 54 | ||||
-rw-r--r-- | src/btop_input.cpp | 14 | ||||
-rw-r--r-- | src/btop_menu.cpp | 15 | ||||
-rw-r--r-- | src/btop_shared.cpp | 174 | ||||
-rw-r--r-- | src/btop_shared.hpp | 19 | ||||
-rw-r--r-- | src/freebsd/btop_collect.cpp | 197 | ||||
-rw-r--r-- | src/linux/btop_collect.cpp | 349 | ||||
-rw-r--r-- | src/osx/btop_collect.cpp | 152 | ||||
-rw-r--r-- | themes/elementarish.theme | 82 | ||||
-rw-r--r-- | themes/kyli0x.theme | 32 |
16 files changed, 831 insertions, 417 deletions
@@ -274,6 +274,15 @@ Also needs a UTF8 locale and a font that covers: sudo zypper in btop ``` * For all other versions, see [openSUSE Software: btop](https://software.opensuse.org/package/btop) +* **Fedora** + ```bash + sudo dnf install btop + ``` +* **RHEL/AlmaLinux 8+** + ```bash + sudo dnf install epel-release + sudo dnf install btop + ``` **Binary release on Homebrew (macOS (x86_64 & ARM64) / Linux (x86_64))** diff --git a/snap/gui/btop.desktop b/snap/gui/btop.desktop new file mode 100644 index 0000000..0135a93 --- /dev/null +++ b/snap/gui/btop.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=btop++ +GenericName=System Monitor +Comment=Resource monitor that shows usage and stats for processor, memory, disks, network and processes +Icon=${SNAP}/meta/gui/icon.svg +Exec=btop +Terminal=true +Categories=System;Monitor;ConsoleOnly; +Keywords=system;process;task diff --git a/snap/gui/icon.svg b/snap/gui/icon.svg new file mode 100644 index 0000000..402b842 --- /dev/null +++ b/snap/gui/icon.svg @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="90" + height="90" + version="1.1" + id="svg70" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs26"> + <pattern + xlink:href="#gray-stripes" + id="pattern1888" /> + <pattern + xlink:href="#red-stripes" + id="pattern1870" /> + <pattern + id="red-stripes" + width="341" + height="90" + patternUnits="userSpaceOnUse"> + <rect + width="341" + height="16" + fill="#f00" + id="rect2" /> + <rect + y="16" + width="341" + height="16" + fill="#d70000" + id="rect4" /> + <rect + y="32" + width="341" + height="16" + fill="#af0000" + id="rect6" /> + <rect + y="48" + width="341" + height="16" + fill="#870000" + id="rect8" /> + <rect + y="64" + width="341" + height="16" + fill="#5f0000" + id="rect10" /> + </pattern> + <pattern + id="gray-stripes" + width="341" + height="90" + patternUnits="userSpaceOnUse"> + <rect + width="341" + height="16" + fill="#585858" + id="rect13" /> + <rect + y="16" + width="341" + height="16" + fill="#4e4e4e" + id="rect15" /> + <rect + y="32" + width="341" + height="16" + fill="#444" + id="rect17" /> + <rect + y="48" + width="341" + height="16" + fill="#3a3a3a" + id="rect19" /> + <rect + y="64" + width="341" + height="16" + fill="#303030" + id="rect21" /> + <rect + y="80" + width="341" + height="10" + fill="#262626" + id="rect23" /> + </pattern> + </defs> + <g + id="g2074" + transform="translate(18)"> + <path + d="M 2,7 V 90 H 47 V 74 h 7 V 55 H 47 V 42 h 7 V 23 H 47 V 7 Z M 19,26 H 37 V 39 H 19 Z m 0,32 H 37 V 71 H 19 Z" + id="path28" + style="fill:#080808" /> + <path + d="M 2,7 V 90 H 47 V 74 h 7 V 55 H 47 V 42 h 7 V 23 H 47 V 7 Z m 1,1 h 43 v 16 h 7 v 17 h -7 v 15 h 7 V 73 H 46 V 89 H 3 Z M 18,25 H 38 V 40 H 18 Z m 1,1 V 39 H 37 V 26 Z M 18,57 H 38 V 72 H 18 Z m 1,1 V 71 H 37 V 58 Z M 4,9 V 88 H 45 V 72 h 7 V 57 H 45 V 40 h 7 V 25 H 45 V 9 Z m 1,1 h 39 v 16 h 7 v 13 h -7 v 19 h 7 V 71 H 44 V 87 H 5 Z M 16,23 H 40 V 42 H 16 Z m 1,1 V 41 H 39 V 24 Z M 16,55 H 40 V 74 H 16 Z m 1,1 V 73 H 39 V 56 Z" + id="path42" + style="fill:url(#pattern1888)" /> + <path + d="M 0,0 V 80 H 42 V 64 h 7 V 48 H 42 V 32 h 7 V 16 H 42 V 0 Z M 14,16 H 35 V 32 H 14 Z m 0,32 H 35 V 64 H 14 Z" + id="path56" + style="fill:url(#pattern1870)" /> + </g> +</svg> diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 860b103..b91baf4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -4,7 +4,6 @@ summary: Resource monitor that shows usage and stats description: | Resource monitor that shows usage and stats for processor, memory, disks, network and processes. C++ version and continuation of bashtop and bpytop. - license: Apache-2.0 base: core20 @@ -16,8 +15,6 @@ architectures: - build-on: amd64 - build-on: arm64 - build-on: armhf - - build-on: ppc64el - - build-on: s390x package-repositories: - type: apt @@ -26,6 +23,8 @@ package-repositories: apps: btop: command: usr/local/bin/btop + extensions: + - gnome-3-38 environment: LC_ALL: C.UTF-8 LANG: C.UTF-8 @@ -38,6 +37,11 @@ apps: - network-observe - home - removable-media + - desktop + - desktop-legacy + - x11 + - wayland + - unity7 parts: btop: diff --git a/src/btop.cpp b/src/btop.cpp index b114753..484a0df 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -34,6 +34,8 @@ tab-size = 4 #include <chrono> #ifdef __APPLE__ #include <CoreFoundation/CoreFoundation.h> + #include <mach-o/dyld.h> + #include <limits.h> #endif #include <btop_shared.hpp> @@ -699,10 +701,17 @@ int main(int argc, char **argv) { } } //? Try to find global btop theme path relative to binary path -#if defined(__linux__) +#ifdef __linux__ { std::error_code ec; Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename(); } +#elif __APPLE__ + { + char buf [PATH_MAX]; + uint32_t bufsize = PATH_MAX; + if(!_NSGetExecutablePath(buf, &bufsize)) + Global::self_path = fs::path(buf).remove_filename(); + } #endif if (std::error_code ec; not Global::self_path.empty()) { Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec); diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6ffc483..479101a 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -92,6 +92,8 @@ namespace Config { {"proc_mem_bytes", "#* Show process memory as bytes instead of percent."}, + {"proc_cpu_graphs", "#* Show cpu graph for each process."}, + {"proc_info_smaps", "#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)"}, {"proc_left", "#* Show proc box on left side of screen instead of right."}, @@ -155,6 +157,8 @@ namespace Config { {"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."}, + {"zfs_hide_datasets", "#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)"}, + {"disk_free_priv", "#* Set to true to show available disk space for privileged users."}, {"show_io_stat", "#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view."}, @@ -222,6 +226,7 @@ namespace Config { {"proc_gradient", true}, {"proc_per_core", false}, {"proc_mem_bytes", true}, + {"proc_cpu_graphs", true}, {"proc_info_smaps", false}, {"proc_left", false}, {"proc_filter_kernel", false}, @@ -241,6 +246,7 @@ namespace Config { {"show_disks", true}, {"only_physical", true}, {"use_fstab", true}, + {"zfs_hide_datasets", false}, {"show_io_stat", true}, {"io_mode", false}, {"base_10_sizes", false}, @@ -264,6 +270,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 +479,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..3603406 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -111,7 +111,7 @@ namespace Draw { } for (size_t i = 0; i < line[1].size(); i += 3) { if (line[1][i] == ' ') { - letter = ' '; + letter = Mv::r(1); i -= 2; } else @@ -388,15 +388,22 @@ namespace Draw { } } //? Generate graph symbol from 5x5 2D vector - graphs.at(current).at(horizon) += (height == 1 and result.at(0) + result.at(1) == 0) ? Mv::r(1) : graph_symbol.at((result.at(0) * 5 + result.at(1))); + if (height == 1) { + if (result.at(0) + result.at(1) == 0) graphs.at(current).at(horizon) += Mv::r(1); + else { + if (not color_gradient.empty()) graphs.at(current).at(horizon) += Theme::g(color_gradient).at(clamp(max(last, data_value), 0ll, 100ll)); + graphs.at(current).at(horizon) += graph_symbol.at((result.at(0) * 5 + result.at(1))); + } + } + else graphs.at(current).at(horizon) += graph_symbol.at((result.at(0) * 5 + result.at(1))); } if (mult and i >= 0) last = data_value; } last = data_value; out.clear(); if (height == 1) { - if (not color_gradient.empty()) - out += (last < 1 and not color_gradient.empty() ? Theme::c("inactive_fg") : Theme::g(color_gradient).at(clamp(last, 0ll, 100ll))); + //if (not color_gradient.empty()) + // out += (last < 1 ? Theme::c("inactive_fg") : Theme::g(color_gradient).at(clamp(last, 0ll, 100ll))); out += graphs.at(current).at(0); } else { @@ -443,7 +450,10 @@ namespace Draw { //? Make room for new characters on graph if (not tty_mode) current = not current; for (const int& i : iota(0, height)) { - if (graphs.at(current).at(i).at(1) == '[') graphs.at(current).at(i).erase(0, 4); + if (height == 1 and graphs.at(current).at(i).at(1) == '[') { + if (graphs.at(current).at(i).at(3) == 'C') graphs.at(current).at(i).erase(0, 4); + else graphs.at(current).at(i).erase(0, graphs.at(current).at(i).find_first_of('m') + 4); + } else if (graphs.at(current).at(i).at(0) == ' ') graphs.at(current).at(i).erase(0, 1); else graphs.at(current).at(i).erase(0, 3); } @@ -528,15 +538,15 @@ namespace Cpu { if (b_column_size > 0 or extra_width > 0) { core_graphs.clear(); for (const auto& core_data : cpu.core_percent) { - core_graphs.emplace_back(5 * b_column_size + extra_width, 1, "", core_data, graph_symbol); + core_graphs.emplace_back(5 * b_column_size + extra_width, 1, "cpu", core_data, graph_symbol); } } if (show_temps) { temp_graphs.clear(); - temp_graphs.emplace_back(5, 1, "", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23); if (not hide_cores and b_column_size > 1) { for (const auto& i : iota((size_t)1, cpu.temp.size())) { - temp_graphs.emplace_back(5, 1, "", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23); } } } @@ -626,16 +636,16 @@ namespace Cpu { + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) - + Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)) + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); - else - out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); + + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); + + out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; if (show_temps and not hide_cores) { const auto [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale); const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) - out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color + out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw); out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } @@ -753,7 +763,7 @@ namespace Mem { for (const auto& [name, disk] : mem.disks) { if (disk.io_read.empty()) continue; - io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "", disk.io_activity, graph_symbol}; + io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "available", disk.io_activity, graph_symbol}; if (io_mode) { //? Create one combined graph for IO read/write if enabled @@ -850,7 +860,7 @@ namespace Mem { const string used_percent = to_string(disk.used_percent); out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } - out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(clamp(disk.io_activity.back(), 50ll, 100ll)) + out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (++cy > height - 3) break; if (io_graph_combined) { @@ -1022,7 +1032,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<size_t, Draw::Graph> p_graphs; unordered_flat_map<size_t, bool> p_wide_cmd; @@ -1107,6 +1117,7 @@ namespace Proc { auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto& mem_bytes = Config::getB("proc_mem_bytes"); auto& vim_keys = Config::getB("vim_keys"); + auto& show_graphs = Config::getB("proc_cpu_graphs"); start = Config::getI("proc_start"); selected = Config::getI("proc_selected"); const int y = show_detailed ? Proc::y + 8 : Proc::y; @@ -1134,6 +1145,10 @@ namespace Proc { prog_size = (width > 70 ? 16 : ( width > 55 ? 8 : width - user_size - thread_size - 33)); cmd_size = (width > 55 ? width - prog_size - user_size - thread_size - 33 : -1); tree_size = width - user_size - thread_size - 23; + if (not show_graphs) { + cmd_size += 5; + tree_size += 5; + } //? Detailed box if (show_detailed) { @@ -1284,7 +1299,7 @@ namespace Proc { out += (thread_size > 0 ? Mv::l(4) + "Threads: " : "") + ljust("User:", user_size) + ' ' + rjust((mem_bytes ? "MemB" : "Mem%"), 5) + ' ' - + rjust("Cpu%", 10) + Fx::ub; + + rjust("Cpu%", (show_graphs ? 10 : 5)) + Fx::ub; } //* End of redraw block @@ -1345,11 +1360,12 @@ 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 - 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)) { + const bool has_graph = show_graphs ? p_counters.contains(p.pid) : false; + if (show_graphs and ((p.cpu_p > 0 and not has_graph) or (not data_same and has_graph))) { if (not has_graph) { p_graphs[p.pid] = Draw::Graph{5, 1, "", {}, graph_symbol}; p_counters[p.pid] = 0; @@ -1441,7 +1457,7 @@ namespace Proc { out += (thread_size > 0 ? t_color + rjust(to_string(min(p.threads, (size_t)9999)), thread_size) + ' ' + end : "" ) + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' - + (is_selected ? "" : Theme::c("inactive_fg")) + graph_bg * 5 + + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + c_color + rjust(cpu_str, 4) + " " + end; if (lc++ > height - 5) break; diff --git a/src/btop_input.cpp b/src/btop_input.cpp index d21feec..586dea9 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -249,7 +249,7 @@ namespace Input { Menu::show(Menu::Menus::Main); return; } - else if (is_in(key, "F1", help_key)) { + else if (is_in(key, "F1", "?", help_key)) { Menu::show(Menu::Menus::Help); return; } @@ -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_menu.cpp b/src/btop_menu.cpp index 6dd420c..8037e0f 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -111,7 +111,7 @@ namespace Menu { {"4", "Toggle PROC box."}, {"d", "Toggle disks view in MEM box."}, {"F2, o", "Shows options."}, - {"F1, h", "Shows this window."}, + {"F1, ?, h", "Shows this window."}, {"ctrl + z", "Sleep program and put in background."}, {"q, ctrl + c", "Quits program."}, {"+, -", "Add/Subtract 100ms to/from update timer."}, @@ -469,6 +469,15 @@ namespace Menu { "This also disables only_physical.", "", "True or False."}, + {"zfs_hide_datasets", + "(Linux) Hide ZFS datasets in disks list.", + "", + "Setting this to True will hide all datasets,", + "and only show ZFS pools.", + "", + "(IO stats will be calculated per-pool)", + "", + "True or False."}, {"disk_free_priv", "(Linux) Type of available disk space.", "", @@ -598,6 +607,10 @@ namespace Menu { " ", "Will show percentage of total memory", "if False."}, + {"proc_cpu_graphs", + "Show cpu graph for each process.", + "", + "True or False"}, {"proc_filter_kernel", "(Linux) Filter kernel processes from output.", "", diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp new file mode 100644 index 0000000..606c082 --- /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 <ranges> + +#include <btop_shared.hpp> +#include <btop_tools.hpp> + +using std::string_literals::operator""s; +namespace rng = std::ranges; +using namespace Tools; + + +namespace Proc { + void proc_sorter(vector<proc_info>& proc_vec, const 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<tree_proc>& 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<proc_info>& in_procs, vector<tree_proc>& 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..1eb81b6 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; @@ -160,6 +160,7 @@ namespace Mem { //* Draw contents of mem box using <mem> as source string draw(const mem_info& mem, const bool force_redraw=false, const bool data_same=false); + } namespace Net { @@ -199,7 +200,7 @@ namespace Proc { extern bool shown, redraw; extern int select_max; extern atomic<int> 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 +269,18 @@ namespace Proc { //* Draw contents of proc box using <plist> as data source string draw(const vector<proc_info>& plist, const bool force_redraw=false, const bool data_same=false); + + struct tree_proc { + std::reference_wrapper<proc_info> entry; + vector<tree_proc> children; + }; + + //* Sort vector of proc_info's + void proc_sorter(vector<proc_info>& proc_vec, const string& sorting, const bool reverse, const bool tree = false); + + //* Recursive sort of process tree + void tree_sort(vector<tree_proc>& 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<proc_info>& in_procs, vector<tree_proc>& 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<proc_info> &in_procs, vector<std::reference_wrapper<proc_info>> &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<size_t> found; + vector<array<long, CPUSTATES>> 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<size_t> found; + found.clear(); struct timeval currentTime; gettimeofday(¤tTime, NULL); const double timeNow = currentTime.tv_ |