From 84a974695afaadc53d1d8576ea66255166ede482 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 28 Sep 2021 23:37:03 +0200 Subject: [PATCH 01/28] make it compile on macos (M1 - arm64). Does not run though --- Makefile | 4 +- src/btop.cpp | 4 + src/btop_tools.hpp | 7 + src/osx/btop_collect.cpp | 1628 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1641 insertions(+), 2 deletions(-) create mode 100644 src/osx/btop_collect.cpp diff --git a/Makefile b/Makefile index e6b4d44..e1b5618 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,8 @@ OBJEXT := o #? Flags, Libraries and Includes override REQFLAGS := -std=c++20 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) +OPTFLAGS ?= -O2 #-ftree-loop-vectorize -flto=$(THREADS) +LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) INC := -I$(INCDIR) -I$(SRCDIR) diff --git a/src/btop.cpp b/src/btop.cpp index 0bd5ab8..945df32 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -225,7 +225,11 @@ void clean_quit(int sig) { //? Assume error if still not cleaned up and call quick_exit to avoid a segfault from Tools::atomic_lock destructor if (Tools::active_locks > 0) { +#ifdef __APPLE__ + exit((sig != -1 ? sig : 0)); +#else quick_exit((sig != -1 ? sig : 0)); +#endif } if (sig != -1) exit(sig); diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 7bb0445..adaaf02 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -32,6 +32,13 @@ tab-size = 4 using std::string, std::vector, std::atomic, std::to_string, std::regex, std::tuple, std::array; +#ifndef HOST_NAME_MAX +#if defined(__APPLE__) +#define HOST_NAME_MAX 255 +#else +#define HOST_NAME_MAX 64 +#endif /* __APPLE__ */ +#endif /* HOST_NAME_MAX */ //? ------------------------------------------------- NAMESPACES ------------------------------------------------------ diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp new file mode 100644 index 0000000..d8ea597 --- /dev/null +++ b/src/osx/btop_collect.cpp @@ -0,0 +1,1628 @@ +/* Copyright 2021 Aristocratos (jakob@qvantnet.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +indent = tab +tab-size = 4 +*/ + +#include +#include +#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; +namespace fs = std::filesystem; +namespace rng = std::ranges; +using namespace Tools; + +//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- + +namespace Cpu { + vector core_old_totals; + vector core_old_idles; + vector available_fields; + vector available_sensors = {"Auto"}; + 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(); + + struct Sensor { + fs::path path; + string label; + int64_t temp = 0; + int64_t high = 0; + int64_t crit = 0; + }; + + unordered_flat_map found_sensors; + string cpu_sensor; + vector core_sensors; + unordered_flat_map core_mapping; +} + +namespace Mem { + double old_uptime; +} + +namespace Shared { + + fs::path procPath, passwd_path; + uint64_t totalMem; + long pageSize, clkTck, coreCount; + int totalMem_len; + + void 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."); + + coreCount = sysconf(_SC_NPROCESSORS_ONLN); + if (coreCount < 1) { + coreCount = 1; + Logger::warning("Could not determine number of cores, defaulting to 1."); + } + + pageSize = sysconf(_SC_PAGE_SIZE); + 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) { + 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; + } + 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(); + + } + +} + +namespace Cpu { + string cpuName; + string cpuHz; + bool has_battery = true; + tuple current_bat; + + 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} + }; + + 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); + } + } + + 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."); + } + } + + 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 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 (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(); + } + } + } + } + + string get_cpuHz() { + static int failed = 0; + if (failed > 4) return ""s; + string cpuhz; + try { + double hz = 0.0; + //? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster) + if (not freq_path.empty()) { + hz = stod(readfile(freq_path, "0.0")) / 1000; + if (hz <= 0.0 and ++failed >= 2) + freq_path.clear(); + } + //? If freq from /sys failed or is missing try to use /proc/cpuinfo + if (hz <= 0.0) { + ifstream cpufreq(Shared::procPath / "cpuinfo"); + if (cpufreq.good()) { + while (cpufreq.ignore(SSmax, '\n')) { + if (cpufreq.peek() == 'c') { + cpufreq.ignore(SSmax, ' '); + if (cpufreq.peek() == 'M') { + cpufreq.ignore(SSmax, ':'); + cpufreq.ignore(1); + cpufreq >> hz; + break; + } + } + } + } + } + + if (hz <= 1 or hz >= 1000000) throw std::runtime_error("Failed to read /sys/devices/system/cpu/cpufreq/policy and /proc/cpuinfo."); + + if (hz >= 1000) { + if (hz >= 10000) cpuhz = to_string((int)round(hz / 1000)); // Future proof until we reach THz speeds :) + else cpuhz = to_string(round(hz / 100) / 10.0).substr(0, 3); + cpuhz += " GHz"; + } + else if (hz > 0) + cpuhz = to_string((int)round(hz)) + " MHz"; + + } + catch (const std::exception& e) { + if (++failed < 5) return ""s; + else { + Logger::warning("get_cpuHZ() : " + (string)e.what()); + return ""s; + } + } + + return cpuhz; + } + + 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 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()); + } + + 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 { + bool has_swap = false; + vector fstab; + fs::file_time_type fstab_time; + int disk_ios = 0; + vector last_found; + + 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); + + mem.stats.at("swap_total") = 0; + + //? 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; + } + 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"); + } + 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(); + } + + 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()); + } + } + + return mem; + } + +} + +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", {}} }; + bool rescale = true; + uint64_t timestamp = 0; + + //* RAII wrapper for getifaddrs + 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 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(); + + 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); + } +} + +namespace Proc { + + vector current_procs; + unordered_flat_map uid_user; + string current_sort; + string current_filter; + bool current_rev = false; + + fs::file_time_type passwd_time; + + uint64_t cputimes; + int collapse = -1, expand = -1; + uint64_t old_cputimes = 0; + atomic numpids = 0; + int filter_found = 0; + + 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) { + 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)) { + filtering = true; + cur_proc.filtered = true; + filter_found++; + } + else { + found = true; + cur_depth = 0; + } + } + 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) { + 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()) { + 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 { + 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)) { + 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) { + cur_proc.filtered = true; + } + 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; + + //? 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("]─")) + out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ "); + + //? Add collapse/expand symbols if process have any children + out_procs.at(cur_pos).get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ "); + } + + //* 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(); + } + } + + //* 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; + + 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&) {} + } + throw std::runtime_error("Failed get uptime from from " + (string)Shared::procPath + "/uptime"); + } +} \ No newline at end of file From fe4db7c16cd349053385eda62e9f2df2e7344d3d Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sat, 2 Oct 2021 23:48:28 +0200 Subject: [PATCH 02/28] 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 From 9732507248b30139d4af54615945c0b4737cff7d Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sat, 2 Oct 2021 23:51:29 +0200 Subject: [PATCH 03/28] extract delimiters --- src/osx/btop_collect.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 0cb8213..49fd7ff 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -266,10 +266,11 @@ namespace Mem char buf[512]; while (fgets(buf, sizeof(buf), fpIn) != NULL) { - char *tokens = strtok(buf, ":\n."); + char *delim = ":\n."; + char *tokens = strtok(buf, delim); while (tokens) { char *label = tokens; - char *val = strtok(nullptr, ":\n."); + char *val = strtok(nullptr, delim); if (strstr(label, "Pages free")) { uint64_t f = stoull(trim(val)); @@ -278,7 +279,7 @@ namespace Mem // } else if (strstr(label, "Pages free")) { } - tokens = strtok(nullptr, ":\n."); + tokens = strtok(nullptr, delim); } } pclose(fpIn); From 0983917f26948d83fd8da103903ec99f88058d87 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sat, 2 Oct 2021 23:53:41 +0200 Subject: [PATCH 04/28] comment --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 35fcd49..fe01879 100644 --- a/Makefile +++ b/Makefile @@ -89,6 +89,7 @@ INC := -I$(INCDIR) -I$(SRCDIR) SU_USER := root SU_GROUP := root +#? This fails to compile on M1 macos (arm64 specific? as it compiles on x86_64 macos) ifeq ($(ARCH),x86_64) override OPTFLAGS += -ftree-loop-vectorize -flto=$(THREADS) endif From 66534eb5b50753217687de0414d390bcb2a14cf1 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sun, 3 Oct 2021 21:45:39 +0200 Subject: [PATCH 05/28] initialize mutex (needed on macos apparently and not on linux) --- src/btop.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index 7349916..06f933a 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -296,7 +296,7 @@ namespace Runner { pthread_mutex_t& pt_mutex; public: int status; - thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { status = pthread_mutex_lock(&pt_mutex); } + thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { pthread_mutex_init(&mtx, NULL); status = pthread_mutex_lock(&pt_mutex); } ~thread_lock() { if (status == 0) pthread_mutex_unlock(&pt_mutex); } }; @@ -306,7 +306,7 @@ namespace Runner { sigset_t mask; pthread_t runner_id; pthread_mutex_t mtx; - + const unordered_flat_map box_bits = { {"proc", 0b0000'0001}, {"net", 0b0000'0100}, @@ -381,13 +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) { - // 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; - // } + if (pt_lck.status != 0) { + Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status); + Logger::error(Global::exit_error_msg); + Global::thread_exception = true; + Input::interrupt = true; + stopping = true; + } //* ----------------------------------------------- THREAD LOOP ----------------------------------------------- while (not Global::quitting) { From 2a44b307ef9e947c1007a86988876668a5731e64 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sun, 3 Oct 2021 21:46:11 +0200 Subject: [PATCH 06/28] basic process info --- src/osx/btop_collect.cpp | 68 +++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 49fd7ff..8fb601b 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -29,6 +29,7 @@ tab-size = 4 #include #include #include +#include #include #include @@ -92,7 +93,6 @@ namespace Shared void init() { - Logger::debug("Shared init"); //? Shared global variables init // passwd_path = (fs::is_regular_file(fs::path("/etc/passwd")) and access("/etc/passwd", R_OK) != -1) ? "/etc/passwd" : ""; @@ -122,7 +122,6 @@ namespace Shared 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"); @@ -207,7 +206,6 @@ namespace Cpu uint64_t freq = 0; size_t size = sizeof(freq); - Logger::debug("get_cpuHz"); return "1.0"; // if (sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0) < 0) // { @@ -231,9 +229,8 @@ namespace Cpu 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; + 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")) @@ -255,7 +252,6 @@ namespace Mem 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; @@ -268,7 +264,8 @@ namespace Mem { char *delim = ":\n."; char *tokens = strtok(buf, delim); - while (tokens) { + while (tokens) + { char *label = tokens; char *val = strtok(nullptr, delim); if (strstr(label, "Pages free")) @@ -276,8 +273,7 @@ namespace Mem uint64_t f = stoull(trim(val)); mem.stats.at("available") = f * 4096; mem.stats.at("free") = f * 4096; - // } else if (strstr(label, "Pages free")) { - + // } else if (strstr(label, "Pages free")) { } tokens = strtok(nullptr, delim); } @@ -320,8 +316,6 @@ namespace Net auto collect(const bool no_update) -> net_info & { - Logger::debug("Net collect"); - return empty_net; } } @@ -426,9 +420,48 @@ namespace Proc //* Collects and sorts process information from /proc auto collect(const bool no_update) -> vector & { - Logger::debug("PROC collect"); - // proc_info init; - // current_procs.push_back(init); + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; + struct kinfo_proc *processes = NULL; + const double uptime = system_uptime(); + auto procs = ¤t_procs; + + for (int retry = 3; retry > 0; retry--) + { + size_t size = 0; + if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) + { + Logger::error("Unable to get size of kproc_infos"); + } + + processes = (struct kinfo_proc *)malloc(size); + + if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) + { + size_t count = size / sizeof(struct kinfo_proc); + for (size_t i = 0; i < count; i++) + { + struct kinfo_proc kproc = processes[i]; + Proc::proc_info p{kproc.kp_proc.p_pid}; + char fullname[PROC_PIDPATHINFO_MAXSIZE]; + proc_pidpath(p.pid, fullname, sizeof(fullname)); + p.cmd = std::string(fullname); + size_t lastSlash = p.cmd.find_last_of('/'); + p.name = p.cmd.substr(lastSlash + 1); + p.ppid = kproc.kp_eproc.e_ppid; + p.p_nice = kproc.kp_proc.p_nice; + struct proc_taskinfo pti; + if (sizeof(pti) == proc_pidinfo(p.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) + { + p.threads = pti.pti_threadnum; + p.cpu_t = pti.pti_total_user + pti.pti_total_system; + p.cpu_c = (double)p.cpu_t / max(1.0, (uptime * Shared::clkTck) - p.cpu_s); + p.cpu_p = 0; + p.cpu_s = pti.pti_total_system; + } + procs->push_back(p); + } + } + } return current_procs; } } @@ -440,10 +473,9 @@ namespace Tools 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) + if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) { - string uptime = "" + static_cast(ts.tv_sec); - return stod(uptime); + return ts.tv_sec; } return 0.0; } From 53e379d74dffe2282b089450728501b51d13d199 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sun, 3 Oct 2021 22:08:21 +0200 Subject: [PATCH 07/28] cpu freq, name & process uid/name --- src/osx/btop_collect.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 8fb601b..addafa6 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -30,6 +30,7 @@ tab-size = 4 #include #include #include +#include #include #include @@ -127,6 +128,8 @@ namespace Shared Logger::warning("Could not get memory size"); } totalMem = memsize; + + Cpu::cpuName = Cpu::get_cpuName(); } } @@ -156,9 +159,14 @@ namespace Cpu string get_cpuName() { - string name("11th Gen Intel(R) Core(TM) i5-11600 @ 2.80GHz"); - - return name; + char buffer[1024]; + size_t size = sizeof(buffer); + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &size, NULL, 0) < 0) + { + Logger::error("Failed to get CPU name"); + return ""; + } + return string(buffer); } bool get_sensors() @@ -207,12 +215,11 @@ namespace Cpu size_t size = sizeof(freq); return "1.0"; - // if (sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0) < 0) - // { - // perror("sysctl"); - // } - // Logger::debug("cpufreq:" + freq); - // return "" + freq; + if (sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0) < 0) + { + Logger::error("Failed to get CPU frequency"); + } + return "" + freq; } auto get_core_mapping() -> unordered_flat_map @@ -458,6 +465,8 @@ namespace Proc p.cpu_p = 0; p.cpu_s = pti.pti_total_system; } + struct passwd *pwd = getpwuid(kproc.kp_eproc.e_ucred.cr_uid); + p.user = pwd->pw_name; procs->push_back(p); } } @@ -470,12 +479,13 @@ namespace Tools { double system_uptime() { - struct timeval ts; + struct timeval ts, currTime; std::size_t len = sizeof(ts); int mib[2] = {CTL_KERN, KERN_BOOTTIME}; if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) { - return ts.tv_sec; + gettimeofday(&currTime, NULL); + return currTime.tv_sec - ts.tv_sec; } return 0.0; } From 8462ae6431fcfe1985d1bbb4404452ddc03cc1de Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sun, 3 Oct 2021 22:42:01 +0200 Subject: [PATCH 08/28] don't crash on deque::back() --- src/btop_draw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 6a8cf68..0f1f93e 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -814,7 +814,7 @@ namespace Mem { if (title.empty()) title = capitalize(name); const string humanized = floating_humanizer(mem.stats.at(name)); const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back())); - if (mem_size > 2) { + if (mem_size > 2 && mem.percent.at(name).size() > 0) { out += Mv::to(y+1+cy, x+1+cx) + divider + ljust(title, 4, false, false, not big_mem) + ljust(":", (big_mem ? 1 : 6)) + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + trans(humanized) + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4); From 17f9f3703c1dd52a86176131a2239cd52ee285bd Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sun, 3 Oct 2021 22:56:14 +0200 Subject: [PATCH 09/28] try to get disks to show --- src/osx/btop_collect.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index addafa6..e44e323 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -262,6 +262,7 @@ namespace Mem if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) return current_mem; + auto &show_disks = Config::getB("show_disks"); auto &mem = current_mem; FILE *fpIn = popen("/usr/bin/vm_stat", "r"); if (fpIn) @@ -280,7 +281,8 @@ namespace Mem uint64_t f = stoull(trim(val)); mem.stats.at("available") = f * 4096; mem.stats.at("free") = f * 4096; - // } else if (strstr(label, "Pages free")) { + mem.stats.at("cached") = 1; + mem.stats.at("used") = Shared::totalMem - (f*4096); } tokens = strtok(nullptr, delim); } @@ -291,7 +293,30 @@ namespace Mem { Logger::error("failed to read vm_stat"); } - mem.stats.at("used") = Shared::totalMem - mem.stats.at("available"); + //? 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(); + } + + if (show_disks) + { + auto& disks = mem.disks; + struct statfs *stfs; + int count = getmntinfo(&stfs, MNT_WAIT); + for (int i = 0; i < count; i++) + { + std::error_code ec; + string mountpoint = stfs[i].f_mntonname; + Logger::debug("found mountpoint " + mountpoint); + string dev = stfs[i].f_mntfromname; + 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 == "/" ? "root" : mountpoint); + disks.at(mountpoint).free = stfs[i].f_bfree; + disks.at(mountpoint).total = stfs[i].f_iosize; + } + } return mem; } From 1fd625086ba42e9440c463ae940563d934b8b5c3 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Sun, 3 Oct 2021 23:21:13 +0200 Subject: [PATCH 10/28] decrease diff with upstream --- src/btop.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index 481fa23..c7428fd 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -306,7 +306,6 @@ namespace Runner { sigset_t mask; pthread_t runner_id; pthread_mutex_t mtx; - const unordered_flat_map box_bits = { {"proc", 0b0000'0001}, {"net", 0b0000'0100}, @@ -852,7 +851,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(); From 264bf2d7da0e3fabb5987cddee73762e52170a51 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Mon, 4 Oct 2021 09:15:35 +0200 Subject: [PATCH 11/28] reformat --- src/osx/btop_collect.cpp | 253 +++++++++++++++------------------------ 1 file changed, 96 insertions(+), 157 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index e44e323..d95332f 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -16,25 +16,25 @@ indent = tab tab-size = 4 */ -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include +#include +#include #include +#include +#include +#include +#include -#include #include +#include #include +#include +#include +#include +#include +#include +#include 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; @@ -44,8 +44,7 @@ using namespace Tools; //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- -namespace Cpu -{ +namespace Cpu { vector core_old_totals; vector core_old_idles; vector available_fields; @@ -63,8 +62,7 @@ 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; @@ -76,24 +74,20 @@ namespace Cpu string cpu_sensor; vector core_sensors; unordered_flat_map core_mapping; -} +} // namespace Cpu -namespace Mem -{ +namespace Mem { double old_uptime; } -namespace Shared -{ +namespace Shared { fs::path passwd_path; uint64_t totalMem; long pageSize, clkTck, coreCount; int totalMem_len; - void init() - { - + void init() { //? Shared global variables init // passwd_path = (fs::is_regular_file(fs::path("/etc/passwd")) and access("/etc/passwd", R_OK) != -1) ? "/etc/passwd" : ""; @@ -101,30 +95,26 @@ namespace Shared // 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."); } int64_t memsize = 0; size_t size = sizeof(memsize); - if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) < 0) - { + if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) < 0) { Logger::warning("Could not get memory size"); } totalMem = memsize; @@ -132,10 +122,9 @@ namespace Shared Cpu::cpuName = Cpu::get_cpuName(); } -} +} // namespace Shared -namespace Cpu -{ +namespace Cpu { string cpuName; string cpuHz; bool has_battery = true; @@ -144,38 +133,34 @@ 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 get_cpuName() { char buffer[1024]; size_t size = sizeof(buffer); - if (sysctlbyname("machdep.cpu.brand_string", &buffer, &size, NULL, 0) < 0) - { + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &size, NULL, 0) < 0) { Logger::error("Failed to get CPU name"); return ""; } return string(buffer); } - bool get_sensors() - { + bool get_sensors() { return not found_sensors.empty(); } - void update_sensors() - { + void update_sensors() { if (cpu_sensor.empty()) return; @@ -187,20 +172,16 @@ namespace Cpu 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) - { + 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(); @@ -209,33 +190,29 @@ namespace Cpu } } - string get_cpuHz() - { + string get_cpuHz() { uint64_t freq = 0; size_t size = sizeof(freq); return "1.0"; - if (sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0) < 0) - { + if (sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0) < 0) { Logger::error("Failed to get CPU frequency"); } return "" + freq; } - auto get_core_mapping() -> unordered_flat_map - { + auto get_core_mapping() -> unordered_flat_map { unordered_flat_map core_map; return core_map; } - auto get_battery() -> tuple - { - // if (not has_battery) + auto get_battery() -> tuple { + //if (not has_battery) return {0, 0, ""}; + } - auto collect(const bool no_update) -> cpu_info & - { + 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; @@ -245,10 +222,9 @@ namespace Cpu return cpu; } -} +} // namespace Cpu -namespace Mem -{ +namespace Mem { bool has_swap = false; vector fstab; fs::file_time_type fstab_time; @@ -257,55 +233,46 @@ namespace Mem mem_info current_mem{}; - auto collect(const bool no_update) -> mem_info & - { + 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_disks = Config::getB("show_disks"); auto &mem = current_mem; FILE *fpIn = popen("/usr/bin/vm_stat", "r"); - if (fpIn) - { + if (fpIn) { char buf[512]; - while (fgets(buf, sizeof(buf), fpIn) != NULL) - { + while (fgets(buf, sizeof(buf), fpIn) != NULL) { char *delim = ":\n."; char *tokens = strtok(buf, delim); - while (tokens) - { + while (tokens) { char *label = tokens; char *val = strtok(nullptr, delim); - if (strstr(label, "Pages free")) - { + if (strstr(label, "Pages free")) { uint64_t f = stoull(trim(val)); mem.stats.at("available") = f * 4096; mem.stats.at("free") = f * 4096; mem.stats.at("cached") = 1; - mem.stats.at("used") = Shared::totalMem - (f*4096); + mem.stats.at("used") = Shared::totalMem - (f * 4096); } tokens = strtok(nullptr, delim); } } pclose(fpIn); - } - else - { + } else { Logger::error("failed to read vm_stat"); } //? Calculate percentages - for (const auto& name : mem_names) { + 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(); } - if (show_disks) - { - auto& disks = mem.disks; + if (show_disks) { + auto &disks = mem.disks; struct statfs *stfs; int count = getmntinfo(&stfs, MNT_WAIT); - for (int i = 0; i < count; i++) - { + for (int i = 0; i < count; i++) { std::error_code ec; string mountpoint = stfs[i].f_mntonname; Logger::debug("found mountpoint " + mountpoint); @@ -320,10 +287,9 @@ namespace Mem return mem; } -} +} // namespace Mem -namespace Net -{ +namespace Net { unordered_flat_map current_net; net_info empty_net = {}; vector interfaces; @@ -335,25 +301,22 @@ namespace Net uint64_t timestamp = 0; //* RAII wrapper for getifaddrs - class getifaddr_wrapper - { + class getifaddr_wrapper { struct ifaddrs *ifaddr; - public: + public: int status; getifaddr_wrapper() { status = getifaddrs(&ifaddr); } ~getifaddr_wrapper() { freeifaddrs(ifaddr); } auto operator()() -> struct ifaddrs * { return ifaddr; } }; - auto collect(const bool no_update) -> net_info & - { + auto collect(const bool no_update) -> net_info & { return empty_net; } -} +} // namespace Net -namespace Proc -{ +namespace Proc { vector current_procs; unordered_flat_map uid_user; @@ -372,64 +335,50 @@ 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) + } 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 + } else children++; _tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter); } @@ -445,33 +394,27 @@ namespace Proc } //* Get detailed info for selected process - void _collect_details(const size_t pid, const uint64_t uptime, vector &procs) - { + 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 & - { + auto collect(const bool no_update) -> vector & { int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; struct kinfo_proc *processes = NULL; const double uptime = system_uptime(); auto procs = ¤t_procs; - for (int retry = 3; retry > 0; retry--) - { + for (int retry = 3; retry > 0; retry--) { size_t size = 0; - if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) - { + if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) { Logger::error("Unable to get size of kproc_infos"); } processes = (struct kinfo_proc *)malloc(size); - if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) - { + if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) { size_t count = size / sizeof(struct kinfo_proc); - for (size_t i = 0; i < count; i++) - { + for (size_t i = 0; i < count; i++) { struct kinfo_proc kproc = processes[i]; Proc::proc_info p{kproc.kp_proc.p_pid}; char fullname[PROC_PIDPATHINFO_MAXSIZE]; @@ -482,8 +425,7 @@ namespace Proc p.ppid = kproc.kp_eproc.e_ppid; p.p_nice = kproc.kp_proc.p_nice; struct proc_taskinfo pti; - if (sizeof(pti) == proc_pidinfo(p.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) - { + if (sizeof(pti) == proc_pidinfo(p.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { p.threads = pti.pti_threadnum; p.cpu_t = pti.pti_total_user + pti.pti_total_system; p.cpu_c = (double)p.cpu_t / max(1.0, (uptime * Shared::clkTck) - p.cpu_s); @@ -498,20 +440,17 @@ namespace Proc } return current_procs; } -} +} // namespace Proc -namespace Tools -{ - double system_uptime() - { +namespace Tools { + double system_uptime() { struct timeval ts, currTime; std::size_t len = sizeof(ts); int mib[2] = {CTL_KERN, KERN_BOOTTIME}; - if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) - { + if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) { gettimeofday(&currTime, NULL); return currTime.tv_sec - ts.tv_sec; } return 0.0; } -} \ No newline at end of file +} // namespace Tools \ No newline at end of file From 50fcdaa8543f717bf36146cfa38636fd39009f23 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Mon, 4 Oct 2021 14:52:56 +0200 Subject: [PATCH 12/28] disks show something --- src/osx/btop_collect.cpp | 41 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index d95332f..0bcbad0 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -4,7 +4,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -207,9 +207,8 @@ namespace Cpu { } auto get_battery() -> tuple { - //if (not has_battery) + // if (not has_battery) return {0, 0, ""}; - } auto collect(const bool no_update) -> cpu_info & { @@ -238,7 +237,10 @@ namespace Mem { return current_mem; auto &show_disks = Config::getB("show_disks"); + auto &swap_disk = Config::getB("swap_disk"); auto &mem = current_mem; + static const bool snapped = (getenv("BTOP_SNAPPED") != NULL); + FILE *fpIn = popen("/usr/bin/vm_stat", "r"); if (fpIn) { char buf[512]; @@ -283,6 +285,39 @@ namespace Mem { disks.at(mountpoint).free = stfs[i].f_bfree; disks.at(mountpoint).total = stfs[i].f_iosize; } + + //? 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); } return mem; } From c1e6d6a62e2810f80fac372e666ec169540b0591 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Mon, 4 Oct 2021 15:15:55 +0200 Subject: [PATCH 13/28] show more disks --- src/osx/btop_collect.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 0bcbad0..74bf85e 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -271,15 +271,40 @@ namespace Mem { } if (show_disks) { + auto& disks_filter = Config::getS("disks_filter"); + bool filter_exclude = false; + auto& only_physical = Config::getB("only_physical"); auto &disks = mem.disks; + 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); + } + } + struct statfs *stfs; int count = getmntinfo(&stfs, MNT_WAIT); + vector found; + found.reserve(last_found.size()); for (int i = 0; i < count; i++) { std::error_code ec; string mountpoint = stfs[i].f_mntonname; - Logger::debug("found mountpoint " + mountpoint); string dev = stfs[i].f_mntfromname; disks[mountpoint] = disk_info{fs::canonical(dev, ec), fs::path(mountpoint).filename()}; + + //? 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; + } + + found.push_back(mountpoint); + if (not v_contains(last_found, mountpoint)) redraw = true; + last_found = std::move(found); + if (disks.at(mountpoint).dev.empty()) disks.at(mountpoint).dev = dev; if (disks.at(mountpoint).name.empty()) disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint); disks.at(mountpoint).free = stfs[i].f_bfree; From 42f966f448b9ad571db7849dc8fd525e0fe72309 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Mon, 4 Oct 2021 15:32:55 +0200 Subject: [PATCH 14/28] some more params --- src/osx/btop_collect.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 74bf85e..23aa192 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -271,9 +271,10 @@ namespace Mem { } if (show_disks) { - auto& disks_filter = Config::getS("disks_filter"); + double uptime = system_uptime(); + auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; - auto& only_physical = Config::getB("only_physical"); + auto &only_physical = Config::getB("only_physical"); auto &disks = mem.disks; vector filter; if (not disks_filter.empty()) { From 8811270332bc2276cd18c1116f4d3c2d64a6f721 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 10:09:24 +0200 Subject: [PATCH 15/28] update Makefile --- Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fe01879..fbb25db 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 +OPTFLAGS ?= -O0 -g LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) @@ -93,6 +93,11 @@ SU_GROUP := root ifeq ($(ARCH),x86_64) override OPTFLAGS += -ftree-loop-vectorize -flto=$(THREADS) endif +ifneq ($(ARCH),arm64) +ifneq ($(PLATFORM),OSX) + override LDCXXFLAGS += -fstack-protector -fstack-clash-protection +endif +endif SOURCES := $(shell find $(SRCDIR) -maxdepth 1 -type f -name *.$(SRCEXT)) @@ -131,7 +136,9 @@ help: @printf " clean Remove built objects\n" @printf " distclean Remove built objects and binaries\n" @printf " install Install btop++ to \$$PREFIX ($(PREFIX))\n" +ifneq ($(PLATFORM),OSX) @printf " setuid Set installed binary owner/group to \$$SU_USER/\$$SU_GROUP ($(SU_USER)/$(SU_GROUP)) and set SUID bit\n" +endif @printf " uninstall Uninstall btop++ from \$$PREFIX\n" #? Make the Directories @@ -160,6 +167,7 @@ install: @printf "\033[1;92mInstalling themes to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop/themes\033[0m\n" @cp -pr themes $(DESTDIR)$(PREFIX)/share/btop +ifneq ($(PLATFORM),OSX) #? Set SUID bit for btop as $SU_USER in $SU_GROUP setuid: @printf "\033[1;97mFile: $(DESTDIR)$(PREFIX)/bin/btop\n" @@ -167,6 +175,7 @@ setuid: @chown $(SU_USER):$(SU_GROUP) $(DESTDIR)$(PREFIX)/bin/btop @printf "\033[1;92mSetting SUID bit\033[0m\n" @chmod u+s $(DESTDIR)$(PREFIX)/bin/btop +endif uninstall: @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\033[0m\n" From 5094b73758ee88617e8d5ce876211e1efa298769 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 10:46:14 +0200 Subject: [PATCH 16/28] allow override optimization flag --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fbb25db..d804c66 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 ?= -O0 -g +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 +ifdef DEBUG + override OPTFLAGS := -O0 -g +endif + #? This fails to compile on M1 macos (arm64 specific? as it compiles on x86_64 macos) ifeq ($(ARCH),x86_64) override OPTFLAGS += -ftree-loop-vectorize -flto=$(THREADS) From bd1050a7404f9766a0125523c868a27d5cfac8e8 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 10:48:07 +0200 Subject: [PATCH 17/28] seems to work indeed --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index d804c66..9d2bf77 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 +OPTFLAGS := -O2 -ftree-loop-vectorize -flto=$(THREADS) LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) @@ -93,10 +93,6 @@ ifdef DEBUG override OPTFLAGS := -O0 -g endif -#? This fails to compile on M1 macos (arm64 specific? as it compiles on x86_64 macos) -ifeq ($(ARCH),x86_64) - override OPTFLAGS += -ftree-loop-vectorize -flto=$(THREADS) -endif ifneq ($(ARCH),arm64) ifneq ($(PLATFORM),OSX) override LDCXXFLAGS += -fstack-protector -fstack-clash-protection From a1c7f935e3a5661688c0de1ad3226f7bc43b9979 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 12:03:48 +0200 Subject: [PATCH 18/28] vm stats from syscall + swap --- src/osx/btop_collect.cpp | 55 ++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 23aa192..dc84fe0 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -18,6 +18,11 @@ tab-size = 4 #include #include +#include +#include +#include +#include +#include #include #include #include @@ -236,34 +241,40 @@ namespace Mem { 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 &show_disks = Config::getB("show_disks"); auto &swap_disk = Config::getB("swap_disk"); auto &mem = current_mem; static const bool snapped = (getenv("BTOP_SNAPPED") != NULL); - FILE *fpIn = popen("/usr/bin/vm_stat", "r"); - if (fpIn) { - char buf[512]; - while (fgets(buf, sizeof(buf), fpIn) != NULL) { - char *delim = ":\n."; - char *tokens = strtok(buf, delim); - while (tokens) { - char *label = tokens; - char *val = strtok(nullptr, delim); - if (strstr(label, "Pages free")) { - uint64_t f = stoull(trim(val)); - mem.stats.at("available") = f * 4096; - mem.stats.at("free") = f * 4096; - mem.stats.at("cached") = 1; - mem.stats.at("used") = Shared::totalMem - (f * 4096); - } - tokens = strtok(nullptr, delim); - } - } - pclose(fpIn); - } else { - Logger::error("failed to read vm_stat"); + vm_statistics p; + mach_msg_type_number_t info_size = HOST_VM_INFO_COUNT; + if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&p, &info_size) == 0) { + mem.stats.at("available") = p.free_count * Shared::pageSize; + mem.stats.at("free") = p.free_count * Shared::pageSize; + mem.stats.at("cached") = 100; + mem.stats.at("used") = ((int64_t)p.active_count + (int64_t)p.inactive_count + (int64_t)p.wire_count) * (int64_t)Shared::pageSize; } + + int mib[2] = {CTL_VM, VM_SWAPUSAGE}; + + struct xsw_usage swap; + size_t len = sizeof(struct xsw_usage); + if (sysctl(mib, 2, &swap, &len, NULL, 0) == 0) { + mem.stats.at("swap_total") = swap.xsu_total; + mem.stats.at("swap_free") = swap.xsu_avail; + mem.stats.at("swap_used") = swap.xsu_used; + } + + 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; //? Calculate percentages for (const auto &name : mem_names) { mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / Shared::totalMem)); From 899be68a78270216bfdcca5f0c87668a87c8792f Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 15:43:05 +0200 Subject: [PATCH 19/28] correct cached size --- src/osx/btop_collect.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index dc84fe0..dce187c 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -247,12 +247,12 @@ namespace Mem { auto &mem = current_mem; static const bool snapped = (getenv("BTOP_SNAPPED") != NULL); - vm_statistics p; - mach_msg_type_number_t info_size = HOST_VM_INFO_COUNT; - if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&p, &info_size) == 0) { + vm_statistics64 p; + mach_msg_type_number_t info_size = HOST_VM_INFO64_COUNT; + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&p, &info_size) == 0) { mem.stats.at("available") = p.free_count * Shared::pageSize; mem.stats.at("free") = p.free_count * Shared::pageSize; - mem.stats.at("cached") = 100; + mem.stats.at("cached") = p.external_page_count * Shared::pageSize; mem.stats.at("used") = ((int64_t)p.active_count + (int64_t)p.inactive_count + (int64_t)p.wire_count) * (int64_t)Shared::pageSize; } From 4eb812d52c6e179ae386df0156021d7c35cbe5a3 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 21:25:42 +0200 Subject: [PATCH 20/28] network --- src/osx/btop_collect.cpp | 220 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 206 insertions(+), 14 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index dce187c..2818bab 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -26,10 +26,14 @@ tab-size = 4 #include #include #include +#include #include #include #include #include +#include +#include +#include #include #include @@ -241,7 +245,7 @@ namespace Mem { 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 &show_swap = Config::getB("show_swap"); auto &show_disks = Config::getB("show_disks"); auto &swap_disk = Config::getB("swap_disk"); auto &mem = current_mem; @@ -253,7 +257,7 @@ namespace Mem { mem.stats.at("available") = p.free_count * Shared::pageSize; mem.stats.at("free") = p.free_count * Shared::pageSize; mem.stats.at("cached") = p.external_page_count * Shared::pageSize; - mem.stats.at("used") = ((int64_t)p.active_count + (int64_t)p.inactive_count + (int64_t)p.wire_count) * (int64_t)Shared::pageSize; + mem.stats.at("used") = (p.active_count + p.inactive_count + p.wire_count) * Shared::pageSize; } int mib[2] = {CTL_VM, VM_SWAPUSAGE}; @@ -267,18 +271,19 @@ namespace Mem { } if (show_swap and mem.stats.at("swap_total") > 0) { - for (const auto& name : swap_names) { + 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(); + while (cmp_greater(mem.percent.at(name).size(), width * 2)) + mem.percent.at(name).pop_front(); } has_swap = true; - } - else + } else has_swap = false; //? 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(); + while (cmp_greater(mem.percent.at(name).size(), width * 2)) + mem.percent.at(name).pop_front(); } if (show_disks) { @@ -314,18 +319,22 @@ namespace Mem { } found.push_back(mountpoint); - if (not v_contains(last_found, mountpoint)) redraw = true; + if (not v_contains(last_found, mountpoint)) + redraw = true; last_found = std::move(found); - if (disks.at(mountpoint).dev.empty()) disks.at(mountpoint).dev = dev; - if (disks.at(mountpoint).name.empty()) disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint); + if (disks.at(mountpoint).dev.empty()) + disks.at(mountpoint).dev = dev; + if (disks.at(mountpoint).name.empty()) + disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint); disks.at(mountpoint).free = stfs[i].f_bfree; disks.at(mountpoint).total = stfs[i].f_iosize; } //? Get disk/partition stats for (auto &[mountpoint, disk] : disks) { - if (std::error_code ec; not fs::exists(mountpoint, ec)) continue; + 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); @@ -346,7 +355,8 @@ namespace Mem { 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"}; + 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"); @@ -354,7 +364,8 @@ namespace Mem { 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); + if (not is_in(name, "/", "swap")) + mem.disks_order.push_back(name); } return mem; } @@ -384,7 +395,188 @@ namespace Net { }; auto collect(const bool no_update) -> net_info & { - return empty_net; + 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(); + + 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); + } + } + + unordered_flat_map> ifstats; + int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; + size_t len; + if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { + Logger::error("failed getting network interfaces"); + } + char *buf = (char *)malloc(len); + if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { + Logger::error("failed getting network interfaces"); + } + char *lim = buf + len; + char *next = NULL; + for (next = buf; next < lim;) { + struct if_msghdr *ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + if (ifm->ifm_type == RTM_IFINFO2) { + struct if_msghdr2 *if2m = (struct if_msghdr2 *)ifm; + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1); + char iface[32]; + strncpy(iface, sdl->sdl_data, sdl->sdl_nlen); + iface[sdl->sdl_nlen] = 0; + ifstats[iface] = std::tuple(if2m->ifm_data.ifi_ibytes, if2m->ifm_data.ifi_obytes); + Logger::debug(iface); + Logger::debug(std::to_string(if2m->ifm_data.ifi_ibytes)); + Logger::debug(std::to_string(if2m->ifm_data.ifi_obytes)); + } + } + + //? Get total recieved and transmitted bytes + device address if no ip was found + for (const auto& iface : interfaces) { + + for (const string dir : {"download", "upload"}) { + auto& saved_stat = net.at(iface).stat.at(dir); + auto& bandwidth = net.at(iface).bandwidth.at(dir); + auto dirval = dir == "download" ? std::get<0>(ifstats[iface]) : std::get<1>(ifstats[iface]); + uint64_t val = saved_stat.last; + try { val = max(dirval, val); } + catch (const std::invalid_argument&) {} + catch (const std::out_of_range&) {} + + //? 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); } } // namespace Net From 600b4f72b3bbbcd85bf5d148942bce7be8cf0b72 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 22:42:42 +0200 Subject: [PATCH 21/28] CPU stuff --- src/osx/btop_collect.cpp | 233 +++++++++++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 24 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 2818bab..bd86e7e 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -16,6 +16,7 @@ indent = tab tab-size = 4 */ +#include #include #include #include @@ -24,16 +25,15 @@ tab-size = 4 #include #include #include +#include #include +#include #include #include #include #include #include #include -#include -#include -#include #include #include @@ -128,7 +128,26 @@ namespace Shared { } totalMem = memsize; + //? 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(); } } // namespace Shared @@ -156,13 +175,49 @@ namespace Cpu { {"guest_nice", 0}}; string get_cpuName() { + string name; char buffer[1024]; size_t size = sizeof(buffer); if (sysctlbyname("machdep.cpu.brand_string", &buffer, &size, NULL, 0) < 0) { Logger::error("Failed to get CPU name"); - return ""; + return name; } - return string(buffer); + name = string(buffer); + + 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 ® : {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); + } + + return name; } bool get_sensors() { @@ -207,11 +262,64 @@ namespace Cpu { if (sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0) < 0) { Logger::error("Failed to get CPU frequency"); } - return "" + freq; + return std::to_string(freq); } auto get_core_mapping() -> unordered_flat_map { unordered_flat_map core_map; + if (cpu_temp_only) return core_map; + + natural_t cpu_count; + natural_t i; + processor_info_array_t info_array; + mach_msg_type_number_t info_count; + kern_return_t error; + processor_cpu_load_info_data_t *cpu_load_info = NULL; + int ret; + + mach_port_t host_port = mach_host_self(); + error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info_array, &info_count); + if (error != KERN_SUCCESS) { + Logger::error("Failed getting CPU info"); + return core_map; + } + cpu_load_info = (processor_cpu_load_info_data_t *)info_array; + for (i = 0; i < cpu_count; i++) { + core_map[i] = i; + } + + //? 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; } @@ -225,9 +333,90 @@ namespace Cpu { return current_cpu; auto &cpu = current_cpu; + natural_t cpu_count; + natural_t i; + processor_info_array_t info_array; + mach_msg_type_number_t info_count; + kern_return_t error; + processor_cpu_load_info_data_t *cpu_load_info = NULL; + int ret; + + mach_port_t host_port = mach_host_self(); + error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info_array, &info_count); + if (error != KERN_SUCCESS) { + Logger::error("Failed getting CPU load info"); + } + cpu_load_info = (processor_cpu_load_info_data_t *)info_array; + long long global_totals = 0; + long long global_idles = 0; + for (i = 0; i < cpu_count; i++) { + vector times; + long long total_sum = 0; + //? 0=user, 1=nice, 2=system, 3=idle, 4=iowait, 5=irq, 6=softirq, 7=steal, 8=guest, 9=guest_nice + times.push_back(cpu_load_info[i].cpu_ticks[CPU_STATE_USER]); + times.push_back(cpu_load_info[i].cpu_ticks[CPU_STATE_NICE]); + times.push_back(cpu_load_info[i].cpu_ticks[CPU_STATE_SYSTEM]); + times.push_back(cpu_load_info[i].cpu_ticks[CPU_STATE_IDLE]); + times.push_back(0); + times.push_back(0); + times.push_back(0); + times.push_back(0); + times.push_back(0); + times.push_back(0); + for (long long t : times) { + total_sum += t; + } + try { + //? 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)); + + global_totals += totals; + global_idles += idles; + //? Calculate cpu total for each core + if (i > Shared::coreCount) break; + const long long calc_totals = max(0ll, totals - core_old_totals.at(i)); + const long long calc_idles = max(0ll, idles - core_old_idles.at(i)); + core_old_totals.at(i) = totals; + core_old_idles.at(i) = idles; + + cpu.core_percent.at(i).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).size() > 40) cpu.core_percent.at(i).pop_front(); + + //? Populate cpu.cpu_percent with all fields from syscall + 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; + } + } catch (const std::exception &e) { + Logger::error("get_cpuHz() : " + (string)e.what()); + throw std::runtime_error("collect() : " + (string)e.what()); + } + } + const long long calc_totals = max(1ll, global_totals - cpu_old.at("totals")); + const long long calc_idles = max(1ll, global_idles - cpu_old.at("idles")); + cpu_old.at("totals") = global_totals; + cpu_old.at("idles") = global_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(); + 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 Cpu @@ -416,10 +605,10 @@ namespace Net { string ipv4, ipv6; //? Iteration over all items in getifaddrs() list - for (auto* ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) { + 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; + 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) @@ -438,7 +627,7 @@ namespace Net { } } - unordered_flat_map> ifstats; + unordered_flat_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; size_t len; if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { @@ -456,27 +645,25 @@ namespace Net { if (ifm->ifm_type == RTM_IFINFO2) { struct if_msghdr2 *if2m = (struct if_msghdr2 *)ifm; struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1); - char iface[32]; + char iface[32]; strncpy(iface, sdl->sdl_data, sdl->sdl_nlen); - iface[sdl->sdl_nlen] = 0; + iface[sdl->sdl_nlen] = 0; ifstats[iface] = std::tuple(if2m->ifm_data.ifi_ibytes, if2m->ifm_data.ifi_obytes); - Logger::debug(iface); - Logger::debug(std::to_string(if2m->ifm_data.ifi_ibytes)); - Logger::debug(std::to_string(if2m->ifm_data.ifi_obytes)); } } //? Get total recieved and transmitted bytes + device address if no ip was found - for (const auto& iface : interfaces) { - + for (const auto &iface : interfaces) { for (const string dir : {"download", "upload"}) { - auto& saved_stat = net.at(iface).stat.at(dir); - auto& bandwidth = net.at(iface).bandwidth.at(dir); + auto &saved_stat = net.at(iface).stat.at(dir); + auto &bandwidth = net.at(iface).bandwidth.at(dir); auto dirval = dir == "download" ? std::get<0>(ifstats[iface]) : std::get<1>(ifstats[iface]); uint64_t val = saved_stat.last; - try { val = max(dirval, val); } - catch (const std::invalid_argument&) {} - catch (const std::out_of_range&) {} + try { + val = max(dirval, val); + } catch (const std::invalid_argument &) { + } catch (const std::out_of_range &) { + } //? Update speed, total and top values saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000)); @@ -494,12 +681,10 @@ namespace Net { 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) { + } 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]; } - } } } From c75b0f1cea34e6c4c70332ba7e2572ec9b70deef Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 23:18:22 +0200 Subject: [PATCH 22/28] ugly hack to get battery --- src/osx/btop_collect.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index bd86e7e..617902c 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -324,8 +324,29 @@ namespace Cpu { } auto get_battery() -> tuple { - // if (not has_battery) - return {0, 0, ""}; + if (not has_battery) return {0, 0, ""}; + + int percent = -1; + long seconds = -1; + string status = "discharging"; + + FILE *bat = popen("pmset -g batt", "r"); + if (bat) { + char buf[2048]; + if (fgets(buf, sizeof(buf), bat) != NULL) { + char *perc = strstr(buf, "%"); + if (perc) { + has_battery = true; + perc -= 3; + string p(perc); + p.resize(3); + percent = atoi(p.c_str()); + } else { + has_battery = false; + } + } + } + return {percent, seconds, status}; } auto collect(const bool no_update) -> cpu_info & { @@ -388,7 +409,7 @@ namespace Cpu { if (cpu.core_percent.at(i).size() > 40) cpu.core_percent.at(i).pop_front(); //? Populate cpu.cpu_percent with all fields from syscall - for (int ii = 0; const auto& val : times) { + 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; } From 0ad93684c2a72293b23d6a2163c9ec51b499dfa3 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 23:24:59 +0200 Subject: [PATCH 23/28] battery hack works on M1 --- src/osx/btop_collect.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 617902c..604c2fb 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -333,7 +333,8 @@ namespace Cpu { FILE *bat = popen("pmset -g batt", "r"); if (bat) { char buf[2048]; - if (fgets(buf, sizeof(buf), bat) != NULL) { + while (fgets(buf, sizeof(buf), bat) != NULL) { + Logger::debug(buf); char *perc = strstr(buf, "%"); if (perc) { has_battery = true; From 096104c90b571e931a3a7d9c813dbfc9aa47e212 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Tue, 5 Oct 2021 23:42:17 +0200 Subject: [PATCH 24/28] battery states --- src/osx/btop_collect.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 604c2fb..e695400 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -342,6 +342,13 @@ namespace Cpu { string p(perc); p.resize(3); percent = atoi(p.c_str()); + if (!strstr(buf, "discharging")) { + if (percent < 100) { + status = "charging"; + } else { + status = "full"; + } + } } else { has_battery = false; } From 0ab2be39857fb3dcdb13b49bc9155f17c7d82a4e Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 6 Oct 2021 00:17:41 +0200 Subject: [PATCH 25/28] procs sorting/filtering --- src/osx/btop_collect.cpp | 209 ++++++++++++++++++++++++++++++++------- 1 file changed, 173 insertions(+), 36 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index e695400..97c30f9 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -274,8 +274,6 @@ namespace Cpu { processor_info_array_t info_array; mach_msg_type_number_t info_count; kern_return_t error; - processor_cpu_load_info_data_t *cpu_load_info = NULL; - int ret; mach_port_t host_port = mach_host_self(); error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info_array, &info_count); @@ -283,7 +281,6 @@ namespace Cpu { Logger::error("Failed getting CPU info"); return core_map; } - cpu_load_info = (processor_cpu_load_info_data_t *)info_array; for (i = 0; i < cpu_count; i++) { core_map[i] = i; } @@ -334,7 +331,6 @@ namespace Cpu { if (bat) { char buf[2048]; while (fgets(buf, sizeof(buf), bat) != NULL) { - Logger::debug(buf); char *perc = strstr(buf, "%"); if (perc) { has_battery = true; @@ -368,7 +364,6 @@ namespace Cpu { mach_msg_type_number_t info_count; kern_return_t error; processor_cpu_load_info_data_t *cpu_load_info = NULL; - int ret; mach_port_t host_port = mach_host_self(); error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info_array, &info_count); @@ -584,6 +579,8 @@ namespace Mem { for (const auto &name : last_found) if (not is_in(name, "/", "swap")) mem.disks_order.push_back(name); + + old_uptime = uptime; } return mem; } @@ -877,45 +874,185 @@ namespace Proc { //* Collects and sorts process information from /proc auto collect(const bool no_update) -> vector & { - int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; - struct kinfo_proc *processes = NULL; - const double uptime = system_uptime(); - auto procs = ¤t_procs; + 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; + } - for (int retry = 3; retry > 0; retry--) { - size_t size = 0; - if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) { - Logger::error("Unable to get size of kproc_infos"); + 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); + } else { + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; + struct kinfo_proc *processes = NULL; + vector found; + for (int retry = 3; retry > 0; retry--) { + size_t size = 0; + if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) { + Logger::error("Unable to get size of kproc_infos"); + } + + processes = (struct kinfo_proc *)malloc(size); + if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) { + size_t count = size / sizeof(struct kinfo_proc); + for (size_t i = 0; i < count; i++) { + struct kinfo_proc kproc = processes[i]; + Proc::proc_info p{(size_t)kproc.kp_proc.p_pid}; + char fullname[PROC_PIDPATHINFO_MAXSIZE]; + const size_t pid = p.pid; + proc_pidpath(pid, fullname, sizeof(fullname)); + p.cmd = std::string(fullname); + size_t lastSlash = p.cmd.find_last_of('/'); + p.name = p.cmd.substr(lastSlash + 1); + p.ppid = kproc.kp_eproc.e_ppid; + p.p_nice = kproc.kp_proc.p_nice; + struct proc_taskinfo pti; + if (sizeof(pti) == proc_pidinfo(p.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { + p.threads = pti.pti_threadnum; + p.cpu_t = pti.pti_total_user + pti.pti_total_system; + p.cpu_c = (double)p.cpu_t / max(1.0, (uptime * Shared::clkTck) - p.cpu_s); + p.cpu_p = 0; + p.cpu_s = pti.pti_total_system; + } + struct passwd *pwd = getpwuid(kproc.kp_eproc.e_ucred.cr_uid); + p.user = pwd->pw_name; + 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(p); + find_old = current_procs.end() - 1; + no_cache = true; + } + } + } } - processes = (struct kinfo_proc *)malloc(size); + // //? 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()); - if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) { - size_t count = size / sizeof(struct kinfo_proc); - for (size_t i = 0; i < count; i++) { - struct kinfo_proc kproc = processes[i]; - Proc::proc_info p{kproc.kp_proc.p_pid}; - char fullname[PROC_PIDPATHINFO_MAXSIZE]; - proc_pidpath(p.pid, fullname, sizeof(fullname)); - p.cmd = std::string(fullname); - size_t lastSlash = p.cmd.find_last_of('/'); - p.name = p.cmd.substr(lastSlash + 1); - p.ppid = kproc.kp_eproc.e_ppid; - p.p_nice = kproc.kp_proc.p_nice; - struct proc_taskinfo pti; - if (sizeof(pti) == proc_pidinfo(p.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { - p.threads = pti.pti_threadnum; - p.cpu_t = pti.pti_total_user + pti.pti_total_system; - p.cpu_c = (double)p.cpu_t / max(1.0, (uptime * Shared::clkTck) - p.cpu_s); - p.cpu_p = 0; - p.cpu_s = pti.pti_total_system; + //? 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; } - struct passwd *pwd = getpwuid(kproc.kp_eproc.e_ucred.cr_uid); - p.user = pwd->pw_name; - procs->push_back(p); } } } + + //* 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; return current_procs; } } // namespace Proc From 548203e93dfaf3ec9f24086bee08aac85891c4df Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 6 Oct 2021 10:33:55 +0200 Subject: [PATCH 26/28] show all disks --- src/osx/btop_collect.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 97c30f9..6724f14 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -534,7 +534,6 @@ namespace Mem { found.push_back(mountpoint); if (not v_contains(last_found, mountpoint)) redraw = true; - last_found = std::move(found); if (disks.at(mountpoint).dev.empty()) disks.at(mountpoint).dev = dev; @@ -544,6 +543,17 @@ namespace Mem { disks.at(mountpoint).total = stfs[i].f_iosize; } + //? 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); + //? Get disk/partition stats for (auto &[mountpoint, disk] : disks) { if (std::error_code ec; not fs::exists(mountpoint, ec)) From bbba17cd35248e4e9ec9bfc1b113758cfcffde1f Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 6 Oct 2021 10:51:36 +0200 Subject: [PATCH 27/28] all disks + load averages --- src/osx/btop_collect.cpp | 87 ++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 6724f14..19e911a 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -358,6 +358,16 @@ namespace Cpu { return current_cpu; auto &cpu = current_cpu; + double avg[3]; + + if (getloadavg(avg, sizeof(avg)) < 0) { + Logger::error("failed to get load averages"); + } + + cpu.load_avg[0] = avg[0]; + cpu.load_avg[1] = avg[1]; + cpu.load_avg[2] = avg[2]; + natural_t cpu_count; natural_t i; processor_info_array_t info_array; @@ -553,7 +563,7 @@ namespace Mem { } if (found.size() != last_found.size()) redraw = true; last_found = std::move(found); - + //? Get disk/partition stats for (auto &[mountpoint, disk] : disks) { if (std::error_code ec; not fs::exists(mountpoint, ec)) @@ -884,12 +894,12 @@ namespace Proc { //* 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 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; @@ -961,8 +971,7 @@ namespace Proc { //? 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") { + } else if (show_detailed and not got_detailed and detailed.status != "Dead") { detailed.status = "Dead"; redraw = true; } @@ -975,14 +984,30 @@ 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 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 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); @@ -1007,20 +1032,15 @@ namespace Proc { //* Match filter if defined if (should_filter) { filter_found = 0; - for (auto& p : current_procs) { + 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; - } + 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 { + } else { p.filtered = false; } } @@ -1033,11 +1053,9 @@ namespace Proc { if (collapser != current_procs.end()) { if (collapse == expand) { collapser->collapsed = not collapser->collapsed; - } - else if (collapse > -1) { + } else if (collapse > -1) { collapser->collapsed = true; - } - else if (expand > -1) { + } else if (expand > -1) { collapser->collapsed = false; } } @@ -1052,14 +1070,13 @@ namespace Proc { 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)) { + 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; From c66b46f850d31c100226e519c55b39df9129aeb8 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 6 Oct 2021 13:41:57 +0200 Subject: [PATCH 28/28] battery state via CoreFoundation --- Makefile | 3 ++ src/osx/btop_collect.cpp | 70 ++++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 9d2bf77..52019a5 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,9 @@ ifdef DEBUG override OPTFLAGS := -O0 -g endif +ifeq ($(PLATFORM), OSX) + override LDCXXFLAGS += -framework IOKit -framework CoreFoundation +endif ifneq ($(ARCH),arm64) ifneq ($(PLATFORM),OSX) override LDCXXFLAGS += -fstack-protector -fstack-clash-protection diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 19e911a..e9eab43 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -16,6 +16,13 @@ indent = tab tab-size = 4 */ +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -323,32 +330,39 @@ namespace Cpu { auto get_battery() -> tuple { if (not has_battery) return {0, 0, ""}; - int percent = -1; + uint32_t percent = -1; long seconds = -1; string status = "discharging"; - - FILE *bat = popen("pmset -g batt", "r"); - if (bat) { - char buf[2048]; - while (fgets(buf, sizeof(buf), bat) != NULL) { - char *perc = strstr(buf, "%"); - if (perc) { - has_battery = true; - perc -= 3; - string p(perc); - p.resize(3); - percent = atoi(p.c_str()); - if (!strstr(buf, "discharging")) { - if (percent < 100) { - status = "charging"; - } else { + CFTypeRef ps_info = IOPSCopyPowerSourcesInfo(); + if (ps_info) { + CFArrayRef one_ps_descriptor = IOPSCopyPowerSourcesList(ps_info); + if (one_ps_descriptor) { + CFDictionaryRef one_ps = IOPSGetPowerSourceDescription(ps_info, CFArrayGetValueAtIndex(one_ps_descriptor, 0)); + has_battery = true; + auto state = CFDictionaryGetValue(one_ps, CFSTR(kIOPSPowerSourceStateKey)); + CFNumberRef remaining = (CFNumberRef)CFDictionaryGetValue(one_ps, CFSTR(kIOPSTimeToEmptyKey)); + int32_t estimatedMinutesRemaining; + if (remaining) { + CFNumberGetValue(remaining, kCFNumberSInt32Type, &estimatedMinutesRemaining); + seconds = estimatedMinutesRemaining * 60; + } + CFNumberRef charge = (CFNumberRef)CFDictionaryGetValue(one_ps, CFSTR(kIOPSCurrentCapacityKey)); + if (charge) { + CFNumberGetValue(charge, kCFNumberSInt32Type, &percent); + } + CFBooleanRef charging = (CFBooleanRef)CFDictionaryGetValue(one_ps, CFSTR(kIOPSIsChargingKey)); + if (charging) { + bool isCharging = CFBooleanGetValue(charging); + if (isCharging) { + status = "charging"; + if (percent == 100) { status = "full"; } } - } else { - has_battery = false; } } + CFRelease(ps_info); + CFRelease(one_ps_descriptor); } return {percent, seconds, status}; } @@ -367,7 +381,7 @@ namespace Cpu { cpu.load_avg[0] = avg[0]; cpu.load_avg[1] = avg[1]; cpu.load_avg[2] = avg[2]; - + natural_t cpu_count; natural_t i; processor_info_array_t info_array; @@ -926,7 +940,7 @@ namespace Proc { if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) { Logger::error("Unable to get size of kproc_infos"); } - + uint64_t cpu_t = 0; processes = (struct kinfo_proc *)malloc(size); if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) { size_t count = size / sizeof(struct kinfo_proc); @@ -944,10 +958,10 @@ namespace Proc { struct proc_taskinfo pti; if (sizeof(pti) == proc_pidinfo(p.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { p.threads = pti.pti_threadnum; - p.cpu_t = pti.pti_total_user + pti.pti_total_system; - p.cpu_c = (double)p.cpu_t / max(1.0, (uptime * Shared::clkTck) - p.cpu_s); - p.cpu_p = 0; + cpu_t = pti.pti_total_user + pti.pti_total_system; p.cpu_s = pti.pti_total_system; + p.cpu_c = (double)p.cpu_t / max(1.0, (uptime * Shared::clkTck) - p.cpu_s); + p.mem = pti.pti_resident_size; } struct passwd *pwd = getpwuid(kproc.kp_eproc.e_ucred.cr_uid); p.user = pwd->pw_name; @@ -960,6 +974,14 @@ namespace Proc { find_old = current_procs.end() - 1; no_cache = true; } + //? Process cpu usage since last update + p.cpu_p = clamp(round(cmult * 1000 * (cpu_t - p.cpu_t) / max((uint64_t)1, cputimes - old_cputimes)) / 10.0, 0.0, 100.0 * Shared::coreCount); + + //? Process cumulative cpu usage since process start + p.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - p.cpu_s); + + //? Update cached value with latest cpu times + p.cpu_t = cpu_t; } } }