summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--snap/gui/btop.desktop11
-rw-r--r--snap/gui/icon.svg111
-rw-r--r--snap/snapcraft.yaml10
-rw-r--r--src/btop.cpp11
-rw-r--r--src/btop_config.cpp8
-rw-r--r--src/btop_draw.cpp54
-rw-r--r--src/btop_input.cpp14
-rw-r--r--src/btop_menu.cpp15
-rw-r--r--src/btop_shared.cpp174
-rw-r--r--src/btop_shared.hpp19
-rw-r--r--src/freebsd/btop_collect.cpp197
-rw-r--r--src/linux/btop_collect.cpp349
-rw-r--r--src/osx/btop_collect.cpp152
-rw-r--r--themes/elementarish.theme82
-rw-r--r--themes/kyli0x.theme32
16 files changed, 831 insertions, 417 deletions
diff --git a/README.md b/README.md
index 3520962..6563be3 100644
--- a/README.md
+++ b/README.md
@@ -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(&currentTime, NULL);
const double timeNow = currentTime.tv_