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..36e2ea1 100644
--- a/Makefile
+++ b/Makefile
@@ -73,32 +73,9 @@ ifeq ($(CXX_IS_CLANG),true)
ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt $(MIN_CLANG_VERSION); echo $$?),0)
CLANG_WORKS := true
endif
-endif
-ifeq ($(CLANG_WORKS),false)
- #? Try to find a newer GCC version
- ifeq ($(shell command -v g++-13 >/dev/null; echo $$?),0)
- CXX := g++-13
- else ifeq ($(shell command -v g++13 >/dev/null; echo $$?),0)
- CXX := g++13
- else ifeq ($(shell command -v g++-12 >/dev/null; echo $$?),0)
- CXX := g++-12
- else ifeq ($(shell command -v g++12 >/dev/null; echo $$?),0)
- CXX := g++12
- else ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0)
- CXX := g++-11
- else ifeq ($(shell command -v g++11 >/dev/null; echo $$?),0)
- CXX := g++11
- else ifeq ($(shell command -v g++ >/dev/null; echo $$?),0)
- CXX := g++
- else
- GCC_NOT_FOUND := true
- endif
- ifndef GCC_NOT_FOUND
- override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0)
- override CXX_VERSION_MAJOR := $(shell echo $(CXX_VERSION) | cut -d '.' -f 1)
- ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0)
- GCC_WORKS := true
- endif
+else
+ ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0)
+ GCC_WORKS := true
endif
endif
@@ -158,6 +135,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..fa67b70 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux)
![macOS](https://img.shields.io/badge/-OSX-black?logo=apple)
![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd)
+![OpenBSD](https://img.shields.io/badge/-OpenBSD-black?logo=openbsd)
![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow)
![c++20](https://img.shields.io/badge/cpp-c%2B%2B20-green)
![latest_release](https://img.shields.io/github/v/tag/aristocratos/btop?label=release)
@@ -17,6 +18,7 @@
[![Continuous Build Linux](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml)
[![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)
[![Continuous Build FreeBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml)
+[![Continuous Build OpenBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-openbsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-openbsd.yml)
## Index
@@ -33,6 +35,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 +871,100 @@ Also needs a UTF8 locale and a font that covers:
+## Compilation OpenBSD
+
+ Requires at least GCC 10.
+
+ Note that GNU make (`gmake`) is required to compile on OpenBSD.
+
+
+
+
+### With gmake
+
+
+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=` | To manually set the target architecture |
+ | `ADDFLAGS=` | For appending flags to both compiler and linker |
+ | `CXX=` | 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
+ ```
+
+
+
## Installing the snap
[![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop)
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index f6b2729..2e76ba5 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -45,6 +45,7 @@ parts:
source-type: git
plugin: make
make-parameters:
+ - CXX=g++-11
- PREFIX=/usr/local
- STATIC=true
- ADDFLAGS="-D SNAPPED"
diff --git a/src/btop.cpp b/src/btop.cpp
index 8a0eb05..9ad5012 100644
--- a/src/btop.cpp
+++ b/src/btop.cpp
@@ -16,6 +16,7 @@ indent = tab
tab-size = 4
*/
+#include
#include
#include
#include
@@ -51,6 +52,7 @@ tab-size = 4
#include "btop_theme.hpp"
#include "btop_draw.hpp"
#include "btop_menu.hpp"
+#include "fmt/core.h"
using std::atomic;
using std::cout;
@@ -109,15 +111,16 @@ namespace Global {
bool arg_tty{}; // defaults to false
bool arg_low_color{}; // defaults to false
int arg_preset = -1;
+ int arg_update = 0;
}
//* A simple argument parser
-void argumentParser(const int& argc, char **argv) {
+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 ] [--utf-force] [--debug]\n\n"
+ "usage: btop [-h] [-v] [-/+t] [-p ] [-u ] [--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"
@@ -125,6 +128,7 @@ void argumentParser(const int& argc, char **argv) {
" -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 start with preset, integer value between 0-9\n"
+ " -u, --update 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"
@@ -159,6 +163,19 @@ void argumentParser(const int& argc, char **argv) {
exit(1);
}
}
+ else if (is_in(argument, "-u", "--update")) {
+ if (++i >= argc) {
+ fmt::println("ERROR: Update option needs an argument");
+ 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");
+ exit(1);
+ }
+ }
else if (argument == "--utf-force")
Global::utf_force = true;
else if (argument == "--debug")
@@ -176,7 +193,7 @@ void term_resize(bool force) {
static atomic resizing (false);
if (Input::polling) {
Global::resized = true;
- Input::interrupt = true;
+ Input::interrupt();
return;
}
atomic_lock lck(resizing, true);
@@ -246,7 +263,7 @@ void term_resize(bool force) {
else if (not Term::refresh()) break;
}
- Input::interrupt = true;
+ Input::interrupt();
}
//* Exit handler; stops threads, restores terminal and saves config changes
@@ -255,7 +272,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 +308,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);
@@ -321,7 +338,7 @@ void _signal_handler(const int sig) {
if (Runner::active) {
Global::should_quit = true;
Runner::stopping = true;
- Input::interrupt = true;
+ Input::interrupt();
}
else {
clean_quit(0);
@@ -331,7 +348,7 @@ void _signal_handler(const int sig) {
if (Runner::active) {
Global::should_sleep = true;
Runner::stopping = true;
- Input::interrupt = true;
+ Input::interrupt();
}
else {
_sleep();
@@ -343,6 +360,9 @@ void _signal_handler(const int sig) {
case SIGWINCH:
term_resize();
break;
+ case SIGUSR1:
+ // Input::poll interrupt
+ break;
}
}
@@ -477,7 +497,7 @@ namespace Runner {
if (pt_lck.status != 0) {
Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status);
Global::thread_exception = true;
- Input::interrupt = true;
+ Input::interrupt();
stopping = true;
}
@@ -488,7 +508,7 @@ namespace Runner {
if (active) {
Global::exit_error_msg = "Runner thread failed to get active lock!";
Global::thread_exception = true;
- Input::interrupt = true;
+ Input::interrupt();
stopping = true;
}
if (stopping or Global::resized) {
@@ -558,7 +578,7 @@ namespace Runner {
coreNum_reset = false;
Cpu::core_mapping = Cpu::get_core_mapping();
Global::resized = true;
- Input::interrupt = true;
+ Input::interrupt();
continue;
}
@@ -655,7 +675,7 @@ namespace Runner {
catch (const std::exception& e) {
Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()};
Global::thread_exception = true;
- Input::interrupt = true;
+ Input::interrupt();
stopping = true;
}
@@ -829,29 +849,23 @@ int main(int argc, char **argv) {
//? Call argument parser if launched with arguments
if (argc > 1) argumentParser(argc, argv);
- //? Setup paths for config, log and user themes
- for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
- if (std::getenv(env) != nullptr and access(std::getenv(env), W_OK) != -1) {
- Config::conf_dir = fs::path(std::getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop");
- break;
- }
- }
- if (Config::conf_dir.empty()) {
- fmt::println("WARNING: Could not get path user HOME folder.\n"
- "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this.");
- }
- else {
- if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) {
- fmt::println("WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n"
- "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this.");
- }
- else {
+ {
+ const auto config_dir = Config::get_config_dir();
+ if (config_dir.has_value()) {
+ Config::conf_dir = config_dir.value();
Config::conf_file = Config::conf_dir / "btop.conf";
Logger::logfile = Config::conf_dir / "btop.log";
Theme::user_theme_dir = Config::conf_dir / "themes";
- if (not fs::exists(Theme::user_theme_dir) and not fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear();
+
+ // If necessary create the user theme directory
+ std::error_code error;
+ if (not fs::exists(Theme::user_theme_dir, error) and not fs::create_directories(Theme::user_theme_dir, error)) {
+ Theme::user_theme_dir.clear();
+ Logger::warning("Failed to create user theme directory: " + error.message());
+ }
}
}
+
//? Try to find global btop theme path relative to binary path
#ifdef __linux__
{ std::error_code ec;
@@ -931,7 +945,7 @@ int main(int argc, char **argv) {
catch (...) { found.clear(); }
}
}
-
+ //
#ifdef __APPLE__
if (found.empty()) {
CFLocaleRef cflocale = CFLocaleCopyCurrent();
@@ -975,7 +989,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");
@@ -1014,6 +1028,12 @@ int main(int argc, char **argv) {
std::signal(SIGTSTP, _signal_handler);
std::signal(SIGCONT, _signal_handler);
std::signal(SIGWINCH, _signal_handler);
+ std::signal(SIGUSR1, _signal_handler);
+
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ pthread_sigmask(SIG_BLOCK, &mask, &Input::signal_mask);
//? Start runner thread
Runner::thread_sem_init();
@@ -1035,9 +1055,10 @@ int main(int argc, char **argv) {
{
const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes"));
if (Term::height < y or Term::width < x) {
+ pthread_sigmask(SIG_SETMASK, &Input::signal_mask, &mask);
term_resize(true);
+ pthread_sigmask(SIG_SETMASK, &mask, nullptr);
Global::resized = false;
- Input::interrupt = false;
}
}
@@ -1050,6 +1071,9 @@ int main(int argc, char **argv) {
//? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
+ if (Global::arg_update != 0) {
+ Config::set("update_ms", Global::arg_update);
+ }
uint64_t update_ms = Config::getI("update_ms");
auto future_time = time_ms();
diff --git a/src/btop_config.cpp b/src/btop_config.cpp
index fd28f67..750cc07 100644
--- a/src/btop_config.cpp
+++ b/src/btop_config.cpp
@@ -24,6 +24,7 @@ tab-size = 4
#include
#include
+#include
#include "btop_config.hpp"
#include "btop_shared.hpp"
@@ -323,6 +324,65 @@ namespace Config {
};
std::unordered_map intsTmp;
+ // Returns a valid config dir or an empty optional
+ // The config dir might be read only, a warning is printed, but a path is returned anyway
+ [[nodiscard]] std::optional get_config_dir() noexcept {
+ fs::path config_dir;
+ {
+ std::error_code error;
+ if (const auto xdg_config_home = std::getenv("XDG_CONFIG_HOME"); xdg_config_home != nullptr) {
+ if (fs::exists(xdg_config_home, error)) {
+ config_dir = fs::path(xdg_config_home) / "btop";
+ }
+ } else if (const auto home = std::getenv("HOME"); home != nullptr) {
+ error.clear();
+ if (fs::exists(home, error)) {
+ config_dir = fs::path(home) / ".config" / "btop";
+ }
+ if (error) {
+ fmt::print(stderr, "\033[0;31mWarning: \033[0m{} could not be accessed: {}\n", config_dir.string(), error.message());
+ config_dir = "";
+ }
+ }
+ }
+
+ // FIXME: This warnings can be noisy if the user deliberately has a non-writable config dir
+ // offer an alternative | disable messages by default | disable messages if config dir is not writable | disable messages with a flag
+ // FIXME: Make happy path not branch
+ if (not config_dir.empty()) {
+ std::error_code error;
+ if (fs::exists(config_dir, error)) {
+ if (fs::is_directory(config_dir, error)) {
+ struct statvfs stats {};
+ if ((fs::status(config_dir, error).permissions() & fs::perms::owner_write) == fs::perms::owner_write and
+ statvfs(config_dir.c_str(), &stats) == 0 and (stats.f_flag & ST_RDONLY) == 0) {
+ return config_dir;
+ } else {
+ fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` is not writable\n", fs::absolute(config_dir).string());
+ // If the config is readable we can still use the provided config, but changes will not be persistent
+ if ((fs::status(config_dir, error).permissions() & fs::perms::owner_read) == fs::perms::owner_read) {
+ fmt::print(stderr, "\033[0;31mWarning: \033[0mLogging is disabled, config changes are not persistent\n");
+ return config_dir;
+ }
+ }
+ } else {
+ fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` is not a directory\n", fs::absolute(config_dir).string());
+ }
+ } else {
+ // Doesn't exist
+ if (fs::create_directories(config_dir, error)) {
+ return config_dir;
+ } else {
+ fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` could not be created: {}\n", fs::absolute(config_dir).string(), error.message());
+ }
+ }
+ } else {
+ fmt::print(stderr, "\033[0;31mWarning: \033[0mCould not determine config path: Make sure `$XDG_CONFIG_HOME` or `$HOME` is set\n");
+ }
+ fmt::print(stderr, "\033[0;31mWarning: \033[0mLogging is disabled, config changes are not persistent\n");
+ return {};
+ }
+
bool _locked(const std::string_view name) {
atomic_wait(writelock, true);
if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end())
@@ -431,8 +491,8 @@ namespace Config {
if (name == "update_ms" and i_value < 100)
validError = "Config value update_ms set too low (<100).";
- else if (name == "update_ms" and i_value > 86400000)
- validError = "Config value update_ms set too high (>86400000).";
+ else if (name == "update_ms" and i_value > ONE_DAY_MILLIS)
+ validError = fmt::format("Config value update_ms set too high (>{}).", ONE_DAY_MILLIS);
else
return true;
@@ -597,12 +657,17 @@ namespace Config {
}
void load(const fs::path& conf_file, vector& load_warnings) {
+ std::error_code error;
if (conf_file.empty())
return;
- else if (not fs::exists(conf_file)) {
+ else if (not fs::exists(conf_file, error)) {
write_new = true;
return;
}
+ if (error) {
+ return;
+ }
+
std::ifstream cread(conf_file);
if (cread.good()) {
vector valid_names;
diff --git a/src/btop_config.hpp b/src/btop_config.hpp
index 2b586af..3651566 100644
--- a/src/btop_config.hpp
+++ b/src/btop_config.hpp
@@ -18,9 +18,10 @@ tab-size = 4
#pragma once
+#include
+#include
#include
#include
-#include
#include
@@ -57,6 +58,10 @@ namespace Config {
extern vector available_batteries;
extern int current_preset;
+ constexpr int ONE_DAY_MILLIS = 1000 * 60 * 60 * 24;
+
+ [[nodiscard]] std::optional get_config_dir() noexcept;
+
//* Check if string only contains space separated valid names for boxes
bool check_boxes(const string& boxes);
@@ -94,7 +99,7 @@ namespace Config {
}
//* Set config key to int
- inline void set(const std::string_view name, const int& value) {
+ inline void set(const std::string_view name, const int value) {
if (_locked(name)) intsTmp.insert_or_assign(name, value);
else ints.at(name) = value;
}
diff --git a/src/btop_input.cpp b/src/btop_input.cpp
index fb9e46d..fbbae58 100644
--- a/src/btop_input.cpp
+++ b/src/btop_input.cpp
@@ -16,12 +16,13 @@ indent = tab
tab-size = 4
*/
-#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include "btop_input.hpp"
@@ -31,17 +32,6 @@ tab-size = 4
#include "btop_menu.hpp"
#include "btop_draw.hpp"
-
-#include "btop_input.hpp"
-#include "btop_tools.hpp"
-#include "btop_config.hpp"
-#include "btop_shared.hpp"
-#include "btop_menu.hpp"
-#include "btop_draw.hpp"
-
-
-using std::cin;
-
using namespace Tools;
using namespace std::literals; // for operator""s
namespace rng = std::ranges;
@@ -89,83 +79,45 @@ namespace Input {
{"[24~", "f12"}
};
- std::atomic interrupt (false);
+ sigset_t signal_mask;
std::atomic polling (false);
array mouse_pos;
std::unordered_map mouse_mappings;
deque history(50, "");
string old_filter;
+ string input;
- struct InputThr {
- InputThr() : thr(run, this) {
- }
-
- static void run(InputThr* that) {
- that->runImpl();
- }
-
- void runImpl() {
- char ch = 0;
-
- // TODO(pg83): read whole buffer
- while (cin.get(ch)) {
- std::lock_guard g(lock);
- current.push_back(ch);
- if (current.size() > 100) {
- current.clear();
- }
- }
- }
-
- size_t avail() {
- std::lock_guard g(lock);
-
- return current.size();
- }
-
- std::string get() {
- std::string res;
-
- {
- std::lock_guard g(lock);
-
- res.swap(current);
- }
-
- return res;
- }
-
- static InputThr& instance() {
- // intentional memory leak, to simplify shutdown process
- static InputThr* input = new InputThr();
-
- return *input;
- }
-
- std::string current;
- // TODO(pg83): use std::conditional_variable instead of sleep
- std::mutex lock;
- std::thread thr;
- };
-
- bool poll(int timeout) {
+ bool poll(const uint64_t timeout) {
atomic_lock lck(polling);
- if (timeout < 1) return InputThr::instance().avail() > 0;
- while (timeout > 0) {
- if (interrupt) {
- interrupt = false;
- return false;
- }
- if (InputThr::instance().avail() > 0) return true;
- sleep_ms(timeout < 10 ? timeout : 10);
- timeout -= 10;
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(STDIN_FILENO, &fds);
+ struct timespec wait;
+ struct timespec *waitptr = nullptr;
+
+ if(timeout != std::numeric_limits::max()) {
+ wait.tv_sec = timeout / 1000;
+ wait.tv_nsec = (timeout % 1000) * 1000000;
+ waitptr = &wait;
}
+
+ if(pselect(STDIN_FILENO + 1, &fds, nullptr, nullptr, waitptr, &signal_mask) > 0) {
+ input.clear();
+ char buf[1024];
+ ssize_t count = 0;
+ while((count = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
+ input.append(std::string_view(buf, count));
+ }
+
+ return true;
+ }
+
return false;
}
string get() {
- string key = InputThr::instance().get();
+ string key = input;
if (not key.empty()) {
//? Remove escape code prefix if present
if (key.substr(0, 2) == Fx::e) {
@@ -238,12 +190,14 @@ namespace Input {
}
string wait() {
- while (InputThr::instance().avail() < 1) {
- sleep_ms(10);
- }
+ while(not poll(std::numeric_limits::max())) {}
return get();
}
+ void interrupt() {
+ kill(getpid(), SIGUSR1);
+ }
+
void clear() {
// do not need it, actually
}
diff --git a/src/btop_input.hpp b/src/btop_input.hpp
index fc6fda0..13f14fe 100644
--- a/src/btop_input.hpp
+++ b/src/btop_input.hpp
@@ -29,9 +29,9 @@ using std::atomic;
using std::deque;
using std::string;
-/* The input functions relies on the following std::cin options being set:
- cin.sync_with_stdio(false);
- cin.tie(nullptr);
+/* The input functions rely on the following termios parameters being set:
+ Non-canonical mode (c_lflags & ~(ICANON))
+ VMIN and VTIME (c_cc) set to 0
These will automatically be set when running Term::init() from btop_tools.cpp
*/
@@ -45,7 +45,9 @@ namespace Input {
//? line, col, height, width
extern std::unordered_map mouse_mappings;
- extern atomic interrupt;
+ //* Signal mask used during polling read
+ extern sigset_t signal_mask;
+
extern atomic polling;
//* Mouse column and line position
@@ -55,7 +57,7 @@ namespace Input {
extern deque history;
//* Poll keyboard & mouse input for ms and return input availabilty as a bool
- bool poll(int timeout=0);
+ bool poll(const uint64_t timeout=0);
//* Get a key or mouse action from input
string get();
@@ -63,6 +65,9 @@ namespace Input {
//* Wait until input is available and return key
string wait();
+ //* Interrupt poll/wait
+ void interrupt();
+
//* Clears last entered key
void clear();
diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp
index aaa9e54..47c53bc 100644
--- a/src/btop_tools.cpp
+++ b/src/btop_tools.cpp
@@ -26,9 +26,10 @@ tab-size = 4
#include
#include
-#include
-#include
+#include
#include
+#include
+#include
#include "unordered_map"
#include "widechar_width.hpp"
@@ -36,7 +37,6 @@ tab-size = 4
#include "btop_tools.hpp"
#include "btop_config.hpp"
-using std::cin;
using std::cout;
using std::floor;
using std::flush;
@@ -76,7 +76,11 @@ namespace Term {
struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ICANON;
- else settings.c_lflag &= ~(ICANON);
+ else {
+ settings.c_lflag &= ~(ICANON);
+ settings.c_cc[VMIN] = 0;
+ settings.c_cc[VTIME] = 0;
+ }
if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
if (on) setlinebuf(stdin);
else setbuf(stdin, nullptr);
@@ -85,12 +89,27 @@ namespace Term {
}
bool refresh(bool only_check) {
- struct winsize w;
- if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0) return false;
- if (width != w.ws_col or height != w.ws_row) {
+ // Query dimensions of '/dev/tty' of the 'STDOUT_FILENO' isn't avaiable.
+ // This variable is set in those cases to avoid calls to ioctl
+ constinit static bool uses_dev_tty = false;
+ struct winsize wsize {};
+ if (uses_dev_tty || ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsize) < 0 || (wsize.ws_col == 0 && wsize.ws_row == 0)) {
+ Logger::error(R"(Couldn't determine terminal size of "STDOUT_FILENO"!)");
+ auto dev_tty = open("/dev/tty", O_RDONLY);
+ if (dev_tty != -1) {
+ ioctl(dev_tty, TIOCGWINSZ, &wsize);
+ close(dev_tty);
+ }
+ else {
+ Logger::error(R"(Couldn't determine terminal size of "/dev/tty"!)");
+ return false;
+ }
+ uses_dev_tty = true;
+ }
+ if (width != wsize.ws_col or height != wsize.ws_row) {
if (not only_check) {
- width = w.ws_col;
- height = w.ws_row;
+ width = wsize.ws_col;
+ height = wsize.ws_row;
}
return true;
}
@@ -134,12 +153,12 @@ namespace Term {
tcgetattr(STDIN_FILENO, &initial_settings);
current_tty = (ttyname(STDIN_FILENO) != nullptr ? static_cast(ttyname(STDIN_FILENO)) : "unknown");
- //? Disable stream sync
- cin.sync_with_stdio(false);
+ //? Disable stream sync - this does not seem to work on OpenBSD
+#ifndef __OpenBSD__
cout.sync_with_stdio(false);
+#endif
//? Disable stream ties
- cin.tie(nullptr);
cout.tie(nullptr);
echo(false);
linebuffered(false);
@@ -651,6 +670,7 @@ namespace Logger {
lose_priv neutered{};
std::error_code ec;
try {
+ // NOTE: `exist()` could throw but since we return with an empty logfile we don't care
if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) {
auto old_log = logfile;
old_log += ".1";
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
+#include
+#include
+#include
+#include
+// man 3 getifaddrs: "BUGS: If both and are being included, must be included before "
+#include
+#include
+#include
+#include
+#include
+#include
+#include // for inet_ntop stuff
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 core_old_totals;
+ vector core_old_idles;
+ vector available_fields = {"total"};
+ vector 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 core_sensors;
+ std::unordered_map 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 current_bat;
+
+ const array time_names = {"user", "nice", "system", "idle"};
+
+ std::unordered_map 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 {
+ std::unordered_map 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 {
+ 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{
+ 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 times_summed = {0, 0, 0, 0};
+
+ for (long i = 0; i < Shared::coreCount; i++) {
+ vector 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 fstab;
+ fs::file_time_type fstab_time;
+ int disk_ios = 0;
+ vector 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 &disks, std::unordered_map &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 {
+ reinterpret_cast(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 (show_disks) {
+ std::unordered_map 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 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 found;
+ found.reserve(last_found.size());
+ for (int i = 0; i < count; i++) {
+ auto fstype = string(stfs[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 = stfs[i].f_mntonname;
+ string dev = stfs[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 = 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;
+ disk.used = disk.total - disk.free;
+ disk.used_percent = round((double)disk.used * 100 / disk.total);
+ disk.free_percent = 100 - disk.used_percent;
+ }
+
+ //? 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();
+ }
+ for (const auto &name : last_found)
+ if (not is_in(name, "/", "swap", "/dev"))
+ mem.disks_order.push_back(name);
+
+ disk_ios = 0;
+ collect_disk(disks, mapping);
+
+ old_uptime = uptime;
+ }
+ return mem;
+ }
+
+} // namespace Mem
+
+namespace Net {
+ std::unordered_map current_net;
+ net_info empty_net = {};
+ vector interfaces;
+ string selected_iface;
+ int errors = 0;
+ std::unordered_map graph_max = {{"download", {}}, {"upload", {}}};
+ std::unordered_map> max_count = {{"download", {}}, {"upload", {}}};
+ bool rescale = true;
+ uint64_t timestamp = 0;
+
+ //* RAII wrapper for getifaddrs
+ class getifaddr_wrapper {
+ struct ifaddrs *ifaddr;
+
+ public:
+ int status;
+ getifaddr_wrapper() { status = getifaddrs(&ifaddr); }
+ ~getifaddr_wrapper() { freeifaddrs(ifaddr); }
+ auto operator()() -> struct ifaddrs * { return ifaddr; }
+ };
+
+ auto collect(bool no_update) -> net_info & {
+ auto &net = current_net;
+ auto &config_iface = Config::getS("net_iface");
+ auto net_sync = Config::getB("net_sync");
+ auto net_auto = Config::getB("net_auto");
+ auto new_timestamp = time_ms();
+
+ if (not no_update and errors < 3) {
+ //? Get interface list using getifaddrs() wrapper
+ getifaddr_wrapper if_wrap{};
+ if (if_wrap.status != 0) {
+ errors++;
+ Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status));
+ redraw = true;
+ return empty_net;
+ }
+ int family = 0;
+ static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); // 46 >= 16, compile-time assurance.
+ enum { IPBUFFER_MAXSIZE = INET6_ADDRSTRLEN }; // manually using the known biggest value, guarded by the above static_assert
+ char ip[IPBUFFER_MAXSIZE];
+ interfaces.clear();
+ string ipv4, ipv6;
+
+ //? Iteration over all items in getifaddrs() list
+ for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == nullptr) continue;
+ family = ifa->ifa_addr->sa_family;
+ const auto &iface = ifa->ifa_name;
+ //? Update available interfaces vector and get status of interface
+ if (not v_contains(interfaces, iface)) {
+ interfaces.push_back(iface);
+ net[iface].connected = (ifa->ifa_flags & IFF_RUNNING);
+
+ // An interface can have more than one IP of the same family associated with it,
+ // but we pick only the first one to show in the NET box.
+ // Note: Interfaces without any IPv4 and IPv6 set are still valid and monitorable!
+ net[iface].ipv4.clear();
+ net[iface].ipv6.clear();
+ }
+ //? Get IPv4 address
+ if (family == AF_INET) {
+ if (net[iface].ipv4.empty()) {
+ if (nullptr != inet_ntop(family, &(reinterpret_cast(ifa->ifa_addr)->sin_addr), ip, IPBUFFER_MAXSIZE)) {
+
+ net[iface].ipv4 = ip;
+ } else {
+ int errsv = errno;
+ Logger::error("Net::collect() -> Failed to convert IPv4 to string for iface " + string(iface) + ", errno: " + strerror(errsv));
+ }
+ }
+ }
+ //? Get IPv6 address
+ else if (family == AF_INET6) {
+ if (net[iface].ipv6.empty()) {
+ if (nullptr != inet_ntop(family, &(reinterpret_cast(ifa->ifa_addr)->sin6_addr), ip, IPBUFFER_MAXSIZE)) {
+ net[iface].ipv6 = ip;
+ } else {
+ int errsv = errno;
+ Logger::error("Net::collect() -> Failed to convert IPv6 to string for iface " + string(iface) + ", errno: " + strerror(errsv));
+ }
+ }
+ } //else, ignoring family==AF_LINK (see man 3 getifaddrs)
+ }
+
+ std::unordered_map> ifstats;
+ int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0};
+ size_t len;
+ if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) {
+ Logger::error("failed getting network interfaces");
+ } else {
+ std::unique_ptr buf(new char[len]);
+ if (sysctl(mib, 6, buf.get(), &len, nullptr, 0) < 0) {
+ Logger::error("failed getting network interfaces");
+ } else {
+ char *lim = buf.get() + len;
+ char *next = nullptr;
+ for (next = buf.get(); next < lim;) {
+ struct if_msghdr *ifm = (struct if_msghdr *)next;
+ next += ifm->ifm_msglen;
+ struct if_data ifm_data = ifm->ifm_data;
+ if (ifm->ifm_addrs & RTA_IFP) {
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)(ifm + 1);
+ char iface[32];
+ strncpy(iface, sdl->sdl_data, sdl->sdl_nlen);
+ iface[sdl->sdl_nlen] = 0;
+ ifstats[iface] = std::tuple(ifm_data.ifi_ibytes, ifm_data.ifi_obytes);
+ }
+ }
+ }
+ }
+
+ //? Get total recieved and transmitted bytes + device address if no ip was found
+ for (const auto &iface : interfaces) {
+ for (const string dir : {"download", "upload"}) {
+ auto &saved_stat = net.at(iface).stat.at(dir);
+ auto &bandwidth = net.at(iface).bandwidth.at(dir);
+ uint64_t val = dir == "download" ? std::get<0>(ifstats[iface]) : std::get<1>(ifstats[iface]);
+
+ //? Update speed, total and top values
+ if (val < saved_stat.last) {
+ saved_stat.rollover += saved_stat.last;
+ saved_stat.last = 0;
+ }
+ if (cmp_greater((unsigned long long)saved_stat.rollover + (unsigned long long)val, numeric_limits::max())) {
+ saved_stat.rollover = 0;
+ saved_stat.last = 0;
+ }
+ saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000));
+ if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed;
+ if (saved_stat.offset > val + saved_stat.rollover) saved_stat.offset = 0;
+ saved_stat.total = (val + saved_stat.rollover) - saved_stat.offset;
+ saved_stat.last = val;
+
+ //? Add values to graph
+ bandwidth.push_back(saved_stat.speed);
+ while (cmp_greater(bandwidth.size(), width * 2)) bandwidth.pop_front();
+
+ //? Set counters for auto scaling
+ if (net_auto and selected_iface == iface) {
+ if (saved_stat.speed > graph_max[dir]) {
+ ++max_count[dir][0];
+ if (max_count[dir][1] > 0) --max_count[dir][1];
+ } else if (graph_max[dir] > 10 << 10 and saved_stat.speed < graph_max[dir] / 10) {
+ ++max_count[dir][1];
+ if (max_count[dir][0] > 0) --max_count[dir][0];
+ }
+ }
+ }
+ }
+
+ //? Clean up net map if needed
+ if (net.size() > interfaces.size()) {
+ for (auto it = net.begin(); it != net.end();) {
+ if (not v_contains(interfaces, it->first))
+ it = net.erase(it);
+ else
+ it++;
+ }
+ }
+
+ timestamp = new_timestamp;
+ }
+ //? Return empty net_info struct if no interfaces was found
+ if (net.empty())
+ return empty_net;
+
+ //? Find an interface to display if selected isn't set or valid
+ if (selected_iface.empty() or not v_contains(interfaces, selected_iface)) {
+ max_count["download"][0] = max_count["download"][1] = max_count["upload"][0] = max_count["upload"][1] = 0;
+ redraw = true;
+ if (net_auto) rescale = true;
+ if (not config_iface.empty() and v_contains(interfaces, config_iface))
+ selected_iface = config_iface;
+ else {
+ //? Sort interfaces by total upload + download bytes
+ auto sorted_interfaces = interfaces;
+ rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) {
+ return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total,
+ net.at(b).stat["download"].total + net.at(b).stat["upload"].total);
+ });
+ selected_iface.clear();
+ //? Try to set to a connected interface
+ for (const auto &iface : sorted_interfaces) {
+ if (net.at(iface).connected) selected_iface = iface;
+ break;
+ }
+ //? If no interface is connected set to first available
+ if (selected_iface.empty() and not sorted_interfaces.empty())
+ selected_iface = sorted_interfaces.at(0);
+ else if (sorted_interfaces.empty())
+ return empty_net;
+ }
+ }
+
+ //? Calculate max scale for graphs if needed
+ if (net_auto) {
+ bool sync = false;
+ for (const auto &dir : {"download", "upload"}) {
+ for (const auto &sel : {0, 1}) {
+ if (rescale or max_count[dir][sel] >= 5) {
+ const long long avg_speed = (net[selected_iface].bandwidth[dir].size() > 5
+ ? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0ll) / 5
+ : net[selected_iface].stat[dir].speed);
+ graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10);
+ max_count[dir][0] = max_count[dir][1] = 0;
+ redraw = true;
+ if (net_sync) sync = true;
+ break;
+ }
+ }
+ //? Sync download/upload graphs if enabled
+ if (sync) {
+ const auto other = (string(dir) == "upload" ? "download" : "upload");
+ graph_max[other] = graph_max[dir];
+ max_count[other][0] = max_count[other][1] = 0;
+ break;
+ }
+ }
+ }
+
+ rescale = false;
+ return net.at(selected_iface);
+ }
+} // namespace Net
+
+namespace Proc {
+
+ vector current_procs;
+ std::unordered_map uid_user;
+ string current_sort;
+ string current_filter;
+ bool current_rev = false;
+
+ fs::file_time_type passwd_time;
+
+ uint64_t cputimes;
+ int collapse = -1, expand = -1;
+ uint64_t old_cputimes = 0;
+ atomic numpids = 0;
+ int filter_found = 0;
+
+ detail_container detailed;
+
+ string get_status(char s) {
+ if (s & SRUN) return "Running";
+ if (s & SSLEEP) return "Sleeping";
+ if (s & SIDL) return "Idle";
+ if (s & SSTOP) return "Stopped";
+ if (s & SZOMB) return "Zombie";
+ return "Unknown";
+ }
+
+ //* Get detailed info for selected process
+ void _collect_details(const size_t pid, vector &procs) {
+ if (pid != detailed.last_pid) {
+ detailed = {};
+ detailed.last_pid = pid;
+ detailed.skip_smaps = not Config::getB("proc_info_smaps");
+ }
+
+ //? Copy proc_info for process from proc vector
+ auto p_info = rng::find(procs, pid, &proc_info::pid);
+ detailed.entry = *p_info;
+
+ //? Update cpu percent deque for process cpu graph
+ if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount;
+ detailed.cpu_percent.push_back(clamp((long long)round(detailed.entry.cpu_p), 0ll, 100ll));
+ while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front();
+
+ //? Process runtime : current time - start time (both in unix time - seconds since epoch)
+ struct timeval currentTime;
+ gettimeofday(¤tTime, nullptr);
+ detailed.elapsed = sec_to_dhms(currentTime.tv_sec - detailed.entry.cpu_s); // only interested in second granularity, so ignoring tc_usec
+ if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3);
+
+ //? Get parent process name
+ if (detailed.parent.empty()) {
+ auto p_entry = rng::find(procs, detailed.entry.ppid, &proc_info::pid);
+ if (p_entry != procs.end()) detailed.parent = p_entry->name;
+ }
+
+ //? Expand process status from single char to explanative string
+ detailed.status = get_status(detailed.entry.state);
+
+ detailed.mem_bytes.push_back(detailed.entry.mem);
+ detailed.memory = floating_humanizer(detailed.entry.mem);
+
+ if (detailed.first_mem == -1 or detailed.first_mem < detailed.mem_bytes.back() / 2 or detailed.first_mem > detailed.mem_bytes.back() * 4) {
+ detailed.first_mem = min((uint64_t)detailed.mem_bytes.back() * 2, Mem::get_totalMem());
+ redraw = true;
+ }
+
+ while (cmp_greater(detailed.mem_bytes.size(), width)) detailed.mem_bytes.pop_front();
+ }
+
+ //* Collects and sorts process information from /proc
+ auto collect(bool no_update) -> vector & {
+ const auto &sorting = Config::getS("proc_sorting");
+ auto reverse = Config::getB("proc_reversed");
+ const auto &filter = Config::getS("proc_filter");
+ auto per_core = Config::getB("proc_per_core");
+ auto tree = Config::getB("proc_tree");
+ auto show_detailed = Config::getB("show_detailed");
+ const size_t detailed_pid = Config::getI("detailed_pid");
+ bool should_filter = current_filter != filter;
+ if (should_filter) current_filter = filter;
+ bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter);
+ if (sorted_change) {
+ current_sort = sorting;
+ current_rev = reverse;
+ }
+
+ const int cmult = (per_core) ? Shared::coreCount : 1;
+ bool got_detailed = false;
+
+ static vector found;
+
+ //* Use pids from last update if only changing filter, sorting or tree options
+ if (no_update and not current_procs.empty()) {
+ if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, current_procs);
+ } else {
+ //* ---------------------------------------------Collection start----------------------------------------------
+
+ should_filter = true;
+ found.clear();
+ struct timeval currentTime;
+ gettimeofday(¤tTime, nullptr);
+ const double timeNow = currentTime.tv_sec + (currentTime.tv_usec / 1'000'000);
+
+ int count = 0;
+ char buf[_POSIX2_LINE_MAX];
+ Shared::kvm_openfiles_wrapper kd(nullptr, nullptr, nullptr, KVM_NO_FILES, buf);
+ const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count);
+
+ for (int i = 0; i < count; i++) {
+ const struct kinfo_proc* kproc = &kprocs[i];
+ const size_t pid = (size_t)kproc->p_pid;
+ if (pid < 1) continue;
+ found.push_back(pid);
+
+ //? Check if pid already exists in current_procs
+ bool no_cache = false;
+ auto find_old = rng::find(current_procs, pid, &proc_info::pid);
+ if (find_old == current_procs.end()) {
+ current_procs.push_back({pid});
+ find_old = current_procs.end() - 1;
+ no_cache = true;
+ }
+
+ auto &new_proc = *find_old;
+
+ //? Get program name, command, username, parent pid, nice and status
+ if (no_cache) {
+ if (string(kproc->p_comm) == "idle"s) {
+ current_procs.pop_back();
+ found.pop_back();
+ continue;
+ }
+ new_proc.name = kproc->p_comm;
+ char** argv = kvm_getargv(kd(), kproc, 0);
+ if (argv) {
+ for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) {
+ new_proc.cmd += argv[i] + " "s;
+ }
+ if (not new_proc.cmd.empty()) new_proc.cmd.pop_back();
+ }
+ if (new_proc.cmd.empty()) new_proc.cmd = new_proc.name;
+ if (new_proc.cmd.size() > 1000) {
+ new_proc.cmd.resize(1000);
+ new_proc.cmd.shrink_to_fit();
+ }
+ new_proc.ppid = kproc->p_ppid;
+ new_proc.cpu_s = round(kproc->p_ustart_sec);
+ struct passwd *pwd = getpwuid(kproc->p_uid);
+ if (pwd)
+ new_proc.user = pwd->pw_name;
+ }
+ new_proc.p_nice = kproc->p_nice;
+ new_proc.state = kproc->p_stat;
+
+ int cpu_t = 0;
+ cpu_t = kproc->p_uctime_usec * 1'000'000 + kproc->p_uctime_sec;
+
+ new_proc.mem = kproc->p_vm_rssize * Shared::pageSize;
+ new_proc.threads = 1; // can't seem to find this in kinfo_proc
+
+ //? Process cpu usage since last update
+ new_proc.cpu_p = clamp((100.0 * kproc->p_pctcpu / Shared::kfscale) * cmult, 0.0, 100.0 * Shared::coreCount);
+
+ //? Process cumulative cpu usage since process start
+ new_proc.cpu_c = (double)(cpu_t * Shared::clkTck / 1'000'000) / max(1.0, timeNow - new_proc.cpu_s);
+
+ //? Update cached value with latest cpu times
+ new_proc.cpu_t = cpu_t;
+
+ if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
+ got_detailed = true;
+ }
+ }
+
+ //? Clear dead processes from current_procs
+ auto eraser = rng::remove_if(current_procs, [&](const auto &element) { return not v_contains(found, element.pid); });
+ current_procs.erase(eraser.begin(), eraser.end());
+
+ //? Update the details info box for process if active
+ if (show_detailed and got_detailed) {
+ _collect_details(detailed_pid, current_procs);
+ } else if (show_detailed and not got_detailed and detailed.status != "Dead") {
+ detailed.status = "Dead";
+ redraw = true;
+ }
+
+ old_cputimes = cputimes;
+
+ }
+
+ //* ---------------------------------------------Collection done-----------------------------------------------
+
+ //* Match filter if defined
+ if (should_filter) {
+ filter_found = 0;
+ for (auto& p : current_procs) {
+ if (not tree and not filter.empty()) {
+ if (not s_contains_ic(to_string(p.pid), filter)
+ and not s_contains_ic(p.name, filter)
+ and not s_contains_ic(p.cmd, filter)
+ and not s_contains_ic(p.user, filter)) {
+ p.filtered = true;
+ filter_found++;
+ }
+ else {
+ p.filtered = false;
+ }
+ }
+ else {
+ p.filtered = false;
+ }
+ }
+ }
+
+ //* Sort processes
+ if (sorted_change or not no_update) {
+ proc_sorter(current_procs, sorting, reverse, tree);
+ }
+
+ //* Generate tree view if enabled
+ if (tree and (not no_update or should_filter or sorted_change)) {
+ bool locate_selection = false;
+ if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) {
+ auto collapser = rng::find(current_procs, find_pid, &proc_info::pid);
+ if (collapser != current_procs.end()) {
+ if (collapse == expand) {
+ collapser->collapsed = not collapser->collapsed;
+ }
+ else if (collapse > -1) {
+ collapser->collapsed = true;
+ }
+ else if (expand > -1) {
+ collapser->collapsed = false;
+ }
+ if (Config::ints.at("proc_selected") > 0) locate_selection = true;
+ }
+ collapse = expand = -1;
+ }
+ if (should_filter or not filter.empty()) filter_found = 0;
+
+ vector tree_procs;
+ tree_procs.reserve(current_procs.size());
+
+ for (auto& p : current_procs) {
+ if (not v_contains(found, p.ppid)) p.ppid = 0;
+ }
+
+ //? Stable sort to retain selected sorting among processes with the same parent
+ rng::stable_sort(current_procs, rng::less{}, & proc_info::ppid);
+
+ //? Start recursive iteration over processes with the lowest shared parent pids
+ for (auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
+ _tree_gen(p, current_procs, tree_procs, 0, false, filter, false, no_update, should_filter);
+ }
+
+ //? Recursive sort over tree structure to account for collapsed processes in the tree
+ int index = 0;
+ tree_sort(tree_procs, sorting, reverse, index, current_procs.size());
+
+ //? Add tree begin symbol to first item if childless
+ if (tree_procs.front().children.empty())
+ tree_procs.front().entry.get().prefix.replace(tree_procs.front().entry.get().prefix.size() - 8, 8, " ┌─ ");
+
+ //? Add tree terminator symbol to last item if childless
+ if (tree_procs.back().children.empty())
+ tree_procs.back().entry.get().prefix.replace(tree_procs.back().entry.get().prefix.size() - 8, 8, " └─ ");
+
+ //? Final sort based on tree index
+ rng::sort(current_procs, rng::less{}, & proc_info::tree_index);
+
+ //? Move current selection/view to the selected process when collapsing/expanding in the tree
+ if (locate_selection) {
+ int loc = rng::find(current_procs, Proc::selected_pid, &proc_info::pid)->tree_index;
+ if (Config::ints.at("proc_start") >= loc or Config::ints.at("proc_start") <= loc - Proc::select_max)
+ Config::ints.at("proc_start") = max(0, loc - 1);
+ Config::ints.at("proc_selected") = loc - Config::ints.at("proc_start") + 1;
+ }
+ }
+
+ numpids = (int)current_procs.size() - filter_found;
+ return current_procs;
+ }
+} // namespace Proc
+
+namespace Tools {
+ double system_uptime() {
+ struct timeval ts, currTime;
+ std::size_t len = sizeof(ts);
+ int mib[2] = {CTL_KERN, KERN_BOOTTIME};
+ if (sysctl(mib, 2, &ts, &len, nullptr, 0) != -1) {
+ gettimeofday(&currTime, nullptr);
+ return currTime.tv_sec - ts.tv_sec;
+ }
+ return 0.0;
+ }
+} // namespace Tools
diff --git a/src/openbsd/internal.h b/src/openbsd/internal.h
new file mode 100644
index 0000000..17840b6
--- /dev/null
+++ b/src/openbsd/internal.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2019-2021 Brian Callahan
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct sysctls {
+ const char *name;
+ int mib0;
+ int mib1;
+ int mib2;
+} sysctlnames[] = {
+ { "hw.machine", CTL_HW, HW_MACHINE, 0 },
+ { "hw.model", CTL_HW, HW_MODEL, 0 },
+ { "hw.ncpu", CTL_HW, HW_NCPU, 0 },
+ { "hw.byteorder", CTL_HW, HW_BYTEORDER, 0 },
+ { "hw.pagesize", CTL_HW, HW_PAGESIZE, 0 },
+ { "hw.disknames", CTL_HW, HW_DISKNAMES, 0 },
+ { "hw.diskcount", CTL_HW, HW_DISKCOUNT, 0 },
+ { "hw.sensors", CTL_HW, HW_SENSORS, 0 },
+ { "hw.model", CTL_HW, HW_MODEL, 0 },
+ { "hw.ncpu", CTL_HW, HW_NCPU, 0 },
+ { "hw.byteorder", CTL_HW, HW_BYTEORDER, 0 },
+ { "hw.pagesize", CTL_HW, HW_PAGESIZE, 0 },
+ { "hw.disknames", CTL_HW, HW_DISKNAMES, 0 },
+ { "hw.diskcount", CTL_HW, HW_DISKCOUNT, 0 },
+ { "hw.sensors", CTL_HW, HW_SENSORS, 0 },
+ { "hw.cpuspeed", CTL_HW, HW_CPUSPEED, 0 },
+ { "hw.setperf", CTL_HW, HW_SETPERF, 0 },
+ { "hw.vendor", CTL_HW, HW_VENDOR, 0 },
+ { "hw.product", CTL_HW, HW_PRODUCT, 0 },
+ { "hw.serialno", CTL_HW, HW_SERIALNO, 0 },
+ { "hw.uuid", CTL_HW, HW_UUID, 0 },
+ { "hw.physmem", CTL_HW, HW_PHYSMEM64, 0 },
+ { "hw.usermem", CTL_HW, HW_USERMEM64, 0 },
+ { "hw.ncpufound", CTL_HW, HW_NCPUFOUND, 0 },
+ { "hw.allowpowerdown", CTL_HW, HW_ALLOWPOWERDOWN, 0 },
+ { "hw.perfpolicy", CTL_HW, HW_PERFPOLICY, 0 },
+ { "hw.smt", CTL_HW, HW_SMT, 0 },
+ { "hw.ncpuonline", CTL_HW, HW_NCPUONLINE, 0 },
+ { "hw.cpuspeed", CTL_HW, HW_CPUSPEED, 0 },
+ { "hw.setperf", CTL_HW, HW_SETPERF, 0 },
+ { "hw.vendor", CTL_HW, HW_VENDOR, 0 },
+ { "hw.product", CTL_HW, HW_PRODUCT, 0 },
+ { "hw.serialno", CTL_HW, HW_SERIALNO, 0 },
+ { "hw.uuid", CTL_HW, HW_UUID, 0 },
+ { "hw.physmem", CTL_HW, HW_PHYSMEM64, 0 },
+ { "hw.usermem", CTL_HW, HW_USERMEM64, 0 },
+ { "hw.ncpufound", CTL_HW, HW_NCPUFOUND, 0 },
+ { "hw.allowpowerdown", CTL_HW, HW_ALLOWPOWERDOWN, 0 },
+ { "hw.perfpolicy", CTL_HW, HW_PERFPOLICY, 0 },
+ { "hw.smt", CTL_HW, HW_SMT, 0 },
+ { "hw.ncpuonline", CTL_HW, HW_NCPUONLINE, 0 },
+ { "kern.ostype", CTL_KERN, KERN_OSTYPE, 0 },
+ { "kern.osrelease", CTL_KERN, KERN_OSRELEASE, 0 },
+ { "kern.osrevision", CTL_KERN, KERN_OSREV, 0 },
+ { "kern.version", CTL_KERN, KERN_VERSION, 0 },
+ { "kern.maxvnodes", CTL_KERN, KERN_MAXVNODES, 0 },
+ { "kern.maxproc", CTL_KERN, KERN_MAXPROC, 0 },
+ { "kern.maxfiles", CTL_KERN, KERN_MAXFILES, 0 },
+ { "kern.argmax", CTL_KERN, KERN_ARGMAX, 0 },
+ { "kern.securelevel", CTL_KERN, KERN_SECURELVL, 0 },
+ { "kern.hostname", CTL_KERN, KERN_HOSTNAME, 0 },
+ { "kern.hostid", CTL_KERN, KERN_HOSTID, 0 },
+ { "kern.clockrate", CTL_KERN, KERN_CLOCKRATE, 0 },
+ { "kern.profiling", CTL_KERN, KERN_PROF, 0 },
+ { "kern.posix1version", CTL_KERN, KERN_POSIX1, 0 },
+ { "kern.ngroups", CTL_KERN, KERN_NGROUPS, 0 },
+ { "kern.job_control", CTL_KERN, KERN_JOB_CONTROL, 0 },
+ { "kern.saved_ids", CTL_KERN, KERN_SAVED_IDS, 0 },
+ { "kern.boottime", CTL_KERN, KERN_BOOTTIME, 0 },
+ { "kern.domainname", CTL_KERN, KERN_DOMAINNAME, 0 },
+ { "kern.maxpartitions", CTL_KERN, KERN_MAXPARTITIONS, 0 },
+ { "kern.rawpartition", CTL_KERN, KERN_RAWPARTITION, 0 },
+ { "kern.maxthread", CTL_KERN, KERN_MAXTHREAD, 0 },
+ { "kern.nthreads", CTL_KERN, KERN_NTHREADS, 0 },
+ { "kern.osversion", CTL_KERN, KERN_OSVERSION, 0 },
+ { "kern.somaxconn", CTL_KERN, KERN_SOMAXCONN, 0 },
+ { "kern.sominconn", CTL_KERN, KERN_SOMINCONN, 0 },
+ { "kern.nosuidcoredump", CTL_KERN, KERN_NOSUIDCOREDUMP, 0 },
+ { "kern.fsync", CTL_KERN, KERN_FSYNC, 0 },
+ { "kern.sysvmsg", CTL_KERN, KERN_SYSVMSG, 0 },
+ { "kern.sysvsem", CTL_KERN, KERN_SYSVSEM, 0 },
+ { "kern.sysvshm", CTL_KERN, KERN_SYSVSHM, 0 },
+ { "kern.msgbufsize", CTL_KERN, KERN_MSGBUFSIZE, 0 },
+ { "kern.malloc", CTL_KERN, KERN_MALLOCSTATS, 0 },
+ { "kern.cp_time", CTL_KERN, KERN_CPTIME, 0 },
+ { "kern.nchstats", CTL_KERN, KERN_NCHSTATS, 0 },
+ { "kern.forkstat", CTL_KERN, KERN_FORKSTAT, 0 },
+ { "kern.tty", CTL_KERN, KERN_TTY, 0 },
+ { "kern.ccpu", CTL_KERN, KERN_CCPU, 0 },
+ { "kern.fscale", CTL_KERN, KERN_FSCALE, 0 },
+ { "kern.nprocs", CTL_KERN, KERN_NPROCS, 0 },
+ { "kern.msgbuf", CTL_KERN, KERN_MSGBUF, 0 },
+ { "kern.pool", CTL_KERN, KERN_POOL, 0 },
+ { "kern.stackgap_random", CTL_KERN, KERN_STACKGAPRANDOM, 0 },
+ { "kern.sysvipc_info", CTL_KERN, KERN_SYSVIPC_INFO, 0 },
+ { "kern.allowkmem", CTL_KERN, KERN_ALLOWKMEM, 0 },
+ { "kern.witnesswatch", CTL_KERN, KERN_WITNESSWATCH, 0 },
+ { "kern.splassert", CTL_KERN, KERN_SPLASSERT, 0 },
+ { "kern.procargs", CTL_KERN, KERN_PROC_ARGS, 0 },
+ { "kern.nfiles", CTL_KERN, KERN_NFILES, 0 },
+ { "kern.ttycount", CTL_KERN, KERN_TTYCOUNT, 0 },
+ { "kern.numvnodes", CTL_KERN, KERN_NUMVNODES, 0 },
+ { "kern.mbstat", CTL_KERN, KERN_MBSTAT, 0 },
+ { "kern.witness", CTL_KERN, KERN_WITNESS, 0 },
+ { "kern.seminfo", CTL_KERN, KERN_SEMINFO, 0 },
+ { "kern.shminfo", CTL_KERN, KERN_SHMINFO, 0 },
+ { "kern.intrcnt", CTL_KERN, KERN_INTRCNT, 0 },
+ { "kern.watchdog", CTL_KERN, KERN_WATCHDOG, 0 },
+ { "kern.proc", CTL_KERN, KERN_PROC, 0 },
+ { "kern.maxclusters", CTL_KERN, KERN_MAXCLUSTERS, 0 },
+ { "kern.evcount", CTL_KERN, KERN_EVCOUNT, 0 },
+ { "kern.timecounter", CTL_KERN, KERN_TIMECOUNTER, 0 },
+ { "kern.maxlocksperuid", CTL_KERN, KERN_MAXLOCKSPERUID, 0 },
+ { "kern.cp_time2", CTL_KERN, KERN_CPTIME2, 0 },
+ { "kern.bufcachepercent", CTL_KERN, KERN_CACHEPCT, 0 },
+ { "kern.file", CTL_KERN, KERN_FILE, 0 },
+ { "kern.wxabort", CTL_KERN, KERN_WXABORT, 0 },
+ { "kern.consdev", CTL_KERN, KERN_CONSDEV, 0 },
+ { "kern.netlivelocks", CTL_KERN, KERN_NETLIVELOCKS, 0 },
+ { "kern.pool_debug", CTL_KERN, KERN_POOL_DEBUG, 0 },
+ { "kern.proc_cwd", CTL_KERN, KERN_PROC_CWD, 0 },
+ { "kern.proc_nobroadcastkill", CTL_KERN, KERN_PROC_NOBROADCASTKILL, 0 },
+ { "kern.proc_vmap", CTL_KERN, KERN_PROC_VMMAP, 0 },
+ { "kern.global_ptrace", CTL_KERN, KERN_GLOBAL_PTRACE, 0 },
+ { "kern.consbufsize", CTL_KERN, KERN_CONSBUFSIZE, 0 },
+ { "kern.consbuf", CTL_KERN, KERN_CONSBUF, 0 },
+ { "kern.audio", CTL_KERN, KERN_AUDIO, 0 },
+ { "kern.cpustats", CTL_KERN, KERN_CPUSTATS, 0 },
+ { "kern.pfstatus", CTL_KERN, KERN_PFSTATUS, 0 },
+ { "kern.timeout_stats", CTL_KERN, KERN_TIMEOUT_STATS, 0 },
+ { "kern.utc_offset", CTL_KERN, KERN_UTC_OFFSET, 0 },
+ { "vm.vmmeter", CTL_VM, VM_METER, 0 },
+ { "vm.loadavg", CTL_VM, VM_LOADAVG, 0 },
+ { "vm.psstrings", CTL_VM, VM_PSSTRINGS, 0 },
+ { "vm.uvmexp", CTL_VM, VM_UVMEXP, 0 },
+ { "vm.swapencrypt", CTL_VM, VM_SWAPENCRYPT, 0 },
+ { "vm.nkmempages", CTL_VM, VM_NKMEMPAGES, 0 },
+ { "vm.anonmin", CTL_VM, VM_ANONMIN, 0 },
+ { "vm.vtextmin", CTL_VM, VM_VTEXTMIN, 0 },
+ { "vm.vnodemin", CTL_VM, VM_VNODEMIN, 0 },
+ { "vm.maxslp", CTL_VM, VM_MAXSLP, 0 },
+ { "vm.uspace", CTL_VM, VM_USPACE, 0 },
+ { "vm.malloc_conf", CTL_VM, VM_MALLOC_CONF, 0 },
+ { NULL, 0, 0, 0 },
+};
diff --git a/src/openbsd/sysctlbyname.cpp b/src/openbsd/sysctlbyname.cpp
new file mode 100644
index 0000000..b92484a
--- /dev/null
+++ b/src/openbsd/sysctlbyname.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019-2021 Brian Callahan
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include "internal.h"
+#include "../btop_tools.hpp"
+
+int
+sysctlbyname(const char *name, void *oldp, size_t *oldlenp,
+ void *newp, size_t newlen)
+{
+ int i, mib[2];
+
+ for (i = 0; i < 132; i++) {
+ // for (i = 0; i < sizeof(sysctlnames) / sizeof(sysctlnames[0]); i++) {
+ if (!strcmp(name, sysctlnames[i].name)) {
+ mib[0] = sysctlnames[i].mib0;
+ mib[1] = sysctlnames[i].mib1;
+
+ return sysctl(mib, 2, oldp, oldlenp, newp, newlen);
+ }
+ }
+
+ errno = ENOENT;
+
+ return (-1);
+}
diff --git a/src/openbsd/sysctlbyname.h b/src/openbsd/sysctlbyname.h
new file mode 100644
index 0000000..3b02382
--- /dev/null
+++ b/src/openbsd/sysctlbyname.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 Brian Callahan
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include
+#include
+
+extern int sysctlbyname(const char *, void *, size_t *, void *, size_t);