Added clock, updated makefile and switch to using semaphore to trigger _runner thread

This commit is contained in:
aristocratos 2021-08-22 16:04:01 +02:00
parent 8bd97b4f24
commit 3f27fb24b5
13 changed files with 463 additions and 254 deletions

112
Makefile
View file

@ -1,29 +1,40 @@
#* Btop++ makefile v1.0
BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m████████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗\n \033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗╚══\033[38;5;160m██\033[38;5;239m╔══╝\033[38;5;160m██\033[38;5;239m╔═══\033[38;5;160m██\033[38;5;239m╗\033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗\n \033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║\033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██████\033[38;5;238m╗\033[38;5;124m██████\033[38;5;238m╗\n \033[38;5;88m██\033[38;5;237m╔══\033[38;5;88m██\033[38;5;237m╗ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║\033[38;5;88m██\033[38;5;237m╔═══╝ ╚═\033[38;5;88m██\033[38;5;237m╔═╝╚═\033[38;5;88m██\033[38;5;237m╔═╝\n \033[38;5;52m██████\033[38;5;236m╔╝ \033[38;5;52m██\033[38;5;236m║ ╚\033[38;5;52m██████\033[38;5;236m╔╝\033[38;5;52m██\033[38;5;236m║ ╚═╝ ╚═╝\n \033[38;5;235m╚═════╝ ╚═╝ ╚═════╝ ╚═╝
BTOP_VERSION = $(shell head -n100 src/btop.cpp 2>/dev/null | grep "Version =" | cut -f2 -d"\"" || echo " unknown")
TIMESTAMP = $(shell date +%s)
PREFIX ?= /usr/local
DOCDIR ?= $(PREFIX)/share/btop/doc
#Compiler and Linker
#? Compiler and Linker
CXX ?= g++
CXX_VERSION = $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
#Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10
#? Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10
ifneq ($(CXX),g++-10)
CXX_VERSION = $(shell $(CXX) -dumpfullversion -dumpversion | cut -f1 -d"." || echo 0)
ifneq ($(shell test $(CXX_VERSION) -ge 11; echo $$?),0)
V_MAJOR = $(shell echo $(CXX_VERSION) | cut -f1 -d"." || echo 0)
ifneq ($(shell test $(V_MAJOR) -ge 11; echo $$?),0)
ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0)
override CXX = g++-11
endif
endif
endif
#Only enable fcf-protection if on x86
ARCH = $(shell uname -p)
#? Only enable fcf-protection if on x86_64
ARCH = $(shell uname -p ||true)
ifeq ($(ARCH),x86_64)
ADDFLAGS = -fcf-protection
endif
ifeq ($(ARCH),unknown)
ARCH = $(shell uname -m ||true)
endif
PLATFORM = $(shell uname -s ||true)
#The Target Binary Program
TARGET := btop
#? Use all CPU cores (will only be set if using Make >=4.3)
MAKEFLAGS := --jobs=$(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
#The Directories, Source, Includes, Objects and Binary
#? The Directories, Source, Includes, Objects and Binary
SRCDIR := src
INCDIR := include
BUILDDIR := obj
@ -32,7 +43,7 @@ SRCEXT := cpp
DEPEXT := d
OBJEXT := o
#Flags, Libraries and Includes
#? Flags, Libraries and Includes
REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic -pedantic-errors -Wfatal-errors
OPTFLAGS := -O2 -ftree-loop-vectorize
@ -40,58 +51,97 @@ override LDCXXFLAGS += -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexce
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := -I$(INCDIR) -I$(SRCDIR)
SU_USER := root
SU_GROUP := root
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
#Default Make
all: directories $(TARGET)
#? Default Make
all: msg directories btop
#Make the Directories
msg:
@printf " $(BANNER)\n"
@printf "\033[1;97mCompiler : \033[0m$(CXX) ($(CXX_VERSION))\n"
@printf "\033[1;97mREQFLAGS : \033[0m$(REQFLAGS)\n"
@printf "\033[1;97mWARNFLAGS : \033[0m$(WARNFLAGS)\n"
@printf "\033[1;97mOPTFLAGS : \033[0m$(OPTFLAGS)\n"
@printf "\033[1;97mLDCXXFLAGS : \033[0m$(LDCXXFLAGS)\n"
@printf "\n\033[1;92mBuilding btop++ v$(BTOP_VERSION) for $(PLATFORM) ($(ARCH))\033[0m\n"
help:
@printf "\033[1;97mbtop++ makefile\033[0m\n"
@printf "usage: make [argument]\n\n"
@printf "arguments:\n"
@printf " all Compile btop (default argument)\n"
@printf " clean Remove built objects\n"
@printf " distclean Remove built objects and binaries\n"
@printf " install Install btop++ to \$$PREFIX\n"
@printf " setuid Set installed binary owner/group to \$$SU_USER/\$$SU_OWNER and set SUID bit\n"
@printf " uninstall Uninstall btop++ from \$$PREFIX\n"
#? Make the Directories
directories:
@mkdir -p $(TARGETDIR)
@mkdir -p $(BUILDDIR)
#Clean only Objects
#? Clean only Objects
clean:
@printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n"
@rm -rf $(BUILDDIR)
#Full Clean, Objects and Binaries
#? Clean Objects and Binaries
distclean: clean
@printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n"
@rm -rf $(TARGETDIR)
install:
@printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n"
@mkdir -p $(DESTDIR)$(PREFIX)/bin
@cp -p $(TARGETDIR)/btop $(DESTDIR)$(PREFIX)/bin/btop
@mkdir -p $(DESTDIR)$(DOCDIR)
@cp -p README.md $(DESTDIR)$(DOCDIR)
@cp -pr themes $(DESTDIR)$(PREFIX)/share/btop
@chmod 755 $(DESTDIR)$(PREFIX)/bin/btop
@printf "\033[1;92mInstalling doc to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\n"
@mkdir -p $(DESTDIR)$(PREFIX)/share/btop
@cp -p README.md $(DESTDIR)$(PREFIX)/share/btop
@printf "\033[1;92mInstalling themes to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop/themes\n"
@cp -pr themes $(DESTDIR)$(PREFIX)/share/btop
#Set suid bit for btop to root, will make btop run with admin privileges regardless of actual user
su-setuid:
@su --session-command "chown root:root $(DESTDIR)$(PREFIX)/bin/btop && chmod 4755 $(DESTDIR)$(PREFIX)/bin/btop" root
#? Set suid bit for btop for $SU_USER in SU_GROUP, will make btop run with (root by default) privileges regardless of actual user
setuid:
@printf "\033[1;97mFile: $(DESTDIR)$(PREFIX)/bin/btop\n"
@printf "\033[1;92mSetting owner \033[1;97m$(SU_USER):$(SU_GROUP)\033[0m\n"
@chown $(SU_USER):$(SU_GROUP) $(DESTDIR)$(PREFIX)/bin/btop
@printf "\033[1;92mSetting SUID bit\033[0m\n"
@chmod u+s $(DESTDIR)$(PREFIX)/bin/btop
uninstall:
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\033[0m\n"
@rm -rf $(DESTDIR)$(PREFIX)/bin/btop
@rm -rf $(DESTDIR)$(DOCDIR)
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\033[0m\n"
@rm -rf $(DESTDIR)$(PREFIX)/share/btop
#Pull in dependency info for *existing* .o files
#? Pull in dependency info for *existing* .o files
-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))
#Link
#? Link
btop: $(OBJECTS)
$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS)
@sleep 0.1 2>/dev/null || true
@printf "\n\033[1;92mLinking and optimizing binary\033[0m\n"
@$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS)
@printf "\033[1;97m./$(TARGETDIR)/btop ($$(du -ah $(TARGETDIR)/btop | cut -f1)iB)\033[0m\n"
@printf "\n\033[1;92mBuild complete in (\033[1;97m$$(date -d @$$(expr $$(date +%s) - $(TIMESTAMP)) -u +%Mm:%Ss)\033[1;92m)\033[0m\n"
#Compile
#? Compile
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $<
@$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT)
@sleep 0.1 2>/dev/null || true
@printf "\033[1;97mCompiling $< \n"
@$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $<
@$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) >/dev/null
@cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp
@sed -e 's|.*:|$(BUILDDIR)/$*.$(OBJEXT):|' < $(BUILDDIR)/$*.$(DEPEXT).tmp > $(BUILDDIR)/$*.$(DEPEXT)
@sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT)
@rm -f $(BUILDDIR)/$*.$(DEPEXT).tmp
#Non-File Targets
.PHONY: all
#? Non-File Targets
.PHONY: all msg help

View file

@ -84,8 +84,8 @@ Any support is greatly appreciated!
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.)
* 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" arguments.
* 16 color TTY mode will be activated if a real tty device is detected. Can be forced with "-t/--tty_on" arguments.
* Wide characters (Are sometimes problematic in web-based terminals)
Also needs a UTF8 locale and a font that covers:
@ -105,7 +105,8 @@ See comments by @sgleizes [link](https://github.com/aristocratos/bpytop/issues/1
If text are misaligned and you are using Konsole or Yakuake, turning off "Bi-Directional text rendering" is a possible fix.
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.
Characters clipping in to each other or text/border misalignments is not bugs caused by btop, 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.
## Screenshots
@ -152,11 +153,12 @@ sudo make install
# only use "sudo" when installing to a NON user owned directory
```
>to make btop always run as root (no need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems)
>to make btop always run as root (or other user), (no need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems)
``` bash
# run after make install and use same PREFIX if any was used at install
make su-setuid
sudo make setuid
# set SU_USER and SU_GROUP to select user and group, default is root:root
```
@ -166,13 +168,13 @@ make su-setuid
sudo make uninstall
```
>to remove any object files
>to remove any object files from source dir
```bash
make clean
```
>to remove all object files, binaries and created directories
>to remove all object files, binaries and created directories in source dir
```bash
make distclean

View file

@ -27,6 +27,8 @@ tab-size = 4
#include <cmath>
#include <iostream>
#include <exception>
#include <tuple>
#include <regex>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
@ -53,8 +55,8 @@ tab-size = 4
#error Platform not supported!
#endif
using std::string, std::string_view, std::vector, std::array, std::atomic, std::endl, std::cout, std::min;
using std::flush, std::endl, std::string_literals::operator""s, std::to_string, std::future, std::async, std::bitset, std::future_status;
using std::string, std::string_view, std::vector, std::atomic, std::endl, std::cout, std::min, std::flush, std::endl;
using std::string_literals::operator""s, std::to_string, std::future, std::async, std::bitset, std::future_status;
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
@ -108,8 +110,9 @@ void argumentParser(const int& argc, char **argv) {
<< " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n"
<< " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
<< " +t, --tty_off force (OFF) tty mode\n"
<< " --utf-foce force start even if no UTF-8 locale was detected"
<< " --debug start in debug mode with loglevel set to DEBUG\n"
<< " --utf-foce force start even if no UTF-8 locale was detected\n"
<< " --debug start in DEBUG mode: shows microsecond timer for information collect\n"
<< " and screen draw functions and sets loglevel to DEBUG\n"
<< endl;
exit(0);
}
@ -270,12 +273,89 @@ void banner_gen() {
+ Fx::i + "v" + Global::Version + Fx::ui;
}
bool update_clock() {
const auto& clock_format = Config::getS("clock_format");
if (not Cpu::shown or clock_format.empty()) return false;
static const unordered_flat_map<string, string> clock_custom_format = {
{"/user", Tools::username()},
{"/host", Tools::hostname()},
{"/uptime", ""}
};
static time_t c_time = 0;
static size_t clock_len = 0;
if (auto n_time = time(NULL); n_time == c_time)
return false;
else
c_time = n_time;
auto& out = Global::clock;
const auto& cpu_bottom = Config::getB("cpu_bottom");
const auto& x = Cpu::x;
const auto y = (cpu_bottom ? Cpu::y + Cpu::height - 1 : Cpu::y);
const auto& width = Cpu::width;
const auto& title_left = (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
const auto& title_right = (cpu_bottom ? Symbols::title_right_down : Symbols::title_right);
string new_clock = clock_format;
for (const auto& [c_format, replacement] : clock_custom_format) {
if (s_contains(new_clock, c_format)) {
if (c_format == "/uptime") {
string upstr = sec_to_dhms(system_uptime());
if (upstr.size() > 8) upstr.resize(upstr.size() - 3);
new_clock = s_replace(new_clock, c_format, upstr);
}
else {
new_clock = s_replace(new_clock, c_format, replacement);
}
}
}
new_clock = uresize(Tools::strf_time(new_clock), std::max(0, width - 56));
out.clear();
if (new_clock.size() != clock_len) {
out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len;
clock_len = new_clock.size();
}
out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left
+ Theme::c("title") + Fx::b + new_clock + Theme::c("cpu_box") + Fx::ub + title_right;
return true;
}
//* Manages secondary thread for collection and drawing of boxes
namespace Runner {
atomic<bool> active (false);
atomic<bool> stopping (false);
atomic<bool> waiting (false);
atomic<bool> do_work (false);
//* Setup semaphore for triggering thread to do work
#if __GNUC__ < 11
#include <semaphore.h>
sem_t do_work;
inline void thread_sem_init() { sem_init(&do_work, 0, 0); }
inline void thread_wait() { sem_wait(&do_work); }
inline void thread_trigger() { sem_post(&do_work); }
#else
#include <semaphore>
std::binary_semaphore do_work(0);
inline void thread_sem_init() { ; }
inline void thread_wait() { do_work.acquire(); }
inline void thread_trigger() { do_work.release(); }
#endif
//* RAII wrapper for pthread_mutex locking
class thread_lock {
pthread_mutex_t& pt_mutex;
public:
int status;
thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { status = pthread_mutex_lock(&pt_mutex); }
~thread_lock() { if (status == 0) pthread_mutex_unlock(&pt_mutex); }
};
string output;
sigset_t mask;
@ -296,6 +376,17 @@ namespace Runner {
cpu_present, cpu_running
};
enum debug_actions {
collect_begin,
draw_begin,
draw_done
};
enum debug_array {
collect,
draw
};
const uint_fast8_t proc_done = 0b0000'0011;
const uint_fast8_t net_done = 0b0000'1100;
const uint_fast8_t mem_done = 0b0011'0000;
@ -314,13 +405,35 @@ namespace Runner {
struct runner_conf current_conf;
void debug_timer(const char* name, const int action) {
switch (action) {
case collect_begin:
debug_times[name].at(collect) = time_micros();
return;
case draw_begin:
debug_times[name].at(draw) = time_micros();
debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect);
debug_times["total"].at(collect) += debug_times[name].at(collect);
return;
case draw_done:
debug_times[name].at(draw) = time_micros() - debug_times[name].at(draw);
debug_times["total"].at(draw) += debug_times[name].at(draw);
return;
}
}
//? ------------------------------- Secondary thread: async launcher and drawing ----------------------------------
void * _runner(void * _) {
(void)_;
//? Block all signals in this thread to avoid deadlock from any signal handlers trying to stop this thread
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTSTP);
sigaddset(&mask, SIGWINCH);
sigaddset(&mask, SIGTERM);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
//? pthread_mutex_lock to make sure this thread is a single instance thread
//? pthread_mutex_lock to lock thread and monitor health from main thread
thread_lock pt_lck(mtx);
if (pt_lck.status != 0) {
Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status);
@ -329,18 +442,18 @@ namespace Runner {
stopping = true;
}
//* ----------------------------------------------- THREAD LOOP -----------------------------------------------
while (not Global::quitting) {
atomic_wait(do_work, false);
do_work = false;
thread_wait();
if (stopping or Global::resized) {
continue;
}
auto& conf = current_conf;
//? Secondary atomic lock used for signaling status to main thread
//? Atomic lock used for blocking non-thread safe actions in main thread
atomic_lock lck(active);
auto& conf = current_conf;
//! DEBUG stats
if (Global::debug) {
debug_times.clear();
@ -356,12 +469,17 @@ namespace Runner {
future<Mem::mem_info&> mem;
future<Net::net_info&> net;
future<vector<Proc::proc_info>&> proc;
//? Loop until all box flags present in bitmask have been zeroed
while (conf.box_mask.count() > 0) {
if (stopping) break;
//? PROC
if (conf.box_mask.test(proc_present)) {
if (not conf.box_mask.test(proc_running)) {
if (Global::debug) debug_times["proc"].at(0) = time_micros();
if (Global::debug) debug_timer("proc", collect_begin);
//? Start async collect
proc = async(Proc::collect, conf.no_update);
conf.box_mask.set(proc_running);
}
@ -370,16 +488,12 @@ namespace Runner {
else if (proc.wait_for(std::chrono::microseconds(10)) == future_status::ready) {
try {
if (Global::debug) {
debug_times["proc"].at(1) = time_micros();
debug_times["proc"].at(0) = debug_times["proc"].at(1) - debug_times["proc"].at(0);
debug_times["total"].at(0) += debug_times["proc"].at(0);
}
if (Global::debug) debug_timer("proc", draw_begin);
//? Draw box
output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["proc"].at(1) = time_micros() - debug_times["proc"].at(1);
debug_times["total"].at(1) += debug_times["proc"].at(1);
}
if (Global::debug) debug_timer("proc", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Proc:: -> " + (string)e.what());
@ -387,10 +501,13 @@ namespace Runner {
conf.box_mask ^= proc_done;
}
}
//? NET
if (conf.box_mask.test(net_present)) {
if (not conf.box_mask.test(net_running)) {
if (Global::debug) debug_times["net"].at(0) = time_micros();
if (Global::debug) debug_timer("net", collect_begin);
//? Start async collect
net = async(Net::collect, conf.no_update);
conf.box_mask.set(net_running);
}
@ -399,16 +516,12 @@ namespace Runner {
else if (net.wait_for(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["net"].at(1) = time_micros();
debug_times["net"].at(0) = debug_times["net"].at(1) - debug_times["net"].at(0);
debug_times["total"].at(0) += debug_times["net"].at(0);
}
if (Global::debug) debug_timer("net", draw_begin);
//? Draw box
output += Net::draw(net.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["net"].at(1) = time_micros() - debug_times["net"].at(1);
debug_times["total"].at(1) += debug_times["net"].at(1);
}
if (Global::debug) debug_timer("net", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Net:: -> " + (string)e.what());
@ -416,10 +529,13 @@ namespace Runner {
conf.box_mask ^= net_done;
}
}
//? MEM
if (conf.box_mask.test(mem_present)) {
if (not conf.box_mask.test(mem_running)) {
if (Global::debug) debug_times["mem"].at(0) = time_micros();
if (Global::debug) debug_timer("mem", collect_begin);
//? Start async collect
mem = async(Mem::collect, conf.no_update);
conf.box_mask.set(mem_running);
}
@ -428,16 +544,12 @@ namespace Runner {
else if (mem.wait_for(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["mem"].at(1) = time_micros();
debug_times["mem"].at(0) = debug_times["mem"].at(1) - debug_times["mem"].at(0);
debug_times["total"].at(0) += debug_times["mem"].at(0);
}
if (Global::debug) debug_timer("mem", draw_begin);
//? Draw box
output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["mem"].at(1) = time_micros() - debug_times["mem"].at(1);
debug_times["total"].at(1) += debug_times["mem"].at(1);
}
if (Global::debug) debug_timer("mem", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Mem:: -> " + (string)e.what());
@ -445,10 +557,13 @@ namespace Runner {
conf.box_mask ^= mem_done;
}
}
//? CPU
if (conf.box_mask.test(cpu_present)) {
if (not conf.box_mask.test(cpu_running)) {
if (Global::debug) debug_times["cpu"].at(0) = time_micros();
if (Global::debug) debug_timer("cpu", collect_begin);
//? Start async collect
cpu = async(Cpu::collect, conf.no_update);
conf.box_mask.set(cpu_running);
}
@ -457,16 +572,12 @@ namespace Runner {
else if (cpu.wait_for(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["cpu"].at(1) = time_micros();
debug_times["cpu"].at(0) = debug_times["cpu"].at(1) - debug_times["cpu"].at(0);
debug_times["total"].at(0) += debug_times["cpu"].at(0);
}
if (Global::debug) debug_timer("cpu", draw_begin);
//? Draw box
output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["cpu"].at(1) = time_micros() - debug_times["cpu"].at(1);
debug_times["total"].at(1) += debug_times["cpu"].at(1);
}
if (Global::debug) debug_timer("cpu", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Cpu:: -> " + (string)e.what());
@ -501,9 +612,10 @@ namespace Runner {
//? If overlay isn't empty, print output without color and then print overlay on top
cout << Term::sync_start << (conf.overlay.empty()
? output + conf.clock
: Theme::c("inactive_fg") + Fx::ub + Fx::uncolor(output + conf.clock) + conf.overlay)
: Fx::ub + Theme::c("inactive_fg") + Fx::uncolor(output + conf.clock) + conf.overlay)
<< Term::sync_end << flush;
}
//* ----------------------------------------------- THREAD LOOP -----------------------------------------------
pthread_exit(NULL);
}
@ -516,20 +628,19 @@ namespace Runner {
if (stopping or Global::resized) return;
if (box == "overlay") {
cout << Term::sync_start << Global::overlay << Term::sync_end;
cout << Term::sync_start << Global::overlay << Term::sync_end << flush;
}
else if (box == "clock") {
if (not Global::clock.empty())
cout << Term::sync_start << Global::clock << Term::sync_end;
cout << Term::sync_start << Global::clock << Term::sync_end << flush;
}
else if (box.empty() and Config::current_boxes.empty()) {
cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end;
cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end << flush;
}
else {
Config::unlock();
Config::lock();
//? Setup bitmask for selected boxes instead of parsing strings
//? Setup bitmask for selected boxes instead of parsing strings in _runner thread loop
bitset<8> box_mask;
for (const auto& box : (box == "all" ? Config::current_boxes : vector{box})) {
box_mask |= box_bits.at(box);
@ -537,9 +648,9 @@ namespace Runner {
current_conf = {box_mask, no_update, force_redraw, Global::overlay, Global::clock};
do_work = true;
atomic_notify(do_work);
thread_trigger();
//? Wait for _runner thread to be active before returning
for (int i = 0; not active and i < 10; i++) sleep_ms(1);
}
}
@ -554,14 +665,9 @@ namespace Runner {
exit(1);
}
else if (ret == EBUSY) {
if (not active) {
do_work = true;
atomic_notify(do_work);
sleep_ms(1);
}
else {
atomic_wait(active);
}
atomic_wait(active);
thread_trigger();
sleep_ms(1);
}
stopping = false;
}
@ -585,11 +691,6 @@ int main(int argc, char **argv) {
std::signal(SIGTSTP, _signal_handler);
std::signal(SIGCONT, _signal_handler);
std::signal(SIGWINCH, _signal_handler);
sigemptyset(&Runner::mask);
sigaddset(&Runner::mask, SIGINT);
sigaddset(&Runner::mask, SIGTSTP);
sigaddset(&Runner::mask, SIGWINCH);
sigaddset(&Runner::mask, SIGTERM);
//? Setup paths for config, log and user themes
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
@ -707,6 +808,7 @@ int main(int argc, char **argv) {
Theme::setTheme();
//? Start runner thread
Runner::thread_sem_init();
if (pthread_create(&Runner::runner_id, NULL, &Runner::_runner, NULL) != 0) {
Global::exit_error_msg = "Failed to create _runner thread!";
exit(1);
@ -743,10 +845,16 @@ int main(int argc, char **argv) {
if (Global::resized) {
Global::resized = false;
Draw::calcSizes();
update_clock();
Runner::run("all", true);
atomic_wait(Runner::active);
}
//? Update clock if needed
if (update_clock()) {
Runner::run("clock");
}
//? Start secondary collect & draw thread at the interval set by <update_ms> config value
if (time_ms() >= future_time) {
Runner::run("all");
@ -767,14 +875,12 @@ int main(int argc, char **argv) {
//? Poll for input and process any input detected
else if (Input::poll(min(1000ul, future_time - current_time))) {
if (not Runner::active)
Config::unlock();
if (not Runner::active) Config::unlock();
Input::process(Input::get());
}
//? Break the loop at 1000ms intervals or if input polling was interrupted
else
break;
else break;
}
}

View file

@ -47,6 +47,8 @@ namespace Config {
{"force_tty", "#* Set to true to force tty mode regardless if a real tty has been detected or not.\n"
"#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."},
{"rounded_corners", "#* Rounded corners on boxes, is ignored if TTY mode is ON."},
{"graph_symbol", "#* Default symbols to use for graph creation, \"braille\", \"block\" or \"tty\".\n"
"#* \"braille\" offers the highest resolution but might not be included in all fonts.\n"
"#* \"block\" has half the resolution of braille but uses more common characters.\n"
@ -112,7 +114,7 @@ namespace Config {
{"show_cpu_freq", "#* Show CPU frequency."},
{"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
{"clock_format", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
{"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
@ -174,7 +176,7 @@ namespace Config {
{"cpu_sensor", "Auto"},
{"cpu_core_map", ""},
{"temp_scale", "celsius"},
{"draw_clock", "%X"},
{"clock_format", "%X"},
{"custom_cpu_name", ""},
{"disks_filter", ""},
{"io_graph_speeds", ""},
@ -188,6 +190,7 @@ namespace Config {
unordered_flat_map<string, bool> bools = {
{"theme_background", true},
{"truecolor", true},
{"rounded_corners", true},
{"proc_reversed", false},
{"proc_tree", false},
{"proc_colors", true},
@ -362,6 +365,10 @@ namespace Config {
cread >> value;
if (not isint(value))
load_warnings.push_back("Got an invalid integer value for config name: " + name);
else if (name == "update_ms" and stoi(value) < 100) {
load_warnings.push_back("Config value update_ms set too low (<100), setting (100).");
ints.at(name) = 100;
}
else
ints.at(name) = stoi(value);
}
@ -376,6 +383,8 @@ namespace Config {
load_warnings.push_back("Invalid log_level: " + value);
else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value))
load_warnings.push_back("Invalid graph symbol identifier: " + value);
else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value)))
load_warnings.push_back("Invalid graph symbol identifier for" + name + ": " + value);
else if (name == "shown_boxes" and not value.empty() and not check_boxes(value))
load_warnings.push_back("Invalid box name(s) in shown_boxes: " + value);
else

View file

@ -36,33 +36,6 @@ using namespace Tools;
namespace rng = std::ranges;
namespace Symbols {
const string h_line = "";
const string v_line = "";
const string dotted_v_line = "";
const string left_up = "";
const string right_up = "";
const string left_down = "";
const string right_down = "";
const string round_left_up = "";
const string round_right_up = "";
const string round_left_down = "";
const string round_right_down = "";
const string title_left_down = "";
const string title_right_down = "";
const string title_left = "";
const string title_right = "";
const string div_right = "";
const string div_left = "";
const string div_up = "";
const string div_down = "";
const string up = "";
const string down = "";
const string left = "";
const string right = "";
const string enter = "";
const string meter = "";
const array<string, 10> superscript = { "", "¹", "²", "³", "", "", "", "", "", "" };
@ -201,11 +174,12 @@ namespace Draw {
string out;
if (line_color.empty()) line_color = Theme::c("div_line");
const auto& tty_mode = Config::getB("tty_mode");
const auto& rounded = Config::getB("rounded_corners");
const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (tty_mode ? std::to_string(num) : Symbols::superscript.at(clamp(num, 0, 9)));
const auto& right_up = (tty_mode ? Symbols::right_up : Symbols::round_right_up);
const auto& left_up = (tty_mode ? Symbols::left_up : Symbols::round_left_up);
const auto& right_down = (tty_mode ? Symbols::right_down : Symbols::round_right_down);
const auto& left_down = (tty_mode ? Symbols::left_down : Symbols::round_left_down);
const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up);
const auto& left_up = (tty_mode or not rounded ? Symbols::left_up : Symbols::round_left_up);
const auto& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down);
const auto& left_down = (tty_mode or not rounded ? Symbols::left_down : Symbols::round_left_down);
out = Fx::reset + line_color;
@ -583,14 +557,14 @@ namespace Mem {
for (const auto& name : mem_names) {
if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name)};
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name), graph_symbol};
else
mem_meters[name] = Draw::Meter{mem_meter, name};
}
if (show_swap and has_swap) {
for (const auto& name : swap_names) {
if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name)};
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name), graph_symbol};
else
mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)};
}
@ -694,7 +668,7 @@ namespace Mem {
const auto& disks = mem.disks;
cx = x + mem_width - 1; cy = 0;
const bool big_disk = disks_width >= 25;
divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Symbols::div_right + Mv::l(disks_width - 1);
divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Fx::ub + Symbols::div_right + Mv::l(disks_width - 1);
if (io_mode) {
for (const auto& mount : mem.disks_order) {
if (not disks.contains(mount)) continue;
@ -758,7 +732,7 @@ namespace Mem {
if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' '
+ disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 7));
+ disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5));
cy++;
if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++;
}
@ -792,7 +766,7 @@ namespace Net {
auto& net_sync = Config::getB("net_sync");
auto& net_auto = Config::getB("net_auto");
auto& tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc"));
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_net"));
string ip_addr = (net.ipv4.empty() ? net.ipv6 : net.ipv4);
if (old_ip != ip_addr) {
old_ip = ip_addr;
@ -1191,7 +1165,7 @@ namespace Proc {
//* Iteration over processes
int lc = 0;
for (int n=0; auto& p : plist) {
if (p.filtered or n++ < start) continue;
if (n++ < start or p.filtered) continue;
bool is_selected = (lc + 1 == selected);
if (is_selected) selected_pid = (int)p.pid;
@ -1261,7 +1235,7 @@ namespace Proc {
out += c_color + uresize(p.name, width_left - 1) + end + ' ';
width_left -= (ulen(p.name) + 1);
}
if (width_left > 7 and (not p.short_cmd.empty() or p.short_cmd == p.name)) {
if (width_left > 7 and p.short_cmd != p.name) {
out += g_color + '(' + uresize(p.short_cmd, width_left - 3, true) + ") ";
width_left -= (ulen(p.short_cmd, true) + 3);
}
@ -1287,7 +1261,7 @@ namespace Proc {
}
out += Fx::reset;
while (lc++ < height - 4) out += Mv::to(y+lc+2, x+1) + string(width - 2, ' ');
while (lc++ < height - 5) out += Mv::to(y+lc+1, x+1) + string(width - 2, ' ');
//? Draw scrollbar if needed
if (numpids > select_max) {
@ -1337,6 +1311,8 @@ namespace Draw {
Mem::box.clear();
Net::box.clear();
Proc::box.clear();
Global::clock.clear();
Global::overlay.clear();
Input::mouse_mappings.clear();

View file

@ -25,6 +25,35 @@ tab-size = 4
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque;
namespace Symbols {
const string h_line = "";
const string v_line = "";
const string dotted_v_line = "";
const string left_up = "";
const string right_up = "";
const string left_down = "";
const string right_down = "";
const string round_left_up = "";
const string round_right_up = "";
const string round_left_down = "";
const string round_right_down = "";
const string title_left_down = "";
const string title_right_down = "";
const string title_left = "";
const string title_right = "";
const string div_right = "";
const string div_left = "";
const string div_up = "";
const string div_down = "";
const string up = "";
const string down = "";
const string left = "";
const string right = "";
const string enter = "";
}
namespace Draw {
//* An editable text field

View file

@ -137,10 +137,10 @@ namespace Input {
key = mouse_event;
if (not Menu::active and key == "mouse_click") {
if (key == "mouse_click") {
const auto& [col, line] = mouse_pos;
for (const auto& [mapped_key, pos] : mouse_mappings) {
for (const auto& [mapped_key, pos] : (Menu::active ? Menu::mouse_mappings : mouse_mappings)) {
if (col >= pos.col and col < pos.col + pos.width and line >= pos.line and line < pos.line + pos.height) {
key = mapped_key;
break;
@ -235,8 +235,10 @@ namespace Input {
Proc::filter = { Config::getS("proc_filter") };
old_filter = Proc::filter.text;
}
else if (key == "e")
else if (key == "e") {
Config::flip("proc_tree");
no_update = false;
}
else if (key == "r")
Config::flip("proc_reversed");
@ -313,6 +315,7 @@ namespace Input {
auto& pid = Config::getI("selected_pid");
if (key == "+" or key == "space") Proc::expand = pid;
if (key == "-" or key == "space") Proc::collapse = pid;
no_update = false;
}
else if (key == "t") {
Logger::debug(key);

View file

@ -75,6 +75,10 @@ namespace Cpu {
unordered_flat_map<int, int> core_mapping;
}
namespace Net {
uint64_t timestamp = 0;
}
namespace Shared {
fs::path procPath, passwd_path;
@ -137,6 +141,7 @@ namespace Shared {
Mem::collect();
//? Init for namespace Net
Net::timestamp = time_ms();
Net::collect();
}
@ -813,6 +818,7 @@ namespace Net {
auto& config_iface = Config::getS("net_iface");
auto& net_sync = Config::getB("net_sync");
auto& net_auto = Config::getB("net_auto");
auto new_timestamp = time_ms();
if (not no_update and errors < 3) {
//? Get interface list using getifaddrs() wrapper
@ -864,7 +870,7 @@ namespace Net {
const uint64_t val = max(stoul(readfile(sys_file, "0")), saved_stat.last);
//? Update speed, total and top values
saved_stat.speed = val - (saved_stat.last == 0 ? val : saved_stat.last);
saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000));
if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed;
if (saved_stat.offset > val) saved_stat.offset = 0;
saved_stat.total = val - saved_stat.offset;
@ -899,6 +905,8 @@ namespace Net {
}
net.compact();
}
timestamp = new_timestamp;
}
//? Return empty net_info struct if no interfaces was found
@ -964,6 +972,9 @@ namespace Proc {
vector<proc_info> current_procs;
unordered_flat_map<string, string> uid_user;
string current_sort;
string current_filter;
bool current_rev = false;
fs::file_time_type passwd_time;
@ -976,17 +987,19 @@ namespace Proc {
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& filter, bool found=false) {
void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<std::reference_wrapper<proc_info>>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false, const bool no_update=false, const bool should_filter=false) {
auto cur_pos = out_procs.size();
bool filtering = false;
//? If filtering, include children of matching processes
if (not filter.empty() and not found) {
if (not found and (should_filter or not filter.empty())) {
if (not s_contains(std::to_string(cur_proc.pid), filter)
and not s_contains(cur_proc.name, filter)
and not s_contains(cur_proc.cmd, filter)
and not s_contains(cur_proc.user, filter)) {
filtering = true;
cur_proc.filtered = true;
filter_found++;
}
else {
found = true;
@ -994,37 +1007,44 @@ namespace Proc {
}
}
//? Add process to vector if not filtered out or currently in a collapsed sub-tree
//? Set tree index position for process if not filtered out or currently in a collapsed sub-tree
if (not collapsed and not filtering) {
out_procs.push_back(cur_proc);
out_procs.push_back(std::ref(cur_proc));
cur_proc.tree_index = out_procs.size() - 1;
//? Try to find name of the binary file and append to program name if not the same
if (cur_proc.short_cmd.empty() and not cur_proc.cmd.empty()) {
std::string_view cmd_view = cur_proc.cmd;
cmd_view = cmd_view.substr(0, min(cmd_view.find(' '), cmd_view.size()));
cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
out_procs.back().short_cmd = (string)cmd_view;
cur_proc.short_cmd = (string)cmd_view;
}
}
else {
cur_proc.tree_index = in_procs.size();
}
//? Recursive iteration over all children
int children = 0;
for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) {
if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) {
out_procs.back().get().cpu_p += p.cpu_p;
out_procs.back().get().mem += p.mem;
out_procs.back().get().threads += p.threads;
}
if (collapsed and not filtering) {
out_procs.back().cpu_p += p.cpu_p;
out_procs.back().mem += p.mem;
out_procs.back().threads += p.threads;
cur_proc.filtered = true;
}
else children++;
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found);
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter);
}
if (collapsed or filtering) return;
//? Add tree terminator symbol if it's the last child in a sub-tree
if (out_procs.size() > cur_pos + 1 and not out_procs.back().prefix.ends_with("]─"))
out_procs.back().prefix.replace(out_procs.back().prefix.size() - 8, 8, " └─ ");
if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─"))
out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ ");
//? Add collapse/expand symbols if process have any children
out_procs.at(cur_pos).prefix = ""s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ ");
out_procs.at(cur_pos).get().prefix = ""s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ ");
}
//* Get detailed info for selected process
@ -1133,12 +1153,18 @@ namespace Proc {
const auto& tree = Config::getB("proc_tree");
const auto& show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid");
const bool should_filter = current_filter != filter;
if (should_filter) current_filter = filter;
const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter);
if (sorted_change) {
current_sort = sorting;
current_rev = reverse;
}
ifstream pread;
string long_string;
string short_str;
filter_found = 0;
const double uptime = system_uptime();
const double uptime = system_uptime();
const int cmult = (per_core) ? Shared::coreCount : 1;
bool got_detailed = false;
@ -1193,7 +1219,7 @@ namespace Proc {
const size_t pid = stoul(pid_str);
found.push_back(pid);
//? Check if pid already exists
//? Check if pid already exists in current_procs
auto find_old = rng::find(current_procs, pid, &proc_info::pid);
bool no_cache = false;
if (find_old == current_procs.end()) {
@ -1210,6 +1236,7 @@ namespace Proc {
if (not pread.good()) continue;
getline(pread, new_proc.name);
pread.close();
//? Check for whitespace characters in name and set offset to get correct fields from stat file
new_proc.name_offset = rng::count(new_proc.name, ' ');
pread.open(d.path() / "cmdline");
@ -1241,7 +1268,6 @@ namespace Proc {
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
const auto& offset = new_proc.name_offset;
short_str.clear();
size_t x = 0, next_x = 3;
@ -1318,19 +1344,7 @@ namespace Proc {
}
}
// //? Clear dead processes from cache at a regular interval
// if (++counter >= 1000 or (cache.size() > found.size() + 100)) {
// counter = 0;
// for (auto it = cache.begin(); it != cache.end();) {
// if (not v_contains(found, it->first))
// it = cache.erase(it);
// else
// it++;
// }
// cache.compact();
// }
//? Clear dead processes from list
//? Clear dead processes from current_procs
auto eraser = rng::remove_if(current_procs, [&](const auto& element){ return not v_contains(found, element.pid); });
current_procs.erase(eraser.begin(), eraser.end());
@ -1343,97 +1357,102 @@ namespace Proc {
redraw = true;
}
old_cputimes = cputimes;
// current_procs.clear();
// current_procs = procs;
}
//* ---------------------------------------------Collection done-----------------------------------------------
//* Sort processes
switch (v_index(sort_vector, sorting)) {
case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break;
case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break;
case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break;
case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break;
case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break;
case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break;
case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break;
case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break;
}
if (reverse) rng::reverse(current_procs);
if (sorted_change or not no_update) {
switch (v_index(sort_vector, sorting)) {
case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break;
case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break;
case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break;
case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break;
case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break;
case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break;
case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break;
case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break;
}
if (reverse) rng::reverse(current_procs);
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
if (not tree and not reverse and sorting == "cpu lazy") {
double max = 10.0, target = 30.0;
for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) {
if (i <= 5 and current_procs.at(i).cpu_p > max)
max = current_procs.at(i).cpu_p;
else if (i == 6)
target = (max > 30.0) ? max : 10.0;
if (i == offset and current_procs.at(i).cpu_p > 30.0)
offset++;
else if (current_procs.at(i).cpu_p > target) {
rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1);
if (++x > 10) break;
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
if (not tree and not reverse and sorting == "cpu lazy") {
double max = 10.0, target = 30.0;
for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) {
if (i <= 5 and current_procs.at(i).cpu_p > max)
max = current_procs.at(i).cpu_p;
else if (i == 6)
target = (max > 30.0) ? max : 10.0;
if (i == offset and current_procs.at(i).cpu_p > 30.0)
offset++;
else if (current_procs.at(i).cpu_p > target) {
rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1);
if (++x > 10) break;
}
}
}
}
//* Match filter if defined
for (auto& p : current_procs) {
if (not tree and not filter.empty()) {
if (not s_contains(to_string(p.pid), filter)
if (should_filter) {
filter_found = 0;
for (auto& p : current_procs) {
if (not tree and not filter.empty()) {
if (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)) {
p.filtered = true;
filter_found++;
}
else {
p.filtered = false;
}
}
else {
p.filtered = false;
}
else if (not tree) {
p.filtered = false;
}
}
//* Generate tree view if enabled
if (tree) {
if (tree and (not no_update or should_filter or sorted_change)) {
if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) {
auto collapser = rng::find(current_procs, find_pid, &proc_info::pid);
if (collapser != current_procs.end()) {
if (collapse == expand) {
collapser->collapsed = not collapser->collapsed;
collapse = expand = -1;
}
else if (collapse > -1) {
collapser->collapsed = true;
collapse = -1;
}
else if (expand > -1) {
collapser->collapsed = false;
expand = -1;
}
}
collapse = expand = -1;
}
if (should_filter or not filter.empty()) filter_found = 0;
vector<proc_info> tree_procs;
vector<std::reference_wrapper<proc_info>> tree_procs;
tree_procs.reserve(current_procs.size());
//? Stable sort to retain selected sorting among processes with the same parent
rng::stable_sort(current_procs, rng::less{}, &proc_info::ppid);
//? Start recursive iteration over processes with the lowest shared parent pids
for (const auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
_tree_gen(p, current_procs, tree_procs, 0, false, filter);
for (auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
_tree_gen(p, current_procs, tree_procs, 0, false, filter, false, no_update, should_filter);
}
//procs.clear();
current_procs = std::move(tree_procs);
//? Final sort based on tree index
rng::sort(current_procs, rng::less{}, &proc_info::tree_index);
if (reverse) rng::reverse(current_procs);
}
numpids = (not filter.empty() ? filter_found : (int)current_procs.size());
numpids = (int)current_procs.size() - filter_found;
return current_procs;
}

View file

@ -34,6 +34,8 @@ namespace Menu {
atomic<bool> active (false);
string output;
unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
const unordered_flat_map<string, unordered_flat_map<string, vector<string>>> menus = {
{ "options", {
{ "normal", {

View file

@ -21,6 +21,8 @@ tab-size = 4
#include <string>
#include <atomic>
#include <btop_input.hpp>
using std::string, std::atomic;
namespace Menu {
@ -28,5 +30,7 @@ namespace Menu {
extern atomic<bool> active;
extern string output;
//? line, col, height, width
extern unordered_flat_map<string, Input::Mouse_loc> mouse_mappings;
}

View file

@ -215,7 +215,7 @@ namespace Proc {
char state = '0';
uint64_t cpu_n = 0, p_nice = 0, ppid = 0, cpu_s = 0, cpu_t = 0;
string prefix = "";
size_t depth = 0;
size_t depth = 0, tree_index = 0;
bool collapsed = false, filtered = false;
};

View file

@ -26,6 +26,7 @@ tab-size = 4
#include <robin_hood.h>
#include <unistd.h>
#include <limits.h>
#include <termios.h>
#include <sys/ioctl.h>
@ -121,7 +122,7 @@ namespace Term {
void restore() {
if (initialized) {
tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush;
cout << mouse_off << clear << Fx::reset << normal_screen << show_cursor << flush;
initialized = false;
}
}
@ -167,6 +168,15 @@ namespace Tools {
return str;
}
string s_replace(const string& str, const string& from, const string& to) {
size_t start_pos = str.find(from);
if(start_pos == std::string::npos)
return str;
string out = str;
out.replace(start_pos, from.length(), to);
return out;
}
string ltrim(const string& str, const string& t_str) {
string_view str_v = str;
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size());
@ -310,16 +320,6 @@ namespace Tools {
atomic_notify(this->atom);
}
thread_lock::thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) {
status = pthread_mutex_lock(&pt_mutex);
}
thread_lock::~thread_lock() {
if (status == 0) {
pthread_mutex_unlock(&pt_mutex);
}
}
string readfile(const std::filesystem::path& path, const string& fallback) {
if (not fs::exists(path)) return fallback;
string out;
@ -345,6 +345,18 @@ namespace Tools {
return {0, ""};
}
string hostname() {
char host[HOST_NAME_MAX];
gethostname(host, HOST_NAME_MAX);
return (string)host;
}
string username() {
auto user = getenv("LOGNAME");
if (user == NULL or strcmp(user, "")) user = getenv("USER");
return (user != NULL ? user : "");
}
}
namespace Logger {

View file

@ -144,6 +144,9 @@ namespace Tools {
//* Resize a string consisting of UTF8 characters from left (only reduces size)
string luresize(const string str, const size_t len, const bool wide=false);
//* Replace <from> in <str> with <to> and return new string
string s_replace(const string& str, const string& from, const string& to);
//* Capatilize <str>
inline string capitalize(string str) {
str.at(0) = toupper(str.at(0));
@ -263,8 +266,11 @@ namespace Tools {
//* Return current time in <strf> format
string strf_time(const string& strf);
string hostname();
string username();
#if __GNUC__ < 11
inline void atomic_wait(const atomic<bool>& atom, const bool old=true) noexcept { while (atom.load() == old); }
inline void atomic_wait(const atomic<bool>& atom, const bool old=true) noexcept { while (atom.load() == old) sleep_ms(1); }
inline void atomic_notify(const atomic<bool>& atom) noexcept { (void)atom; }
#else
inline void atomic_wait(const atomic<bool>& atom, const bool old=true) noexcept { atom.wait(old); }
@ -280,15 +286,6 @@ namespace Tools {
~atomic_lock();
};
//* RAII wrapper for pthread_mutex_lock & unlock
class thread_lock {
pthread_mutex_t& pt_mutex;
public:
int status;
thread_lock(pthread_mutex_t& mtx);
~thread_lock();
};
//* Read a complete file and return as a string
string readfile(const std::filesystem::path& path, const string& fallback="");