mirror of
https://github.com/aristocratos/btop.git
synced 2024-05-21 04:43:36 +12:00
Added proc info box and changed Proc::collect() to reuse old data when changing filters or sorting etc.
This commit is contained in:
parent
9ee9f3232d
commit
3a4f33485a
27
src/btop.cpp
27
src/btop.cpp
|
@ -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 ------------------------------------------------------
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -61,6 +61,6 @@ namespace Input {
|
|||
void clear();
|
||||
|
||||
//* Process actions for input <key>
|
||||
void process(const string key);
|
||||
void process(const string& key);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue