From fe4db7c16cd349053385eda62e9f2df2e7344d3d Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sat, 2 Oct 2021 23:48:28 +0200 Subject: [PATCH] first infos on macos: memory used & free --- Makefile | 6 +- src/btop.cpp | 16 +- src/osx/btop_collect.cpp | 1549 +++++--------------------------------- 3 files changed, 199 insertions(+), 1372 deletions(-) diff --git a/Makefile b/Makefile index e1b5618..35fcd49 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ OBJEXT := o #? Flags, Libraries and Includes override REQFLAGS := -std=c++20 WARNFLAGS := -Wall -Wextra -pedantic -OPTFLAGS ?= -O2 #-ftree-loop-vectorize -flto=$(THREADS) +OPTFLAGS ?= -O2 LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) @@ -89,6 +89,10 @@ INC := -I$(INCDIR) -I$(SRCDIR) SU_USER := root SU_GROUP := root +ifeq ($(ARCH),x86_64) + override OPTFLAGS += -ftree-loop-vectorize -flto=$(THREADS) +endif + SOURCES := $(shell find $(SRCDIR) -maxdepth 1 -type f -name *.$(SRCEXT)) SOURCES += $(shell find $(SRCDIR)/$(PLATFORM_DIR) -type f -name *.$(SRCEXT)) diff --git a/src/btop.cpp b/src/btop.cpp index 6486f98..6c26506 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -381,12 +381,13 @@ namespace Runner { //? pthread_mutex_lock to lock thread and monitor health from main thread thread_lock pt_lck(mtx); - if (pt_lck.status != 0) { - Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status); - Global::thread_exception = true; - Input::interrupt = true; - stopping = true; - } + // if (pt_lck.status != 0) { + // Logger::error("exception in runner thread - set stopping"); + // Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status); + // Global::thread_exception = true; + // Input::interrupt = true; + // stopping = true; + // } //* ----------------------------------------------- THREAD LOOP ----------------------------------------------- while (not Global::quitting) { @@ -536,6 +537,7 @@ namespace Runner { } catch (const std::exception& e) { Global::exit_error_msg = "Exception in runner thread -> " + (string)e.what(); + Logger::error(Global::exit_error_msg); Global::thread_exception = true; Input::interrupt = true; stopping = true; @@ -850,7 +852,7 @@ int main(int argc, char **argv) { try { while (not true not_eq not false) { //? Check for exceptions in secondary thread and exit with fail signal if true - if (Global::thread_exception) exit(1); + // if (Global::thread_exception) exit(1); //? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly) term_resize(); diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index d8ea597..0cb8213 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -24,22 +24,26 @@ tab-size = 4 #include #include #include +#include #include #include +#include +#include #include #include #include -using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min; using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; +using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min; namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- -namespace Cpu { +namespace Cpu +{ vector core_old_totals; vector core_old_idles; vector available_fields; @@ -57,7 +61,8 @@ namespace Cpu { //* Search /proc/cpuinfo for a cpu name string get_cpuName(); - struct Sensor { + struct Sensor + { fs::path path; string label; int64_t temp = 0; @@ -71,82 +76,64 @@ namespace Cpu { unordered_flat_map core_mapping; } -namespace Mem { +namespace Mem +{ double old_uptime; } -namespace Shared { +namespace Shared +{ - fs::path procPath, passwd_path; + fs::path passwd_path; uint64_t totalMem; long pageSize, clkTck, coreCount; int totalMem_len; - void init() { + void init() + { + Logger::debug("Shared init"); //? Shared global variables init - procPath = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : ""; - if (procPath.empty()) - throw std::runtime_error("Proc filesystem not found or no permission to read from it!"); - passwd_path = (fs::is_regular_file(fs::path("/etc/passwd")) and access("/etc/passwd", R_OK) != -1) ? "/etc/passwd" : ""; - if (passwd_path.empty()) - Logger::warning("Could not read /etc/passwd, will show UID instead of username."); + // passwd_path = (fs::is_regular_file(fs::path("/etc/passwd")) and access("/etc/passwd", R_OK) != -1) ? "/etc/passwd" : ""; + // if (passwd_path.empty()) + // Logger::warning("Could not read /etc/passwd, will show UID instead of username."); coreCount = sysconf(_SC_NPROCESSORS_ONLN); - if (coreCount < 1) { + if (coreCount < 1) + { coreCount = 1; Logger::warning("Could not determine number of cores, defaulting to 1."); } pageSize = sysconf(_SC_PAGE_SIZE); - if (pageSize <= 0) { + if (pageSize <= 0) + { pageSize = 4096; Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect."); } clkTck = sysconf(_SC_CLK_TCK); - if (clkTck <= 0) { + if (clkTck <= 0) + { clkTck = 100; Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect."); } - ifstream meminfo(Shared::procPath / "meminfo"); - if (meminfo.good()) { - meminfo.ignore(SSmax, ':'); - meminfo >> totalMem; - totalMem_len = to_string(totalMem).size(); - totalMem <<= 10; + int64_t memsize = 0; + size_t size = sizeof(memsize); + Logger::debug("getting memsize"); + if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) < 0) + { + Logger::warning("Could not get memory size"); } - if (not meminfo.good() or totalMem == 0) - throw std::runtime_error("Could not get total memory size from /proc/meminfo"); - - //? Init for namespace Cpu - if (not fs::exists(Cpu::freq_path) or access(Cpu::freq_path.c_str(), R_OK) == -1) Cpu::freq_path.clear(); - Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {}); - Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); - Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); - Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); - Cpu::collect(); - for (auto& [field, vec] : Cpu::current_cpu.cpu_percent) { - if (not vec.empty()) Cpu::available_fields.push_back(field); - } - Cpu::cpuName = Cpu::get_cpuName(); - Cpu::got_sensors = Cpu::get_sensors(); - for (const auto& [sensor, ignored] : Cpu::found_sensors) { - Cpu::available_sensors.push_back(sensor); - } - Cpu::core_mapping = Cpu::get_core_mapping(); - - //? Init for namespace Mem - Mem::old_uptime = system_uptime(); - Mem::collect(); - + totalMem = memsize; } } -namespace Cpu { +namespace Cpu +{ string cpuName; string cpuHz; bool has_battery = true; @@ -155,962 +142,191 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"}; unordered_flat_map cpu_old = { - {"totals", 0}, - {"idles", 0}, - {"user", 0}, - {"nice", 0}, - {"system", 0}, - {"idle", 0}, - {"iowait", 0}, - {"irq", 0}, - {"softirq", 0}, - {"steal", 0}, - {"guest", 0}, - {"guest_nice", 0} - }; + {"totals", 0}, + {"idles", 0}, + {"user", 0}, + {"nice", 0}, + {"system", 0}, + {"idle", 0}, + {"iowait", 0}, + {"irq", 0}, + {"softirq", 0}, + {"steal", 0}, + {"guest", 0}, + {"guest_nice", 0}}; - string get_cpuName() { - string name; - ifstream cpuinfo(Shared::procPath / "cpuinfo"); - if (cpuinfo.good()) { - for (string instr; getline(cpuinfo, instr, ':') and not instr.starts_with("model name");) - cpuinfo.ignore(SSmax, '\n'); - if (cpuinfo.bad()) return 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)) { - auto cpu_pos = v_index(name_vec, "CPU"s); - if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')')) - name = name_vec.at(cpu_pos + 1); - else - name.clear(); - } - else if (v_contains(name_vec, "Ryzen"s)) { - auto ryz_pos = v_index(name_vec, "Ryzen"s); - name = "Ryzen" + (ryz_pos < name_vec.size() - 1 ? ' ' + name_vec.at(ryz_pos + 1) : "") - + (ryz_pos < name_vec.size() - 2 ? ' ' + name_vec.at(ryz_pos + 2) : ""); - } - else if (s_contains(name, "Intel"s) and v_contains(name_vec, "CPU"s)) { - auto cpu_pos = v_index(name_vec, "CPU"s); - if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1) != "@") - name = name_vec.at(cpu_pos + 1); - else - name.clear(); - } - else - name.clear(); - - if (name.empty() and not name_vec.empty()) { - for (const auto& n : name_vec) { - if (n == "@") break; - name += n + ' '; - } - name.pop_back(); - for (const auto& reg : {regex("Processor"), regex("CPU"), regex("\\(R\\)"), regex("\\(TM\\)"), regex("Intel"), - regex("AMD"), regex("Core"), regex("\\d?\\.?\\d+[mMgG][hH][zZ]")}) { - name = std::regex_replace(name, reg, ""); - } - name = trim(name); - } - } + string get_cpuName() + { + string name("11th Gen Intel(R) Core(TM) i5-11600 @ 2.80GHz"); return name; } - bool get_sensors() { - bool got_cpu = false, got_coretemp = false; - vector 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 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")) { - 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."); - } - } - + bool get_sensors() + { return not found_sensors.empty(); } - void update_sensors() { - if (cpu_sensor.empty()) return; + 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); + 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 (current_cpu.temp.at(0).size() > 20) + current_cpu.temp.at(0).pop_front(); - if (Config::getB("show_coretemp") and not cpu_temp_only) { + if (Config::getB("show_coretemp") and not cpu_temp_only) + { vector done; - for (const auto& sensor : core_sensors) { - if (v_contains(done, sensor)) continue; + 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 (cmp_less(core + 1, current_cpu.temp.size()) and cmp_less(temp, core_sensors.size())) { + for (const auto &[core, temp] : core_mapping) + { + if (cmp_less(core + 1, current_cpu.temp.size()) and cmp_less(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(); + if (current_cpu.temp.at(core + 1).size() > 20) + current_cpu.temp.at(core + 1).pop_front(); } } } } - string get_cpuHz() { - static int failed = 0; - if (failed > 4) return ""s; - string cpuhz; - try { - double hz = 0.0; - //? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster) - if (not freq_path.empty()) { - hz = stod(readfile(freq_path, "0.0")) / 1000; - if (hz <= 0.0 and ++failed >= 2) - freq_path.clear(); - } - //? If freq from /sys failed or is missing try to use /proc/cpuinfo - if (hz <= 0.0) { - ifstream cpufreq(Shared::procPath / "cpuinfo"); - if (cpufreq.good()) { - while (cpufreq.ignore(SSmax, '\n')) { - if (cpufreq.peek() == 'c') { - cpufreq.ignore(SSmax, ' '); - if (cpufreq.peek() == 'M') { - cpufreq.ignore(SSmax, ':'); - cpufreq.ignore(1); - cpufreq >> hz; - break; - } - } - } - } - } + string get_cpuHz() + { + uint64_t freq = 0; + size_t size = sizeof(freq); - if (hz <= 1 or hz >= 1000000) throw std::runtime_error("Failed to read /sys/devices/system/cpu/cpufreq/policy and /proc/cpuinfo."); - - if (hz >= 1000) { - if (hz >= 10000) cpuhz = to_string((int)round(hz / 1000)); // Future proof until we reach THz speeds :) - else cpuhz = to_string(round(hz / 100) / 10.0).substr(0, 3); - cpuhz += " GHz"; - } - else if (hz > 0) - cpuhz = to_string((int)round(hz)) + " MHz"; - - } - catch (const std::exception& e) { - if (++failed < 5) return ""s; - else { - Logger::warning("get_cpuHZ() : " + (string)e.what()); - return ""s; - } - } - - return cpuhz; + Logger::debug("get_cpuHz"); + return "1.0"; + // if (sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0) < 0) + // { + // perror("sysctl"); + // } + // Logger::debug("cpufreq:" + freq); + // return "" + freq; } - auto get_core_mapping() -> unordered_flat_map { + auto get_core_mapping() -> unordered_flat_map + { unordered_flat_map 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, n = 0; - for (string instr; cpuinfo >> instr;) { - if (instr == "processor") { - cpuinfo.ignore(SSmax, ':'); - cpuinfo >> cpu; - } - else if (instr.starts_with("core")) { - cpuinfo.ignore(SSmax, ':'); - cpuinfo >> 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 (cmp_less(core_map.size(), Shared::coreCount)) { - if (Shared::coreCount % 2 == 0 and (long)core_map.size() == Shared::coreCount / 2) { - 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, n = 0; i < Shared::coreCount; i++) { - if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; - core_map[i] = n++; - } - } - } - - //? Apply user set custom mapping if any - 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 cmp_greater(new_id, core_sensors.size())) continue; - core_map.at(change_id) = new_id; - } - } - catch (...) {} - } - return core_map; } - auto get_battery() -> tuple { - 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 get_battery() -> tuple + { + // if (not has_battery) + return {0, 0, ""}; } - 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; - - ifstream cread; - - try { - //? Get cpu load averages from /proc/loadavg - cread.open(Shared::procPath / "loadavg"); - if (cread.good()) { - cread >> cpu.load_avg[0] >> cpu.load_avg[1] >> cpu.load_avg[2]; - } - cread.close(); - - //? Get cpu total times for all cores from /proc/stat - cread.open(Shared::procPath / "stat"); - for (int i = 0; cread.good() and cread.peek() == 'c'; i++) { - cread.ignore(SSmax, ' '); - - //? Expected on kernel 2.6.3> : 0=user, 1=nice, 2=system, 3=idle, 4=iowait, 5=irq, 6=softirq, 7=steal, 8=guest, 9=guest_nice - vector times; - long long total_sum = 0; - - for (uint64_t val; cread >> val; total_sum += val) { - times.push_back(val); - } - cread.clear(); - if (times.size() < 4) throw std::runtime_error("Malformatted /proc/stat"); - - //? Subtract fields 8-9 and any future unknown fields - const long long totals = max(0ll, total_sum - (times.size() > 8 ? std::accumulate(times.begin() + 8, times.end(), 0) : 0)); - - //? Add iowait field if present - const long long idles = max(0ll, times.at(3) + (times.size() > 4 ? times.at(4) : 0)); - - //? Calculate values for totals from first line of stat - if (i == 0) { - const long long calc_totals = max(1ll, totals - cpu_old.at("totals")); - const long long calc_idles = max(1ll, idles - cpu_old.at("idles")); - cpu_old.at("totals") = totals; - cpu_old.at("idles") = idles; - - //? Total usage of cpu - cpu.cpu_percent.at("total").push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll)); - - //? Reduce size if there are more values than needed for graph - while (cmp_greater(cpu.cpu_percent.at("total").size(), width * 2)) cpu.cpu_percent.at("total").pop_front(); - - //? Populate cpu.cpu_percent with all fields from stat - for (int ii = 0; const auto& val : times) { - cpu.cpu_percent.at(time_names.at(ii)).push_back(clamp((long long)round((double)(val - cpu_old.at(time_names.at(ii))) * 100 / calc_totals), 0ll, 100ll)); - cpu_old.at(time_names.at(ii)) = val; - - //? Reduce size if there are more values than needed for graph - while (cmp_greater(cpu.cpu_percent.at(time_names.at(ii)).size(), width * 2)) cpu.cpu_percent.at(time_names.at(ii)).pop_front(); - - if (++ii == 10) break; - } - } - //? Calculate cpu total for each core - else { - if (i > Shared::coreCount) break; - const long long calc_totals = max(0ll, totals - core_old_totals.at(i-1)); - const long long calc_idles = max(0ll, idles - core_old_idles.at(i-1)); - core_old_totals.at(i-1) = totals; - core_old_idles.at(i-1) = idles; - - cpu.core_percent.at(i-1).push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll)); - - //? Reduce size if there are more values than needed for graph - if (cpu.core_percent.at(i-1).size() > 40) cpu.core_percent.at(i-1).pop_front(); - - } - } - } - catch (const std::exception& e) { - Logger::debug("get_cpuHz() : " + (string)e.what()); - if (cread.bad()) throw std::runtime_error("Failed to read /proc/stat"); - else throw std::runtime_error("collect() : " + (string)e.what()); - } + auto collect(const bool no_update) -> cpu_info & + { + Logger::debug("CPU collect"); + // if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) + // return current_cpu; + auto &cpu = current_cpu; if (Config::getB("show_cpu_freq")) cpuHz = get_cpuHz(); - if (Config::getB("check_temp") and got_sensors) - update_sensors(); - - if (Config::getB("show_battery") and has_battery) - current_bat = get_battery(); - return cpu; } } -namespace Mem { +namespace Mem +{ bool has_swap = false; vector fstab; fs::file_time_type fstab_time; int disk_ios = 0; vector last_found; - mem_info current_mem {}; + mem_info current_mem{}; - auto collect(const bool no_update) -> mem_info& { - if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) return current_mem; - auto& show_swap = Config::getB("show_swap"); - auto& swap_disk = Config::getB("swap_disk"); - auto& show_disks = Config::getB("show_disks"); - auto& mem = current_mem; - static const bool snapped = (getenv("BTOP_SNAPPED") != NULL); + auto collect(const bool no_update) -> mem_info & + { + Logger::debug("collect MEM"); + if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) + return current_mem; - mem.stats.at("swap_total") = 0; + auto &mem = current_mem; + FILE *fpIn = popen("/usr/bin/vm_stat", "r"); + if (fpIn) + { + char buf[512]; + while (fgets(buf, sizeof(buf), fpIn) != NULL) + { + char *tokens = strtok(buf, ":\n."); + while (tokens) { + char *label = tokens; + char *val = strtok(nullptr, ":\n."); + if (strstr(label, "Pages free")) + { + uint64_t f = stoull(trim(val)); + mem.stats.at("available") = f * 4096; + mem.stats.at("free") = f * 4096; + // } else if (strstr(label, "Pages free")) { - //? Read memory info from /proc/meminfo - ifstream meminfo(Shared::procPath / "meminfo"); - if (meminfo.good()) { - bool got_avail = false; - for (string label; meminfo >> label;) { - if (label == "MemFree:") { - meminfo >> mem.stats.at("free"); - mem.stats.at("free") <<= 10; + } + tokens = strtok(nullptr, ":\n."); } - else if (label == "MemAvailable:") { - meminfo >> mem.stats.at("available"); - mem.stats.at("available") <<= 10; - got_avail = true; - } - else if (label == "Cached:") { - meminfo >> mem.stats.at("cached"); - mem.stats.at("cached") <<= 10; - if (not show_swap and not swap_disk) break; - } - else if (label == "SwapTotal:") { - meminfo >> mem.stats.at("swap_total"); - mem.stats.at("swap_total") <<= 10; - } - else if (label == "SwapFree:") { - meminfo >> mem.stats.at("swap_free"); - mem.stats.at("swap_free") <<= 10; - break; - } - meminfo.ignore(SSmax, '\n'); } - if (not got_avail) mem.stats.at("available") = mem.stats.at("free") + mem.stats.at("cached"); - mem.stats.at("used") = Shared::totalMem - mem.stats.at("available"); - if (mem.stats.at("swap_total") > 0) mem.stats.at("swap_used") = mem.stats.at("swap_total") - mem.stats.at("swap_free"); + pclose(fpIn); } else - throw std::runtime_error("Failed to read /proc/meminfo"); - - meminfo.close(); - - //? Calculate percentages - for (const auto& name : mem_names) { - mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / Shared::totalMem)); - while (cmp_greater(mem.percent.at(name).size(), width * 2)) mem.percent.at(name).pop_front(); + { + Logger::error("failed to read vm_stat"); } - - if (show_swap and mem.stats.at("swap_total") > 0) { - for (const auto& name : swap_names) { - mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total"))); - while (cmp_greater(mem.percent.at(name).size(), width * 2)) mem.percent.at(name).pop_front(); - } - has_swap = true; - } - else - has_swap = false; - - //? Get disks stats - if (show_disks) { - double uptime = system_uptime(); - try { - auto& disks_filter = Config::getS("disks_filter"); - bool filter_exclude = false; - auto& use_fstab = Config::getB("use_fstab"); - auto& only_physical = Config::getB("only_physical"); - auto& disks = mem.disks; - ifstream diskread; - - vector filter; - if (not disks_filter.empty()) { - filter = ssplit(disks_filter); - if (filter.at(0).starts_with("exclude=")) { - filter_exclude = true; - filter.at(0) = filter.at(0).substr(8); - } - } - - //? Get list of "real" filesystems from /proc/filesystems - vector fstypes; - if (only_physical and not use_fstab) { - fstypes = {"zfs", "wslfs", "drvfs"}; - diskread.open(Shared::procPath / "filesystems"); - if (diskread.good()) { - for (string fstype; diskread >> fstype;) { - if (not is_in(fstype, "nodev", "squashfs", "nullfs")) - fstypes.push_back(fstype); - diskread.ignore(SSmax, '\n'); - } - } - else - throw std::runtime_error("Failed to read /proc/filesystems"); - diskread.close(); - } - - //? Get disk list to use from fstab if enabled - if (use_fstab and fs::last_write_time("/etc/fstab") != fstab_time) { - fstab.clear(); - fstab_time = fs::last_write_time("/etc/fstab"); - diskread.open("/etc/fstab"); - if (diskread.good()) { - for (string instr; diskread >> instr;) { - if (not instr.starts_with('#')) { - diskread >> instr; - if (snapped and instr == "/") fstab.push_back("/mnt"); - else if (not is_in(instr, "none", "swap")) fstab.push_back(instr); - } - diskread.ignore(SSmax, '\n'); - } - } - else - throw std::runtime_error("Failed to read /etc/fstab"); - diskread.close(); - } - - //? Get mounts from /etc/mtab or /proc/self/mounts - diskread.open((fs::exists("/etc/mtab") ? fs::path("/etc/mtab") : Shared::procPath / "self/mounts")); - if (diskread.good()) { - vector found; - found.reserve(last_found.size()); - string dev, mountpoint, fstype; - while (not diskread.eof()) { - std::error_code ec; - diskread >> dev >> mountpoint >> fstype; - - //? Match filter if not empty - if (not filter.empty()) { - bool match = v_contains(filter, mountpoint); - if ((filter_exclude and match) or (not filter_exclude and not match)) - continue; - } - - if ((not use_fstab and not only_physical) - or (use_fstab and v_contains(fstab, mountpoint)) - or (not use_fstab and only_physical and v_contains(fstypes, fstype))) { - found.push_back(mountpoint); - if (not v_contains(last_found, mountpoint)) redraw = true; - - //? Save mountpoint, name, dev path and path to /sys/block stat file - if (not disks.contains(mountpoint)) { - disks[mountpoint] = disk_info{fs::canonical(dev, ec), fs::path(mountpoint).filename()}; - if (disks.at(mountpoint).dev.empty()) disks.at(mountpoint).dev = dev; - if (disks.at(mountpoint).name.empty()) disks.at(mountpoint).name = (mountpoint == "/" or (snapped and mountpoint == "/mnt") ? "root" : mountpoint); - string devname = disks.at(mountpoint).dev.filename(); - while (devname.size() >= 2) { - if (fs::exists("/sys/block/" + devname + "/stat", ec) and access(string("/sys/block/" + devname + "/stat").c_str(), R_OK) == 0) { - disks.at(mountpoint).stat = "/sys/block/" + devname + "/stat"; - break; - } - devname.resize(devname.size() - 1); - } - } - - } - diskread.ignore(SSmax, '\n'); - } - //? Remove disks no longer mounted or filtered out - if (swap_disk and has_swap) found.push_back("swap"); - for (auto it = disks.begin(); it != disks.end();) { - if (not v_contains(found, it->first)) - it = disks.erase(it); - else - it++; - } - if (found.size() != last_found.size()) redraw = true; - last_found = std::move(found); - } - else - throw std::runtime_error("Failed to get mounts from /etc/mtab and /proc/self/mounts"); - diskread.close(); - - //? Get disk/partition stats - for (auto& [mountpoint, disk] : disks) { - if (std::error_code ec; not fs::exists(mountpoint, ec)) continue; - struct statvfs vfs; - if (statvfs(mountpoint.c_str(), &vfs) < 0) { - Logger::warning("Failed to get disk/partition stats with statvfs() for: " + mountpoint); - continue; - } - disk.total = vfs.f_blocks * vfs.f_frsize; - disk.free = vfs.f_bfree * vfs.f_frsize; - disk.used = disk.total - disk.free; - disk.used_percent = round((double)disk.used * 100 / disk.total); - disk.free_percent = 100 - disk.used_percent; - } - - //? Setup disks order in UI and add swap if enabled - mem.disks_order.clear(); - if (snapped and disks.contains("/mnt")) mem.disks_order.push_back("/mnt"); - else if (disks.contains("/")) mem.disks_order.push_back("/"); - if (swap_disk and has_swap) { - mem.disks_order.push_back("swap"); - if (not disks.contains("swap")) disks["swap"] = {"", "swap"}; - disks.at("swap").total = mem.stats.at("swap_total"); - disks.at("swap").used = mem.stats.at("swap_used"); - disks.at("swap").free = mem.stats.at("swap_free"); - disks.at("swap").used_percent = mem.percent.at("swap_used").back(); - disks.at("swap").free_percent = mem.percent.at("swap_free").back(); - } - for (const auto& name : last_found) - if (not is_in(name, "/", "swap")) mem.disks_order.push_back(name); - - //? Get disks IO - int64_t sectors_read, sectors_write, io_ticks; - disk_ios = 0; - for (auto& [ignored, disk] : disks) { - if (disk.stat.empty() or access(disk.stat.c_str(), R_OK) != 0) continue; - diskread.open(disk.stat); - if (diskread.good()) { - disk_ios++; - for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); } - diskread >> sectors_read; - if (disk.io_read.empty()) - disk.io_read.push_back(0); - else - disk.io_read.push_back(max((int64_t)0, (sectors_read - disk.old_io.at(0)) * 512)); - disk.old_io.at(0) = sectors_read; - while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); - - for (int i = 0; i < 3; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); } - diskread >> sectors_write; - if (disk.io_write.empty()) - disk.io_write.push_back(0); - else - disk.io_write.push_back(max((int64_t)0, (sectors_write - disk.old_io.at(1)) * 512)); - disk.old_io.at(1) = sectors_write; - while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front(); - - for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); } - diskread >> io_ticks; - if (disk.io_activity.empty()) - disk.io_activity.push_back(0); - else - disk.io_activity.push_back(clamp((long)round((double)(io_ticks - disk.old_io.at(2)) / (uptime - old_uptime) / 10), 0l, 100l)); - disk.old_io.at(2) = io_ticks; - while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); - } - diskread.close(); - } - old_uptime = uptime; - } - catch (const std::exception& e) { - Logger::warning("Error in Mem::collect() : " + (string)e.what()); - } - } - + mem.stats.at("used") = Shared::totalMem - mem.stats.at("available"); return mem; } } -namespace Net { +namespace Net +{ unordered_flat_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = { {"download", {}}, {"upload", {}} }; - unordered_flat_map> max_count = { {"download", {}}, {"upload", {}} }; + unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; + unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; //* RAII wrapper for getifaddrs - class getifaddr_wrapper { - struct ifaddrs* ifaddr; + class getifaddr_wrapper + { + struct ifaddrs *ifaddr; + public: int status; getifaddr_wrapper() { status = getifaddrs(&ifaddr); } ~getifaddr_wrapper() { freeifaddrs(ifaddr); } - auto operator()() -> struct ifaddrs* { return ifaddr; } + auto operator()() -> struct ifaddrs * { return ifaddr; } }; - auto collect(const bool no_update) -> net_info& { - auto& net = current_net; - auto& config_iface = Config::getS("net_iface"); - auto& net_sync = Config::getB("net_sync"); - auto& net_auto = Config::getB("net_auto"); - auto new_timestamp = time_ms(); + auto collect(const bool no_update) -> net_info & + { + Logger::debug("Net collect"); - if (not no_update and errors < 3) { - //? Get interface list using getifaddrs() wrapper - getifaddr_wrapper if_wrap {}; - if (if_wrap.status != 0) { - errors++; - Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); - redraw = true; - return empty_net; - } - int family = 0; - char ip[NI_MAXHOST]; - interfaces.clear(); - string ipv4, ipv6; - - //? Iteration over all items in getifaddrs() list - for (auto* ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) continue; - family = ifa->ifa_addr->sa_family; - const auto& iface = ifa->ifa_name; - - //? Get IPv4 address - if (family == AF_INET) { - if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) - net[iface].ipv4 = ip; - } - //? Get IPv6 address - else if (family == AF_INET6) { - if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) - net[iface].ipv6 = ip; - } - - //? Update available interfaces vector and get status of interface - if (not v_contains(interfaces, iface)) { - interfaces.push_back(iface); - net[iface].connected = (ifa->ifa_flags & IFF_RUNNING); - } - } - - //? Get total recieved and transmitted bytes + device address if no ip was found - for (const auto& iface : interfaces) { - if (net.at(iface).ipv4.empty() and net.at(iface).ipv6.empty()) - net.at(iface).ipv4 = readfile("/sys/class/net/" + iface + "/address"); - - for (const string dir : {"download", "upload"}) { - const fs::path sys_file = "/sys/class/net/" + iface + "/statistics/" + (dir == "download" ? "rx_bytes" : "tx_bytes"); - auto& saved_stat = net.at(iface).stat.at(dir); - auto& bandwidth = net.at(iface).bandwidth.at(dir); - - const uint64_t val = max((uint64_t)stoul(readfile(sys_file, "0")), saved_stat.last); - - //? Update speed, total and top values - saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000)); - if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed; - if (saved_stat.offset > val) saved_stat.offset = 0; - saved_stat.total = val - saved_stat.offset; - saved_stat.last = val; - - //? Add values to graph - bandwidth.push_back(saved_stat.speed); - while (cmp_greater(bandwidth.size(), width * 2)) bandwidth.pop_front(); - - //? Set counters for auto scaling - if (net_auto and selected_iface == iface) { - if (saved_stat.speed > graph_max[dir]) { - ++max_count[dir][0]; - if (max_count[dir][1] > 0) --max_count[dir][1]; - } - else if (graph_max[dir] > 10 << 10 and saved_stat.speed < graph_max[dir] / 10) { - ++max_count[dir][1]; - if (max_count[dir][0] > 0) --max_count[dir][0]; - } - - } - } - } - - //? Clean up net map if needed - if (net.size() > interfaces.size()) { - for (auto it = net.begin(); it != net.end();) { - if (not v_contains(interfaces, it->first)) - it = net.erase(it); - else - it++; - } - net.compact(); - } - - timestamp = new_timestamp; - } - - //? Return empty net_info struct if no interfaces was found - if (net.empty()) - return empty_net; - - //? Find an interface to display if selected isn't set or valid - if (selected_iface.empty() or not v_contains(interfaces, selected_iface)) { - max_count["download"][0] = max_count["download"][1] = max_count["upload"][0] = max_count["upload"][1] = 0; - redraw = true; - if (net_auto) rescale = true; - if (not config_iface.empty() and v_contains(interfaces, config_iface)) selected_iface = config_iface; - else { - //? Sort interfaces by total upload + download bytes - auto sorted_interfaces = interfaces; - rng::sort(sorted_interfaces, [&](const auto& a, const auto& b){ - return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total, - net.at(b).stat["download"].total + net.at(b).stat["upload"].total); - }); - selected_iface.clear(); - //? Try to set to a connected interface - for (const auto& iface : sorted_interfaces) { - if (net.at(iface).connected) selected_iface = iface; - break; - } - //? If no interface is connected set to first available - if (selected_iface.empty() and not sorted_interfaces.empty()) selected_iface = sorted_interfaces.at(0); - else if (sorted_interfaces.empty()) return empty_net; - - } - } - - //? Calculate max scale for graphs if needed - if (net_auto) { - bool sync = false; - for (const auto& dir: {"download", "upload"}) { - for (const auto& sel : {0, 1}) { - if (rescale or max_count[dir][sel] >= 5) { - const uint64_t avg_speed = (net[selected_iface].bandwidth[dir].size() > 5 - ? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0) / 5 - : net[selected_iface].stat[dir].speed); - graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10); - max_count[dir][0] = max_count[dir][1] = 0; - redraw = true; - if (net_sync) sync = true; - break; - } - } - //? Sync download/upload graphs if enabled - if (sync) { - const auto other = (string(dir) == "upload" ? "download" : "upload"); - graph_max[other] = graph_max[dir]; - max_count[other][0] = max_count[other][1] = 0; - break; - } - } - } - - rescale = false; - return net.at(selected_iface); + return empty_net; } } -namespace Proc { +namespace Proc +{ vector current_procs; unordered_flat_map uid_user; @@ -1129,59 +345,69 @@ namespace Proc { detail_container detailed; //* Generate process tree list - void _tree_gen(proc_info& cur_proc, vector& in_procs, vector>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false, const bool no_update=false, const bool should_filter=false) { + void _tree_gen(proc_info &cur_proc, vector &in_procs, vector> &out_procs, int cur_depth, const bool collapsed, const string &filter, bool found = false, const bool no_update = false, const bool should_filter = false) + { auto cur_pos = out_procs.size(); bool filtering = false; //? If filtering, include children of matching processes - if (not found and (should_filter or not filter.empty())) { - if (not s_contains(std::to_string(cur_proc.pid), filter) - and not s_contains(cur_proc.name, filter) - and not s_contains(cur_proc.cmd, filter) - and not s_contains(cur_proc.user, filter)) { + if (not found and (should_filter or not filter.empty())) + { + if (not s_contains(std::to_string(cur_proc.pid), filter) and not s_contains(cur_proc.name, filter) and not s_contains(cur_proc.cmd, filter) and not s_contains(cur_proc.user, filter)) + { filtering = true; cur_proc.filtered = true; filter_found++; } - else { + else + { found = true; cur_depth = 0; } } - else if (cur_proc.filtered) cur_proc.filtered = false; + else if (cur_proc.filtered) + cur_proc.filtered = false; //? Set tree index position for process if not filtered out or currently in a collapsed sub-tree - if (not collapsed and not filtering) { + if (not collapsed and not filtering) + { out_procs.push_back(std::ref(cur_proc)); cur_proc.tree_index = out_procs.size() - 1; //? Try to find name of the binary file and append to program name if not the same - if (cur_proc.short_cmd.empty() and not cur_proc.cmd.empty()) { + if (cur_proc.short_cmd.empty() and not cur_proc.cmd.empty()) + { std::string_view cmd_view = cur_proc.cmd; cmd_view = cmd_view.substr((size_t)0, min(cmd_view.find(' '), cmd_view.size())); cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size())); cur_proc.short_cmd = (string)cmd_view; } } - else { + else + { cur_proc.tree_index = in_procs.size(); } //? Recursive iteration over all children int children = 0; - for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) { - if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) { + for (auto &p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) + { + if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) + { out_procs.back().get().cpu_p += p.cpu_p; out_procs.back().get().mem += p.mem; out_procs.back().get().threads += p.threads; filter_found++; } - if (collapsed and not filtering) { + if (collapsed and not filtering) + { cur_proc.filtered = true; } - else children++; + else + children++; _tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter); } - if (collapsed or filtering) return; + if (collapsed or filtering) + return; //? Add tree terminator symbol if it's the last child in a sub-tree if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─")) @@ -1192,437 +418,32 @@ namespace Proc { } //* Get detailed info for selected process - void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { - fs::path pid_path = Shared::procPath / std::to_string(pid); - - if (pid != detailed.last_pid) { - detailed = {}; - detailed.last_pid = pid; - detailed.skip_smaps = not Config::getB("proc_info_smaps"); - } - - //? Copy proc_info for process from proc vector - auto p_info = rng::find(procs, pid, &proc_info::pid); - detailed.entry = *p_info; - - //? Update cpu percent deque for process cpu graph - if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount; - detailed.cpu_percent.push_back(clamp((long long)round(detailed.entry.cpu_p), 0ll, 100ll)); - while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front(); - - //? Process runtime - detailed.elapsed = sec_to_dhms(uptime - (detailed.entry.cpu_s / Shared::clkTck)); - if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3); - - //? Get parent process name - if (detailed.parent.empty()) { - auto p_entry = rng::find(procs, detailed.entry.ppid, &proc_info::pid); - if (p_entry != procs.end()) detailed.parent = p_entry->name; - } - - //? Expand process status from single char to explanative string - detailed.status = (proc_states.contains(detailed.entry.state)) ? proc_states.at(detailed.entry.state) : "Unknown"; - - ifstream d_read; - string short_str; - - //? Try to get RSS mem from proc/[pid]/smaps - detailed.memory.clear(); - if (not detailed.skip_smaps and fs::exists(pid_path / "smaps")) { - d_read.open(pid_path / "smaps"); - uint64_t rss = 0; - try { - while (d_read.good()) { - d_read.ignore(SSmax, 'R'); - if (d_read.peek() == 's') { - d_read.ignore(SSmax, ':'); - getline(d_read, short_str, 'k'); - rss += stoull(short_str); - } - } - if (rss == detailed.entry.mem >> 10) - detailed.skip_smaps = true; - else { - detailed.mem_bytes.push_back(rss << 10); - detailed.memory = floating_humanizer(rss, false, 1); - } - } - catch (const std::invalid_argument&) {} - catch (const std::out_of_range&) {} - d_read.close(); - } - if (detailed.memory.empty()) { - detailed.mem_bytes.push_back(detailed.entry.mem); - detailed.memory = floating_humanizer(detailed.entry.mem); - } - if (detailed.first_mem == -1 or detailed.first_mem < detailed.mem_bytes.back() / 2 or detailed.first_mem > detailed.mem_bytes.back() * 4) { - detailed.first_mem = min((uint64_t)detailed.mem_bytes.back() * 2, Shared::totalMem); - redraw = true; - } - - while (cmp_greater(detailed.mem_bytes.size(), width)) detailed.mem_bytes.pop_front(); - - //? Get bytes read and written from proc/[pid]/io - if (fs::exists(pid_path / "io")) { - d_read.open(pid_path / "io"); - try { - string name; - while (d_read.good()) { - getline(d_read, name, ':'); - if (name.ends_with("read_bytes")) { - getline(d_read, short_str); - detailed.io_read = floating_humanizer(stoull(short_str)); - } - else if (name.ends_with("write_bytes")) { - getline(d_read, short_str); - detailed.io_write = floating_humanizer(stoull(short_str)); - break; - } - else - d_read.ignore(SSmax, '\n'); - } - } - catch (const std::invalid_argument&) {} - catch (const std::out_of_range&) {} - d_read.close(); - } + void _collect_details(const size_t pid, const uint64_t uptime, vector &procs) + { } //* Collects and sorts process information from /proc - auto collect(const bool no_update) -> vector& { - const auto& sorting = Config::getS("proc_sorting"); - const auto& reverse = Config::getB("proc_reversed"); - const auto& filter = Config::getS("proc_filter"); - const auto& per_core = Config::getB("proc_per_core"); - const auto& tree = Config::getB("proc_tree"); - const auto& show_detailed = Config::getB("show_detailed"); - const size_t detailed_pid = Config::getI("detailed_pid"); - bool should_filter = current_filter != filter; - if (should_filter) current_filter = filter; - const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter); - if (sorted_change) { - current_sort = sorting; - current_rev = reverse; - } - ifstream pread; - string long_string; - string short_str; - - const double uptime = system_uptime(); - - const int cmult = (per_core) ? Shared::coreCount : 1; - bool got_detailed = false; - - //* Use pids from last update if only changing filter, sorting or tree options - if (no_update and not current_procs.empty()) { - if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, round(uptime), current_procs); - } - //* ---------------------------------------------Collection start---------------------------------------------- - else { - should_filter = true; - - //? Update uid_user map if /etc/passwd changed since last run - if (not Shared::passwd_path.empty() and fs::last_write_time(Shared::passwd_path) != passwd_time) { - string r_uid, r_user; - passwd_time = fs::last_write_time(Shared::passwd_path); - uid_user.clear(); - pread.open(Shared::passwd_path); - if (pread.good()) { - while (not pread.eof()) { - getline(pread, r_user, ':'); - pread.ignore(SSmax, ':'); - getline(pread, r_uid, ':'); - uid_user[r_uid] = r_user; - pread.ignore(SSmax, '\n'); - } - } - else { - Shared::passwd_path.clear(); - } - pread.close(); - } - - //? Get cpu total times from /proc/stat - cputimes = 0; - pread.open(Shared::procPath / "stat"); - if (pread.good()) { - pread.ignore(SSmax, ' '); - for (uint64_t times; pread >> times; cputimes += times); - } - else throw std::runtime_error("Failure to read /proc/stat"); - pread.close(); - - //? Iterate over all pids in /proc - vector found; - for (const auto& d: fs::directory_iterator(Shared::procPath)) { - if (Runner::stopping) - return current_procs; - if (pread.is_open()) pread.close(); - - const string pid_str = d.path().filename(); - if (not isdigit(pid_str[0])) continue; - - const size_t pid = stoul(pid_str); - found.push_back(pid); - - //? Check if pid already exists in current_procs - auto find_old = rng::find(current_procs, pid, &proc_info::pid); - bool no_cache = false; - if (find_old == current_procs.end()) { - current_procs.push_back({pid}); - find_old = current_procs.end() - 1; - no_cache = true; - } - - auto& new_proc = *find_old; - - //? Get program name, command and username - if (no_cache) { - pread.open(d.path() / "comm"); - if (not pread.good()) continue; - getline(pread, new_proc.name); - pread.close(); - //? Check for whitespace characters in name and set offset to get correct fields from stat file - new_proc.name_offset = rng::count(new_proc.name, ' '); - - pread.open(d.path() / "cmdline"); - if (not pread.good()) continue; - long_string.clear(); - while(getline(pread, long_string, '\0')) new_proc.cmd += long_string + ' '; - pread.close(); - if (not new_proc.cmd.empty()) new_proc.cmd.pop_back(); - - pread.open(d.path() / "status"); - if (not pread.good()) continue; - string uid; - string line; - while (not pread.eof()) { - getline(pread, line, ':'); - if (line == "Uid") { - pread.ignore(); - getline(pread, uid, '\t'); - break; - } else { - pread.ignore(SSmax, '\n'); - } - } - pread.close(); - new_proc.user = (uid_user.contains(uid)) ? uid_user.at(uid) : uid; - } - - //? Parse /proc/[pid]/stat - pread.open(d.path() / "stat"); - if (not pread.good()) continue; - - const auto& offset = new_proc.name_offset; - short_str.clear(); - int x = 0, next_x = 3; - uint64_t cpu_t = 0; - try { - for (;;) { - while (++x < next_x + offset) pread.ignore(SSmax, ' '); - getline(pread, short_str, ' '); - if (not pread.good()) break; - - switch (x-offset) { - case 3: //? Process state - new_proc.state = short_str.at(0); - if (new_proc.ppid != 0) next_x = 14; - continue; - case 4: //? Parent pid - new_proc.ppid = stoull(short_str); - next_x = 14; - continue; - case 14: //? Process utime - cpu_t = stoull(short_str); - continue; - case 15: //? Process stime - cpu_t += stoull(short_str); - next_x = 19; - continue; - case 19: //? Nice value - new_proc.p_nice = stoull(short_str); - continue; - case 20: //? Number of threads - new_proc.threads = stoull(short_str); - if (new_proc.cpu_s == 0) { - next_x = 22; - new_proc.cpu_t = cpu_t; - } - else - next_x = 24; - continue; - case 22: //? Get cpu seconds if missing - new_proc.cpu_s = stoull(short_str); - next_x = 24; - continue; - case 24: //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x) - if (cmp_greater(short_str.size(), Shared::totalMem_len)) - new_proc.mem = Shared::totalMem; - else - new_proc.mem = stoull(short_str) * Shared::pageSize; - } - break; - } - - } - catch (const std::invalid_argument&) { continue; } - catch (const std::out_of_range&) { continue; } - - pread.close(); - - if (x-offset < 24) continue; - - //? Get RSS memory from /proc/[pid]/statm if value from /proc/[pid]/stat looks wrong - if (new_proc.mem >= Shared::totalMem) { - pread.open(d.path() / "statm"); - if (not pread.good()) continue; - pread.ignore(SSmax, ' '); - pread >> new_proc.mem; - new_proc.mem *= Shared::pageSize; - pread.close(); - } - - //? Process cpu usage since last update - new_proc.cpu_p = clamp(round(cmult * 1000 * (cpu_t - new_proc.cpu_t) / max((uint64_t)1, cputimes - old_cputimes)) / 10.0, 0.0, 100.0 * Shared::coreCount); - - //? 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 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) { - got_detailed = true; - } - } - - //? Clear dead processes from current_procs - auto eraser = rng::remove_if(current_procs, [&](const auto& element){ return not v_contains(found, element.pid); }); - current_procs.erase(eraser.begin(), eraser.end()); - - //? Update the details info box for process if active - if (show_detailed and got_detailed) { - _collect_details(detailed_pid, round(uptime), current_procs); - } - else if (show_detailed and not got_detailed and detailed.status != "Dead") { - detailed.status = "Dead"; - redraw = true; - } - - old_cputimes = cputimes; - } - //* ---------------------------------------------Collection done----------------------------------------------- - - //* 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 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; - } - if (reverse) rng::reverse(current_procs); - - //* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage - if (not tree and not reverse and sorting == "cpu lazy") { - double max = 10.0, target = 30.0; - for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) { - if (i <= 5 and current_procs.at(i).cpu_p > max) - max = current_procs.at(i).cpu_p; - else if (i == 6) - target = (max > 30.0) ? max : 10.0; - if (i == offset and current_procs.at(i).cpu_p > 30.0) - offset++; - else if (current_procs.at(i).cpu_p > target) { - rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1); - if (++x > 10) break; - } - } - } - } - - //* Match filter if defined - if (should_filter) { - filter_found = 0; - for (auto& p : current_procs) { - if (not tree and not filter.empty()) { - if (not s_contains(to_string(p.pid), filter) - and not s_contains(p.name, filter) - and not s_contains(p.cmd, filter) - and not s_contains(p.user, filter)) { - p.filtered = true; - filter_found++; - } - else { - p.filtered = false; - } - } - else { - p.filtered = false; - } - } - } - - //* Generate tree view if enabled - if (tree and (not no_update or should_filter or sorted_change)) { - if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) { - auto collapser = rng::find(current_procs, find_pid, &proc_info::pid); - if (collapser != current_procs.end()) { - if (collapse == expand) { - collapser->collapsed = not collapser->collapsed; - } - else if (collapse > -1) { - collapser->collapsed = true; - } - else if (expand > -1) { - collapser->collapsed = false; - } - } - collapse = expand = -1; - } - if (should_filter or not filter.empty()) filter_found = 0; - - vector> tree_procs; - tree_procs.reserve(current_procs.size()); - - //? Stable sort to retain selected sorting among processes with the same parent - rng::stable_sort(current_procs, rng::less{}, &proc_info::ppid); - - //? Start recursive iteration over processes with the lowest shared parent pids - for (auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { - _tree_gen(p, current_procs, tree_procs, 0, false, filter, false, no_update, should_filter); - } - - //? Final sort based on tree index - rng::sort(current_procs, rng::less{}, &proc_info::tree_index); - if (reverse) rng::reverse(current_procs); - - } - - numpids = (int)current_procs.size() - filter_found; - + auto collect(const bool no_update) -> vector & + { + Logger::debug("PROC collect"); + // proc_info init; + // current_procs.push_back(init); return current_procs; } } -namespace Tools { - double system_uptime() { - string upstr; - ifstream pread(Shared::procPath / "uptime"); - if (pread.good()) { - try { - getline(pread, upstr, ' '); - pread.close(); - return stod(upstr); - } - catch (const std::invalid_argument&) {} - catch (const std::out_of_range&) {} +namespace Tools +{ + double system_uptime() + { + struct timeval ts; + std::size_t len = sizeof(ts); + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + if (sysctl(mib, 2, &ts, &len, NULL, 0) == 0) + { + string uptime = "" + static_cast(ts.tv_sec); + return stod(uptime); } - throw std::runtime_error("Failed get uptime from from " + (string)Shared::procPath + "/uptime"); + return 0.0; } } \ No newline at end of file