summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/btop.cpp74
-rw-r--r--src/btop_shared.hpp4
-rw-r--r--src/netbsd/btop_collect.cpp1420
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();
+ }