diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/btop.cpp | 74 | ||||
-rw-r--r-- | src/btop_shared.hpp | 4 | ||||
-rw-r--r-- | src/netbsd/btop_collect.cpp | 1420 |
3 files changed, 1470 insertions, 28 deletions
diff --git a/src/btop.cpp b/src/btop.cpp index 8eae107..6d4753a 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -120,15 +120,41 @@ namespace Global { static void print_version() { if constexpr (GIT_COMMIT.empty()) { - fmt::print("btop version: {}\n", Global::Version); + fmt::println("btop version: {}", Global::Version); } else { - fmt::print("btop version: {}+{}\n", Global::Version, GIT_COMMIT); + fmt::println("btop version: {}+{}", Global::Version, GIT_COMMIT); } } static void print_version_with_build_info() { print_version(); - fmt::print("Compiled with: {} ({})\nConfigured with: {}\n", COMPILER, COMPILER_VERSION, CONFIGURE_COMMAND); + fmt::println("Compiled with: {} ({})\nConfigured with: {}", COMPILER, COMPILER_VERSION, CONFIGURE_COMMAND); +} + +static void print_usage() { + fmt::println("\033[1;4mUsage:\033[0;1m btop\033[0m [OPTIONS]\n"); +} + +static void print_help() { + print_usage(); + fmt::println( + "{0}{1}Options:{2}\n" + " {0}-h, --help {2}show this help message and exit\n" + " {0}-v, --version {2}show version info and exit\n" + " {0}-lc, --low-color {2}disable truecolor, converts 24-bit colors to 256-color\n" + " {0}-t, --tty_on {2}force (ON) tty mode, max 16 colors and tty friendly graph symbols\n" + " {0}+t, --tty_off {2}force (OFF) tty mode\n" + " {0}-p, --preset <id> {2}start with preset, integer value between 0-9\n" + " {0}-u, --update <ms> {2}set the program update rate in milliseconds\n" + " {0} --utf-force {2}force start even if no UTF-8 locale was detected\n" + " {0} --debug {2}start in DEBUG mode: shows microsecond timer for information collect\n" + " {0} {2}and screen draw functions and sets loglevel to DEBUG", + "\033[1m", "\033[4m", "\033[0m" + ); +} + +static void print_help_hint() { + fmt::println("For more information, try '{0}--help{1}'", "\033[1m", "\033[0m"); } //* A simple argument parser @@ -136,20 +162,7 @@ void argumentParser(const int argc, char **argv) { for(int i = 1; i < argc; i++) { const string argument = argv[i]; if (is_in(argument, "-h", "--help")) { - fmt::println( - "usage: btop [-h] [-v] [-/+t] [-p <id>] [-u <ms>] [--utf-force] [--debug]\n\n" - "optional arguments:\n" - " -h, --help show this help message and exit\n" - " -v, --version show version info and exit\n" - " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n" - " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n" - " +t, --tty_off force (OFF) tty mode\n" - " -p, --preset <id> start with preset, integer value between 0-9\n" - " -u, --update <ms> set the program update rate in milliseconds\n" - " --utf-force force start even if no UTF-8 locale was detected\n" - " --debug start in DEBUG mode: shows microsecond timer for information collect\n" - " and screen draw functions and sets loglevel to DEBUG" - ); + print_help(); exit(0); } else if (is_in(argument, "-v")) { @@ -173,27 +186,35 @@ void argumentParser(const int argc, char **argv) { } else if (is_in(argument, "-p", "--preset")) { if (++i >= argc) { - fmt::println("ERROR: Preset option needs an argument."); + fmt::println("{0}error:{1} Preset option needs an argument\n", "\033[1;31m", "\033[0m"); + print_usage(); + print_help_hint(); exit(1); } else if (const string val = argv[i]; isint(val) and val.size() == 1) { Global::arg_preset = std::clamp(stoi(val), 0, 9); } else { - fmt::println("ERROR: Preset option only accepts an integer value between 0-9."); + fmt::println("{0}error: {1}Preset option only accepts an integer value between 0-9\n", "\033[1;31m", "\033[0m"); + print_usage(); + print_help_hint(); exit(1); } } else if (is_in(argument, "-u", "--update")) { if (++i >= argc) { - fmt::println("ERROR: Update option needs an argument"); + fmt::println("{0}error:{1} Update option needs an argument\n", "\033[1;31m", "\033[0m"); + print_usage(); + print_help_hint(); exit(1); } const std::string value = argv[i]; if (isint(value)) { Global::arg_update = std::clamp(std::stoi(value), 100, Config::ONE_DAY_MILLIS); } else { - fmt::println("ERROR: Invalid update rate"); + fmt::println("{0}error:{1} Invalid update rate\n", "\033[1;31m", "\033[0m"); + print_usage(); + print_help_hint(); exit(1); } } @@ -202,8 +223,9 @@ void argumentParser(const int argc, char **argv) { else if (argument == "--debug") Global::debug = true; else { - fmt::println(" Unknown argument: {}\n" - " Use -h or --help for help.", argument); + fmt::println("{0}error:{2} unexpected argument '{1}{3}{2}' found\n", "\033[1;31m", "\033[33m", "\033[0m", argument); + print_usage(); + print_help_hint(); exit(1); } } @@ -293,7 +315,7 @@ void clean_quit(int sig) { Global::quitting = true; Runner::stop(); if (Global::_runner_started) { - #if defined __APPLE__ || defined __OpenBSD__ + #if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__ if (pthread_join(Runner::runner_id, nullptr) != 0) { Logger::warning("Failed to join _runner thread on exit!"); pthread_cancel(Runner::runner_id); @@ -329,7 +351,7 @@ void clean_quit(int sig) { const auto excode = (sig != -1 ? sig : 0); -#if defined __APPLE__ || defined __OpenBSD__ +#if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__ _Exit(excode); #else quick_exit(excode); @@ -1024,7 +1046,7 @@ int main(int argc, char **argv) { Config::set("tty_mode", true); Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols"); } -#if not defined __APPLE__ && not defined __OpenBSD__ +#if not defined __APPLE__ && not defined __OpenBSD__ && not defined __NetBSD__ else if (not Global::arg_tty and Term::current_tty.starts_with("/dev/tty")) { Config::set("tty_mode", true); Logger::info("Real tty detected: setting 16 color mode and using tty friendly graph symbols"); diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 0a8f452..f1f974e 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -34,7 +34,7 @@ tab-size = 4 #include <ifaddrs.h> // clang-format on -#if defined(__FreeBSD__) || defined(__OpenBSD__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) # include <kvm.h> #endif @@ -93,7 +93,7 @@ namespace Shared { extern long coreCount, page_size, clk_tck; -#if defined(__FreeBSD__) || defined(__OpenBSD__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) struct KvmDeleter { void operator()(kvm_t* handle) { kvm_close(handle); diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp new file mode 100644 index 0000000..234b7d5 --- /dev/null +++ b/src/netbsd/btop_collect.cpp @@ -0,0 +1,1420 @@ +/* Copyright 2021 Aristocratos (jakob@qvantnet.com) + Copyright 2024 Santhosh Raju (fox@NetBSD.org) + + 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 <arpa/inet.h> +#include <cstddef> +#include <cstdio> +#include <cstdlib> +#include <cstring> +// man 3 getifaddrs: "BUGS: If both <net/if.h> and <ifaddrs.h> are being included, <net/if.h> must be included before <ifaddrs.h>" +#include <net/if.h> +#include <ifaddrs.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <netdb.h> +#include <netinet/tcp_fsm.h> +#include <netinet/in.h> // for inet_ntop stuff +#include <pwd.h> +#include <prop/proplib.h> +#include <sys/endian.h> +#include <sys/iostat.h> +#include <sys/envsys.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/statvfs.h> +#include <sys/sysctl.h> +#include <sys/sched.h> +#include <sys/signal.h> +#include <sys/siginfo.h> +#include <sys/proc.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/ucred.h> +#include <sys/mount.h> +#include <sys/vmmeter.h> +#include <sys/disk.h> +#include <vector> +#include <kvm.h> +#include <paths.h> +#include <fcntl.h> +#include <unistd.h> +#include <uvm/uvm_extern.h> + +#include <stdexcept> +#include <cmath> +#include <fstream> +#include <numeric> +#include <ranges> +#include <algorithm> +#include <regex> +#include <string> +#include <memory> +#include <utility> + +#include "../btop_config.hpp" +#include "../btop_shared.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 = {"total"}; + vector<string> available_sensors = {"Auto"}; + cpu_info current_cpu; + 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; + }; + + string cpu_sensor; + vector<string> core_sensors; + std::unordered_map<int, int> core_mapping; +} // namespace Cpu + +namespace Mem { + double old_uptime; +} + +namespace Shared { + + fs::path passwd_path; + uint64_t totalMem; + long pageSize, clkTck, coreCount, physicalCoreCount, arg_max; + int totalMem_len, kfscale; + long bootTime; + size_t size; + + void init() { + //? Shared global variables init + int mib[2]; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + int ncpu; + size_t len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, nullptr, 0) == -1) { + Logger::warning("Could not determine number of cores, defaulting to 1."); + } else { + coreCount = ncpu; + } + + size = sizeof(pageSize); + if (sysctlbyname("hw.pagesize", &pageSize, &size, nullptr, 0) < 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."); + } + + size = sizeof(totalMem); + if (sysctlbyname("hw.physmem64", &totalMem, &size, nullptr, 0) < 0) { + Logger::warning("Could not get memory size"); + } + + struct timeval result; + size = sizeof(result); + if (sysctlbyname("kern.boottime", &result, &size, nullptr, 0) < 0) { + Logger::warning("Could not get boot time"); + } else { + bootTime = result.tv_sec; + } + + size = sizeof(kfscale); + if (sysctlbyname("kern.fscale", &kfscale, &size, nullptr, 0) == -1) { + kfscale = 2048; + } + + //* Get maximum length of process arguments + arg_max = sysconf(_SC_ARG_MAX); + + //? Init for namespace Cpu + 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() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field); + } + Cpu::cpuName = Cpu::get_cpuName(); + Cpu::got_sensors = Cpu::get_sensors(); + Cpu::core_mapping = Cpu::get_core_mapping(); + + //? Init for namespace Mem + Mem::old_uptime = system_uptime(); + Mem::collect(); + } +} // namespace Shared + +namespace Cpu { + string cpuName; + string cpuHz; + bool has_battery = true; + tuple<int, float, long, string> current_bat; + + const array<string, 10> time_names = {"user", "nice", "system", "idle"}; + + std::unordered_map<string, long long> cpu_old = { + {"totals", 0}, + {"idles", 0}, + {"user", 0}, + {"nice", 0}, + {"system", 0}, + {"idle", 0} + }; + + string get_cpuName() { + string name; + char buffer[1024]; + size_t size = sizeof(buffer); + if (sysctlbyname("hw.model", &buffer, &size, nullptr, 0) < 0) { + Logger::error("Failed to get CPU name"); + return name; + } + 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& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Core"}) { + name = s_replace(name, replace, ""); + name = s_replace(name, " ", " "); + } + name = trim(name); + } + + return name; + } + + bool get_sensors() { + got_sensors = false; + prop_dictionary_t dict; + prop_object_t fields_array; + // List of common thermal sensors in NetBSD. + const string sensors[6] = { + "acpitz0", + "acpitz1", + "coretemp0", + "coretemp1", + "thinkpad0", + "amdzentemp0" + }; + + int fd = open(_PATH_SYSMON, O_RDONLY); + if (fd == -1) { + Logger::warning("failed to open " + string(_PATH_SYSMON)); + return got_sensors; + } + + if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("failed to open envsys dict"); + return got_sensors; + } + + if (prop_dictionary_count(dict) == 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("no drivers registered for envsys"); + return got_sensors; + } + + // Search through a known list of sensors and break the loop on finding the first. + for(const string &sensor : sensors) { + fields_array = prop_dictionary_get(prop_dictionary_t(dict), sensor.c_str()); + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { + Logger::warning("unknown device " + sensor); + } else { + Cpu::cpu_sensor = sensor; + break; + } + } + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { + if (fd != -1) { + close(fd); + } + return got_sensors; + } + + if (Config::getB("show_coretemp") and Config::getB("check_temp")) { + got_sensors = true; + } + return got_sensors; + } + +#define MUKTOC(v) ((v - 273150000) / 1000000.0) + + void update_sensors() { + int64_t current_temp = -1; + current_cpu.temp_max = 95; + prop_dictionary_t dict, fields, props; + + int fd = open(_PATH_SYSMON, O_RDONLY); + if (fd == -1) { + Logger::warning("failed to open " + string(_PATH_SYSMON)); + return; + } + + if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("failed to open envsys dict"); + return; + } + + if (prop_dictionary_count(dict) == 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("no drivers registered for envsys"); + return; + } + + prop_object_t fields_array = prop_dictionary_get(prop_dictionary_t(dict), Cpu::cpu_sensor.c_str()); + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { + if (fd != -1) { + close(fd); + } + Logger::warning("unknown device " + Cpu::cpu_sensor); + return; + } + + prop_object_iterator_t fields_iter = prop_array_iterator(prop_array_t(fields_array)); + if (fields_iter == NULL) { + if (fd != -1) { + close(fd); + } + return; + } + + string prop_description = "no description"; + while ((fields = (prop_dictionary_t) prop_object_iterator_next(prop_object_iterator_t(fields_iter))) != NULL) { + props = (prop_dictionary_t) prop_dictionary_get(fields, "device-properties"); + if (props != NULL) continue; + + prop_object_t cur_value = prop_dictionary_get(fields, "cur-value"); + prop_object_t max_value = prop_dictionary_get(fields, "critical-max"); + prop_object_t description = prop_dictionary_get(fields, "description"); + + if (description == NULL || cur_value == NULL) { + continue; + } + + + prop_description = prop_string_cstring(prop_string_t(description)); + + if (prop_description == "temperature") { + current_temp = prop_number_integer_value(prop_number_t(cur_value)); + if (max_value != NULL) { + current_cpu.temp_max = MUKTOC(prop_number_integer_value(prop_number_t(max_value))); + } + } + } + + prop_object_iterator_release(fields_iter); + prop_object_release(dict); + + if (current_temp > -1) { + current_temp = MUKTOC(current_temp); + for (int i = 0; i < Shared::coreCount; i++) { + if (cmp_less(i + 1, current_cpu.temp.size())) { + current_cpu.temp.at(i + 1).push_back(current_temp); + if (current_cpu.temp.at(i + 1).size() > 20) { + current_cpu.temp.at(i + 1).pop_front(); + } + } + } + current_cpu.temp.at(0).push_back(current_temp); + if (current_cpu.temp.at(0).size() > 20) { + current_cpu.temp.at(0).pop_front(); + } + } + + } + + string get_cpuHz() { + unsigned int freq = 1; + size_t size = sizeof(freq); + + if (sysctlbyname("hw.cpuspeed", &freq, &size, nullptr, 0) < 0) { + return ""; + } + return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz + } + + auto get_core_mapping() -> std::unordered_map<int, int> { + std::unordered_map<int, int> core_map; + if (cpu_temp_only) return core_map; + + for (long i = 0; i < Shared::coreCount; 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; + } + + auto get_battery() -> tuple<int, float, long, string> { + if (not has_battery) return {0, 0.0, 0, ""}; + + prop_dictionary_t dict, fields, props; + + int64_t total_charge = 0; + int64_t total_capacity = 0; + + int fd = open(_PATH_SYSMON, O_RDONLY); + if (fd == -1) { + Logger::warning("failed to open " + string(_PATH_SYSMON)); + has_battery = false; + return {0, 0.0, 0, ""}; + } + + if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0) { + if (fd != -1) { + close(fd); + } + has_battery = false; + Logger::warning("failed to open envsys dict"); + return {0, 0.0, 0, ""}; + } + + if (prop_dictionary_count(dict) == 0) { + if (fd != -1) { + close(fd); + } + has_battery = false; + Logger::warning("no drivers registered for envsys"); + return {0, 0.0, 0, ""}; + } + + prop_object_t fields_array = prop_dictionary_get(prop_dictionary_t(dict), "acpibat0"); + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { + if (fd != -1) { + close(fd); + } + has_battery = false; + Logger::warning("unknown device 'acpibat0'"); + return {0, 0.0, 0, ""}; + } + + prop_object_iterator_t fields_iter = prop_array_iterator(prop_array_t(fields_array)); + if (fields_iter == NULL) { + if (fd != -1) { + close(fd); + } + has_battery = false; + return {0, 0.0, 0, ""}; + } + + /* only assume battery is not present if explicitly stated */ + bool is_battery = false; + int64_t is_present = 1; + int64_t cur_charge = 0; + int64_t max_charge = 0; + string status = "unknown"; + string prop_description = "no description"; + + while ((fields = (prop_dictionary_t) prop_object_iterator_next(prop_object_iterator_t(fields_iter))) != NULL) { + props = (prop_dictionary_t) prop_dictionary_get(fields, "device-properties"); + if (props != NULL) continue; + + prop_object_t cur_value = prop_dictionary_get(fields, "cur-value"); + prop_object_t max_value = prop_dictionary_get(fields, "max-value"); + prop_object_t description = prop_dictionary_get(fields, "description"); + + if (description == NULL || cur_value == NULL) { + continue; + } + + + prop_description = prop_string_cstring(prop_string_t(description)); + + if (prop_description == "charge") { + if (max_value == NULL) { + continue; + } + cur_charge = prop_number_integer_value(prop_number_t(cur_value)); + max_charge = prop_number_integer_value(prop_number_t(max_value)); + } + + if (prop_description == "present") { + is_present = prop_number_integer_value(prop_number_t(cur_value)); + } + + if (prop_description == "charging") { + status = prop_description; + string charging_type = prop_string_cstring(prop_string_t(prop_dictionary_get(fields, "type"))); + is_battery = charging_type == "Battery charge" ? true : false; + } + + if (is_battery && is_present) { + total_charge += cur_charge; + total_capacity += max_charge; + } + } + + prop_object_iterator_release(fields_iter); + prop_object_release(dict); + + uint32_t percent = ((double)total_charge / (double)total_capacity) * 100.0; + + if (percent == 100) { + status = "full"; + } + + return {percent, -1, -1, status}; + } + + auto collect(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 (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) { + Logger::error("failed to get load averages"); + } + + vector<array<long, CPUSTATES>> cpu_time(Shared::coreCount); + size_t size = sizeof(long) * CPUSTATES * Shared::coreCount; + if (sysctlbyname("kern.cp_time", &cpu_time[0], &size, nullptr, 0) == -1) { + Logger::error("failed to get CPU time"); + } + long long global_totals = 0; + long long global_idles = 0; + vector<long long> times_summed = {0, 0, 0, 0}; + + for (long i = 0; i < Shared::coreCount; i++) { + vector<long long> times; + //? 0=user, 1=nice, 2=system, 3=idle + for (int x = 0; const unsigned int c_state : {CP_USER, CP_NICE, CP_SYS, CP_IDLE}) { + auto val = cpu_time[i][c_state]; + times.push_back(val); + times_summed.at(x++) += val; + } + try { + //? All values + const long long totals = std::accumulate(times.begin(), times.end(), 0ll); + + //? Idle time + const long long idles = times.at(3); + + 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(); + + } catch (const std::exception &e) { + Logger::error("Cpu::collect() : " + (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")); + + //? Populate cpu.cpu_percent with all fields from syscall + for (int ii = 0; const auto &val : times_summed) { + 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(); + + ii++; + } + + 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")) { + auto hz = get_cpuHz(); + if (hz != "") { + cpuHz = hz; + } + } + + if (Config::getB("check_temp") and got_sensors) + update_sensors(); + + if (Config::getB("show_battery") and has_battery) + current_bat = get_battery(); + + return current_cpu; + } +} // namespace 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{}; + + uint64_t get_totalMem() { + return Shared::totalMem; + } + + void assign_values(struct disk_info& disk, int64_t readBytes, int64_t writeBytes) { + disk_ios++; + if (disk.io_read.empty()) { + disk.io_read.push_back(0); + } else { + disk.io_read.push_back(max((int64_t)0, (readBytes - disk.old_io.at(0)))); + } + disk.old_io.at(0) = readBytes; + while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); + + if (disk.io_write.empty()) { + disk.io_write.push_back(0); + } else { + disk.io_write.push_back(max((int64_t)0, (writeBytes - disk.old_io.at(1)))); + } + disk.old_io.at(1) = writeBytes; + while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front(); + + // no io times - need to push something anyway or we'll get an ABORT + if (disk.io_activity.empty()) + disk.io_activity.push_back(0); + else + disk.io_activity.push_back(clamp((long)round((double)(disk.io_write.back() + disk.io_read.back()) / (1 << 20)), 0l, 100l)); + while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); + } + + void collect_disk(std::unordered_map<string, disk_info> &disks, std::unordered_map<string, string> &mapping) { + uint64_t total_bytes_read = 0; + uint64_t total_bytes_write = 0; + + int num_drives = 0; + int mib[3] = { CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl)}; + + size_t size; + if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1) { + Logger::error("sysctl hw.drivestats failed"); + return; + } + num_drives = size / sizeof(struct io_sysctl); + + auto drives = std::unique_ptr<struct io_sysctl[], void(*)(void*)> { + reinterpret_cast<struct io_sysctl*>(malloc(size)), + free + }; + + if (sysctl(mib, 3, drives.get(), &size, NULL, 0) == -1) { + Logger::error("sysctl hw.iostats failed"); + } + for (int i = 0; i < num_drives; i++) { + for (auto& [ignored, disk] : disks) { + if (disk.dev.string().find(drives[i].name) != string::npos) { + string mountpoint = mapping.at(disk.dev); + total_bytes_read = drives[i].rbytes; + total_bytes_write = drives[i].wbytes; + assign_values(disk, total_bytes_read, total_bytes_write); + } + } + } + + } + + auto collect(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 show_disks = Config::getB("show_disks"); + auto swap_disk = Config::getB("swap_disk"); + auto &mem = current_mem; + static bool snapped = (getenv("BTOP_SNAPPED") != nullptr); + + uint64_t memActive, memWired, memCached, memFree; + size_t size; + + static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2}; + struct uvmexp_sysctl uvmexp; + size = sizeof(uvmexp); + if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) { + Logger::error("uvmexp sysctl failed"); + bzero(&uvmexp, sizeof(uvmexp)); + } + + memActive = uvmexp.active * Shared::pageSize; + memWired = uvmexp.wired * Shared::pageSize; + memFree = uvmexp.free * Shared::pageSize; + memCached = (uvmexp.filepages + uvmexp.execpages + uvmexp.anonpages) * Shared::pageSize; + mem.stats.at("used") = memActive + memWired; + mem.stats.at("available") = Shared::totalMem - (memActive + memWired); + mem.stats.at("cached") = memCached; + mem.stats.at("free") = memFree; + + if (show_swap) { + mem.stats.at("swap_total") = uvmexp.swpages * Shared::pageSize; + mem.stats.at("swap_used") = uvmexp.swpginuse * Shared::pageSize; + mem.stats.at("swap_free") = (uvmexp.swpages - uvmexp.swpginuse) * Shared::pageSize; + } + + 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)); + while (cmp_greater(mem.percent.at(name).size(), width * 2)) + mem.percent.at(name).pop_front(); + } + + if (show_disks) { + std::unordered_map<string, string> mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + double uptime = system_uptime(); + auto &disks_filter = Config::getS("disks_filter"); + bool filter_exclude = false; + // auto only_physical = Config::getB("only_physical"); + auto &disks = mem.disks; + vector<string> 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 statvfs *stvfs; + int count = getmntinfo(&stvfs, MNT_WAIT); + vector<string> found; + found.reserve(last_found.size()); + for (int i = 0; i < count; i++) { + auto fstype = string(stvfs[i].f_fstypename); + if (fstype == "autofs" || fstype == "devfs" || fstype == "linprocfs" || fstype == "procfs" || fstype == "tmpfs" || fstype == "linsysfs" || + fstype == "fdesckfs") { + // in memory filesystems -> not useful to show + continue; + } + + std::error_code ec; + string mountpoint = stvfs[i].f_mntonname; + string dev = stvfs[i].f_mntfromname; + mapping[dev] = mountpoint; + + //? 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 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 == "/" ? "root" : mountpoint); + } + + + if (not v_contains(last_found, mountpoint)) + redraw = true; + + disks.at(mountpoint).free = stvfs[i].f_bfree; + disks.at(mountpoint).total = stvfs[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)) + 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; + if (disk.total != 0) { + disk.used_percent = round((double)disk.used * 100 / disk.total); + disk.free_percent = 100 - disk.used_percent; + } else { + disk.used_percent = 0; + disk.free_percent = 0; + } + } + + //? 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(); + } |