mirror of https://github.com/aristocratos/btop.git
Added cpu temperature functionality
This commit is contained in:
parent
e33b4b7b0c
commit
102ed6179e
2
Makefile
2
Makefile
|
@ -73,7 +73,7 @@ uninstall:
|
||||||
|
|
||||||
#Link
|
#Link
|
||||||
btop: $(OBJECTS)
|
btop: $(OBJECTS)
|
||||||
$(CXX) -o $(TARGETDIR)/btop $^ -pthread
|
$(CXX) -o $(TARGETDIR)/btop $^ $(LINKFLAGS)
|
||||||
|
|
||||||
#Compile
|
#Compile
|
||||||
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
|
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
|
||||||
|
|
67
src/btop.cpp
67
src/btop.cpp
|
@ -17,7 +17,7 @@ tab-size = 4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <thread>
|
#include <pthread.h>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -169,6 +169,11 @@ void clean_quit(const int sig) {
|
||||||
Config::write();
|
Config::write();
|
||||||
Input::clear();
|
Input::clear();
|
||||||
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
|
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);
|
if (sig != -1) exit(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,35 +258,47 @@ namespace Runner {
|
||||||
string output;
|
string output;
|
||||||
string overlay;
|
string overlay;
|
||||||
string clock;
|
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
|
//* 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);
|
atomic_lock lck(active);
|
||||||
auto timestamp = time_micros();
|
auto timestamp = time_micros();
|
||||||
output.clear();
|
output.clear();
|
||||||
|
const auto& conf = current_conf;
|
||||||
|
|
||||||
for (const auto& box : boxes) {
|
for (const auto& box : conf.boxes) {
|
||||||
if (stopping) break;
|
if (stopping) break;
|
||||||
try {
|
try {
|
||||||
if (box == "cpu") {
|
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") {
|
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") {
|
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") {
|
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) {
|
catch (const std::exception& e) {
|
||||||
string fname = box;
|
string fname = box;
|
||||||
fname[0] = toupper(fname[0]);
|
fname[0] = toupper(fname[0]);
|
||||||
Global::exit_error_msg = "Exception in runner thread -> "
|
Global::exit_error_msg = "Exception in runner thread -> "
|
||||||
+ fname + "::draw(" + fname + "::collect(no_update=" + (no_update ? "true" : "false")
|
+ fname + "::draw(" + fname + "::collect(no_update=" + (conf.no_update ? "true" : "false")
|
||||||
+ "), force_redraw=" + (force_redraw ? "true" : "false") + ") : " + (string)e.what();
|
+ "), force_redraw=" + (conf.force_redraw ? "true" : "false") + ") : " + (string)e.what();
|
||||||
Global::thread_exception = true;
|
Global::thread_exception = true;
|
||||||
Input::interrupt = true;
|
Input::interrupt = true;
|
||||||
stopping = true;
|
stopping = true;
|
||||||
|
@ -290,20 +307,20 @@ namespace Runner {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopping) {
|
if (stopping) {
|
||||||
return;
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boxes.empty()) {
|
if (conf.boxes.empty()) {
|
||||||
output = Term::clear + Mv::to(10, 10) + "No boxes shown!";
|
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;
|
cout << Term::sync_start << (overlay.empty() ? output + clock : Theme::c("inactive_fg") + Fx::uncolor(output + clock) + overlay) << Term::sync_end << flush;
|
||||||
|
|
||||||
//! DEBUG stats -->
|
//! DEBUG stats -->
|
||||||
cout << Fx::reset << Mv::to(1, 20) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
|
cout << Fx::reset << Mv::to(1, 20) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
|
||||||
|
|
||||||
// Input::interrupt = true;
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
//* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all": all boxes
|
//* 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())
|
else if (not clock.empty())
|
||||||
clock.clear();
|
clock.clear();
|
||||||
|
|
||||||
std::thread run_thread(_runner, (box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw);
|
current_conf = {(box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw};
|
||||||
run_thread.detach();
|
|
||||||
|
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
|
//? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize
|
||||||
std::atexit(_exit_handler);
|
std::atexit(_exit_handler);
|
||||||
std::at_quick_exit(_exit_handler);
|
|
||||||
std::signal(SIGINT, _signal_handler);
|
std::signal(SIGINT, _signal_handler);
|
||||||
std::signal(SIGTSTP, _signal_handler);
|
std::signal(SIGTSTP, _signal_handler);
|
||||||
std::signal(SIGCONT, _signal_handler);
|
std::signal(SIGCONT, _signal_handler);
|
||||||
std::signal(SIGWINCH, _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
|
//? Setup paths for config, log and user themes
|
||||||
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
|
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
|
//? Loop over input polling and input action processing
|
||||||
for (auto current_time = time_ms(); current_time < future_time and not Global::resized; current_time = time_ms()) {
|
for (auto current_time = time_ms(); current_time < future_time and not Global::resized; current_time = time_ms()) {
|
||||||
|
|
||||||
//? Check for external clock changes to avoid a timer bugs
|
//? Check for external clock changes and for changes to the update timer
|
||||||
if (future_time - current_time > update_ms)
|
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;
|
future_time = current_time;
|
||||||
|
|
||||||
//? Poll for input and process any input detected
|
//? 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."},
|
{"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"
|
{"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)."},
|
"#* 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_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."},
|
{"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."},
|
{"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\"."},
|
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
|
||||||
|
|
||||||
{"show_cpu_freq", "#* Show CPU frequency."},
|
{"show_cpu_freq", "#* Show CPU frequency."},
|
||||||
|
@ -173,6 +177,7 @@ namespace Config {
|
||||||
{"cpu_graph_upper", "total"},
|
{"cpu_graph_upper", "total"},
|
||||||
{"cpu_graph_lower", "total"},
|
{"cpu_graph_lower", "total"},
|
||||||
{"cpu_sensor", "Auto"},
|
{"cpu_sensor", "Auto"},
|
||||||
|
{"cpu_core_map", ""},
|
||||||
{"temp_scale", "celsius"},
|
{"temp_scale", "celsius"},
|
||||||
{"draw_clock", "%X"},
|
{"draw_clock", "%X"},
|
||||||
{"custom_cpu_name", ""},
|
{"custom_cpu_name", ""},
|
||||||
|
|
|
@ -241,6 +241,8 @@ namespace Draw {
|
||||||
}
|
}
|
||||||
|
|
||||||
//* Meter class ------------------------------------------------------------------------------------------------------------>
|
//* Meter class ------------------------------------------------------------------------------------------------------------>
|
||||||
|
Meter::Meter() {}
|
||||||
|
|
||||||
Meter::Meter(const int width, const string& color_gradient, const bool invert) : width(width), color_gradient(color_gradient), invert(invert) {
|
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, "");
|
cache.insert(cache.begin(), 101, "");
|
||||||
}
|
}
|
||||||
|
@ -292,9 +294,9 @@ namespace Draw {
|
||||||
for (const int& horizon : iota(0, height)) {
|
for (const int& horizon : iota(0, height)) {
|
||||||
const int cur_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100;
|
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 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
|
//? Calculate previous + current value to fit two values in 1 braille character
|
||||||
for (int ai = 0; const auto& value : {last, data_value}) {
|
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)
|
if (value >= cur_high)
|
||||||
result[ai++] = 4;
|
result[ai++] = 4;
|
||||||
else if (value <= cur_low)
|
else if (value <= cur_low)
|
||||||
|
@ -378,37 +380,149 @@ namespace Cpu {
|
||||||
int x = 1, y = 1, width, height;
|
int x = 1, y = 1, width, height;
|
||||||
int b_columns, b_column_size;
|
int b_columns, b_column_size;
|
||||||
int b_x, b_y, b_width, b_height;
|
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;
|
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) {
|
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;
|
string out;
|
||||||
out.reserve(width * height);
|
out.reserve(width * height);
|
||||||
|
//* Redraw elements not needed to be updated every cycle
|
||||||
if (redraw or force_redraw) {
|
if (redraw or force_redraw) {
|
||||||
auto& cpu_bottom = Config::getB("cpu_bottom");
|
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;
|
const int button_y = cpu_bottom ? y + height - 1 : y;
|
||||||
out += box;
|
out += box;
|
||||||
const string title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
|
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);
|
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())
|
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;
|
+ 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);
|
//? Core text and graphs
|
||||||
out += Mv::to(y + 4, x + 5);
|
int cx = 0, cy = 1, cc = 0;
|
||||||
for (int i = 0; const auto& cper : cpu.core_percent) { out += ljust("Core" + to_string(i++) + "=" + to_string(cper.back()), 10); }
|
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);
|
if (show_temps and not hide_cores) {
|
||||||
for (const auto& [name, value] : cpu.cpu_percent) { out += ljust(name + "=" + to_string(value.back()), 12); }
|
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;
|
redraw = false;
|
||||||
return out;
|
return out;
|
||||||
|
@ -425,6 +539,7 @@ namespace Mem {
|
||||||
string box;
|
string box;
|
||||||
|
|
||||||
string draw(const mem_info& mem, const bool force_redraw, const bool data_same) {
|
string draw(const mem_info& mem, const bool force_redraw, const bool data_same) {
|
||||||
|
if (Runner::stopping) return "";
|
||||||
(void)mem;
|
(void)mem;
|
||||||
(void)data_same;
|
(void)data_same;
|
||||||
string out = Mv::to(0, 0);
|
string out = Mv::to(0, 0);
|
||||||
|
@ -447,6 +562,7 @@ namespace Net {
|
||||||
string box;
|
string box;
|
||||||
|
|
||||||
string draw(const net_info& net, const bool force_redraw, const bool data_same) {
|
string draw(const net_info& net, const bool force_redraw, const bool data_same) {
|
||||||
|
if (Runner::stopping) return "";
|
||||||
(void)net;
|
(void)net;
|
||||||
(void)data_same;
|
(void)data_same;
|
||||||
string out = Mv::to(0, 0);
|
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) {
|
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");
|
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 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"));
|
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");
|
auto& proc_colors = Config::getB("proc_colors");
|
||||||
const auto& tty_mode = Config::getB("tty_mode");
|
auto& tty_mode = Config::getB("tty_mode");
|
||||||
const auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc"));
|
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];
|
auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up"))[1];
|
||||||
const auto& mem_bytes = Config::getB("proc_mem_bytes");
|
auto& mem_bytes = Config::getB("proc_mem_bytes");
|
||||||
start = Config::getI("proc_start");
|
start = Config::getI("proc_start");
|
||||||
selected = Config::getI("proc_selected");
|
selected = Config::getI("proc_selected");
|
||||||
uint64_t total_mem = 16328872 << 10;
|
|
||||||
const int y = show_detailed ? Proc::y + 8 : Proc::y;
|
const int y = show_detailed ? Proc::y + 8 : Proc::y;
|
||||||
const int height = show_detailed ? Proc::height - 8 : Proc::height;
|
const int height = show_detailed ? Proc::height - 8 : Proc::height;
|
||||||
const int select_max = show_detailed ? Proc::select_max - 8 : Proc::select_max;
|
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
|
//? Create cpu and mem graphs if process is alive
|
||||||
if (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};
|
detailed_mem_graph = {d_width / 3, 1, "", detailed.mem_bytes, graph_symbol, false, false, detailed.first_mem};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,7 +762,7 @@ namespace Proc {
|
||||||
}
|
}
|
||||||
|
|
||||||
//? Filter
|
//? 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));
|
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'
|
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")
|
+ 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) : "");
|
string cpu_str = (alive ? to_string(detailed.entry.cpu_p) : "");
|
||||||
if (alive) {
|
if (alive) {
|
||||||
cpu_str.resize((detailed.entry.cpu_p < 10 or detailed.entry.cpu_p >= 100 ? 3 : 4));
|
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))
|
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;
|
+ Mv::to(d_y + 1, dgraph_x + 1) + Theme::c("title") + Fx::b + cpu_str;
|
||||||
|
@ -812,7 +928,7 @@ namespace Proc {
|
||||||
if (proc_colors) {
|
if (proc_colors) {
|
||||||
end = Theme::c("main_fg") + Fx::ub;
|
end = Theme::c("main_fg") + Fx::ub;
|
||||||
array<string, 3> colors;
|
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) {
|
if (proc_gradient) {
|
||||||
int val = (min(v, 100) + 100) - calc * 100 / select_max;
|
int val = (min(v, 100) + 100) - calc * 100 / select_max;
|
||||||
if (val < 100) colors[i++] = Theme::g("proc_color")[val];
|
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;
|
+ 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
|
//? 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;
|
counter = 0;
|
||||||
for (auto element = p_graphs.begin(); element != p_graphs.end();) {
|
for (auto element = p_graphs.begin(); element != p_graphs.end();) {
|
||||||
if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) {
|
if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) {
|
||||||
|
|
|
@ -44,11 +44,12 @@ namespace Draw {
|
||||||
|
|
||||||
//* Class holding a percentage meter
|
//* Class holding a percentage meter
|
||||||
class Meter {
|
class Meter {
|
||||||
const int width;
|
int width;
|
||||||
const string color_gradient;
|
string color_gradient;
|
||||||
const bool invert;
|
bool invert;
|
||||||
vector<string> cache;
|
vector<string> cache;
|
||||||
public:
|
public:
|
||||||
|
Meter();
|
||||||
Meter(const int width, const string& color_gradient, const bool invert = false);
|
Meter(const int width, const string& color_gradient, const bool invert = false);
|
||||||
|
|
||||||
//* Return a string representation of the meter with given value
|
//* Return a string representation of the meter with given value
|
||||||
|
|
|
@ -17,6 +17,7 @@ tab-size = 4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
#include <btop_input.hpp>
|
#include <btop_input.hpp>
|
||||||
#include <btop_tools.hpp>
|
#include <btop_tools.hpp>
|
||||||
|
@ -28,6 +29,7 @@ tab-size = 4
|
||||||
|
|
||||||
using std::cin, std::string_literals::operator""s;
|
using std::cin, std::string_literals::operator""s;
|
||||||
using namespace Tools;
|
using namespace Tools;
|
||||||
|
namespace rng = std::ranges;
|
||||||
|
|
||||||
namespace Input {
|
namespace Input {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -73,7 +75,7 @@ namespace Input {
|
||||||
array<int, 2> mouse_pos;
|
array<int, 2> mouse_pos;
|
||||||
unordered_flat_map<string, Mouse_loc> mouse_mappings;
|
unordered_flat_map<string, Mouse_loc> mouse_mappings;
|
||||||
|
|
||||||
string last = "";
|
deque<string> history(50, "");
|
||||||
string old_filter;
|
string old_filter;
|
||||||
|
|
||||||
bool poll(int timeout) {
|
bool poll(int timeout) {
|
||||||
|
@ -120,9 +122,8 @@ namespace Input {
|
||||||
key.clear();
|
key.clear();
|
||||||
|
|
||||||
if (Config::getB("proc_filtering")) {
|
if (Config::getB("proc_filtering")) {
|
||||||
if (mouse_event == "mouse_click") last = mouse_event;
|
if (mouse_event == "mouse_click") return mouse_event;
|
||||||
else last.clear();
|
else return "";
|
||||||
return last;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//? Get column and line position of mouse and check for any actions mapped to current position
|
//? 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)
|
else if (ulen(key) > 1)
|
||||||
key.clear();
|
key.clear();
|
||||||
|
|
||||||
last = key;
|
history.push_back(key);
|
||||||
|
history.pop_front();
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
string wait() {
|
string wait() {
|
||||||
while (cin.rdbuf()->in_avail() < 1) {
|
while (cin.rdbuf()->in_avail() < 1) {
|
||||||
// if (interrupt) { interrupt = false; return ""; }
|
|
||||||
sleep_ms(10);
|
sleep_ms(10);
|
||||||
}
|
}
|
||||||
return get();
|
return get();
|
||||||
|
@ -170,7 +171,7 @@ namespace Input {
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
if (cin.rdbuf()->in_avail() > 0) cin.ignore(SSmax);
|
if (cin.rdbuf()->in_avail() > 0) cin.ignore(SSmax);
|
||||||
last.clear();
|
history.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void process(const string& key) {
|
void process(const string& key) {
|
||||||
|
@ -178,8 +179,6 @@ namespace Input {
|
||||||
try {
|
try {
|
||||||
auto& filtering = Config::getB("proc_filtering");
|
auto& filtering = Config::getB("proc_filtering");
|
||||||
if (not filtering and key == "q") clean_quit(0);
|
if (not filtering and key == "q") clean_quit(0);
|
||||||
bool no_update = true;
|
|
||||||
bool redraw = true;
|
|
||||||
|
|
||||||
//? Global input actions
|
//? Global input actions
|
||||||
if (not filtering) {
|
if (not filtering) {
|
||||||
|
@ -198,6 +197,8 @@ namespace Input {
|
||||||
//? Input actions for proc box
|
//? Input actions for proc box
|
||||||
if (Proc::shown) {
|
if (Proc::shown) {
|
||||||
bool keep_going = false;
|
bool keep_going = false;
|
||||||
|
bool no_update = true;
|
||||||
|
bool redraw = true;
|
||||||
if (filtering) {
|
if (filtering) {
|
||||||
if (key == "enter") {
|
if (key == "enter") {
|
||||||
Config::set("proc_filter", Proc::filter.text);
|
Config::set("proc_filter", Proc::filter.text);
|
||||||
|
@ -341,6 +342,37 @@ namespace Input {
|
||||||
return;
|
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 <atomic>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <robin_hood.h>
|
#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:
|
/* The input functions relies on the following std::cin options being set:
|
||||||
cin.sync_with_stdio(false);
|
cin.sync_with_stdio(false);
|
||||||
cin.tie(NULL);
|
cin.tie(NULL);
|
||||||
|
@ -46,7 +47,7 @@ namespace Input {
|
||||||
extern array<int, 2> mouse_pos;
|
extern array<int, 2> mouse_pos;
|
||||||
|
|
||||||
//* Last entered key
|
//* Last entered key
|
||||||
extern string last;
|
extern deque<string> history;
|
||||||
|
|
||||||
//* Poll keyboard & mouse input for <timeout> ms and return input availabilty as a bool
|
//* Poll keyboard & mouse input for <timeout> ms and return input availabilty as a bool
|
||||||
bool poll(int timeout=0);
|
bool poll(int timeout=0);
|
||||||
|
|
|
@ -48,20 +48,45 @@ namespace Tools {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Cpu {
|
namespace Cpu {
|
||||||
vector<uint64_t> core_old_totals;
|
vector<long long> core_old_totals;
|
||||||
vector<uint64_t> core_old_idles;
|
vector<long long> core_old_idles;
|
||||||
vector<string> available_fields;
|
vector<string> available_fields;
|
||||||
cpu_info current_cpu;
|
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 {
|
namespace Shared {
|
||||||
|
|
||||||
fs::path procPath;
|
fs::path procPath, passwd_path;
|
||||||
fs::path passwd_path;
|
|
||||||
fs::file_time_type passwd_time;
|
fs::file_time_type passwd_time;
|
||||||
uint64_t totalMem;
|
uint64_t totalMem;
|
||||||
long pageSize, clkTck, coreCount;
|
long pageSize, clkTck, coreCount;
|
||||||
string cpuName;
|
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
|
|
||||||
|
@ -102,6 +127,7 @@ namespace Shared {
|
||||||
throw std::runtime_error("Could not get total memory size from /proc/meminfo");
|
throw std::runtime_error("Could not get total memory size from /proc/meminfo");
|
||||||
|
|
||||||
//? Init for namespace Cpu
|
//? 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.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::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {});
|
||||||
Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0);
|
Cpu::core_old_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);
|
if (not vec.empty()) Cpu::available_fields.push_back(field);
|
||||||
}
|
}
|
||||||
Cpu::cpuName = Cpu::get_cpuName();
|
Cpu::cpuName = Cpu::get_cpuName();
|
||||||
|
Cpu::got_sensors = Cpu::get_sensors();
|
||||||
|
Cpu::core_mapping = Cpu::get_core_mapping();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Cpu {
|
namespace Cpu {
|
||||||
bool got_sensors = false;
|
|
||||||
string cpuName;
|
string cpuName;
|
||||||
string cpuHz;
|
string cpuHz;
|
||||||
|
|
||||||
const array<string, 10> time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"};
|
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},
|
{"totals", 0},
|
||||||
{"idles", 0},
|
{"idles", 0},
|
||||||
{"user", 0},
|
{"user", 0},
|
||||||
|
@ -148,19 +177,19 @@ namespace Cpu {
|
||||||
getline(cpuinfo, name);
|
getline(cpuinfo, name);
|
||||||
auto name_vec = ssplit(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);
|
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(')'))
|
if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')'))
|
||||||
name = name_vec.at(cpu_pos + 1);
|
name = name_vec.at(cpu_pos + 1);
|
||||||
else
|
else
|
||||||
name.clear();
|
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);
|
auto ryz_pos = v_index(name_vec, "Ryzen"s);
|
||||||
name = "Ryzen" + (ryz_pos < name_vec.size() - 1 ? ' ' + name_vec.at(ryz_pos + 1) : "")
|
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) : "");
|
+ (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);
|
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) != "@")
|
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);
|
name = name_vec.at(cpu_pos + 1);
|
||||||
|
@ -187,56 +216,254 @@ namespace Cpu {
|
||||||
return name;
|
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() {
|
string get_cpuHz() {
|
||||||
static bool failed = false;
|
static int failed = 0;
|
||||||
if (failed) return "";
|
if (failed > 4) return ""s;
|
||||||
string cpuhz;
|
string cpuhz;
|
||||||
try {
|
try {
|
||||||
ifstream cpuinfo(Shared::procPath / "cpuinfo");
|
double hz = 0.0;
|
||||||
if (cpuinfo.good()) {
|
//? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster)
|
||||||
string instr;
|
if (not freq_path.empty()) {
|
||||||
while (getline(cpuinfo, instr, ':') and not instr.starts_with("cpu MHz"))
|
hz = stod(readfile(freq_path, "0.0")) / 1000;
|
||||||
cpuinfo.ignore(SSmax, '\n');
|
if (hz <= 0.0 and ++failed >= 2)
|
||||||
cpuinfo.ignore(1);
|
freq_path.clear();
|
||||||
getline(cpuinfo, instr);
|
}
|
||||||
if (instr.empty()) throw std::runtime_error("");
|
//? 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 >= 1000) {
|
||||||
if (hz_int >= 10000) cpuhz = to_string((int)round((double)hz_int / 1000)); // Future proof until we reach THz speeds :)
|
if (hz >= 10000) cpuhz = to_string((int)round(hz / 1000)); // Future proof until we reach THz speeds :)
|
||||||
else cpuhz = to_string(round((double)hz_int / 100) / 10.0).substr(0, 3);
|
else cpuhz = to_string(round(hz / 100) / 10.0).substr(0, 3);
|
||||||
cpuhz += " GHz";
|
cpuhz += " GHz";
|
||||||
}
|
}
|
||||||
else if (hz_int > 0)
|
else if (hz > 0)
|
||||||
cpuhz = to_string(hz_int) + " MHz";
|
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;
|
return cpuhz;
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu_info collect(const bool no_update) {
|
unordered_flat_map<int, int> get_core_mapping() {
|
||||||
if (no_update and not current_cpu.cpu_percent.at("total").empty()) return current_cpu;
|
unordered_flat_map<int, int> core_map;
|
||||||
auto& cpu = current_cpu;
|
ifstream cpuinfo("/proc/cpuinfo");
|
||||||
// const auto& cpu_sensor = Config::getS("cpu_sensor");
|
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;
|
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
|
//? Get cpu total times for all cores from /proc/stat
|
||||||
cread.open(Shared::procPath / "stat");
|
cread.open(Shared::procPath / "stat");
|
||||||
if (cread.good()) {
|
for (int i = 0; cread.good() and cread.peek() == 'c'; i++) {
|
||||||
for (int i = 0; getline(cread, short_str, ' ') and short_str.starts_with("cpu"); 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
|
//? 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<uint64_t> times;
|
vector<long long> times;
|
||||||
uint64_t total_sum = 0;
|
long long total_sum = 0;
|
||||||
|
|
||||||
for (uint64_t val; cread >> val; total_sum += val) {
|
for (uint64_t val; cread >> val; total_sum += val) {
|
||||||
times.push_back(val);
|
times.push_back(val);
|
||||||
|
@ -245,31 +472,31 @@ namespace Cpu {
|
||||||
if (times.size() < 4) throw std::runtime_error("Malformatted /proc/stat");
|
if (times.size() < 4) throw std::runtime_error("Malformatted /proc/stat");
|
||||||
|
|
||||||
//? Subtract fields 8-9 and any future unknown fields
|
//? 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
|
//? 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
|
//? Calculate values for totals from first line of stat
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
const uint64_t calc_totals = totals - cpu_old["totals"];
|
const long long calc_totals = max(1ll, totals - cpu_old.at("totals"));
|
||||||
const uint64_t calc_idles = idles - cpu_old["idles"];
|
const long long calc_idles = max(1ll, idles - cpu_old.at("idles"));
|
||||||
cpu_old["totals"] = totals;
|
cpu_old.at("totals") = totals;
|
||||||
cpu_old["idles"] = idles;
|
cpu_old.at("idles") = idles;
|
||||||
|
|
||||||
//? Total usage of cpu
|
//? 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
|
//? 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
|
//? Populate cpu.cpu_percent with all fields from stat
|
||||||
for (int ii = 0; const auto& val : times) {
|
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.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[time_names.at(ii)] = val;
|
cpu_old.at(time_names.at(ii)) = val;
|
||||||
|
|
||||||
//? Reduce size if there are more values than needed for graph
|
//? 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;
|
if (++ii == 10) break;
|
||||||
}
|
}
|
||||||
|
@ -277,27 +504,31 @@ namespace Cpu {
|
||||||
//? Calculate cpu total for each core
|
//? Calculate cpu total for each core
|
||||||
else {
|
else {
|
||||||
if (i > Shared::coreCount) break;
|
if (i > Shared::coreCount) break;
|
||||||
const uint64_t calc_totals = totals - core_old_totals[i-1];;
|
const long long calc_totals = max(0ll, totals - core_old_totals.at(i-1));
|
||||||
const uint64_t calc_idles = idles - core_old_idles[i-1];;
|
const long long calc_idles = max(0ll, idles - core_old_idles.at(i-1));
|
||||||
core_old_totals[i-1] = totals;
|
core_old_totals.at(i-1) = totals;
|
||||||
core_old_idles[i-1] = idles;
|
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
|
//? 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 {
|
catch (const std::exception& e) {
|
||||||
throw std::runtime_error("Failed to read /proc/stat");
|
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"))
|
if (Config::getB("show_cpu_freq"))
|
||||||
cpuHz = get_cpuHz();
|
cpuHz = get_cpuHz();
|
||||||
|
|
||||||
|
if (Config::getB("check_temp") and got_sensors)
|
||||||
|
update_sensors();
|
||||||
|
|
||||||
return cpu;
|
return cpu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,10 +686,9 @@ namespace Proc {
|
||||||
detailed.memory.clear();
|
detailed.memory.clear();
|
||||||
if (not detailed.skip_smaps and fs::exists(pid_path / "smaps")) {
|
if (not detailed.skip_smaps and fs::exists(pid_path / "smaps")) {
|
||||||
d_read.open(pid_path / "smaps");
|
d_read.open(pid_path / "smaps");
|
||||||
if (d_read.good()) {
|
|
||||||
uint64_t rss = 0;
|
uint64_t rss = 0;
|
||||||
try {
|
try {
|
||||||
while (not d_read.eof()) {
|
while (d_read.good()) {
|
||||||
d_read.ignore(SSmax, 'R');
|
d_read.ignore(SSmax, 'R');
|
||||||
if (d_read.peek() == 's') {
|
if (d_read.peek() == 's') {
|
||||||
d_read.ignore(SSmax, ':');
|
d_read.ignore(SSmax, ':');
|
||||||
|
@ -475,7 +705,6 @@ namespace Proc {
|
||||||
}
|
}
|
||||||
catch (const std::invalid_argument&) {}
|
catch (const std::invalid_argument&) {}
|
||||||
catch (const std::out_of_range&) {}
|
catch (const std::out_of_range&) {}
|
||||||
}
|
|
||||||
d_read.close();
|
d_read.close();
|
||||||
}
|
}
|
||||||
if (detailed.memory.empty()) {
|
if (detailed.memory.empty()) {
|
||||||
|
@ -492,10 +721,9 @@ namespace Proc {
|
||||||
//? Get bytes read and written from proc/[pid]/io
|
//? Get bytes read and written from proc/[pid]/io
|
||||||
if (fs::exists(pid_path / "io")) {
|
if (fs::exists(pid_path / "io")) {
|
||||||
d_read.open(pid_path / "io");
|
d_read.open(pid_path / "io");
|
||||||
if (d_read.good()) {
|
|
||||||
try {
|
try {
|
||||||
string name;
|
string name;
|
||||||
while (not d_read.eof()) {
|
while (d_read.good()) {
|
||||||
getline(d_read, name, ':');
|
getline(d_read, name, ':');
|
||||||
if (name.ends_with("read_bytes")) {
|
if (name.ends_with("read_bytes")) {
|
||||||
getline(d_read, short_str);
|
getline(d_read, short_str);
|
||||||
|
@ -512,7 +740,6 @@ namespace Proc {
|
||||||
}
|
}
|
||||||
catch (const std::invalid_argument&) {}
|
catch (const std::invalid_argument&) {}
|
||||||
catch (const std::out_of_range&) {}
|
catch (const std::out_of_range&) {}
|
||||||
}
|
|
||||||
d_read.close();
|
d_read.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,7 +798,7 @@ namespace Proc {
|
||||||
//* Iterate over all pids in /proc
|
//* Iterate over all pids in /proc
|
||||||
for (const auto& d: fs::directory_iterator(Shared::procPath)) {
|
for (const auto& d: fs::directory_iterator(Shared::procPath)) {
|
||||||
if (Runner::stopping)
|
if (Runner::stopping)
|
||||||
return current_procs;
|
return procs;
|
||||||
if (pread.is_open()) pread.close();
|
if (pread.is_open()) pread.close();
|
||||||
|
|
||||||
const string pid_str = d.path().filename();
|
const string pid_str = d.path().filename();
|
||||||
|
@ -629,8 +856,8 @@ namespace Proc {
|
||||||
size_t x = 0, next_x = 3;
|
size_t x = 0, next_x = 3;
|
||||||
uint64_t cpu_t = 0;
|
uint64_t cpu_t = 0;
|
||||||
try {
|
try {
|
||||||
for (;;) {
|
while (pread.good()) {
|
||||||
while (++x - offset < next_x) {
|
while (pread.good() and ++x - offset < next_x) {
|
||||||
pread.ignore(SSmax, ' ');
|
pread.ignore(SSmax, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,10 +923,10 @@ namespace Proc {
|
||||||
if (x-offset < 24) continue;
|
if (x-offset < 24) continue;
|
||||||
|
|
||||||
//? Process cpu usage since last update
|
//? 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
|
//? 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
|
//? Update cache with latest cpu times
|
||||||
cache[new_proc.pid].cpu_t = cpu_t;
|
cache[new_proc.pid].cpu_t = cpu_t;
|
||||||
|
@ -714,7 +941,7 @@ namespace Proc {
|
||||||
}
|
}
|
||||||
|
|
||||||
//* Clear dead processes from cache at a regular interval
|
//* 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;
|
counter = 0;
|
||||||
unordered_flat_map<size_t, p_cache> r_cache;
|
unordered_flat_map<size_t, p_cache> r_cache;
|
||||||
r_cache.reserve(procs.size());
|
r_cache.reserve(procs.size());
|
||||||
|
|
|
@ -34,6 +34,7 @@ void banner_gen();
|
||||||
|
|
||||||
namespace Global {
|
namespace Global {
|
||||||
extern const string Version;
|
extern const string Version;
|
||||||
|
extern atomic<bool> quitting;
|
||||||
extern string exit_error_msg;
|
extern string exit_error_msg;
|
||||||
extern atomic<bool> thread_exception;
|
extern atomic<bool> thread_exception;
|
||||||
extern string banner;
|
extern string banner;
|
||||||
|
@ -70,7 +71,7 @@ namespace Shared {
|
||||||
namespace Cpu {
|
namespace Cpu {
|
||||||
extern string box;
|
extern string box;
|
||||||
extern int x, y, width, height;
|
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;
|
extern string cpuName, cpuHz;
|
||||||
|
|
||||||
struct cpu_info {
|
struct cpu_info {
|
||||||
|
@ -89,6 +90,7 @@ namespace Cpu {
|
||||||
};
|
};
|
||||||
vector<deque<long long>> core_percent;
|
vector<deque<long long>> core_percent;
|
||||||
vector<deque<long long>> temp;
|
vector<deque<long long>> temp;
|
||||||
|
long long temp_max = 0;
|
||||||
array<float, 3> load_avg;
|
array<float, 3> load_avg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,12 +99,6 @@ namespace Cpu {
|
||||||
|
|
||||||
//* Draw contents of cpu box using <cpu> as source
|
//* 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);
|
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 {
|
namespace Mem {
|
||||||
|
|
|
@ -115,6 +115,8 @@ namespace Term {
|
||||||
|
|
||||||
namespace Tools {
|
namespace Tools {
|
||||||
|
|
||||||
|
atomic<int> active_locks (0);
|
||||||
|
|
||||||
string uresize(string str, const size_t len, const bool wide) {
|
string uresize(string str, const size_t len, const bool wide) {
|
||||||
if (len < 1 or str.empty()) return "";
|
if (len < 1 or str.empty()) return "";
|
||||||
for (size_t x = 0, i = 0; i < str.size(); i++) {
|
for (size_t x = 0, i = 0; i < str.size(); i++) {
|
||||||
|
@ -264,7 +266,9 @@ namespace Tools {
|
||||||
return out;
|
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;
|
string new_str;
|
||||||
new_str.reserve(str.size() * n);
|
new_str.reserve(str.size() * n);
|
||||||
for (; n > 0; n--) new_str.append(str);
|
for (; n > 0; n--) new_str.append(str);
|
||||||
|
@ -280,14 +284,41 @@ namespace Tools {
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic_lock::atomic_lock(atomic<bool>& atom) : atom(atom) {
|
atomic_lock::atomic_lock(atomic<bool>& atom) : atom(atom) {
|
||||||
|
active_locks++;
|
||||||
while (not this->atom.compare_exchange_strong(this->not_true, true));
|
while (not this->atom.compare_exchange_strong(this->not_true, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic_lock::~atomic_lock() {
|
atomic_lock::~atomic_lock() {
|
||||||
|
active_locks--;
|
||||||
this->atom.store(false);
|
this->atom.store(false);
|
||||||
atomic_notify(this->atom);
|
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 {
|
namespace Logger {
|
||||||
|
|
|
@ -26,9 +26,10 @@ tab-size = 4
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#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 ------------------------------------------------------
|
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
|
||||||
|
@ -124,6 +125,7 @@ namespace Term {
|
||||||
|
|
||||||
namespace Tools {
|
namespace Tools {
|
||||||
constexpr auto SSmax = std::numeric_limits<std::streamsize>::max();
|
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)
|
//* 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) {
|
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);
|
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
|
//* 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
|
//* Return current time in <strf> format
|
||||||
string strf_time(const string& strf);
|
string strf_time(const string& strf);
|
||||||
|
@ -264,6 +266,11 @@ namespace Tools {
|
||||||
~atomic_lock();
|
~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