diff options
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | src/btop.cpp | 12 | ||||
-rw-r--r-- | src/btop_tools.hpp | 7 | ||||
-rw-r--r-- | src/osx/btop_collect.cpp | 492 |
4 files changed, 515 insertions, 5 deletions
@@ -81,14 +81,19 @@ 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 +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) 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 + 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 64b925d..481fa23 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); @@ -292,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); } }; @@ -302,7 +306,7 @@ namespace Runner { sigset_t mask; pthread_t runner_id; pthread_mutex_t mtx; - + const unordered_flat_map<string, uint_fast8_t> box_bits = { {"proc", 0b0000'0001}, {"net", 0b0000'0100}, @@ -379,6 +383,7 @@ namespace Runner { 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); + Logger::error(Global::exit_error_msg); Global::thread_exception = true; Input::interrupt = true; stopping = true; @@ -532,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; @@ -846,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/btop_tools.hpp b/src/btop_tools.hpp index 97e010a..ea3b543 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..addafa6 --- /dev/null +++ b/src/osx/btop_collect.cpp @@ -0,0 +1,492 @@ +/* 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 <fstream> +#include <ranges> +#include <cmath> +#include <unistd.h> +#include <numeric> +#include <regex> +#include <sys/statvfs.h> +#include <netdb.h> +#include <string> +#include <ifaddrs.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#include <libproc.h> +#include <pwd.h> + +#include <btop_shared.hpp> +#include <btop_config.hpp> +#include <btop_tools.hpp> + +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 +{ + vector<long long> core_old_totals; + vector<long long> core_old_idles; + vector<string> available_fields; + vector<string> 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<string, Sensor> found_sensors; + string cpu_sensor; + vector<string> core_sensors; + unordered_flat_map<int, int> core_mapping; +} + +namespace Mem +{ + double old_uptime; +} + +namespace Shared +{ + + fs::path passwd_path; + uint64_t totalMem; + long pageSize, clkTck, coreCount; + int totalMem_len; + + 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" : ""; + // 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."); + } + + int64_t memsize = 0; + size_t size = sizeof(memsize); + if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) < 0) + { + Logger::warning("Could not get memory size"); + } + totalMem = memsize; + + Cpu::cpuName = Cpu::get_cpuName(); + } + +} + +namespace Cpu +{ + string cpuName; + string cpuHz; + bool has_battery = true; + tuple<int, long, string> current_bat; + + const array<string, 10> time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"}; + + unordered_flat_map<string, long long> 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() + { + 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() + { + return not found_sensors.empty(); + } + + void update_sensors() + { + if (cpu_sensor.empty()) + return; + + const auto &cpu_sensor = (not Config::getS("cpu_sensor").empty() and found_sensors.contains(Config::getS("cpu_sensor")) ? Config::getS("cpu_sensor") : Cpu::cpu_sensor); + + found_sensors.at(cpu_sensor).temp = stol(readfile(found_sensors.at(cpu_sensor).path, "0")) / 1000; + current_cpu.temp.at(0).push_back(found_sensors.at(cpu_sensor).temp); + current_cpu.temp_max = found_sensors.at(cpu_sensor).crit; + if (current_cpu.temp.at(0).size() > 20) + current_cpu.temp.at(0).pop_front(); + + if (Config::getB("show_coretemp") and not cpu_temp_only) + { + vector<string> done; + for (const auto &sensor : core_sensors) + { + if (v_contains(done, sensor)) + continue; + found_sensors.at(sensor).temp = stol(readfile(found_sensors.at(sensor).path, "0")) / 1000; + done.push_back(sensor); + } + for (const auto &[core, temp] : core_mapping) + { + if (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() + { + uint64_t freq = 0; + size_t size = sizeof(freq); + + return "1.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<int, int> + { + unordered_flat_map<int, int> core_map; + return core_map; + } + + auto get_battery() -> tuple<int, long, string> + { + // 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; + + if (Config::getB("show_cpu_freq")) + cpuHz = get_cpuHz(); + + return cpu; + } +} + +namespace Mem +{ + bool has_swap = false; + vector<string> fstab; + fs::file_time_type fstab_time; + int disk_ios = 0; + vector<string> 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 &mem = current_mem; + 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; + // } else if (strstr(label, "Pages free")) { + } + tokens = strtok(nullptr, delim); + } + } + pclose(fpIn); + } + else + { + Logger::error("failed to read vm_stat"); + } + mem.stats.at("used") = Shared::totalMem - mem.stats.at("available"); + return mem; + } + +} + +namespace Net +{ + unordered_flat_map<string, net_info> current_net; + net_info empty_net = {}; + vector<string> interfaces; + string selected_iface; + int errors = 0; + unordered_flat_map<string, uint64_t> graph_max = {{"download", {}}, {"upload", {}}}; + unordered_flat_map<string, array<int, 2>> 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 & + { + return empty_net; + } +} + +namespace Proc +{ + + vector<proc_info> current_procs; + unordered_flat_map<string, string> 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<int> numpids = 0; + int filter_found = 0; + + detail_container detailed; + + //* Generate process tree list + void _tree_gen(proc_info &cur_proc, vector<proc_info> &in_procs, vector<std::reference_wrapper<proc_info>> &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<proc_info> &procs) + { + } + + //* Collects and sorts process information from /proc + auto collect(const bool no_update) -> vector<proc_info> & + { + 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; + } + struct passwd *pwd = getpwuid(kproc.kp_eproc.e_ucred.cr_uid); + p.user = pwd->pw_name; + procs->push_back(p); + } + } + } + return current_procs; + } +} + +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) + { + gettimeofday(&currTime, NULL); + return currTime.tv_sec - ts.tv_sec; + } + return 0.0; + } +}
\ No newline at end of file |