summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJakob P. Liljenberg <admin@qvantnet.com>2024-01-02 16:49:12 +0100
committerGitHub <noreply@github.com>2024-01-02 16:49:12 +0100
commite3dbab9b4b326d10347b1373e61bf4d00924d119 (patch)
treec5a1eb4a3b1ff33f70cfc457ca4b49aec8a15a57
parent285fb215d12a5e0c686b29e1039027cbb2b246da (diff)
parent6a271e5f359a2f1c495d8e844fa1c84572b2c603 (diff)
Merge pull request #607 from joske/openbsd
Adds support for OpenBSD
-rw-r--r--.github/workflows/continuous-build-freebsd.yml2
-rw-r--r--.github/workflows/continuous-build-linux.yml2
-rw-r--r--.github/workflows/continuous-build-macos.yml8
-rw-r--r--.github/workflows/continuous-build-openbsd.yml58
-rw-r--r--Makefile8
-rw-r--r--README.md95
-rw-r--r--src/btop.cpp8
-rw-r--r--src/btop_tools.cpp4
-rw-r--r--src/openbsd/btop_collect.cpp1295
-rw-r--r--src/openbsd/internal.h157
-rw-r--r--src/openbsd/sysctlbyname.cpp46
-rw-r--r--src/openbsd/sysctlbyname.h20
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
+
diff --git a/Makefile b/Makefile
index 2b02f3d..9f4db2e 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index f220e86..fef49c2 100644
--- a/README.md
+++ b/README.md
@@ -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