Added collect for battery

This commit is contained in:
aristocratos 2021-09-17 14:25:54 +02:00
parent 8583a8a2ef
commit 650df9ac39
12 changed files with 302 additions and 88 deletions

View file

@ -0,0 +1,3 @@
## v0.9.0
* Test release

View file

@ -1,4 +1,4 @@
#* Btop++ makefile v1.0
#* Btop++ makefile v1.2
BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m████████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗\n \033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗╚══\033[38;5;160m██\033[38;5;239m╔══╝\033[38;5;160m██\033[38;5;239m╔═══\033[38;5;160m██\033[38;5;239m╗\033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗\n \033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║\033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██████\033[38;5;238m╗\033[38;5;124m██████\033[38;5;238m╗\n \033[38;5;88m██\033[38;5;237m╔══\033[38;5;88m██\033[38;5;237m╗ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║\033[38;5;88m██\033[38;5;237m╔═══╝ ╚═\033[38;5;88m██\033[38;5;237m╔═╝╚═\033[38;5;88m██\033[38;5;237m╔═╝\n \033[38;5;52m██████\033[38;5;236m╔╝ \033[38;5;52m██\033[38;5;236m║ ╚\033[38;5;52m██████\033[38;5;236m╔╝\033[38;5;52m██\033[38;5;236m║ ╚═╝ ╚═╝\n \033[38;5;235m╚═════╝ ╚═╝ ╚═════╝ ╚═╝ \033[1;3;38;5;240mMakefile v1.2\033[0m
@ -16,7 +16,7 @@ ifeq ($(ARCH),unknown)
ARCH := $(shell uname -m || echo unknown)
endif
ifeq ($(ARCH),x86_64)
ADDFLAGS := -fcf-protection
override ADDFLAGS += -fcf-protection
endif
#? Make sure PLATFORM Darwin is OSX and not Darwin
@ -72,9 +72,9 @@ OBJEXT := o
#? Flags, Libraries and Includes
override REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -pedantic -pedantic-errors -Wfatal-errors
WARNFLAGS := -Wall -Wextra -pedantic
OPTFLAGS ?= -O2 -ftree-loop-vectorize -flto=$(THREADS)
LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection $(ADDFLAGS)
LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection -static $(ADDFLAGS)
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := -I$(INCDIR) -I$(SRCDIR)

View file

@ -239,6 +239,7 @@ namespace Runner {
atomic<bool> active (false);
atomic<bool> stopping (false);
atomic<bool> waiting (false);
atomic<bool> redraw (false);
//* Setup semaphore for triggering thread to do work
#if __GNUC__ < 11
@ -265,6 +266,7 @@ namespace Runner {
};
string output;
string empty_bg;
bool pause_output = false;
sigset_t mask;
pthread_t runner_id;
@ -358,14 +360,14 @@ namespace Runner {
continue;
}
//? Atomic lock used for blocking non-thread safe actions in main thread
//? Atomic lock used for blocking non thread-safe actions in main thread
atomic_lock lck(active);
auto& conf = current_conf;
//! DEBUG stats
if (Global::debug) {
if (debug_bg.empty()) Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug");
if (debug_bg.empty() or redraw) Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug");
debug_times.clear();
debug_times["total"] = {0, 0};
}
@ -508,8 +510,28 @@ namespace Runner {
continue;
}
if (redraw or conf.force_redraw) {
empty_bg.clear();
redraw = false;
}
if (not pause_output) output += conf.clock;
if (not conf.overlay.empty() and not conf.background_update) pause_output = true;
if (output.empty() and not pause_output) {
if (empty_bg.empty()) {
const int x = Term::width / 2 - 10, y = Term::height / 2 - 10;
output += Term::clear;
empty_bg += Draw::banner_gen(y, 0, true)
+ Mv::to(y+6, x) + Theme::c("title") + Fx::b + "No boxes shown!"
+ Mv::to(y+8, x) + Theme::c("hi_fg") + "1" + Theme::c("main_fg") + " | Show CPU box"
+ Mv::to(y+9, x) + Theme::c("hi_fg") + "2" + Theme::c("main_fg") + " | Show MEM box"
+ Mv::to(y+10, x) + Theme::c("hi_fg") + "3" + Theme::c("main_fg") + " | Show NET box"
+ Mv::to(y+11, x) + Theme::c("hi_fg") + "4" + Theme::c("main_fg") + " | Show PROC box"
+ Mv::to(y+12, x-2) + Theme::c("hi_fg") + "esc" + Theme::c("main_fg") + " | Show menu"
+ Mv::to(y+13, x) + Theme::c("hi_fg") + "q" + Theme::c("main_fg") + " | Quit";
}
output += empty_bg;
}
//! DEBUG stats -->
if (Global::debug and not Menu::active) {
@ -546,14 +568,11 @@ namespace Runner {
else if (box == "clock") {
cout << Term::sync_start << Global::clock << Term::sync_end << flush;
}
else if (Config::current_boxes.empty()) {
cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end << flush;
}
else {
Config::unlock();
Config::lock();
//? Setup bitmask for selected boxes instead of parsing strings in _runner thread loop
//? Setup bitmask for selected boxes and pass to _runner thread
bitset<8> box_mask;
for (const auto& box : (box == "all" ? Config::current_boxes : vector{box})) {
box_mask |= box_bits.at(box);
@ -767,7 +786,7 @@ int main(int argc, char **argv) {
//? Trigger secondary thread to redraw if terminal has been resized
if (Global::resized) {
Draw::calcSizes();
Draw::update_clock();
Draw::update_clock(true);
Global::resized = false;
if (Menu::active) Menu::process();
else Runner::run("all", true, true);

View file

@ -109,6 +109,7 @@ 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"
"#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.\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\""},
@ -146,7 +147,7 @@ namespace Config {
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
{"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n"
{"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (100 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n"
"#* Example: \"/mnt/media:100 /:20 /boot:1\"."},
{"net_download", "#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False."},
@ -276,6 +277,10 @@ namespace Config {
validError = "Value out of range!";
return false;
}
catch (const std::exception& e) {
validError = (string)e.what();
return false;
}
if (name == "update_ms" and i_value < 100)
validError = "Config value update_ms set too low (<100).";

View file

@ -261,7 +261,7 @@ namespace Draw {
return out + Fx::reset + Mv::to(y + 1, x + 1);
}
bool update_clock() {
bool update_clock(bool force) {
const auto& clock_format = Config::getS("clock_format");
if (not Cpu::shown or clock_format.empty()) {
if (clock_format.empty() and not Global::clock.empty()) Global::clock.clear();
@ -275,16 +275,15 @@ namespace Draw {
};
static time_t c_time = 0;
static size_t clock_len = 0;
static string old_clock;
string new_clock;
static string clock_str;
if (auto n_time = time(NULL); n_time == c_time)
if (auto n_time = time(NULL); not force and n_time == c_time)
return false;
else {
c_time = n_time;
new_clock = Tools::strf_time(clock_format);
if (new_clock == old_clock) return false;
old_clock = new_clock;
const auto new_clock = Tools::strf_time(clock_format);
if (not force and new_clock == clock_str) return false;
clock_str = new_clock;
}
auto& out = Global::clock;
@ -297,29 +296,30 @@ namespace Draw {
for (const auto& [c_format, replacement] : clock_custom_format) {
if (s_contains(new_clock, c_format)) {
if (s_contains(clock_str, c_format)) {
if (c_format == "/uptime") {
string upstr = sec_to_dhms(system_uptime());
if (upstr.size() > 8) upstr.resize(upstr.size() - 3);
new_clock = s_replace(new_clock, c_format, upstr);
clock_str = s_replace(clock_str, c_format, upstr);
}
else {
new_clock = s_replace(new_clock, c_format, replacement);
clock_str = s_replace(clock_str, c_format, replacement);
}
}
}
new_clock = uresize(new_clock, std::max(0, width - 56));
clock_str = uresize(clock_str, std::max(0, width - 56));
out.clear();
if (new_clock.size() != clock_len) {
if (not Global::resized and clock_len > 0) out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len;
clock_len = new_clock.size();
if (clock_str.size() != clock_len) {
if (not Global::resized and clock_len > 0)
out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len;
clock_len = clock_str.size();
}
out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left
+ Theme::c("title") + Fx::b + new_clock + Theme::c("cpu_box") + Fx::ub + title_right;
+ Theme::c("title") + Fx::b + clock_str + Theme::c("cpu_box") + Fx::ub + title_right;
return true;
}
@ -488,7 +488,7 @@ namespace Cpu {
string out;
out.reserve(width * height);
//* Redraw elements not needed to be updated every cycle
if (redraw or force_redraw) {
if (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));
@ -533,8 +533,31 @@ namespace Cpu {
}
}
}
}
//? Draw battery if enabled and present
if (Config::getB("show_battery") and has_battery) {
static int old_percent = 0;
static long old_seconds = 0;
static string old_status;
static Draw::Meter bat_meter {10, "cpu", true};
static const unordered_flat_map<string, string> bat_symbols = {
{"charging", ""},
{"discharging", ""},
{"full", ""},
{"unknown", ""}
};
const auto& [percent, seconds, status] = current_bat;
if (redraw or percent != old_percent or seconds != old_seconds or status != old_status) {
old_percent = percent;
old_seconds = seconds;
old_status = status;
const string bat_time = (seconds > 0 ? to_string(seconds / 3600) + ':' + to_string((seconds % 3600) / 60) : "");
const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown"));
}
}
try {
//? Cpu graphs
out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(cpu.cpu_percent.at(graph_up_field), (data_same or redraw));
@ -693,7 +716,10 @@ namespace Mem {
for (const auto& entry : split) {
auto vals = ssplit(entry);
if (vals.size() == 2 and mem.disks.contains(vals.at(0)) and isint(vals.at(1)))
custom_speeds[vals.at(0)] = std::stoi(vals.at(1));
try {
custom_speeds[vals.at(0)] = std::stoi(vals.at(1));
}
catch (const std::out_of_range&) { continue; }
}
}
}
@ -705,7 +731,7 @@ namespace Mem {
if (io_mode) {
//? Create one combined graph for IO read/write if enabled
long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 10) << 20;
long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 100) << 20;
if (io_graph_combined) {
deque<long long> combined(disk.io_read.size(), 0);
rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>());
@ -1442,7 +1468,7 @@ namespace Draw {
Global::clock.clear();
Global::overlay.clear();
Runner::pause_output = false;
Runner::debug_bg.clear();
Runner::redraw = true;
Proc::p_counters.clear();
Proc::p_graphs.clear();
if (Menu::active) Menu::redraw = true;

View file

@ -77,7 +77,7 @@ namespace Draw {
//* Create a box and return as a string
string createBox(const int x, const int y, const int width, const int height, string line_color="", const bool fill=false, const string title="", const string title2="", const int num=0);
bool update_clock();
bool update_clock(bool force=false);
//* Class holding a percentage meter
class Meter {

View file

@ -190,25 +190,24 @@ namespace Input {
exit(0);
}
else if (is_in(key, "escape", "m")) {
Menu::menuMask.set(Menu::Main);
Menu::process();
Menu::show(Menu::Menus::Main);
return;
}
else if (is_in(key, "F1", "h")) {
Menu::menuMask.set(Menu::Help);
Menu::process();
Menu::show(Menu::Menus::Help);
return;
}
else if (is_in(key, "F2", "o")) {
Menu::menuMask.set(Menu::Options);
Menu::process();
Menu::show(Menu::Menus::Options);
return;
}
else if (is_in(key, "1", "2", "3", "4")) {
atomic_wait(Runner::active);
static const array<string, 4> boxes = {"cpu", "mem", "net", "proc"};
Config::toggle_box(boxes.at(std::stoi(key) - 1));
term_resize(true);
Draw::calcSizes();
Runner::run("all", false, true);
return;
}
else
keep_going = true;
@ -270,11 +269,6 @@ namespace Input {
else if (key == "delete" and not Config::getS("proc_filter").empty())
Config::set("proc_filter", ""s);
else if (key == "ö") {
Menu::menuMask.set(Menu::Menus::SignalSend);
Menu::process();
return;
}
else if (key.starts_with("mouse_")) {
redraw = false;
const auto& [col, line] = mouse_pos;
@ -339,18 +333,14 @@ namespace Input {
else if (is_in(key, "t", "k") and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) {
atomic_wait(Runner::active);
if (Config::getB("show_detailed") and Config::getI("proc_selected") == 0 and Proc::detailed.status == "Dead") return;
Menu::menuMask.set(Menu::SignalSend);
Menu::signalToSend = (key == "t" ? SIGTERM : SIGKILL);
Menu::process();
Menu::show(Menu::Menus::SignalSend, (key == "t" ? SIGTERM : SIGKILL));
return;
}
else if (key == "s" and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) {
if (Term::width < 80 or Term::height < 20) return;
atomic_wait(Runner::active);
if (Config::getB("show_detailed") and Config::getI("proc_selected") == 0 and Proc::detailed.status == "Dead") return;
Menu::menuMask.set(Menu::SignalChoose);
Menu::signalToSend = -1;
Menu::process();
Menu::show(Menu::Menus::SignalChoose);
return;
}
else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) {

View file

@ -317,6 +317,9 @@ namespace Menu {
"Can be needed on certain cpus to get correct",
"temperature for correct core.",
"",
"Use lm-sensors or similar to see which cores",
"are reporting temperatures on your machine.",
"",
"Format: \"X:Y\"",
"X=core with wrong temp.",
"Y=core with correct temp.",
@ -376,7 +379,7 @@ namespace Menu {
"Toggle IO activity graphs.",
"",
"Show small IO graphs that for disk activity",
"percentage when not in IO mode.",
"(disk busy time) when not in IO mode.",
"",
"True or False."},
{"io_mode",
@ -397,10 +400,10 @@ namespace Menu {
"",
"Manually set which speed in MiB/s that",
"equals 100 percent in the io graphs.",
"(10 MiB/s by default).",
"(100 MiB/s by default).",
"",
"Format: \"device:speed\" seperate disks with a",
"comma \",\".",
"Format: \"device:speed\" seperate disks with",
"whitespace \" \".",
"",
"Example: \"/dev/sda:100, /dev/sdb:20\"."},
{"show_swap",
@ -477,6 +480,7 @@ namespace Menu {
"Network Interface.",
"",
"Manually set the starting Network Interface.",
"",
"Will otherwise automatically choose the NIC",
"with the highest total download since boot."},
},
@ -928,6 +932,7 @@ namespace Menu {
bool screen_redraw = false;
bool theme_refresh = false;
//? Draw background if needed else process input
if (redraw) {
mouse_mappings.clear();
selPred.reset();
@ -960,7 +965,15 @@ namespace Menu {
const auto& option = categories[selected_cat][item_height * page + selected][0];
if (selPred.test(isString) and Config::stringValid(option, editor.text)) {
Config::set(option, editor.text);
if (option == "shown_boxes") screen_redraw = true;
if (is_in(option, "shown_boxes", "custom_cpu_name")) screen_redraw = true;
else if (option == "clock_format") {
Draw::update_clock(true);
screen_redraw = true;
}
else if (option == "cpu_core_map") {
atomic_wait(Runner::active);
Cpu::core_mapping = Cpu::get_core_mapping();
}
}
else if (selPred.test(isInt) and Config::intValid(option, editor.text)) {
Config::set(option, stoi(editor.text));
@ -1060,6 +1073,9 @@ namespace Menu {
}
else if (is_in(option, "rounded_corners", "theme_background"))
theme_refresh = true;
else if (option == "background_update") {
Runner::pause_output = false;
}
}
else if (selPred.test(isBrowseable)) {
auto& optList = optionsList.at(option).get();
@ -1071,6 +1087,10 @@ namespace Menu {
if (option == "color_theme")
theme_refresh = true;
else if (option == "log_level") {
Logger::set(optList.at(i));
Logger::info("Logger set to " + optList.at(i));
}
else if (is_in(option, "proc_sorting", "cpu_sensor") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_"))
screen_redraw = true;
}
@ -1081,6 +1101,7 @@ namespace Menu {
retval = NoChange;
}
//? Draw the menu
if (retval == Changed) {
Config::unlock();
auto& out = Global::overlay;
@ -1093,6 +1114,7 @@ namespace Menu {
selected = select_max;
}
//? Get variable properties for currently selected option
if (selPred.none() or last_sel != (selected_cat << 8) + selected) {
selPred.reset();
last_sel = (selected_cat << 8) + selected;
@ -1113,6 +1135,7 @@ namespace Menu {
selPred.set(isEditable);
}
//? Category buttons
out += Mv::to(y+7, x+4);
for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) {
out += Fx::b + (i == selected_cat
@ -1127,6 +1150,7 @@ namespace Menu {
out += Mv::to(y+6 + height, x+2) + Theme::c("hi_fg") + Symbols::title_left_down + Fx::b + Symbols::up + Theme::c("title") + " page "
+ to_string(page+1) + '/' + to_string(pages) + ' ' + Theme::c("hi_fg") + Symbols::down + Fx::ub + Symbols::title_right_down;
}
//? Option name and value
auto cy = y+9;
for (int c = 0, i = max(0, item_height * page); c++ < item_height and i < (int)categories[selected_cat].size(); i++) {
const auto& option = categories[selected_cat][i][0];
@ -1149,6 +1173,7 @@ namespace Menu {
if (selPred.test(isEditable)) {
out += Fx::b + Mv::to(cy-1, x+28 - (not editing and selPred.test(isInt) ? 2 : 0)) + (tty_mode ? "E" : Symbols::enter);
}
//? Description of selected option
out += Fx::reset + Theme::c("title") + Fx::b;
for (int cyy = y+7; const auto& desc : categories[selected_cat][i]) {
if (cyy++ == y+7) continue;
@ -1175,9 +1200,11 @@ namespace Menu {
optionsMenu("");
}
if (screen_redraw) {
auto overlay_bkp = Global::overlay;
auto overlay_bkp = move(Global::overlay);
auto clock_bkp = move(Global::clock);
Draw::calcSizes();
Global::overlay = overlay_bkp;
Global::overlay = move(overlay_bkp);
Global::clock = move(clock_bkp);
recollect = true;
}
if (recollect) {
@ -1291,4 +1318,10 @@ namespace Menu {
process();
}
}
void show(int menu, int signal) {
menuMask.set(menu);
signalToSend = signal;
process();
}
}

View file

@ -80,4 +80,7 @@ namespace Menu {
//* Handles redirection of input for menu functions and handles return codes
void process(string key="");
//* Show a menu from enum Menu::Menus
void show(int menu, int signal=-1);
}

View file

@ -26,8 +26,9 @@ tab-size = 4
#include <robin_hood.h>
#include <array>
#include <ifaddrs.h>
#include <tuple>
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array;
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array, std::tuple;
void term_resize(bool force=false);
void banner_gen();
@ -49,6 +50,7 @@ namespace Runner {
extern atomic<bool> active;
extern atomic<bool> reading;
extern atomic<bool> stopping;
extern atomic<bool> redraw;
extern pthread_t runner_id;
extern bool pause_output;
extern string debug_bg;
@ -75,10 +77,11 @@ namespace Shared {
namespace Cpu {
extern string box;
extern int x, y, width, height, min_width, min_height;
extern bool shown, redraw, got_sensors, cpu_temp_only;
extern bool shown, redraw, got_sensors, cpu_temp_only, has_battery;
extern string cpuName, cpuHz;
extern vector<string> available_fields;
extern vector<string> available_sensors;
extern tuple<int, long, string> current_bat;
struct cpu_info {
unordered_flat_map<string, deque<long long>> cpu_percent = {
@ -105,6 +108,13 @@ 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);
//* Parse /proc/cpu info for mapping of core ids
auto get_core_mapping() -> unordered_flat_map<int, int>;
extern unordered_flat_map<int, int> core_mapping;
//* Get battery info from /sys
auto get_battery() -> tuple<int, long, string>;
}
namespace Mem {
@ -128,10 +138,12 @@ namespace Mem {
};
struct mem_info {
unordered_flat_map<string, uint64_t> stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0},
{"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}};
unordered_flat_map<string, deque<long long>> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}},
{"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}};
unordered_flat_map<string, uint64_t> stats =
{{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0},
{"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}};
unordered_flat_map<string, deque<long long>> percent =
{{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}},
{"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}};
unordered_flat_map<string, disk_info> disks;
vector<string> disks_order;
};

View file

@ -338,7 +338,7 @@ namespace Tools {
for (string readstr; getline(file, readstr); out += readstr);
}
catch (const std::exception& e) {
throw std::runtime_error("Exception when reading " + (string)path + " : " + e.what());
throw std::runtime_error("readfile() : Exception when reading " + (string)path + " : " + e.what());
}
return (out.empty() ? fallback : out);
}

View file

@ -57,9 +57,6 @@ namespace Cpu {
//* Search /proc/cpuinfo for a cpu name
string get_cpuName();
//* Parse /proc/cpu info for mapping of core ids
auto get_core_mapping() -> unordered_flat_map<int, int>;
struct Sensor {
fs::path path;
string label;
@ -150,6 +147,8 @@ namespace Shared {
namespace Cpu {
string cpuName;
string cpuHz;
bool has_battery = true;
tuple<int, long, string> current_bat;
const array<string, 10> time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"};
@ -175,8 +174,25 @@ namespace Cpu {
for (string instr; getline(cpuinfo, instr, ':') and not instr.starts_with("model name");)
cpuinfo.ignore(SSmax, '\n');
if (cpuinfo.bad()) return name;
cpuinfo.ignore(1);
getline(cpuinfo, name);
else if (not cpuinfo.eof()) {
cpuinfo.ignore(1);
getline(cpuinfo, name);
}
else if (fs::exists("/sys/devices")) {
for (const auto& d : fs::directory_iterator("/sys/devices")) {
if (string(d.path().filename()).starts_with("arm")) {
name = d.path().filename();
break;
}
}
if (not name.empty()) {
auto name_vec = ssplit(name, '_');
if (name_vec.size() < 2) return capitalize(name);
else return capitalize(name_vec.at(1)) + (name_vec.size() > 2 ? ' ' + capitalize(name_vec.at(2)) : "");
}
}
auto name_vec = ssplit(name);
if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) {
@ -300,7 +316,7 @@ namespace Cpu {
}
catch (...) {}
if (not got_coretemp) cpu_temp_only = true;
if (not got_coretemp or core_sensors.empty()) 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")) {
@ -397,11 +413,12 @@ namespace Cpu {
auto get_core_mapping() -> unordered_flat_map<int, int> {
unordered_flat_map<int, int> core_map;
if (cpu_temp_only) return core_map;
//? Try to get core mapping from /proc/cpuinfo
ifstream cpuinfo(Shared::procPath / "cpuinfo");
if (cpuinfo.good()) {
int cpu, core;
int cpu, core, n = 0;
for (string instr; cpuinfo >> instr;) {
if (instr == "processor") {
cpuinfo.ignore(SSmax, ':');
@ -410,22 +427,31 @@ namespace Cpu {
else if (instr.starts_with("core")) {
cpuinfo.ignore(SSmax, ':');
cpuinfo >> core;
core_map[cpu] = core;
if (std::cmp_greater_equal(core, core_sensors.size())) {
if (std::cmp_greater_equal(n, core_sensors.size())) n = 0;
core_map[cpu] = n++;
}
else
core_map[cpu] = core;
}
cpuinfo.ignore(SSmax, '\n');
}
}
//? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely map 0-0 1-1 2-2 etc.
//? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely, map 0-0 1-1 2-2 etc.
if (cmp_less(core_map.size(), Shared::coreCount)) {
if (Shared::coreCount % 2 == 0 and (long)core_map.size() == Shared::coreCount / 2) {
for (int i = 0; i < Shared::coreCount / 2; i++)
core_map[Shared::coreCount / 2 + i] = i;
for (int i = 0, n = 0; i < Shared::coreCount / 2; i++) {
if (std::cmp_greater_equal(n, core_sensors.size())) n = 0;
core_map[Shared::coreCount / 2 + i] = n++;
}
}
else {
core_map.clear();
for (int i = 0; i < Shared::coreCount; i++)
core_map[i] = i;
for (int i = 0, n = 0; i < Shared::coreCount; i++) {
if (std::cmp_greater_equal(n, core_sensors.size())) n = 0;
core_map[i] = n++;
}
}
}
@ -438,7 +464,7 @@ namespace Cpu {
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;
if (not core_map.contains(change_id) or cmp_greater(new_id, core_sensors.size())) continue;
core_map.at(change_id) = new_id;
}
}
@ -448,6 +474,100 @@ namespace Cpu {
return core_map;
}
auto get_battery() -> tuple<int, long, string> {
if (not has_battery) return {0, 0, ""};
static fs::path bat_dir, energy_now_path, energy_full_path, power_now_path, status_path, online_path;
static bool use_energy = true;
//? Get paths to needed files and check for valid values on first run
if (bat_dir.empty() and has_battery) {
if (fs::exists("/sys/class/power_supply")) {
for (const auto& d : fs::directory_iterator("/sys/class/power_supply")) {
if (const string dir_name = d.path().filename(); d.is_directory() and (dir_name.starts_with("BAT") or s_contains(str_to_lower(dir_name), "battery"))) {
bat_dir = d.path();
break;
}
}
}
if (bat_dir.empty()) {
has_battery = false;
return {0, 0, ""};
}
else {
if (fs::exists(bat_dir / "energy_now")) energy_now_path = bat_dir / "energy_now";
else if (fs::exists(bat_dir / "charge_now")) energy_now_path = bat_dir / "charge_now";
else use_energy = false;
if (fs::exists(bat_dir / "energy_full")) energy_full_path = bat_dir / "energy_full";
else if (fs::exists(bat_dir / "charge_full")) energy_full_path = bat_dir / "charge_full";
else use_energy = false;
if (not use_energy and not fs::exists(bat_dir / "capacity")) {
has_battery = false;
return {0, 0, ""};
}
if (fs::exists(bat_dir / "power_now")) power_now_path = bat_dir / "power_now";
else if (fs::exists(bat_dir / "current_now")) power_now_path = bat_dir / "current_now";
if (fs::exists(bat_dir / "AC0/online")) online_path = bat_dir / "AC0/online";
else if (fs::exists(bat_dir / "AC/online")) online_path = bat_dir / "AC/online";
}
}
int percent = -1;
long seconds = -1;
//? Try to get battery percentage
if (use_energy) {
try {
percent = round(100.0 * stoll(readfile(energy_now_path, "-1")) / stoll(readfile(energy_full_path, "1")));
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
if (percent < 0) {
try {
percent = stoll(readfile(bat_dir / "capacity", "-1"));
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
if (percent < 0) {
has_battery = false;
return {0, 0, ""};
}
//? Get charging/discharging status
string status = str_to_lower(readfile(bat_dir / "status", "unknown"));
if (status == "unknown" and not online_path.empty()) {
const auto online = readfile(online_path, "0");
if (online == "1" and percent < 100) status = "charging";
else if (online == "1") status = "full";
else status = "discharging";
}
//? Get seconds to empty
if (not is_in(status, "charging", "full")) {
if (use_energy and not power_now_path.empty()) {
try {
seconds = round((double)stoll(readfile(energy_now_path, "0")) / stoll(readfile(power_now_path, "1")) * 3600);
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
if (seconds < 0 and fs::exists(bat_dir / "time_to_empty")) {
try {
seconds = stoll(readfile(bat_dir / "time_to_empty", "0")) * 60;
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
}
return {percent, seconds, status};
}
auto collect(const bool no_update) -> cpu_info& {
if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) return current_cpu;
auto& cpu = current_cpu;
@ -535,6 +655,9 @@ namespace Cpu {
if (Config::getB("check_temp") and got_sensors)
update_sensors();
if (Config::getB("show_battery") and has_battery)
current_bat = get_battery();
return cpu;
}
}
@ -717,6 +840,7 @@ namespace Mem {
else
it++;
}
if (found.size() != last_found.size()) redraw = true;
last_found = std::move(found);
}
else
@ -1346,7 +1470,7 @@ namespace Proc {
//? Process cumulative cpu usage since process start
new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - new_proc.cpu_s);
//? Update cache with latest cpu times
//? Update cached value with latest cpu times
new_proc.cpu_t = cpu_t;
if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
@ -1374,14 +1498,14 @@ namespace Proc {
//* Sort processes
if (sorted_change or not no_update) {
switch (v_index(sort_vector, sorting)) {
case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break;
case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break;
case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break;
case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break;
case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break;
case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break;
case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break;
case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break;
case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break;
case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break;
case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break;
case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break;
case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break;
case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break;
case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break;
}
if (reverse) rng::reverse(current_procs);
@ -1403,7 +1527,6 @@ namespace Proc {
}
}
//* Match filter if defined
if (should_filter) {
filter_found = 0;