diff options
author | Jakob P. Liljenberg <admin@qvantnet.com> | 2024-01-02 16:49:12 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-02 16:49:12 +0100 |
commit | e3dbab9b4b326d10347b1373e61bf4d00924d119 (patch) | |
tree | c5a1eb4a3b1ff33f70cfc457ca4b49aec8a15a57 | |
parent | 285fb215d12a5e0c686b29e1039027cbb2b246da (diff) | |
parent | 6a271e5f359a2f1c495d8e844fa1c84572b2c603 (diff) |
Merge pull request #607 from joske/openbsd
Adds support for OpenBSD
-rw-r--r-- | .github/workflows/continuous-build-freebsd.yml | 2 | ||||
-rw-r--r-- | .github/workflows/continuous-build-linux.yml | 2 | ||||
-rw-r--r-- | .github/workflows/continuous-build-macos.yml | 8 | ||||
-rw-r--r-- | .github/workflows/continuous-build-openbsd.yml | 58 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | README.md | 95 | ||||
-rw-r--r-- | src/btop.cpp | 8 | ||||
-rw-r--r-- | src/btop_tools.cpp | 4 | ||||
-rw-r--r-- | src/openbsd/btop_collect.cpp | 1295 | ||||
-rw-r--r-- | src/openbsd/internal.h | 157 | ||||
-rw-r--r-- | src/openbsd/sysctlbyname.cpp | 46 | ||||
-rw-r--r-- | src/openbsd/sysctlbyname.h | 20 |
12 files changed, 1695 insertions, 8 deletions
diff --git a/.github/workflows/continuous-build-freebsd.yml b/.github/workflows/continuous-build-freebsd.yml index 041133f..d06190f 100644 --- a/.github/workflows/continuous-build-freebsd.yml +++ b/.github/workflows/continuous-build-freebsd.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/osx/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-freebsd.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/osx/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-freebsd.yml' diff --git a/.github/workflows/continuous-build-linux.yml b/.github/workflows/continuous-build-linux.yml index 3ef236c..39de640 100644 --- a/.github/workflows/continuous-build-linux.yml +++ b/.github/workflows/continuous-build-linux.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-linux.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-linux.yml' diff --git a/.github/workflows/continuous-build-macos.yml b/.github/workflows/continuous-build-macos.yml index 717e9a7..c8915dd 100644 --- a/.github/workflows/continuous-build-macos.yml +++ b/.github/workflows/continuous-build-macos.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-macos.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-macos.yml' @@ -44,18 +46,18 @@ jobs: with: name: btop-x86_64-macos11-BigSur path: 'bin/*' - + build-macos12: runs-on: macos-12 steps: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: latest-stable - uses: actions/checkout@v3 with: submodules: recursive - + - name: Compile run: | make CXX=g++-12 ARCH=x86_64 STATIC=true STRIP=true diff --git a/.github/workflows/continuous-build-openbsd.yml b/.github/workflows/continuous-build-openbsd.yml new file mode 100644 index 0000000..afb1a93 --- /dev/null +++ b/.github/workflows/continuous-build-openbsd.yml @@ -0,0 +1,58 @@ +name: Continuous Build OpenBSD + +on: + workflow_dispatch: + push: + branches: + - main + tags-ignore: + - '*.*' + paths: + - 'src/**' + - '!src/linux/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-openbsd.yml' + pull_request: + branches: + - main + paths: + - 'src/**' + - '!src/linux/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-openbsd.yml' + +jobs: + build-openbsd: + runs-on: ubuntu-22.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Compile + uses: vmactions/openbsd-vm@v1 + with: + release: '7.4' + usesh: true + prepare: | + pkg_add gmake gcc%11 g++%11 coreutils git + git config --global --add safe.directory /home/runner/work/btop/btop + run: | + gmake CXX=eg++ STATIC=true STRIP=true + GIT_HASH=$(git rev-parse --short "$GITHUB_SHA") + mv bin/btop bin/btop-GCC11-"$GIT_HASH" + ls -alh bin + + - uses: actions/upload-artifact@v3 + with: + name: btop-x86_64-openbsd-7.4 + path: 'bin/*' + if-no-files-found: error + @@ -90,6 +90,8 @@ ifeq ($(CLANG_WORKS),false) CXX := g++11 else ifeq ($(shell command -v g++ >/dev/null; echo $$?),0) CXX := g++ + else ifeq ($(shell command -v eg++ >/dev/null; echo $$?),0) + CXX := eg++ else GCC_NOT_FOUND := true endif @@ -158,6 +160,12 @@ else ifeq ($(PLATFORM_LC),macos) THREADS := $(shell sysctl -n hw.ncpu || echo 1) override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation SU_GROUP := wheel +else ifeq ($(PLATFORM_LC),openbsd) + PLATFORM_DIR := openbsd + THREADS := $(shell sysctl -n hw.ncpu || echo 1) + override ADDFLAGS += -lkvm + export MAKE = gmake + SU_GROUP := wheel else $(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m")) endif @@ -33,6 +33,7 @@ * [Compilation Linux](#compilation-linux) * [Compilation macOS](#compilation-macos-osx) * [Compilation FreeBSD](#compilation-freebsd) +* [Compilation OpenBSD](#compilation-openbsd) * [GPU compatibility](#gpu-compatibility) * [Installing the snap](#installing-the-snap) * [Configurability](#configurability) @@ -868,6 +869,100 @@ Also needs a UTF8 locale and a font that covers: </details> +## Compilation OpenBSD + + Requires at least GCC 10. + + Note that GNU make (`gmake`) is required to compile on OpenBSD. + +<details> +<summary> + +### With gmake +</summary> + +1. **Install dependencies** + + ```bash + pkg_add gmake gcc%11 g++%11 coreutils git + ``` + +2. **Clone repository** + + ```bash + git clone https://github.com/aristocratos/btop.git + cd btop + ``` + +3. **Compile** + + ```bash + gmake CXX=eg++ + ``` + + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `STATIC=true` | For static compilation (only libgcc and libstdc++) | + | `QUIET=true` | For less verbose output | + | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=<architecture>` | To manually set the target architecture | + | `ADDFLAGS=<flags>` | For appending flags to both compiler and linker | + | `CXX=<compiler>` | Manualy set which compiler to use | + + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + +4. **Install** + + ```bash + sudo gmake install + ``` + + Append `PREFIX=/target/dir` to set target, default: `/usr/local` + + Notice! Only use "sudo" when installing to a NON user owned directory. + +5. **(Recommended) Set suid bit to make btop always run as root (or other user)** + + ```bash + sudo gmake setuid + ``` + + No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. + + Run after make install and use same PREFIX if any was used at install. + + Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel` + +* **Uninstall** + + ```bash + sudo gmake uninstall + ``` + +* **Remove any object files from source dir** + + ```bash + gmake clean + ``` + +* **Remove all object files, binaries and created directories in source dir** + + ```bash + gmake distclean + ``` + +* **Show help** + + ```bash + gmake help + ``` + +</details> + ## Installing the snap [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) diff --git a/src/btop.cpp b/src/btop.cpp index 8a0eb05..48d0482 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -255,7 +255,7 @@ void clean_quit(int sig) { Global::quitting = true; Runner::stop(); if (Global::_runner_started) { - #ifdef __APPLE__ + #if defined __APPLE__ || defined __OpenBSD__ if (pthread_join(Runner::runner_id, nullptr) != 0) { Logger::warning("Failed to join _runner thread on exit!"); pthread_cancel(Runner::runner_id); @@ -291,7 +291,7 @@ void clean_quit(int sig) { const auto excode = (sig != -1 ? sig : 0); -#ifdef __APPLE__ +#if defined __APPLE__ || defined __OpenBSD__ _Exit(excode); #else quick_exit(excode); @@ -931,7 +931,7 @@ int main(int argc, char **argv) { catch (...) { found.clear(); } } } - + // #ifdef __APPLE__ if (found.empty()) { CFLocaleRef cflocale = CFLocaleCopyCurrent(); @@ -975,7 +975,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"); } -#ifndef __APPLE__ +#if not defined __APPLE__ && not defined __OpenBSD__ 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_tools.cpp b/src/btop_tools.cpp index aaa9e54..e17edf8 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -134,9 +134,11 @@ namespace Term { tcgetattr(STDIN_FILENO, &initial_settings); current_tty = (ttyname(STDIN_FILENO) != nullptr ? static_cast<string>(ttyname(STDIN_FILENO)) : "unknown"); - //? Disable stream sync + //? Disable stream sync - this does not seem to work on OpenBSD +#ifndef __OpenBSD__ cin.sync_with_stdio(false); cout.sync_with_stdio(false); +#endif //? Disable stream ties cin.tie(nullptr); diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp new file mode 100644 index 0000000..df35662 --- /dev/null +++ b/src/openbsd/btop_collect.cpp @@ -0,0 +1,1295 @@ +/* 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 <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 <sys/endian.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/user.h> +#include <sys/param.h> +#include <sys/ucred.h> +#include <sys/mount.h> +#include <sys/vmmeter.h> +#include <sys/limits.h> +#include <sys/sensors.h> +#include <sys/disk.h> +#include <vector> +#include <kvm.h> +#include <paths.h> +#include <fcntl.h> +#include <unistd.h> + +#include <stdexcept> +#include <cmath> +#include <fstream> +#include <numeric> +#include <ranges> +#include <algorithm> +#include <regex> +#include <string> +#include <memory> + +#include "../btop_config.hpp" +#include "../btop_shared.hpp" +#include "../btop_tools.hpp" + +#include "./sysctlbyname.h" + +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; + + 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; + } + + pageSize = sysconf(_SC_PAGE_SIZE); + if (pageSize <= 0) { + pageSize = 4096; + Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect."); + } + + clkTck = sysconf(_SC_CLK_TCK); + if (clkTck <= 0) { + clkTck = 100; + Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect."); + } + + int64_t memsize = 0; + size_t size = sizeof(memsize); + if (sysctlbyname("hw.physmem", &memsize, &size, nullptr, 0) < 0) { + Logger::warning("Could not get memory size"); + } + totalMem = memsize; + + 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(); + } + + //* RAII wrapper for kvm_openfiles + class kvm_openfiles_wrapper { + kvm_t* kd = nullptr; + public: + kvm_openfiles_wrapper(const char* execf, const char* coref, const char* swapf, int flags, char* err) { + this->kd = kvm_openfiles(execf, coref, swapf, flags, err); + } + ~kvm_openfiles_wrapper() { kvm_close(kd); } + auto operator()() -> kvm_t* { return kd; } + }; + +} // namespace Shared + +namespace Cpu { + string cpuName; + string cpuHz; + bool has_battery = true; + tuple<int, long, string> current_bat; + + const array<string, 10> time_names = {"user", "nice", "system", "idle"}; + + 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; + } + + int64_t get_sensor(string device, sensor_type type, int num) { + int64_t temp = -1; + struct sensordev sensordev; + struct sensor sensor; + size_t sdlen, slen; + int dev; + int mib[] = {CTL_HW, HW_SENSORS, 0, 0, 0}; + + sdlen = sizeof(sensordev); + slen = sizeof(sensor); + for (dev = 0;; dev++) { + mib[2] = dev; + if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { + if (errno == ENXIO) + continue; + if (errno == ENOENT) + break; + } + if (strstr(sensordev.xname, device.c_str())) { + mib[3] = type; + mib[4] = num; + if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) { + if (errno != ENOENT) { + Logger::warning("sysctl"); + continue; + } + } + temp = sensor.value; + break; + } + } + return temp; + } + + bool get_sensors() { + got_sensors = false; + if (Config::getB("show_coretemp") and Config::getB("check_temp")) { + if (get_sensor(string("cpu0") , SENSOR_TEMP, 0) > 0) { + got_sensors = true; + current_cpu.temp_max = 100; // we don't have this info + } else { + Logger::warning("Could not get temp sensor"); + } + } + return got_sensors; + } + +#define MUKTOC(v) ((v - 273150000) / 1000000.0) + + void update_sensors() { + int temp = 0; + int p_temp = 0; + + temp = get_sensor(string("cpu0"), SENSOR_TEMP, 0); + if (temp > -1) { + temp = MUKTOC(temp); + p_temp = 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(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(p_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, long, string> { + if (not has_battery) return {0, 0, ""}; + + long seconds = -1; + uint32_t percent = -1; + string status = "discharging"; + int64_t full, remaining; + full = get_sensor("acpibat0", SENSOR_AMPHOUR, 0); + remaining = get_sensor("acpibat0", SENSOR_AMPHOUR, 3); + int64_t state = get_sensor("acpibat0", SENSOR_INTEGER, 0); + if (full < 0) { + has_battery = false; + Logger::warning("failed to get battery"); + } else { + float_t f = full / 1000; + float_t r = remaining / 1000; + has_battery = true; + percent = r / f * 100; + if (percent == 100) { + status = "full"; + } + switch (state) { + case 0: + status = "full"; + percent = 100; + break; + case 2: + status = "charging"; + break; + } + } + + return {percent, seconds, 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"); + } + + auto cp_time = std::unique_ptr<struct cpustats[]>{ + new struct cpustats[Shared::coreCount] + }; + size_t size = Shared::coreCount * sizeof(struct cpustats); + static int cpustats_mib[] = {CTL_KERN, KERN_CPUSTATS, /*fillme*/0}; + for (int i = 0; i < Shared::coreCount; i++) { + cpustats_mib[2] = i / 2; + if (sysctl(cpustats_mib, 3, &cp_time[i], &size, NULL, 0) == -1) { + Logger::error("sysctl kern.cpustats failed"); + } + } + 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 = cp_time[i].cs_time[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; + } + + 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[2] = { CTL_HW, HW_DISKCOUNT }; + + size_t size; + if (sysctl(mib, 2, &num_drives, &size, NULL, 0) >= 0) { + mib[0] = CTL_HW; + mib[1] = HW_DISKSTATS; + size = num_drives * sizeof(struct diskstats); + auto p = std::unique_ptr<struct diskstats[], void(*)(void*)> { + reinterpret_cast<struct diskstats*>(malloc(size)), + free + }; + if (sysctl(mib, 2, p.get(), &size, NULL, 0) == -1) { + Logger::error("failed to get disk stats"); + return; + } + for (int i = 0; i < num_drives; i++) { + for (auto& [ignored, disk] : disks) { + if (disk.dev.string().find(p[i].ds_name) != string::npos) { + string mountpoint = mapping.at(disk.dev); + total_bytes_read = p[i].ds_rbytes; + total_bytes_write = p[i].ds_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); + + u_int memActive, memWire, cachedMem; + // u_int freeMem; + size_t size; + static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; + static int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT}; + struct uvmexp uvmexp; + struct bcachestats bcstats; + size = sizeof(uvmexp); + if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) { + Logger::error("sysctl failed"); + bzero(&uvmexp, sizeof(uvmexp)); + } + size = sizeof(bcstats); + if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) == -1) { + Logger::error("sysctl failed"); + bzero(&bcstats, sizeof(bcstats)); + } + memActive = uvmexp.active * Shared::pageSize; + memWire = uvmexp.wired; + // freeMem = uvmexp.free * Shared::pageSize; + cachedMem = bcstats.numbufpages * Shared::pageSize; + mem.stats.at("used") = memActive; + mem.stats.at("available") = Shared::totalMem - memActive - memWire; + mem.stats.at("cached") = cachedMem; + mem.stats.at("free") = Shared::totalMem - memActive - memWire; + + if (show_swap) { + int total = uvmexp.swpages * Shared::pageSize; + mem.stats.at("swap_total") = total; + int swapped = uvmexp.swpgonly * Shared::pageSize; + mem.stats.at("swap_used") = swapped; + mem.stats.at("swap_free") = total - swapped; + } + + 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 |