Added proc info box and changed Proc::collect() to reuse old data when changing filters or sorting etc.

This commit is contained in:
aristocratos 2021-07-26 01:06:34 +02:00
parent 9ee9f3232d
commit 3a4f33485a
9 changed files with 424 additions and 165 deletions

View file

@ -72,11 +72,11 @@ namespace Global {
size_t banner_width = 0;
string overlay;
fs::path self_path;
string exit_error_msg;
atomic<bool> thread_exception (false);
fs::path self_path;
bool debuginit = false;
bool debug = false;
bool utf_force = false;
@ -300,7 +300,7 @@ namespace Runner {
output += "No boxes shown!";
}
//? If overlay isn't empty, print output without color and effects and then print overlay over
//? If overlay isn't empty, print output without color and effects and then print overlay on top
cout << Term::sync_start << (overlay.empty() ? output : Theme::c("inactive_fg") + Fx::uncolor(output) + overlay) << Term::sync_end << flush;
//! DEBUG stats -->
@ -357,16 +357,6 @@ int main(int argc, char **argv) {
std::signal(SIGCONT, _signal_handler);
std::signal(SIGWINCH, _signal_handler);
//? Linux init
#if defined(LINUX)
Global::coreCount = sysconf(_SC_NPROCESSORS_ONLN);
if (Global::coreCount < 1) Global::coreCount = 1;
{ std::error_code ec;
Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
}
#endif
//? Setup paths for config, log and user themes
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
@ -388,6 +378,11 @@ int main(int argc, char **argv) {
}
}
//? Try to find global btop theme path relative to binary path
#if defined(LINUX)
{ std::error_code ec;
Global::self_path = fs::read_symlink("/proc/self/exe", ec).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);
if (ec or not fs::is_directory(Theme::theme_dir) or access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
@ -482,15 +477,15 @@ int main(int argc, char **argv) {
//? Calculate sizes of all boxes
Draw::calcSizes();
//? Start first collection and drawing run
Runner::run();
Global::debuginit = false; //! Debug -- remove
//? Switch to alternative terminal buffer, hide cursor, and print out box outlines
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << Term::mouse_on;
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
//? Start first collection and drawing run
Runner::run();
//* ------------------------------------------------ TESTING ------------------------------------------------------

View file

@ -83,6 +83,8 @@ namespace Config {
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
{"proc_info_smaps", "#* Use /proc/[pid]/smaps for memory information in the process info box (slow but more accurate)"},
{"cpu_graph_upper", "#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n"
"#* Select from a list of detected attributes from the options menu."},
@ -188,6 +190,7 @@ namespace Config {
{"proc_gradient", true},
{"proc_per_core", false},
{"proc_mem_bytes", true},
{"proc_info_smaps", false},
{"cpu_invert_lower", true},
{"cpu_single_graph", false},
{"show_uptime", true},
@ -223,6 +226,7 @@ namespace Config {
{"selected_pid", 0},
{"proc_start", 0},
{"proc_selected", 0},
{"proc_last_selected", 0},
};
unordered_flat_map<string, int> intsTmp;

View file

@ -265,9 +265,14 @@ namespace Draw {
for (int i : iota(data_offset, (int)data.size())) {
if (tty_mode and mult and i % 2 != 0) continue;
else if (not tty_mode) current = not current;
if (i == -1) { data_value = 0; last = 0; }
else data_value = data[i];
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
if (i == -1) {
data_value = 0;
last = 0;
}
else {
data_value = data[i];
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
}
//? Vertical iteration over height of graph
for (const int& horizon : iota(0, height)) {
@ -279,7 +284,7 @@ namespace Draw {
if (value >= cur_high)
result[ai++] = 4;
else if (value <= cur_low)
result[ai++] = max(clamp_min, 0);
result[ai++] = clamp_min;
else {
result[ai++] = clamp((int)round((float)(value - cur_low) * 4 / (cur_high - cur_low) + mod), clamp_min, 4);
}
@ -329,7 +334,9 @@ namespace Draw {
graphs[(i % 2 != 0)].push_back((value_width < width) ? ((height == 1) ? Mv::r(1) : ""s) * (width - value_width) : "");
}
if (data.size() == 0) return;
this->_create((data.size() == 1 ? deque{0, data[0]} : data), data_offset);
if (data.size() == 1) data_offset--;
this->_create(data, data_offset);
// this->_create((data.size() == 1 ? deque{0, data[0]} : data), data_offset);
}
string& Graph::operator()(const deque<long long>& data, const bool data_same) {
@ -428,16 +435,24 @@ namespace Proc {
unordered_flat_map<size_t, int> p_counters;
int counter = 0;
Draw::TextEdit filter;
Draw::Graph detailed_cpu_graph;
Draw::Graph detailed_mem_graph;
int user_size, thread_size, prog_size, cmd_size, tree_size;
int dgraph_x, dgraph_width, d_width, d_x, d_y;
string box;
bool selection(const string& cmd_key) {
int selection(const string& cmd_key) {
auto start = Config::getI("proc_start");
auto selected = Config::getI("proc_selected");
auto last_selected = Config::getI("proc_last_selected");
const int select_max = (Config::getB("show_detailed") ? Proc::select_max - 8 : Proc::select_max);
int numpids = Proc::numpids;
if (cmd_key == "up" and selected > 0) {
if (start > 0 and selected == 1) start--;
else selected--;
if (Config::getI("proc_last_selected") > 0) Config::set("proc_last_selected", 0);
}
else if (cmd_key == "mouse_scroll_up" and start > 0) {
start = max(0, start - 3);
@ -447,11 +462,15 @@ namespace Proc {
}
else if (cmd_key == "down") {
if (start < numpids - select_max and selected == select_max) start++;
else if (selected == 0 and last_selected > 0) {
selected = last_selected;
Config::set("proc_last_selected", 0);
}
else selected++;
}
else if (cmd_key == "page_up") {
if (selected > 0 and start == 0) selected = 0;
else start = max(0, start - (height - 3));
else start = max(0, start - select_max);
}
else if (cmd_key == "page_down") {
if (selected > 0 and start >= numpids - select_max) selected = select_max;
@ -465,6 +484,10 @@ namespace Proc {
start = max(0, numpids - select_max);
if (selected > 0) selected = select_max;
}
else if (cmd_key.starts_with("mousey")) {
int mouse_y = std::stoi(cmd_key.substr(6));
start = clamp((int)round((double)mouse_y * (numpids - select_max - 2) / (select_max - 2)), 0, max(0, numpids - select_max));
}
bool changed = false;
if (start != Config::getI("proc_start")) {
@ -475,7 +498,7 @@ namespace Proc {
Config::set("proc_selected", selected);
changed = true;
}
return changed;
return (not changed ? -1 : selected);
}
string draw(const vector<proc_info>& plist, const bool force_redraw, const bool data_same) {
@ -485,23 +508,82 @@ namespace Proc {
auto& proc_colors = Config::getB("proc_colors");
const auto& graph_symbol = (Config::getB("tty_mode") ? "tty" : Config::getS("graph_symbol_proc"));
const auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? "braille_up" : graph_symbol + "_up"))[1];
const auto& mem_bytes = Config::getB("proc_mem_bytes");
start = Config::getI("proc_start");
selected = Config::getI("proc_selected");
uint64_t total_mem = 16328872 << 10;
int y = show_detailed ? Proc::y + 9 : Proc::y;
int height = show_detailed ? Proc::height - 9 : Proc::height;
const int y = show_detailed ? Proc::y + 8 : Proc::y;
const int height = show_detailed ? Proc::height - 8 : Proc::height;
const int select_max = show_detailed ? Proc::select_max - 8 : Proc::select_max;
int numpids = Proc::numpids;
string out;
out.reserve(width * height);
//* Redraw elements not needed to be updated every cycle
if (redraw or force_redraw) {
out = box;
const string title_left = Theme::c("proc_box") + Symbols::title_left;
const string title_right = Theme::c("proc_box") + Symbols::title_right;
const auto& filtering = Config::getB("proc_filtering"); // ? filter(20) : Config::getS("proc_filter"))
const auto filter_text = (filtering) ? filter(max(6, width - 58)) : uresize(Config::getS("proc_filter"), max(6, width - 58));
for (const auto& key : {"T", "K", "S", "enter"})
if (Input::mouse_mappings.contains(key)) Input::mouse_mappings.erase(key);
//? Adapt sizes of text fields
user_size = (width < 75 ? 5 : 10);
thread_size = (width < 75 ? - 1 : 4);
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;
//? Detailed box
if (show_detailed) {
const bool alive = detailed.status != "Dead";
dgraph_x = x;
dgraph_width = max(width / 3, width - 121);
d_width = width - dgraph_width - 1;
d_x = x + dgraph_width + 1;
d_y = Proc::y;
//? Create cpu and mem graphs if process is alive
if (alive) {
detailed_cpu_graph = {dgraph_width - 1, 7, "cpu", detailed.cpu_percent, graph_symbol};
detailed_mem_graph = {d_width / 3, 1, "", detailed.mem_bytes, graph_symbol, false, false, detailed.first_mem};
}
//? Draw structure of details box
const string pid_str = to_string(detailed.entry.pid);
out += Mv::to(y, x) + title_right + Symbols::h_line + title_left + Theme::c("hi_fg") + Fx::b + Symbols::superscript[4]
+ Theme::c("title") + "proc" + Fx::ub + title_right + Symbols::h_line * (width - 10) + title_left
+ Mv::to(d_y, dgraph_x + 2) + title_left + Fx::b + Theme::c("title") + pid_str + Fx::ub + title_left
+ title_right + Fx::b + Theme::c("title") + uresize(detailed.entry.name, dgraph_width - pid_str.size() - 7) + Fx::ub + title_right;
out += Mv::to(d_y, d_x - 1) + Theme::c("proc_box") + Symbols::div_up + Mv::to(y, d_x - 1) + Symbols::div_down + Theme::c("div_line");
for (const int& i : iota(1, 8)) out += Mv::to(d_y + i, d_x - 1) + Symbols::v_line;
const string& t_color = (not alive or selected > 0 ? Theme::c("inactive_fg") : Theme::c("title"));
const string& hi_color = (not alive or selected > 0 ? t_color : Theme::c("hi_fg"));
const string hide = (selected > 0 ? t_color + "hide " : Theme::c("title") + "hide " + Theme::c("hi_fg"));
int mouse_x = d_x + 2;
out += Mv::to(d_y, d_x + 1);
if (width > 55) {
out += title_left + hi_color + Fx::b + 'T' + t_color + "erminate" + Fx::ub + title_right;
if (alive and selected == 0) Input::mouse_mappings["T"] = {d_y, mouse_x, 1, 9};
mouse_x += 11;
}
out += title_left + hi_color + Fx::b + 'K' + t_color + "ill" + Fx::ub + title_right
+ title_left + hi_color + Fx::b + 'S' + t_color + "ignals" + Fx::ub + title_right
+ Mv::to(d_y, d_x + d_width - 10) + title_left + t_color + Fx::b + hide + Symbols::enter + Fx::ub + title_right;
if (alive and selected == 0) {
Input::mouse_mappings["K"] = {d_y, mouse_x, 1, 4};
mouse_x += 6;
Input::mouse_mappings["S"] = {d_y, mouse_x, 1, 7};
}
if (selected == 0) Input::mouse_mappings["enter"] = {d_y, d_x + d_width - 9, 1, 6};
}
//? Filter
const auto& filtering = Config::getB("proc_filtering"); // ? filter(20) : Config::getS("proc_filter"))
const auto filter_text = (filtering) ? filter(max(6, width - 58)) : uresize(Config::getS("proc_filter"), max(6, width - 58));
out += Mv::to(y, x+9) + title_left + (not filter_text.empty() ? Fx::b : "") + Theme::c("hi_fg") + 'f'
+ Theme::c("title") + (not filter_text.empty() ? ' ' + filter_text : "ilter")
+ (not filtering and not filter_text.empty() ? Theme::c("hi_fg") + " del" : "")
@ -523,40 +605,115 @@ namespace Proc {
if (width > 55 + sort_len) {
out += Mv::to(y, sort_pos - 25) + title_left + (Config::getB("proc_per_core") ? Fx::b : "") + Theme::c("title")
+ "per-" + Theme::c("hi_fg") + 'c' + Theme::c("title") + "ore" + Fx::ub + title_right;
Input::mouse_mappings["c"] = {y, sort_pos - 24, 1, 8};
}
if (width > 45 + sort_len) {
out += Mv::to(y, sort_pos - 15) + title_left + (Config::getB("proc_reversed") ? Fx::b : "") + Theme::c("hi_fg")
+ 'r' + Theme::c("title") + "everse" + Fx::ub + title_right;
Input::mouse_mappings["r"] = {y, sort_pos - 14, 1, 7};
}
if (width > 35 + sort_len) {
out += Mv::to(y, sort_pos - 6) + title_left + (Config::getB("proc_tree") ? Fx::b : "") + Theme::c("title") + "tre"
+ Theme::c("hi_fg") + 'e' + Fx::ub + title_right;
Input::mouse_mappings["e"] = {y, sort_pos - 5, 1, 4};
}
out += Mv::to(y, sort_pos) + title_left + Fx::b + Theme::c("hi_fg") + "< " + Theme::c("title") + sorting + Theme::c("hi_fg")
+ " >" + Fx::ub + title_right;
Input::mouse_mappings["left"] = {y, sort_pos + 1, 1, 2};
Input::mouse_mappings["right"] = {y, sort_pos + sort_len + 3, 1, 2};
// out += Mv::to(y, x) + Mv::r(12)
// + trans("Filter: " + filter_text)
// + trans(rjust("Per core: " + (Config::getB("proc_per_core") ? "On "s : "Off"s) + " Sorting: "
// + string(Config::getS("proc_sorting")), width - 23 - ulen(filter_text)));
//? select, info and signal buttons
const string down_button = (selected == select_max and start == numpids - select_max ? Theme::c("inactive_fg") : Theme::c("hi_fg")) + Symbols::down;
const string t_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("title"));
const string hi_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("hi_fg"));
int mouse_x = x + 14;
out += Mv::to(y + height - 1, x + 1) + title_left + Fx::b + hi_color + Symbols::up + Theme::c("title") + " select " + down_button + Fx::ub + title_right
+ title_left + Fx::b + t_color + "info " + hi_color + Symbols::enter + Fx::ub + title_right;
if (selected > 0) Input::mouse_mappings["enter"] = {y + height - 1, mouse_x, 1, 6};
mouse_x += 8;
if (width > 60) {
out += title_left + Fx::b + hi_color + 'T' + t_color + "erminate" + Fx::ub + title_right;
if (selected > 0) Input::mouse_mappings["T"] = {y + height - 1, mouse_x, 1, 9};
mouse_x += 11;
}
if (width > 55) {
out += title_left + Fx::b + hi_color + 'K' + t_color + "ill" + Fx::ub + title_right;
if (selected > 0) Input::mouse_mappings["K"] = {y + height - 1, mouse_x, 1, 4};
mouse_x += 6;
}
out += title_left + Fx::b + hi_color + 'S' + t_color + "ignals" + Fx::ub + title_right;
if (selected > 0) Input::mouse_mappings["S"] = {y + height - 1, mouse_x, 1, 7};
//? Labels for fields in list
if (not proc_tree)
out += Mv::to(y+1, x+1) + Theme::c("title") + Fx::b
+ rjust("Pid:", 8) + " "
+ ljust("Program:", (width < 70 ? width - 45 : 16))
+ (width >= 70 ? ljust("Command:", width - 61) : "")
+ (width >= 77 ? Mv::l(4) + "Threads: " : " Tr: ")
+ ljust("User:", 10) + " " + rjust("MemB", 5)
+ " " + rjust("Cpu%", 10) + Fx::ub;
+ rjust("Pid:", 8) + ' '
+ ljust("Program:", prog_size) + ' '
+ (cmd_size > 0 ? ljust("Command:", cmd_size) : "") + ' ';
else
out += Mv::to(y+1, x+1) + Theme::c("title") + Fx::b + ljust("Tree:", width - 40)
+ "Threads: " + ljust("User:", 10) + " " + rjust("MemB", 5)
+ " " + rjust("Cpu%", 10) + Fx::ub;
out += Mv::to(y+1, x+1) + Theme::c("title") + Fx::b
+ ljust("Tree:", tree_size) + ' ';
out += (thread_size > 0 ? Mv::l(4) + "Threads: " : "")
+ ljust("User:", user_size) + ' '
+ rjust((mem_bytes ? "MemB" : "Mem%"), 5) + ' '
+ rjust("Cpu%", 10) + Fx::ub;
}
//* End of redraw block
//? Draw details box if shown
if (show_detailed) {
const bool alive = detailed.status != "Dead";
const int item_fit = floor((double)(d_width - 2) / 10);
const int item_width = floor((double)(d_width - 2) / min(item_fit, 8));
//? Graph part of box
string cpu_str = (alive ? to_string(detailed.entry.cpu_p) : "");
if (alive) {
cpu_str.resize((detailed.entry.cpu_p < 10 or detailed.entry.cpu_p >= 100 ? 3 : 4));
cpu_str += '%' + Mv::r(1) + (dgraph_width < 20 ? "C" : "Core") + to_string(detailed.entry.cpu_n);
}
out += Mv::to(d_y + 1, dgraph_x + 1) + detailed_cpu_graph(detailed.cpu_percent, (data_same or not alive))
+ Mv::to(d_y + 1, dgraph_x + 1) + Theme::c("title") + Fx::b + cpu_str;
for (int i = 0; const auto& l : {'C', 'P', 'U'})
out += Mv::to(d_y + 3 + i++, dgraph_x + 1) + l;
//? Info part of box
const string mv_down = Mv::l(item_width) + Mv::d(1) + Fx::ub + Theme::c("main_fg");
const string mv_up = Mv::u(1) + Fx::b + Theme::c("title");
const string stat_color = (not alive ? Theme::c("inactive_fg") : (detailed.status == "Running" ? Theme::c("proc_misc") : ""));
out += Mv::to(d_y + 1, d_x + 1)
+ cjust("Status:", item_width) + mv_down + stat_color + cjust(detailed.status, item_width) + mv_up
+ cjust("Elapsed:", item_width) + mv_down + cjust(detailed.elapsed, item_width);
if (item_fit >= 3) out += mv_up + cjust("IO/R:", item_width) + mv_down + cjust(detailed.io_read, item_width);
if (item_fit >= 4) out += mv_up + cjust("IO/W:", item_width) + mv_down + cjust(detailed.io_write, item_width);
if (item_fit >= 5) out += mv_up + cjust("Parent:", item_width) + mv_down + cjust(detailed.parent, item_width);
if (item_fit >= 6) out += mv_up + cjust("User:", item_width) + mv_down + cjust(detailed.entry.user, item_width);
if (item_fit >= 7) out += mv_up + cjust("Nice:", item_width) + mv_down + cjust(to_string(detailed.entry.p_nice), item_width);
if (item_fit >= 8) out += mv_up + cjust("Threads:", item_width) + mv_down + cjust(to_string(detailed.entry.threads), item_width);
const double mem_p = (double)detailed.mem_bytes.back() * 100 / Shared::totalMem;
// const int mem_fuzz = min(detailed.mem * 100 / (Shared::totalMem / 10), 100ul);
string mem_str = to_string(mem_p);
mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4));
out += Mv::to(d_y + 4, d_x + 1) + Theme::c("title") + Fx::b + rjust((item_fit > 4 ? "Memory: " : "M:") + mem_str + "% ", d_width / 3)
+ Theme::c("inactive_fg") + Fx::ub + graph_bg * (d_width / 3) + Mv::l(d_width / 3)
+ Theme::c("proc_misc") + detailed_mem_graph(detailed.mem_bytes, (data_same or not alive)) + ' '
+ Theme::c("title") + Fx::b + detailed.memory;
for (int i = 0; const auto& l : {'C', 'M', 'D'})
out += Mv::to(d_y + 5 + i++, d_x + 1) + l;
out += Theme::c("main_fg") + Fx::ub;
const int cmd_size = ulen(detailed.entry.cmd);
for (int num_lines = min(3, (int)ceil((double)cmd_size / (d_width - 5))), i = 0; i < num_lines; i++) {
out += Mv::to(d_y + 5 + (num_lines == 1 ? 1 : i), d_x + 3)
+ cjust(luresize(detailed.entry.cmd, cmd_size - (d_width - 5) * i), d_width - 5, true);
}
}
//* Check bounds of current selection and view
//? Check bounds of current selection and view
if (start > 0 and numpids <= select_max)
start = 0;
if (start > numpids - select_max)
@ -627,13 +784,13 @@ namespace Proc {
if (not proc_tree) {
out += Mv::to(y+2+lc, x+1)
+ g_color + rjust(to_string(p.pid), 8) + ' '
+ c_color + ljust(p.name, (width < 70 ? width - 46 : 15)) + end
+ (width >= 70 ? g_color + ' ' + ljust(p.cmd, width - 62, true) : "");
+ c_color + ljust(p.name, prog_size) + ' ' + end
+ (cmd_size > 0 ? g_color + ljust(p.cmd, cmd_size, true) + ' ' : "");
}
//? Tree view line
else {
string prefix_pid = p.prefix + to_string(p.pid);
int width_left = width - 38;
int width_left = tree_size;
out += Mv::to(y+2+lc, x+1) + g_color + uresize(prefix_pid, width_left) + ' ';
width_left -= ulen(prefix_pid);
if (width_left > 0) {
@ -649,11 +806,18 @@ namespace Proc {
//? Common end of line
string cpu_str = to_string(p.cpu_p);
if (p.cpu_p < 10 or p.cpu_p >= 100) cpu_str.resize(3);
out += t_color + rjust(to_string(p.threads), 5) + end + ' '
+ g_color + ljust(p.user, 10) + ' '
+ m_color + rjust(floating_humanizer(p.mem, true), 5) + end + ' '
string mem_str = (mem_bytes ? floating_humanizer(p.mem, true) : "");
if (not mem_bytes) {
double mem_p = (double)p.mem * 100 / Shared::totalMem;
mem_str = to_string(mem_p);
mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4));
mem_str += '%';
}
out += (thread_size > 0 ? t_color + rjust(to_string(p.threads), thread_size) + ' ' + end : "" )
+ g_color + ljust(p.user, user_size) + ' '
+ m_color + rjust(mem_str, 5) + end + ' '
+ (is_selected ? "" : Theme::c("inactive_fg")) + graph_bg * 5
+ (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs[p.pid]({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + ' ' + end
+ (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs[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;
}
@ -661,25 +825,26 @@ namespace Proc {
out += Fx::reset;
while (lc++ < height - 4) out += Mv::to(y+lc+2, x+1) + string(width - 2, ' ');
//* Draw scrollbar if needed
//? Draw scrollbar if needed
if (numpids > select_max) {
const int scroll_pos = clamp((int)round((double)start * (select_max - 2) / (numpids - (select_max - 2))), 0, height - 5);
out += Mv::to(y + 1, x + width - 2) + Fx::b + Theme::c("main_fg") + ""
+ Mv::to(y + height - 2, x + width - 2) + ""
out += Mv::to(y + 1, x + width - 2) + Fx::b + Theme::c("main_fg") + Symbols::up
+ Mv::to(y + height - 2, x + width - 2) + Symbols::down
+ Mv::to(y + 2 + scroll_pos, x + width - 2) + "";
}
//* Current selection and number of processes
//? Current selection and number of processes
string location = to_string(start + selected) + '/' + to_string(numpids);
string loc_clear = Symbols::h_line * max(0ul, 9 - location.size());
out += Mv::to(y+height, x+width - 3 - max(9, (int)location.size())) + Theme::c("proc_box") + loc_clear
+ Symbols::title_left + Theme::c("title") + Fx::b + location + Fx::ub + Theme::c("proc_box") + Symbols::title_right;
//* Check if all graphs still have running processes at a regular interval
if (++counter >= 1000) {
//? Clear out left over graphs from dead processes at a regular interval
if (not data_same and ++counter >= 1000) {
counter = 0;
for (auto element = p_graphs.begin(); element != p_graphs.end();) {
if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) {
p_graphs.erase(element);
element = p_graphs.erase(element);
p_counters.erase(element->first);
}
else
@ -720,7 +885,7 @@ namespace Draw {
width = round((double)Term::width * width_p / 100);
height = max(8, (int)round((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p) / 100));
b_columns = max(1, (int)ceil((double)(Global::coreCount + 1) / (height - 5)));
b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5)));
if (b_columns * (21 + 12 * got_sensors) < width - (width / 3)) {
b_column_size = 2;
b_width = (21 + 12 * got_sensors) * b_columns - (b_columns - 1);
@ -738,7 +903,7 @@ namespace Draw {
}
if (b_column_size == 0) b_width = (8 + 6 * got_sensors) * b_columns + 1;
b_height = min(height - 2, (int)ceil((double)Global::coreCount / b_columns) + 4);
b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4);
b_x = width - b_width - 1;
b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1;

View file

@ -24,6 +24,7 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_menu.hpp>
#include <btop_draw.hpp>
#include <signal.h>
using std::cin, std::string_literals::operator""s;
using namespace Tools;
@ -171,12 +172,12 @@ namespace Input {
last.clear();
}
void process(const string key) {
void process(const string& key) {
if (key.empty()) return;
try {
auto& filtering = Config::getB("proc_filtering");
if (not filtering and key == "q") clean_quit(0);
bool recollect = true;
bool recollect = false;
bool redraw = true;
//? Input actions for proc box
@ -196,8 +197,6 @@ namespace Input {
else if (Proc::filter.command(key)) {
if (Config::getS("proc_filter") != Proc::filter.text)
Config::set("proc_filter", Proc::filter.text);
else
recollect = false;
}
else
return;
@ -218,9 +217,8 @@ namespace Input {
Config::flip("proc_filtering");
Proc::filter = { Config::getS("proc_filter") };
old_filter = Proc::filter.text;
recollect = false;
}
else if (key == "t")
else if (key == "e")
Config::flip("proc_tree");
else if (key == "r")
@ -240,26 +238,83 @@ namespace Input {
Runner::run("all", true, true);
}
else if (key.starts_with("mouse_")) {
recollect = redraw = false;
redraw = false;
const auto& [col, line] = mouse_pos;
int y = (Config::getB("show_detailed") ? Proc::y + 9 : Proc::y);
if (col >= Proc::x + 1 and col < Proc::x + Proc::width - 1 and line >= y + 1 and line < y + Proc::height - 1) {
const int y = (Config::getB("show_detailed") ? Proc::y + 8 : Proc::y);
const int height = (Config::getB("show_detailed") ? Proc::height - 8 : Proc::height);
if (col >= Proc::x + 1 and col < Proc::x + Proc::width and line >= y + 1 and line < y + height - 1) {
if (key == "mouse_click") {
if (col < Proc::x + Proc::width - 2) {
const auto& current_selection = Config::getI("proc_selected");
if (current_selection == line - y - 1) {
redraw = true;
goto proc_mouse_enter;
}
else if (current_selection == 0 or line - y - 1 == 0)
redraw = true;
Config::set("proc_selected", line - y - 1);
Runner::run("proc", true, false);
}
else if (line == y + 1) {
if (Proc::selection("page_up") == -1) return;
}
else if (line == y + height - 2) {
if (Proc::selection("page_down") == -1) return;
}
else if (Proc::selection("mousey" + to_string(line - y - 2)) == -1)
return;
}
else
if (not Proc::selection(key)) keep_going = true;
goto proc_mouse_scroll;
}
else if (key == "mouse_click" and Config::getI("proc_selected") > 0) {
Config::set("proc_selected", 0);
keep_going = true;
redraw = true;
}
else
keep_going = true;
}
else if (key == "enter") {
proc_mouse_enter:
if (Config::getI("proc_selected") == 0 and not Config::getB("show_detailed")) {
return;
}
else if (Config::getI("proc_selected") > 0 and Config::getI("detailed_pid") != Config::getI("selected_pid")) {
Config::set("detailed_pid", Config::getI("selected_pid"));
Config::set("proc_last_selected", Config::getI("proc_selected"));
Config::set("proc_selected", 0);
Config::set("show_detailed", true);
recollect = redraw = true;
}
else if (Config::getB("show_detailed")) {
if (Config::getI("proc_last_selected") > 0) Config::set("proc_selected", Config::getI("proc_last_selected"));
Config::set("proc_last_selected", 0);
Config::set("detailed_pid", 0);
Config::set("show_detailed", false);
redraw = true;
}
}
else if (key == "T") {
Logger::debug(key);
return;
}
else if (key == "K") {
Logger::debug(key);
return;
}
else if (key == "S") {
Logger::debug(key);
return;
}
else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) {
proc_mouse_scroll:
recollect = redraw = false;
if (not Proc::selection(key)) keep_going = true;
auto old_selected = Config::getI("proc_selected");
auto new_selected = Proc::selection(key);
if (new_selected == -1)
return;
else if (old_selected != new_selected and (old_selected == 0 or new_selected == 0))
redraw = true;
}
else keep_going = true;

View file

@ -61,6 +61,6 @@ namespace Input {
void clear();
//* Process actions for input <key>
void process(const string key);
void process(const string& key);
}

View file

@ -21,7 +21,6 @@ tab-size = 4
#include <fstream>
#include <ranges>
#include <cmath>
#include <cmath>
#include <unistd.h>
#include <btop_shared.hpp>
@ -29,7 +28,7 @@ tab-size = 4
#include <btop_tools.hpp>
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
std::round, std::string_literals::operator""s;
std::round, std::max, std::min, std::string_literals::operator""s;
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
@ -51,10 +50,16 @@ namespace Shared {
fs::path proc_path;
fs::path passwd_path;
fs::file_time_type passwd_time;
long page_size;
long clk_tck;
uint64_t totalMem;
long pageSize, clkTck, coreCount;
void init() {
coreCount = sysconf(_SC_NPROCESSORS_ONLN);
if (Shared::coreCount < 1) {
Shared::coreCount = 1;
Logger::warning("Could not determine number of cores, defaulting to 1.");
}
proc_path = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : "";
if (proc_path.empty())
throw std::runtime_error("Proc filesystem not found or no permission to read from it!");
@ -63,17 +68,26 @@ namespace Shared {
if (passwd_path.empty())
Logger::warning("Could not read /etc/passwd, will show UID instead of username.");
page_size = sysconf(_SC_PAGE_SIZE);
if (page_size <= 0) {
page_size = 4096;
pageSize = sysconf(_SC_PAGE_SIZE);
if (pageSize <= 0) {
pageSize = 4096;
Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect.");
}
clk_tck = sysconf(_SC_CLK_TCK);
if (clk_tck <= 0) {
clk_tck = 100;
clkTck = sysconf(_SC_CLK_TCK);
if (clkTck <= 0) {
clkTck = 100;
Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect.");
}
ifstream meminfo(Shared::proc_path / "meminfo");
if (meminfo.good()) {
meminfo.ignore(SSmax, ':');
meminfo >> totalMem;
totalMem <<= 10;
}
if (not meminfo.good() or totalMem == 0)
throw std::runtime_error("Could not get total memory size from /proc/meminfo");
}
}
@ -84,8 +98,8 @@ namespace Cpu {
cpu_info current_cpu;
cpu_info& collect(const bool return_last) {
(void)return_last;
cpu_info collect(const bool no_update) {
(void)no_update;
return current_cpu;
}
}
@ -95,8 +109,8 @@ namespace Mem {
mem_info current_mem;
mem_info& collect(const bool return_last) {
(void)return_last;
mem_info collect(const bool no_update) {
(void)no_update;
return current_mem;
}
@ -105,8 +119,8 @@ namespace Mem {
namespace Net {
net_info current_net;
net_info& collect(const bool return_last) {
(void)return_last;
net_info collect(const bool no_update) {
(void)no_update;
return current_net;
}
}
@ -125,13 +139,13 @@ namespace Proc {
vector<proc_info> current_procs;
unordered_flat_map<size_t, p_cache> cache;
unordered_flat_map<string, string> uid_user;
uint64_t cputimes;
int counter = 0;
}
uint64_t old_cputimes = 0;
atomic<int> numpids = 0;
size_t reserve_pids = 500;
bool tree_state = false;
vector<string> sort_vector = {
"pid",
"name",
@ -180,15 +194,14 @@ namespace Proc {
if (not collapsed and not filtering) {
out_procs.push_back(cur_proc);
if (auto& cmdline = cache.at(cur_proc.pid).cmd; not cmdline.empty() and not cmdline.starts_with("(")) {
std::string_view cmd_view = cmdline;
if (std::string_view cmd_view = cur_proc.cmd; not cmd_view.empty()) {
// std::string_view cmd_view = cmdline;
cmd_view = cmd_view.substr(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()));
if (cmd_view == cur_proc.name)
cmdline.clear();
out_procs.back().cmd.clear();
else
cmdline = '(' + (string)cmd_view + ')';
out_procs.back().cmd = cmdline;
out_procs.back().cmd = '(' + (string)cmd_view + ')';
}
}
@ -211,16 +224,13 @@ namespace Proc {
}
//* Get detailed info for selected process
void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs, const bool is_filtered) {
void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs) {
fs::path pid_path = Shared::proc_path / std::to_string(pid);
if (pid != detailed.last_pid) {
detailed = {};
detailed.last_pid = pid;
detailed.cpu_percent.clear();
detailed.parent.clear();
detailed.io_read.clear();
detailed.io_write.clear();
detailed.skip_smaps = false;
detailed.skip_smaps = (not Config::getB("proc_info_smaps"));
}
//? Copy proc_info for process from proc vector
@ -228,11 +238,12 @@ namespace Proc {
detailed.entry = *p;
//? Update cpu percent deque for process cpu graph
detailed.cpu_percent.push_back(round(detailed.entry.cpu_c));
detailed.cpu_percent.push_back(round(detailed.entry.cpu_p));
while (detailed.cpu_percent.size() > (size_t)Term::width) detailed.cpu_percent.pop_front();
//? Process runtime
detailed.elapsed = sec_to_dhms(uptime - (cache.at(pid).cpu_s / Shared::clk_tck));
detailed.elapsed = sec_to_dhms(uptime - (cache.at(pid).cpu_s / Shared::clkTck));
if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3);
//? Get parent process name
if (detailed.parent.empty() and cache.contains(detailed.entry.ppid)) detailed.parent = cache.at(detailed.entry.ppid).name;
@ -260,15 +271,26 @@ namespace Proc {
}
if (rss == detailed.entry.mem >> 10)
detailed.skip_smaps = true;
else
else {
detailed.mem_bytes.push_back(rss << 10);
detailed.memory = floating_humanizer(rss, false, 1);
}
}
catch (const std::invalid_argument&) {}
catch (const std::out_of_range&) {}
}
d_read.close();
}
if (detailed.memory.empty()) detailed.memory = floating_humanizer(detailed.entry.mem, false);
if (detailed.memory.empty()) {
detailed.mem_bytes.push_back(detailed.entry.mem);
detailed.memory = floating_humanizer(detailed.entry.mem);
}
if (detailed.first_mem == -1 or detailed.first_mem < detailed.mem_bytes.back() / 2 or detailed.first_mem > detailed.mem_bytes.back() * 4) {
detailed.first_mem = min((uint64_t)detailed.mem_bytes.back() * 2, Shared::totalMem);
redraw = true;
}
while (detailed.mem_bytes.size() > (size_t)Term::width) detailed.mem_bytes.pop_front();
//? Get bytes read and written from proc/[pid]/io
if (fs::exists(pid_path / "io")) {
@ -296,23 +318,15 @@ namespace Proc {
}
d_read.close();
}
if (is_filtered) procs.erase(p);
}
//* Collects and sorts process information from /proc
vector<proc_info>& collect(const bool return_last) {
if (return_last) return current_procs;
vector<proc_info> collect(const bool no_update) {
const auto& sorting = Config::getS("proc_sorting");
const auto& reverse = Config::getB("proc_reversed");
const auto& filter = Config::getS("proc_filter");
const auto& per_core = Config::getB("proc_per_core");
const auto& tree = Config::getB("proc_tree");
if (tree_state != tree) {
cache.clear();
tree_state = tree;
}
const auto& show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid");
ifstream pread;
@ -322,9 +336,12 @@ namespace Proc {
vector<proc_info> procs;
procs.reserve(reserve_pids + 10);
int npids = 0;
const int cmult = (per_core) ? Global::coreCount : 1;
const int cmult = (per_core) ? Shared::coreCount : 1;
bool got_detailed = false;
bool detailed_filtered = false;
if (no_update and not cache.empty()) {
procs = current_procs;
goto proc_no_update;
}
//? Update uid_user map if /etc/passwd changed since last run
if (not Shared::passwd_path.empty() and fs::last_write_time(Shared::passwd_path) != Shared::passwd_time) {
@ -345,14 +362,14 @@ namespace Proc {
}
//* Get cpu total times from /proc/stat
uint64_t cputimes = 0;
cputimes = 0;
pread.open(Shared::proc_path / "stat");
if (pread.good()) {
pread.ignore(SSmax, ' ');
for (uint64_t times; pread >> times; cputimes += times);
pread.close();
}
else return current_procs;
else throw std::runtime_error("Failure to read /proc/stat");
//* Iterate over all pids in /proc
for (const auto& d: fs::directory_iterator(Shared::proc_path)) {
@ -402,17 +419,6 @@ namespace Proc {
cache[new_proc.pid] = {name, cmd, user, name_offset};
}
//* Match filter if defined
if (not tree and not filter.empty()
and not s_contains(pid_str, filter)
and not s_contains(cache[new_proc.pid].name, filter)
and not s_contains(cache[new_proc.pid].cmd, filter)
and not s_contains(cache[new_proc.pid].user, filter)) {
if (show_detailed and new_proc.pid == detailed_pid)
detailed_filtered = true;
else
continue;
}
new_proc.name = cache[new_proc.pid].name;
new_proc.cmd = cache[new_proc.pid].cmd;
new_proc.user = cache[new_proc.pid].user;
@ -473,11 +479,11 @@ namespace Proc {
continue;
}
case 24: { //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x)
new_proc.mem = stoull(short_str) * Shared::page_size;
next_x = 40;
new_proc.mem = stoull(short_str) * Shared::pageSize;
next_x = 39;
continue;
}
case 40: { //? CPU number last executed on
case 39: { //? CPU number last executed on
new_proc.cpu_n = stoull(short_str);
goto stat_loop_done;
}
@ -497,7 +503,7 @@ namespace Proc {
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / (cputimes - old_cputimes)) / 10.0;
//? Process cumulative cpu usage since process start
new_proc.cpu_c = ((double)cpu_t / Shared::clk_tck) / (uptime - (cache[new_proc.pid].cpu_s / Shared::clk_tck));
new_proc.cpu_c = ((double)cpu_t / Shared::clkTck) / (uptime - (cache[new_proc.pid].cpu_s / Shared::clkTck));
//? Update cache with latest cpu times
cache[new_proc.pid].cpu_t = cpu_t;
@ -511,12 +517,42 @@ namespace Proc {
}
//* Clear dead processes from cache at a regular interval
if (++counter >= 10000 or ((int)cache.size() > npids + 100)) {
counter = 0;
unordered_flat_map<size_t, p_cache> r_cache;
r_cache.reserve(procs.size());
rng::for_each(procs, [&r_cache](const auto &p) {
if (cache.contains(p.pid))
r_cache[p.pid] = cache.at(p.pid);
});
cache = std::move(r_cache);
}
old_cputimes = cputimes;
reserve_pids = npids;
current_procs = procs;
//* Update the details info box for process if active
if (show_detailed and got_detailed) {
_collect_details(detailed_pid, round(uptime), procs, detailed_filtered);
_collect_details(detailed_pid, round(uptime), procs);
}
else if (show_detailed and not got_detailed) {
else if (show_detailed and not got_detailed and detailed.status != "Dead") {
detailed.status = "Dead";
redraw = true;
}
proc_no_update:
//* Match filter if defined
if (not tree and not filter.empty()) {
const auto filtered = rng::remove_if(procs, [&filter](const auto& p) {
return (not s_contains(to_string(p.pid), filter)
and not s_contains(p.name, filter)
and not s_contains(p.cmd, filter)
and not s_contains(p.user, filter));
});
procs.erase(filtered.begin(), filtered.end());
}
//* Sort processes
@ -566,24 +602,9 @@ namespace Proc {
procs = std::move(tree_procs);
}
//* Clear dead processes from cache at a regular interval
if (++counter >= 10000 or ((int)cache.size() > npids + 100)) {
counter = 0;
unordered_flat_map<size_t, p_cache> r_cache;
r_cache.reserve(procs.size());
rng::for_each(procs, [&r_cache](const auto &p) {
if (cache.contains(p.pid))
r_cache[p.pid] = cache.at(p.pid);
});
cache = std::move(r_cache);
}
old_cputimes = cputimes;
numpids = (int)procs.size();
reserve_pids = npids;
current_procs = std::move(procs);
return current_procs;
return procs;
}
}

View file

@ -35,7 +35,6 @@ namespace Global {
extern const string Version;
extern string exit_error_msg;
extern atomic<bool> thread_exception;
extern int coreCount;
extern string banner;
extern atomic<bool> resized;
extern string overlay;
@ -60,6 +59,9 @@ namespace Tools {
namespace Shared {
//* Initialize platform specific needed variables and check for errors
void init();
extern long coreCount, page_size, clk_tck;
extern uint64_t totalMem;
}
@ -75,7 +77,7 @@ namespace Cpu {
};
//* Collect cpu stats and temperatures
cpu_info& collect(const bool return_last=false);
cpu_info collect(const bool no_update=false);
//* Draw contents of cpu box using <cpu> as source
string draw(const cpu_info& cpu, const bool force_redraw=false, const bool data_same=false);
@ -98,7 +100,7 @@ namespace Mem {
};
//* Collect mem & disks stats
mem_info& collect(const bool return_last=false);
mem_info collect(const bool no_update=false);
//* 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);
@ -120,7 +122,7 @@ namespace Net {
};
//* Collect net upload/download stats
net_info& collect(const bool return_last=false);
net_info collect(const bool no_update=false);
//* Draw contents of net box using <net> as source
string draw(const net_info& net, const bool force_redraw=false, const bool data_same=false);
@ -144,7 +146,7 @@ namespace Proc {
//* Container for process information
struct proc_info {
size_t pid;
size_t pid = 0;
string name = "", cmd = "";
size_t threads = 0;
string user = "";
@ -159,18 +161,21 @@ namespace Proc {
struct detail_container {
proc_info entry;
string elapsed, parent, status, io_read, io_write, memory;
long long first_mem = -1;
size_t last_pid = 0;
bool skip_smaps = false;
deque<long long> cpu_percent;
deque<long long> mem_bytes;
};
//? Contains all info for proc detailed box
extern detail_container detailed;
//* Collect and sort process information from /proc
vector<proc_info>& collect(const bool return_last=false);
vector<proc_info> collect(const bool no_update=false);
//* Update current selection and view
bool selection(const string& cmd_key);
//* Update current selection and view, returns -1 if no change otherwise the current selection
int selection(const string& cmd_key);
//* 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);

View file

@ -32,7 +32,7 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_tools.hpp>
using std::string_view, std::array, std::max, std::to_string, std::cin, robin_hood::unordered_flat_map;
using std::string_view, std::array, std::max, std::floor, std::to_string, std::cin, robin_hood::unordered_flat_map;
namespace fs = std::filesystem;
namespace rng = std::ranges;
@ -167,26 +167,37 @@ namespace Tools {
string ljust(string str, const size_t x, const bool utf, const bool limit) {
if (utf) {
if (limit and ulen(str) > x) str = uresize(str, x);
if (limit and ulen(str) > x) return uresize(str, x);
return str + string(max((int)(x - ulen(str)), 0), ' ');
}
else {
if (limit and str.size() > x) str.resize(x);
if (limit and str.size() > x) { str.resize(x); return str; }
return str + string(max((int)(x - str.size()), 0), ' ');
}
}
string rjust(string str, const size_t x, const bool utf, const bool limit) {
if (utf) {
if (limit and ulen(str) > x) str = uresize(str, x);
if (limit and ulen(str) > x) return uresize(str, x);
return string(max((int)(x - ulen(str)), 0), ' ') + str;
}
else {
if (limit and str.size() > x) str.resize(x);
if (limit and str.size() > x) { str.resize(x); return str; };
return string(max((int)(x - str.size()), 0), ' ') + str;
}
}
string cjust(string str, const size_t x, const bool utf, const bool limit) {
if (utf) {
if (limit and ulen(str) > x) return uresize(str, x);
return string(max((int)ceil((double)(x - ulen(str)) / 2), 0), ' ') + str + string(max((int)floor((double)(x - ulen(str)) / 2), 0), ' ');
}
else {
if (limit and str.size() > x) { str.resize(x); return str; }
return string(max((int)ceil((double)(x - str.size()) / 2), 0), ' ') + str + string(max((int)floor((double)(x - str.size()) / 2), 0), ' ');
}
}
string trans(const string& str) {
string_view oldstr = str;
string newstr;

View file

@ -225,6 +225,9 @@ namespace Tools {
//* Right justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
string rjust(string str, const size_t x, const bool utf=false, const bool limit=true);
//* Center justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
string cjust(string str, const size_t x, const bool utf=false, const bool limit=true);
//* Replace whitespaces " " with escape code for move right
string trans(const string& str);