Added cpu temperature functionality

This commit is contained in:
aristocratos 2021-08-03 23:47:46 +02:00
parent e33b4b7b0c
commit 102ed6179e
11 changed files with 619 additions and 172 deletions

View file

@ -73,7 +73,7 @@ uninstall:
#Link
btop: $(OBJECTS)
$(CXX) -o $(TARGETDIR)/btop $^ -pthread
$(CXX) -o $(TARGETDIR)/btop $^ $(LINKFLAGS)
#Compile
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)

View file

@ -17,7 +17,7 @@ tab-size = 4
*/
#include <csignal>
#include <thread>
#include <pthread.h>
#include <numeric>
#include <ranges>
#include <unistd.h>
@ -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<string> 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<string> 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

View file

@ -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", ""},

View file

@ -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<Draw::Graph> core_graphs;
vector<Draw::Graph> 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<proc_info>& 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<string, 3> 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()) {

View file

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

View file

@ -17,6 +17,7 @@ tab-size = 4
*/
#include <iostream>
#include <ranges>
#include <btop_input.hpp>
#include <btop_tools.hpp>
@ -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<int, 2> mouse_pos;
unordered_flat_map<string, Mouse_loc> mouse_mappings;
string last = "";
deque<string> 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;
}
}
}

View file

@ -22,8 +22,9 @@ tab-size = 4
#include <atomic>
#include <array>
#include <robin_hood.h>
#include <deque>
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<int, 2> mouse_pos;
//* Last entered key
extern string last;
extern deque<string> history;
//* Poll keyboard & mouse input for <timeout> ms and return input availabilty as a bool
bool poll(int timeout=0);

View file

@ -48,20 +48,45 @@ namespace Tools {
}
namespace Cpu {
vector<uint64_t> core_old_totals;
vector<uint64_t> core_old_idles;
vector<long long> core_old_totals;
vector<long long> core_old_idles;
vector<string> 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<int, int> 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<string, Sensor> found_sensors;
string cpu_sensor;
vector<string> core_sensors;
unordered_flat_map<int, int> 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<string, 10> time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"};
unordered_flat_map<string, uint64_t> cpu_old = {
unordered_flat_map<string, long long> 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<fs::path> 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<string> 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<int, int> get_core_mapping() {
unordered_flat_map<int, int> 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<uint64_t> 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<long long> 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<size_t, p_cache> r_cache;
r_cache.reserve(procs.size());

View file

@ -34,6 +34,7 @@ void banner_gen();
namespace Global {
extern const string Version;
extern atomic<bool> quitting;
extern string exit_error_msg;
extern atomic<bool> 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<deque<long long>> core_percent;
vector<deque<long long>> temp;
long long temp_max = 0;
array<float, 3> load_avg;
};
@ -97,12 +99,6 @@ namespace Cpu {
//* Draw contents of cpu box using <cpu> as source
string draw(const cpu_info& cpu, const bool force_redraw=false, const bool data_same=false);
//* Try to get name of cpu
string get_cpuName();
//* Try to get current cpu clock speed
string get_cpuHz();
}
namespace Mem {

View file

@ -115,6 +115,8 @@ namespace Term {
namespace Tools {
atomic<int> 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<bool>& 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<long long, string> 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 {

View file

@ -26,9 +26,10 @@ tab-size = 4
#include <ranges>
#include <chrono>
#include <thread>
#include <tuple>
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<std::streamsize>::max();
extern atomic<int> 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 <str> <n> 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 <strf> 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<long long, string> celsius_to(long long celsius, string scale);
}