Proc::collect() optimization and started on Proc::_collect_details()

This commit is contained in:
aristocratos 2021-06-30 22:28:12 +02:00
parent 331665dc55
commit 3634633e21
10 changed files with 271 additions and 181 deletions

View file

@ -2,7 +2,21 @@ PREFIX ?= /usr/local
DOCDIR ?= $(PREFIX)/share/btop/doc
#Compiler and Linker
CXX := g++
CXX := g++
#If using g++ try to make sure we are using version 11 or 10
ifeq ($(CXX),g++)
CXX_VERSION = $(shell $(CXX) -dumpversion)
ifneq ($(shell test $(CXX_VERSION) -ge 11; echo $$?),0)
ifneq ($(shell command -v g++-11),)
CXX := g++-11
else ifneq ($(shell test $(CXX_VERSION) -eq 10; echo $$?),0)
ifneq ($(shell command -v g++-10),)
CXX := g++-10
endif
endif
endif
endif
#The Target Binary Program
TARGET := btop
@ -20,9 +34,6 @@ OBJEXT := o
CXXFLAGS := -std=c++20 -pthread -O3 -Wall -Wextra -Wno-stringop-overread -pedantic
INC := -I$(INCDIR) -I$(SRCDIR)
#---------------------------------------------------------------------------------
#DO NOT EDIT BELOW THIS LINE
#---------------------------------------------------------------------------------
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
@ -34,12 +45,12 @@ directories:
@mkdir -p $(TARGETDIR)
@mkdir -p $(BUILDDIR)
#Clean only Objecst
#Clean only Objects
clean:
@rm -rf $(BUILDDIR)
#Full Clean, Objects and Binaries
dist-clean: clean
distclean: clean
@rm -rf $(TARGETDIR)
install:
@ -50,6 +61,14 @@ install:
@cp -pr themes $(DESTDIR)$(PREFIX)/share/btop
@chmod 755 $(DESTDIR)$(PREFIX)/bin/btop
#Set suid bit for btop to root, will make btop run as root regardless of actual user
su-setuid:
@su --session-command "make as-root-setuid" root
as-root-setuid:
@chown root:root $(DESTDIR)$(PREFIX)/bin/btop
@chmod 4755 $(DESTDIR)$(PREFIX)/bin/btop
uninstall:
@rm -rf $(DESTDIR)$(PREFIX)/bin/btop
@rm -rf $(DESTDIR)$(DOCDIR)

View file

