Added Cpu::collect(), Cpu::get_cpuName() and Cpu::getHz()

This commit is contained in:
aristocratos 2021-07-29 23:40:56 +02:00
parent 3a4f33485a
commit e33b4b7b0c
12 changed files with 664 additions and 278 deletions

View file

@ -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)

View file

@ -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
```

View file

@ -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()) {

View file

@ -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();
}
}
}

View file

@ -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();

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -21,6 +21,7 @@ tab-size = 4
#include <ranges>
#include <algorithm>
#include <fstream>
#include <unistd.h>
#include <btop_tools.hpp>
#include <btop_config.hpp>

View file

@ -32,7 +32,7 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_tools.hpp>
using std::string_view, std::array, std::max, std::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();
}
}

View file

@ -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