diff --git a/Makefile b/Makefile index e4ae032..9d04958 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ uninstall: #Link btop: $(OBJECTS) - $(CXX) -o $(TARGETDIR)/btop $^ -pthread + $(CXX) -o $(TARGETDIR)/btop $^ $(LINKFLAGS) #Compile $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) diff --git a/src/btop.cpp b/src/btop.cpp index 7925f77..e23f6af 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -17,7 +17,7 @@ tab-size = 4 */ #include -#include +#include #include #include #include @@ -169,6 +169,11 @@ void clean_quit(const int sig) { Config::write(); 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)); + if (sig != -1) exit(sig); } @@ -253,35 +258,47 @@ namespace Runner { string output; string overlay; string clock; + sigset_t mask; + + struct runner_conf { + vector boxes; + bool no_update = false; + bool force_redraw = false; + }; + + struct runner_conf current_conf; //* Secondary thread; run collect, draw and print out - void _runner(const vector boxes, const bool no_update, const bool force_redraw) { + void * _runner(void * _) { + (void) _; + pthread_sigmask(SIG_BLOCK, &mask, NULL); atomic_lock lck(active); auto timestamp = time_micros(); output.clear(); + const auto& conf = current_conf; - for (const auto& box : boxes) { + for (const auto& box : conf.boxes) { if (stopping) break; try { if (box == "cpu") { - output += Cpu::draw(Cpu::collect(no_update), force_redraw, no_update); + output += Cpu::draw(Cpu::collect(conf.no_update), conf.force_redraw, conf.no_update); } else if (box == "mem") { - output += Mem::draw(Mem::collect(no_update), force_redraw, no_update); + output += Mem::draw(Mem::collect(conf.no_update), conf.force_redraw, conf.no_update); } else if (box == "net") { - output += Net::draw(Net::collect(no_update), force_redraw, no_update); + output += Net::draw(Net::collect(conf.no_update), conf.force_redraw, conf.no_update); } else if (box == "proc") { - output += Proc::draw(Proc::collect(no_update), force_redraw, no_update); + output += Proc::draw(Proc::collect(conf.no_update), conf.force_redraw, conf.no_update); } } catch (const std::exception& e) { string fname = box; fname[0] = toupper(fname[0]); Global::exit_error_msg = "Exception in runner thread -> " - + fname + "::draw(" + fname + "::collect(no_update=" + (no_update ? "true" : "false") - + "), force_redraw=" + (force_redraw ? "true" : "false") + ") : " + (string)e.what(); + + fname + "::draw(" + fname + "::collect(no_update=" + (conf.no_update ? "true" : "false") + + "), force_redraw=" + (conf.force_redraw ? "true" : "false") + ") : " + (string)e.what(); Global::thread_exception = true; Input::interrupt = true; stopping = true; @@ -290,20 +307,20 @@ namespace Runner { } if (stopping) { - return; + pthread_exit(NULL); } - if (boxes.empty()) { + if (conf.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 + //? If overlay isn't empty, print output without color or effects and then print overlay on top 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(1, 20) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush; - // Input::interrupt = true; + pthread_exit(NULL); } //* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all": all boxes @@ -332,8 +349,14 @@ namespace Runner { 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(); + current_conf = {(box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw}; + + pthread_t runner_id; + if (pthread_create(&runner_id, NULL, &_runner, NULL) != 0) + throw std::runtime_error("Failed to create _runner thread!"); + + if (pthread_detach(runner_id) != 0) + throw std::runtime_error("Failed to detach _runner thread!"); } } @@ -359,11 +382,15 @@ int main(int argc, char **argv) { //? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize std::atexit(_exit_handler); - std::at_quick_exit(_exit_handler); std::signal(SIGINT, _signal_handler); std::signal(SIGTSTP, _signal_handler); std::signal(SIGCONT, _signal_handler); std::signal(SIGWINCH, _signal_handler); + sigemptyset(&Runner::mask); + sigaddset(&Runner::mask, SIGINT); + sigaddset(&Runner::mask, SIGTSTP); + sigaddset(&Runner::mask, SIGWINCH); + sigaddset(&Runner::mask, SIGTERM); //? Setup paths for config, log and user themes for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) { @@ -643,8 +670,12 @@ 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()) { - //? Check for external clock changes to avoid a timer bugs - if (future_time - current_time > update_ms) + //? Check for external clock changes and for changes to the update timer + if (update_ms != (uint64_t)Config::getI("update_ms")) { + update_ms = Config::getI("update_ms"); + future_time = time_ms() + update_ms; + } + else if (future_time - current_time > update_ms) future_time = current_time; //? Poll for input and process any input detected diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 2b3e708..ba5b53b 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -63,7 +63,7 @@ namespace Config { {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."}, - {"update_ms", "#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs."}, + {"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."}, {"proc_update_mult", "#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n" "#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)."}, @@ -83,7 +83,7 @@ namespace Config { {"proc_mem_bytes", "#* Show process memory as bytes instead of percent."}, - {"proc_info_smaps", "#* Use /proc/[pid]/smaps for memory information in the process info box (slow but more accurate)"}, + {"proc_info_smaps", "#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)"}, {"proc_left", "#* Show proc box on left side of screen instead of right."}, @@ -107,6 +107,10 @@ namespace Config { {"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."}, + {"cpu_core_map", "#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.\n" + "#* Format \"x:y\" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.\n" + "#* Example: \"4:0 5:1 6:3\""}, + {"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."}, {"show_cpu_freq", "#* Show CPU frequency."}, @@ -173,6 +177,7 @@ namespace Config { {"cpu_graph_upper", "total"}, {"cpu_graph_lower", "total"}, {"cpu_sensor", "Auto"}, + {"cpu_core_map", ""}, {"temp_scale", "celsius"}, {"draw_clock", "%X"}, {"custom_cpu_name", ""}, diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 3854781..f48cf73 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -241,6 +241,8 @@ namespace Draw { } //* Meter class ------------------------------------------------------------------------------------------------------------> + Meter::Meter() {} + Meter::Meter(const int width, const string& color_gradient, const bool invert) : width(width), color_gradient(color_gradient), invert(invert) { cache.insert(cache.begin(), 101, ""); } @@ -292,9 +294,9 @@ namespace Draw { for (const int& horizon : iota(0, height)) { const int cur_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100; const int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0; - const int clamp_min = (no_zero and horizon == height - 1 and i != -1) ? 1 : 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; if (value >= cur_high) result[ai++] = 4; else if (value <= cur_low) @@ -378,37 +380,149 @@ namespace Cpu { int x = 1, y = 1, width, height; int b_columns, b_column_size; int b_x, b_y, b_width, b_height; - bool shown = true, redraw = true; + int graph_up_height; + bool shown = true, redraw = true, mid_line = false; string box; + Draw::Graph graph_upper; + Draw::Graph graph_lower; + Draw::Meter cpu_meter; + vector core_graphs; + vector temp_graphs; string draw(const cpu_info& cpu, const bool force_redraw, const bool data_same) { - (void)data_same; + if (Runner::stopping) return ""; + if (force_redraw) redraw = true; + const bool show_temps = (Config::getB("check_temp") and got_sensors); + auto& single_graph = Config::getB("cpu_single_graph"); + const bool hide_cores = show_temps and (cpu_temp_only or not Config::getB("show_coretemp")); + const int extra_width = (hide_cores ? max(6, 6 * b_column_size) : 0); + auto& graph_up_field = Config::getS("cpu_graph_upper"); + 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& temp_scale = Config::getS("temp_scale"); string out; out.reserve(width * height); + //* Redraw elements not needed to be updated every cycle if (redraw or force_redraw) { auto& cpu_bottom = Config::getB("cpu_bottom"); + mid_line = (not single_graph and graph_up_field != graph_lo_field); + graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0 ? 1 : 0)); + const int graph_low_height = height - 2 - graph_up_height - (mid_line ? 1 : 0); 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); + + //? Buttons on title out += Mv::to(button_y, x + 10) + title_left + Theme::c("hi_fg") + Fx::b + 'm' + Theme::c("title") + "enu" + Fx::ub + title_right; + Input::mouse_mappings["m"] = {button_y, x + 11, 1, 4}; + const string update = to_string(Config::getI("update_ms")) + "ms"; + out += Mv::to(button_y, x + width - update.size() - 8) + title_left + Fx::b + Theme::c("hi_fg") + "- " + Theme::c("title") + update + + Theme::c("hi_fg") + " +" + Fx::ub + title_right; + Input::mouse_mappings["-"] = {button_y, x + width - (int)update.size() - 7, 1, 2}; + 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"}; + 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")}; + 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 + + Mv::to(y + graph_up_height + 1, x + ((width - b_width) / 2) - ((graph_up_field.size() + graph_lo_field.size()) / 2) - 4) + + Theme::c("main_fg") + graph_up_field + Mv::r(1) + "▲▼" + Mv::r(1) + graph_lo_field; + } + if (b_column_size > 0 or extra_width > 0) { + core_graphs.clear(); + for (const auto& core_data : cpu.core_percent) { + core_graphs.emplace_back(5 * b_column_size + extra_width, 1, "", core_data, graph_symbol); + } + } + 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) { + 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); + } + } + } } + try { + //? Cpu graphs, cpu clock and cpu meter + out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(cpu.cpu_percent.at(graph_up_field), (data_same or redraw)); + if (not single_graph) + out += Mv::to( y + graph_up_height + 1 + (mid_line ? 1 : 0), x + 1) + graph_lower(cpu.cpu_percent.at(graph_lo_field), (data_same or redraw)); 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()) + out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) + 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") + '%'; + 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; + } + out += Theme::c("div_line") + Symbols::v_line; - //! VALS + } catch (const std::exception& e) { throw std::runtime_error("graphs, clock, meter : " + (string)e.what()); } - 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); } + //? Core text and graphs + int cx = 0, cy = 1, cc = 0; + for (const auto& n : iota(0, Shared::coreCount)) { + out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "") + + 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); + 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 += Mv::to(y + 6, x + 5); - for (const auto& [name, value] : cpu.cpu_percent) { out += ljust(name + "=" + to_string(value.back()), 12); } + 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; + } + out += Theme::c("div_line") + Symbols::v_line; + + if (++cy > ceil((double)Shared::coreCount / b_columns) and n != Shared::coreCount - 1) { + if (++cc >= b_columns) break; + cy = 1; cx = (b_width / b_columns) * cc; + } + } + + //? Load average + if (cy < b_height - 1 and cc <= b_columns) { + string lavg_pre; + int sep = 1; + if (b_column_size == 2 and got_sensors) { lavg_pre = "Load AVG:"; sep = 3; } + else if (b_column_size == 2 or (b_column_size == 1 and got_sensors)) { lavg_pre = "LAV:"; } + else if (b_column_size == 1 or (b_column_size == 0 and got_sensors)) { lavg_pre = "L"; } + string lavg; + for (const auto& val : cpu.load_avg) { + lavg += string(sep, ' ') + (lavg_pre.size() < 3 ? to_string((int)round(val)) : to_string(val).substr(0, 4)); + } + out += Mv::to(b_y + b_height - 2, b_x + cx + 1) + Theme::c("main_fg") + lavg_pre + lavg; + } + + //? Uptime + if (Config::getB("show_uptime")) { + string upstr = sec_to_dhms(system_uptime()); + if (upstr.size() > 8) { + upstr.resize(upstr.size() - 3); + upstr = trans(upstr); + } + out += Mv::to(y + (single_graph or not Config::getB("cpu_invert_lower") ? 1 : height - 2), x + 2) + + Theme::c("graph_text") + "up" + Mv::r(1) + upstr; + } redraw = false; return out; @@ -425,6 +539,7 @@ namespace Mem { string box; 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); @@ -447,6 +562,7 @@ namespace Net { string box; string draw(const net_info& net, const bool force_redraw, const bool data_same) { + if (Runner::stopping) return ""; (void)net; (void)data_same; string out = Mv::to(0, 0); @@ -537,17 +653,17 @@ namespace Proc { } string draw(const vector& plist, const bool force_redraw, const bool data_same) { + if (Runner::stopping) return ""; 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("lowcolor") and Theme::gradients.contains("proc")); auto& proc_colors = Config::getB("proc_colors"); - 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"); + 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& mem_bytes = Config::getB("proc_mem_bytes"); start = Config::getI("proc_start"); selected = Config::getI("proc_selected"); - uint64_t total_mem = 16328872 << 10; const int y = show_detailed ? Proc::y + 8 : Proc::y; const int height = show_detailed ? Proc::height - 8 : Proc::height; const int select_max = show_detailed ? Proc::select_max - 8 : Proc::select_max; @@ -584,7 +700,7 @@ namespace Proc { //? Create cpu and mem graphs if process is alive if (alive) { - detailed_cpu_graph = {dgraph_width - 1, 7, "cpu", detailed.cpu_percent, graph_symbol}; + detailed_cpu_graph = {dgraph_width - 1, 7, "cpu", detailed.cpu_percent, graph_symbol, false, true}; detailed_mem_graph = {d_width / 3, 1, "", detailed.mem_bytes, graph_symbol, false, false, detailed.first_mem}; } @@ -646,7 +762,7 @@ namespace Proc { } //? Filter - const auto& filtering = Config::getB("proc_filtering"); // ? filter(20) : Config::getS("proc_filter")) + auto& filtering = Config::getB("proc_filtering"); // ? filter(20) : Config::getS("proc_filter")) const auto filter_text = (filtering) ? filter(max(6, width - 58)) : uresize(Config::getS("proc_filter"), max(6, width - 58)); out += Mv::to(y, x+9) + title_left + (not filter_text.empty() ? Fx::b : "") + Theme::c("hi_fg") + 'f' + Theme::c("title") + (not filter_text.empty() ? ' ' + filter_text : "ilter") @@ -735,7 +851,7 @@ 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 + 1); + cpu_str += '%' + Mv::r(1) + (dgraph_width < 20 ? "C" : "Core") + to_string(detailed.entry.cpu_n); } 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; @@ -812,7 +928,7 @@ namespace Proc { if (proc_colors) { end = Theme::c("main_fg") + Fx::ub; array colors; - for (int i = 0; int v : {(int)round(p.cpu_p), (int)round(p.mem * 100 / total_mem), (int)p.threads / 3}) { + 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]; @@ -892,7 +1008,7 @@ namespace Proc { + 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) { + if (not data_same and ++counter >= 100) { counter = 0; for (auto element = p_graphs.begin(); element != p_graphs.end();) { if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index 0c5d2be..094f751 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -44,11 +44,12 @@ namespace Draw { //* Class holding a percentage meter class Meter { - const int width; - const string color_gradient; - const bool invert; + int width; + string color_gradient; + bool invert; vector cache; public: + Meter(); Meter(const int width, const string& color_gradient, const bool invert = false); //* Return a string representation of the meter with given value diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 28e97fb..bf1ad75 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -17,6 +17,7 @@ tab-size = 4 */ #include +#include #include #include @@ -28,6 +29,7 @@ tab-size = 4 using std::cin, std::string_literals::operator""s; using namespace Tools; +namespace rng = std::ranges; namespace Input { namespace { @@ -73,7 +75,7 @@ namespace Input { array mouse_pos; unordered_flat_map mouse_mappings; - string last = ""; + deque history(50, ""); string old_filter; bool poll(int timeout) { @@ -120,9 +122,8 @@ namespace Input { key.clear(); if (Config::getB("proc_filtering")) { - if (mouse_event == "mouse_click") last = mouse_event; - else last.clear(); - return last; + if (mouse_event == "mouse_click") return mouse_event; + else return ""; } //? Get column and line position of mouse and check for any actions mapped to current position @@ -155,14 +156,14 @@ namespace Input { else if (ulen(key) > 1) key.clear(); - last = key; + history.push_back(key); + history.pop_front(); } return key; } string wait() { while (cin.rdbuf()->in_avail() < 1) { - // if (interrupt) { interrupt = false; return ""; } sleep_ms(10); } return get(); @@ -170,7 +171,7 @@ namespace Input { void clear() { if (cin.rdbuf()->in_avail() > 0) cin.ignore(SSmax); - last.clear(); + history.clear(); } void process(const string& key) { @@ -178,8 +179,6 @@ namespace Input { try { auto& filtering = Config::getB("proc_filtering"); if (not filtering and key == "q") clean_quit(0); - bool no_update = true; - bool redraw = true; //? Global input actions if (not filtering) { @@ -198,6 +197,8 @@ namespace Input { //? Input actions for proc box if (Proc::shown) { bool keep_going = false; + bool no_update = true; + bool redraw = true; if (filtering) { if (key == "enter") { Config::set("proc_filter", Proc::filter.text); @@ -341,6 +342,37 @@ namespace Input { return; } } + + //? Input actions for proc box + if (Cpu::shown) { + bool keep_going = false; + bool no_update = true; + bool redraw = true; + static uint64_t last_press = 0; + + if (key == "+" and Config::getI("update_ms") <= 86399900) { + int add = (Config::getI("update_ms") <= 86399000 and last_press >= time_ms() - 200 + and rng::all_of(Input::history, [](const auto& str){ return str == "+"; }) + ? 1000 : 100); + Config::set("update_ms", Config::getI("update_ms") + add); + last_press = time_ms(); + redraw = true; + } + else if (key == "-" and Config::getI("update_ms") >= 200) { + int sub = (Config::getI("update_ms") >= 2000 and last_press >= time_ms() - 200 + and rng::all_of(Input::history, [](const auto& str){ return str == "-"; }) + ? 1000 : 100); + Config::set("update_ms", Config::getI("update_ms") - sub); + last_press = time_ms(); + redraw = true; + } + else keep_going = true; + + if (not keep_going) { + Runner::run("cpu", no_update, redraw); + return; + } + } } diff --git a/src/btop_input.hpp b/src/btop_input.hpp index ed7521a..fdcf6e2 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -22,8 +22,9 @@ tab-size = 4 #include #include #include +#include -using robin_hood::unordered_flat_map, std::array, std::string, std::atomic; +using robin_hood::unordered_flat_map, std::array, std::string, std::atomic, std::deque; /* The input functions relies on the following std::cin options being set: cin.sync_with_stdio(false); cin.tie(NULL); @@ -46,7 +47,7 @@ namespace Input { extern array mouse_pos; //* Last entered key - extern string last; + extern deque history; //* Poll keyboard & mouse input for ms and return input availabilty as a bool bool poll(int timeout=0); diff --git a/src/btop_linux.cpp b/src/btop_linux.cpp index 76e486a..ebe31c0 100644 --- a/src/btop_linux.cpp +++ b/src/btop_linux.cpp @@ -48,20 +48,45 @@ namespace Tools { } namespace Cpu { - vector core_old_totals; - vector core_old_idles; + vector core_old_totals; + vector core_old_idles; vector available_fields; cpu_info current_cpu; + fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; + bool got_sensors = false, cpu_temp_only = false; + + //* Populate found_sensors map + bool get_sensors(); + + //* Get current cpu clock speed + string get_cpuHz(); + + //* Search /proc/cpuinfo for a cpu name + string get_cpuName(); + + //* Parse /proc/cpu info for mapping of core ids + unordered_flat_map get_core_mapping(); + + struct Sensor { + fs::path path; + string label; + int64_t temp = 0; + int64_t high = 0; + int64_t crit = 0; + }; + + unordered_flat_map found_sensors; + string cpu_sensor; + vector core_sensors; + unordered_flat_map core_mapping; } namespace Shared { - fs::path procPath; - fs::path passwd_path; + fs::path procPath, passwd_path; fs::file_time_type passwd_time; uint64_t totalMem; long pageSize, clkTck, coreCount; - string cpuName; void init() { @@ -102,6 +127,7 @@ namespace Shared { throw std::runtime_error("Could not get total memory size from /proc/meminfo"); //? Init for namespace Cpu + if (not fs::exists(Cpu::freq_path) or access(Cpu::freq_path.c_str(), R_OK) == -1) Cpu::freq_path.clear(); 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); @@ -111,18 +137,21 @@ namespace Shared { if (not vec.empty()) Cpu::available_fields.push_back(field); } Cpu::cpuName = Cpu::get_cpuName(); + Cpu::got_sensors = Cpu::get_sensors(); + Cpu::core_mapping = Cpu::get_core_mapping(); + + } } namespace Cpu { - bool got_sensors = false; string cpuName; string cpuHz; const array time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"}; - unordered_flat_map cpu_old = { + unordered_flat_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -148,19 +177,19 @@ namespace Cpu { 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")) { + if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) { 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")) { + else if (v_contains(name_vec, "Ryzen"s)) { 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")) { + else if (s_contains(name, "Intel"s) and v_contains(name_vec, "CPU"s)) { 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); @@ -187,56 +216,254 @@ namespace Cpu { return name; } - string get_cpuHz() { - static bool failed = false; - if (failed) return ""; - string cpuhz; + bool get_sensors() { + bool got_cpu = false, got_coretemp = false; + vector search_paths; 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(""); + //? Setup up paths to search for sensors + if (fs::exists(fs::path("/sys/class/hwmon")) and access("/sys/class/hwmon", R_OK) != -1) { + for (const auto& dir : fs::directory_iterator(fs::path("/sys/class/hwmon"))) { + fs::path add_path = fs::canonical(dir.path()); + if (v_contains(search_paths, add_path) or v_contains(search_paths, add_path / "device")) continue; - int hz_int = round(std::stod(instr)); + if (s_contains(add_path, "coretemp")) + got_coretemp = true; - 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"; + if (fs::exists(add_path / "temp1_input")) { + search_paths.push_back(add_path); + } + else if (fs::exists(add_path / "device/temp1_input")) + search_paths.push_back(add_path / "device"); } - else if (hz_int > 0) - cpuhz = to_string(hz_int) + " MHz"; + } + if (not got_coretemp and fs::exists(fs::path("/sys/devices/platform/coretemp.0/hwmon"))) { + for (auto& d : fs::directory_iterator(fs::path("/sys/devices/platform/coretemp.0/hwmon"))) { + fs::path add_path = fs::canonical(d.path()); + + if (fs::exists(d.path() / "temp1_input") and not v_contains(search_paths, add_path)) { + search_paths.push_back(add_path); + got_coretemp = true; + } + } + } + //? Scan any found directories for temperature sensors + if (not search_paths.empty()) { + for (const auto& path : search_paths) { + const string pname = readfile(path / "name", path.filename()); + for (int i = 1; fs::exists(path / string("temp" + to_string(i) + "_input")); i++) { + const string basepath = path / string("temp" + to_string(i) + "_"); + const string label = readfile(fs::path(basepath + "label"), "temp" + to_string(i)); + const string sensor_name = pname + "/" + label; + const int64_t temp = stol(readfile(fs::path(basepath + "input"), "0")) / 1000; + const int64_t high = stol(readfile(fs::path(basepath + "max"), "80000")) / 1000; + const int64_t crit = stol(readfile(fs::path(basepath + "crit"), "95000")) / 1000; + + found_sensors[sensor_name] = {fs::path(basepath + "input"), label, temp, high, crit}; + + if (not got_cpu and (label.starts_with("Package id") or label.starts_with("Tdie"))) { + got_cpu = true; + cpu_sensor = sensor_name; + } + else if (label.starts_with("Core") or label.starts_with("Tccd")) { + got_coretemp = true; + if (not v_contains(core_sensors, sensor_name)) core_sensors.push_back(sensor_name); + } + } + } + } + //? If no good candidate for cpu temp has been found scan /sys/class/thermal + if (not got_cpu and fs::exists(fs::path("/sys/class/thermal"))) { + const string rootpath = fs::path("/sys/class/thermal/thermal_zone"); + for (int i = 0; fs::exists(fs::path(rootpath + to_string(i))); i++) { + const fs::path basepath = rootpath + to_string(i); + if (not fs::exists(basepath / "temp")) continue; + const string label = readfile(basepath / "type", "temp" + to_string(i)); + const string sensor_name = "thermal" + to_string(i) + "/" + label; + const int64_t temp = stol(readfile(basepath / "temp", "0")) / 1000; + + int64_t high, crit; + for (int ii = 0; fs::exists(basepath / string("trip_point_" + to_string(ii) + "_temp")); ii++) { + const string trip_type = readfile(basepath / string("trip_point_" + to_string(ii) + "_type")); + if (not is_in(trip_type, "high", "critical")) continue; + auto& val = (trip_type == "high" ? high : crit); + val = stol(readfile(basepath / string("trip_point_" + to_string(ii) + "_temp"), "0")) / 1000; + } + if (high < 1) high = 80; + if (crit < 1) crit = 95; + + found_sensors[sensor_name] = {basepath / "temp", label, temp, high, crit}; + } + } + + } + catch (...) {} + + if (not got_coretemp) cpu_temp_only = true; + if (cpu_sensor.empty() and not found_sensors.empty()) { + for (const auto& [name, sensor] : found_sensors) { + if (s_contains(str_to_lower(name), "cpu")) { + cpu_sensor = name; + break; + } + } + if (cpu_sensor.empty()) { + cpu_sensor = found_sensors.begin()->first; + Logger::warning("No good candidate for cpu sensor found, using random from all found sensors."); } } - catch (...) { - failed = true; - Logger::warning("Failed to get cpu clock speed from /proc/cpuinfo."); - cpuhz.clear(); + + return not found_sensors.empty(); + } + + void update_sensors() { + if (cpu_sensor.empty()) return; + + const auto& cpu_sensor = (not Config::getS("cpu_sensor").empty() and found_sensors.contains(Config::getS("cpu_sensor")) ? Config::getS("cpu_sensor") : Cpu::cpu_sensor); + + found_sensors.at(cpu_sensor).temp = stol(readfile(found_sensors.at(cpu_sensor).path, "0")) / 1000; + current_cpu.temp.at(0).push_back(found_sensors.at(cpu_sensor).temp); + current_cpu.temp_max = found_sensors.at(cpu_sensor).crit; + if (current_cpu.temp.at(0).size() > 20) current_cpu.temp.at(0).pop_front(); + + if (Config::getB("show_coretemp") and not cpu_temp_only) { + vector done; + for (const auto& sensor : core_sensors) { + if (v_contains(done, sensor)) continue; + found_sensors.at(sensor).temp = stol(readfile(found_sensors.at(sensor).path, "0")) / 1000; + done.push_back(sensor); + } + for (const auto& [core, temp] : core_mapping) { + if ((size_t)core + 1 < current_cpu.temp.size() and (size_t)temp < core_sensors.size()) { + current_cpu.temp.at(core + 1).push_back(found_sensors.at(core_sensors.at(temp)).temp); + if (current_cpu.temp.at(core + 1).size() > 20) current_cpu.temp.at(core + 1).pop_front(); + } + } + } + } + + string get_cpuHz() { + static int failed = 0; + if (failed > 4) return ""s; + string cpuhz; + try { + double hz = 0.0; + //? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster) + if (not freq_path.empty()) { + hz = stod(readfile(freq_path, "0.0")) / 1000; + if (hz <= 0.0 and ++failed >= 2) + freq_path.clear(); + } + //? If freq from /sys failed or is missing try to use /proc/cpuinfo + if (hz <= 0.0) { + ifstream cpufreq(Shared::procPath / "cpuinfo"); + if (cpufreq.good()) { + while (cpufreq.ignore(SSmax, '\n')) { + if (cpufreq.peek() == 'c') { + cpufreq.ignore(SSmax, ' '); + if (cpufreq.peek() == 'M') { + cpufreq.ignore(SSmax, ':'); + cpufreq.ignore(1); + cpufreq >> hz; + break; + } + } + } + } + } + + if (hz <= 1 or hz >= 1000000) throw std::runtime_error("Failed to read /sys/devices/system/cpu/cpufreq/policy and /proc/cpuinfo."); + + if (hz >= 1000) { + if (hz >= 10000) cpuhz = to_string((int)round(hz / 1000)); // Future proof until we reach THz speeds :) + else cpuhz = to_string(round(hz / 100) / 10.0).substr(0, 3); + cpuhz += " GHz"; + } + else if (hz > 0) + cpuhz = to_string((int)round(hz)) + " MHz"; + + } + catch (const std::exception& e) { + if (++failed < 5) return ""s; + else { + Logger::warning("get_cpuHZ() : " + (string)e.what()); + return ""s; + } } return cpuhz; } - cpu_info collect(const bool no_update) { - 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"); + unordered_flat_map get_core_mapping() { + unordered_flat_map core_map; + ifstream cpuinfo("/proc/cpuinfo"); + if (cpuinfo.good()) { + int cpu, core; + for (string instr; cpuinfo >> instr;) { + if (instr == "processor") { + cpuinfo.ignore(SSmax, ':'); + cpuinfo >> cpu; + } + else if (instr.starts_with("core")) { + cpuinfo.ignore(SSmax, ':'); + cpuinfo >> core; + core_map[cpu] = core; + } + cpuinfo.ignore(SSmax, '\n'); + } + } + + if (core_map.size() < (size_t)Shared::coreCount) { + if (Shared::coreCount % 2 == 0 and core_map.size() == (size_t)Shared::coreCount / 2) { + for (int i = 0; i < Shared::coreCount / 2; i++) + core_map[Shared::coreCount / 2 + i] = i; + } + else { + core_map.clear(); + for (int i = 0; i < Shared::coreCount; i++) + core_map[i] = i; + } + } + + const auto& custom_map = Config::getS("cpu_core_map"); + if (not custom_map.empty()) { + try { + for (const auto& split : ssplit(custom_map)) { + const auto vals = ssplit(split, ':'); + if (vals.size() != 2) continue; + 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; + } + } + catch (...) {} + } + + return core_map; + } + + cpu_info collect(const bool no_update) { + if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) return current_cpu; + auto& cpu = current_cpu; - 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++) { + try { + //? Get cpu load averages from /proc/loadavg + cread.open(Shared::procPath / "loadavg"); + if (cread.good()) { + cread >> cpu.load_avg[0] >> cpu.load_avg[1] >> cpu.load_avg[2]; + } + cread.close(); - //? 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 times; - uint64_t total_sum = 0; + //? Get cpu total times for all cores from /proc/stat + cread.open(Shared::procPath / "stat"); + for (int i = 0; cread.good() and cread.peek() == 'c'; i++) { + cread.ignore(SSmax, ' '); + + //? Expected 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 times; + long long total_sum = 0; for (uint64_t val; cread >> val; total_sum += val) { times.push_back(val); @@ -245,31 +472,31 @@ namespace Cpu { 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); + const long long totals = max(0ll, 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); + const long long idles = max(0ll, times.at(3) + (times.size() > 4 ? times.at(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; + const long long calc_totals = max(1ll, totals - cpu_old.at("totals")); + const long long calc_idles = max(1ll, idles - cpu_old.at("idles")); + cpu_old.at("totals") = totals; + cpu_old.at("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)); + 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 ((int)cpu.cpu_percent["total"].size() > Term::width * 2) cpu.cpu_percent["total"].pop_front(); + while (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) { - 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; + cpu.cpu_percent.at(time_names.at(ii)).push_back(clamp((long long)round((double)(val - cpu_old.at(time_names.at(ii))) * 100 / calc_totals), 0ll, 100ll)); + cpu_old.at(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(); + 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 (++ii == 10) break; } @@ -277,27 +504,31 @@ namespace Cpu { //? 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; + const long long calc_totals = max(0ll, totals - core_old_totals.at(i-1)); + const long long calc_idles = max(0ll, idles - core_old_idles.at(i-1)); + core_old_totals.at(i-1) = totals; + core_old_idles.at(i-1) = idles; - cpu.core_percent[i-1].push_back(clamp((uint64_t)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ul, 100ul)); + cpu.core_percent.at(i-1).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 - if ((int)cpu.core_percent[i-1].size() > 20) cpu.core_percent[i-1].pop_front(); + if (cpu.core_percent.at(i-1).size() > 40) cpu.core_percent.at(i-1).pop_front(); } } - cread.close(); } - else { - throw std::runtime_error("Failed to read /proc/stat"); + catch (const std::exception& e) { + Logger::debug("get_cpuHz() : " + (string)e.what()); + if (cread.bad()) throw std::runtime_error("Failed to read /proc/stat"); + else throw std::runtime_error("collect() : " + (string)e.what()); } if (Config::getB("show_cpu_freq")) cpuHz = get_cpuHz(); + if (Config::getB("check_temp") and got_sensors) + update_sensors(); + return cpu; } } @@ -455,27 +686,25 @@ namespace Proc { detailed.memory.clear(); if (not detailed.skip_smaps and fs::exists(pid_path / "smaps")) { d_read.open(pid_path / "smaps"); - if (d_read.good()) { - uint64_t rss = 0; - try { - while (not d_read.eof()) { - d_read.ignore(SSmax, 'R'); - if (d_read.peek() == 's') { - d_read.ignore(SSmax, ':'); - getline(d_read, short_str, 'k'); - rss += stoull(short_str); - } - } - if (rss == detailed.entry.mem >> 10) - detailed.skip_smaps = true; - else { - detailed.mem_bytes.push_back(rss << 10); - detailed.memory = floating_humanizer(rss, false, 1); + uint64_t rss = 0; + try { + while (d_read.good()) { + d_read.ignore(SSmax, 'R'); + if (d_read.peek() == 's') { + d_read.ignore(SSmax, ':'); + getline(d_read, short_str, 'k'); + rss += stoull(short_str); } } - catch (const std::invalid_argument&) {} - catch (const std::out_of_range&) {} + if (rss == detailed.entry.mem >> 10) + detailed.skip_smaps = true; + else { + detailed.mem_bytes.push_back(rss << 10); + detailed.memory = floating_humanizer(rss, false, 1); + } } + catch (const std::invalid_argument&) {} + catch (const std::out_of_range&) {} d_read.close(); } if (detailed.memory.empty()) { @@ -492,27 +721,25 @@ namespace Proc { //? Get bytes read and written from proc/[pid]/io if (fs::exists(pid_path / "io")) { d_read.open(pid_path / "io"); - if (d_read.good()) { - try { - string name; - while (not d_read.eof()) { - getline(d_read, name, ':'); - if (name.ends_with("read_bytes")) { - getline(d_read, short_str); - detailed.io_read = floating_humanizer(stoull(short_str)); - } - else if (name.ends_with("write_bytes")) { - getline(d_read, short_str); - detailed.io_write = floating_humanizer(stoull(short_str)); - break; - } - else - d_read.ignore(SSmax, '\n'); + try { + string name; + while (d_read.good()) { + getline(d_read, name, ':'); + if (name.ends_with("read_bytes")) { + getline(d_read, short_str); + detailed.io_read = floating_humanizer(stoull(short_str)); } + else if (name.ends_with("write_bytes")) { + getline(d_read, short_str); + detailed.io_write = floating_humanizer(stoull(short_str)); + break; + } + else + d_read.ignore(SSmax, '\n'); } - catch (const std::invalid_argument&) {} - catch (const std::out_of_range&) {} } + catch (const std::invalid_argument&) {} + catch (const std::out_of_range&) {} d_read.close(); } } @@ -571,7 +798,7 @@ namespace Proc { //* Iterate over all pids in /proc for (const auto& d: fs::directory_iterator(Shared::procPath)) { if (Runner::stopping) - return current_procs; + return procs; if (pread.is_open()) pread.close(); const string pid_str = d.path().filename(); @@ -629,8 +856,8 @@ namespace Proc { size_t x = 0, next_x = 3; uint64_t cpu_t = 0; try { - for (;;) { - while (++x - offset < next_x) { + while (pread.good()) { + while (pread.good() and ++x - offset < next_x) { pread.ignore(SSmax, ' '); } @@ -696,10 +923,10 @@ 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) / (cputimes - old_cputimes)) / 10.0; + new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[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 / ((uptime * Shared::clkTck) - cache[new_proc.pid].cpu_s); + new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - cache[new_proc.pid].cpu_s); //? Update cache with latest cpu times cache[new_proc.pid].cpu_t = cpu_t; @@ -714,7 +941,7 @@ namespace Proc { } //* Clear dead processes from cache at a regular interval - if (++counter >= 10000 or (cache.size() > procs.size() + 100)) { + if (++counter >= 1000 or (cache.size() > procs.size() + 100)) { counter = 0; unordered_flat_map r_cache; r_cache.reserve(procs.size()); diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 10ed8d5..c541e93 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -34,6 +34,7 @@ void banner_gen(); namespace Global { extern const string Version; + extern atomic quitting; extern string exit_error_msg; extern atomic thread_exception; extern string banner; @@ -70,7 +71,7 @@ namespace Shared { namespace Cpu { extern string box; extern int x, y, width, height; - extern bool shown, redraw, got_sensors; + extern bool shown, redraw, got_sensors, cpu_temp_only; extern string cpuName, cpuHz; struct cpu_info { @@ -89,6 +90,7 @@ namespace Cpu { }; vector> core_percent; vector> temp; + long long temp_max = 0; array load_avg; }; @@ -97,12 +99,6 @@ namespace Cpu { //* Draw contents of cpu box using 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 { diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 8e19b97..4e2b1f3 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -115,6 +115,8 @@ namespace Term { namespace Tools { + atomic active_locks (0); + 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++) { @@ -264,7 +266,9 @@ namespace Tools { return out; } - std::string operator*(const string& str, size_t n) { + std::string operator*(const string& str, int64_t n) { + if (n < 1 or str.empty()) return ""; + else if(n == 1) return str; string new_str; new_str.reserve(str.size() * n); for (; n > 0; n--) new_str.append(str); @@ -280,14 +284,41 @@ namespace Tools { } atomic_lock::atomic_lock(atomic& atom) : atom(atom) { + active_locks++; while (not this->atom.compare_exchange_strong(this->not_true, true)); } atomic_lock::~atomic_lock() { + active_locks--; this->atom.store(false); atomic_notify(this->atom); } + string readfile(const std::filesystem::path& path, const string& fallback) { + if (not fs::exists(path)) return fallback; + string out; + try { + std::ifstream file(path); + for (string readstr; getline(file, readstr); out += readstr); + } + catch (const std::exception& e) { + throw std::runtime_error("Exception when reading " + (string)path + " : " + e.what()); + } + return (out.empty() ? fallback : out); + } + + tuple celsius_to(long long celsius, string scale) { + if (scale == "celsius") + return {celsius, "°C"}; + else if (scale == "fahrenheit") + return {(long long)round((double)celsius * 1.8 + 32), "°F"}; + else if (scale == "kelvin") + return {(long long)round((double)celsius + 273.15), "K "}; + else if (scale == "rankine") + return {(long long)round((double)celsius * 1.8 + 491.67), "°R"}; + return {0, ""}; + } + } namespace Logger { diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index e684f11..3c38752 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -26,9 +26,10 @@ tab-size = 4 #include #include #include +#include -using std::string, std::vector, std::atomic, std::to_string, std::regex; +using std::string, std::vector, std::atomic, std::to_string, std::regex, std::tuple; //? ------------------------------------------------- NAMESPACES ------------------------------------------------------ @@ -124,6 +125,7 @@ namespace Term { namespace Tools { constexpr auto SSmax = std::numeric_limits::max(); + extern atomic active_locks; //* 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) { @@ -242,7 +244,7 @@ namespace Tools { string floating_humanizer(uint64_t value, const bool shorten=false, size_t start=0, const bool bit=false, const bool per_second=false); //* Add std::string operator * : Repeat string number of times - std::string operator*(const string& str, size_t n); + std::string operator*(const string& str, int64_t n); //* Return current time in format string strf_time(const string& strf); @@ -264,6 +266,11 @@ namespace Tools { ~atomic_lock(); }; + //* Read a complete file and return as a string + string readfile(const std::filesystem::path& path, const string& fallback=""); + + //* Convert a celsius value to celsius, fahrenheit, kelvin or rankin and return tuple with new value and unit. + tuple celsius_to(long long celsius, string scale); }