Added mem and disks

This commit is contained in:
aristocratos 2021-08-10 20:20:33 +02:00
parent 95db0eaf90
commit acb20832d1
10 changed files with 686 additions and 267 deletions

View file

@ -25,12 +25,13 @@ DEPEXT := d
OBJEXT := o
#Flags, Libraries and Includes
REQFLAGS := -std=c++20 -pthread
WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic
OPTFLAGS := -O3
CXXFLAGS := $(OPTFLAGS) $(WARNFLAGS)
LINKFLAGS += -pthread
INC := -I$(INCDIR) -I$(SRCDIR)
REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic -pedantic-errors -Wfatal-errors
OPTFLAGS := -O2 -ftree-loop-vectorize
override LDCXXFLAGS += -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection -fcf-protection -flto
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := -I$(INCDIR) -I$(SRCDIR)
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
@ -73,12 +74,12 @@ uninstall:
#Link
btop: $(OBJECTS)
$(CXX) -o $(TARGETDIR)/btop $^ $(LINKFLAGS)
$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS)
#Compile
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(CXX) $(REQFLAGS) $(CXXFLAGS) $(INC) -c -o $@ $<
@$(CXX) $(REQFLAGS) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT)
$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $<
@$(CXX) $(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

@ -18,6 +18,8 @@ tab-size = 4
#include <csignal>
#include <pthread.h>
#include <thread>
#include <future>
#include <numeric>
#include <ranges>
#include <unistd.h>
@ -85,6 +87,7 @@ namespace Global {
uint64_t start_time;
atomic<bool> resized (false);
atomic<int> resizing (0);
atomic<bool> quitting (false);
bool arg_tty = false;
@ -138,31 +141,33 @@ 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) {
if (auto refreshed = Term::refresh() or force) {
if (auto refreshed = Term::refresh(); refreshed or force) {
if (force and refreshed) force = false;
Global::resized = true;
Runner::stop();
}
else return;
auto rez_state = ++Global::resizing;
if (rez_state > 1) return;
Global::resized = true;
Runner::stop();
while (not force) {
sleep_ms(100);
if (not Term::refresh()) break;
if (rez_state != Global::resizing) rez_state = --Global::resizing;
else if (not Term::refresh()) break;
}
Input::interrupt = true;
Draw::calcSizes();
Global::resizing = 0;
}
//* Exit handler; stops threads, restores terminal and saves config changes
void clean_quit(const int sig) {
void clean_quit(int sig) {
if (Global::quitting) return;
Global::quitting = true;
Runner::stop();
if (Term::initialized) {
Term::restore();
}
if (not Global::exit_error_msg.empty()) {
sig = 1;
Logger::error(Global::exit_error_msg);
std::cerr << "ERROR: " << Global::exit_error_msg << endl;
}
@ -170,9 +175,19 @@ void clean_quit(const int sig) {
Input::clear();
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
//? Call quick_exit if there is any existing Tools::atomic_lock objects to avoid a segfault from its destructor
//! There's probably a better solution to this...
if (Tools::active_locks > 0) quick_exit((sig != -1 ? sig : 0));
//? Wait for any remaining Tools::atomic_lock destructors to finish for max 1000ms
for (int i = 0; Tools::active_locks > 0 and i < 100; i++) {
sleep_ms(10);
}
if (Term::initialized) {
Term::restore();
}
//? Assume error if still not cleaned up and call quick_exit to avoid a segfault from Tools::atomic_lock destructor
if (Tools::active_locks > 0) {
quick_exit((sig != -1 ? sig : 0));
}
if (sig != -1) exit(sig);
}
@ -254,11 +269,13 @@ void banner_gen() {
namespace Runner {
atomic<bool> active (false);
atomic<bool> stopping (false);
atomic<bool> waiting (false);
string output;
string overlay;
string clock;
sigset_t mask;
pthread_mutex_t mtx;
struct runner_conf {
vector<string> boxes;
@ -268,14 +285,20 @@ namespace Runner {
struct runner_conf current_conf;
//* Secondary thread; run collect, draw and print out
//? -------------------------------------- Single instance secondary thread ---------------------------------------
void * _runner(void * _) {
(void) _;
pthread_sigmask(SIG_BLOCK, &mask, NULL);
int ret = pthread_mutex_lock(&mtx);
if (active or stopping or ret != 0 or Global::resized) {
if (ret == 0) pthread_mutex_unlock(&mtx);
pthread_exit(NULL);
}
atomic_lock lck(active);
auto timestamp = time_micros();
output.clear();
const auto& conf = current_conf;
string output;
output.reserve(Term::height * Term::width);
const auto conf = current_conf;
for (const auto& box : conf.boxes) {
if (stopping) break;
@ -307,6 +330,7 @@ namespace Runner {
}
if (stopping) {
pthread_mutex_unlock(&mtx);
pthread_exit(NULL);
}
@ -320,13 +344,16 @@ namespace Runner {
//! DEBUG stats -->
cout << Fx::reset << Mv::to(1, 20) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
pthread_mutex_unlock(&mtx);
pthread_exit(NULL);
}
//? ------------------------------------------ Secondary thread end -----------------------------------------------
//* 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) {
atomic_lock lck(waiting);
atomic_wait(active);
if (stopping) return;
if (stopping or Global::resized) return;
if (box == "overlay") {
cout << Term::sync_start << Global::overlay << Term::sync_end;
@ -357,13 +384,25 @@ namespace Runner {
if (pthread_detach(runner_id) != 0)
throw std::runtime_error("Failed to detach _runner thread!");
for (int i = 0; not active and i < 10; i++) sleep_ms(1);
}
}
//* Stops any secondary thread running
void stop() {
stopping = true;
atomic_wait(active);
int ret = pthread_mutex_trylock(&mtx);
if (ret == EOWNERDEAD or ret == ENOTRECOVERABLE) {
if (active) active = false;
Global::exit_error_msg = "Runner thread died unexpectedly!";
if (not Global::quitting) exit(1);
}
else if (ret == EBUSY)
atomic_wait(active);
else if (ret == 0)
pthread_mutex_unlock(&mtx);
sleep_ms(1);
stopping = false;
}
@ -514,131 +553,6 @@ int main(int argc, char **argv) {
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
//* ------------------------------------------------ TESTING ------------------------------------------------------
if (false) {
cout << "Current: " << std::setlocale(LC_ALL, NULL) << endl;
exit(0);
}
//* Test theme
if (false) {
string key;
bool no_redraw = false;
Config::unlock();
auto theme_index = v_index(Theme::themes, Config::getS("color_theme"));
uint64_t timer = 0;
while (key != "q") {
key.clear();
if (not no_redraw) {
cout << Fx::reset << Term::clear << "Theme: " << Config::getS("color_theme") << ". Generation took " << timer << " μs." << endl;
size_t i = 0;
for(const auto& item : Theme::colors) {
cout << rjust(item.first, 15) << ":" << item.second << ""s * 10 << Fx::reset << " ";
if (++i == 4) {
i = 0;
cout << endl;
}
}
cout << Fx::reset << endl;
cout << "Gradients:";
for (const auto& [name, cvec] : Theme::gradients) {
cout << endl << rjust(name + ":", 10);
for (auto& color : cvec) {
cout << color << "";
}
cout << Fx::reset << endl;
}
}
no_redraw = true;
key = Input::wait();
if (key.empty()) continue;
if (key == "right") {
if (theme_index == Theme::themes.size() - 1) theme_index = 0;
else theme_index++;
}
else if (key == "left") {
if (theme_index == 0) theme_index = Theme::themes.size() - 1;
else theme_index--;
}
else continue;
no_redraw = false;
Config::set("color_theme", Theme::themes.at(theme_index));
timer = time_micros();
Theme::setTheme();
timer = time_micros() - timer;
}
exit(0);
}
//* Test graphs
if (false) {
deque<long long> mydata;
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);
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);
cout << Draw::createBox(5, 10, Term::width - 10, 12, Theme::c("proc_box"), false, "braille", "", 1) << Mv::save;
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, "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)
<< Mv::restore << Mv::d(13) << kgraph2(mydata, true)
<< Mv::restore << Mv::d(26) << kgraph3(mydata, true) << '\n'
<< Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
// Input::wait();
for (;;) {
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(mydata2)
<< Term::sync_end << endl;
cout << Mv::d(1) << "Time: " << time_micros() - kts << " μs. " << flush;
if (Input::poll()) {
if (Input::get() == "space") Input::wait();
else break;
}
sleep_ms(50);
}
Input::get();
exit(0);
}
//? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
uint64_t update_ms = Config::getI("update_ms");
@ -647,17 +561,17 @@ int main(int argc, char **argv) {
try {
while (not true not_eq not false) {
//? Check for exceptions in secondary thread and exit with fail signal if true
if (Global::thread_exception) clean_quit(1);
if (Global::thread_exception) exit(1);
//? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
term_resize();
//? Print out boxes outlines and trigger secondary thread to redraw if terminal has been resized
//? Trigger secondary thread to redraw if terminal has been resized
if (Global::resized) {
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
Global::resized = false;
if (time_ms() < future_time)
Runner::run("all", true, true);
Draw::calcSizes();
Runner::run("all", true);
atomic_wait(Runner::active);
}
//? Start secondary collect & draw thread at the interval set by <update_ms> config value
@ -668,7 +582,7 @@ int main(int argc, char **argv) {
}
//? Loop over input polling and input action processing
for (auto current_time = time_ms(); current_time < future_time and not Global::resized; current_time = time_ms()) {
for (auto current_time = time_ms(); current_time < future_time; current_time = time_ms()) {
//? Check for external clock changes and for changes to the update timer
if (update_ms != (uint64_t)Config::getI("update_ms")) {

View file

@ -121,8 +121,8 @@ namespace Config {
{"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
{"disks_filter", "#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with a comma \",\".\n"
"#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot, /home/user\"."},
{"disks_filter", "#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace \" \".\n"
"#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot /home/user\"."},
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
@ -144,8 +144,8 @@ namespace Config {
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
{"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"device:speed\" separate disks with a comma \",\".\n"
"#* Example: \"/dev/sda:100, /dev/sdb:20\"."},
{"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n"
"#* Example: \"/mnt/media:100 /:20 /boot:1\"."},
{"net_download", "#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"."},
@ -273,6 +273,7 @@ namespace Config {
void unlock() {
if (not locked) return;
atomic_wait(Runner::active);
atomic_lock lck(writelock);
try {
if (Proc::shown) {

View file

@ -146,7 +146,7 @@ namespace Draw {
else {
const string first = uresize(text, --upos);
pos = first.size();
text = first + text.substr(pos);
text = first + luresize(text.substr(pos), ulen(text) - upos - 1);
}
}
else if (key == "delete" and pos < text.size()) {
@ -159,7 +159,7 @@ namespace Draw {
}
else if (ulen(key) == 1) {
if (key.size() == 1) {
text.insert(pos++, 1, key[0]);
text.insert(pos++, 1, key.at(0));
upos++;
}
else {
@ -201,7 +201,7 @@ namespace Draw {
string out;
if (line_color.empty()) line_color = Theme::c("div_line");
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 string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (tty_mode ? std::to_string(num) : Symbols::superscript.at(clamp(num, 0, 9)));
const auto& right_up = (tty_mode ? Symbols::right_up : Symbols::round_right_up);
const auto& left_up = (tty_mode ? Symbols::left_up : Symbols::round_left_up);
const auto& right_down = (tty_mode ? Symbols::right_down : Symbols::round_right_down);
@ -255,7 +255,7 @@ namespace Draw {
for (const int& i : iota(1, width + 1)) {
int y = round((double)i * 100.0 / width);
if (value >= y)
out += Theme::g(color_gradient)[invert ? 100 - y : y] + Symbols::meter;
out += Theme::g(color_gradient).at(invert ? 100 - y : y) + Symbols::meter;
else {
out += Theme::c("meter_bg") + Symbols::meter * (width + 1 - i);
break;
@ -273,7 +273,7 @@ namespace Draw {
const float mod = (height == 1) ? 0.3 : 0.1;
long long data_value = 0;
if (mult and data_offset > 0) {
last = data[data_offset - 1];
last = data.at(data_offset - 1);
if (max_value > 0) last = clamp((last + offset) * 100 / max_value, 0ll, 100ll);
}
@ -286,7 +286,7 @@ namespace Draw {
last = 0;
}
else {
data_value = data[i];
data_value = data.at(i);
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
}
@ -296,7 +296,7 @@ namespace Draw {
const int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0;
//? Calculate previous + current value to fit two values in 1 braille character
for (int ai = 0; const auto& value : {last, data_value}) {
const int clamp_min = (no_zero and horizon == height - 1 and not (i == -1 and ai == 0)) ? 1 : 0;
const int clamp_min = (no_zero and horizon == height - 1 and not (mult and i == data_offset and ai == 0)) ? 1 : 0;
if (value >= cur_high)
result[ai++] = 4;
else if (value <= cur_low)
@ -306,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])];
graphs.at(current).at(horizon) += (height == 1 and result.at(0) + result.at(1) == 0) ? Mv::r(1) : graph_symbol.at((result.at(0) * 5 + result.at(1)));
}
if (mult and i >= 0) last = data_value;
}
@ -314,15 +314,15 @@ namespace Draw {
out.clear();
if (height == 1) {
if (not color_gradient.empty())
out += (last < 1 and not color_gradient.empty() ? Theme::c("inactive_fg") : Theme::g(color_gradient)[last]);
out += graphs[current][0];
out += (last < 1 and not color_gradient.empty() ? Theme::c("inactive_fg") : Theme::g(color_gradient).at(last));
out += graphs.at(current).at(0);
}
else {
for (const int& i : iota(0, height)) {
if (i > 0) out += Mv::d(1) + Mv::l(width);
if (not color_gradient.empty())
out += (invert) ? Theme::g(color_gradient)[i * 100 / (height - 1)] : Theme::g(color_gradient)[100 - (i * 100 / (height - 1))];
out += (invert) ? graphs[current][ (height - 1) - i] : graphs[current][i];
out += (invert) ? Theme::g(color_gradient).at(i * 100 / (height - 1)) : Theme::g(color_gradient).at(100 - (i * 100 / (height - 1)));
out += (invert) ? graphs.at(current).at((height - 1) - i) : graphs.at(current).at(i);
}
}
if (not color_gradient.empty()) out += Fx::reset;
@ -360,8 +360,8 @@ namespace Draw {
//? Make room for new characters on graph
if (not tty_mode) current = not current;
for (const int& i : iota(0, height)) {
if (graphs[current][i][1] == '[') graphs[current][i].erase(0, 4);
else graphs[current][i].erase(0, 3);
if (graphs.at(current).at(i).at(1) == '[') graphs.at(current).at(i).erase(0, 4);
else graphs.at(current).at(i).erase(0, 3);
}
this->_create(data, (int)data.size() - 1);
return out;
@ -400,7 +400,7 @@ namespace Cpu {
auto& graph_lo_field = Config::getS("cpu_graph_lower");
auto& tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_cpu"));
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up"))[1];
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(1);
auto& temp_scale = Config::getS("temp_scale");
string out;
out.reserve(width * height);
@ -425,10 +425,10 @@ namespace Cpu {
Input::mouse_mappings["+"] = {button_y, x + width - 5, 1, 2};
//? Graphs & meters
graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", cpu.cpu_percent.at(graph_up_field), graph_symbol};
cpu_meter = Draw::Meter{b_width - (show_temps ? 23 : 11), "cpu"};
graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", cpu.cpu_percent.at(graph_up_field), graph_symbol, false, true};
cpu_meter = Draw::Meter{b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11), "cpu"};
if (not single_graph)
graph_lower = Draw::Graph{x + width - b_width - 3, graph_low_height, "cpu", cpu.cpu_percent.at(graph_lo_field), graph_symbol, Config::getB("cpu_invert_lower")};
graph_lower = Draw::Graph{x + width - b_width - 3, graph_low_height, "cpu", cpu.cpu_percent.at(graph_lo_field), graph_symbol, Config::getB("cpu_invert_lower"), true};
if (mid_line) {
out += Mv::to(y + graph_up_height + 1, x) + Fx::ub + Theme::c("cpu_box") + Symbols::div_left + Theme::c("div_line")
+ Symbols::h_line * (width - b_width - 2) + Symbols::div_right
@ -444,7 +444,7 @@ namespace Cpu {
if (show_temps) {
temp_graphs.clear();
temp_graphs.emplace_back(5, 1, "", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23);
if (not hide_cores) {
if (not hide_cores and b_column_size > 1) {
for (const auto& i : iota((size_t)1, cpu.temp.size())) {
temp_graphs.emplace_back(5, 1, "", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23);
}
@ -463,11 +463,14 @@ namespace Cpu {
+ Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right;
out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back())
+ Theme::g("cpu")[cpu.cpu_percent.at("total").back()] + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%';
+ Theme::g("cpu").at(cpu.cpu_percent.at("total").back()) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%';
if (show_temps) {
const auto& [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale);
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + Theme::g("temp")[clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)]
+ temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw) + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale);
const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll));
if (b_column_size > 1 or b_columns > 1)
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color
+ temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw);
out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
}
out += Theme::c("div_line") + Symbols::v_line;
@ -480,15 +483,18 @@ namespace Cpu {
+ ljust(to_string(n), (b_column_size == 0 ? 2 : 3));
if (b_column_size > 0 or extra_width > 0)
out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width)
+ Theme::g("cpu")[cpu.core_percent[n].back()] + core_graphs[n](cpu.core_percent[n], data_same or redraw);
+ Theme::g("cpu").at(cpu.core_percent.at(n).back()) + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw);
else
out += Theme::g("cpu")[cpu.core_percent[n].back()];
out += rjust(to_string(cpu.core_percent[n].back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%';
out += Theme::g("cpu").at(cpu.core_percent.at(n).back());
out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%';
if (show_temps and not hide_cores) {
const auto& [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale);
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + Theme::g("temp")[clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)]
+ temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw) + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
const auto [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale);
const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll));
if (b_column_size > 1)
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color
+ temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw);
out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
}
out += Theme::c("div_line") + Symbols::v_line;
@ -525,7 +531,7 @@ namespace Cpu {
}
redraw = false;
return out;
return out + Fx::reset;
}
}
@ -535,19 +541,230 @@ namespace Mem {
int min_w = 36, min_h = 10;
int x = 1, y, width, height;
int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter;
int disks_io_h = 0;
bool shown = true, redraw = true;
string box;
unordered_flat_map<string, Draw::Meter> mem_meters;
unordered_flat_map<string, Draw::Graph> mem_graphs;
unordered_flat_map<string, Draw::Meter> disk_meters_used;
unordered_flat_map<string, Draw::Meter> disk_meters_free;
unordered_flat_map<string, Draw::Graph> io_graphs;
string draw(const mem_info& mem, const bool force_redraw, const bool data_same) {
if (Runner::stopping) return "";
(void)mem;
(void)data_same;
string out = Mv::to(0, 0);
if (redraw or force_redraw) {
redraw = false;
if (force_redraw) redraw = true;
auto& show_swap = Config::getB("show_swap");
auto& swap_disk = Config::getB("swap_disk");
auto& show_disks = Config::getB("show_disks");
auto& show_io_stat = Config::getB("show_io_stat");
auto& io_mode = Config::getB("io_mode");
auto& io_graph_combined = Config::getB("io_graph_combined");
auto& use_graphs = Config::getB("mem_graphs");
auto& tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_mem"));
// auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(1);
string out;
out.reserve(height * width);
//* Redraw elements not needed to be updated every cycle
if (redraw) {
out += box;
mem_meters.clear();
mem_graphs.clear();
disk_meters_free.clear();
disk_meters_used.clear();
io_graphs.clear();
//? Mem graphs and meters
for (const auto& name : mem_names) {
if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name)};
else
mem_meters[name] = Draw::Meter{mem_meter, name};
}
if (show_swap and has_swap) {
for (const auto& name : swap_names) {
if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name)};
else
mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)};
}
}
//? Disk meters and io graphs
if (show_disks) {
if (show_io_stat or io_mode) {
if (io_mode)
disks_io_h = max((int)floor((double)(height - 2 - disk_ios) / max(1, disk_ios)), (io_graph_combined ? 1 : 2));
else
disks_io_h = 1;
int half_height = ceil((double)disks_io_h / 2);
unordered_flat_map<string, int> custom_speeds;
if (not Config::getS("io_graph_speeds").empty()) {
auto split = ssplit(Config::getS("io_graph_speeds"));
for (const auto& entry : split) {
auto vals = ssplit(entry);
if (vals.size() == 2 and mem.disks.contains(vals.at(0)) and isint(vals.at(1)))
custom_speeds[vals.at(0)] = std::stoi(vals.at(1));
}
}
for (const auto& [name, disk] : mem.disks) {
if (disk.io_read.empty()) continue;
long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 10) << 20;
//? Create one combined graph for IO read/write if enabled
if (not io_mode or (io_mode and io_graph_combined)) {
deque<long long> combined(disk.io_read.size(), 0);
rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>());
io_graphs[name] = Draw::Graph{disks_width - (io_mode ? 0 : 6), disks_io_h, "available", combined, graph_symbol, false, true, speed};
}
else {
io_graphs[name + "_read"] = Draw::Graph{disks_width, half_height, "free", disk.io_read, graph_symbol, false, true, speed};
io_graphs[name + "_write"] = Draw::Graph{disks_width, disks_io_h - half_height, "used", disk.io_write, graph_symbol, true, true, speed};
}
}
}
if (disk_meter > 0) {
for (int i = 0; const auto& name : mem.disks_order) {
if (i * 2 > height - 2) break;
disk_meters_used[name] = Draw::Meter{disk_meter, "used"};
if ((int)mem.disks_order.size() * 3 <= height - 1)
disk_meters_free[name] = Draw::Meter{disk_meter, "free"};
}
}
}
out += Mv::to(y, x + width - 6) + Fx::ub + Theme::c("mem_box") + Symbols::title_left + (io_mode ? Fx::b : "") + Theme::c("hi_fg")
+ 'i' + Theme::c("title") + 'o' + Fx::ub + Theme::c("mem_box") + Symbols::title_right;
Input::mouse_mappings["i"] = {y, x + width - 5, 1, 2};
}
return out;
//? Mem and swap
int cx = 1, cy = 1;
string divider = (graph_height > 0 ? Mv::l(2) + Theme::c("mem_box") + Symbols::div_left + Theme::c("div_line") + Symbols::h_line * (mem_width - 1)
+ (show_disks ? "" : Theme::c("mem_box")) + Symbols::div_right + Mv::l(mem_width - 1) + Theme::c("main_fg") : "");
string up = (graph_height >= 2 ? Mv::l(mem_width - 2) + Mv::u(graph_height - 1) : "");
bool big_mem = mem_width > 21;
out += Mv::to(y + 1, x + 2) + Theme::c("title") + Fx::b + "Total:" + rjust(floating_humanizer(Shared::totalMem), mem_width - 9) + Fx::ub + Theme::c("main_fg");
vector<string> comb_names (mem_names.begin(), mem_names.end());
if (show_swap and has_swap and not swap_disk) comb_names.insert(comb_names.end(), swap_names.begin(), swap_names.end());
for (auto name : comb_names) {
if (cy > height - 4) break;
string title;
if (name == "swap_used") {
if (cy > height - 5) break;
if (height - cy > 6) {
if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider;
cy += 1;
}
out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(mem.stats.at("swap_total")), mem_width - 8)
+ Theme::c("main_fg") + Fx::ub;
cy += 1;
title = "Used";
}
else if (name == "swap_free")
title = "Free";
if (title.empty()) title = capitalize(name);
const string humanized = floating_humanizer(mem.stats.at(name));
const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back()));
if (mem_size > 2) {
out += Mv::to(y+1+cy, x+1+cx) + divider + ljust(title, 4, false, false, not big_mem) + ljust(":", (big_mem ? 1 : 6))
+ Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + trans(humanized)
+ Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4);
cy += (graph_height == 0 ? 2 : graph_height + 1);
}
else {
out += Mv::to(y+1+cy, x+1+cx) + ljust(title, (mem_size > 1 ? 5 : 1)) + (graph_height >= 2 ? "" : " ")
+ graphics + Theme::c("title") + rjust(humanized, (mem_size > 1 ? 9 : 7));
cy += (graph_height == 0 ? 1 : graph_height);
}
}
if (graph_height > 0 and cy < height - 2)
out += Mv::to(y+1+cy, x+1+cx) + divider;
//? Disks
if (show_disks) {
const auto& disks = mem.disks;
cx = x + mem_width - 1; cy = 0;
const bool big_disk = disks_width >= 25;
divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Symbols::div_right + Mv::l(disks_width - 1);
if (io_mode) {
for (const auto& mount : mem.disks_order) {
if (cy > height - 3) break;
const auto& disk = disks.at(mount);
if (disk.io_read.empty()) continue;
const string total = floating_humanizer(disk.total, not big_disk);
out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 2) + Mv::to(y+1+cy, x+cx + disks_width - total.size() - 1)
+ trans(total) + Fx::ub;
if (big_disk) {
const string used_percent = to_string(disk.used_percent);
out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2)) + Theme::c("main_fg") + used_percent + '%';
}
if (++cy > height - 3) break;
if (io_graph_combined) {
auto comb_val = disk.io_read.back() + disk.io_write.back();
const string humanized = (disk.io_write.back() > 0 ? ""s : ""s) + (disk.io_read.back() > 0 ? ""s : ""s)
+ (comb_val > 0 ? Mv::r(1) + floating_humanizer(comb_val, true) : "RW");
if (disks_io_h == 1) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ');
out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount)({comb_val}, redraw or data_same)
+ Mv::to(y+1+cy, x+1+cx) + Theme::c("main_fg") + humanized;
cy += disks_io_h;
}
else {
const string human_read = (disk.io_read.back() > 0 ? "" + floating_humanizer(disk.io_read.back(), true) : "R");
const string human_write = (disk.io_write.back() > 0 ? "" + floating_humanizer(disk.io_write.back(), true) : "W");
if (disks_io_h <= 3) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ') + Mv::to(y+cy + disks_io_h, x+1+cx) + string(5, ' ');
out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width)
+ Mv::d(1) + io_graphs.at(mount + "_write")(disk.io_write, redraw or data_same)
+ Mv::to(y+1+cy, x+1+cx) + human_read + Mv::to(y+cy + disks_io_h, x+1+cx) + human_write;
cy += disks_io_h;
}
}
}
else {
for (const auto& mount : mem.disks_order) {
if (cy > height - 3) break;
const auto& disk = disks.at(mount);
auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll);
const string human_io = (comb_val > 0 and big_disk ? (disk.io_write.back() > 0 ? ""s : ""s) + (disk.io_read.back() > 0 ? ""s : ""s)
+ floating_humanizer(comb_val, true) : "");
const string human_total = floating_humanizer(disk.total, not big_disk);
const string human_used = floating_humanizer(disk.used, not big_disk);
const string human_free = floating_humanizer(disk.free, not big_disk);
out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 2) + Mv::to(y+1+cy, x+cx + disks_width - human_total.size() - 1)
+ trans(human_total) + Fx::ub + Theme::c("main_fg");
if (big_disk and not human_io.empty())
out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)human_io.size() / 2)) + Theme::c("main_fg") + human_io;
if (++cy > height - 3) break;
if (show_io_stat and io_graphs.contains(mount)) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO: " : " IO " + Mv::l(2)) + io_graphs.at(mount)({comb_val}, redraw or data_same);
if (not big_disk) out += Mv::to(y+1+cy, x+cx) + Theme::c("main_fg") + human_io;
if (++cy > height - 3) break;
}
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' '
+ disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 7));
if (++cy > height - 3) break;
if ((int)disks.size() * 3 + (show_io_stat ? disk_ios : 0) <= height - 1) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' '
+ disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 7));
cy++;
if ((int)disks.size() * 4 + (show_io_stat ? disk_ios : 0) <= height - 1) cy++;
}
}
}
if (cy < height - 2) out += Mv::to(y+1+cy, x+1+cx) + divider;
}
redraw = false;
return out + Fx::reset;
}
}
@ -570,7 +787,7 @@ namespace Net {
redraw = false;
out += box;
}
return out;
return out + Fx::reset;
}
}
@ -660,7 +877,7 @@ namespace Proc {
auto& proc_colors = Config::getB("proc_colors");
auto& tty_mode = Config::getB("tty_mode");
auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc"));
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up"))[1];
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(1);
auto& mem_bytes = Config::getB("proc_mem_bytes");
start = Config::getI("proc_start");
selected = Config::getI("proc_selected");
@ -707,7 +924,7 @@ namespace Proc {
//? Draw structure of details box
const string pid_str = to_string(detailed.entry.pid);
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"
+ (tty_mode ? "4" : Symbols::superscript.at(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;
@ -911,7 +1128,7 @@ namespace Proc {
p_counters.erase(p.pid);
}
else
p_counters[p.pid] = 0;
p_counters.at(p.pid) = 0;
}
out += Fx::reset;
@ -931,20 +1148,20 @@ namespace Proc {
for (int i = 0; int v : {(int)round(p.cpu_p), (int)round(p.mem * 100 / Shared::totalMem), (int)p.threads / 3}) {
if (proc_gradient) {
int val = (min(v, 100) + 100) - calc * 100 / select_max;
if (val < 100) colors[i++] = Theme::g("proc_color")[val];
else colors[i++] = Theme::g("process")[val - 100];
if (val < 100) colors[i++] = Theme::g("proc_color").at(val);
else colors[i++] = Theme::g("process").at(val - 100);
}
else
colors[i++] = Theme::g("process")[min(v, 100)];
colors[i++] = Theme::g("process").at(min(v, 100));
}
c_color = colors[0]; m_color = colors[1]; t_color = colors[2];
c_color = colors.at(0); m_color = colors.at(1); t_color = colors.at(2);
}
else {
c_color = m_color = t_color = Fx::b;
end = Fx::ub;
}
if (proc_gradient) {
g_color = Theme::g("proc")[calc * 100 / select_max];
g_color = Theme::g("proc").at(calc * 100 / select_max);
}
}
@ -985,7 +1202,7 @@ namespace Proc {
+ 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 + ' '
+ (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' '
+ c_color + rjust(cpu_str, 4) + " " + end;
if (lc++ > height - 5) break;
}
@ -1109,8 +1326,8 @@ namespace Draw {
if (show_disks) {
mem_width = ceil((double)(width - 3) / 2);
disks_width = width - mem_width - 3;
mem_width += mem_width % 2;
disks_width = width - mem_width - 2;
divider = x + mem_width;
}
else
@ -1124,7 +1341,7 @@ namespace Draw {
else
mem_size = 1;
mem_meter = max(0, width - (disks_width * show_disks) - (mem_size > 2 ? 9 : 20));
mem_meter = max(0, mem_width - (mem_size > 2 ? 7 : 17));
if (mem_size == 1) mem_meter += 6;
if (mem_graphs) {
@ -1181,6 +1398,5 @@ namespace Draw {
select_max = height - 3;
box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4);
}
}
}

View file

@ -81,8 +81,8 @@ namespace Input {
bool poll(int timeout) {
if (timeout < 1) return cin.rdbuf()->in_avail() > 0;
while (timeout > 0) {
if (interrupt) return interrupt = false;
if (cin.rdbuf()->in_avail() > 0) return true;
if (interrupt) { interrupt = false; return false; }
sleep_ms(timeout < 10 ? timeout : 10);
timeout -= 10;
}
@ -373,6 +373,22 @@ namespace Input {
return;
}
}
//? Input actions for mem box
if (Mem::shown) {
bool keep_going = false;
bool no_update = true;
bool redraw = true;
if (key == "i") {
Config::flip("io_mode");
}
if (not keep_going) {
Runner::run("mem", no_update, redraw);
return;
}
}
}

View file

@ -24,6 +24,7 @@ tab-size = 4
#include <unistd.h>
#include <numeric>
#include <regex>
#include <sys/statvfs.h>
#include <btop_shared.hpp>
#include <btop_config.hpp>
@ -37,16 +38,6 @@ using namespace Tools;
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
namespace Tools {
double system_uptime() {
string upstr;
ifstream pread("/proc/uptime");
getline(pread, upstr, ' ');
pread.close();
return stod(upstr);
}
}
namespace Cpu {
vector<long long> core_old_totals;
vector<long long> core_old_idles;
@ -84,7 +75,6 @@ namespace Cpu {
namespace Shared {
fs::path procPath, passwd_path;
fs::file_time_type passwd_time;
uint64_t totalMem;
long pageSize, clkTck, coreCount;
@ -140,6 +130,8 @@ namespace Shared {
Cpu::got_sensors = Cpu::get_sensors();
Cpu::core_mapping = Cpu::get_core_mapping();
//? Init for namespace Mem
Mem::collect();
}
@ -395,7 +387,9 @@ namespace Cpu {
auto get_core_mapping() -> unordered_flat_map<int, int> {
unordered_flat_map<int, int> core_map;
ifstream cpuinfo("/proc/cpuinfo");
//? Try to get core mapping from /proc/cpuinfo
ifstream cpuinfo(Shared::procPath / "cpuinfo");
if (cpuinfo.good()) {
int cpu, core;
for (string instr; cpuinfo >> instr;) {
@ -412,8 +406,9 @@ namespace Cpu {
}
}
//? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely map 0-0 1-1 2-2 etc.
if (core_map.size() < (size_t)Shared::coreCount) {
if (Shared::coreCount % 2 == 0 and core_map.size() == (size_t)Shared::coreCount / 2) {
if (Shared::coreCount % 2 == 0 and (long)core_map.size() == Shared::coreCount / 2) {
for (int i = 0; i < Shared::coreCount / 2; i++)
core_map[Shared::coreCount / 2 + i] = i;
}
@ -424,6 +419,7 @@ namespace Cpu {
}
}
//? Apply user set custom mapping if any
const auto& custom_map = Config::getS("cpu_core_map");
if (not custom_map.empty()) {
try {
@ -433,7 +429,7 @@ namespace Cpu {
int change_id = std::stoi(vals.at(0));
int new_id = std::stoi(vals.at(1));
if (not core_map.contains(change_id) or new_id >= Shared::coreCount) continue;
core_map[change_id] = new_id;
core_map.at(change_id) = new_id;
}
}
catch (...) {}
@ -488,7 +484,7 @@ namespace Cpu {
cpu.cpu_percent.at("total").push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll));
//? Reduce size if there are more values than needed for graph
while (cpu.cpu_percent.at("total").size() > (size_t)Term::width * 2) cpu.cpu_percent.at("total").pop_front();
if (cpu.cpu_percent.at("total").size() > (size_t)Term::width * 2) cpu.cpu_percent.at("total").pop_front();
//? Populate cpu.cpu_percent with all fields from stat
for (int ii = 0; const auto& val : times) {
@ -496,7 +492,7 @@ namespace Cpu {
cpu_old.at(time_names.at(ii)) = val;
//? Reduce size if there are more values than needed for graph
while (cpu.cpu_percent.at(time_names.at(ii)).size() > (size_t)Term::width * 2) cpu.cpu_percent.at(time_names.at(ii)).pop_front();
if (cpu.cpu_percent.at(time_names.at(ii)).size() > (size_t)Term::width * 2) cpu.cpu_percent.at(time_names.at(ii)).pop_front();
if (++ii == 10) break;
}
@ -535,12 +531,254 @@ namespace Cpu {
namespace Mem {
bool has_swap = false;
bool disks_fail = false;
vector<string> fstab;
fs::file_time_type fstab_time;
int disk_ios = 0;
mem_info current_mem;
mem_info current_mem {};
auto collect(const bool no_update) -> mem_info {
(void)no_update;
return current_mem;
if (Runner::stopping or no_update) return current_mem;
auto& show_swap = Config::getB("show_swap");
auto& swap_disk = Config::getB("swap_disk");
auto& show_disks = Config::getB("show_disks");
auto& mem = current_mem;
mem.stats.at("swap_total") = 0;
//? Read memory info from /proc/meminfo
ifstream meminfo(Shared::procPath / "meminfo");
if (meminfo.good()) {
bool got_avail = false;
for (string label; meminfo >> label;) {
if (label == "MemFree:") {
meminfo >> mem.stats.at("free");
mem.stats.at("free") <<= 10;
}
else if (label == "MemAvailable:") {
meminfo >> mem.stats.at("available");
mem.stats.at("available") <<= 10;
got_avail = true;
}
else if (label == "Cached:") {
meminfo >> mem.stats.at("cached");
mem.stats.at("cached") <<= 10;
if (not show_swap and not swap_disk) break;
}
else if (label == "SwapTotal:") {
meminfo >> mem.stats.at("swap_total");
mem.stats.at("swap_total") <<= 10;
}
else if (label == "SwapFree:") {
meminfo >> mem.stats.at("swap_free");
mem.stats.at("swap_free") <<= 10;
break;
}
meminfo.ignore(SSmax, '\n');
}
if (not got_avail) mem.stats.at("available") = mem.stats.at("free") + mem.stats.at("cached");
mem.stats.at("used") = Shared::totalMem - mem.stats.at("available");
if (mem.stats.at("swap_total") > 0) mem.stats.at("swap_used") = mem.stats.at("swap_total") - mem.stats.at("swap_free");
}
else
throw std::runtime_error("Failed to read /proc/meminfo");
meminfo.close();
//? Calculate percentages
for (const auto& name : mem_names) {
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / Shared::totalMem));
}
if (show_swap and mem.stats.at("swap_total") > 0) {
for (const auto& name : swap_names) {
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total")));
}
has_swap = true;
}
else
has_swap = false;
//? Remove values beyond whats needed for graph creation
for (auto& [ignored, deq] : mem.percent) {
if (deq.size() > (size_t)Term::width * 2) deq.pop_front();
}
//? Get disks stats
if (show_disks and not disks_fail) {
try {
auto& disks_filter = Config::getS("disks_filter");
bool filter_exclude = false;
auto& use_fstab = Config::getB("use_fstab");
auto& only_physical = Config::getB("only_physical");
auto& disks = mem.disks;
ifstream diskread;
vector<string> filter;
if (not disks_filter.empty()) {
filter = ssplit(disks_filter);
if (filter.at(0).starts_with("exclude=")) {
filter_exclude = true;
filter.at(0) = filter.at(0).substr(8);
}
}
//? Get list of "real" filesystems from /proc/filesystems
vector<string> fstypes;
if (only_physical and not use_fstab) {
fstypes = {"zfs", "wslfs", "drvfs"};
diskread.open(Shared::procPath / "filesystems");
if (diskread.good()) {
for (string fstype; diskread >> fstype;) {
if (not is_in(fstype, "nodev", "squashfs", "nullfs"))
fstypes.push_back(fstype);
diskread.ignore(SSmax, '\n');
}
}
else
throw std::runtime_error("Failed to read /proc/filesystems");
diskread.close();
}
//? Get disk list to use from fstab if enabled
if (use_fstab and fs::last_write_time("/etc/fstab") != fstab_time) {
fstab.clear();
fstab_time = fs::last_write_time("/etc/fstab");
diskread.open("/etc/fstab");
if (diskread.good()) {
for (string instr; diskread >> instr;) {
if (not instr.starts_with('#')) {
diskread >> instr;
if (not is_in(instr, "none", "swap")) fstab.push_back(instr);
}
diskread.ignore(SSmax, '\n');
}
}
else
throw std::runtime_error("Failed to read /etc/fstab");
diskread.close();
}
//? Get mounts from /etc/mtab or /proc/self/mounts
vector<string> found;
diskread.open((fs::exists("/etc/mtab") ? fs::path("/etc/mtab") : Shared::procPath / "self/mounts"));
if (diskread.good()) {
string dev, mountpoint, fstype;
while (not diskread.eof()) {
std::error_code ec;
diskread >> dev >> mountpoint >> fstype;
//? Match filter if not empty
if (not filter.empty()) {
bool match = v_contains(filter, mountpoint);
if ((filter_exclude and match) or (not filter_exclude and not match))
continue;
}
if ((not use_fstab and not only_physical)
or (use_fstab and v_contains(fstab, mountpoint))
or (not use_fstab and only_physical and v_contains(fstypes, fstype))) {
found.push_back(mountpoint);
//? Save mountpoint, name, dev path and path to /sys/block stat file
if (not disks.contains(mountpoint)) {
disks[mountpoint] = disk_info{fs::canonical(dev, ec), fs::path(mountpoint).filename()};
if (disks.at(mountpoint).dev.empty()) disks.at(mountpoint).dev = dev;
if (disks.at(mountpoint).name.empty()) disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint);
string devname = disks.at(mountpoint).dev.filename();
while (devname.size() >= 2) {
if (fs::exists("/sys/block/" + devname + "/stat")) {
disks.at(mountpoint).stat = "/sys/block/" + devname + "/stat";
break;
}
devname.resize(devname.size() - 1);
}
}
}
diskread.ignore(SSmax, '\n');
}
//? Remove disks no longer mounted or filtered out
if (swap_disk and has_swap) found.push_back("swap");
for (auto i = disks.begin(); i != disks.end();) {
if (not v_contains(found, i->first))
i = disks.erase(i);
else
i++;
}
}
else
throw std::runtime_error("Failed to get mounts from /etc/mtab and /proc/self/mounts");
diskread.close();
//? Get disk/partition stats
for (auto& [mountpoint, disk] : disks) {
if (not fs::exists(mountpoint)) continue;
struct statvfs vfs;
if (statvfs(mountpoint.c_str(), &vfs) < 0) {
Logger::warning("Failed to get disk/partition stats with statvfs() for: " + mountpoint);
continue;
}
disk.total = vfs.f_blocks * vfs.f_frsize;
disk.free = vfs.f_bfree * vfs.f_frsize;
disk.used = disk.total - disk.free;
disk.used_percent = round((double)disk.used * 100 / disk.total);
disk.free_percent = 100 - disk.used_percent;
}
//? Setup disks order in UI and add swap if enabled
mem.disks_order.clear();
if (disks.contains("/")) mem.disks_order.push_back("/");
if (swap_disk and has_swap) {
mem.disks_order.push_back("swap");
if (not disks.contains("swap")) disks["swap"] = {"", "swap"};
disks.at("swap").total = mem.stats.at("swap_total");
disks.at("swap").used = mem.stats.at("swap_used");
disks.at("swap").free = mem.stats.at("swap_free");
disks.at("swap").used_percent = mem.percent.at("swap_used").back();
disks.at("swap").free_percent = mem.percent.at("swap_free").back();
}
for (const auto& name : found)
if (not is_in(name, "/", "swap")) mem.disks_order.push_back(name);
//? Get disks IO
int64_t sectors_read, sectors_write;
disk_ios = 0;
for (auto& [ignored, disk] : disks) {
if (disk.stat.empty()) continue;
diskread.open(disk.stat);
if (diskread.good()) {
disk_ios++;
for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); }
diskread >> sectors_read;
if (disk.io_read.empty())
disk.io_read.push_back(0);
else
disk.io_read.push_back(max(0l, (sectors_read - disk.old_io.at(0)) * 512));
disk.old_io.at(0) = sectors_read;
if (disk.io_read.size() > (size_t)Term::width * 2) disk.io_read.pop_front();
for (int i = 0; i < 3; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); }
diskread >> sectors_write;
if (disk.io_write.empty())
disk.io_write.push_back(0);
else
disk.io_write.push_back(max(0l, (sectors_write - disk.old_io.at(1)) * 512));
disk.old_io.at(1) = sectors_write;
if (disk.io_write.size() > (size_t)Term::width * 2) disk.io_write.pop_front();
}
diskread.close();
}
}
catch (const std::exception& e) {
Logger::warning("Error in Mem::collect() : " + (string)e.what());
disks_fail = true;
}
}
return mem;
}
}
@ -568,6 +806,7 @@ namespace Proc {
vector<proc_info> current_procs;
unordered_flat_map<size_t, p_cache> cache;
unordered_flat_map<string, string> uid_user;
fs::file_time_type passwd_time;
uint64_t cputimes;
int counter = 0;
@ -657,17 +896,17 @@ namespace Proc {
if (pid != detailed.last_pid) {
detailed = {};
detailed.last_pid = pid;
detailed.skip_smaps = (not Config::getB("proc_info_smaps"));
detailed.skip_smaps = not Config::getB("proc_info_smaps");
}
//? Copy proc_info for process from proc vector
auto p = rng::find(procs, pid, &proc_info::pid);
detailed.entry = *p;
auto p_info = rng::find(procs, pid, &proc_info::pid);
detailed.entry = *p_info;
//? 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();
if (detailed.cpu_percent.size() > (size_t)Term::width) detailed.cpu_percent.pop_front();
//? Process runtime
detailed.elapsed = sec_to_dhms(uptime - (cache.at(pid).cpu_s / Shared::clkTck));
@ -716,7 +955,7 @@ namespace Proc {
redraw = true;
}
while (detailed.mem_bytes.size() > (size_t)Term::width) detailed.mem_bytes.pop_front();
if (detailed.mem_bytes.size() > (size_t)Term::width) detailed.mem_bytes.pop_front();
//? Get bytes read and written from proc/[pid]/io
if (fs::exists(pid_path / "io")) {
@ -768,9 +1007,9 @@ namespace Proc {
}
//? Update uid_user map if /etc/passwd changed since last run
if (not Shared::passwd_path.empty() and fs::last_write_time(Shared::passwd_path) != Shared::passwd_time) {
if (not Shared::passwd_path.empty() and fs::last_write_time(Shared::passwd_path) != passwd_time) {
string r_uid, r_user;
Shared::passwd_time = fs::last_write_time(Shared::passwd_path);
passwd_time = fs::last_write_time(Shared::passwd_path);
uid_user.clear();
pread.open(Shared::passwd_path);
if (pread.good()) {
@ -782,6 +1021,9 @@ namespace Proc {
pread.ignore(SSmax, '\n');
}
}
else {
Shared::passwd_path.clear();
}
pread.close();
}
@ -842,9 +1084,9 @@ namespace Proc {
cache[new_proc.pid] = {name, cmd, user, name_offset};
}
new_proc.name = cache[new_proc.pid].name;
new_proc.cmd = cache[new_proc.pid].cmd;
new_proc.user = cache[new_proc.pid].user;
new_proc.name = cache.at(new_proc.pid).name;
new_proc.cmd = cache.at(new_proc.pid).cmd;
new_proc.user = cache.at(new_proc.pid).user;
//* Parse /proc/[pid]/stat
pread.open(d.path() / "stat");
@ -856,16 +1098,17 @@ namespace Proc {
size_t x = 0, next_x = 3;
uint64_t cpu_t = 0;
try {
while (pread.good()) {
for (;;) {
while (pread.good() and ++x - offset < next_x) {
pread.ignore(SSmax, ' ');
}
if (pread.bad()) goto stat_loop_done;
getline(pread, short_str, ' ');
switch (x-offset) {
case 3: { //? Process state
new_proc.state = short_str[0];
new_proc.state = short_str.at(0);
continue;
}
case 4: { //? Parent pid
@ -888,16 +1131,16 @@ namespace Proc {
}
case 20: { //? Number of threads
new_proc.threads = stoull(short_str);
if (cache[new_proc.pid].cpu_s == 0) {
if (cache.at(new_proc.pid).cpu_s == 0) {
next_x = 22;
cache[new_proc.pid].cpu_t = cpu_t;
cache.at(new_proc.pid).cpu_t = cpu_t;
}
else
next_x = 24;
continue;
}
case 22: { //? Save cpu seconds to cache if missing
cache[new_proc.pid].cpu_s = stoull(short_str);
cache.at(new_proc.pid).cpu_s = stoull(short_str);
next_x = 24;
continue;
}
@ -923,13 +1166,13 @@ namespace Proc {
if (x-offset < 24) continue;
//? Process cpu usage since last update
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / max(1ul, cputimes - old_cputimes)) / 10.0;
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache.at(new_proc.pid).cpu_t) / max(1ul, cputimes - old_cputimes)) / 10.0;
//? Process cumulative cpu usage since process start
new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - cache[new_proc.pid].cpu_s);
new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - cache.at(new_proc.pid).cpu_s);
//? Update cache with latest cpu times
cache[new_proc.pid].cpu_t = cpu_t;
cache.at(new_proc.pid).cpu_t = cpu_t;
if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
got_detailed = true;
@ -994,13 +1237,13 @@ namespace Proc {
if (not tree and not reverse and sorting == "cpu lazy") {
double max = 10.0, target = 30.0;
for (size_t i = 0, x = 0, offset = 0; i < procs.size(); i++) {
if (i <= 5 and procs[i].cpu_p > max)
max = procs[i].cpu_p;
if (i <= 5 and procs.at(i).cpu_p > max)
max = procs.at(i).cpu_p;
else if (i == 6)
target = (max > 30.0) ? max : 10.0;
if (i == offset and procs[i].cpu_p > 30.0)
if (i == offset and procs.at(i).cpu_p > 30.0)
offset++;
else if (procs[i].cpu_p > target) {
else if (procs.at(i).cpu_p > target) {
rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
if (++x > 10) break;
}
@ -1042,4 +1285,14 @@ namespace Proc {
}
}
namespace Tools {
double system_uptime() {
string upstr;
ifstream pread(Shared::procPath / "uptime");
getline(pread, upstr, ' ');
pread.close();
return stod(upstr);
}
}
#endif

View file

@ -28,7 +28,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 clean_quit(int sig=-1);
void term_resize(bool force=false);
void banner_gen();
@ -105,16 +105,28 @@ namespace Mem {
extern string box;
extern int x, y, width, height;
extern bool has_swap, shown, redraw;
const array<string, 4> mem_names = {"used", "available", "cached", "free"};
const array<string, 2> swap_names = {"swap_used", "swap_free"};
extern int disk_ios;
struct disk_info {
uint64_t total = 0, used = 0;
std::filesystem::path dev;
string name;
std::filesystem::path stat = "";
int64_t total = 0, used = 0, free = 0;
int used_percent = 0, free_percent = 0;
array<int64_t, 2> old_io = {0, 0};
deque<long long> io_read = {};
deque<long long> io_write = {};
};
struct mem_info {
uint64_t total = 0, available = 0, cached = 0, free = 0;
unordered_flat_map<string, deque<long long>> percent;
unordered_flat_map<string, deque<long long>> disks_io;
unordered_flat_map<string, uint64_t> stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0},
{"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}};
unordered_flat_map<string, deque<long long>> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}},
{"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}};
unordered_flat_map<string, disk_info> disks;
vector<string> disks_order;
};
//* Collect mem & disks stats
@ -177,11 +189,11 @@ namespace Proc {
//* Container for process info box
struct detail_container {
size_t last_pid = 0;
bool skip_smaps = false;
proc_info entry;
string elapsed, parent, status, io_read, io_write, memory;
long long first_mem = -1;
size_t last_pid = 0;
bool skip_smaps = false;
deque<long long> cpu_percent;
deque<long long> mem_bytes;
};

View file

@ -67,22 +67,22 @@ namespace Theme {
{ "cpu_start", "#77ca9b" },
{ "cpu_mid", "#cbc06c" },
{ "cpu_end", "#dc4c4c" },
{ "free_start", "#223014" },
{ "free_start", "#384f21" },
{ "free_mid", "#b5e685" },
{ "free_end", "#dcff85" },
{ "cached_start", "#0b1a29" },
{ "cached_start", "#163350" },
{ "cached_mid", "#74e6fc" },
{ "cached_end", "#26c5ff" },
{ "available_start", "#292107" },
{ "available_start", "#4e3f0e" },
{ "available_mid", "#ffd77a" },
{ "available_end", "#ffb814" },
{ "used_start", "#3b1f1c" },
{ "used_start", "#592b26" },
{ "used_mid", "#d9626d" },
{ "used_end", "#ff4769" },
{ "download_start", "#231a63" },
{ "download_start", "#291f75" },
{ "download_mid", "#4f43a3" },
{ "download_end", "#b0a9de" },
{ "upload_start", "#510554" },
{ "upload_start", "#620665" },
{ "upload_mid", "#7d4180" },
{ "upload_end", "#dcafde" },
{ "process_start", "#80d0a3" },

View file

@ -73,7 +73,7 @@ namespace Term {
bool refresh() {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0) return false;
if (width != w.ws_col or height != w.ws_row) {
width = w.ws_col;
height = w.ws_row;

View file

@ -139,6 +139,12 @@ namespace Tools {
//* Resize a string consisting of UTF8 characters from left (only reduces size)
string luresize(const string str, const size_t len, const bool wide=false);
//* Capatilize <str>
inline string capitalize(string str) {
str.at(0) = toupper(str.at(0));
return str;
}
//* Return <str> with only uppercase characters
inline string str_to_upper(string str) {
std::ranges::for_each(str, [](auto& c) { c = ::toupper(c); } );
@ -171,7 +177,7 @@ namespace Tools {
//* Compare <first> with all following values
template<typename First, typename ... T>
bool is_in(First &&first, T && ... t) {
inline bool is_in(const First& first, const T& ... t) {
return ((first == t) || ...);
}