mirror of
https://github.com/aristocratos/btop.git
synced 2024-09-28 06:11:28 +02:00
Added cpu temperature functionality
This commit is contained in:
parent
e33b4b7b0c
commit
102ed6179e
2
Makefile
2
Makefile
@ -73,7 +73,7 @@ uninstall:
|
||||
|
||||
#Link
|
||||
btop: $(OBJECTS)
|
||||
$(CXX) -o $(TARGETDIR)/btop $^ -pthread
|
||||
$(CXX) -o $(TARGETDIR)/btop $^ $(LINKFLAGS)
|
||||
|
||||
#Compile
|
||||
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
|
||||
|
67
src/btop.cpp
67
src/btop.cpp
@ -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
|
||||
|
@ -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", ""},
|
||||
|
@ -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);
|
||||
out += Mv::to(button_y, x + 10) + title_left + Theme::c("hi_fg") + Fx::b + 'm' + Theme::c("title") + "enu" + Fx::ub + 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()) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
bool get_sensors() {
|
||||
bool got_cpu = false, got_coretemp = false;
|
||||
vector<fs::path> search_paths;
|
||||
try {
|
||||
//? 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;
|
||||
|
||||
if (s_contains(add_path, "coretemp"))
|
||||
got_coretemp = true;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
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 bool failed = false;
|
||||
if (failed) return "";
|
||||
static int failed = 0;
|
||||
if (failed > 4) return ""s;
|
||||
string cpuhz;
|
||||
try {
|
||||
ifstream cpuinfo(Shared::procPath / "cpuinfo");
|
||||
if (cpuinfo.good()) {
|
||||
string instr;
|
||||
while (getline(cpuinfo, instr, ':') and not instr.starts_with("cpu MHz"))
|
||||
cpuinfo.ignore(SSmax, '\n');
|
||||
cpuinfo.ignore(1);
|
||||
getline(cpuinfo, instr);
|
||||
if (instr.empty()) throw std::runtime_error("");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int hz_int = round(std::stod(instr));
|
||||
if (hz <= 1 or hz >= 1000000) throw std::runtime_error("Failed to read /sys/devices/system/cpu/cpufreq/policy and /proc/cpuinfo.");
|
||||
|
||||
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);
|
||||
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_int > 0)
|
||||
cpuhz = to_string(hz_int) + " MHz";
|
||||
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;
|
||||
}
|
||||
catch (...) {
|
||||
failed = true;
|
||||
Logger::warning("Failed to get cpu clock speed from /proc/cpuinfo.");
|
||||
cpuhz.clear();
|
||||
}
|
||||
|
||||
return cpuhz;
|
||||
}
|
||||
|
||||
cpu_info collect(const bool no_update) {
|
||||
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;
|
||||
|
||||
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();
|
||||
|
||||
//? 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++) {
|
||||
for (int i = 0; cread.good() and cread.peek() == 'c'; i++) {
|
||||
cread.ignore(SSmax, ' ');
|
||||
|
||||
//? 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;
|
||||
//? 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,10 +686,9 @@ 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()) {
|
||||
while (d_read.good()) {
|
||||
d_read.ignore(SSmax, 'R');
|
||||
if (d_read.peek() == 's') {
|
||||
d_read.ignore(SSmax, ':');
|
||||
@ -475,7 +705,6 @@ namespace Proc {
|
||||
}
|
||||
catch (const std::invalid_argument&) {}
|
||||
catch (const std::out_of_range&) {}
|
||||
}
|
||||
d_read.close();
|
||||
}
|
||||
if (detailed.memory.empty()) {
|
||||
@ -492,10 +721,9 @@ 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()) {
|
||||
while (d_read.good()) {
|
||||
getline(d_read, name, ':');
|
||||
if (name.ends_with("read_bytes")) {
|
||||
getline(d_read, short_str);
|
||||
@ -512,7 +740,6 @@ namespace Proc {
|
||||
}
|
||||
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());
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user