summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/continuous-build-linux.yml (renamed from .github/workflows/continuous-build.yml)0
-rw-r--r--.github/workflows/continuous-build-macos.yml37
-rw-r--r--README.md1
-rw-r--r--src/btop.cpp4
-rw-r--r--src/btop_config.cpp3
-rw-r--r--src/btop_input.cpp2
-rw-r--r--src/btop_menu.cpp2
-rw-r--r--src/btop_shared.hpp2
-rw-r--r--src/btop_tools.cpp2
-rw-r--r--src/btop_tools.hpp8
-rw-r--r--src/osx/btop_collect.cpp1405
-rw-r--r--src/osx/sensors.cpp92
-rw-r--r--src/osx/sensors.hpp7
-rw-r--r--src/osx/smc.cpp120
-rw-r--r--src/osx/smc.hpp96
15 files changed, 1773 insertions, 8 deletions
diff --git a/.github/workflows/continuous-build.yml b/.github/workflows/continuous-build-linux.yml
index 38f3f2c..38f3f2c 100644
--- a/.github/workflows/continuous-build.yml
+++ b/.github/workflows/continuous-build-linux.yml
diff --git a/.github/workflows/continuous-build-macos.yml b/.github/workflows/continuous-build-macos.yml
new file mode 100644
index 0000000..b747e92
--- /dev/null
+++ b/.github/workflows/continuous-build-macos.yml
@@ -0,0 +1,37 @@
+name: Continuous Build MacOS
+
+on:
+ push:
+ branches:
+ - OSX
+ tags-ignore:
+ - '*.*'
+ paths:
+ - 'src/**'
+ - '!src/linux/**'
+ - '!src/freebsd/**'
+ - 'include/**'
+ - 'Makefile'
+ - '.github/workflows/*'
+
+jobs:
+ build-osx:
+
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install build tools
+ run: |
+ git checkout OSX
+ - name: Compile
+ run: |
+ make CXX=g++-11 ARCH=x86_64
+ GIT_HASH=$(git rev-parse --short "$GITHUB_SHA")
+ mv bin/btop bin/btop-x86_64-$GIT_HASH
+ ls -alh bin
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: btop-x86_64-macos
+ path: 'bin/*'
diff --git a/README.md b/README.md
index 3286929..2dd6f21 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@
[![Coffee](https://img.shields.io/badge/-Buy%20me%20a%20Coffee-grey?logo=Ko-fi)](https://ko-fi.com/aristocratos)
[![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop)
[![Continuous Build](https://github.com/aristocratos/btop/actions/workflows/continuous-build.yml/badge.svg)](https://github.com/aristocratos/btop/actions)
+[![Continuous Build MacOS](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml)
## Index
diff --git a/src/btop.cpp b/src/btop.cpp
index b9be5e8..5d782f6 100644
--- a/src/btop.cpp
+++ b/src/btop.cpp
@@ -88,7 +88,6 @@ namespace Global {
int arg_preset = -1;
}
-
//* A simple argument parser
void argumentParser(const int& argc, char **argv) {
for(int i = 1; i < argc; i++) {
@@ -320,7 +319,7 @@ namespace Runner {
pthread_mutex_t& pt_mutex;
public:
int status;
- thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { pthread_mutex_init(&mtx, NULL); status = pthread_mutex_lock(&pt_mutex); }
+ thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { pthread_mutex_init(&pt_mutex, NULL); status = pthread_mutex_lock(&pt_mutex); }
~thread_lock() { if (status == 0) pthread_mutex_unlock(&pt_mutex); }
};
@@ -571,7 +570,6 @@ namespace Runner {
<< Term::sync_end << flush;
}
//* ----------------------------------------------- THREAD LOOP -----------------------------------------------
-
pthread_exit(NULL);
}
//? ------------------------------------------ Secondary thread end -----------------------------------------------
diff --git a/src/btop_config.cpp b/src/btop_config.cpp
index 6284084..66069d7 100644
--- a/src/btop_config.cpp
+++ b/src/btop_config.cpp
@@ -479,7 +479,7 @@ namespace Config {
}
catch (const std::exception& e) {
Global::exit_error_msg = "Exception during Config::unlock() : " + (string)e.what();
- exit(1);
+ clean_quit(1);
}
locked = false;
@@ -530,7 +530,6 @@ namespace Config {
vector<string> valid_names;
for (auto &n : descriptions)
valid_names.push_back(n[0]);
-
if (string v_string; cread.peek() != '#' or (getline(cread, v_string, '\n') and not s_contains(v_string, Global::Version)))
write_new = true;
while (not cread.eof()) {
diff --git a/src/btop_input.cpp b/src/btop_input.cpp
index 885121f..d7b8ece 100644
--- a/src/btop_input.cpp
+++ b/src/btop_input.cpp
@@ -193,7 +193,7 @@ namespace Input {
if (not filtering) {
bool keep_going = false;
if (str_to_lower(key) == "q") {
- exit(0);
+ clean_quit(0);
}
else if (is_in(key, "escape", "m")) {
Menu::show(Menu::Menus::Main);
diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp
index 891ea1f..c3af6d6 100644
--- a/src/btop_menu.cpp
+++ b/src/btop_menu.cpp
@@ -913,7 +913,7 @@ namespace Menu {
currentMenu = Menus::Help;
return Switch;
case Quit:
- exit(0);
+ clean_quit(0);
}
}
else if (is_in(key, "down", "tab", "mouse_scroll_down", "j")) {
diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp
index b5eaf6b..c9f66df 100644
--- a/src/btop_shared.hpp
+++ b/src/btop_shared.hpp
@@ -34,6 +34,8 @@ using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std:
void term_resize(bool force=false);
void banner_gen();
+extern void clean_quit(int sig);
+
namespace Global {
extern const vector<array<string, 2>> Banner_src;
extern const string Version;
diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp
index 061e43f..b7e80da 100644
--- a/src/btop_tools.cpp
+++ b/src/btop_tools.cpp
@@ -23,10 +23,10 @@ tab-size = 4
#include <sstream>
#include <iomanip>
#include <utility>
+#include <ranges>
#include <robin_hood.h>
#include <unistd.h>
-#include <limits.h>
#include <termios.h>
#include <sys/ioctl.h>
diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp
index 218fbca..f22de84 100644
--- a/src/btop_tools.hpp
+++ b/src/btop_tools.hpp
@@ -28,6 +28,14 @@ tab-size = 4
#include <thread>
#include <tuple>
#include <pthread.h>
+#include <limits.h>
+#ifndef HOST_NAME_MAX
+ #ifdef __APPLE__
+ #define HOST_NAME_MAX 255
+ #else
+ #define HOST_NAME_MAX 64
+ #endif
+#endif
using std::string, std::vector, std::atomic, std::to_string, std::tuple, std::array;
diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp
new file mode 100644
index 0000000..f976a55
--- /dev/null
+++ b/src/osx/btop_collect.cpp
@@ -0,0 +1,1405 @@
+/* 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 <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <libproc.h>
+#include <mach/mach.h>
+#include <mach/mach_host.h>
+#include <mach/mach_init.h>
+#include <mach/mach_types.h>
+#include <mach/processor_info.h>
+#include <mach/vm_statistics.h>
+#include <mach/mach_time.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <netdb.h>
+#include <netinet/tcp_fsm.h>
+#include <pwd.h>
+#include <sys/socket.h>
+#include <sys/statvfs.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdexcept>
+
+#include <btop_config.hpp>
+#include <btop_shared.hpp>
+#include <btop_tools.hpp>
+#include <cmath>
+#include <fstream>
+#include <numeric>
+#include <ranges>
+#include <regex>
+#include <string>
+
+#include "sensors.hpp"
+#include "smc.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;
+ 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;
+ };
+
+ string cpu_sensor;
+ vector<string> core_sensors;
+ unordered_flat_map<int, int> core_mapping;
+} // namespace Cpu
+
+namespace Mem {
+ double old_uptime;
+}
+
+ class MachProcessorInfo {
+ public:
+ processor_info_array_t info_array;
+ mach_msg_type_number_t info_count;
+ MachProcessorInfo() {}
+ virtual ~MachProcessorInfo() {vm_deallocate(mach_task_self(), (vm_address_t)info_array, (vm_size_t)sizeof(processor_info_array_t) * info_count);}
+ };
+
+namespace Shared {
+
+ fs::path passwd_path;
+ uint64_t totalMem;
+ long pageSize, coreCount, clkTck, physicalCoreCount, arg_max;
+ double machTck;
+ int totalMem_len;
+
+ void init() {
+ //? Shared global variables init
+
+ coreCount = sysconf(_SC_NPROCESSORS_ONLN); // this returns all logical cores (threads)
+ if (coreCount < 1) {
+ coreCount = 1;
+ Logger::warning("Could not determine number of cores, defaulting to 1.");
+ }
+
+ size_t physicalCoreCountSize = sizeof(physicalCoreCount);
+ if (sysctlbyname("hw.physicalcpu", &physicalCoreCount, &physicalCoreCountSize, NULL, 0) < 0) {
+ Logger::error("Could not get physical core count");
+ }
+
+ 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.");
+ }
+
+ mach_timebase_info_data_t convf;
+ if (mach_timebase_info(&convf) == KERN_SUCCESS) {
+ machTck = convf.numer / convf.denom;
+ } else {
+ Logger::warning("Could not get mach clock tick conversion factor. Defaulting to 100, processes cpu usage might be incorrect.");
+ machTck = 100;
+ }
+
+ 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;
+
+ //* Get maximum length of process arguments
+ arg_max = sysconf(_SC_ARG_MAX);
+
+ //? 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() 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;
+ bool macM1 = false;
+ tuple<int, long, string> current_bat;
+
+ const array<string, 10> time_names = {"user", "nice", "system", "idle"};
+
+ unordered_flat_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("machdep.cpu.brand_string", &buffer, &size, NULL, 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;
+ if (Config::getB("show_coretemp") and Config::getB("check_temp")) {
+ ThermalSensors sensors;
+ if (sensors.getSensors() > 0) {
+ got_sensors = true;
+ cpu_temp_only = true;
+ macM1 = true;
+ } else {
+ // try SMC (intel)
+ SMCConnection smcCon;
+ try {
+ long long t = smcCon.getTemp(-1); // check if we have package T
+ if (t > -1) {
+ got_sensors = true;
+ } else {
+ got_sensors = false;
+ }
+ } catch (std::runtime_error &e) {
+ // ignore, we don't have temp
+ got_sensors = false;
+ }
+ }
+ }
+ return got_sensors;
+ }
+
+ void update_sensors() {
+ current_cpu.temp_max = 95; // we have no idea how to get the critical temp
+ try {
+ if (macM1) {
+ ThermalSensors sensors;
+ current_cpu.temp.at(0).push_back(sensors.getSensors());
+ if (current_cpu.temp.at(0).size() > 20)
+ current_cpu.temp.at(0).pop_front();
+
+ } else {
+ SMCConnection smcCon;
+ int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount;
+ long long packageT = smcCon.getTemp(-1); // -1 returns package T
+ current_cpu.temp.at(0).push_back(packageT);
+
+ if (Config::getB("show_coretemp") and not cpu_temp_only) {
+ for (int core = 0; core < Shared::coreCount; core++) {
+ long long temp = smcCon.getTemp(core / threadsPerCore); // same temp for all threads of same physical core
+ if (cmp_less(core + 1, current_cpu.temp.size())) {
+ current_cpu.temp.at(core + 1).push_back(temp);
+ if (current_cpu.temp.at(core + 1).size() > 20)
+ current_cpu.temp.at(core + 1).pop_front();
+ }
+ }
+ }
+ }
+ } catch (std::runtime_error &e) {
+ got_sensors = false;
+ Logger::error("failed getting CPU temp");
+ }
+ }
+
+ string get_cpuHz() {
+ unsigned int freq = 1;
+ size_t size = sizeof(freq);
+
+ int mib[] = {CTL_HW, HW_CPU_FREQ};
+
+ if (sysctl(mib, 2, &freq, &size, NULL, 0) < 0) {
+ // this fails on Apple Silicon macs. Apparently you're not allowed to know
+ return "";
+ }
+ return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3);
+ }
+
+ auto get_core_mapping() -> unordered_flat_map<int, int> {
+ unordered_flat_map<int, int> core_map;
+ if (cpu_temp_only) return core_map;
+
+ natural_t cpu_count;
+ natural_t i;
+ MachProcessorInfo info {};
+ kern_return_t error;
+
+ error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
+ if (error != KERN_SUCCESS) {
+ Logger::error("Failed getting CPU info");
+ return core_map;
+ }
+ 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;
+ }
+
+ class IOPSInfo_Wrap {
+ CFTypeRef data;
+ public:
+ IOPSInfo_Wrap() { data = IOPSCopyPowerSourcesInfo(); }
+ CFTypeRef& operator()() { return data; }
+ ~IOPSInfo_Wrap() { CFRelease(data); }
+ };
+
+ class IOPSList_Wrap {
+ CFArrayRef data;
+ public:
+ IOPSList_Wrap(CFTypeRef cft_ref) { data = IOPSCopyPowerSourcesList(cft_ref); }
+ CFArrayRef& operator()() { return data; }
+ ~IOPSList_Wrap() { CFRelease(data); }
+ };
+
+ auto get_battery() -> tuple<int, long, string> {
+ if (not has_battery) return {0, 0, ""};
+
+ uint32_t percent = -1;
+ long seconds = -1;
+ string status = "discharging";
+ IOPSInfo_Wrap ps_info{};
+ if (ps_info()) {
+ IOPSList_Wrap one_ps_descriptor(ps_info());
+ if (one_ps_descriptor()) {
+ if (CFArrayGetCount(one_ps_descriptor())) {
+ CFDictionaryRef one_ps = IOPSGetPowerSourceDescription(ps_info(), CFArrayGetValueAtIndex(one_ps_descriptor(), 0));
+ has_battery = true;
+ 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;
+ }
+ } else {
+ has_battery = false;
+ }
+ }
+ 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;
+
+ double avg[3];
+
+ if (getloadavg(avg, sizeof(avg)) < 0) {
+ Logger::error("failed to get load averages");
+ }
+
+ cpu.load_avg = { (float)avg[0], (float)avg[1], (float)avg[2]};
+
+ natural_t cpu_count;
+ natural_t i;
+ kern_return_t error;
+ processor_cpu_load_info_data_t *cpu_load_info = NULL;
+
+ MachProcessorInfo info{};
+ error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
+ if (error != KERN_SUCCESS) {
+ Logger::error("Failed getting CPU load info");
+ }
+ cpu_load_info = (processor_cpu_load_info_data_t *)info.info_array;
+ long long global_totals = 0;
+ long long global_idles = 0;
+ vector<long long> times_summed = {0, 0, 0, 0};
+ for (i = 0; i < cpu_count; i++) {
+ vector<long long> times;
+ //? 0=user, 1=nice, 2=system, 3=idle
+ for (int x = 0; const unsigned int c_state : {CPU_STATE_USER, CPU_STATE_NICE, CPU_STATE_SYSTEM, CPU_STATE_IDLE}) {
+ auto val = cpu_load_info[i].cpu_ticks[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 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;
+ }
+
+ int64_t getCFNumber(CFDictionaryRef dict, const void *key) {
+ CFNumberRef ref = (CFNumberRef)CFDictionaryGetValue(dict, key);
+ if (ref) {
+ int64_t value;
+ CFNumberGetValue(ref, kCFNumberSInt64Type, &value);
+ return value;
+ }
+ return 0;
+ }
+
+ string getCFString(io_registry_entry_t volumeRef, CFStringRef key) {
+ CFStringRef bsdNameRef = (CFStringRef)IORegistryEntryCreateCFProperty(volumeRef, key, kCFAllocatorDefault, 0);
+ if (bsdNameRef) {
+ char buf[200];
+ CFStringGetCString(bsdNameRef, buf, 200, kCFStringEncodingASCII);
+ CFRelease(bsdNameRef);
+ return string(buf);
+ }
+ return "";
+ }
+
+ bool isWhole(io_registry_entry_t volumeRef) {
+ CFBooleanRef isWhole = (CFBooleanRef)IORegistryEntryCreateCFProperty(volumeRef, CFSTR("Whole"), kCFAllocatorDefault, 0);
+ Boolean val = CFBooleanGetValue(isWhole);
+ CFRelease(isWhole);
+ return bool(val);
+ }
+
+ class IOObject {
+ public:
+ IOObject(string name, io_object_t& obj) : name(name), object(obj) {}
+ virtual ~IOObject() { IOObjectRelease(object); }
+ private:
+ string name;
+ io_object_t &object;
+ };
+
+ void collect_disk(unordered_flat_map<string, disk_info> &disks, unordered_flat_map<string, string> &mapping) {
+ io_registry_entry_t drive;
+ io_iterator_t drive_list;
+
+ mach_port_t libtop_master_port;
+ if (IOMasterPort(bootstrap_port, &libtop_master_port)) {
+ Logger::error("errot getting master port");
+ return;
+ }
+ /* Get the list of all drive objects. */
+ if (IOServiceGetMatchingServices(libtop_master_port,
+ IOServiceMatching("IOMediaBSDClient"), &drive_list)) {
+ Logger::error("Error in IOServiceGetMatchingServices()");
+ return;
+ }
+ auto d = IOObject("drive list", drive_list); // dummy var so it gets destroyed
+ while ((drive = IOIteratorNext(drive_list)) != 0) {
+ auto dr = IOObject("drive", drive);
+ io_registry_entry_t volumeRef;
+ IORegistryEntryGetParentEntry(drive, kIOServicePlane, &volumeRef);
+ if (volumeRef) {
+ if (!isWhole(volumeRef)) {
+ string bsdName = getCFString(volumeRef, CFSTR("BSD Name"));
+ string device = getCFString(volumeRef, CFSTR("VolGroupMntFromName"));
+ if (!mapping.contains(device)) {
+ device = "/dev/" + bsdName; // try again with BSD name - not all volumes seem to have VolGroupMntFromName property
+ }
+ if (device != "") {
+ if (mapping.contains(device)) {
+ string mountpoint = mapping.at(device);
+ if (disks.contains(mountpoint)) {
+ auto& disk = disks.at(mountpoint);
+ CFDictionaryRef properties;
+ IORegistryEntryCreateCFProperties(volumeRef, (CFMutableDictionaryRef *)&properties, kCFAllocatorDefault, 0);
+ if (properties) {
+ CFDictionaryRef statistics = (CFDictionaryRef)CFDictionaryGetValue(properties, CFSTR("Statistics"));
+ if (statistics) {
+ disk_ios++;
+ int64_t readBytes = getCFNumber(statistics, CFSTR("Bytes read from block device"));
+ 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();
+
+ int64_t writeBytes = getCFNumber(statistics, CFSTR("Bytes written to block device"));
+ 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();
+
+ // IOKit does not give us IO times, (use IO read + IO write with 1 MiB being 100% to get some activity indication)
+ 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();
+ }
+ }
+ CFRelease(properties);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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 &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);
+
+ 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("free") = p.free_count * Shared::pageSize;
+ mem.stats.at("cached") = p.external_page_count * Shared::pageSize;
+ mem.stats.at("used") = (p.active_count + p.inactive_count + p.wire_count) * Shared::pageSize;
+ mem.stats.at("available") = Shared::totalMem - mem.stats.at("used");
+ }
+
+ 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));
+ while (cmp_greater(mem.percent.at(name).size(), width * 2))
+ mem.percent.at(name).pop_front();
+ }
+
+ if (show_disks) {
+ unordered_flat_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 statfs *stfs;
+ int count = getmntinfo(&stfs, MNT_WAIT);
+ vector<string> found;
+ found.reserve(last_found.size());
+ for (int i = 0; i < count; i++) {
+ std::error_code ec;
+ string mountpoint = stfs[i].f_mntonname;
+ string dev = stfs[i].f_mntfromname;
+ mapping[dev] = mountpoint;
+
+ if (string(stfs[i].f_fstypename) == "autofs") {
+ continue;
+ }
+
+ //? 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 = stfs[i].f_bfree;
+ 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))
+ 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;