@ -17,7 +17,6 @@
* [Themes](#themes)
* [Support and funding](#support-and-funding)
* [Prerequisites](#prerequisites) (Read this if you are having issues!)
* [Dependencies](#dependencies)
* [Screenshots](#screenshots)
* [Installation](#installation)
* [Configurability](#configurability)
@ -67,7 +66,7 @@ Btop++ uses the same theme files as bpytop and bashtop (some color values missin
See [themes](https://github.com/aristocratos/btop/tree/master/themes) folder for available themes.
The `make install` command places the default themes in `[/usr/local]/share/btop/themes`.
The `make install` command places the default themes in `[$PREFIX or /usr/local]/share/btop/themes`.
User created themes should be placed in `$XDG_CONFIG_HOME/btop/themes` or `$HOME/.config/btop/themes`.
Let me know if you want to contribute with new themes.
@ -82,15 +81,16 @@ Any support is greatly appreciated!
## Prerequisites
For correct display, a terminal with support for:
For best experience, a terminal with support for:
* 24-bit truecolor ([See list of terminals with truecolor support](https://gist.github.com/XVilka/8346728))
* 256-color terminals are supported through 24-bit to 256-color conversion when setting "truecolor" to False in the options or with "-lc/--low-color" argument.
(16 color TTY mode now available as well.)
* Wide characters (Are sometimes problematic in web-based terminals)
Also needs a UTF8 locale and a font that covers:
* Unicode Block “Braille Patterns” U+2800 - U+28FF
* Unicode Block “Braille Patterns” U+2800 - U+28FF (Not needed in TTY mode or with graphs set to type: block or tty.)
* Unicode Block “Geometric Shapes” U+25A0 - U+25FF
* Unicode Block "Box Drawing" and "Block Elements" U+2500 - U+259F
@ -108,14 +108,6 @@ If text are misaligned and you are using Konsole or Yakuake, turning off "Bi-Dir
Characters clipping in to each other or text/border misalignments is not bugs caused by bpytop, but most likely a fontconfig or terminal problem where the braille characters making up the graphs aren't rendered correctly.
Look to the creators of the terminal emulator you use to fix these issues if the previous mentioned fixes don't work for you.
## Dependencies
None
### Compiling from source
Needs at least G++ 10, preferably 11 (or higher) to make use of some C++20 functionality.
## Screenshots
Main UI showing details for a selected process.
@ -134,7 +126,14 @@ Options menu.
#### Manual compilation and installation
Requires GCC/G++ 10 or higher!
Needs at least GCC/G++ 10, preferably 11 (or higher) to make use of some C++20 functionality.
>Install dependencies (Ubuntu 21.04 Hirsute)
``` bash
sudo apt install git build-essential gcc-11 g++-11
# On earlier Ubuntu versions that don't have gcc-11 and g++-11 replace with gcc-10 and g++-10
```
>Clone and compile
@ -142,26 +141,42 @@ Requires GCC/G++ 10 or higher!
git clone https://github.com/aristocratos/btop.git
cd btop
make
# use "make -jX" where X is your number of cores for multithreaded compilation
```
>to install
``` bash
sudo make install
# use "make install PREFIX=/target/dir" to set target, default: /usr/local
# only use "sudo" when installing to a NON user owned directory
```
>to make btop always run as root (to enable signal sending to any process and for /proc read permissions on some systems)
``` bash
make su-setuid
```
>to uninstall
``` bash
sudo make uninstall
```
>to remove any compiled files
>to remove any object files
```bash
make clean
```
>to remove all object files, binaries and created directories
```bash
make distclean
```
## Configurability
All options changeable from within UI.

View file

@ -599,7 +599,7 @@ int main(int argc, char **argv){
size_t lc;
string ostring;
uint64_t tsl, timestamp2, rcount = 0;
list<uint64_t> avgtimes = {0};
list<uint64_t> avgtimes;
size_t timer = 2000;
bool filtering = false;
vector<string> greyscale;
@ -657,8 +657,8 @@ int main(int argc, char **argv){
if (rcount > 0) avgtimes.push_front(timestamp);
if (avgtimes.size() > 100) avgtimes.pop_back();
avgtimes.push_front(timestamp);
if (avgtimes.size() > 30) avgtimes.pop_back();
cout << pbox << ostring << Fx::reset << "\n" << endl;
cout << Mv::to(Term::height - 4, 1) << "Processes call took: " << rjust(to_string(timestamp), 5) << " μs. Average: " <<
rjust(to_string(accumulate(avgtimes.begin(), avgtimes.end(), 0) / avgtimes.size()), 5) << " μs of " << avgtimes.size() <<

View file

@ -211,12 +211,14 @@ namespace Config {
{"tty_mode", false},
{"force_tty", false},
{"lowcolor", false},
{"show_detailed", false},
};
unordered_flat_map<string, bool> boolsTmp;
unordered_flat_map<string, int> ints = {
{"update_ms", 2000},
{"proc_update_mult", 2},
{"detailed_pid", 0},
};
unordered_flat_map<string, int> intsTmp;
@ -273,6 +275,7 @@ namespace Config {
}
void unlock(){
if (not locked) return;
atomic_wait_set(writelock);
for (auto& item : stringsTmp){

View file

@ -106,25 +106,25 @@ namespace Draw {
out = Fx::reset + lcolor;
//* Draw horizontal lines
//? Draw horizontal lines
for (size_t hpos : {c.y, c.y + c.height - 1}){
out += Mv::to(hpos, c.x) + Symbols::h_line * (c.width - 1);
}
//* Draw vertical lines and fill if enabled
//? Draw vertical lines and fill if enabled
for (size_t hpos : iota(c.y + 1, c.y + c.height - 1)){
out += Mv::to(hpos, c.x) + Symbols::v_line +
((c.fill) ? string(c.width - 2, ' ') : Mv::r(c.width - 2)) +
Symbols::v_line;
}
//* Draw corners
//? Draw corners
out += Mv::to(c.y, c.x) + Symbols::left_up +
Mv::to(c.y, c.x + c.width - 1) + Symbols::right_up +
Mv::to(c.y + c.height - 1, c.x) + Symbols::left_down +
Mv::to(c.y + c.height - 1, c.x + c.width - 1) + Symbols::right_down;
//* Draw titles if defined
//? Draw titles if defined
if (not c.title.empty()){
out += Mv::to(c.y, c.x + 2) + Symbols::title_left + Fx::b + numbering + Theme::c("title") + c.title +
Fx::ub + lcolor + Symbols::title_right;
@ -199,7 +199,7 @@ namespace Draw {
if (no_zero and horizon == height - 1 and i != -1 and result[ai] == 0) result[ai] = 1;
}
}
//? Generate braille symbol from 5x5 2D vector
//? Generate graph symbol from 5x5 2D vector
graphs[current][horizon] += (height == 1 and result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[(result[0] * 5 + result[1])];
}
if (mult and i > data_offset) last = data_value;

View file

@ -95,6 +95,7 @@ namespace Proc {
namespace {
struct p_cache {
string name, cmd, user;
size_t name_offset;
uint64_t cpu_t = 0, cpu_s = 0;
string prefix = "";
size_t depth = 0;
@ -120,8 +121,10 @@ namespace Proc {
"cpu lazy",
};
detail_container detailed;
//* Generate process tree list
void _tree_gen(const proc_info& cur_proc, const vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth, const bool collapsed, const string& prefix, const string& filter, bool found){
void _tree_gen(const proc_info& cur_proc, const vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false){
auto cur_pos = out_procs.size();
bool filtering = false;
@ -150,14 +153,46 @@ namespace Proc {
out_procs.back().threads += p.threads;
}
else children++;
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cache.at(cur_proc.pid).collapsed), prefix, filter, found);
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cache.at(cur_proc.pid).collapsed), filter, found);
}
if (collapsed or filtering) return;
if (out_procs.size() > cur_pos + 1 and not out_procs.back().prefix.ends_with("] "))
out_procs.back().prefix.replace(out_procs.back().prefix.size() - 8, 8, " └─ ");
out_procs.at(cur_pos).prefix = ""s * cur_depth + (children > 0 ? (cache.at(cur_proc.pid).collapsed ? "[+] " : "[-] ") : prefix);
out_procs.at(cur_pos).prefix = ""s * cur_depth + (children > 0 ? (cache.at(cur_proc.pid).collapsed ? "[+] " : "[-] ") : " ├─ ");
}
//* Get datiled info for selected process
void _collect_details(proc_info p){
fs::path pid_path = Shared::proc_path / std::to_string(p.pid);
detailed.entry = p;
ifstream d_read;
//* Get RSS mem from smaps
if (fs::exists(pid_path / "smaps")) {
pid_path /= "smaps";
d_read.open(pid_path);
if (d_read.good()) {
uint64_t rss = 0;
string val;
try {
while (not d_read.eof()) {
d_read.ignore(SSmax, 'R');
if (d_read.peek() == 's') {
d_read.ignore(SSmax, ':');
getline(d_read, val, 'k');
rss += stoull(val) << 10;
}
}
detailed.entry.mem = rss;
}
catch (std::invalid_argument const&) {}
catch (std::out_of_range const&) {}
}
d_read.close();
}
}
vector<proc_info> current_procs;
@ -166,10 +201,12 @@ namespace Proc {
vector<proc_info>& collect(){
atomic_wait_set(collecting);
auto& sorting = Config::getS("proc_sorting");
auto& reverse = Config::getB("proc_reversed");
auto reverse = Config::getB("proc_reversed");
auto& filter = Config::getS("proc_filter");
auto& per_core = Config::getB("proc_per_core");
auto& tree = Config::getB("proc_tree");
auto per_core = Config::getB("proc_per_core");
auto tree = Config::getB("proc_tree");
auto show_detailed = Config::getB("show_detailed");
size_t detailed_pid = Config::getI("detailed_pid");
ifstream pread;
string long_string;
string short_str;
@ -178,6 +215,7 @@ namespace Proc {
procs.reserve((numpids + 10));
int npids = 0;
int cmult = (per_core) ? Global::coreCount : 1;
bool got_detailed = false;
//* 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) {
@ -218,154 +256,160 @@ namespace Proc {
bool new_cache = false;
string pid_str = d.path().filename();
if (d.is_directory() and isdigit(pid_str[0])) {
npids++;
proc_info new_proc (stoul(pid_str));
if (not isdigit(pid_str[0])) continue;
//* Cache program name, command and username
if (not cache.contains(new_proc.pid)) {
string name, cmd, user;
new_cache = true;
pread.open(d.path() / "comm");
if (pread.good()) {
getline(pread, name);
pread.close();
}
else continue;
npids++;
proc_info new_proc (stoul(pid_str));
pread.open(d.path() / "cmdline");
if (pread.good()) {
string tmpstr;
while(getline(pread, tmpstr, '\0')) cmd += tmpstr + ' ';
pread.close();
if (not cmd.empty()) cmd.pop_back();
}
else continue;
//* Cache program name, command and username
if (not cache.contains(new_proc.pid)) {
string name, cmd, user;
new_cache = true;
pread.open(d.path() / "comm");
if (not pread.good()) continue;
getline(pread, name);
pread.close();
size_t name_offset = rng::count(name, ' ');
pread.open(d.path() / "status");
if (pread.good()) {
string uid;
while (not pread.eof()){
string line;
getline(pread, line, ':');
if (line == "Uid") {
pread.ignore();
getline(pread, uid, '\t');
break;
} else {
pread.ignore(SSmax, '\n');
}
}
pread.close();
user = (uid_user.contains(uid)) ? uid_user.at(uid) : uid;
pread.open(d.path() / "cmdline");
if (not pread.good()) continue;
long_string.clear();
while(getline(pread, long_string, '\0')) cmd += long_string + ' ';
pread.close();
if (not cmd.empty()) cmd.pop_back();
pread.open(d.path() / "status");
if (not pread.good()) continue;
string uid;
string line;
while (not pread.eof()){
getline(pread, line, ':');
if (line == "Uid") {
pread.ignore();
getline(pread, uid, '\t');
break;
} else {
pread.ignore(SSmax, '\n');
}
else continue;
cache[new_proc.pid] = {name, cmd, user};
}
pread.close();
user = (uid_user.contains(uid)) ? uid_user.at(uid) : uid;
//* Match filter if defined
if (not tree and not filter.empty()
and pid_str.find(filter) == string::npos
and cache[new_proc.pid].name.find(filter) == string::npos
and cache[new_proc.pid].cmd.find(filter) == string::npos
and cache[new_proc.pid].user.find(filter) == string::npos) {
if (new_cache) cache.erase(new_proc.pid);
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;
//* Parse /proc/[pid]/stat
pread.open(d.path() / "stat");
if (pread.good()) {
//? Check cached name for whitespace characters and set offset to get correct fields from stat file
size_t offset = rng::count(cache.at(new_proc.pid).name, ' ');
size_t x = 0, next_x = 3;
uint64_t cpu_t = 0;
try {
while (not pread.eof()) {
if (++x > 40 + offset) break;
if (x-offset < next_x) {
pread.ignore(SSmax, ' ');
continue;
}
else
getline(pread, short_str, ' ');
switch (x-offset) {
case 3: { //? Process state
new_proc.state = short_str[0];
break;
}
case 4: { //? Process parent pid
new_proc.ppid = stoull(short_str);
next_x = 14;
break;
}
case 14: { //? Process utime
cpu_t = stoull(short_str);
break;
}
case 15: { //? Process stime
cpu_t += stoull(short_str);
next_x = 19;
break;
}
case 19: { //? Process nice value
new_proc.p_nice = stoull(short_str);
break;
}
case 20: { //? Process number of threads
new_proc.threads = stoull(short_str);
next_x = (new_cache) ? 22 : 40;
break;
}
case 22: { //? Cache cpu seconds
cache[new_proc.pid].cpu_s = stoull(short_str);
next_x = 40;
break;
}
case 40: { //? CPU number last executed on
new_proc.cpu_n = stoull(short_str);
break;
}
}
}
pread.close();
}
catch (...) {
continue;
}
if (x-offset < 22) continue;
//? Process cpu usage since last update
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));
//? Update cache with latest cpu times
cache[new_proc.pid].cpu_t = cpu_t;
}
else continue;
//* Get RSS memory in bytes from /proc/[pid]/statm
pread.open(d.path() / "statm");
if (pread.good()) {
pread.ignore(SSmax, ' ');
getline(pread, short_str, ' ');
pread.close();
new_proc.mem = stoull(short_str) * Shared::page_size;
}
//? Push process to vector
procs.push_back(new_proc);
cache[new_proc.pid] = {name, cmd, user, name_offset};
}
//* Match filter if defined
if (not tree and not filter.empty()
and not (show_detailed and new_proc.pid == detailed_pid)
and pid_str.find(filter) == string::npos
and cache[new_proc.pid].name.find(filter) == string::npos
and cache[new_proc.pid].cmd.find(filter) == string::npos
and cache[new_proc.pid].user.find(filter) == string::npos) {
if (new_cache) cache.erase(new_proc.pid);
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;
//* Parse /proc/[pid]/stat
pread.open(d.path() / "stat");
if (not pread.good()) continue;
//? Check cached value for whitespace characters in name and set offset to get correct fields from stat file
size_t& offset = cache.at(new_proc.pid).name_offset;
short_str.clear();
size_t x = 0, next_x = 3;
uint64_t cpu_t = 0;
try {
for (;;) {
while (++x - offset < next_x) {
pread.ignore(SSmax, ' ');
}
getline(pread, short_str, ' ');
switch (x-offset) {
case 3: { //? Process state
new_proc.state = short_str[0];
continue;
}
case 4: { //? Parent pid
new_proc.ppid = stoull(short_str);
next_x = 14;
continue;
}
case 14: { //? Process utime
cpu_t = stoull(short_str);
continue;
}
case 15: { //? Process stime
cpu_t += stoull(short_str);
next_x = 19;
continue;
}
case 19: { //? Nice value
new_proc.p_nice = stoull(short_str);
continue;
}
case 20: { //? Number of threads
new_proc.threads = stoull(short_str);
next_x = (new_cache) ? 22 : 24;
continue;
}
case 22: { //? Save cpu seconds to cache if missing
cache[new_proc.pid].cpu_s = stoull(short_str);
next_x = 24;
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;
continue;
}
case 40: { //? CPU number last executed on
new_proc.cpu_n = stoull(short_str);
goto stat_loop_done;
}
}
}
}
catch (std::invalid_argument const&) { continue; }
catch (std::out_of_range const&) { continue; }
catch (std::ios_base::failure const&) {}
stat_loop_done:
pread.close();
if (x-offset < 24) continue;
// _parse_smaps(new_proc);
//? Process cpu usage since last update
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));
//? Update cache with latest cpu times
cache[new_proc.pid].cpu_t = cpu_t;
//? Update the details info box for process if active
if (show_detailed and new_proc.pid == detailed_pid) {
_collect_details(new_proc);
got_detailed = true;
}
//? Push process to vector
procs.push_back(new_proc);
}
if (show_detailed and not got_detailed) {
detailed.entry.state = 'X';
}
//* Sort processes
@ -403,10 +447,9 @@ namespace Proc {
//? Stable sort to retain selected sorting among processes with the same parent
rng::stable_sort(procs, rng::less{}, &proc_info::ppid);
string prefix = " ├─ ";
//? Start recursive iteration over processes with the lowest shared parent pids
for (auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
_tree_gen(p, procs, tree_procs, 0, cache.at(p.pid).collapsed, prefix, filter, false);
_tree_gen(p, procs, tree_procs, 0, cache.at(p.pid).collapsed, filter);
}
procs.swap(tree_procs);
}

Binary file not shown.

View file

@ -32,7 +32,7 @@ namespace Global {
}
namespace Tools {
//* Platform specific function for system_uptime
//* Platform specific function for system_uptime (seconds since last restart)
double system_uptime();
}
@ -45,6 +45,8 @@ namespace Proc {
extern size_t numpids;
extern std::atomic<bool> stop;
extern std::atomic<bool> collecting;
//? Contains the valid sorting options for processes
extern vector<string> sort_vector;
//* Container for process information
@ -60,6 +62,14 @@ namespace Proc {
string prefix = "";
};
//* Container for process info box
struct detail_container {
proc_info entry;
string elapsed, parent, status, io_read, io_write;
};
extern detail_container detailed;
extern vector<proc_info> current_procs;
//* Collects and sorts process information from /proc, saves and returns reference to Proc::current_procs;

View file

@ -362,12 +362,12 @@ namespace Tools {
#if (__GNUC__ > 10)
//* Redirects to atomic wait
void atomic_wait(atomic<bool>& atom, bool val){
void atomic_wait(const atomic<bool>& atom, bool val){
atom.wait(val);
}
#else
//* Crude implementation of atomic wait for GCC 10
void atomic_wait(atomic<bool>& atom, bool val){
void atomic_wait(const atomic<bool>& atom, bool val){
while (atom == val) std::this_thread::sleep_for(std::chrono::microseconds(1));
}
#endif

View file

@ -225,7 +225,7 @@ namespace Tools {
string strf_time(string strf);
//* Waits for <atom> to not be <val>
void atomic_wait(std::atomic<bool>& atom, bool val=true);
void atomic_wait(const std::atomic<bool>& atom, bool val=true);
//* Waits for <atom> to not be <val> and then sets it to <val> again
void atomic_wait_set(std::atomic<bool>& atom, bool val=true);