mirror of
https://github.com/aristocratos/btop.git
synced 2024-05-21 04:43:36 +12:00
Added Cpu::collect(), Cpu::get_cpuName() and Cpu::getHz()
This commit is contained in:
parent
3a4f33485a
commit
e33b4b7b0c
10
Makefile
10
Makefile
|
@ -25,7 +25,11 @@ DEPEXT := d
|
|||
OBJEXT := o
|
||||
|
||||
#Flags, Libraries and Includes
|
||||
CXXFLAGS := -std=c++20 -pthread -O3 -Wall -Wextra -Wno-stringop-overread -pedantic
|
||||
REQFLAGS := -std=c++20 -pthread
|
||||
WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic
|
||||
OPTFLAGS := -O3
|
||||
CXXFLAGS := $(OPTFLAGS) $(WARNFLAGS)
|
||||
LINKFLAGS += -pthread
|
||||
INC := -I$(INCDIR) -I$(SRCDIR)
|
||||
|
||||
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
|
||||
|
@ -73,8 +77,8 @@ btop: $(OBJECTS)
|
|||
|
||||
#Compile
|
||||
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
|
||||
$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $<
|
||||
@$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT)
|
||||
$(CXX) $(REQFLAGS) $(CXXFLAGS) $(INC) -c -o $@ $<
|
||||
@$(CXX) $(REQFLAGS) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT)
|
||||
@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)
|
||||
|
|
|
@ -126,12 +126,13 @@ Options menu.
|
|||
|
||||
#### Manual compilation and installation
|
||||
|
||||
Needs GCC/G++ 11 or higher, (GCC 10 is missing some C++20 features).
|
||||
Needs GCC 10 or higher, (GCC 11 preferably).
|
||||
|
||||
>Install dependencies (Ubuntu 21.04 Hirsute)
|
||||
|
||||
``` bash
|
||||
sudo apt install git build-essential gcc-11 g++-11
|
||||
#gcc-10 g++-10 if on older
|
||||
```
|
||||
|
||||
>Clone and compile
|
||||
|
@ -151,9 +152,10 @@ sudo make install
|
|||
# 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)
|
||||
>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)
|
||||
|
||||
``` bash
|
||||
# run after make install and use same PREFIX if any was used at install
|
||||
make su-setuid
|
||||
```
|
||||
|
||||
|
|
118
src/btop.cpp
118
src/btop.cpp
|
@ -71,6 +71,7 @@ namespace Global {
|
|||
string banner;
|
||||
size_t banner_width = 0;
|
||||
string overlay;
|
||||
string clock;
|
||||
|
||||
fs::path self_path;
|
||||
|
||||
|
@ -136,7 +137,7 @@ void argumentParser(const int& argc, char **argv) {
|
|||
}
|
||||
|
||||
//* Handler for SIGWINCH and general resizing events, does nothing if terminal hasn't been resized unless force=true
|
||||
void term_resize(bool force=false) {
|
||||
void term_resize(bool force) {
|
||||
if (auto refreshed = Term::refresh() or force) {
|
||||
if (force and refreshed) force = false;
|
||||
Global::resized = true;
|
||||
|
@ -160,13 +161,13 @@ void clean_quit(const int sig) {
|
|||
Runner::stop();
|
||||
if (Term::initialized) {
|
||||
Term::restore();
|
||||
if (not Global::debuginit) cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush;
|
||||
}
|
||||
if (not Global::exit_error_msg.empty()) {
|
||||
Logger::error(Global::exit_error_msg);
|
||||
cout << "ERROR: " << Global::exit_error_msg << endl;
|
||||
std::cerr << "ERROR: " << Global::exit_error_msg << endl;
|
||||
}
|
||||
Config::write();
|
||||
Input::clear();
|
||||
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
|
||||
if (sig != -1) exit(sig);
|
||||
}
|
||||
|
@ -174,17 +175,13 @@ void clean_quit(const int sig) {
|
|||
//* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP
|
||||
void _sleep() {
|
||||
Runner::stop();
|
||||
if (Term::initialized) {
|
||||
Term::restore();
|
||||
if (not Global::debuginit) cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush;
|
||||
}
|
||||
Term::restore();
|
||||
std::raise(SIGSTOP);
|
||||
}
|
||||
|
||||
//* Handler for SIGCONT; re-initialize terminal and force a resize event
|
||||
void _resume() {
|
||||
Term::init();
|
||||
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << Term::mouse_on << flush;
|
||||
term_resize(true);
|
||||
}
|
||||
|
||||
|
@ -255,9 +252,11 @@ namespace Runner {
|
|||
|
||||
string output;
|
||||
string overlay;
|
||||
string clock;
|
||||
|
||||
//* Secondary thread; run collect, draw and print out
|
||||
void _runner(const vector<string> boxes, const bool no_update, const bool force_redraw) {
|
||||
atomic_lock lck(active);
|
||||
auto timestamp = time_micros();
|
||||
output.clear();
|
||||
|
||||
|
@ -291,48 +290,57 @@ namespace Runner {
|
|||
}
|
||||
|
||||
if (stopping) {
|
||||
active = false;
|
||||
active.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
output += "No boxes shown!";
|
||||
if (boxes.empty()) {
|
||||
output = Term::clear + Mv::to(10, 10) + "No boxes shown!";
|
||||
}
|
||||
|
||||
//? 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;
|
||||
cout << Term::sync_start << (overlay.empty() ? output + clock : Theme::c("inactive_fg") + Fx::uncolor(output + clock) + overlay) << Term::sync_end << flush;
|
||||
|
||||
//! DEBUG stats -->
|
||||
cout << Fx::reset << Mv::to(2, 2) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
|
||||
cout << Fx::reset << Mv::to(1, 20) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
|
||||
|
||||
Input::interrupt = true;
|
||||
|
||||
active = false;
|
||||
active.notify_all();
|
||||
// Input::interrupt = true;
|
||||
}
|
||||
|
||||
//* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all" for all boxes
|
||||
//* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all": all boxes
|
||||
void run(const string& box, const bool no_update, const bool force_redraw) {
|
||||
active.wait(true);
|
||||
Config::unlock();
|
||||
atomic_wait(active);
|
||||
if (stopping) return;
|
||||
active = true;
|
||||
Config::lock();
|
||||
if (not Global::overlay.empty())
|
||||
overlay = Global::overlay;
|
||||
else if (not overlay.empty())
|
||||
overlay.clear();
|
||||
|
||||
std::thread run_thread(_runner, (box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw);
|
||||
run_thread.detach();
|
||||
if (box == "overlay") {
|
||||
cout << Term::sync_start << Global::overlay << Term::sync_end;
|
||||
}
|
||||
else if (box == "clock") {
|
||||
if (not Global::clock.empty())
|
||||
cout << Term::sync_start << Global::clock << Term::sync_end;
|
||||
}
|
||||
else {
|
||||
Config::unlock();
|
||||
Config::lock();
|
||||
|
||||
if (not Global::overlay.empty())
|
||||
overlay = Global::overlay;
|
||||
else if (not overlay.empty())
|
||||
overlay.clear();
|
||||
|
||||
if (not Global::clock.empty())
|
||||
clock = Global::clock;
|
||||
else if (not clock.empty())
|
||||
clock.clear();
|
||||
|
||||
std::thread run_thread(_runner, (box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw);
|
||||
run_thread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
//* Stops any secondary thread running and unlocks config
|
||||
void stop() {
|
||||
stopping = true;
|
||||
active.wait(true);
|
||||
Config::unlock();
|
||||
atomic_wait(active);
|
||||
stopping = false;
|
||||
}
|
||||
|
||||
|
@ -357,7 +365,6 @@ int main(int argc, char **argv) {
|
|||
std::signal(SIGCONT, _signal_handler);
|
||||
std::signal(SIGWINCH, _signal_handler);
|
||||
|
||||
|
||||
//? Setup paths for config, log and user themes
|
||||
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
|
||||
if (getenv(env) != NULL and access(getenv(env), W_OK) != -1) {
|
||||
|
@ -367,8 +374,8 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
if (not Config::conf_dir.empty()) {
|
||||
if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) {
|
||||
cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled." << endl;
|
||||
cout << "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
|
||||
cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n"
|
||||
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
|
||||
}
|
||||
else {
|
||||
Config::conf_file = Config::conf_dir / "btop.conf";
|
||||
|
@ -398,9 +405,10 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
//? Config init
|
||||
{ vector<string> load_errors;
|
||||
Config::load(Config::conf_file, load_errors);
|
||||
{ vector<string> load_warnings;
|
||||
Config::load(Config::conf_file, load_warnings);
|
||||
|
||||
if (Config::current_boxes.empty()) Config::check_boxes(Config::getS("shown_boxes"));
|
||||
Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor")));
|
||||
|
||||
if (Global::debug) Logger::set("DEBUG");
|
||||
|
@ -408,7 +416,7 @@ int main(int argc, char **argv) {
|
|||
|
||||
Logger::info("Logger set to " + Config::getS("log_level"));
|
||||
|
||||
for (const auto& err_str : load_errors) Logger::warning(err_str);
|
||||
for (const auto& err_str : load_warnings) Logger::warning(err_str);
|
||||
}
|
||||
|
||||
//? Try to find and set a UTF-8 locale
|
||||
|
@ -442,9 +450,7 @@ int main(int argc, char **argv) {
|
|||
|
||||
//? Initialize terminal and set options
|
||||
if (not Term::init()) {
|
||||
string err_msg = "No tty detected!\nbtop++ needs an interactive shell to run.";
|
||||
Logger::error(err_msg);
|
||||
cout << "ERROR: " << err_msg << endl;
|
||||
Global::exit_error_msg = "No tty detected!\nbtop++ needs an interactive shell to run.";
|
||||
clean_quit(1);
|
||||
}
|
||||
|
||||
|
@ -477,15 +483,9 @@ int main(int argc, char **argv) {
|
|||
//? Calculate sizes of all boxes
|
||||
Draw::calcSizes();
|
||||
|
||||
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;
|
||||
//? Print out box outlines
|
||||
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 ------------------------------------------------------
|
||||
|
||||
|
@ -563,10 +563,10 @@ int main(int argc, char **argv) {
|
|||
for (long long i = 0; i <= 100; i++) mydata.push_back(i);
|
||||
for (long long i = 100; i >= 0; i--) mydata.push_back(i);
|
||||
// mydata.push_back(50);
|
||||
auto mydata2 = mydata;
|
||||
mydata2.push_back(10);
|
||||
for (long long i = 0; i <= 100; i++) mydata2.push_back(i);
|
||||
for (long long i = 100; i >= 0; i--) mydata2.push_back(i);
|
||||
deque<long long> mydata2 = {2, 3};
|
||||
// mydata2.push_back(10);
|
||||
// for (long long i = 0; i <= 100; i++) mydata2.push_back(i);
|
||||
// for (long long i = 100; i >= 0; i--) mydata2.push_back(i);
|
||||
|
||||
|
||||
|
||||
|
@ -574,9 +574,9 @@ int main(int argc, char **argv) {
|
|||
cout << Draw::createBox(5, 23, Term::width - 10, 12, Theme::c("proc_box"), false, "block", "", 2);
|
||||
cout << Draw::createBox(5, 36, Term::width - 10, 12, Theme::c("proc_box"), false, "tty", "", 3) << flush;
|
||||
auto kts = time_micros();
|
||||
Draw::Graph kgraph {Term::width - 13, 10, "cpu", mydata, "block", false, false};
|
||||
Draw::Graph kgraph2 {Term::width - 13, 10, "upload", {0, 1}, "block", false, false};
|
||||
Draw::Graph kgraph3 {Term::width - 13, 10, "download", {}, "block", false, false};
|
||||
Draw::Graph kgraph {Term::width - 13, 10, "cpu", mydata, "braille", false, false};
|
||||
Draw::Graph kgraph2 {Term::width - 13, 10, "upload", {0}, "braille", false, false};
|
||||
Draw::Graph kgraph3 {Term::width - 13, 10, "download", mydata2, "braille", false, false};
|
||||
|
||||
|
||||
cout << Mv::restore << kgraph(mydata, true)
|
||||
|
@ -584,12 +584,18 @@ int main(int argc, char **argv) {
|
|||
<< Mv::restore << Mv::d(26) << kgraph3(mydata, true) << '\n'
|
||||
<< Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
|
||||
|
||||
// Input::wait();
|
||||
|
||||
for (;;) {
|
||||
mydata.back() = std::rand() % 101;
|
||||
mydata.push_back(std::rand() % 101);
|
||||
mydata2.push_back(mydata.back());
|
||||
if (mydata.size() > 1000) mydata.pop_front();
|
||||
if (mydata2.size() > 1000) mydata2.pop_front();
|
||||
kgraph3 = {Term::width - 13, 10, "cpu", mydata2, "braille", false, false};
|
||||
kts = time_micros();
|
||||
cout << Term::sync_start << Mv::restore << kgraph(mydata)
|
||||
<< Mv::restore << Mv::d(13) << kgraph2(mydata)
|
||||
<< Mv::restore << Mv::d(26) << kgraph3(mydata)
|
||||
<< Mv::restore << Mv::d(26) << kgraph3(mydata2)
|
||||
<< Term::sync_end << endl;
|
||||
cout << Mv::d(1) << "Time: " << time_micros() - kts << " μs. " << flush;
|
||||
if (Input::poll()) {
|
||||
|
|
|
@ -85,6 +85,8 @@ namespace Config {
|
|||
|
||||
{"proc_info_smaps", "#* Use /proc/[pid]/smaps for memory information in the process info box (slow but more accurate)"},
|
||||
|
||||
{"proc_left", "#* Show proc box on left side of screen instead of right."},
|
||||
|
||||
{"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."},
|
||||
|
||||
|
@ -95,6 +97,8 @@ namespace Config {
|
|||
|
||||
{"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."},
|
||||
|
||||
{"cpu_bottom", "#* Show cpu box at bottom of screen instead of top."},
|
||||
|
||||
{"show_uptime", "#* Shows the system uptime in the CPU box."},
|
||||
|
||||
{"check_temp", "#* Show cpu temperature."},
|
||||
|
@ -118,6 +122,8 @@ namespace Config {
|
|||
|
||||
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
|
||||
|
||||
{"mem_below_net", "#* Show mem box below net box instead of above."},
|
||||
|
||||
{"show_swap", "#* If swap memory should be shown in memory box."},
|
||||
|
||||
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
|
||||
|
@ -191,14 +197,17 @@ namespace Config {
|
|||
{"proc_per_core", false},
|
||||
{"proc_mem_bytes", true},
|
||||
{"proc_info_smaps", false},
|
||||
{"proc_left", false},
|
||||
{"cpu_invert_lower", true},
|
||||
{"cpu_single_graph", false},
|
||||
{"cpu_bottom", false},
|
||||
{"show_uptime", true},
|
||||
{"check_temp", true},
|
||||
{"show_coretemp", true},
|
||||
{"show_cpu_freq", true},
|
||||
{"background_update", true},
|
||||
{"mem_graphs", true},
|
||||
{"mem_below_net", false},
|
||||
{"show_swap", true},
|
||||
{"swap_disk", true},
|
||||
{"show_disks", true},
|
||||
|
@ -233,7 +242,7 @@ namespace Config {
|
|||
vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
|
||||
|
||||
bool _locked(const string& name) {
|
||||
writelock.wait(true);
|
||||
atomic_wait(writelock);
|
||||
if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end())
|
||||
write_new = true;
|
||||
return locked.load();
|
||||
|
@ -245,7 +254,7 @@ namespace Config {
|
|||
vector<string> current_boxes;
|
||||
|
||||
void lock() {
|
||||
writelock.wait(true);
|
||||
atomic_wait(writelock);
|
||||
locked = true;
|
||||
}
|
||||
|
||||
|
@ -259,9 +268,7 @@ namespace Config {
|
|||
|
||||
void unlock() {
|
||||
if (not locked) return;
|
||||
writelock.wait(true);
|
||||
writelock = true;
|
||||
|
||||
atomic_lock lck(writelock);
|
||||
try {
|
||||
if (Proc::shown) {
|
||||
ints.at("selected_pid") = Proc::selected_pid;
|
||||
|
@ -290,8 +297,6 @@ namespace Config {
|
|||
}
|
||||
|
||||
locked = false;
|
||||
writelock = false;
|
||||
writelock.notify_all();
|
||||
}
|
||||
|
||||
bool check_boxes(const string& boxes) {
|
||||
|
@ -299,11 +304,25 @@ namespace Config {
|
|||
for (auto& box : new_boxes) {
|
||||
if (not v_contains(valid_boxes, box)) return false;
|
||||
}
|
||||
current_boxes.swap(new_boxes);
|
||||
current_boxes = move(new_boxes);
|
||||
return true;
|
||||
}
|
||||
|
||||
void load(const fs::path& conf_file, vector<string>& load_errors) {
|
||||
void toggle_box(const string& box) {
|
||||
if (not v_contains(current_boxes, box))
|
||||
current_boxes.push_back(box);
|
||||
else
|
||||
current_boxes.erase(rng::find(current_boxes, box));
|
||||
|
||||
string new_boxes;
|
||||
if (not current_boxes.empty()) {
|
||||
for (const auto& b : current_boxes) new_boxes += b + ' ';
|
||||
new_boxes.pop_back();
|
||||
}
|
||||
Config::set("shown_boxes", new_boxes);
|
||||
}
|
||||
|
||||
void load(const fs::path& conf_file, vector<string>& load_warnings) {
|
||||
if (conf_file.empty())
|
||||
return;
|
||||
else if (not fs::exists(conf_file)) {
|
||||
|
@ -337,14 +356,14 @@ namespace Config {
|
|||
if (bools.contains(name)) {
|
||||
cread >> value;
|
||||
if (not isbool(value))
|
||||
load_errors.push_back("Got an invalid bool value for config name: " + name);
|
||||
load_warnings.push_back("Got an invalid bool value for config name: " + name);
|
||||
else
|
||||
bools.at(name) = stobool(value);
|
||||
}
|
||||
else if (ints.contains(name)) {
|
||||
cread >> value;
|
||||
if (not isint(value))
|
||||
load_errors.push_back("Got an invalid integer value for config name: " + name);
|
||||
load_warnings.push_back("Got an invalid integer value for config name: " + name);
|
||||
else
|
||||
ints.at(name) = stoi(value);
|
||||
}
|
||||
|
@ -356,19 +375,19 @@ namespace Config {
|
|||
else cread >> value;
|
||||
|
||||
if (name == "log_level" and not v_contains(Logger::log_levels, value))
|
||||
load_errors.push_back("Invalid log_level: " + value);
|
||||
load_warnings.push_back("Invalid log_level: " + value);
|
||||
else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value))
|
||||
load_errors.push_back("Invalid graph symbol identifier: " + value);
|
||||
load_warnings.push_back("Invalid graph symbol identifier: " + value);
|
||||
else if (name == "shown_boxes" and not value.empty() and not check_boxes(value))
|
||||
load_errors.push_back("Invalid box name in shown_boxes. Using default.");
|
||||
load_warnings.push_back("Invalid box name(s) in shown_boxes: " + value);
|
||||
else
|
||||
strings.at(name) = value;
|
||||
}
|
||||
|
||||
cread.ignore(SSmax, '\n');
|
||||
}
|
||||
cread.close();
|
||||
if (not load_errors.empty()) write_new = true;
|
||||
|
||||
if (not load_warnings.empty()) write_new = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,7 +407,6 @@ namespace Config {
|
|||
else if (bools.contains(name))
|
||||
cwrite << (bools.at(name) ? "True" : "False");
|
||||
}
|
||||
cwrite.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,6 +45,9 @@ namespace Config {
|
|||
//* Check if string only contains space seperated valid names for boxes
|
||||
bool check_boxes(const string& boxes);
|
||||
|
||||
//* Toggle box and update config string shown_boxes
|
||||
void toggle_box(const string& box);
|
||||
|
||||
bool _locked(const string& name);
|
||||
|
||||
//* Return bool for config key <name>
|
||||
|
@ -84,7 +87,7 @@ namespace Config {
|
|||
void unlock();
|
||||
|
||||
//* Load the config file from disk
|
||||
void load(const std::filesystem::path& conf_file, vector<string>& load_errors);
|
||||
void load(const std::filesystem::path& conf_file, vector<string>& load_warnings);
|
||||
|
||||
//* Write the config file to disk
|
||||
void write();
|
||||
|
|
|
@ -36,16 +36,26 @@ using namespace Tools;
|
|||
namespace rng = std::ranges;
|
||||
|
||||
namespace Symbols {
|
||||
const string h_line = "─";
|
||||
const string v_line = "│";
|
||||
const string left_up = "┌";
|
||||
const string right_up = "┐";
|
||||
const string left_down = "└";
|
||||
const string right_down = "┘";
|
||||
const string title_left = "┤";
|
||||
const string title_right = "├";
|
||||
const string div_up = "┬";
|
||||
const string div_down = "┴";
|
||||
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 = "↓";
|
||||
|
@ -190,7 +200,12 @@ namespace Draw {
|
|||
string createBox(const int x, const int y, const int width, const int height, string line_color, const bool fill, const string title, const string title2, const int num) {
|
||||
string out;
|
||||
if (line_color.empty()) line_color = Theme::c("div_line");
|
||||
const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (Config::getB("tty_mode") ? std::to_string(num) : Symbols::superscript[num]);
|
||||
const auto& tty_mode = Config::getB("tty_mode");
|
||||
const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (tty_mode ? std::to_string(num) : Symbols::superscript[num]);
|
||||
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);
|
||||
|
||||
out = Fx::reset + line_color;
|
||||
|
||||
|
@ -207,10 +222,10 @@ namespace Draw {
|
|||
}
|
||||
|
||||
//? Draw corners
|
||||
out += Mv::to(y, x) + Symbols::left_up
|
||||
+ Mv::to(y, x + width - 1) + Symbols::right_up
|
||||
+ Mv::to(y + height - 1, x) + Symbols::left_down
|
||||
+ Mv::to(y + height - 1, x + width - 1) + Symbols::right_down;
|
||||
out += Mv::to(y, x) + left_up
|
||||
+ Mv::to(y, x + width - 1) + right_up
|
||||
+ Mv::to(y + height - 1, x) +left_down
|
||||
+ Mv::to(y + height - 1, x + width - 1) + right_down;
|
||||
|
||||
//? Draw titles if defined
|
||||
if (not title.empty()) {
|
||||
|
@ -218,8 +233,8 @@ namespace Draw {
|
|||
+ Fx::ub + line_color + Symbols::title_right;
|
||||
}
|
||||
if (not title2.empty()) {
|
||||
out += Mv::to(y + height - 1, x + 2) + Symbols::title_left + Theme::c("title") + title2
|
||||
+ Fx::ub + line_color + Symbols::title_right;
|
||||
out += Mv::to(y + height - 1, x + 2) + Symbols::title_left_down + Fx::b + numbering + Theme::c("title") + title2
|
||||
+ Fx::ub + line_color + Symbols::title_right_down;
|
||||
}
|
||||
|
||||
return out + Fx::reset + Mv::to(y + 1, x + 1);
|
||||
|
@ -251,7 +266,6 @@ namespace Draw {
|
|||
//* Graph class ------------------------------------------------------------------------------------------------------------>
|
||||
void Graph::_create(const deque<long long>& data, int data_offset) {
|
||||
const bool mult = (data.size() - data_offset > 1);
|
||||
if (mult and (data.size() - data_offset) % 2 != 0) data_offset--;
|
||||
const auto& graph_symbol = Symbols::graph_symbols.at(symbol + '_' + (invert ? "down" : "up"));
|
||||
array<int, 2> result;
|
||||
const float mod = (height == 1) ? 0.3 : 0.1;
|
||||
|
@ -262,10 +276,10 @@ namespace Draw {
|
|||
}
|
||||
|
||||
//? Horizontal iteration over values in <data>
|
||||
for (int i : iota(data_offset, (int)data.size())) {
|
||||
for (const 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) {
|
||||
else if (not tty_mode and mult) current = not current;
|
||||
if (i < 0) {
|
||||
data_value = 0;
|
||||
last = 0;
|
||||
}
|
||||
|
@ -292,7 +306,7 @@ namespace Draw {
|
|||
//? 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;
|
||||
if (mult and i >= 0) last = data_value;
|
||||
}
|
||||
last = data_value;
|
||||
out.clear();
|
||||
|
@ -312,21 +326,22 @@ namespace Draw {
|
|||
if (not color_gradient.empty()) out += Fx::reset;
|
||||
}
|
||||
|
||||
Graph::Graph() {};
|
||||
Graph::Graph() {}
|
||||
|
||||
Graph::Graph(int width, int height, const string& color_gradient, const deque<long long>& data, const string& symbol, bool invert, bool no_zero, long long max_value, long long offset)
|
||||
: width(width), height(height), color_gradient(color_gradient), invert(invert), no_zero(no_zero), offset(offset) {
|
||||
if (Config::getB("tty_mode") or symbol == "tty") {
|
||||
tty_mode = true;
|
||||
this->symbol = "tty";
|
||||
}
|
||||
if (Config::getB("tty_mode") or symbol == "tty") { tty_mode = true; this->symbol = "tty"; }
|
||||
else if (symbol != "default") this->symbol = symbol;
|
||||
else this->symbol = Config::getS("graph_symbol");
|
||||
|
||||
if (max_value == 0 and offset > 0) max_value = 100;
|
||||
this->max_value = max_value;
|
||||
int value_width = ceil((float)data.size() / 2);
|
||||
int data_offset = 0;
|
||||
if (value_width > width) data_offset = data.size() - width * 2;
|
||||
const int value_width = ceil((double)data.size() / 2);
|
||||
int data_offset = (value_width > width) ? data.size() - width * 2 : 0;
|
||||
|
||||
if ((data.size() - data_offset) % 2 != 0) {
|
||||
data_offset--;
|
||||
}
|
||||
|
||||
//? Populate the two switching graph vectors and fill empty space if data size < width
|
||||
for (const int& i : iota(0, height * 2)) {
|
||||
|
@ -334,19 +349,17 @@ 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;
|
||||
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) {
|
||||
if (data_same) return out;
|
||||
|
||||
//? Make room for new characters on graph
|
||||
bool select_graph = (tty_mode) ? current : not current;
|
||||
if (not tty_mode) current = not current;
|
||||
for (const int& i : iota(0, height)) {
|
||||
if (graphs[select_graph][i][1] == '[') graphs[select_graph][i].erase(0, 4);
|
||||
else graphs[select_graph][i].erase(0, 3);
|
||||
if (graphs[current][i][1] == '[') graphs[current][i].erase(0, 4);
|
||||
else graphs[current][i].erase(0, 3);
|
||||
}
|
||||
this->_create(data, (int)data.size() - 1);
|
||||
return out;
|
||||
|
@ -369,13 +382,35 @@ namespace Cpu {
|
|||
string box;
|
||||
|
||||
string draw(const cpu_info& cpu, const bool force_redraw, const bool data_same) {
|
||||
(void)cpu;
|
||||
(void)data_same;
|
||||
string out;
|
||||
out.reserve(width * height);
|
||||
if (redraw or force_redraw) {
|
||||
redraw = false;
|
||||
auto& cpu_bottom = Config::getB("cpu_bottom");
|
||||
const int button_y = cpu_bottom ? y + height - 1 : y;
|
||||
out += box;
|
||||
const string title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
|
||||
const string title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right);
|
||||
out += Mv::to(button_y, x + 10) + title_left + Theme::c("hi_fg") + Fx::b + 'm' + Theme::c("title") + "enu" + Fx::ub + title_right;
|
||||
|
||||
}
|
||||
|
||||
if (Config::getB("show_cpu_freq") and not cpuHz.empty())
|
||||
out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * max(0ul, 7 - cpuHz.size())
|
||||
+ Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right;
|
||||
|
||||
|
||||
//! VALS
|
||||
|
||||
out += Mv::to(y + 2, x + 5) + Theme::c("title") + Fx::b + ljust("Cpu total=" + to_string(cpu.cpu_percent.at("total").back()), 20);
|
||||
out += Mv::to(y + 4, x + 5);
|
||||
for (int i = 0; const auto& cper : cpu.core_percent) { out += ljust("Core" + to_string(i++) + "=" + to_string(cper.back()), 10); }
|
||||
|
||||
out += Mv::to(y + 6, x + 5);
|
||||
for (const auto& [name, value] : cpu.cpu_percent) { out += ljust(name + "=" + to_string(value.back()), 12); }
|
||||
|
||||
|
||||
redraw = false;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -392,7 +427,7 @@ namespace Mem {
|
|||
string draw(const mem_info& mem, const bool force_redraw, const bool data_same) {
|
||||
(void)mem;
|
||||
(void)data_same;
|
||||
string out;
|
||||
string out = Mv::to(0, 0);
|
||||
if (redraw or force_redraw) {
|
||||
redraw = false;
|
||||
out += box;
|
||||
|
@ -414,7 +449,7 @@ namespace Net {
|
|||
string draw(const net_info& net, const bool force_redraw, const bool data_same) {
|
||||
(void)net;
|
||||
(void)data_same;
|
||||
string out;
|
||||
string out = Mv::to(0, 0);
|
||||
if (redraw or force_redraw) {
|
||||
redraw = false;
|
||||
out += box;
|
||||
|
@ -504,9 +539,10 @@ namespace Proc {
|
|||
string draw(const vector<proc_info>& plist, const bool force_redraw, const bool data_same) {
|
||||
auto& proc_tree = Config::getB("proc_tree");
|
||||
const bool show_detailed = (Config::getB("show_detailed") and Proc::detailed.last_pid == (size_t)Config::getI("detailed_pid"));
|
||||
const bool proc_gradient = (Config::getB("proc_gradient") and not Config::getB("tty_mode") and not Config::getB("lowcolor"));
|
||||
const bool proc_gradient = (Config::getB("proc_gradient") and not Config::getB("lowcolor") and Theme::gradients.contains("proc"));
|
||||
auto& proc_colors = Config::getB("proc_colors");
|
||||
const auto& graph_symbol = (Config::getB("tty_mode") ? "tty" : Config::getS("graph_symbol_proc"));
|
||||
const auto& tty_mode = Config::getB("tty_mode");
|
||||
const auto& graph_symbol = (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");
|
||||
|
@ -516,14 +552,17 @@ namespace Proc {
|
|||
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;
|
||||
if (force_redraw) redraw = true;
|
||||
string out;
|
||||
out.reserve(width * height);
|
||||
|
||||
//* Redraw elements not needed to be updated every cycle
|
||||
if (redraw or force_redraw) {
|
||||
if (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 string title_left_down = Theme::c("proc_box") + Symbols::title_left_down;
|
||||
const string title_right_down = Theme::c("proc_box") + Symbols::title_right_down;
|
||||
for (const auto& key : {"T", "K", "S", "enter"})
|
||||
if (Input::mouse_mappings.contains(key)) Input::mouse_mappings.erase(key);
|
||||
|
||||
|
@ -551,10 +590,11 @@ namespace Proc {
|
|||
|
||||
//? 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(y, x) + Theme::c("proc_box") + Symbols::div_left + Symbols::h_line + title_left + Theme::c("hi_fg") + Fx::b
|
||||
+ (tty_mode ? "4" : Symbols::superscript[4]) + Theme::c("title") + "proc"
|
||||
+ Fx::ub + title_right + Symbols::h_line * (width - 10) + Symbols::div_right
|
||||
+ Mv::to(d_y, dgraph_x + 2) + title_left + Fx::b + Theme::c("title") + pid_str + Fx::ub + title_right
|
||||
+ title_left + Fx::b + Theme::c("title") + uresize(detailed.entry.name, dgraph_width - pid_str.size() - 7, true) + 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;
|
||||
|
@ -565,20 +605,44 @@ namespace Proc {
|
|||
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};
|
||||
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
|
||||
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};
|
||||
Input::mouse_mappings["k"] = {d_y, mouse_x, 1, 4};
|
||||
mouse_x += 6;
|
||||
Input::mouse_mappings["S"] = {d_y, mouse_x, 1, 7};
|
||||
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};
|
||||
|
||||
//? Labels
|
||||
const int item_fit = floor((double)(d_width - 2) / 10);
|
||||
const int item_width = floor((double)(d_width - 2) / min(item_fit, 8));
|
||||
out += Mv::to(d_y + 1, d_x + 1) + Fx::b + Theme::c("title")
|
||||
+ cjust("Status:", item_width)
|
||||
+ cjust("Elapsed:", item_width);
|
||||
if (item_fit >= 3) out += cjust("IO/R:", item_width);
|
||||
if (item_fit >= 4) out += cjust("IO/W:", item_width);
|
||||
if (item_fit >= 5) out += cjust("Parent:", item_width);
|
||||
if (item_fit >= 6) out += cjust("User:", item_width);
|
||||
if (item_fit >= 7) out += cjust("Nice:", item_width);
|
||||
if (item_fit >= 8) out += cjust("Threads:", item_width);
|
||||
|
||||
//? Command line
|
||||
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, true);
|
||||
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, true), d_width - 5, true, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//? Filter
|
||||
|
@ -627,22 +691,22 @@ namespace Proc {
|
|||
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;
|
||||
out += Mv::to(y + height - 1, x + 1) + title_left_down + Fx::b + hi_color + Symbols::up + Theme::c("title") + " select " + down_button + Fx::ub + title_right_down
|
||||
+ title_left_down + Fx::b + t_color + "info " + hi_color + Symbols::enter + Fx::ub + title_right_down;
|
||||
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};
|
||||
out += title_left_down + Fx::b + hi_color + 't' + t_color + "erminate" + Fx::ub + title_right_down;
|
||||
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};
|
||||
out += title_left_down + Fx::b + hi_color + 'k' + t_color + "ill" + Fx::ub + title_right_down;
|
||||
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};
|
||||
out += title_left_down + Fx::b + hi_color + 's' + t_color + "ignals" + Fx::ub + title_right_down;
|
||||
if (selected > 0) Input::mouse_mappings["s"] = {y + height - 1, mouse_x, 1, 7};
|
||||
|
||||
//? Labels for fields in list
|
||||
if (not proc_tree)
|
||||
|
@ -671,45 +735,34 @@ namespace Proc {
|
|||
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);
|
||||
cpu_str += '%' + Mv::r(1) + (dgraph_width < 20 ? "C" : "Core") + to_string(detailed.entry.cpu_n + 1);
|
||||
}
|
||||
out += Mv::to(d_y + 1, dgraph_x + 1) + detailed_cpu_graph(detailed.cpu_percent, (data_same or not alive))
|
||||
out += Mv::to(d_y + 1, dgraph_x + 1) + Fx::ub + detailed_cpu_graph(detailed.cpu_percent, (redraw or 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 string stat_color = (not alive ? Theme::c("inactive_fg") : (detailed.status == "Running" ? Theme::c("proc_misc") : Theme::c("main_fg")));
|
||||
out += Mv::to(d_y + 2, d_x + 1) + stat_color + Fx::ub
|
||||
+ cjust(detailed.status, item_width) + Theme::c("main_fg")
|
||||
+ cjust(detailed.elapsed, item_width);
|
||||
if (item_fit >= 3) out += cjust(detailed.io_read, item_width);
|
||||
if (item_fit >= 4) out += cjust(detailed.io_write, item_width);
|
||||
if (item_fit >= 5) out += cjust(detailed.parent, item_width, true);
|
||||
if (item_fit >= 6) out += cjust(detailed.entry.user, item_width, true);
|
||||
if (item_fit >= 7) out += cjust(to_string(detailed.entry.p_nice), item_width);
|
||||
if (item_fit >= 8) out += 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)
|
||||
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) - 2)
|
||||
+ 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("proc_misc") + detailed_mem_graph(detailed.mem_bytes, (redraw or 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -731,16 +784,15 @@ namespace Proc {
|
|||
if (is_selected) selected_pid = (int)p.pid;
|
||||
|
||||
//? Update graphs for processes with above 0.0% cpu usage, delete if below 0.1% 10x times
|
||||
if (not data_same and (p.cpu_p > 0 or p_counters.contains(p.pid))) {
|
||||
if (not p_graphs.contains(p.pid)) {
|
||||
const bool has_graph = p_counters.contains(p.pid);
|
||||
if ((p.cpu_p > 0 and not has_graph) or (not data_same and has_graph)) {
|
||||
if (not has_graph) {
|
||||
p_graphs[p.pid] = {5, 1, "", {}, graph_symbol};
|
||||
p_counters[p.pid] = 0;
|
||||
}
|
||||
else if (p.cpu_p < 0.1) {
|
||||
if (++p_counters[p.pid] >= 10) {
|
||||
p_graphs.erase(p.pid);
|
||||
p_counters.erase(p.pid);
|
||||
}
|
||||
else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) {
|
||||
p_graphs.erase(p.pid);
|
||||
p_counters.erase(p.pid);
|
||||
}
|
||||
else
|
||||
p_counters[p.pid] = 0;
|
||||
|
@ -784,8 +836,8 @@ 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, prog_size) + ' ' + end
|
||||
+ (cmd_size > 0 ? g_color + ljust(p.cmd, cmd_size, true) + ' ' : "");
|
||||
+ c_color + ljust(p.name, prog_size, true) + ' ' + end
|
||||
+ (cmd_size > 0 ? g_color + ljust(p.cmd, cmd_size, true, true) + ' ' : "");
|
||||
}
|
||||
//? Tree view line
|
||||
else {
|
||||
|
@ -798,8 +850,8 @@ namespace Proc {
|
|||
width_left -= (ulen(p.name) + 1);
|
||||
}
|
||||
if (width_left > 7 and not p.cmd.empty()) {
|
||||
out += g_color + uresize(p.cmd, width_left - 1) + ' ';
|
||||
width_left -= (ulen(p.cmd) + 1);
|
||||
out += g_color + uresize(p.cmd, width_left - 1, true) + ' ';
|
||||
width_left -= (ulen(p.cmd, true) + 1);
|
||||
}
|
||||
out += string(max(0, width_left), ' ');
|
||||
}
|
||||
|
@ -813,8 +865,8 @@ namespace Proc {
|
|||
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) + ' '
|
||||
out += (thread_size > 0 ? t_color + rjust(to_string(min(p.threads, 9999ul)), thread_size) + ' ' + end : "" )
|
||||
+ g_color + ljust(((int)p.user.size() > user_size ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' '
|
||||
+ m_color + rjust(mem_str, 5) + end + ' '
|
||||
+ (is_selected ? "" : Theme::c("inactive_fg")) + graph_bg * 5
|
||||
+ (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 + ' '
|
||||
|
@ -836,8 +888,8 @@ namespace Proc {
|
|||
//? 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;
|
||||
out += Mv::to(y + height - 1, x+width - 3 - max(9, (int)location.size())) + Theme::c("proc_box") + loc_clear
|
||||
+ Symbols::title_left_down + Theme::c("title") + Fx::b + location + Fx::ub + Theme::c("proc_box") + Symbols::title_right_down;
|
||||
|
||||
//? Clear out left over graphs from dead processes at a regular interval
|
||||
if (not data_same and ++counter >= 1000) {
|
||||
|
@ -862,6 +914,9 @@ namespace Proc {
|
|||
namespace Draw {
|
||||
void calcSizes() {
|
||||
auto& boxes = Config::getS("shown_boxes");
|
||||
auto& cpu_bottom = Config::getB("cpu_bottom");
|
||||
auto& mem_below_net = Config::getB("mem_below_net");
|
||||
auto& proc_left = Config::getB("proc_left");
|
||||
|
||||
Cpu::box.clear();
|
||||
Mem::box.clear();
|
||||
|
@ -870,6 +925,8 @@ namespace Draw {
|
|||
|
||||
Input::mouse_mappings.clear();
|
||||
|
||||
Cpu::x = Mem::x = Net::x = Proc::x = 1;
|
||||
Cpu::y = Mem::y = Net::y = Proc::y = 1;
|
||||
Cpu::width = Mem::width = Net::width = Proc::width = 0;
|
||||
Cpu::height = Mem::height = Net::height = Proc::height = 0;
|
||||
Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = true;
|
||||
|
@ -882,38 +939,40 @@ namespace Draw {
|
|||
//* Calculate and draw cpu box outlines
|
||||
if (Cpu::shown) {
|
||||
using namespace Cpu;
|
||||
const bool show_temp = (Config::getB("check_temp") and got_sensors);
|
||||
width = round((double)Term::width * width_p / 100);
|
||||
height = max(8, (int)round((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p) / 100));
|
||||
x = 1;
|
||||
y = cpu_bottom ? Term::height - height + 1 : 1;
|
||||
|
||||
b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5)));
|
||||
if (b_columns * (21 + 12 * got_sensors) < width - (width / 3)) {
|
||||
if (b_columns * (21 + 12 * show_temp) < width - (width / 3)) {
|
||||
b_column_size = 2;
|
||||
b_width = (21 + 12 * got_sensors) * b_columns - (b_columns - 1);
|
||||
b_width = (21 + 12 * show_temp) * b_columns - (b_columns - 1);
|
||||
}
|
||||
else if (b_columns * (15 + 6 * got_sensors) < width - (width / 3)) {
|
||||
else if (b_columns * (15 + 6 * show_temp) < width - (width / 3)) {
|
||||
b_column_size = 1;
|
||||
b_width = (15 + 6 * got_sensors) * b_columns - (b_columns - 1);
|
||||
b_width = (15 + 6 * show_temp) * b_columns - (b_columns - 1);
|
||||
}
|
||||
else if (b_columns * (8 + 6 * got_sensors) < width - (width / 3)) {
|
||||
else if (b_columns * (8 + 6 * show_temp) < width - (width / 3)) {
|
||||
b_column_size = 0;
|
||||
}
|
||||
else {
|
||||
b_columns = (width - width / 3) / (8 + 6 * got_sensors);
|
||||
b_columns = (width - width / 3) / (8 + 6 * show_temp);
|
||||
b_column_size = 0;
|
||||
}
|
||||
|
||||
if (b_column_size == 0) b_width = (8 + 6 * got_sensors) * b_columns + 1;
|
||||
if (b_column_size == 0) b_width = (8 + 6 * show_temp) * b_columns + 1;
|
||||
b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4);
|
||||
|
||||
b_x = width - b_width - 1;
|
||||
b_x = x + width - b_width - 1;
|
||||
b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1;
|
||||
|
||||
box = createBox(x, y, width, height, Theme::c("cpu_box"), true, "cpu", "", 1);
|
||||
box += Mv::to(y, x + 10) + Theme::c("cpu_box") + Symbols::title_left + Fx::b + Theme::c("hi_fg")
|
||||
+ 'M' + Theme::c("title") + "enu" + Fx::ub + Theme::c("cpu_box") + Symbols::title_right;
|
||||
box = createBox(x, y, width, height, Theme::c("cpu_box"), true, (cpu_bottom ? "" : "cpu"), (cpu_bottom ? "cpu" : ""), 1);
|
||||
|
||||
auto& custom = Config::getS("custom_cpu_name");
|
||||
box += createBox(b_x, b_y, b_width, b_height, "", false, uresize((custom.empty() ? cpuName : custom) , b_width - 14));
|
||||
const string cpu_title = uresize((custom.empty() ? Cpu::cpuName : custom) , b_width - 14);
|
||||
box += createBox(b_x, b_y, b_width, b_height, "", false, cpu_title);
|
||||
}
|
||||
|
||||
//* Calculate and draw mem box outlines
|
||||
|
@ -926,7 +985,11 @@ namespace Draw {
|
|||
width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100);
|
||||
height = round((double)Term::height * (100 - Cpu::height_p * Cpu::shown - Net::height_p * Net::shown) / 100) + 1;
|
||||
if (height + Cpu::height > Term::height) height = Term::height - Cpu::height;
|
||||
y = Cpu::height + 1;
|
||||
x = (proc_left and Proc::shown) ? Term::width - width + 1: 1;
|
||||
if (mem_below_net and Net::shown)
|
||||
y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0);
|
||||
else
|
||||
y = cpu_bottom ? 1 : Cpu::height + 1;
|
||||
|
||||
if (show_disks) {
|
||||
mem_width = ceil((double)(width - 3) / 2);
|
||||
|
@ -975,10 +1038,15 @@ namespace Draw {
|
|||
using namespace Net;
|
||||
width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100);
|
||||
height = Term::height - Cpu::height - Mem::height;
|
||||
y = Term::height - height + 1;
|
||||
x = (proc_left and Proc::shown) ? Term::width - width + 1 : 1;
|
||||
if (mem_below_net and Mem::shown)
|
||||
y = cpu_bottom ? 1 : Cpu::height + 1;
|
||||
else
|
||||
y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0);
|
||||
|
||||
b_width = (width > 45) ? 27 : 19;
|
||||
b_height = (height > 10) ? 9 : height - 2;
|
||||
b_x = width - b_width - 1;
|
||||
b_x = x + width - b_width - 1;
|
||||
b_y = y + ((height - 2) / 2) - b_height / 2 + 1;
|
||||
d_graph_height = round((double)(height - 2) / 2);
|
||||
u_graph_height = height - 2 - d_graph_height;
|
||||
|
@ -992,8 +1060,8 @@ namespace Draw {
|
|||
using namespace Proc;
|
||||
width = Term::width - (Mem::shown ? Mem::width : (Net::shown ? Net::width : 0));
|
||||
height = Term::height - Cpu::height;
|
||||
x = Term::width - width + 1;
|
||||
y = Cpu::height + 1;
|
||||
x = proc_left ? 1 : Term::width - width + 1;
|
||||
y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + 1;
|
||||
select_max = height - 3;
|
||||
box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4);
|
||||
}
|
||||
|
|
|
@ -162,13 +162,14 @@ namespace Input {
|
|||
|
||||
string wait() {
|
||||
while (cin.rdbuf()->in_avail() < 1) {
|
||||
if (interrupt) { interrupt = false; return ""; }
|
||||
// if (interrupt) { interrupt = false; return ""; }
|
||||
sleep_ms(10);
|
||||
}
|
||||
return get();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if (cin.rdbuf()->in_avail() > 0) cin.ignore(SSmax);
|
||||
last.clear();
|
||||
}
|
||||
|
||||
|
@ -177,9 +178,23 @@ namespace Input {
|
|||
try {
|
||||
auto& filtering = Config::getB("proc_filtering");
|
||||
if (not filtering and key == "q") clean_quit(0);
|
||||
bool recollect = false;
|
||||
bool no_update = true;
|
||||
bool redraw = true;
|
||||
|
||||
//? Global input actions
|
||||
if (not filtering) {
|
||||
bool keep_going = false;
|
||||
if (is_in(key, "1", "2", "3", "4")) {
|
||||
static const array<string, 4> boxes = {"cpu", "mem", "net", "proc"};
|
||||
Config::toggle_box(boxes.at(std::stoi(key) - 1));
|
||||
term_resize(true);
|
||||
}
|
||||
else
|
||||
keep_going = true;
|
||||
|
||||
if (not keep_going) return;
|
||||
}
|
||||
|
||||
//? Input actions for proc box
|
||||
if (Proc::shown) {
|
||||
bool keep_going = false;
|
||||
|
@ -253,7 +268,6 @@ namespace Input {
|
|||
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;
|
||||
|
@ -284,31 +298,35 @@ namespace Input {
|
|||
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") {
|
||||
else if (is_in(key, "+", "-", "space") and Config::getB("proc_tree") and Config::getI("proc_selected") > 0) {
|
||||
atomic_wait(Runner::active);
|
||||
auto& pid = Config::getI("selected_pid");
|
||||
if (key == "+" or key == "space") Proc::expand = pid;
|
||||
if (key == "-" or key == "space") Proc::collapse = pid;
|
||||
}
|
||||
else if (key == "t") {
|
||||
Logger::debug(key);
|
||||
return;
|
||||
}
|
||||
else if (key == "K") {
|
||||
else if (key == "k") {
|
||||
Logger::debug(key);
|
||||
return;
|
||||
}
|
||||
else if (key == "S") {
|
||||
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;
|
||||
redraw = false;
|
||||
auto old_selected = Config::getI("proc_selected");
|
||||
auto new_selected = Proc::selection(key);
|
||||
if (new_selected == -1)
|
||||
|
@ -319,7 +337,7 @@ namespace Input {
|
|||
else keep_going = true;
|
||||
|
||||
if (not keep_going) {
|
||||
Runner::run("proc", not recollect, redraw);
|
||||
Runner::run("proc", no_update, redraw);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,13 +22,15 @@ tab-size = 4
|
|||
#include <ranges>
|
||||
#include <cmath>
|
||||
#include <unistd.h>
|
||||
#include <numeric>
|
||||
#include <regex>
|
||||
|
||||
#include <btop_shared.hpp>
|
||||
#include <btop_config.hpp>
|
||||
#include <btop_tools.hpp>
|
||||
|
||||
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
|
||||
std::round, std::max, std::min, std::string_literals::operator""s;
|
||||
std::round, std::max, std::min, std::clamp, std::string_literals::operator""s;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
using namespace Tools;
|
||||
|
@ -45,29 +47,39 @@ namespace Tools {
|
|||
}
|
||||
}
|
||||
|
||||
namespace Cpu {
|
||||
vector<uint64_t> core_old_totals;
|
||||
vector<uint64_t> core_old_idles;
|
||||
vector<string> available_fields;
|
||||
cpu_info current_cpu;
|
||||
}
|
||||
|
||||
namespace Shared {
|
||||
|
||||
fs::path proc_path;
|
||||
fs::path procPath;
|
||||
fs::path passwd_path;
|
||||
fs::file_time_type passwd_time;
|
||||
uint64_t totalMem;
|
||||
long pageSize, clkTck, coreCount;
|
||||
string cpuName;
|
||||
|
||||
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())
|
||||
//? Shared global variables init
|
||||
procPath = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : "";
|
||||
if (procPath.empty())
|
||||
throw std::runtime_error("Proc filesystem not found or no permission to read from it!");
|
||||
|
||||
passwd_path = (fs::is_regular_file(fs::path("/etc/passwd")) and access("/etc/passwd", R_OK) != -1) ? "/etc/passwd" : "";
|
||||
if (passwd_path.empty())
|
||||
Logger::warning("Could not read /etc/passwd, will show UID instead of username.");
|
||||
|
||||
coreCount = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
if (coreCount < 1) {
|
||||
coreCount = 1;
|
||||
Logger::warning("Could not determine number of cores, defaulting to 1.");
|
||||
}
|
||||
|
||||
pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
if (pageSize <= 0) {
|
||||
pageSize = 4096;
|
||||
|
@ -80,7 +92,7 @@ namespace Shared {
|
|||
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");
|
||||
ifstream meminfo(Shared::procPath / "meminfo");
|
||||
if (meminfo.good()) {
|
||||
meminfo.ignore(SSmax, ':');
|
||||
meminfo >> totalMem;
|
||||
|
@ -88,19 +100,205 @@ namespace Shared {
|
|||
}
|
||||
if (not meminfo.good() or totalMem == 0)
|
||||
throw std::runtime_error("Could not get total memory size from /proc/meminfo");
|
||||
|
||||
//? Init for namespace Cpu
|
||||
Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {});
|
||||
Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {});
|
||||
Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0);
|
||||
Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0);
|
||||
Cpu::collect();
|
||||
for (auto& [field, vec] : Cpu::current_cpu.cpu_percent) {
|
||||
if (not vec.empty()) Cpu::available_fields.push_back(field);
|
||||
}
|
||||
Cpu::cpuName = Cpu::get_cpuName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Cpu {
|
||||
bool got_sensors = false;
|
||||
string cpuName = "";
|
||||
string cpuName;
|
||||
string cpuHz;
|
||||
|
||||
cpu_info current_cpu;
|
||||
const array<string, 10> time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"};
|
||||
|
||||
unordered_flat_map<string, uint64_t> cpu_old = {
|
||||
{"totals", 0},
|
||||
{"idles", 0},
|
||||
{"user", 0},
|
||||
{"nice", 0},
|
||||
{"system", 0},
|
||||
{"idle", 0},
|
||||
{"iowait", 0},
|
||||
{"irq", 0},
|
||||
{"softirq", 0},
|
||||
{"steal", 0},
|
||||
{"guest", 0},
|
||||
{"guest_nice", 0}
|
||||
};
|
||||
|
||||
string get_cpuName() {
|
||||
string name;
|
||||
ifstream cpuinfo(Shared::procPath / "cpuinfo");
|
||||
if (cpuinfo.good()) {
|
||||
for (string instr; getline(cpuinfo, instr, ':') and not instr.starts_with("model name");)
|
||||
cpuinfo.ignore(SSmax, '\n');
|
||||
if (cpuinfo.bad()) return name;
|
||||
cpuinfo.ignore(1);
|
||||
getline(cpuinfo, name);
|
||||
auto name_vec = ssplit(name);
|
||||
|
||||
if ((s_contains(name, "Xeon") or v_contains(name_vec, "Duo")) and v_contains(name_vec, "CPU")) {
|
||||
auto cpu_pos = v_index(name_vec, "CPU"s);
|
||||
if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')'))
|
||||
name = name_vec.at(cpu_pos + 1);
|
||||
else
|
||||
name.clear();
|
||||
}
|
||||
else if (v_contains(name_vec, "Ryzen")) {
|
||||
auto ryz_pos = v_index(name_vec, "Ryzen"s);
|
||||
name = "Ryzen" + (ryz_pos < name_vec.size() - 1 ? ' ' + name_vec.at(ryz_pos + 1) : "")
|
||||
+ (ryz_pos < name_vec.size() - 2 ? ' ' + name_vec.at(ryz_pos + 2) : "");
|
||||
}
|
||||
else if (s_contains(name, "Intel") and v_contains(name_vec, "CPU")) {
|
||||
auto cpu_pos = v_index(name_vec, "CPU"s);
|
||||
if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1) != "@")
|
||||
name = name_vec.at(cpu_pos + 1);
|
||||
else
|
||||
name.clear();
|
||||
}
|
||||
else
|
||||
name.clear();
|
||||
|
||||
if (name.empty() and not name_vec.empty()) {
|
||||
for (const auto& n : name_vec) {
|
||||
if (n == "@") break;
|
||||
name += n + ' ';
|
||||
}
|
||||
name.pop_back();
|
||||
for (const auto& reg : {regex("Processor"), regex("CPU"), regex("\\(R\\)"), regex("\\(TM\\)"), regex("Intel"),
|
||||
regex("AMD"), regex("Core"), regex("\\d?\\.?\\d+[mMgG][hH][zZ]")}) {
|
||||
name = std::regex_replace(name, reg, "");
|
||||
}
|
||||
name = trim(name);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
string get_cpuHz() {
|
||||
static bool failed = false;
|
||||
if (failed) return "";
|
||||
string cpuhz;
|
||||
try {
|
||||
ifstream cpuinfo(Shared::procPath / "cpuinfo");
|
||||
if (cpuinfo.good()) {
|
||||
string instr;
|
||||
while (getline(cpuinfo, instr, ':') and not instr.starts_with("cpu MHz"))
|
||||
cpuinfo.ignore(SSmax, '\n');
|
||||
cpuinfo.ignore(1);
|
||||
getline(cpuinfo, instr);
|
||||
if (instr.empty()) throw std::runtime_error("");
|
||||
|
||||
int hz_int = round(std::stod(instr));
|
||||
|
||||
if (hz_int >= 1000) {
|
||||
if (hz_int >= 10000) cpuhz = to_string((int)round((double)hz_int / 1000)); // Future proof until we reach THz speeds :)
|
||||
else cpuhz = to_string(round((double)hz_int / 100) / 10.0).substr(0, 3);
|
||||
cpuhz += " GHz";
|
||||
}
|
||||
else if (hz_int > 0)
|
||||
cpuhz = to_string(hz_int) + " MHz";
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
failed = true;
|
||||
Logger::warning("Failed to get cpu clock speed from /proc/cpuinfo.");
|
||||
cpuhz.clear();
|
||||
}
|
||||
|
||||
return cpuhz;
|
||||
}
|
||||
|
||||
cpu_info collect(const bool no_update) {
|
||||
(void)no_update;
|
||||
return current_cpu;
|
||||
if (no_update and not current_cpu.cpu_percent.at("total").empty()) return current_cpu;
|
||||
auto& cpu = current_cpu;
|
||||
// const auto& cpu_sensor = Config::getS("cpu_sensor");
|
||||
|
||||
string short_str;
|
||||
ifstream cread;
|
||||
|
||||
//? Get cpu total times for all cores from /proc/stat
|
||||
cread.open(Shared::procPath / "stat");
|
||||
if (cread.good()) {
|
||||
for (int i = 0; getline(cread, short_str, ' ') and short_str.starts_with("cpu"); i++) {
|
||||
|
||||
//? Excepted on kernel 2.6.3> : 0=user, 1=nice, 2=system, 3=idle, 4=iowait, 5=irq, 6=softirq, 7=steal, 8=guest, 9=guest_nice
|
||||
vector<uint64_t> times;
|
||||
uint64_t total_sum = 0;
|
||||
|
||||
for (uint64_t val; cread >> val; total_sum += val) {
|
||||
times.push_back(val);
|
||||
}
|
||||
cread.clear();
|
||||
if (times.size() < 4) throw std::runtime_error("Malformatted /proc/stat");
|
||||
|
||||
//? Subtract fields 8-9 and any future unknown fields
|
||||
const uint64_t totals = total_sum - (times.size() > 8 ? std::accumulate(times.begin() + 8, times.end(), 0) : 0);
|
||||
|
||||
//? Add iowait field if present
|
||||
const uint64_t idles = times[3] + (times.size() > 4 ? times[4] : 0);
|
||||
|
||||
//? Calculate values for totals from first line of stat
|
||||
if (i == 0) {
|
||||
const uint64_t calc_totals = totals - cpu_old["totals"];
|
||||
const uint64_t calc_idles = idles - cpu_old["idles"];
|
||||
cpu_old["totals"] = totals;
|
||||
cpu_old["idles"] = idles;
|
||||
|
||||
//? Total usage of cpu
|
||||
cpu.cpu_percent["total"].push_back(clamp((uint64_t)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ul, 100ul));
|
||||
|
||||
//? Reduce size if there are more values than needed for graph
|
||||
while ((int)cpu.cpu_percent["total"].size() > Term::width * 2) cpu.cpu_percent["total"].pop_front();
|
||||
|
||||
//? Populate cpu.cpu_percent with all fields from stat
|
||||
for (int ii = 0; const auto& val : times) {
|
||||
cpu.cpu_percent[time_names.at(ii)].push_back(clamp((uint64_t)round((double)(val - cpu_old[time_names.at(ii)]) * 100 / calc_totals), 0ul, 100ul));
|
||||
cpu_old[time_names.at(ii)] = val;
|
||||
|
||||
//? Reduce size if there are more values than needed for graph
|
||||
while ((int)cpu.cpu_percent[time_names.at(ii)].size() > Term::width * 2) cpu.cpu_percent[time_names.at(ii)].pop_front();
|
||||
|
||||
if (++ii == 10) break;
|
||||
}
|
||||
}
|
||||
//? Calculate cpu total for each core
|
||||
else {
|
||||
if (i > Shared::coreCount) break;
|
||||
const uint64_t calc_totals = totals - core_old_totals[i-1];;
|
||||
const uint64_t calc_idles = idles - core_old_idles[i-1];;
|
||||
core_old_totals[i-1] = totals;
|
||||
core_old_idles[i-1] = idles;
|
||||
|
||||
cpu.core_percent[i-1].push_back(clamp((uint64_t)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ul, 100ul));
|
||||
|
||||
//? Reduce size if there are more values than needed for graph
|
||||
if ((int)cpu.core_percent[i-1].size() > 20) cpu.core_percent[i-1].pop_front();
|
||||
|
||||
}
|
||||
}
|
||||
cread.close();
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Failed to read /proc/stat");
|
||||
}
|
||||
|
||||
if (Config::getB("show_cpu_freq"))
|
||||
cpuHz = get_cpuHz();
|
||||
|
||||
return cpu;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,9 +341,9 @@ namespace Proc {
|
|||
|
||||
int counter = 0;
|
||||
}
|
||||
int collapse = -1, expand = -1;
|
||||
uint64_t old_cputimes = 0;
|
||||
atomic<int> numpids = 0;
|
||||
size_t reserve_pids = 500;
|
||||
vector<string> sort_vector = {
|
||||
"pid",
|
||||
"name",
|
||||
|
@ -174,7 +372,6 @@ namespace Proc {
|
|||
|
||||
//* 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) {
|
||||
if (Runner::stopping) return;
|
||||
auto cur_pos = out_procs.size();
|
||||
bool filtering = false;
|
||||
|
||||
|
@ -195,7 +392,6 @@ namespace Proc {
|
|||
if (not collapsed and not filtering) {
|
||||
out_procs.push_back(cur_proc);
|
||||
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)
|
||||
|
@ -225,7 +421,7 @@ namespace Proc {
|
|||
|
||||
//* Get detailed info for selected process
|
||||
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);
|
||||
fs::path pid_path = Shared::procPath / std::to_string(pid);
|
||||
|
||||
if (pid != detailed.last_pid) {
|
||||
detailed = {};
|
||||
|
@ -238,6 +434,7 @@ namespace Proc {
|
|||
detailed.entry = *p;
|
||||
|
||||
//? Update cpu percent deque for process cpu graph
|
||||
if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount;
|
||||
detailed.cpu_percent.push_back(round(detailed.entry.cpu_p));
|
||||
while (detailed.cpu_percent.size() > (size_t)Term::width) detailed.cpu_percent.pop_front();
|
||||
|
||||
|
@ -334,12 +531,12 @@ namespace Proc {
|
|||
string short_str;
|
||||
const double uptime = system_uptime();
|
||||
vector<proc_info> procs;
|
||||
procs.reserve(reserve_pids + 10);
|
||||
int npids = 0;
|
||||
procs.reserve(current_procs.size() + 10);
|
||||
const int cmult = (per_core) ? Shared::coreCount : 1;
|
||||
bool got_detailed = false;
|
||||
if (no_update and not cache.empty()) {
|
||||
procs = current_procs;
|
||||
if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, round(uptime), procs);
|
||||
goto proc_no_update;
|
||||
}
|
||||
|
||||
|
@ -363,7 +560,7 @@ namespace Proc {
|
|||
|
||||
//* Get cpu total times from /proc/stat
|
||||
cputimes = 0;
|
||||
pread.open(Shared::proc_path / "stat");
|
||||
pread.open(Shared::procPath / "stat");
|
||||
if (pread.good()) {
|
||||
pread.ignore(SSmax, ' ');
|
||||
for (uint64_t times; pread >> times; cputimes += times);
|
||||
|
@ -372,7 +569,7 @@ namespace Proc {
|
|||
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)) {
|
||||
for (const auto& d: fs::directory_iterator(Shared::procPath)) {
|
||||
if (Runner::stopping)
|
||||
return current_procs;
|
||||
if (pread.is_open()) pread.close();
|
||||
|
@ -380,7 +577,6 @@ namespace Proc {
|
|||
const string pid_str = d.path().filename();
|
||||
if (not isdigit(pid_str[0])) continue;
|
||||
|
||||
npids++;
|
||||
proc_info new_proc (stoul(pid_str));
|
||||
|
||||
//* Cache program name, command and username
|
||||
|
@ -503,7 +699,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::clkTck) / (uptime - (cache[new_proc.pid].cpu_s / Shared::clkTck));
|
||||
new_proc.cpu_c = (double)cpu_t / ((uptime * Shared::clkTck) - cache[new_proc.pid].cpu_s);
|
||||
|
||||
//? Update cache with latest cpu times
|
||||
cache[new_proc.pid].cpu_t = cpu_t;
|
||||
|
@ -518,7 +714,7 @@ namespace Proc {
|
|||
}
|
||||
|
||||
//* Clear dead processes from cache at a regular interval
|
||||
if (++counter >= 10000 or ((int)cache.size() > npids + 100)) {
|
||||
if (++counter >= 10000 or (cache.size() > procs.size() + 100)) {
|
||||
counter = 0;
|
||||
unordered_flat_map<size_t, p_cache> r_cache;
|
||||
r_cache.reserve(procs.size());
|
||||
|
@ -529,10 +725,6 @@ namespace Proc {
|
|||
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);
|
||||
|
@ -542,6 +734,9 @@ namespace Proc {
|
|||
redraw = true;
|
||||
}
|
||||
|
||||
old_cputimes = cputimes;
|
||||
current_procs = procs;
|
||||
|
||||
proc_no_update:
|
||||
|
||||
//* Match filter if defined
|
||||
|
@ -587,6 +782,19 @@ namespace Proc {
|
|||
|
||||
//* Generate tree view if enabled
|
||||
if (tree) {
|
||||
if (collapse > -1 and collapse == expand) {
|
||||
if (cache.contains(collapse)) cache.at(collapse).collapsed = not cache.at(collapse).collapsed;
|
||||
collapse = expand = -1;
|
||||
}
|
||||
else if (collapse > -1) {
|
||||
if (cache.contains(collapse)) cache.at(collapse).collapsed = true;
|
||||
collapse = -1;
|
||||
}
|
||||
else if (expand > -1) {
|
||||
if (cache.contains(expand)) cache.at(expand).collapsed = false;
|
||||
expand = -1;
|
||||
}
|
||||
|
||||
vector<proc_info> tree_procs;
|
||||
tree_procs.reserve(procs.size());
|
||||
|
||||
|
@ -595,10 +803,9 @@ namespace Proc {
|
|||
|
||||
//? Start recursive iteration over processes with the lowest shared parent pids
|
||||
for (const 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, filter);
|
||||
_tree_gen(p, procs, tree_procs, 0, false, filter);
|
||||
}
|
||||
|
||||
if (Runner::stopping) return current_procs;
|
||||
procs = std::move(tree_procs);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ tab-size = 4
|
|||
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array;
|
||||
|
||||
void clean_quit(const int sig=-1);
|
||||
void term_resize(bool force=false);
|
||||
void banner_gen();
|
||||
|
||||
namespace Global {
|
||||
|
@ -38,6 +39,7 @@ namespace Global {
|
|||
extern string banner;
|
||||
extern atomic<bool> resized;
|
||||
extern string overlay;
|
||||
extern string clock;
|
||||
}
|
||||
|
||||
namespace Runner {
|
||||
|
@ -66,12 +68,26 @@ namespace Shared {
|
|||
|
||||
|
||||
namespace Cpu {
|
||||
extern string box, cpuName;
|
||||
extern string box;
|
||||
extern int x, y, width, height;
|
||||
extern bool shown, redraw, got_sensors;
|
||||
extern string cpuName, cpuHz;
|
||||
|
||||
struct cpu_info {
|
||||
vector<deque<long long>> percent;
|
||||
unordered_flat_map<string, deque<long long>> cpu_percent = {
|
||||
{"total", {}},
|
||||
{"user", {}},
|
||||
{"nice", {}},
|
||||
{"system", {}},
|
||||
{"idle", {}},
|
||||
{"iowait", {}},
|
||||
{"irq", {}},
|
||||
{"softirq", {}},
|
||||
{"steal", {}},
|
||||
{"guest", {}},
|
||||
{"guest_nice", {}}
|
||||
};
|
||||
vector<deque<long long>> core_percent;
|
||||
vector<deque<long long>> temp;
|
||||
array<float, 3> load_avg;
|
||||
};
|
||||
|
@ -81,6 +97,12 @@ namespace Cpu {
|
|||
|
||||
//* 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);
|
||||
|
||||
//* Try to get name of cpu
|
||||
string get_cpuName();
|
||||
|
||||
//* Try to get current cpu clock speed
|
||||
string get_cpuHz();
|
||||
}
|
||||
|
||||
namespace Mem {
|
||||
|
@ -136,7 +158,7 @@ namespace Proc {
|
|||
extern bool shown, redraw;
|
||||
extern int select_max;
|
||||
extern atomic<int> detailed_pid;
|
||||
extern int selected_pid, start, selected;
|
||||
extern int selected_pid, start, selected, collapse, expand;
|
||||
|
||||
//? Contains the valid sorting options for processes
|
||||
extern vector<string> sort_vector;
|
||||
|
|
|
@ -21,6 +21,7 @@ tab-size = 4
|
|||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <btop_tools.hpp>
|
||||
#include <btop_config.hpp>
|
||||
|
|
|
@ -32,7 +32,7 @@ tab-size = 4
|
|||
#include <btop_shared.hpp>
|
||||
#include <btop_tools.hpp>
|
||||
|
||||
using std::string_view, std::array, std::max, std::floor, 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, std::cout, std::flush, robin_hood::unordered_flat_map;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
|
||||
|
@ -93,6 +93,7 @@ namespace Term {
|
|||
echo(false);
|
||||
linebuffered(false);
|
||||
refresh();
|
||||
cout << alt_screen << hide_cursor << mouse_on << flush;
|
||||
Global::resized = false;
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +105,7 @@ namespace Term {
|
|||
echo(true);
|
||||
linebuffered(true);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
|
||||
cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush;
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
@ -113,11 +115,12 @@ namespace Term {
|
|||
|
||||
namespace Tools {
|
||||
|
||||
string uresize(string str, const size_t len) {
|
||||
string uresize(string str, const size_t len, const bool wide) {
|
||||
if (len < 1 or str.empty()) return "";
|
||||
for (size_t x = 0, i = 0; i < str.size(); i++) {
|
||||
if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) x++;
|
||||
if (x == len + 1) {
|
||||
if (wide and static_cast<unsigned char>(str.at(i)) > 0xef) x += 2;
|
||||
else if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) x++;
|
||||
if (x >= len + 1) {
|
||||
str.resize(i);
|
||||
str.shrink_to_fit();
|
||||
break;
|
||||
|
@ -126,14 +129,18 @@ namespace Tools {
|
|||
return str;
|
||||
}
|
||||
|
||||
string luresize(string str, const size_t len) {
|
||||
string luresize(string str, const size_t len, const bool wide) {
|
||||
if (len < 1 or str.empty()) return "";
|
||||
for (size_t x = 0, last_pos = 0, i = str.size() - 1; i > 0 ; i--) {
|
||||
if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) {
|
||||
if (wide and static_cast<unsigned char>(str.at(i)) > 0xef) {
|
||||
x += 2;
|
||||
last_pos = max(0ul, i - 1);
|
||||
}
|
||||
else if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) {
|
||||
x++;
|
||||
last_pos = i;
|
||||
}
|
||||
if (x == len) {
|
||||
if (x >= len) {
|
||||
str = str.substr(last_pos);
|
||||
str.shrink_to_fit();
|
||||
break;
|
||||
|
@ -165,9 +172,9 @@ namespace Tools {
|
|||
return out;
|
||||
}
|
||||
|
||||
string ljust(string str, const size_t x, const bool utf, const bool limit) {
|
||||
string ljust(string str, const size_t x, const bool utf, const bool wide, const bool limit) {
|
||||
if (utf) {
|
||||
if (limit and ulen(str) > x) return uresize(str, x);
|
||||
if (limit and ulen(str, wide) > x) return uresize(str, x, wide);
|
||||
return str + string(max((int)(x - ulen(str)), 0), ' ');
|
||||
}
|
||||
else {
|
||||
|
@ -176,9 +183,9 @@ namespace Tools {
|
|||
}
|
||||
}
|
||||
|
||||
string rjust(string str, const size_t x, const bool utf, const bool limit) {
|
||||
string rjust(string str, const size_t x, const bool utf, const bool wide, const bool limit) {
|
||||
if (utf) {
|
||||
if (limit and ulen(str) > x) return uresize(str, x);
|
||||
if (limit and ulen(str, wide) > x) return uresize(str, x, wide);
|
||||
return string(max((int)(x - ulen(str)), 0), ' ') + str;
|
||||
}
|
||||
else {
|
||||
|
@ -187,9 +194,9 @@ namespace Tools {
|
|||
}
|
||||
}
|
||||
|
||||
string cjust(string str, const size_t x, const bool utf, const bool limit) {
|
||||
string cjust(string str, const size_t x, const bool utf, const bool wide, const bool limit) {
|
||||
if (utf) {
|
||||
if (limit and ulen(str) > x) return uresize(str, x);
|
||||
if (limit and ulen(str, wide) > x) return uresize(str, x, wide);
|
||||
return string(max((int)ceil((double)(x - ulen(str)) / 2), 0), ' ') + str + string(max((int)floor((double)(x - ulen(str)) / 2), 0), ' ');
|
||||
}
|
||||
else {
|
||||
|
@ -272,6 +279,15 @@ namespace Tools {
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
atomic_lock::atomic_lock(atomic<bool>& atom) : atom(atom) {
|
||||
while (not this->atom.compare_exchange_strong(this->not_true, true));
|
||||
}
|
||||
|
||||
atomic_lock::~atomic_lock() {
|
||||
this->atom.store(false);
|
||||
atomic_notify(this->atom);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Logger {
|
||||
|
@ -291,22 +307,24 @@ namespace Logger {
|
|||
|
||||
void log_write(const size_t level, const string& msg) {
|
||||
if (loglevel < level or logfile.empty()) return;
|
||||
busy.wait(true);
|
||||
busy = true;
|
||||
atomic_lock lck(busy);
|
||||
std::error_code ec;
|
||||
if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) {
|
||||
auto old_log = logfile;
|
||||
old_log += ".1";
|
||||
if (fs::exists(old_log)) fs::remove(old_log, ec);
|
||||
if (not ec) fs::rename(logfile, old_log, ec);
|
||||
try {
|
||||
if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) {
|
||||
auto old_log = logfile;
|
||||
old_log += ".1";
|
||||
if (fs::exists(old_log)) fs::remove(old_log, ec);
|
||||
if (not ec) fs::rename(logfile, old_log, ec);
|
||||
}
|
||||
if (not ec) {
|
||||
std::ofstream lwrite(logfile, std::ios::app);
|
||||
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";}
|
||||
lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
|
||||
}
|
||||
else logfile.clear();
|
||||
}
|
||||
if (not ec) {
|
||||
std::ofstream lwrite(logfile, std::ios::app);
|
||||
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";}
|
||||
lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
|
||||
catch (const std::exception& e) {
|
||||
throw std::runtime_error("Exception in Logger::log_write() : " + (string)e.what());
|
||||
}
|
||||
else logfile.clear();
|
||||
busy = false;
|
||||
busy.notify_one();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,16 +125,17 @@ namespace Term {
|
|||
namespace Tools {
|
||||
constexpr auto SSmax = std::numeric_limits<std::streamsize>::max();
|
||||
|
||||
//* Return number of UTF8 characters in a string
|
||||
inline size_t ulen(const string& str) {
|
||||
return std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
|
||||
};
|
||||
//* Return number of UTF8 characters in a string (counts UTF-8 characters with a width > 1 as 2 characters)
|
||||
inline size_t ulen(const string& str, const bool wide=false) {
|
||||
return std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; })
|
||||
+ (wide ? std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) > 0xef); }) : 0);
|
||||
}
|
||||
|
||||
//* Resize a string consisting of UTF8 characters (only reduces size)
|
||||
string uresize(const string str, const size_t len);
|
||||
string uresize(const string str, const size_t len, const bool wide=false);
|
||||
|
||||
//* Resize a string consisting of UTF8 characters from left (only reduces size)
|
||||
string luresize(const string str, const size_t len);
|
||||
string luresize(const string str, const size_t len, const bool wide=false);
|
||||
|
||||
//* Return <str> with only uppercase characters
|
||||
inline string str_to_upper(string str) {
|
||||
|
@ -211,22 +212,22 @@ namespace Tools {
|
|||
//* Left/right-trim <t_str> from <str> and return new string
|
||||
inline string trim(const string& str, const string& t_str = " ") {
|
||||
return ltrim(rtrim(str, t_str), t_str);
|
||||
};
|
||||
}
|
||||
|
||||
//* Split <string> at all occurrences of <delim> and return as vector of strings
|
||||
vector<string> ssplit(const string& str, const char& delim = ' ');
|
||||
|
||||
//* Put current thread to sleep for <ms> milliseconds
|
||||
inline void sleep_ms(const size_t& ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); };
|
||||
inline void sleep_ms(const size_t& ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); }
|
||||
|
||||
//* Left justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
|
||||
string ljust(string str, const size_t x, const bool utf=false, const bool limit=true);
|
||||
string ljust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true);
|
||||
|
||||
//* 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);
|
||||
string rjust(string str, const size_t x, const bool utf=false, const bool wide=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);
|
||||
string cjust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true);
|
||||
|
||||
//* Replace whitespaces " " with escape code for move right
|
||||
string trans(const string& str);
|
||||
|
@ -246,6 +247,24 @@ namespace Tools {
|
|||
//* Return current time in <strf> format
|
||||
string strf_time(const string& strf);
|
||||
|
||||
#if __GNUC__ < 11
|
||||
inline void atomic_wait(const atomic<bool>& atom, const bool old=true) noexcept { while (atom.load() == old); }
|
||||
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); }
|
||||
inline void atomic_notify(const atomic<bool>& atom) noexcept { atom.notify_all(); }
|
||||
#endif
|
||||
|
||||
//* Waits for atomic<bool> to be false and sets it to true on construct, sets to false and notifies on destruct
|
||||
class atomic_lock {
|
||||
atomic<bool>& atom;
|
||||
bool not_true = false;
|
||||
public:
|
||||
atomic_lock(atomic<bool>& atom);
|
||||
~atomic_lock();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
//* Simple logging implementation
|
||||
|
|
Loading…
Reference in a new issue