Merge branch 'aristocratos:main' into main

This commit is contained in:
kz6fittycent 2024-03-13 11:02:55 -05:00 committed by GitHub
commit 8af8389bd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 3179 additions and 3296 deletions

View file

@ -28,10 +28,9 @@ Any bug that can be solved by just reading the [prerequisites](https://github.co
[If applicable, add screenshots to help explain your problem.] [If applicable, add screenshots to help explain your problem.]
**Info (please complete the following information):** **Info (please complete the following information):**
- btop++ version: `btop -v` - btop++ version: `btop --version`
- If using snap: `snap info btop` - If using snap: `snap info btop`
- Binary: [self compiled or static binary from release] - Binary: [self compiled or static binary from release]
- (If compiled) Compiler and version:
- Architecture: [x86_64, aarch64, etc.] `uname -m` - Architecture: [x86_64, aarch64, etc.] `uname -m`
- Platform: [Linux, FreeBSD, OsX] - Platform: [Linux, FreeBSD, OsX]
- (Linux) Kernel: `uname -r` - (Linux) Kernel: `uname -r`

View file

@ -33,8 +33,7 @@ jobs:
with: with:
release: '14.0' release: '14.0'
usesh: true usesh: true
prepare: pkg install -y cmake ninja prepare: pkg install -y cmake ninja lowdown
run: | run: |
CXX=clang++ cmake -B build -G Ninja -DBTOP_STATIC=ON CXX=clang++ cmake -B build -G Ninja -DBTOP_STATIC=ON
cmake --build build --verbose cmake --build build --verbose

View file

@ -30,11 +30,10 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install build tools - name: Install build tools
run: apk add --no-cache --update clang cmake lld ninja run: apk add --no-cache --update clang cmake lld ninja lowdown
- name: Configure - name: Configure
run: CXX=clang++ LDFLAGS=-fuse-ld=lld cmake -B build -G Ninja -DBTOP_STATIC=ON run: CXX=clang++ LDFLAGS=-fuse-ld=lld cmake -B build -G Ninja -DBTOP_STATIC=ON
- name: Compile - name: Compile
run: cmake --build build --verbose run: cmake --build build --verbose

View file

@ -32,7 +32,7 @@ jobs:
run: | run: |
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
brew update --quiet brew update --quiet
brew install --force --overwrite cmake llvm@17 ninja brew install --force --overwrite cmake llvm@17 ninja lowdown
- name: Configure - name: Configure
run: | run: |
@ -44,4 +44,3 @@ jobs:
- name: Compile - name: Compile
run: cmake --build build --verbose run: cmake --build build --verbose

View file

@ -11,6 +11,7 @@ on:
- 'src/**' - 'src/**'
- '!src/linux/**' - '!src/linux/**'
- '!src/osx/**' - '!src/osx/**'
- '!src/openbsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build-freebsd.yml' - '.github/workflows/continuous-build-freebsd.yml'
@ -21,6 +22,7 @@ on:
- 'src/**' - 'src/**'
- '!src/linux/**' - '!src/linux/**'
- '!src/osx/**' - '!src/osx/**'
- '!src/openbsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build-freebsd.yml' - '.github/workflows/continuous-build-freebsd.yml'
@ -43,7 +45,7 @@ jobs:
release: '14.0' release: '14.0'
usesh: true usesh: true
prepare: | prepare: |
pkg install -y gmake gcc coreutils git pkg install -y gmake gcc coreutils git lowdown
git config --global --add safe.directory /home/runner/work/btop/btop git config --global --add safe.directory /home/runner/work/btop/btop
run: | run: |
CXX=${{ matrix.compiler }} gmake STATIC=true STRIP=true CXX=${{ matrix.compiler }} gmake STATIC=true STRIP=true
@ -57,4 +59,3 @@ jobs:
name: btop-x86_64-freebsd-14 name: btop-x86_64-freebsd-14
path: 'bin/*' path: 'bin/*'
if-no-files-found: error if-no-files-found: error

View file

@ -0,0 +1,45 @@
name: Continuous Build Gpu
on:
workflow_dispatch:
push:
branches:
- main
tags-ignore:
- '*.*'
paths:
- 'src/**'
- '!src/osx/**'
- '!src/freebsd/**'
- '!src/openbsd/**'
- 'include/**'
- 'Makefile'
- '.github/workflows/continuous-build-gpu.yml'
pull_request:
branches:
- main
paths:
- 'src/**'
- '!src/osx/**'
- '!src/freebsd/**'
- '!src/openbsd/**'
- 'include/**'
- 'Makefile'
- '.github/workflows/continuous-build-gpu.yml'
jobs:
gpu_build_linux:
runs-on: ubuntu-latest
container: alpine:edge
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- name: Install build tools
run: apk add --no-cache --update gcc g++ make
- name: Compile
run: make CXX=g++ GPU_SUPPORT=true

View file

@ -11,6 +11,7 @@ on:
- 'src/**' - 'src/**'
- '!src/osx/**' - '!src/osx/**'
- '!src/freebsd/**' - '!src/freebsd/**'
- '!src/openbsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build-linux.yml' - '.github/workflows/continuous-build-linux.yml'
@ -21,6 +22,7 @@ on:
- 'src/**' - 'src/**'
- '!src/osx/**' - '!src/osx/**'
- '!src/freebsd/**' - '!src/freebsd/**'
- '!src/openbsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build-linux.yml' - '.github/workflows/continuous-build-linux.yml'

View file

@ -11,6 +11,7 @@ on:
- 'src/**' - 'src/**'
- '!src/linux/**' - '!src/linux/**'
- '!src/freebsd/**' - '!src/freebsd/**'
- '!src/openbsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build-macos.yml' - '.github/workflows/continuous-build-macos.yml'
@ -21,6 +22,7 @@ on:
- 'src/**' - 'src/**'
- '!src/linux/**' - '!src/linux/**'
- '!src/freebsd/**' - '!src/freebsd/**'
- '!src/openbsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build-macos.yml' - '.github/workflows/continuous-build-macos.yml'
@ -44,18 +46,18 @@ jobs:
with: with:
name: btop-x86_64-macos11-BigSur name: btop-x86_64-macos11-BigSur
path: 'bin/*' path: 'bin/*'
build-macos12: build-macos12:
runs-on: macos-12 runs-on: macos-12
steps: steps:
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: latest-stable xcode-version: latest-stable
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Compile - name: Compile
run: | run: |
make CXX=g++-12 ARCH=x86_64 STATIC=true STRIP=true make CXX=g++-12 ARCH=x86_64 STATIC=true STRIP=true

View file

@ -0,0 +1,57 @@
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 lowdown
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

View file

@ -1,18 +1,26 @@
name: 🧪 Test snap can be built on x86_64 name: 🧪 Test snap can be built on x86_64
on: on:
workflow_dispatch:
push: push:
branches: [ main ] branches: [ main ]
tags-ignore: tags-ignore:
- '*.*' - '*.*'
paths: paths:
- 'src/**' - 'src/**'
- '!src/osx/**' - '!src/osx/**'
- '!src/freebsd/**' - '!src/freebsd/**'
- 'include/**' - 'include/**'
- 'Makefile' - 'Makefile'
- '.github/workflows/continuous-build-linux.yml' - '.github/workflows/test-snap-can-build.yml'
pull_request: pull_request:
branches: [ main ] branches: [ main ]
paths:
- 'src/**'
- '!src/osx/**'
- '!src/freebsd/**'
- 'include/**'
- 'Makefile'
- '.github/workflows/test-snap-can-build.yml'
jobs: jobs:
build: build:

5
.gitignore vendored
View file

@ -46,9 +46,14 @@ stage/
*.out *.out
*.app *.app
# Compiled man page
btop.1
build build
bin bin
btop btop
/obj/
config.h
.*/ .*/
# Optional libraries # Optional libraries

View file

@ -1,6 +1,30 @@
## v1.3.2
Description | Author(s) | References
--- | --- | ---
fix: Can't detect librocm 6.0.x | @imwints, @aristocratos | #761
## v1.3.1
Description | Author(s) | References
--- | --- | ---
GPU: Added support for dynamic loading of ROCm v6 libraries | @aristocratos, @fxzjshm | 5511131, #737
Increase max network interface name to 15 | @tessus | #714
Fix OpenBSD UTF-8 locale detection | @lcheylus, @imwints | #753, #717
Add hot-reloading of config file with CTRL+R or SIGUSR2 signal | @MartinPit | #722
Add battery power draw for linux and freebsd | @vsey | #689
Fix crash caused by string exception when cpu clock is exactly between 999.5 and 999.9 Mhz | @rkmcode | #735
Write newline at end of config file | @planet36 | #743
Add theme based on Everforest Dark Medium palette | @M-Sviridov | #746
fix: don't mangle memory for zombie processes | @joske | #747
Share common code from collect | @imwints | #756
Fixed incorrect used and available memory for OSX | | 4461a43
## v1.3.0 ## v1.3.0
* Added Gpu Support | @romner-set | PR #529 * Added Gpu Support Linux | @romner-set | PR #529
* Added platform support for OpenBSD | @joske | PR #607
* Enable macos clang | @muneebmahmed | PR #666 * Enable macos clang | @muneebmahmed | PR #666
@ -42,6 +66,16 @@
* Create adwaita.theme | @flipflop133 | #485 * Create adwaita.theme | @flipflop133 | #485
* Try get terminal size of "/dev/tty" if stdout fails | @imwints | PR #627
* Refresh rate program argument | @imwints | PR #640
* Improved error handling when determining the config directory | @imwints | #652
* Use native POSIX polling syscalls to read input | @lvxnull | #624
* Conditional compile on Big Sur and up | @joske | PR #690
+ Various fixes by @imwints, @simplepad, @joske, @gwena, @cpalv, @iambeingtracked, @mattico, @NexAdn + Various fixes by @imwints, @simplepad, @joske, @gwena, @cpalv, @iambeingtracked, @mattico, @NexAdn
## v1.2.13 ## v1.2.13
@ -146,7 +180,7 @@
* Fixed: Wrong memory unit when shorten and size is less than 10, by @mohi001 * Fixed: Wrong memory unit when shorten and size is less than 10, by @mohi001
* Fixed: Use cpu cores avarage temp if missing cpu package temp for FreeBSD * Fixed: Use cpu cores average temp if missing cpu package temp for FreeBSD
* Changed: Enter symbol to a more common variant * Changed: Enter symbol to a more common variant
@ -442,7 +476,7 @@
* Fixed: Sizing constraints bug on start and boxes can be toggled from size error screen * Fixed: Sizing constraints bug on start and boxes can be toggled from size error screen
* Fixed: UTF-8 check crashing if LANG was set to non existant locale * Fixed: UTF-8 check crashing if LANG was set to non existent locale
## v1.0.4 ## v1.0.4

View file

@ -11,7 +11,6 @@ if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
endif() endif()
project("btop" project("btop"
VERSION 1.2.13
DESCRIPTION "A monitor of resources" DESCRIPTION "A monitor of resources"
HOMEPAGE_URL "https://github.com/aristocratos/btop" HOMEPAGE_URL "https://github.com/aristocratos/btop"
LANGUAGES CXX LANGUAGES CXX
@ -41,6 +40,7 @@ option(BTOP_LTO "Enable LTO" ON)
option(BTOP_USE_MOLD "Use mold to link btop" OFF) option(BTOP_USE_MOLD "Use mold to link btop" OFF)
option(BTOP_PEDANTIC "Enable a bunch of additional warnings" OFF) option(BTOP_PEDANTIC "Enable a bunch of additional warnings" OFF)
option(BTOP_WERROR "Compile with warnings as errors" OFF) option(BTOP_WERROR "Compile with warnings as errors" OFF)
option(BTOP_FORTIFY "Detect buffer overflows with _FORTIFY_SOURCE=3" ON)
option(BTOP_GPU "Enable GPU support" ON) option(BTOP_GPU "Enable GPU support" ON)
cmake_dependent_option(BTOP_RSMI_STATIC "Link statically to ROCm SMI" OFF "BTOP_GPU" OFF) cmake_dependent_option(BTOP_RSMI_STATIC "Link statically to ROCm SMI" OFF "BTOP_GPU" OFF)
@ -64,6 +64,8 @@ if(APPLE)
target_sources(btop PRIVATE src/osx/btop_collect.cpp src/osx/sensors.cpp src/osx/smc.cpp) target_sources(btop PRIVATE src/osx/btop_collect.cpp src/osx/sensors.cpp src/osx/smc.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
target_sources(btop PRIVATE src/freebsd/btop_collect.cpp) target_sources(btop PRIVATE src/freebsd/btop_collect.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
target_sources(btop PRIVATE src/openbsd/btop_collect.cpp src/openbsd/sysctlbyname.cpp)
elseif(LINUX) elseif(LINUX)
target_sources(btop PRIVATE src/linux/btop_collect.cpp) target_sources(btop PRIVATE src/linux/btop_collect.cpp)
else() else()
@ -75,6 +77,21 @@ if(NOT CXX_HAVE_RANGES)
message(FATAL_ERROR "The compiler doesn't support <ranges>") message(FATAL_ERROR "The compiler doesn't support <ranges>")
endif() endif()
# Generate build info
execute_process(
COMMAND "git" "rev-parse" "--short" "HEAD"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
set(CONFIGURE_COMMAND
"cmake -DBTOP_STATIC=${BTOP_STATIC} -DBTOP_USE_MOLD=${BTOP_USE_MOLD} -DBTOP_FORTIFY=${BTOP_FORTIFY} -DBTOP_GPU=${BTOP_GPU}"
)
get_filename_component(CXX_COMPILER_BASENAME "${CMAKE_CXX_COMPILER}" NAME)
set(COMPILER "${CXX_COMPILER_BASENAME}")
set(COMPILER_VERSION "${CMAKE_CXX_COMPILER_VERSION}")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY IMMEDIATE)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Check for and enable LTO # Check for and enable LTO
check_ipo_supported(RESULT ipo_supported) check_ipo_supported(RESULT ipo_supported)
if(ipo_supported AND BTOP_LTO) if(ipo_supported AND BTOP_LTO)
@ -97,7 +114,7 @@ if(BTOP_WERROR)
endif() endif()
if(NOT APPLE) if(NOT APPLE)
target_compile_options(btop PRIVATE -fstack-clash-protection) target_compile_options(btop PRIVATE -fstack-clash-protection)
endif() endif()
check_cxx_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR) check_cxx_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR)
if(HAS_FSTACK_PROTECTOR) if(HAS_FSTACK_PROTECTOR)
@ -109,10 +126,11 @@ if(HAS_FCF_PROTECTION)
endif() endif()
target_compile_definitions(btop PRIVATE target_compile_definitions(btop PRIVATE
FMT_HEADER_ONLY
_FILE_OFFSET_BITS=64 _FILE_OFFSET_BITS=64
$<$<CONFIG:Debug>:_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1> $<$<CONFIG:Debug>:_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1>
# Only has an effect with optimizations enabled # Only has an effect with optimizations enabled
$<$<NOT:$<CONFIG:Debug>>:_FORTIFY_SOURCE=2> $<$<AND:$<NOT:$<CONFIG:Debug>>,$<BOOL:${BTOP_FORTIFY}>>:_FORTIFY_SOURCE=3>
) )
target_include_directories(btop SYSTEM PRIVATE include) target_include_directories(btop SYSTEM PRIVATE include)
@ -129,7 +147,7 @@ if(LINUX AND BTOP_GPU)
if(BTOP_RSMI_STATIC) if(BTOP_RSMI_STATIC)
# ROCm doesn't properly add it's folders to the module path if `CMAKE_MODULE_PATH` is already # ROCm doesn't properly add it's folders to the module path if `CMAKE_MODULE_PATH` is already
# set # set
# We could also manully append ROCm's path here # We could also manually append ROCm's path here
set(_CMAKE_MODULE_PATH CMAKE_MODULE_PATH) set(_CMAKE_MODULE_PATH CMAKE_MODULE_PATH)
unset(CMAKE_MODULE_PATH) unset(CMAKE_MODULE_PATH)
@ -178,12 +196,34 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
endif() endif()
find_package(devstat REQUIRED) find_package(devstat REQUIRED)
target_link_libraries(btop devstat::devstat) find_package(kvm REQUIRED)
target_link_libraries(btop devstat::devstat kvm::kvm)
if(BTOP_STATIC) if(BTOP_STATIC)
find_package(elf REQUIRED) find_package(elf REQUIRED)
find_package(kvm REQUIRED) target_link_libraries(btop elf::elf)
target_link_libraries(btop elf::elf kvm::kvm)
endif() endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(btop PRIVATE -static-libstdc++)
endif()
find_package(kvm REQUIRED)
target_link_libraries(btop kvm::kvm)
endif()
# Check if lowdown is installed
find_program(LOWDOWN_EXECUTABLE lowdown)
if(LOWDOWN_EXECUTABLE)
# Custom target to compile Markdown to man page using lowdown
add_custom_target(btop.1 ALL
COMMAND lowdown -s -Tman -o btop.1 manpage.md
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
# Install the man page
install(FILES btop.1 DESTINATION "share/man/man1")
else()
message(WARNING "Command 'lowdown' not found: skipping generating man page btop.1")
endif() endif()
install(TARGETS btop RUNTIME) install(TARGETS btop RUNTIME)
@ -191,4 +231,3 @@ install(FILES "btop.desktop" DESTINATION "share/applications")
install(FILES "Img/icon.png" DESTINATION "share/icons/hicolor/48x48/apps" RENAME "btop.png") install(FILES "Img/icon.png" DESTINATION "share/icons/hicolor/48x48/apps" RENAME "btop.png")
install(FILES "Img/icon.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "btop.svg") install(FILES "Img/icon.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "btop.svg")
install(DIRECTORY "themes" DESTINATION "share/btop") install(DIRECTORY "themes" DESTINATION "share/btop")

100
Makefile
View file

@ -50,6 +50,11 @@ ifeq ($(GPU_SUPPORT),true)
override ADDFLAGS += -DGPU_SUPPORT override ADDFLAGS += -DGPU_SUPPORT
endif endif
FORTIFY_SOURCE ?= true
ifeq ($(FORTIFY_SOURCE),true)
override ADDFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3
endif
#? Compiler and Linker #? Compiler and Linker
ifeq ($(shell $(CXX) --version | grep clang >/dev/null 2>&1; echo $$?),0) ifeq ($(shell $(CXX) --version | grep clang >/dev/null 2>&1; echo $$?),0)
override CXX_IS_CLANG := true override CXX_IS_CLANG := true
@ -61,6 +66,10 @@ CLANG_WORKS = false
GCC_WORKS = false GCC_WORKS = false
MIN_CLANG_VERSION = 16 MIN_CLANG_VERSION = 16
ifeq ($(DEBUG),true)
override ADDFLAGS += -DBTOP_DEBUG
endif
#? Supported is Clang 16.0.0 and later #? Supported is Clang 16.0.0 and later
ifeq ($(CXX_IS_CLANG),true) ifeq ($(CXX_IS_CLANG),true)
ifeq ($(shell $(CXX) --version | grep Apple >/dev/null 2>&1; echo $$?),0) ifeq ($(shell $(CXX) --version | grep Apple >/dev/null 2>&1; echo $$?),0)
@ -69,32 +78,9 @@ ifeq ($(CXX_IS_CLANG),true)
ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt $(MIN_CLANG_VERSION); echo $$?),0) ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt $(MIN_CLANG_VERSION); echo $$?),0)
CLANG_WORKS := true CLANG_WORKS := true
endif endif
endif else
ifeq ($(CLANG_WORKS),false) ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0)
#? Try to find a newer GCC version GCC_WORKS := true
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
endif endif
endif endif
@ -154,6 +140,12 @@ else ifeq ($(PLATFORM_LC),macos)
THREADS := $(shell sysctl -n hw.ncpu || echo 1) THREADS := $(shell sysctl -n hw.ncpu || echo 1)
override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation
SU_GROUP := wheel SU_GROUP := wheel
else ifeq ($(PLATFORM_LC),openbsd)
PLATFORM_DIR := openbsd
THREADS := $(shell sysctl -n hw.ncpu || echo 1)
override ADDFLAGS += -lkvm -static-libstdc++
export MAKE = gmake
SU_GROUP := wheel
else else
$(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m")) $(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m"))
endif endif
@ -171,6 +163,12 @@ else
LTO := $(THREADS) LTO := $(THREADS)
endif endif
GIT_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null || true)
CONFIGURE_COMMAND := $(MAKE) STATIC=$(STATIC) FORTIFY_SOURCE=$(FORTIFY_SOURCE)
ifeq ($(PLATFORM_LC),linux)
CONFIGURE_COMMAND += GPU_SUPPORT=$(GPU_SUPPORT) RSMI_STATIC=$(RSMI_STATIC)
endif
#? The Directories, Source, Includes, Objects and Binary #? The Directories, Source, Includes, Objects and Binary
SRCDIR := src SRCDIR := src
INCDIRS := include $(wildcard lib/**/include) INCDIRS := include $(wildcard lib/**/include)
@ -187,10 +185,10 @@ override GOODFLAGS := $(foreach flag,$(TESTFLAGS),$(strip $(shell echo "int main
override REQFLAGS := -std=c++20 override REQFLAGS := -std=c++20
WARNFLAGS := -Wall -Wextra -pedantic WARNFLAGS := -Wall -Wextra -pedantic
OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO) OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO)
LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS) LDCXXFLAGS := -pthread -DFMT_HEADER_ONLY -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS)
override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS)
INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR) INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR) -I$(BUILDDIR)
SU_USER := root SU_USER := root
ifdef DEBUG ifdef DEBUG
@ -230,7 +228,7 @@ endif
#? Default Make #? Default Make
.ONESHELL: .ONESHELL:
all: | info rocm_smi info-quiet directories btop all: | info rocm_smi info-quiet directories btop.1 config.h btop
ifneq ($(QUIET),true) ifneq ($(QUIET),true)
info: info:
@ -251,7 +249,6 @@ info:
@true @true
endif endif
info-quiet: | info rocm_smi info-quiet: | info rocm_smi
@printf "\n\033[1;92mBuilding btop++ \033[91m(\033[97mv$(BTOP_VERSION)\033[91m) \033[93m$(PLATFORM) \033[96m$(ARCH)\033[0m\n" @printf "\n\033[1;92mBuilding btop++ \033[91m(\033[97mv$(BTOP_VERSION)\033[91m) \033[93m$(PLATFORM) \033[96m$(ARCH)\033[0m\n"
@ -275,17 +272,33 @@ directories:
@$(VERBOSE) || printf "mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)\n" @$(VERBOSE) || printf "mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)\n"
@mkdir -p $(BUILDDIR)/$(PLATFORM_DIR) @mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)
config.h: $(BUILDDIR)/config.h
$(BUILDDIR)/config.h: $(SRCDIR)/config.h.in | directories
@$(QUIET) || printf "\033[1mConfiguring $(BUILDDIR)/config.h\033[0m\n"
@$(VERBOSE) || printf 'sed -e "s|@GIT_COMMIT@|$(GIT_COMMIT)|" -e "s|@CONFIGURE_COMMAND@|$(CONFIGURE_COMMAND)|" -e "s|@COMPILER@|$(CXX)|" -e "s|@COMPILER_VERSION@|$(CXX_VERSION)|" $< | tee $@ > /dev/null\n'
@sed -e "s|@GIT_COMMIT@|$(GIT_COMMIT)|" -e "s|@CONFIGURE_COMMAND@|$(CONFIGURE_COMMAND)|" -e "s|@COMPILER@|$(CXX)|" -e "s|@COMPILER_VERSION@|$(CXX_VERSION)|" $< | tee $@ > /dev/null
#? Man page
btop.1: manpage.md | directories
ifeq ($(shell command -v lowdown >/dev/null; echo $$?),0)
@printf "\n\033[1;92mGenerating man page $@\033[37m...\033[0m\n"
lowdown -s -Tman -o $@ $<
else
@printf "\n\033[1;93mCommand 'lowdown' not found: skipping generating man page $@\033[0m\n"
endif
#? Clean only Objects #? Clean only Objects
clean: clean:
@printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n"
@rm -rf $(BUILDDIR) @rm -rf $(BUILDDIR)
@cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true @test -e lib/rocm_smi_lib/build && cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true
#? Clean Objects and Binaries #? Clean Objects and Binaries
distclean: clean distclean: clean
@printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n"
@rm -rf $(TARGETDIR) @rm -rf $(TARGETDIR)
@rm -rf lib/rocm_smi_lib/build @test -e lib/rocm_smi_lib/build && rm -rf lib/rocm_smi_lib/build || true
install: install:
@printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n" @printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n"
@ -306,7 +319,11 @@ install:
@printf "\033[1;92mInstalling SVG icon to: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg\n" @printf "\033[1;92mInstalling SVG icon to: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg\n"
@mkdir -p $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps @mkdir -p $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps
@cp -p Img/icon.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg @cp -p Img/icon.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg
ifneq ($(wildcard btop.1),)
@printf "\033[1;92mInstalling man page to: \033[1;97m$(DESTDIR)$(PREFIX)/share/man/man1/btop.1\n"
@mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1
@cp -p btop.1 $(DESTDIR)$(PREFIX)/share/man/man1/btop.1
endif
#? Set SUID bit for btop as $SU_USER in $SU_GROUP #? Set SUID bit for btop as $SU_USER in $SU_GROUP
setuid: setuid:
@ -316,17 +333,20 @@ setuid:
@printf "\033[1;92mSetting SUID bit\033[0m\n" @printf "\033[1;92mSetting SUID bit\033[0m\n"
@chmod u+s $(DESTDIR)$(PREFIX)/bin/btop @chmod u+s $(DESTDIR)$(PREFIX)/bin/btop
# With 'rm -v' user will see what files (if any) got removed
uninstall: uninstall:
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\033[0m\n"
@rm -rf $(DESTDIR)$(PREFIX)/bin/btop @rm -rfv $(DESTDIR)$(PREFIX)/bin/btop
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\033[0m\n"
@rm -rf $(DESTDIR)$(PREFIX)/share/btop @rm -rfv $(DESTDIR)$(PREFIX)/share/btop
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/applications/btop.desktop\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/applications/btop.desktop\033[0m\n"
@rm -rf $(DESTDIR)$(PREFIX)/share/applications/btop.desktop @rm -rfv $(DESTDIR)$(PREFIX)/share/applications/btop.desktop
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/btop.png\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/btop.png\033[0m\n"
@rm -rf $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/btop.png @rm -rfv $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/btop.png
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg\033[0m\n" @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg\033[0m\n"
@rm -rf $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg @rm -rfv $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg
@printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/man/man1/btop.1\033[0m\n"
@rm -rfv $(DESTDIR)$(PREFIX)/share/man/man1/btop.1
#? Pull in dependency info for *existing* .o files #? Pull in dependency info for *existing* .o files
-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))
@ -370,7 +390,7 @@ btop: $(OBJECTS) | rocm_smi directories
#? Compile #? Compile
.ONESHELL: .ONESHELL:
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories config.h
@sleep 0.3 2>/dev/null || true @sleep 0.3 2>/dev/null || true
@TSTAMP=$$(date +%s 2>/dev/null || echo "0") @TSTAMP=$$(date +%s 2>/dev/null || echo "0")
@$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n" @$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n"
@ -379,4 +399,4 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories
@printf "\033[1;92m$$($(PROGRESS))$(P)\033[10D\033[5C-> \033[1;37m$@ \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$($(DATE_CMD) +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n" @printf "\033[1;92m$$($(PROGRESS))$(P)\033[10D\033[5C-> \033[1;37m$@ \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$($(DATE_CMD) +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n"
#? Non-File Targets #? Non-File Targets
.PHONY: all msg help pre .PHONY: all config.h msg help pre

264
README.md
View file

@ -7,6 +7,7 @@
![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) ![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux)
![macOS](https://img.shields.io/badge/-OSX-black?logo=apple) ![macOS](https://img.shields.io/badge/-OSX-black?logo=apple)
![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd) ![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) ![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow)
![c++20](https://img.shields.io/badge/cpp-c%2B%2B20-green) ![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) ![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 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 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 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 ## Index
@ -33,6 +35,7 @@
* [Compilation Linux](#compilation-linux) * [Compilation Linux](#compilation-linux)
* [Compilation macOS](#compilation-macos-osx) * [Compilation macOS](#compilation-macos-osx)
* [Compilation FreeBSD](#compilation-freebsd) * [Compilation FreeBSD](#compilation-freebsd)
* [Compilation OpenBSD](#compilation-openbsd)
* [GPU compatibility](#gpu-compatibility) * [GPU compatibility](#gpu-compatibility)
* [Installing the snap](#installing-the-snap) * [Installing the snap](#installing-the-snap)
* [Configurability](#configurability) * [Configurability](#configurability)
@ -40,6 +43,15 @@
## News ## News
##### 7 January 2024
Btop release v1.3.0
Big release with GPU support added for Linux and platform support for OpenBSD. Big thanks to [@romner-set](https://github.com/romner-set) (GPU support) and [@joske](https://github.com/joske) (OpenBSD support) for contributions.
And a multitude of bugfixes and small changes, see [CHANGELOG.md](CHANGELOG.md) and latest [release](https://github.com/aristocratos/btop/releases/latest) for detailed list and attributions.
See news entry below for more information regarding GPU support.
##### 25 November 2023 ##### 25 November 2023
GPU monitoring added for Linux! GPU monitoring added for Linux!
@ -66,6 +78,9 @@ Big update with version bump to 1.3 coming soon.
First release of btop4win available at https://github.com/aristocratos/btop4win First release of btop4win available at https://github.com/aristocratos/btop4win
<details>
<summary>More...</summary>
##### 16 January 2022 ##### 16 January 2022
Release v1.2.0 with FreeBSD support. No release binaries for FreeBSD provided as of yet. Release v1.2.0 with FreeBSD support. No release binaries for FreeBSD provided as of yet.
@ -81,9 +96,6 @@ macOS binaries + installer are included for both x86 and ARM64 (Apple Silicon) i
Big thank you to [@joske](https://github.com/joske) who wrote the vast majority of the implementation! Big thank you to [@joske](https://github.com/joske) who wrote the vast majority of the implementation!
<details>
<summary>More...</summary>
##### 30 October 2021 ##### 30 October 2021
Work on the OSX [macOS] and FreeBSD branches, both initiated and mostly worked on by [@joske](https://github.com/joske), will likely be completed in the coming weeks. Work on the OSX [macOS] and FreeBSD branches, both initiated and mostly worked on by [@joske](https://github.com/joske), will likely be completed in the coming weeks.
@ -94,7 +106,7 @@ If you want to help out, test for bugs/fix bugs or just try out the branches:
**macOS / OSX** **macOS / OSX**
```bash ```bash
# Install and use Homebrew or MacPorts package managers for easy dependency installation # Install and use Homebrew or MacPorts package managers for easy dependency installation
brew install coreutils make gcc@11 brew install coreutils make gcc@11 lowdown
git clone https://github.com/aristocratos/btop.git git clone https://github.com/aristocratos/btop.git
cd btop cd btop
git checkout OSX git checkout OSX
@ -103,7 +115,7 @@ gmake
**FreeBSD** **FreeBSD**
```bash ```bash
sudo pkg install gmake gcc11 coreutils git sudo pkg install gmake gcc11 coreutils git lowdown
git clone https://github.com/aristocratos/btop.git git clone https://github.com/aristocratos/btop.git
cd btop cd btop
git checkout freebsd git checkout freebsd
@ -210,6 +222,22 @@ Also needs a UTF8 locale and a font that covers:
* Unicode Block “Geometric Shapes” U+25A0 - U+25FF * Unicode Block “Geometric Shapes” U+25A0 - U+25FF
* Unicode Block "Box Drawing" and "Block Elements" U+2500 - U+259F * Unicode Block "Box Drawing" and "Block Elements" U+2500 - U+259F
### **Optional Dependencies (Needed for GPU monitoring)**
GPU monitoring also requires a btop binary built with GPU support (`GPU_SUPPORT=true` flag).
See [GPU compatibility](#gpu-compatibility) section for more about compiling with GPU support.
* **NVIDIA**
If you have an NVIDIA GPU you must use an official NVIDIA driver, both the closed-source and open-source ones have been verified to work.
In addition to that you must also have the nvidia-ml dynamic library installed, which should be included with the driver package of your distribution.
* **AMD**
If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packaged for your distribution.
### **Notice (Text rendering issues)** ### **Notice (Text rendering issues)**
* If you are having problems with the characters in the graphs not looking like they do in the screenshots, it's likely a problem with your systems configured fallback font not having support for braille characters. * If you are having problems with the characters in the graphs not looking like they do in the screenshots, it's likely a problem with your systems configured fallback font not having support for braille characters.
@ -350,12 +378,12 @@ Also needs a UTF8 locale and a font that covers:
* **AMD** * **AMD**
AMDGPU data is queried using the [ROCm SMI](https://github.com/RadeonOpenCompute/rocm_smi_lib) library, which may or may not be packaged for your distribution. If your distribution doesn't provide a package, btop++ is statically linked to ROCm SMI with the `RSMI_STATIC=true` make flag. AMDGPU data is queried using the [ROCm SMI](https://github.com/rocm/rocm_smi_lib) library, which may or may not be packaged for your distribution. If your distribution doesn't provide a package, btop++ is statically linked to ROCm SMI with the `RSMI_STATIC=true` make flag.
This flag expects the ROCm SMI source code in `lib/rocm_smi_lib`, and compilation will fail if it's not there. The latest tested version is 5.6.x, which can be obtained with the following command: This flag expects the ROCm SMI source code in `lib/rocm_smi_lib`, and compilation will fail if it's not there. The latest tested version is 5.6.x, which can be obtained with the following command:
```bash ```bash
git clone https://github.com/RadeonOpenCompute/rocm_smi_lib.git --depth 1 -b rocm-5.6.x lib/rocm_smi_lib git clone https://github.com/rocm/rocm_smi_lib.git --depth 1 -b rocm-5.6.x lib/rocm_smi_lib
``` ```
<details> <details>
@ -364,11 +392,11 @@ Also needs a UTF8 locale and a font that covers:
### With Make ### With Make
</summary> </summary>
1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** 1. **Install dependencies (example for Ubuntu 21.04 Hirsute)**
```bash ```bash
sudo apt install coreutils sed git build-essential gcc-11 g++-11 sudo apt install coreutils sed git build-essential gcc-11 g++-11 lowdown
``` ```
2. **Clone repository** 2. **Clone repository**
@ -391,17 +419,19 @@ Also needs a UTF8 locale and a font that covers:
| `STATIC=true` | For static compilation | | `STATIC=true` | For static compilation |
| `QUIET=true` | For less verbose output | | `QUIET=true` | For less verbose output |
| `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | | `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 | | `ARCH=<architecture>` | To manually set the target architecture |
| `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` |
| `GPU_SUPPORT=<true\|false>` | Enable/disable GPU support (Enabled by default on X86_64 Linux) | | `GPU_SUPPORT=<true\|false>` | Enable/disable GPU support (Enabled by default on X86_64 Linux) |
| `RSMI_STATIC=true` | To statically link the ROCm SMI library used for querying AMDGPU | | `RSMI_STATIC=true` | To statically link the ROCm SMI library used for querying AMDGPU |
| `ADDFLAGS=<flags>` | For appending flags to both compiler and linker | | `ADDFLAGS=<flags>` | For appending flags to both compiler and linker |
| `CXX=<compiler>` | Manualy set which compiler to use | | `CXX=<compiler>` | Manually set which compiler to use |
Example: `make ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. Example: `make ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc. Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc.
4. **Install** 4. **Install**
```bash ```bash
sudo make install sudo make install
@ -411,7 +441,7 @@ Also needs a UTF8 locale and a font that covers:
Notice! Only use "sudo" when installing to a NON user owned directory. Notice! Only use "sudo" when installing to a NON user owned directory.
5. **(Optional) Set suid bit to make btop always run as root (or other user)** 5. **(Optional) Set suid bit to make btop always run as root (or other user)**
```bash ```bash
sudo make setuid sudo make setuid
@ -456,12 +486,12 @@ Also needs a UTF8 locale and a font that covers:
1. **Install build dependencies** 1. **Install build dependencies**
Requires Clang / GCC, CMake, Ninja and Git Requires Clang / GCC, CMake, Ninja, Lowdown and Git
For example, with Debian Bookworm: For example, with Debian Bookworm:
```bash ```bash
sudo apt install cmake git g++ ninja-build sudo apt install cmake git g++ ninja-build lowdown
``` ```
2. **Clone the repository** 2. **Clone the repository**
@ -490,6 +520,7 @@ Also needs a UTF8 locale and a font that covers:
| `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) | | `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) |
| `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) | | `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) |
| `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) | | `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) |
| `-DBTOP_FORTIFY=<ON\|OFF>` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) |
| `-DBTOP_GPU=<ON\|OFF>` | Enable GPU support (ON by default) | | `-DBTOP_GPU=<ON\|OFF>` | Enable GPU support (ON by default) |
| `-DBTOP_RSMI_STATIC=<ON\|OFF>` | Build and link the ROCm SMI library statically (OFF by default) | | `-DBTOP_RSMI_STATIC=<ON\|OFF>` | Build and link the ROCm SMI library statically (OFF by default) |
| `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) | | `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) |
@ -538,7 +569,7 @@ Also needs a UTF8 locale and a font that covers:
1. **Install dependencies (example for Homebrew)** 1. **Install dependencies (example for Homebrew)**
```bash ```bash
brew install coreutils make gcc@12 brew install coreutils make gcc@12 lowdown
``` ```
2. **Clone repository** 2. **Clone repository**
@ -561,13 +592,15 @@ Also needs a UTF8 locale and a font that covers:
| `STATIC=true` | For static compilation (only libgcc and libstdc++) | | `STATIC=true` | For static compilation (only libgcc and libstdc++) |
| `QUIET=true` | For less verbose output | | `QUIET=true` | For less verbose output |
| `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) |
| `ARCH=<architecture>` | To manually set the target architecture | | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging |
| `ARCH=<architecture>` | To manually set the target architecture |
| `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` |
| `ADDFLAGS=<flags>` | For appending flags to both compiler and linker | | `ADDFLAGS=<flags>` | For appending flags to both compiler and linker |
| `CXX=<compiler>` | Manualy set which compiler to use | | `CXX=<compiler>` | Manually set which compiler to use |
Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
4. **Install** 4. **Install**
```bash ```bash
sudo gmake install sudo gmake install
@ -577,7 +610,7 @@ Also needs a UTF8 locale and a font that covers:
Notice! Only use "sudo" when installing to a NON user owned directory. 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)** 5. **(Recommended) Set suid bit to make btop always run as root (or other user)**
```bash ```bash
sudo gmake setuid sudo gmake setuid
@ -622,11 +655,11 @@ Also needs a UTF8 locale and a font that covers:
1. **Install build dependencies** 1. **Install build dependencies**
Requires Clang, CMake, Ninja and Git Requires Clang, CMake, Ninja, Lowdown and Git
```bash ```bash
brew update --quiet brew update --quiet
brew install cmake git llvm ninja brew install cmake git llvm ninja lowdown
``` ```
2. **Clone the repository** 2. **Clone the repository**
@ -660,6 +693,7 @@ Also needs a UTF8 locale and a font that covers:
| `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) | | `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) |
| `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) | | `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) |
| `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) | | `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) |
| `-DBTOP_FORTIFY=<ON\|OFF>` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) |
| `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) | | `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) |
To force any specific compiler, run `CXX=<compiler> cmake -B build -G Ninja` To force any specific compiler, run `CXX=<compiler> cmake -B build -G Ninja`
@ -702,7 +736,7 @@ Also needs a UTF8 locale and a font that covers:
1. **Install dependencies** 1. **Install dependencies**
```bash ```bash
sudo pkg install gmake gcc11 coreutils git sudo pkg install gmake gcc11 coreutils git lowdown
``` ```
2. **Clone repository** 2. **Clone repository**
@ -726,18 +760,20 @@ Also needs a UTF8 locale and a font that covers:
| `STATIC=true` | For static compilation (only libgcc and libstdc++) | | `STATIC=true` | For static compilation (only libgcc and libstdc++) |
| `QUIET=true` | For less verbose output | | `QUIET=true` | For less verbose output |
| `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) |
| `ARCH=<architecture>` | To manually set the target architecture | | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging |
| `ARCH=<architecture>` | To manually set the target architecture |
| `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` |
| `ADDFLAGS=<flags>` | For appending flags to both compiler and linker | | `ADDFLAGS=<flags>` | For appending flags to both compiler and linker |
| `CXX=<compiler>` | Manualy set which compiler to use | | `CXX=<compiler>` | Manually set which compiler to use |
Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system.
4. **Install** 4. **Install**
```bash ```bash
sudo gmake install sudo gmake install
``` ```
Append `PREFIX=/target/dir` to set target, default: `/usr/local` Append `PREFIX=/target/dir` to set target, default: `/usr/local`
Notice! Only use "sudo" when installing to a NON user owned directory. Notice! Only use "sudo" when installing to a NON user owned directory.
@ -747,7 +783,7 @@ Also needs a UTF8 locale and a font that covers:
```bash ```bash
sudo gmake setuid sudo gmake setuid
``` ```
No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. 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. Run after make install and use same PREFIX if any was used at install.
@ -787,18 +823,18 @@ Also needs a UTF8 locale and a font that covers:
1. **Install build dependencies** 1. **Install build dependencies**
Requires Clang / GCC, CMake, Ninja and Git Requires Clang / GCC, CMake, Ninja, Lowdown and Git
_**Note:** LLVM's libc++ shipped with FreeBSD 13 is too old and cannot compile btop._ _**Note:** LLVM's libc++ shipped with FreeBSD 13 is too old and cannot compile btop._
FreeBSD 14 and later: FreeBSD 14 and later:
```bash ```bash
pkg install cmake ninja pkg install cmake ninja lowdown
``` ```
FreeBSD 13: FreeBSD 13:
```bash ```bash
pkg install cmake gcc13 ninja pkg install cmake gcc13 ninja lowdown
``` ```
2. **Clone the repository** 2. **Clone the repository**
@ -836,6 +872,7 @@ Also needs a UTF8 locale and a font that covers:
| `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) | | `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) |
| `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) | | `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) |
| `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) | | `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) |
| `-DBTOP_FORTIFY=<ON\|OFF>` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) |
| `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) | | `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) |
_**Note:** Static linking does not work with GCC._ _**Note:** Static linking does not work with GCC._
@ -865,6 +902,169 @@ Also needs a UTF8 locale and a font that covers:
</details> </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 lowdown
```
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 |
| `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` |
| `ADDFLAGS=<flags>` | For appending flags to both compiler and linker |
| `CXX=<compiler>` | Manually 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>
<details>
<summary>
### With CMake (Community maintained)
</summary>
1. **Install build dependencies**
Requires GCC, CMake, Ninja, Lowdown and Git
_**Note:** LLVM's libc++ shipped with OpenBSD 7.4 is too old and cannot compile btop._
```bash
pkg_add cmake g++%11 git ninja lowdown
```
2. **Clone the repository**
```bash
git clone https://github.com/aristocratos/btop.git && cd btop
```
3. **Compile**
```bash
# Configure
CXX=eg++ cmake -B build -G Ninja
# Build
cmake --build build
```
This will automatically build a release version of btop.
Some useful options to pass to the configure step:
| Configure flag | Description |
|---------------------------------|-------------------------------------------------------------------------|
| `-DBTOP_LTO=<ON\|OFF>` | Enables link time optimization (ON by default) |
| `-DBTOP_USE_MOLD=<ON\|OFF>` | Use mold to link btop (OFF by default) |
| `-DBTOP_PEDANTIC=<ON\|OFF>` | Compile with additional warnings (OFF by default) |
| `-DBTOP_WERROR=<ON\|OFF>` | Compile with warnings as errors (OFF by default) |
| `-DBTOP_FORTIFY=<ON\|OFF>` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) |
| `-DCMAKE_INSTALL_PREFIX=<path>` | The installation prefix ('/usr/local' by default) |
To force any other compiler, run `CXX=<compiler> cmake -B build -G Ninja`
4. **Install**
```bash
cmake --install build
```
May require root privileges
5. **Uninstall**
CMake doesn't generate an uninstall target by default. To remove installed files, run
```
cat build/install_manifest.txt | xargs rm -irv
```
6. **Cleanup build directory**
```bash
cmake --build build -t clean
```
</details>
## Installing the snap ## Installing the snap
[![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop)

View file

@ -3,7 +3,7 @@
# Find libkvm, the Kernel Data Access Library # Find libkvm, the Kernel Data Access Library
# #
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") if(BSD)
find_path(kvm_INCLUDE_DIR NAMES kvm.h) find_path(kvm_INCLUDE_DIR NAMES kvm.h)
find_library(kvm_LIBRARY NAMES kvm) find_library(kvm_LIBRARY NAMES kvm)

File diff suppressed because it is too large Load diff

57
manpage.md Normal file
View file

@ -0,0 +1,57 @@
% btop(1) | User Commands
%
% "January 4 2024"
# NAME
btop - Resource monitor that shows usage and stats for processor, memory, disks, network, and processes.
# SYNOPSIS
**btop** [**-lc**] [**-t** | **+t**] [**-p** _id_] [**\-\-utf-force**]
[**\-\-debug**] [{**-h** | **\-\-help**} | {**-v** | **\-\-version**}]
# DESCRIPTION
**btop** is a program that shows usage and stats for processor, memory, disks, network, and processes.
# OPTIONS
The program follows the usual GNU command line syntax, with long options
starting with two dashes ('-'). A summary of options is included below.
**-lc**, **\-\-low-color**
: Disable truecolor, converts 24-bit colors to 256-color.
**-t**, **\-\-tty_on**
: Force (ON) tty mode, max 16 colors and tty-friendly graph symbols.
**+t**, **\-\-tty_off**
: Force (OFF) tty mode.
**-p**, **\-\-preset _id_**
: Start with preset, integer value between 0-9.
**\-\-utf-force**
: Force start even if no UTF-8 locale was detected.
**\-\-debug**
: Start in DEBUG mode: shows microsecond timer for information collect and screen draw functions and sets loglevel to DEBUG.
**-h**, **\-\-help**
: Show summary of options.
**-v**, **\-\-version**
: Show version of program.
# BUGS
The upstream bug tracker can be found at https://github.com/aristocratos/btop/issues.
# SEE ALSO
**top**(1), **htop**(1)
# AUTHOR
**btop** was written by Jakob P. Liljenberg a.k.a. "Aristocratos".

View file

@ -45,9 +45,12 @@ parts:
source-type: git source-type: git
plugin: make plugin: make
make-parameters: make-parameters:
- CXX=g++-11
- PREFIX=/usr/local - PREFIX=/usr/local
- STATIC=true - STATIC=true
- ADDFLAGS="-D SNAPPED" - ADDFLAGS="-D SNAPPED"
# Add 'lowdown' to build dependencies as soon at Snap builder updates from
# Ubuntu 20.04 "Focal" to something newer that has Lowdown included
build-packages: build-packages:
- coreutils - coreutils
- sed - sed
@ -55,7 +58,7 @@ parts:
- build-essential - build-essential
- gcc-11 - gcc-11
- g++-11 - g++-11
override-pull: | override-pull: |
snapcraftctl pull snapcraftctl pull
snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1)" snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1)"

View file

@ -16,6 +16,7 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <algorithm>
#include <csignal> #include <csignal>
#include <clocale> #include <clocale>
#include <pthread.h> #include <pthread.h>
@ -32,6 +33,7 @@ tab-size = 4
#include <tuple> #include <tuple>
#include <regex> #include <regex>
#include <chrono> #include <chrono>
#include <utility>
#ifdef __APPLE__ #ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
@ -50,6 +52,9 @@ tab-size = 4
#include "btop_theme.hpp" #include "btop_theme.hpp"
#include "btop_draw.hpp" #include "btop_draw.hpp"
#include "btop_menu.hpp" #include "btop_menu.hpp"
#include "config.h"
#include "fmt/core.h"
#include "fmt/ostream.h"
using std::atomic; using std::atomic;
using std::cout; using std::cout;
@ -75,7 +80,7 @@ namespace Global {
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
}; };
const string Version = "1.3.0"; const string Version = "1.3.2";
int coreCount; int coreCount;
string overlay; string overlay;
@ -93,9 +98,9 @@ namespace Global {
string exit_error_msg; string exit_error_msg;
atomic<bool> thread_exception (false); atomic<bool> thread_exception (false);
bool debuginit{}; // defaults to false bool debuginit{};
bool debug{}; // defaults to false bool debug{};
bool utf_force{}; // defaults to false bool utf_force{};
uint64_t start_time; uint64_t start_time;
@ -104,19 +109,35 @@ namespace Global {
atomic<bool> should_quit (false); atomic<bool> should_quit (false);
atomic<bool> should_sleep (false); atomic<bool> should_sleep (false);
atomic<bool> _runner_started (false); atomic<bool> _runner_started (false);
atomic<bool> init_conf (false);
atomic<bool> reload_conf (false);
bool arg_tty{}; // defaults to false bool arg_tty{};
bool arg_low_color{}; // defaults to false bool arg_low_color{};
int arg_preset = -1; int arg_preset = -1;
int arg_update = 0;
}
static void print_version() {
if constexpr (GIT_COMMIT.empty()) {
fmt::print("btop version: {}\n", Global::Version);
} else {
fmt::print("btop version: {}+{}\n", Global::Version, GIT_COMMIT);
}
}
static void print_version_with_build_info() {
print_version();
fmt::print("Compiled with: {} ({})\nConfigured with: {}\n", COMPILER, COMPILER_VERSION, CONFIGURE_COMMAND);
} }
//* A simple argument parser //* 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++) { for(int i = 1; i < argc; i++) {
const string argument = argv[i]; const string argument = argv[i];
if (is_in(argument, "-h", "--help")) { if (is_in(argument, "-h", "--help")) {
fmt::println( fmt::println(
"usage: btop [-h] [-v] [-/+t] [-p <id>] [--utf-force] [--debug]\n\n" "usage: btop [-h] [-v] [-/+t] [-p <id>] [-u <ms>] [--utf-force] [--debug]\n\n"
"optional arguments:\n" "optional arguments:\n"
" -h, --help show this help message and exit\n" " -h, --help show this help message and exit\n"
" -v, --version show version info and exit\n" " -v, --version show version info and exit\n"
@ -124,14 +145,19 @@ 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_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
" +t, --tty_off force (OFF) tty mode\n" " +t, --tty_off force (OFF) tty mode\n"
" -p, --preset <id> start with preset, integer value between 0-9\n" " -p, --preset <id> start with preset, integer value between 0-9\n"
" -u, --update <ms> set the program update rate in milliseconds\n"
" --utf-force force start even if no UTF-8 locale was detected\n" " --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" " --debug start in DEBUG mode: shows microsecond timer for information collect\n"
" and screen draw functions and sets loglevel to DEBUG" " and screen draw functions and sets loglevel to DEBUG"
); );
exit(0); exit(0);
} }
else if (is_in(argument, "-v", "--version")) { else if (is_in(argument, "-v")) {
fmt::println("btop version: {}", Global::Version); print_version();
exit(0);
}
else if (is_in(argument, "--version")) {
print_version_with_build_info();
exit(0); exit(0);
} }
else if (is_in(argument, "-lc", "--low-color")) { else if (is_in(argument, "-lc", "--low-color")) {
@ -158,6 +184,19 @@ void argumentParser(const int& argc, char **argv) {
exit(1); 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") else if (argument == "--utf-force")
Global::utf_force = true; Global::utf_force = true;
else if (argument == "--debug") else if (argument == "--debug")
@ -175,7 +214,7 @@ void term_resize(bool force) {
static atomic<bool> resizing (false); static atomic<bool> resizing (false);
if (Input::polling) { if (Input::polling) {
Global::resized = true; Global::resized = true;
Input::interrupt = true; Input::interrupt();
return; return;
} }
atomic_lock lck(resizing, true); atomic_lock lck(resizing, true);
@ -245,7 +284,7 @@ void term_resize(bool force) {
else if (not Term::refresh()) break; else if (not Term::refresh()) break;
} }
Input::interrupt = true; Input::interrupt();
} }
//* Exit handler; stops threads, restores terminal and saves config changes //* Exit handler; stops threads, restores terminal and saves config changes
@ -254,7 +293,7 @@ void clean_quit(int sig) {
Global::quitting = true; Global::quitting = true;
Runner::stop(); Runner::stop();
if (Global::_runner_started) { if (Global::_runner_started) {
#ifdef __APPLE__ #if defined __APPLE__ || defined __OpenBSD__
if (pthread_join(Runner::runner_id, nullptr) != 0) { if (pthread_join(Runner::runner_id, nullptr) != 0) {
Logger::warning("Failed to join _runner thread on exit!"); Logger::warning("Failed to join _runner thread on exit!");
pthread_cancel(Runner::runner_id); pthread_cancel(Runner::runner_id);
@ -290,7 +329,7 @@ void clean_quit(int sig) {
const auto excode = (sig != -1 ? sig : 0); const auto excode = (sig != -1 ? sig : 0);
#ifdef __APPLE__ #if defined __APPLE__ || defined __OpenBSD__
_Exit(excode); _Exit(excode);
#else #else
quick_exit(excode); quick_exit(excode);
@ -320,7 +359,7 @@ void _signal_handler(const int sig) {
if (Runner::active) { if (Runner::active) {
Global::should_quit = true; Global::should_quit = true;
Runner::stopping = true; Runner::stopping = true;
Input::interrupt = true; Input::interrupt();
} }
else { else {
clean_quit(0); clean_quit(0);
@ -330,7 +369,7 @@ void _signal_handler(const int sig) {
if (Runner::active) { if (Runner::active) {
Global::should_sleep = true; Global::should_sleep = true;
Runner::stopping = true; Runner::stopping = true;
Input::interrupt = true; Input::interrupt();
} }
else { else {
_sleep(); _sleep();
@ -342,9 +381,41 @@ void _signal_handler(const int sig) {
case SIGWINCH: case SIGWINCH:
term_resize(); term_resize();
break; break;
case SIGUSR1:
// Input::poll interrupt
break;
case SIGUSR2:
Global::reload_conf = true;
Input::interrupt();
break;
} }
} }
//* Config init
void init_config(){
atomic_lock lck(Global::init_conf);
vector<string> load_warnings;
Config::load(Config::conf_file, load_warnings);
Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor")));
static bool first_init = true;
if (Global::debug and first_init) {
Logger::set("DEBUG");
Logger::debug("Running in DEBUG mode!");
}
else Logger::set(Config::getS("log_level"));
static string log_level;
if (const string current_level = Config::getS("log_level"); log_level != current_level) {
log_level = current_level;
Logger::info("Logger set to " + (Global::debug ? "DEBUG" : log_level));
}
for (const auto& err_str : load_warnings) Logger::warning(err_str);
first_init = false;
}
//* Manages secondary thread for collection and drawing of boxes //* Manages secondary thread for collection and drawing of boxes
namespace Runner { namespace Runner {
atomic<bool> active (false); atomic<bool> active (false);
@ -381,7 +452,7 @@ namespace Runner {
} }
}; };
//* Wrapper for raising priviliges when using SUID bit //* Wrapper for raising privileges when using SUID bit
class gain_priv { class gain_priv {
int status = -1; int status = -1;
public: public:
@ -397,7 +468,7 @@ namespace Runner {
string output; string output;
string empty_bg; string empty_bg;
bool pause_output{}; // defaults to false bool pause_output{};
sigset_t mask; sigset_t mask;
pthread_t runner_id; pthread_t runner_id;
pthread_mutex_t mtx; pthread_mutex_t mtx;
@ -416,7 +487,7 @@ namespace Runner {
}; };
string debug_bg; string debug_bg;
unordered_flat_map<string, array<uint64_t, 2>> debug_times; std::unordered_map<string, array<uint64_t, 2>> debug_times;
class MyNumPunct : public std::numpunct<char> class MyNumPunct : public std::numpunct<char>
{ {
@ -476,7 +547,7 @@ namespace Runner {
if (pt_lck.status != 0) { if (pt_lck.status != 0) {
Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status); Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status);
Global::thread_exception = true; Global::thread_exception = true;
Input::interrupt = true; Input::interrupt();
stopping = true; stopping = true;
} }
@ -487,7 +558,7 @@ namespace Runner {
if (active) { if (active) {
Global::exit_error_msg = "Runner thread failed to get active lock!"; Global::exit_error_msg = "Runner thread failed to get active lock!";
Global::thread_exception = true; Global::thread_exception = true;
Input::interrupt = true; Input::interrupt();
stopping = true; stopping = true;
} }
if (stopping or Global::resized) { if (stopping or Global::resized) {
@ -557,7 +628,7 @@ namespace Runner {
coreNum_reset = false; coreNum_reset = false;
Cpu::core_mapping = Cpu::get_core_mapping(); Cpu::core_mapping = Cpu::get_core_mapping();
Global::resized = true; Global::resized = true;
Input::interrupt = true; Input::interrupt();
continue; continue;
} }
@ -654,7 +725,7 @@ namespace Runner {
catch (const std::exception& e) { catch (const std::exception& e) {
Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()}; Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()};
Global::thread_exception = true; Global::thread_exception = true;
Input::interrupt = true; Input::interrupt();
stopping = true; stopping = true;
} }
@ -814,7 +885,7 @@ int main(int argc, char **argv) {
Global::start_time = time_s(); Global::start_time = time_s();
//? Save real and effective userid's and drop priviliges until needed if running with SUID bit set //? Save real and effective userid's and drop privileges until needed if running with SUID bit set
Global::real_uid = getuid(); Global::real_uid = getuid();
Global::set_uid = geteuid(); Global::set_uid = geteuid();
if (Global::real_uid != Global::set_uid) { if (Global::real_uid != Global::set_uid) {
@ -828,29 +899,23 @@ int main(int argc, char **argv) {
//? Call argument parser if launched with arguments //? Call argument parser if launched with arguments
if (argc > 1) argumentParser(argc, argv); if (argc > 1) argumentParser(argc, argv);
//? Setup paths for config, log and user themes {
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) { const auto config_dir = Config::get_config_dir();
if (std::getenv(env) != nullptr and access(std::getenv(env), W_OK) != -1) { if (config_dir.has_value()) {
Config::conf_dir = fs::path(std::getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop"); Config::conf_dir = config_dir.value();
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 {
Config::conf_file = Config::conf_dir / "btop.conf"; Config::conf_file = Config::conf_dir / "btop.conf";
Logger::logfile = Config::conf_dir / "btop.log"; Logger::logfile = Config::conf_dir / "btop.log";
Theme::user_theme_dir = Config::conf_dir / "themes"; 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 //? Try to find global btop theme path relative to binary path
#ifdef __linux__ #ifdef __linux__
{ std::error_code ec; { std::error_code ec;
@ -879,22 +944,7 @@ int main(int argc, char **argv) {
} }
//? Config init //? Config init
{ vector<string> load_warnings; init_config();
Config::load(Config::conf_file, load_warnings);
if (Config::current_boxes.empty()) Config::check_boxes(Config::getS("shown_boxes"));
Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor")));
if (Global::debug) {
Logger::set("DEBUG");
Logger::debug("Starting in DEBUG mode!");
}
else Logger::set(Config::getS("log_level"));
Logger::info("Logger set to " + (Global::debug ? "DEBUG" : Config::getS("log_level")));
for (const auto& err_str : load_warnings) Logger::warning(err_str);
}
//? Try to find and set a UTF-8 locale //? Try to find and set a UTF-8 locale
if (std::setlocale(LC_ALL, "") != nullptr and not s_contains((string)std::setlocale(LC_ALL, ""), ";") if (std::setlocale(LC_ALL, "") != nullptr and not s_contains((string)std::setlocale(LC_ALL, ""), ";")
@ -903,8 +953,8 @@ int main(int argc, char **argv) {
} }
else { else {
string found; string found;
bool set_failure{}; // defaults to false bool set_failure{};
for (const auto loc_env : array{"LANG", "LC_ALL"}) { for (const auto loc_env : array{"LANG", "LC_ALL", "LC_CTYPE"}) {
if (std::getenv(loc_env) != nullptr and str_to_upper(s_replace((string)std::getenv(loc_env), "-", "")).ends_with("UTF8")) { if (std::getenv(loc_env) != nullptr and str_to_upper(s_replace((string)std::getenv(loc_env), "-", "")).ends_with("UTF8")) {
found = std::getenv(loc_env); found = std::getenv(loc_env);
if (std::setlocale(LC_ALL, found.c_str()) == nullptr) { if (std::setlocale(LC_ALL, found.c_str()) == nullptr) {
@ -930,7 +980,7 @@ int main(int argc, char **argv) {
catch (...) { found.clear(); } catch (...) { found.clear(); }
} }
} }
//
#ifdef __APPLE__ #ifdef __APPLE__
if (found.empty()) { if (found.empty()) {
CFLocaleRef cflocale = CFLocaleCopyCurrent(); CFLocaleRef cflocale = CFLocaleCopyCurrent();
@ -974,7 +1024,7 @@ int main(int argc, char **argv) {
Config::set("tty_mode", true); Config::set("tty_mode", true);
Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols"); 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")) { else if (not Global::arg_tty and Term::current_tty.starts_with("/dev/tty")) {
Config::set("tty_mode", true); Config::set("tty_mode", true);
Logger::info("Real tty detected: setting 16 color mode and using tty friendly graph symbols"); Logger::info("Real tty detected: setting 16 color mode and using tty friendly graph symbols");
@ -1003,6 +1053,11 @@ int main(int argc, char **argv) {
clean_quit(1); clean_quit(1);
} }
if (not Config::check_boxes(Config::getS("shown_boxes"))) {
Config::check_boxes("cpu mem net proc");
Config::set("shown_boxes", "cpu mem net proc"s);
}
//? Update list of available themes and generate the selected theme //? Update list of available themes and generate the selected theme
Theme::updateThemes(); Theme::updateThemes();
Theme::setTheme(); Theme::setTheme();
@ -1013,6 +1068,13 @@ int main(int argc, char **argv) {
std::signal(SIGTSTP, _signal_handler); std::signal(SIGTSTP, _signal_handler);
std::signal(SIGCONT, _signal_handler); std::signal(SIGCONT, _signal_handler);
std::signal(SIGWINCH, _signal_handler); std::signal(SIGWINCH, _signal_handler);
std::signal(SIGUSR1, _signal_handler);
std::signal(SIGUSR2, _signal_handler);
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &mask, &Input::signal_mask);
//? Start runner thread //? Start runner thread
Runner::thread_sem_init(); Runner::thread_sem_init();
@ -1034,9 +1096,10 @@ int main(int argc, char **argv) {
{ {
const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes")); const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes"));
if (Term::height < y or Term::width < x) { if (Term::height < y or Term::width < x) {
pthread_sigmask(SIG_SETMASK, &Input::signal_mask, &mask);
term_resize(true); term_resize(true);
pthread_sigmask(SIG_SETMASK, &mask, nullptr);
Global::resized = false; Global::resized = false;
Input::interrupt = false;
} }
} }
@ -1049,15 +1112,36 @@ int main(int argc, char **argv) {
//? ------------------------------------------------ MAIN LOOP ---------------------------------------------------- //? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
if (Global::arg_update != 0) {
Config::set("update_ms", Global::arg_update);
}
uint64_t update_ms = Config::getI("update_ms"); uint64_t update_ms = Config::getI("update_ms");
auto future_time = time_ms(); auto future_time = time_ms();
try { try {
while (not true not_eq not false) { while (not true not_eq not false) {
//? Check for exceptions in secondary thread and exit with fail signal if true //? Check for exceptions in secondary thread and exit with fail signal if true
if (Global::thread_exception) clean_quit(1); if (Global::thread_exception) {
else if (Global::should_quit) clean_quit(0); clean_quit(1);
else if (Global::should_sleep) { Global::should_sleep = false; _sleep(); } }
else if (Global::should_quit) {
clean_quit(0);
}
else if (Global::should_sleep) {
Global::should_sleep = false;
_sleep();
}
//? Hot reload config from CTRL + R or SIGUSR2
else if (Global::reload_conf) {
Global::reload_conf = false;
if (Runner::active) Runner::stop();
Config::unlock();
init_config();
Theme::updateThemes();
Theme::setTheme();
Draw::banner_gen(0, 0, false, true);
Global::resized = true;
}
//? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly) //? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
term_resize(Global::resized); term_resize(Global::resized);
@ -1092,9 +1176,9 @@ int main(int argc, char **argv) {
update_ms = Config::getI("update_ms"); update_ms = Config::getI("update_ms");
future_time = time_ms() + update_ms; future_time = time_ms() + update_ms;
} }
else if (future_time - current_time > update_ms) else if (future_time - current_time > update_ms) {
future_time = current_time; future_time = current_time;
}
//? Poll for input and process any input detected //? Poll for input and process any input detected
else if (Input::poll(min((uint64_t)1000, future_time - current_time))) { else if (Input::poll(min((uint64_t)1000, future_time - current_time))) {
if (not Runner::active) Config::unlock(); if (not Runner::active) Config::unlock();

View file

@ -21,8 +21,10 @@ tab-size = 4
#include <fstream> #include <fstream>
#include <ranges> #include <ranges>
#include <string_view> #include <string_view>
#include <utility>
#include <fmt/core.h> #include <fmt/core.h>
#include <sys/statvfs.h>
#include "btop_config.hpp" #include "btop_config.hpp"
#include "btop_shared.hpp" #include "btop_shared.hpp"
@ -197,6 +199,8 @@ namespace Config {
{"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."}, {"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."},
{"show_battery_watts", "#* Show power stats of battery next to charge indicator."},
{"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" {"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n"
"#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}, "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."},
#ifdef GPU_SUPPORT #ifdef GPU_SUPPORT
@ -215,7 +219,7 @@ namespace Config {
#endif #endif
}; };
unordered_flat_map<std::string_view, string> strings = { std::unordered_map<std::string_view, string> strings = {
{"color_theme", "Default"}, {"color_theme", "Default"},
{"shown_boxes", "cpu mem net proc"}, {"shown_boxes", "cpu mem net proc"},
{"graph_symbol", "braille"}, {"graph_symbol", "braille"},
@ -251,9 +255,9 @@ namespace Config {
{"show_gpu_info", "Auto"} {"show_gpu_info", "Auto"}
#endif #endif
}; };
unordered_flat_map<std::string_view, string> stringsTmp; std::unordered_map<std::string_view, string> stringsTmp;
unordered_flat_map<std::string_view, bool> bools = { std::unordered_map<std::string_view, bool> bools = {
{"theme_background", true}, {"theme_background", true},
{"truecolor", true}, {"truecolor", true},
{"rounded_corners", true}, {"rounded_corners", true},
@ -291,6 +295,7 @@ namespace Config {
{"net_auto", true}, {"net_auto", true},
{"net_sync", true}, {"net_sync", true},
{"show_battery", true}, {"show_battery", true},
{"show_battery_watts", true},
{"vim_keys", false}, {"vim_keys", false},
{"tty_mode", false}, {"tty_mode", false},
{"disk_free_priv", false}, {"disk_free_priv", false},
@ -304,9 +309,9 @@ namespace Config {
{"gpu_mirror_graph", true}, {"gpu_mirror_graph", true},
#endif #endif
}; };
unordered_flat_map<std::string_view, bool> boolsTmp; std::unordered_map<std::string_view, bool> boolsTmp;
unordered_flat_map<std::string_view, int> ints = { std::unordered_map<std::string_view, int> ints = {
{"update_ms", 2000}, {"update_ms", 2000},
{"net_download", 100}, {"net_download", 100},
{"net_upload", 100}, {"net_upload", 100},
@ -317,7 +322,66 @@ namespace Config {
{"proc_selected", 0}, {"proc_selected", 0},
{"proc_last_selected", 0}, {"proc_last_selected", 0},
}; };
unordered_flat_map<std::string_view, int> intsTmp; std::unordered_map<std::string_view, int> 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<fs::path> 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) { bool _locked(const std::string_view name) {
atomic_wait(writelock, true); atomic_wait(writelock, true);
@ -427,8 +491,8 @@ namespace Config {
if (name == "update_ms" and i_value < 100) if (name == "update_ms" and i_value < 100)
validError = "Config value update_ms set too low (<100)."; validError = "Config value update_ms set too low (<100).";
else if (name == "update_ms" and i_value > 86400000) else if (name == "update_ms" and i_value > ONE_DAY_MILLIS)
validError = "Config value update_ms set too high (>86400000)."; validError = fmt::format("Config value update_ms set too high (>{}).", ONE_DAY_MILLIS);
else else
return true; return true;
@ -446,7 +510,7 @@ namespace Config {
else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value))) else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value)))
validError = fmt::format("Invalid graph symbol identifier for {}: {}", name, value); validError = fmt::format("Invalid graph symbol identifier for {}: {}", name, value);
else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) else if (name == "shown_boxes" and not Global::init_conf and not value.empty() and not check_boxes(value))
validError = "Invalid box name(s) in shown_boxes!"; validError = "Invalid box name(s) in shown_boxes!";
#ifdef GPU_SUPPORT #ifdef GPU_SUPPORT
@ -558,8 +622,7 @@ namespace Config {
if (not v_contains(valid_boxes, box)) return false; if (not v_contains(valid_boxes, box)) return false;
#ifdef GPU_SUPPORT #ifdef GPU_SUPPORT
if (box.starts_with("gpu")) { if (box.starts_with("gpu")) {
size_t gpu_num = stoi(box.substr(3)); size_t gpu_num = stoi(box.substr(3)) + 1;
if (gpu_num == 0) gpu_num = 5;
if (std::cmp_greater(gpu_num, Gpu::gpu_names.size())) return false; if (std::cmp_greater(gpu_num, Gpu::gpu_names.size())) return false;
} }
#endif #endif
@ -593,12 +656,17 @@ namespace Config {
} }
void load(const fs::path& conf_file, vector<string>& load_warnings) { void load(const fs::path& conf_file, vector<string>& load_warnings) {
std::error_code error;
if (conf_file.empty()) if (conf_file.empty())
return; return;
else if (not fs::exists(conf_file)) { else if (not fs::exists(conf_file, error)) {
write_new = true; write_new = true;
return; return;
} }
if (error) {
return;
}
std::ifstream cread(conf_file); std::ifstream cread(conf_file);
if (cread.good()) { if (cread.good()) {
vector<string> valid_names; vector<string> valid_names;
@ -664,9 +732,9 @@ namespace Config {
if (geteuid() != Global::real_uid and seteuid(Global::real_uid) != 0) return; if (geteuid() != Global::real_uid and seteuid(Global::real_uid) != 0) return;
std::ofstream cwrite(conf_file, std::ios::trunc); std::ofstream cwrite(conf_file, std::ios::trunc);
if (cwrite.good()) { if (cwrite.good()) {
cwrite << "#? Config file for btop v. " << Global::Version; cwrite << "#? Config file for btop v. " << Global::Version << "\n";
for (auto [name, description] : descriptions) { for (auto [name, description] : descriptions) {
cwrite << "\n\n" << (description.empty() ? "" : description + "\n") cwrite << "\n" << (description.empty() ? "" : description + "\n")
<< name << " = "; << name << " = ";
if (strings.contains(name)) if (strings.contains(name))
cwrite << "\"" << strings.at(name) << "\""; cwrite << "\"" << strings.at(name) << "\"";
@ -674,6 +742,7 @@ namespace Config {
cwrite << ints.at(name); cwrite << ints.at(name);
else if (bools.contains(name)) else if (bools.contains(name))
cwrite << (bools.at(name) ? "True" : "False"); cwrite << (bools.at(name) ? "True" : "False");
cwrite << "\n";
} }
} }
} }

View file

@ -18,15 +18,15 @@ tab-size = 4
#pragma once #pragma once
#include <filesystem>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <filesystem>
#include <robin_hood.h> #include <unordered_map>
using std::string; using std::string;
using std::vector; using std::vector;
using robin_hood::unordered_flat_map;
//* Functions and variables for reading and writing the btop config file //* Functions and variables for reading and writing the btop config file
namespace Config { namespace Config {
@ -34,12 +34,12 @@ namespace Config {
extern std::filesystem::path conf_dir; extern std::filesystem::path conf_dir;
extern std::filesystem::path conf_file; extern std::filesystem::path conf_file;
extern unordered_flat_map<std::string_view, string> strings; extern std::unordered_map<std::string_view, string> strings;
extern unordered_flat_map<std::string_view, string> stringsTmp; extern std::unordered_map<std::string_view, string> stringsTmp;
extern unordered_flat_map<std::string_view, bool> bools; extern std::unordered_map<std::string_view, bool> bools;
extern unordered_flat_map<std::string_view, bool> boolsTmp; extern std::unordered_map<std::string_view, bool> boolsTmp;
extern unordered_flat_map<std::string_view, int> ints; extern std::unordered_map<std::string_view, int> ints;
extern unordered_flat_map<std::string_view, int> intsTmp; extern std::unordered_map<std::string_view, int> intsTmp;
const vector<string> valid_graph_symbols = { "braille", "block", "tty" }; const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
const vector<string> valid_graph_symbols_def = { "default", "braille", "block", "tty" }; const vector<string> valid_graph_symbols_def = { "default", "braille", "block", "tty" };
@ -58,6 +58,10 @@ namespace Config {
extern vector<string> available_batteries; extern vector<string> available_batteries;
extern int current_preset; extern int current_preset;
constexpr int ONE_DAY_MILLIS = 1000 * 60 * 60 * 24;
[[nodiscard]] std::optional<std::filesystem::path> get_config_dir() noexcept;
//* Check if string only contains space separated valid names for boxes //* Check if string only contains space separated valid names for boxes
bool check_boxes(const string& boxes); bool check_boxes(const string& boxes);
@ -95,7 +99,7 @@ namespace Config {
} }
//* Set config key <name> to int <value> //* Set config key <name> to int <value>
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); if (_locked(name)) intsTmp.insert_or_assign(name, value);
else ints.at(name) = value; else ints.at(name) = value;
} }

View file

@ -22,6 +22,7 @@ tab-size = 4
#include <ranges> #include <ranges>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <utility>
#include "btop_draw.hpp" #include "btop_draw.hpp"
#include "btop_config.hpp" #include "btop_config.hpp"
@ -54,7 +55,7 @@ namespace Symbols {
const array<string, 10> superscript = { "", "¹", "²", "³", "", "", "", "", "", "" }; const array<string, 10> superscript = { "", "¹", "²", "³", "", "", "", "", "", "" };
const unordered_flat_map<string, vector<string>> graph_symbols = { const std::unordered_map<string, vector<string>> graph_symbols = {
{ "braille_up", { { "braille_up", {
" ", "", "", "", "", " ", "", "", "", "",
"", "", "", "", "", "", "", "", "", "",
@ -300,14 +301,14 @@ namespace Draw {
return false; return false;
} }
static const unordered_flat_map<string, string> clock_custom_format = { static const std::unordered_map<string, string> clock_custom_format = {
{"/user", Tools::username()}, {"/user", Tools::username()},
{"/host", Tools::hostname()}, {"/host", Tools::hostname()},
{"/uptime", ""} {"/uptime", ""}
}; };
static time_t c_time{}; // defaults to 0 static time_t c_time{};
static size_t clock_len{}; // defaults to 0 static size_t clock_len{};
static string clock_str; static string clock_str;
if (auto n_time = time(nullptr); not force and n_time == c_time) if (auto n_time = time(nullptr); not force and n_time == c_time)
@ -560,12 +561,12 @@ namespace Cpu {
const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right);
static int bat_pos = 0, bat_len = 0; static int bat_pos = 0, bat_len = 0;
if (cpu.cpu_percent.at("total").empty() if (safeVal(cpu.cpu_percent, "total"s).empty()
or cpu.core_percent.at(0).empty() or safeVal(cpu.core_percent, 0).empty()
or (show_temps and cpu.temp.at(0).empty())) return ""; or (show_temps and safeVal(cpu.temp, 0).empty())) return "";
if (cpu.cpu_percent.at("total").empty() if (safeVal(cpu.cpu_percent, "total"s).empty()
or cpu.core_percent.at(0).empty() or safeVal(cpu.core_percent, 0).empty()
or (show_temps and cpu.temp.at(0).empty())) return ""; or (show_temps and safeVal(cpu.temp, 0).empty())) return "";
string out; string out;
out.reserve(width * height); out.reserve(width * height);
@ -608,7 +609,7 @@ namespace Cpu {
if (gpu.supported_functions.temp_info) if (gpu.supported_functions.temp_info)
gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 }; gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 };
if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total)
gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol }; gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol };
if (gpu.supported_functions.gpu_utilization) { if (gpu.supported_functions.gpu_utilization) {
gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9);
gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" };
@ -617,12 +618,12 @@ namespace Cpu {
bool utilization_support = gpu.supported_functions.gpu_utilization; bool utilization_support = gpu.supported_functions.gpu_utilization;
if (++i < gpus.size()) { if (++i < gpus.size()) {
if (utilization_support) if (utilization_support)
graph = Draw::Graph{graph_width, graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true}; graph = Draw::Graph{graph_width, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true};
} else { } else {
if (utilization_support) if (utilization_support)
graph = Draw::Graph{ graph = Draw::Graph{
graph_width + graph_default_width%graph_width - (int)gpus.size() + 1, graph_width + graph_default_width%graph_width - (int)gpus.size() + 1,
graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true
}; };
break; break;
} }
@ -630,7 +631,7 @@ namespace Cpu {
} else { } else {
graphs.resize(1); graphs.resize(1);
graph_width = graph_default_width; graph_width = graph_default_width;
graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", Gpu::shared_gpu_percent.at(graph_field), graph_symbol, invert, true }; graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(Gpu::shared_gpu_percent, graph_field), graph_symbol, invert, true };
gpu_temp_graphs.resize(gpus.size()); gpu_temp_graphs.resize(gpus.size());
gpu_mem_graphs.resize(gpus.size()); gpu_mem_graphs.resize(gpus.size());
gpu_meters.resize(gpus.size()); gpu_meters.resize(gpus.size());
@ -638,7 +639,7 @@ namespace Cpu {
if (gpus[i].supported_functions.temp_info) if (gpus[i].supported_functions.temp_info)
gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 };
if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total)
gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol };
if (gpus[i].supported_functions.gpu_utilization) { if (gpus[i].supported_functions.gpu_utilization) {
gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9);
gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" };
@ -649,7 +650,7 @@ namespace Cpu {
#endif #endif
graphs.resize(1); graphs.resize(1);
graph_width = graph_default_width; graph_width = graph_default_width;
graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", cpu.cpu_percent.at(graph_field), graph_symbol, invert, true }; graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(cpu.cpu_percent, graph_field), graph_symbol, invert, true };
#ifdef GPU_SUPPORT #ifdef GPU_SUPPORT
if (std::cmp_less(Gpu::shown, gpus.size())) { if (std::cmp_less(Gpu::shown, gpus.size())) {
gpu_temp_graphs.resize(gpus.size()); gpu_temp_graphs.resize(gpus.size());
@ -659,7 +660,7 @@ namespace Cpu {
if (gpus[i].supported_functions.temp_info) if (gpus[i].supported_functions.temp_info)
gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 };
if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total)
gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol };
if (gpus[i].supported_functions.gpu_utilization) { if (gpus[i].supported_functions.gpu_utilization) {
gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9);
gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" };
@ -692,10 +693,10 @@ namespace Cpu {
if (show_temps) { if (show_temps) {
temp_graphs.clear(); temp_graphs.clear();
temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23); temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, 0), graph_symbol, false, false, cpu.temp_max, -23);
if (not hide_cores and b_column_size > 1) { if (not hide_cores and b_column_size > 1) {
for (const auto& i : iota((size_t)1, cpu.temp.size())) { for (const auto& i : iota((size_t)1, cpu.temp.size())) {
temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23); temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, i), graph_symbol, false, false, cpu.temp_max, -23);
} }
} }
} }
@ -705,25 +706,28 @@ namespace Cpu {
if (Config::getB("show_battery") and has_battery) { if (Config::getB("show_battery") and has_battery) {
static int old_percent{}; // defaults to = 0 static int old_percent{}; // defaults to = 0
static long old_seconds{}; // defaults to = 0 static long old_seconds{}; // defaults to = 0
static float old_watts{}; // defaults to = 0
static string old_status; static string old_status;
static Draw::Meter bat_meter {10, "cpu", true}; static Draw::Meter bat_meter {10, "cpu", true};
static const unordered_flat_map<string, string> bat_symbols = { static const std::unordered_map<string, string> bat_symbols = {
{"charging", ""}, {"charging", ""},
{"discharging", ""}, {"discharging", ""},
{"full", ""}, {"full", ""},
{"unknown", ""} {"unknown", ""}
}; };
const auto& [percent, seconds, status] = current_bat; const auto& [percent, watts, seconds, status] = current_bat;
if (redraw or percent != old_percent or seconds != old_seconds or status != old_status) { if (redraw or percent != old_percent or (watts != old_watts and Config::getB("show_battery_watts")) or seconds != old_seconds or status != old_status) {
old_percent = percent; old_percent = percent;
old_watts = watts;
old_seconds = seconds; old_seconds = seconds;
old_status = status; old_status = status;
const string str_time = (seconds > 0 ? sec_to_dhms(seconds, true, true) : ""); const string str_time = (seconds > 0 ? sec_to_dhms(seconds, true, true) : "");
const string str_percent = to_string(percent) + '%'; const string str_percent = to_string(percent) + '%';
const string str_watts = (watts != -1 and Config::getB("show_battery_watts") ? fmt::format("{:.2f}", watts) + 'W' : "");
const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown")); const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown"));
const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + to_string(Config::getI("update_ms")).size(); const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + str_watts.size() + to_string(Config::getI("update_ms")).size();
const int current_pos = Term::width - current_len - 17; const int current_pos = Term::width - current_len - 17;
if ((bat_pos != current_pos or bat_len != current_len) and bat_pos > 0 and not redraw) if ((bat_pos != current_pos or bat_len != current_len) and bat_pos > 0 and not redraw)
@ -733,7 +737,7 @@ namespace Cpu {
out += Mv::to(y, bat_pos) + title_left + Theme::c("title") + Fx::b + "BAT" + bat_symbol + ' ' + str_percent out += Mv::to(y, bat_pos) + title_left + Theme::c("title") + Fx::b + "BAT" + bat_symbol + ' ' + str_percent
+ (Term::width >= 100 ? Fx::ub + ' ' + bat_meter(percent) + Fx::b : "") + (Term::width >= 100 ? Fx::ub + ' ' + bat_meter(percent) + Fx::b : "")
+ (not str_time.empty() ? ' ' + Theme::c("title") + str_time : " ") + Fx::ub + title_right; + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : "") + (not str_watts.empty() ? " " + Theme::c("title") + Fx::b + str_watts : "") + Fx::ub + title_right;
} }
} }
else if (bat_pos > 0) { else if (bat_pos > 0) {
@ -749,7 +753,7 @@ namespace Cpu {
if (graph_field.starts_with("gpu")) if (graph_field.starts_with("gpu"))
if (graph_field.find("totals") != string::npos) if (graph_field.find("totals") != string::npos)
for (unsigned long i = 0;;) { for (unsigned long i = 0;;) {
out += graphs[i](gpus[i].gpu_percent.at(graph_field), (data_same or redraw)); out += graphs[i](safeVal(gpus[i].gpu_percent, graph_field), (data_same or redraw));
if (gpus.size() > 1) { if (gpus.size() > 1) {
auto i_str = to_string(i); auto i_str = to_string(i);
out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str
@ -761,13 +765,13 @@ namespace Cpu {
else break; else break;
} }
else else
out += graphs[0](Gpu::shared_gpu_percent.at(graph_field), (data_same or redraw)); out += graphs[0](safeVal(Gpu::shared_gpu_percent, graph_field), (data_same or redraw));
else else
#else #else
(void)graph_height; (void)graph_height;
(void)graph_width; (void)graph_width;
#endif #endif
out += graphs[0](cpu.cpu_percent.at(graph_field), (data_same or redraw)); out += graphs[0](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw));
}; };
draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field);
@ -792,14 +796,14 @@ namespace Cpu {
out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size())
+ Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right;
out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back()) out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back())
+ Theme::g("cpu").at(clamp(cpu.cpu_percent.at("total").back(), 0ll, 100ll)) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; + Theme::g("cpu").at(clamp(safeVal(cpu.cpu_percent, "total"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(cpu.cpu_percent, "total"s).back()), 4) + Theme::c("main_fg") + '%';
if (show_temps) { if (show_temps) {
const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale);
const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll));
if (b_column_size > 1 or b_columns > 1) if ((b_column_size > 1 or b_columns > 1) and temp_graphs.size() >= 1ll)
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color
+ temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw); + temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw);
out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
} }
out += Theme::c("div_line") + Symbols::v_line; out += Theme::c("div_line") + Symbols::v_line;
@ -810,21 +814,22 @@ namespace Cpu {
int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3); int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3);
if (Shared::coreCount >= 100) core_width++; if (Shared::coreCount >= 100) core_width++;
for (const auto& n : iota(0, Shared::coreCount)) { for (const auto& n : iota(0, Shared::coreCount)) {
if (cmp_less(core_graphs.size(), n+1)) break;
out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "") out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "")
+ ljust(to_string(n), core_width); + ljust(to_string(n), core_width);
if (b_column_size > 0 or extra_width > 0) if (b_column_size > 0 or extra_width > 0)
out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width)
+ core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); + core_graphs.at(n)(safeVal(cpu.core_percent, n), data_same or redraw);
out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll));
out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%';
if (show_temps and not hide_cores) { if (show_temps and not hide_cores and std::cmp_greater_equal(temp_graphs.size(), n)) {
const auto [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale); const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale);
const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll));
if (b_column_size > 1) if (b_column_size > 1)
out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5)
+ temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw); + temp_graphs.at(n+1)(safeVal(cpu.temp, n+1), data_same or redraw);
out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit;
} }
@ -882,14 +887,14 @@ namespace Cpu {
} }
if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9)); if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9));
if (gpus[i].supported_functions.gpu_utilization) { if (gpus[i].supported_functions.gpu_utilization) {
string meter = gpu_meters[i](gpus[i].gpu_percent.at("gpu-totals").back()); string meter = gpu_meters[i](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back());
out += (meter.size() > 1 ? " " : "") + meter out += (meter.size() > 1 ? " " : "") + meter
+ Theme::g("cpu").at(gpus[i].gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpus[i].gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; + Theme::g("cpu").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%';
} else out += Mv::r(gpu_meter_width); } else out += Mv::r(gpu_meter_width);
if (gpus[i].supported_functions.mem_used) { if (gpus[i].supported_functions.mem_used) {
out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(gpus[i].gpu_percent.at("gpu-vram-totals").back()) out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s).back())
+ gpu_mem_graphs[i](gpus[i].gpu_percent.at("gpu-vram-totals"), data_same or redraw) + Theme::c("main_fg") + gpu_mem_graphs[i](safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), data_same or redraw) + Theme::c("main_fg")
+ rjust(floating_humanizer(gpus[i].mem_used, true), 5); + rjust(floating_humanizer(gpus[i].mem_used, true), 5);
if (gpus[i].supported_functions.mem_total) if (gpus[i].supported_functions.mem_total)
out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true); out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true);
@ -967,12 +972,12 @@ namespace Gpu {
out += box[index]; out += box[index];
if (gpu.supported_functions.gpu_utilization) { if (gpu.supported_functions.gpu_utilization) {
graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", gpu.gpu_percent.at("gpu-totals"), graph_symbol, false, true}; // TODO cpu -> gpu graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, false, true}; // TODO cpu -> gpu
if (not single_graph) { if (not single_graph) {
graph_lower = Draw::Graph{ graph_lower = Draw::Graph{
x + width - b_width - 3, x + width - b_width - 3,
graph_low_height, "cpu", graph_low_height, "cpu",
gpu.gpu_percent.at("gpu-totals"), safeVal(gpu.gpu_percent, "gpu-totals"s),
graph_symbol, graph_symbol,
Config::getB("cpu_invert_lower"), true Config::getB("cpu_invert_lower"), true
}; };
@ -986,7 +991,7 @@ namespace Gpu {
if (gpu.supported_functions.mem_utilization) if (gpu.supported_functions.mem_utilization)
mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization
if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total)
mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol}; mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol};
} }
@ -994,12 +999,12 @@ namespace Gpu {
//? Gpu graph, meter & clock speed //? Gpu graph, meter & clock speed
if (gpu.supported_functions.gpu_utilization) { if (gpu.supported_functions.gpu_utilization) {
out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index]));
if (not single_graph) if (not single_graph)
out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index]));
out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(gpu.gpu_percent.at("gpu-totals").back()) out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(safeVal(gpu.gpu_percent, "gpu-totals"s).back())
+ Theme::g("cpu").at(gpu.gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpu.gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; + Theme::g("cpu").at(clamp(safeVal(gpu.gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%';
//? Temperature graph, I assume the device supports utilization if it supports temperature //? Temperature graph, I assume the device supports utilization if it supports temperature
if (show_temps) { if (show_temps) {
@ -1019,10 +1024,10 @@ namespace Gpu {
//? Power usage meter, power state //? Power usage meter, power state
if (gpu.supported_functions.pwr_usage) { if (gpu.supported_functions.pwr_usage) {
out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(gpu.gpu_percent.at("gpu-pwr-totals").back()) out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back())
+ Theme::g("cached").at(gpu.gpu_percent.at("gpu-pwr-totals").back()) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; + Theme::g("cached").at(clamp(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W';
if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card
out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(gpu.pwr_state) + to_string(gpu.pwr_state); out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state);
} }
if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) { if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) {
@ -1038,9 +1043,9 @@ namespace Gpu {
+ Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up + Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up
+ Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line") + Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line")
+ Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right + Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right
+ Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(gpu.gpu_percent.at("gpu-vram-totals"), (data_same or redraw[index])) + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(safeVal(gpu.gpu_percent, "gpu-vram-totals"s), (data_same or redraw[index]))
+ Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub + Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub
+ Mv::r(3) + rjust(to_string(gpu.gpu_percent.at("gpu-vram-totals").back()), 3) + '%'; + Mv::r(3) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%';
//? Memory utilization //? Memory utilization
if (gpu.supported_functions.mem_utilization) if (gpu.supported_functions.mem_utilization)
@ -1096,11 +1101,11 @@ namespace Mem {
int disks_io_half = 0; int disks_io_half = 0;
bool shown = true, redraw = true; bool shown = true, redraw = true;
string box; string box;
unordered_flat_map<string, Draw::Meter> mem_meters; std::unordered_map<string, Draw::Meter> mem_meters;
unordered_flat_map<string, Draw::Graph> mem_graphs; std::unordered_map<string, Draw::Graph> mem_graphs;
unordered_flat_map<string, Draw::Meter> disk_meters_used; std::unordered_map<string, Draw::Meter> disk_meters_used;
unordered_flat_map<string, Draw::Meter> disk_meters_free; std::unordered_map<string, Draw::Meter> disk_meters_free;
unordered_flat_map<string, Draw::Graph> io_graphs; std::unordered_map<string, Draw::Graph> io_graphs;
string draw(const mem_info& mem, bool force_redraw, bool data_same) { string draw(const mem_info& mem, bool force_redraw, bool data_same) {
if (Runner::stopping) return ""; if (Runner::stopping) return "";
@ -1132,14 +1137,14 @@ namespace Mem {
for (const auto& name : mem_names) { for (const auto& name : mem_names) {
if (use_graphs) if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name), graph_symbol}; mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, safeVal(mem.percent, name), graph_symbol};
else else
mem_meters[name] = Draw::Meter{mem_meter, name}; mem_meters[name] = Draw::Meter{mem_meter, name};
} }
if (show_swap and has_swap) { if (show_swap and has_swap) {
for (const auto& name : swap_names) { for (const auto& name : swap_names) {
if (use_graphs) if (use_graphs)
mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name), graph_symbol}; mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), safeVal(mem.percent, name), graph_symbol};
else else
mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)};
} }
@ -1148,7 +1153,7 @@ namespace Mem {
//? Disk meters and io graphs //? Disk meters and io graphs
if (show_disks) { if (show_disks) {
if (show_io_stat or io_mode) { if (show_io_stat or io_mode) {
unordered_flat_map<string, int> custom_speeds; std::unordered_map<string, int> custom_speeds;
int half_height = 0; int half_height = 0;
if (io_mode) { if (io_mode) {
disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2)); disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2));
@ -1230,7 +1235,7 @@ namespace Mem {
if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider; if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider;
cy += 1; cy += 1;
} }
out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(mem.stats.at("swap_total")), mem_width - 8) out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(safeVal(mem.stats, "swap_total"s)), mem_width - 8)
+ Theme::c("main_fg") + Fx::ub; + Theme::c("main_fg") + Fx::ub;
cy += 1; cy += 1;
title = "Used"; title = "Used";
@ -1239,13 +1244,16 @@ namespace Mem {
title = "Free"; title = "Free";
if (title.empty()) title = capitalize(name); if (title.empty()) title = capitalize(name);
const string humanized = floating_humanizer(mem.stats.at(name)); const string humanized = floating_humanizer(safeVal(mem.stats, name));
const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0);
const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back())); const string graphics = (
use_graphs and mem_graphs.contains(name) ? mem_graphs.at(name)(safeVal(mem.percent, name), redraw or data_same)
: mem_meters.contains(name) ? mem_meters.at(name)(safeVal(mem.percent, name).back())
: "");
if (mem_size > 2) { if (mem_size > 2) {
out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":"
+ Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized))
+ Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4); + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(safeVal(mem.percent, name).back()) + "%", 4);
cy += (graph_height == 0 ? 2 : graph_height + 1); cy += (graph_height == 0 ? 2 : graph_height + 1);
} }
else { else {
@ -1268,7 +1276,7 @@ namespace Mem {
for (const auto& mount : mem.disks_order) { for (const auto& mount : mem.disks_order) {
if (not disks.contains(mount)) continue; if (not disks.contains(mount)) continue;
if (cy > height - 3) break; if (cy > height - 3) break;
const auto& disk = disks.at(mount); const auto& disk = safeVal(disks, mount);
if (disk.io_read.empty()) continue; if (disk.io_read.empty()) continue;
const string total = floating_humanizer(disk.total, not big_disk); const string total = floating_humanizer(disk.total, not big_disk);
out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size()) out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size())
@ -1277,9 +1285,12 @@ namespace Mem {
const string used_percent = to_string(disk.used_percent); const string used_percent = to_string(disk.used_percent);
out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div;
} }
if (io_graphs.contains(mount + "_activity")) {
out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6)
+ Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg");
}
if (++cy > height - 3) break; if (++cy > height - 3) break;
if (not io_graphs.contains(mount)) continue;
if (io_graph_combined) { if (io_graph_combined) {
auto comb_val = disk.io_read.back() + disk.io_write.back(); auto comb_val = disk.io_read.back() + disk.io_write.back();
const string humanized = (disk.io_write.back() > 0 ? ""s : ""s) + (disk.io_read.back() > 0 ? ""s : ""s) const string humanized = (disk.io_write.back() > 0 ? ""s : ""s) + (disk.io_read.back() > 0 ? ""s : ""s)
@ -1304,7 +1315,8 @@ namespace Mem {
for (const auto& mount : mem.disks_order) { for (const auto& mount : mem.disks_order) {
if (not disks.contains(mount)) continue; if (not disks.contains(mount)) continue;
if (cy > height - 3) break; if (cy > height - 3) break;
const auto& disk = disks.at(mount); const auto& disk = safeVal(disks, mount);
if (disk.name.empty() or not disk_meters_used.contains(mount)) continue;
auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll);
const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? ""s : ""s) + (disk.io_read.back() > 0 and big_disk ? ""s : ""s) const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? ""s : ""s) + (disk.io_read.back() > 0 and big_disk ? ""s : ""s)
+ floating_humanizer(comb_val, true) : ""); + floating_humanizer(comb_val, true) : "");
@ -1328,7 +1340,7 @@ namespace Mem {
+ disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5));
if (++cy > height - 3) break; if (++cy > height - 3) break;
if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { if (disk_meters_free.contains(mount) and cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) {
out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' '
+ disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5));
cy++; cy++;
@ -1352,8 +1364,9 @@ namespace Net {
int x = 1, y, width = 20, height; int x = 1, y, width = 20, height;
int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height;
bool shown = true, redraw = true; bool shown = true, redraw = true;
const int MAX_IFNAMSIZ = 15;
string old_ip; string old_ip;
unordered_flat_map<string, Draw::Graph> graphs; std::unordered_map<string, Draw::Graph> graphs;
string box; string box;
string draw(const net_info& net, bool force_redraw, bool data_same) { string draw(const net_info& net, bool force_redraw, bool data_same) {
@ -1372,16 +1385,16 @@ namespace Net {
out.reserve(width * height); out.reserve(width * height);
const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left;
const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right;
const int i_size = min((int)selected_iface.size(), 10); const int i_size = min((int)selected_iface.size(), MAX_IFNAMSIZ);
const long long down_max = (net_auto ? graph_max.at("download") : ((long long)(Config::getI("net_download")) << 20) / 8); const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8);
const long long up_max = (net_auto ? graph_max.at("upload") : ((long long)(Config::getI("net_upload")) << 20) / 8); const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8);
//* Redraw elements not needed to be updated every cycle //* Redraw elements not needed to be updated every cycle
if (redraw) { if (redraw) {
out = box; out = box;
//? Graphs //? Graphs
graphs.clear(); graphs.clear();
if (net.bandwidth.at("download").empty() or net.bandwidth.at("upload").empty()) if (safeVal(net.bandwidth, "download"s).empty() or safeVal(net.bandwidth, "upload"s).empty())
return out + Fx::reset; return out + Fx::reset;
graphs["download"] = Draw::Graph{ graphs["download"] = Draw::Graph{
width - b_width - 2, u_graph_height, "download", width - b_width - 2, u_graph_height, "download",
@ -1394,8 +1407,8 @@ namespace Net {
//? Interface selector and buttons //? Interface selector and buttons
out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "<b " + Theme::c("title") out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "<b " + Theme::c("title")
+ uresize(selected_iface, 10) + Theme::c("hi_fg") + " n>" + title_right + uresize(selected_iface, MAX_IFNAMSIZ) + Theme::c("hi_fg") + " n>" + title_right
+ Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (net.stat.at("download").offset + net.stat.at("upload").offset > 0 ? Fx::b : "") + 'z' + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z'
+ Theme::c("title") + "ero" + title_right; + Theme::c("title") + "ero" + title_right;
Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3};
Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3};
@ -1419,13 +1432,13 @@ namespace Net {
//? Graphs and stats //? Graphs and stats
int cy = 0; int cy = 0;
for (const string dir : {"download", "upload"}) { for (const string dir : {"download", "upload"}) {
out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(net.bandwidth.at(dir), redraw or data_same or not net.connected) out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected)
+ Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text")
+ floating_humanizer((dir == "upload" ? up_max : down_max), true); + floating_humanizer((dir == "upload" ? up_max : down_max), true);
const string speed = floating_humanizer(net.stat.at(dir).speed, false, 0, false, true); const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true);
const string speed_bits = (b_width >= 20 ? floating_humanizer(net.stat.at(dir).speed, false, 0, true, true) : ""); const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : "");
const string top = floating_humanizer(net.stat.at(dir).top, false, 0, true, true); const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true);
const string total = floating_humanizer(net.stat.at(dir).total); const string total = floating_humanizer(safeVal(net.stat, dir).total);
const string symbol = (dir == "upload" ? "" : ""); const string symbol = (dir == "upload" ? "" : "");
out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : ""); out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : "");
cy += (b_height == 5 ? 2 : 1); cy += (b_height == 5 ? 2 : 1);
@ -1453,9 +1466,9 @@ namespace Proc {
bool shown = true, redraw = true; bool shown = true, redraw = true;
int selected_pid = 0, selected_depth = 0; int selected_pid = 0, selected_depth = 0;
string selected_name; string selected_name;
unordered_flat_map<size_t, Draw::Graph> p_graphs; std::unordered_map<size_t, Draw::Graph> p_graphs;
unordered_flat_map<size_t, bool> p_wide_cmd; std::unordered_map<size_t, bool> p_wide_cmd;
unordered_flat_map<size_t, int> p_counters; std::unordered_map<size_t, int> p_counters;
int counter = 0; int counter = 0;
Draw::TextEdit filter; Draw::TextEdit filter;
Draw::Graph detailed_cpu_graph; Draw::Graph detailed_cpu_graph;
@ -1790,11 +1803,11 @@ namespace Proc {
p_counters[p.pid] = 0; p_counters[p.pid] = 0;
} }
else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) { else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) {
p_graphs.erase(p.pid); if (p_graphs.contains(p.pid)) p_graphs.erase(p.pid);
p_counters.erase(p.pid); p_counters.erase(p.pid);
} }
else else
p_counters.at(p.pid) = 0; p_counters[p.pid] = 0;
} }
out += Fx::reset; out += Fx::reset;
@ -1918,25 +1931,18 @@ namespace Proc {
//? Clear out left over graphs from dead processes at a regular interval //? Clear out left over graphs from dead processes at a regular interval
if (not data_same and ++counter >= 100) { if (not data_same and ++counter >= 100) {
counter = 0; counter = 0;
for (auto element = p_graphs.begin(); element != p_graphs.end();) {
if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) {
element = p_graphs.erase(element);
p_counters.erase(element->first);
}
else
++element;
}
p_graphs.compact();
p_counters.compact();
for (auto element = p_wide_cmd.begin(); element != p_wide_cmd.end();) { std::erase_if(p_graphs, [&](const auto& pair) {
if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { return rng::find(plist, pair.first, &proc_info::pid) == plist.end();
element = p_wide_cmd.erase(element); });
}
else std::erase_if(p_counters, [&](const auto& pair) {
++element; return rng::find(plist, pair.first, &proc_info::pid) == plist.end();
} });
p_wide_cmd.compact();
std::erase_if(p_wide_cmd, [&](const auto& pair) {
return rng::find(plist, pair.first, &proc_info::pid) == plist.end();
});
} }
if (selected == 0 and selected_pid != 0) { if (selected == 0 and selected_pid != 0) {
@ -2008,7 +2014,7 @@ namespace Draw {
#ifdef GPU_SUPPORT #ifdef GPU_SUPPORT
const bool show_gpu_on = Config::getS("show_gpu_info") == "On"; const bool show_gpu_on = Config::getS("show_gpu_info") == "On";
const bool gpus_shown_in_cpu_panel = Gpu::gpu_names.size() > 0 and ( const bool gpus_shown_in_cpu_panel = Gpu::gpu_names.size() > 0 and (
show_gpu_on or (Config::getS("cpu_graph_lower") == "Auto" and Gpu::shown == 0) show_gpu_on or (Config::getS("show_gpu_info") == "Auto" and Gpu::shown == 0)
); );
const int gpus_height_offset = (Gpu::gpu_names.size() - Gpu::shown)*gpus_shown_in_cpu_panel; const int gpus_height_offset = (Gpu::gpu_names.size() - Gpu::shown)*gpus_shown_in_cpu_panel;
int gpus_extra_height = gpus_shown_in_cpu_panel ? Gpu::gpu_names.size() - (show_gpu_on ? 0 : Gpu::shown) : 0; int gpus_extra_height = gpus_shown_in_cpu_panel ? Gpu::gpu_names.size() - (show_gpu_on ? 0 : Gpu::shown) : 0;
@ -2236,3 +2242,4 @@ namespace Draw {
} }
} }
} }

View file

@ -21,10 +21,9 @@ tab-size = 4
#include <string> #include <string>
#include <vector> #include <vector>
#include <array> #include <array>
#include <robin_hood.h> #include <unordered_map>
#include <deque> #include <deque>
using robin_hood::unordered_flat_map;
using std::array; using std::array;
using std::deque; using std::deque;
using std::string; using std::string;
@ -66,8 +65,8 @@ namespace Draw {
//* An editable text field //* An editable text field
class TextEdit { class TextEdit {
size_t pos{}; // defaults to 0 size_t pos{};
size_t upos{}; // defaults to 0 size_t upos{};
bool numeric; bool numeric;
public: public:
string text; string text;
@ -108,7 +107,7 @@ namespace Draw {
long long offset; long long offset;
long long last = 0, max_value = 0; long long last = 0, max_value = 0;
bool current = true, tty_mode = false; bool current = true, tty_mode = false;
unordered_flat_map<bool, vector<string>> graphs = { {true, {}}, {false, {}}}; std::unordered_map<bool, vector<string>> graphs = { {true, {}}, {false, {}}};
//* Create two representations of the graph to switch between to represent two values for each braille character //* Create two representations of the graph to switch between to represent two values for each braille character
void _create(const deque<long long>& data, int data_offset); void _create(const deque<long long>& data, int data_offset);
@ -135,6 +134,6 @@ namespace Draw {
namespace Proc { namespace Proc {
extern Draw::TextEdit filter; extern Draw::TextEdit filter;
extern unordered_flat_map<size_t, Draw::Graph> p_graphs; extern std::unordered_map<size_t, Draw::Graph> p_graphs;
extern unordered_flat_map<size_t, int> p_counters; extern std::unordered_map<size_t, int> p_counters;
} }

View file

@ -16,12 +16,13 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <iostream> #include <limits>
#include <ranges> #include <ranges>
#include <vector> #include <vector>
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <signal.h> #include <signal.h>
#include <sys/select.h>
#include <utility> #include <utility>
#include "btop_input.hpp" #include "btop_input.hpp"
@ -31,17 +32,6 @@ tab-size = 4
#include "btop_menu.hpp" #include "btop_menu.hpp"
#include "btop_draw.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 Tools;
using namespace std::literals; // for operator""s using namespace std::literals; // for operator""s
namespace rng = std::ranges; namespace rng = std::ranges;
@ -49,8 +39,9 @@ namespace rng = std::ranges;
namespace Input { namespace Input {
//* Map for translating key codes to readable values //* Map for translating key codes to readable values
const unordered_flat_map<string, string> Key_escapes = { const std::unordered_map<string, string> Key_escapes = {
{"\033", "escape"}, {"\033", "escape"},
{"\x12", "ctrl_r"},
{"\n", "enter"}, {"\n", "enter"},
{" ", "space"}, {" ", "space"},
{"\x7f", "backspace"}, {"\x7f", "backspace"},
@ -89,83 +80,45 @@ namespace Input {
{"[24~", "f12"} {"[24~", "f12"}
}; };
std::atomic<bool> interrupt (false); sigset_t signal_mask;
std::atomic<bool> polling (false); std::atomic<bool> polling (false);
array<int, 2> mouse_pos; array<int, 2> mouse_pos;
unordered_flat_map<string, Mouse_loc> mouse_mappings; std::unordered_map<string, Mouse_loc> mouse_mappings;
deque<string> history(50, ""); deque<string> history(50, "");
string old_filter; string old_filter;
string input;
struct InputThr { bool poll(const uint64_t timeout) {
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<std::mutex> g(lock);
current.push_back(ch);
if (current.size() > 100) {
current.clear();
}
}
}
size_t avail() {
std::lock_guard<std::mutex> g(lock);
return current.size();
}
std::string get() {
std::string res;
{
std::lock_guard<std::mutex> 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) {
atomic_lock lck(polling); atomic_lock lck(polling);
if (timeout < 1) return InputThr::instance().avail() > 0; fd_set fds;
while (timeout > 0) { FD_ZERO(&fds);
if (interrupt) { FD_SET(STDIN_FILENO, &fds);
interrupt = false; struct timespec wait;
return false; struct timespec *waitptr = nullptr;
}
if (InputThr::instance().avail() > 0) return true; if(timeout != std::numeric_limits<uint64_t>::max()) {
sleep_ms(timeout < 10 ? timeout : 10); wait.tv_sec = timeout / 1000;
timeout -= 10; 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; return false;
} }
string get() { string get() {
string key = InputThr::instance().get(); string key = input;
if (not key.empty()) { if (not key.empty()) {
//? Remove escape code prefix if present //? Remove escape code prefix if present
if (key.substr(0, 2) == Fx::e) { if (key.substr(0, 2) == Fx::e) {
@ -238,12 +191,14 @@ namespace Input {
} }
string wait() { string wait() {
while (InputThr::instance().avail() < 1) { while(not poll(std::numeric_limits<uint64_t>::max())) {}
sleep_ms(10);
}
return get(); return get();
} }
void interrupt() {
kill(getpid(), SIGUSR1);
}
void clear() { void clear() {
// do not need it, actually // do not need it, actually
} }
@ -304,8 +259,10 @@ namespace Input {
Draw::calcSizes(); Draw::calcSizes();
Runner::run("all", false, true); Runner::run("all", false, true);
return; return;
} } else if (is_in(key, "ctrl_r")) {
else kill(getpid(), SIGUSR2);
return;
} else
keep_going = true; keep_going = true;
if (not keep_going) return; if (not keep_going) return;

View file

@ -21,18 +21,17 @@ tab-size = 4
#include <string> #include <string>
#include <atomic> #include <atomic>
#include <array> #include <array>
#include <robin_hood.h> #include <unordered_map>
#include <deque> #include <deque>
using robin_hood::unordered_flat_map;
using std::array; using std::array;
using std::atomic; using std::atomic;
using std::deque; using std::deque;
using std::string; using std::string;
/* The input functions relies on the following std::cin options being set: /* The input functions rely on the following termios parameters being set:
cin.sync_with_stdio(false); Non-canonical mode (c_lflags & ~(ICANON))
cin.tie(nullptr); VMIN and VTIME (c_cc) set to 0
These will automatically be set when running Term::init() from btop_tools.cpp These will automatically be set when running Term::init() from btop_tools.cpp
*/ */
@ -44,9 +43,11 @@ namespace Input {
}; };
//? line, col, height, width //? line, col, height, width
extern unordered_flat_map<string, Mouse_loc> mouse_mappings; extern std::unordered_map<string, Mouse_loc> mouse_mappings;
//* Signal mask used during polling read
extern sigset_t signal_mask;
extern atomic<bool> interrupt;
extern atomic<bool> polling; extern atomic<bool> polling;
//* Mouse column and line position //* Mouse column and line position
@ -55,8 +56,8 @@ namespace Input {
//* Last entered key //* Last entered key
extern deque<string> history; extern deque<string> history;
//* Poll keyboard & mouse input for <timeout> ms and return input availabilty as a bool //* Poll keyboard & mouse input for <timeout> ms and return input availability as a bool
bool poll(int timeout=0); bool poll(const uint64_t timeout=0);
//* Get a key or mouse action from input //* Get a key or mouse action from input
string get(); string get();
@ -64,6 +65,9 @@ namespace Input {
//* Wait until input is available and return key //* Wait until input is available and return key
string wait(); string wait();
//* Interrupt poll/wait
void interrupt();
//* Clears last entered key //* Clears last entered key
void clear(); void clear();

View file

@ -17,7 +17,7 @@ tab-size = 4
*/ */
#include <deque> #include <deque>
#include <robin_hood.h> #include <unordered_map>
#include <array> #include <array>
#include <signal.h> #include <signal.h>
#include <errno.h> #include <errno.h>
@ -31,7 +31,6 @@ tab-size = 4
#include "btop_draw.hpp" #include "btop_draw.hpp"
#include "btop_shared.hpp" #include "btop_shared.hpp"
using robin_hood::unordered_flat_map;
using std::array; using std::array;
using std::ceil; using std::ceil;
using std::max; using std::max;
@ -50,8 +49,8 @@ namespace Menu {
bool redraw{true}; bool redraw{true};
int currentMenu = -1; int currentMenu = -1;
msgBox messageBox; msgBox messageBox;
int signalToSend{}; // defaults to 0 int signalToSend{};
int signalKillRet{}; // defaults to 0 int signalKillRet{};
const array<string, 32> P_Signals = { const array<string, 32> P_Signals = {
"0", "0",
@ -123,7 +122,7 @@ namespace Menu {
#endif #endif
}; };
unordered_flat_map<string, Input::Mouse_loc> mouse_mappings; std::unordered_map<string, Input::Mouse_loc> mouse_mappings;
const array<array<string, 3>, 3> menu_normal = { const array<array<string, 3>, 3> menu_normal = {
array<string, 3>{ array<string, 3>{
@ -178,6 +177,7 @@ namespace Menu {
{"F2, o", "Shows options."}, {"F2, o", "Shows options."},
{"F1, ?, h", "Shows this window."}, {"F1, ?, h", "Shows this window."},
{"ctrl + z", "Sleep program and put in background."}, {"ctrl + z", "Sleep program and put in background."},
{"ctrl + r", "Reloads config file from disk."},
{"q, ctrl + c", "Quits program."}, {"q, ctrl + c", "Quits program."},
{"+, -", "Add/Subtract 100ms to/from update timer."}, {"+, -", "Add/Subtract 100ms to/from update timer."},
{"Up, Down", "Select in process list."}, {"Up, Down", "Select in process list."},
@ -354,6 +354,11 @@ namespace Menu {
"Can be both batteries and UPS.", "Can be both batteries and UPS.",
"", "",
"\"Auto\" for auto detection."}, "\"Auto\" for auto detection."},
{"show_battery_watts",
"Show battery power.",
"",
"Show discharge power when discharging.",
"Show charging power when charging."},
{"log_level", {"log_level",
"Set loglevel for error.log", "Set loglevel for error.log",
"", "",
@ -485,7 +490,7 @@ namespace Menu {
"Kelvin, 0 = absolute zero, 1 degree change", "Kelvin, 0 = absolute zero, 1 degree change",
"equals 1 degree change in Celsius.", "equals 1 degree change in Celsius.",
"", "",
"Rankine, 0 = abosulte zero, 1 degree change", "Rankine, 0 = absolute zero, 1 degree change",
"equals 1 degree change in Fahrenheit."}, "equals 1 degree change in Fahrenheit."},
{"show_cpu_freq", {"show_cpu_freq",
"Show CPU frequency.", "Show CPU frequency.",
@ -646,7 +651,7 @@ namespace Menu {
"", "",
"Begin line with \"exclude=\" to change to", "Begin line with \"exclude=\" to change to",
"exclude filter.", "exclude filter.",
"Oterwise defaults to \"most include\" filter.", "Otherwise defaults to \"most include\" filter.",
"", "",
"Example:", "Example:",
"\"exclude=/boot /home/user\""}, "\"exclude=/boot /home/user\""},
@ -869,8 +874,8 @@ namespace Menu {
int signalChoose(const string& key) { int signalChoose(const string& key) {
auto s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid")); auto s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
static int x{}; // defaults to 0 static int x{};
static int y{}; // defaults to 0 static int y{};
static int selected_signal = -1; static int selected_signal = -1;
if (bg.empty()) selected_signal = -1; if (bg.empty()) selected_signal = -1;
@ -1071,8 +1076,8 @@ namespace Menu {
int mainMenu(const string& key) { int mainMenu(const string& key) {
enum MenuItems { Options, Help, Quit }; enum MenuItems { Options, Help, Quit };
static int y{}; // defaults to 0 static int y{};
static int selected{}; // defaults to 0 static int selected{};
static vector<string> colors_selected; static vector<string> colors_selected;
static vector<string> colors_normal; static vector<string> colors_normal;
auto tty_mode = Config::getB("tty_mode"); auto tty_mode = Config::getB("tty_mode");
@ -1150,22 +1155,22 @@ namespace Menu {
int optionsMenu(const string& key) { int optionsMenu(const string& key) {
enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable}; enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable};
static int y{}; // defaults to 0 static int y{};
static int x{}; // defaults to 0 static int x{};
static int height{}; // defaults to 0 static int height{};
static int page{}; // defaults to 0 static int page{};
static int pages{}; // defaults to 0 static int pages{};
static int selected{}; // defaults to 0 static int selected{};
static int select_max{}; // defaults to 0 static int select_max{};
static int item_height{}; // defaults to 0 static int item_height{};
static int selected_cat{}; // defaults to 0 static int selected_cat{};
static int max_items{}; // defaults to 0 static int max_items{};
static int last_sel{}; // defaults to 0 static int last_sel{};
static bool editing{}; // defaults to false static bool editing{};
static Draw::TextEdit editor; static Draw::TextEdit editor;
static string warnings; static string warnings;
static bitset<8> selPred; static bitset<8> selPred;
static const unordered_flat_map<string, std::reference_wrapper<const vector<string>>> optionsList = { static const std::unordered_map<string, std::reference_wrapper<const vector<string>>> optionsList = {
{"color_theme", std::cref(Theme::themes)}, {"color_theme", std::cref(Theme::themes)},
{"log_level", std::cref(Logger::log_levels)}, {"log_level", std::cref(Logger::log_levels)},
{"temp_scale", std::cref(Config::temp_scales)}, {"temp_scale", std::cref(Config::temp_scales)},
@ -1197,9 +1202,9 @@ namespace Menu {
Theme::updateThemes(); Theme::updateThemes();
} }
int retval = Changed; int retval = Changed;
bool recollect{}; // defaults to false bool recollect{};
bool screen_redraw{}; // defaults to false bool screen_redraw{};
bool theme_refresh{}; // defaults to false bool theme_refresh{};
//? Draw background if needed else process input //? Draw background if needed else process input
if (redraw) { if (redraw) {
@ -1501,11 +1506,11 @@ namespace Menu {
} }
int helpMenu(const string& key) { int helpMenu(const string& key) {
static int y{}; // defaults to 0 static int y{};
static int x{}; // defaults to 0 static int x{};
static int height{}; // defaults to 0 static int height{};
static int page{}; // defaults to 0 static int page{};
static int pages{}; // defaults to 0 static int pages{};
if (bg.empty()) page = 0; if (bg.empty()) page = 0;
int retval = Changed; int retval = Changed;

View file

@ -38,7 +38,7 @@ namespace Menu {
extern bool redraw; extern bool redraw;
//? line, col, height, width //? line, col, height, width
extern unordered_flat_map<string, Input::Mouse_loc> mouse_mappings; extern std::unordered_map<string, Input::Mouse_loc> mouse_mappings;
//* Creates a message box centered on screen //* Creates a message box centered on screen
//? Height of box is determined by size of content vector //? Height of box is determined by size of content vector
@ -46,12 +46,12 @@ namespace Menu {
//? Strings in content vector is not checked for box width overflow //? Strings in content vector is not checked for box width overflow
class msgBox { class msgBox {
string box_contents, button_left, button_right; string box_contents, button_left, button_right;
int height{}; // defaults to 0 int height{};
int width{}; // defaults to 0 int width{};
int boxtype{}; // defaults to 0 int boxtype{};
int selected{}; // defaults to 0 int selected{};
int x{}; // defaults to 0 int x{};
int y{}; // defaults to 0 int y{};
public: public:
enum BoxTypes { OK, YES_NO, NO_YES }; enum BoxTypes { OK, YES_NO, NO_YES };
enum msgReturn { enum msgReturn {

View file

@ -29,7 +29,7 @@ using namespace Tools;
namespace Gpu { namespace Gpu {
vector<string> gpu_names; vector<string> gpu_names;
vector<int> gpu_b_height_offsets; vector<int> gpu_b_height_offsets;
unordered_flat_map<string, deque<long long>> shared_gpu_percent = { std::unordered_map<string, deque<long long>> shared_gpu_percent = {
{"gpu-average", {}}, {"gpu-average", {}},
{"gpu-vram-total", {}}, {"gpu-vram-total", {}},
{"gpu-pwr-total", {}}, {"gpu-pwr-total", {}},

View file

@ -25,11 +25,19 @@ tab-size = 4
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include <ifaddrs.h> #include <unordered_map>
#include <robin_hood.h>
#include <unistd.h> #include <unistd.h>
using robin_hood::unordered_flat_map; // From `man 3 getifaddrs`: <net/if.h> must be included before <ifaddrs.h>
// clang-format off
#include <net/if.h>
#include <ifaddrs.h>
// clang-format on
#if defined(__FreeBSD__) || defined(__OpenBSD__)
# include <kvm.h>
#endif
using std::array; using std::array;
using std::atomic; using std::atomic;
using std::deque; using std::deque;
@ -55,6 +63,7 @@ namespace Global {
extern string overlay; extern string overlay;
extern string clock; extern string clock;
extern uid_t real_uid, set_uid; extern uid_t real_uid, set_uid;
extern atomic<bool> init_conf;
} }
namespace Runner { namespace Runner {
@ -83,6 +92,15 @@ namespace Shared {
void init(); void init();
extern long coreCount, page_size, clk_tck; extern long coreCount, page_size, clk_tck;
#if defined(__FreeBSD__) || defined(__OpenBSD__)
struct KvmDeleter {
void operator()(kvm_t* handle) {
kvm_close(handle);
}
};
using KvmPtr = std::unique_ptr<kvm_t, KvmDeleter>;
#endif
} }
@ -98,7 +116,7 @@ namespace Gpu {
extern vector<int> gpu_b_height_offsets; extern vector<int> gpu_b_height_offsets;
extern long long gpu_pwr_total_max; extern long long gpu_pwr_total_max;
extern unordered_flat_map<string, deque<long long>> shared_gpu_percent; // averages, power/vram total extern std::unordered_map<string, deque<long long>> shared_gpu_percent; // averages, power/vram total
const array mem_names { "used"s, "free"s }; const array mem_names { "used"s, "free"s };
@ -124,7 +142,7 @@ namespace Gpu {
//* Per-device container for GPU info //* Per-device container for GPU info
struct gpu_info { struct gpu_info {
unordered_flat_map<string, deque<long long>> gpu_percent = { std::unordered_map<string, deque<long long>> gpu_percent = {
{"gpu-totals", {}}, {"gpu-totals", {}},
{"gpu-vram-totals", {}}, {"gpu-vram-totals", {}},
{"gpu-pwr-totals", {}}, {"gpu-pwr-totals", {}},
@ -178,10 +196,10 @@ namespace Cpu {
extern string cpuName, cpuHz; extern string cpuName, cpuHz;
extern vector<string> available_fields; extern vector<string> available_fields;
extern vector<string> available_sensors; extern vector<string> available_sensors;
extern tuple<int, long, string> current_bat; extern tuple<int, float, long, string> current_bat;
struct cpu_info { struct cpu_info {
unordered_flat_map<string, deque<long long>> cpu_percent = { std::unordered_map<string, deque<long long>> cpu_percent = {
{"total", {}}, {"total", {}},
{"user", {}}, {"user", {}},
{"nice", {}}, {"nice", {}},
@ -207,13 +225,13 @@ namespace Cpu {
string draw(const cpu_info& cpu, const vector<Gpu::gpu_info>& gpu, bool force_redraw = false, bool data_same = false); string draw(const cpu_info& cpu, const vector<Gpu::gpu_info>& gpu, bool force_redraw = false, bool data_same = false);
//* Parse /proc/cpu info for mapping of core ids //* Parse /proc/cpu info for mapping of core ids
auto get_core_mapping() -> unordered_flat_map<int, int>; auto get_core_mapping() -> std::unordered_map<int, int>;
extern unordered_flat_map<int, int> core_mapping; extern std::unordered_map<int, int> core_mapping;
auto get_cpuHz() -> string; auto get_cpuHz() -> string;
//* Get battery info from /sys //* Get battery info from /sys
auto get_battery() -> tuple<int, long, string>; auto get_battery() -> tuple<int, float, long, string>;
} }
namespace Mem { namespace Mem {
@ -229,11 +247,11 @@ namespace Mem {
string name; string name;
string fstype{}; // defaults to "" string fstype{}; // defaults to ""
std::filesystem::path stat{}; // defaults to "" std::filesystem::path stat{}; // defaults to ""
int64_t total{}; // defaults to 0 int64_t total{};
int64_t used{}; // defaults to 0 int64_t used{};
int64_t free{}; // defaults to 0 int64_t free{};
int used_percent{}; // defaults to 0 int used_percent{};
int free_percent{}; // defaults to 0 int free_percent{};
array<int64_t, 3> old_io = {0, 0, 0}; array<int64_t, 3> old_io = {0, 0, 0};
deque<long long> io_read = {}; deque<long long> io_read = {};
@ -242,13 +260,13 @@ namespace Mem {
}; };
struct mem_info { struct mem_info {
unordered_flat_map<string, uint64_t> stats = std::unordered_map<string, uint64_t> stats =
{{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0},
{"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}};
unordered_flat_map<string, deque<long long>> percent = std::unordered_map<string, deque<long long>> percent =
{{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}},
{"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}};
unordered_flat_map<string, disk_info> disks; std::unordered_map<string, disk_info> disks;
vector<string> disks_order; vector<string> disks_order;
}; };
@ -270,26 +288,37 @@ namespace Net {
extern string selected_iface; extern string selected_iface;
extern vector<string> interfaces; extern vector<string> interfaces;
extern bool rescale; extern bool rescale;
extern unordered_flat_map<string, uint64_t> graph_max; extern std::unordered_map<string, uint64_t> graph_max;
struct net_stat { struct net_stat {
uint64_t speed{}; // defaults to 0 uint64_t speed{};
uint64_t top{}; // defaults to 0 uint64_t top{};
uint64_t total{}; // defaults to 0 uint64_t total{};
uint64_t last{}; // defaults to 0 uint64_t last{};
uint64_t offset{}; // defaults to 0 uint64_t offset{};
uint64_t rollover{}; // defaults to 0 uint64_t rollover{};
}; };
struct net_info { struct net_info {
unordered_flat_map<string, deque<long long>> bandwidth = { {"download", {}}, {"upload", {}} }; std::unordered_map<string, deque<long long>> bandwidth = { {"download", {}}, {"upload", {}} };
unordered_flat_map<string, net_stat> stat = { {"download", {}}, {"upload", {}} }; std::unordered_map<string, net_stat> stat = { {"download", {}}, {"upload", {}} };
string ipv4{}; // defaults to "" string ipv4{}; // defaults to ""
string ipv6{}; // defaults to "" string ipv6{}; // defaults to ""
bool connected{}; // defaults to false bool connected{};
}; };
extern unordered_flat_map<string, net_info> current_net; class IfAddrsPtr {
struct ifaddrs* ifaddr;
int status;
public:
IfAddrsPtr() { status = getifaddrs(&ifaddr); }
~IfAddrsPtr() { freeifaddrs(ifaddr); }
[[nodiscard]] constexpr auto operator()() -> struct ifaddrs* { return ifaddr; }
[[nodiscard]] constexpr auto get() -> struct ifaddrs* { return ifaddr; }
[[nodiscard]] constexpr auto get_status() const noexcept -> int { return status; };
};
extern std::unordered_map<string, net_info> current_net;
//* Collect net upload/download stats //* Collect net upload/download stats
auto collect(bool no_update=false) -> net_info&; auto collect(bool no_update=false) -> net_info&;
@ -322,7 +351,7 @@ namespace Proc {
}; };
//? Translation from process state char to explanative string //? Translation from process state char to explanative string
const unordered_flat_map<char, string> proc_states = { const std::unordered_map<char, string> proc_states = {
{'R', "Running"}, {'R', "Running"},
{'S', "Sleeping"}, {'S', "Sleeping"},
{'D', "Waiting"}, {'D', "Waiting"},
@ -338,32 +367,32 @@ namespace Proc {
//* Container for process information //* Container for process information
struct proc_info { struct proc_info {
size_t pid{}; // defaults to 0 size_t pid{};
string name{}; // defaults to "" string name{}; // defaults to ""
string cmd{}; // defaults to "" string cmd{}; // defaults to ""
string short_cmd{}; // defaults to "" string short_cmd{}; // defaults to ""
size_t threads{}; // defaults to 0 size_t threads{};
int name_offset{}; // defaults to 0 int name_offset{};
string user{}; // defaults to "" string user{}; // defaults to ""
uint64_t mem{}; // defaults to 0 uint64_t mem{};
double cpu_p{}; // defaults to = 0.0 double cpu_p{}; // defaults to = 0.0
double cpu_c{}; // defaults to = 0.0 double cpu_c{}; // defaults to = 0.0
char state = '0'; char state = '0';
int64_t p_nice{}; // defaults to 0 int64_t p_nice{};
uint64_t ppid{}; // defaults to 0 uint64_t ppid{};
uint64_t cpu_s{}; // defaults to 0 uint64_t cpu_s{};
uint64_t cpu_t{}; // defaults to 0 uint64_t cpu_t{};
string prefix{}; // defaults to "" string prefix{}; // defaults to ""
size_t depth{}; // defaults to 0 size_t depth{};
size_t tree_index{}; // defaults to 0 size_t tree_index{};
bool collapsed{}; // defaults to false bool collapsed{};
bool filtered{}; // defaults to false bool filtered{};
}; };
//* Container for process info box //* Container for process info box
struct detail_container { struct detail_container {
size_t last_pid{}; // defaults to 0 size_t last_pid{};
bool skip_smaps{}; // defaults to false bool skip_smaps{};
proc_info entry; proc_info entry;
string elapsed, parent, status, io_read, io_write, memory; string elapsed, parent, status, io_read, io_write, memory;
long long first_mem = -1; long long first_mem = -1;

View file

@ -42,11 +42,11 @@ namespace Theme {
fs::path theme_dir; fs::path theme_dir;
fs::path user_theme_dir; fs::path user_theme_dir;
vector<string> themes; vector<string> themes;
unordered_flat_map<string, string> colors; std::unordered_map<string, string> colors;
unordered_flat_map<string, array<int, 3>> rgbs; std::unordered_map<string, array<int, 3>> rgbs;
unordered_flat_map<string, array<string, 101>> gradients; std::unordered_map<string, array<string, 101>> gradients;
const unordered_flat_map<string, string> Default_theme = { const std::unordered_map<string, string> Default_theme = {
{ "main_bg", "#00" }, { "main_bg", "#00" },
{ "main_fg", "#cc" }, { "main_fg", "#cc" },
{ "title", "#ee" }, { "title", "#ee" },
@ -91,7 +91,7 @@ namespace Theme {
{ "process_end", "#d45454" } { "process_end", "#d45454" }
}; };
const unordered_flat_map<string, string> TTY_theme = { const std::unordered_map<string, string> TTY_theme = {
{ "main_bg", "\x1b[0;40m" }, { "main_bg", "\x1b[0;40m" },
{ "main_fg", "\x1b[37m" }, { "main_fg", "\x1b[37m" },
{ "title", "\x1b[97m" }, { "title", "\x1b[97m" },
@ -224,7 +224,7 @@ namespace Theme {
} }
//* Generate colors and rgb decimal vectors for the theme //* Generate colors and rgb decimal vectors for the theme
void generateColors(const unordered_flat_map<string, string>& source) { void generateColors(const std::unordered_map<string, string>& source) {
vector<string> t_rgb; vector<string> t_rgb;
string depth; string depth;
bool t_to_256 = Config::getB("lowcolor"); bool t_to_256 = Config::getB("lowcolor");
@ -372,7 +372,7 @@ namespace Theme {
//* Load a .theme file from disk //* Load a .theme file from disk
auto loadFile(const string& filename) { auto loadFile(const string& filename) {
unordered_flat_map<string, string> theme_out; std::unordered_map<string, string> theme_out;
const fs::path filepath = filename; const fs::path filepath = filename;
if (not fs::exists(filepath)) if (not fs::exists(filepath))
return Default_theme; return Default_theme;

View file

@ -22,18 +22,17 @@ tab-size = 4
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <vector> #include <vector>
#include <robin_hood.h> #include <unordered_map>
using std::array; using std::array;
using std::string; using std::string;
using std::vector; using std::vector;
using robin_hood::unordered_flat_map;
namespace Theme { namespace Theme {
extern std::filesystem::path theme_dir; extern std::filesystem::path theme_dir;
extern std::filesystem::path user_theme_dir; extern std::filesystem::path user_theme_dir;
//* Contains "Default" and "TTY" at indeces 0 and 1, otherwise full paths to theme files //* Contains "Default" and "TTY" at indices 0 and 1, otherwise full paths to theme files
extern vector<string> themes; extern vector<string> themes;
//* Generate escape sequence for 24-bit or 256 color and return as a string //* Generate escape sequence for 24-bit or 256 color and return as a string
@ -54,9 +53,9 @@ namespace Theme {
//* Set current theme from current "color_theme" value in config //* Set current theme from current "color_theme" value in config
void setTheme(); void setTheme();
extern unordered_flat_map<string, string> colors; extern std::unordered_map<string, string> colors;
extern unordered_flat_map<string, array<int, 3>> rgbs; extern std::unordered_map<string, array<int, 3>> rgbs;
extern unordered_flat_map<string, array<string, 101>> gradients; extern std::unordered_map<string, array<string, 101>> gradients;
//* Return escape code for color <name> //* Return escape code for color <name>
inline const string& c(const string& name) { return colors.at(name); } inline const string& c(const string& name) { return colors.at(name); }

View file

@ -26,24 +26,23 @@ tab-size = 4
#include <utility> #include <utility>
#include <ranges> #include <ranges>
#include <unistd.h> #include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include "robin_hood.h" #include "unordered_map"
#include "widechar_width.hpp" #include "widechar_width.hpp"
#include "btop_shared.hpp" #include "btop_shared.hpp"
#include "btop_tools.hpp" #include "btop_tools.hpp"
#include "btop_config.hpp" #include "btop_config.hpp"
using std::cin;
using std::cout; using std::cout;
using std::floor; using std::floor;
using std::flush; using std::flush;
using std::max; using std::max;
using std::string_view; using std::string_view;
using std::to_string; using std::to_string;
using robin_hood::unordered_flat_map;
using namespace std::literals; // to use operator""s using namespace std::literals; // to use operator""s
@ -55,9 +54,9 @@ namespace rng = std::ranges;
//* Collection of escape codes and functions for terminal manipulation //* Collection of escape codes and functions for terminal manipulation
namespace Term { namespace Term {
atomic<bool> initialized{}; // defaults to false atomic<bool> initialized{};
atomic<int> width{}; // defaults to 0 atomic<int> width{};
atomic<int> height{}; // defaults to 0 atomic<int> height{};
string current_tty; string current_tty;
namespace { namespace {
@ -77,7 +76,11 @@ namespace Term {
struct termios settings; struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false; if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ICANON; 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 (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
if (on) setlinebuf(stdin); if (on) setlinebuf(stdin);
else setbuf(stdin, nullptr); else setbuf(stdin, nullptr);
@ -86,12 +89,27 @@ namespace Term {
} }
bool refresh(bool only_check) { bool refresh(bool only_check) {
struct winsize w; // Query dimensions of '/dev/tty' of the 'STDOUT_FILENO' isn't available.
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0) return false; // This variable is set in those cases to avoid calls to ioctl
if (width != w.ws_col or height != w.ws_row) { 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) { if (not only_check) {
width = w.ws_col; width = wsize.ws_col;
height = w.ws_row; height = wsize.ws_row;
} }
return true; return true;
} }
@ -135,12 +153,12 @@ namespace Term {
tcgetattr(STDIN_FILENO, &initial_settings); tcgetattr(STDIN_FILENO, &initial_settings);
current_tty = (ttyname(STDIN_FILENO) != nullptr ? static_cast<string>(ttyname(STDIN_FILENO)) : "unknown"); 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
cin.sync_with_stdio(false); #ifndef __OpenBSD__
cout.sync_with_stdio(false); cout.sync_with_stdio(false);
#endif
//? Disable stream ties //? Disable stream ties
cin.tie(nullptr);
cout.tie(nullptr); cout.tie(nullptr);
echo(false); echo(false);
linebuffered(false); linebuffered(false);
@ -626,7 +644,7 @@ namespace Logger {
size_t loglevel; size_t loglevel;
fs::path logfile; fs::path logfile;
//* Wrapper for lowering priviliges if using SUID bit and currently isn't using real userid //* Wrapper for lowering privileges if using SUID bit and currently isn't using real userid
class lose_priv { class lose_priv {
int status = -1; int status = -1;
public: public:
@ -652,6 +670,7 @@ namespace Logger {
lose_priv neutered{}; lose_priv neutered{};
std::error_code ec; std::error_code ec;
try { 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) { if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) {
auto old_log = logfile; auto old_log = logfile;
old_log += ".1"; old_log += ".1";

View file

@ -18,6 +18,10 @@ tab-size = 4
#pragma once #pragma once
#if !defined(NDEBUG)
# define BTOP_DEBUG
#endif
#include <algorithm> // for std::ranges::count_if #include <algorithm> // for std::ranges::count_if
#include <array> #include <array>
#include <atomic> #include <atomic>
@ -31,6 +35,10 @@ tab-size = 4
#include <vector> #include <vector>
#include <pthread.h> #include <pthread.h>
#include <limits.h> #include <limits.h>
#include <unordered_map>
#ifdef BTOP_DEBUG
#include <source_location>
#endif
#ifndef HOST_NAME_MAX #ifndef HOST_NAME_MAX
#ifdef __APPLE__ #ifdef __APPLE__
#define HOST_NAME_MAX 255 #define HOST_NAME_MAX 255
@ -38,11 +46,9 @@ tab-size = 4
#define HOST_NAME_MAX 64 #define HOST_NAME_MAX 64
#endif #endif
#endif #endif
#define FMT_HEADER_ONLY
#include "fmt/core.h" #include "fmt/core.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
using std::array; using std::array;
using std::atomic; using std::atomic;
@ -108,7 +114,7 @@ namespace Mv {
//* Save cursor position //* Save cursor position
const string save = Fx::e + "s"; const string save = Fx::e + "s";
//* Restore saved cursor postion //* Restore saved cursor position
const string restore = Fx::e + "u"; const string restore = Fx::e + "u";
} }
@ -146,6 +152,35 @@ namespace Term {
void restore(); void restore();
} }
//* Simple logging implementation
namespace Logger {
const vector<string> log_levels = {
"DISABLED",
"ERROR",
"WARNING",
"INFO",
"DEBUG",
};
extern std::filesystem::path logfile;
enum Level : size_t {
DISABLED = 0,
ERROR = 1,
WARNING = 2,
INFO = 3,
DEBUG = 4,
};
//* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
void set(const string& level);
void log_write(const Level level, const string& msg);
inline void error(const string msg) { log_write(ERROR, msg); }
inline void warning(const string msg) { log_write(WARNING, msg); }
inline void info(const string msg) { log_write(INFO, msg); }
inline void debug(const string msg) { log_write(DEBUG, msg); }
}
//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- //? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
namespace Tools { namespace Tools {
@ -251,7 +286,7 @@ namespace Tools {
return is_in(str, "true", "True"); return is_in(str, "true", "True");
} }
//* Check if a string is a valid integer value (only postive) //* Check if a string is a valid integer value (only positive)
inline bool isint(const string& str) { inline bool isint(const string& str) {
return all_of(str.begin(), str.end(), ::isdigit); return all_of(str.begin(), str.end(), ::isdigit);
} }
@ -304,6 +339,50 @@ namespace Tools {
//* Add std::string operator * : Repeat string <str> <n> number of times //* Add std::string operator * : Repeat string <str> <n> number of times
std::string operator*(const string& str, int64_t n); std::string operator*(const string& str, int64_t n);
template <typename K, typename T>
#ifdef BTOP_DEBUG
const T& safeVal(const std::unordered_map<K, T>& map, const K& key, const T& fallback = T{}, std::source_location loc = std::source_location::current()) {
if (map.contains(key)) {
return map.at(key);
} else {
Logger::error(fmt::format("safeVal() called with invalid key: [{}] in file: {} on line: {}", key, loc.file_name(), loc.line()));
return fallback;
}
};
#else
const T& safeVal(const std::unordered_map<K, T>& map, const K& key, const T& fallback = T{}) {
if (map.contains(key)) {
return map.at(key);
} else {
Logger::error(fmt::format("safeVal() called with invalid key: [{}] (Compile btop with DEBUG=true for more extensive logging!)", key));
return fallback;
}
};
#endif
template <typename T>
#ifdef BTOP_DEBUG
const T& safeVal(const std::vector<T>& vec, const size_t& index, const T& fallback = T{}, std::source_location loc = std::source_location::current()) {
if (index < vec.size()) {
return vec.at(index);
} else {
Logger::error(fmt::format("safeVal() called with invalid index: [{}] in file: {} on line: {}", index, loc.file_name(), loc.line()));
return fallback;
}
};
#else
const T& safeVal(const std::vector<T>& vec, const size_t& index, const T& fallback = T{}) {
if (index < vec.size()) {
return vec.at(index);
} else {
Logger::error(fmt::format("safeVal() called with invalid index: [{}] (Compile btop with DEBUG=true for more extensive logging!)", index));
return fallback;
}
};
#endif
//* Return current time in <strf> format //* Return current time in <strf> format
string strf_time(const string& strf); string strf_time(const string& strf);
@ -329,7 +408,7 @@ namespace Tools {
//* Sets atomic<bool> to true on construct, sets to false on destruct //* Sets atomic<bool> to true on construct, sets to false on destruct
class atomic_lock { class atomic_lock {
atomic<bool>& atom; atomic<bool>& atom;
bool not_true{}; // defaults to false bool not_true{};
public: public:
atomic_lock(atomic<bool>& atom, bool wait = false); atomic_lock(atomic<bool>& atom, bool wait = false);
~atomic_lock(); ~atomic_lock();
@ -342,35 +421,6 @@ namespace Tools {
auto celsius_to(const long long& celsius, const string& scale) -> tuple<long long, string>; auto celsius_to(const long long& celsius, const string& scale) -> tuple<long long, string>;
} }
//* Simple logging implementation
namespace Logger {
const vector<string> log_levels = {
"DISABLED",
"ERROR",
"WARNING",
"INFO",
"DEBUG",
};
extern std::filesystem::path logfile;
enum Level : size_t {
DISABLED = 0,
ERROR = 1,
WARNING = 2,
INFO = 3,
DEBUG = 4,
};
//* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
void set(const string& level);
void log_write(const Level level, const string& msg);
inline void error(const string msg) { log_write(ERROR, msg); }
inline void warning(const string msg) { log_write(WARNING, msg); }
inline void info(const string msg) { log_write(INFO, msg); }
inline void debug(const string msg) { log_write(DEBUG, msg); }
}
namespace Tools { namespace Tools {
//* Creates a named timer that is started on construct (by default) and reports elapsed time in microseconds to Logger::debug() on destruct if running //* Creates a named timer that is started on construct (by default) and reports elapsed time in microseconds to Logger::debug() on destruct if running
//* Unless delayed_report is set to false, all reporting is buffered and delayed until DebugTimer is destructed or .force_report() is called //* Unless delayed_report is set to false, all reporting is buffered and delayed until DebugTimer is destructed or .force_report() is called

10
src/config.h.in Normal file
View file

@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <string_view>
constexpr std::string_view GIT_COMMIT = "@GIT_COMMIT@";
constexpr std::string_view COMPILER = "@COMPILER@";
constexpr std::string_view COMPILER_VERSION = "@COMPILER_VERSION@";
constexpr std::string_view CONFIGURE_COMMAND = "@CONFIGURE_COMMAND@";

View file

@ -58,6 +58,7 @@ tab-size = 4
#include <regex> #include <regex>
#include <string> #include <string>
#include <memory> #include <memory>
#include <utility>
#include "../btop_config.hpp" #include "../btop_config.hpp"
#include "../btop_shared.hpp" #include "../btop_shared.hpp"
@ -98,7 +99,7 @@ namespace Cpu {
string cpu_sensor; string cpu_sensor;
vector<string> core_sensors; vector<string> core_sensors;
unordered_flat_map<int, int> core_mapping; std::unordered_map<int, int> core_mapping;
} // namespace Cpu } // namespace Cpu
namespace Mem { namespace Mem {
@ -169,42 +170,36 @@ namespace Shared {
Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); 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_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0);
Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0);
Logger::debug("Init -> Cpu::collect()");
Cpu::collect(); Cpu::collect();
for (auto &[field, vec] : Cpu::current_cpu.cpu_percent) { 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); if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field);
} }
Logger::debug("Init -> Cpu::get_cpuName()");
Cpu::cpuName = Cpu::get_cpuName(); Cpu::cpuName = Cpu::get_cpuName();
Logger::debug("Init -> Cpu::get_sensors()");
Cpu::got_sensors = Cpu::get_sensors(); Cpu::got_sensors = Cpu::get_sensors();
Logger::debug("Init -> Cpu::get_core_mapping()");
Cpu::core_mapping = Cpu::get_core_mapping(); Cpu::core_mapping = Cpu::get_core_mapping();
//? Init for namespace Mem //? Init for namespace Mem
Mem::old_uptime = system_uptime(); Mem::old_uptime = system_uptime();
Logger::debug("Init -> Mem::collect()");
Mem::collect(); Mem::collect();
Logger::debug("Init -> Mem::get_zpools()");
Mem::get_zpools(); Mem::get_zpools();
} }
//* 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 Shared
namespace Cpu { namespace Cpu {
string cpuName; string cpuName;
string cpuHz; string cpuHz;
bool has_battery = true; bool has_battery = true;
tuple<int, long, string> current_bat; tuple<int, float, long, string> current_bat;
const array<string, 10> time_names = {"user", "nice", "system", "idle"}; const array<string, 10> time_names = {"user", "nice", "system", "idle"};
unordered_flat_map<string, long long> cpu_old = { std::unordered_map<string, long long> cpu_old = {
{"totals", 0}, {"totals", 0},
{"idles", 0}, {"idles", 0},
{"user", 0}, {"user", 0},
@ -270,7 +265,7 @@ namespace Cpu {
got_sensors = true; got_sensors = true;
int temp; int temp;
size_t size = sizeof(temp); size_t size = sizeof(temp);
sysctlbyname("dev.cpu.0.coretemp.tjmax", &temp, &size, nullptr, 0); //asuming the max temp is same for all cores sysctlbyname("dev.cpu.0.coretemp.tjmax", &temp, &size, nullptr, 0); //assuming the max temp is same for all cores
temp = (temp - 2732) / 10; // since it's an int, it's multiplied by 10, and offset to absolute zero... temp = (temp - 2732) / 10; // since it's an int, it's multiplied by 10, and offset to absolute zero...
current_cpu.temp_max = temp; current_cpu.temp_max = temp;
} }
@ -323,8 +318,8 @@ namespace Cpu {
return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz
} }
auto get_core_mapping() -> unordered_flat_map<int, int> { auto get_core_mapping() -> std::unordered_map<int, int> {
unordered_flat_map<int, int> core_map; std::unordered_map<int, int> core_map;
if (cpu_temp_only) return core_map; if (cpu_temp_only) return core_map;
for (long i = 0; i < Shared::coreCount; i++) { for (long i = 0; i < Shared::coreCount; i++) {
@ -366,10 +361,11 @@ namespace Cpu {
return core_map; return core_map;
} }
auto get_battery() -> tuple<int, long, string> { auto get_battery() -> tuple<int, float, long, string> {
if (not has_battery) return {0, 0, ""}; if (not has_battery) return {0, 0, 0, ""};
long seconds = -1; long seconds = -1;
float watts = -1;
uint32_t percent = -1; uint32_t percent = -1;
size_t size = sizeof(percent); size_t size = sizeof(percent);
string status = "discharging"; string status = "discharging";
@ -381,6 +377,10 @@ namespace Cpu {
if (sysctlbyname("hw.acpi.battery.time", &seconds, &size, nullptr, 0) < 0) { if (sysctlbyname("hw.acpi.battery.time", &seconds, &size, nullptr, 0) < 0) {
seconds = 0; seconds = 0;
} }
size = sizeof(watts);
if (sysctlbyname("hw.acpi.battery.rate", &watts, &size, nullptr, 0) < 0) {
watts = -1;
}
int state; int state;
size = sizeof(state); size = sizeof(state);
if (sysctlbyname("hw.acpi.battery.state", &state, &size, nullptr, 0) < 0) { if (sysctlbyname("hw.acpi.battery.state", &state, &size, nullptr, 0) < 0) {
@ -395,7 +395,7 @@ namespace Cpu {
} }
} }
return {percent, seconds, status}; return {percent, watts, seconds, status};
} }
auto collect(bool no_update) -> cpu_info & { auto collect(bool no_update) -> cpu_info & {
@ -557,7 +557,7 @@ namespace Mem {
} }
} }
void collect_disk(unordered_flat_map<string, disk_info> &disks, unordered_flat_map<string, string> &mapping) { void collect_disk(std::unordered_map<string, disk_info> &disks, std::unordered_map<string, string> &mapping) {
// this bit is for 'regular' mounts // this bit is for 'regular' mounts
static struct statinfo cur; static struct statinfo cur;
long double etime = 0; long double etime = 0;
@ -572,7 +572,7 @@ namespace Mem {
auto d = cur.dinfo->devices[i]; auto d = cur.dinfo->devices[i];
string devStatName = "/dev/" + string(d.device_name) + std::to_string(d.unit_number); string devStatName = "/dev/" + string(d.device_name) + std::to_string(d.unit_number);
for (auto& [ignored, disk] : disks) { // find matching mountpoints - could be multiple as d.device_name is only ada (and d.unit_number is the device number), while the disk.dev is like /dev/ada0s1 for (auto& [ignored, disk] : disks) { // find matching mountpoints - could be multiple as d.device_name is only ada (and d.unit_number is the device number), while the disk.dev is like /dev/ada0s1
if (disk.dev.string().rfind(devStatName, 0) == 0) { if (disk.dev.string().rfind(devStatName, 0) == 0 and mapping.contains(disk.dev)) {
devstat_compute_statistics(&d, nullptr, etime, DSM_TOTAL_BYTES_READ, &total_bytes_read, DSM_TOTAL_BYTES_WRITE, &total_bytes_write, DSM_NONE); devstat_compute_statistics(&d, nullptr, etime, DSM_TOTAL_BYTES_READ, &total_bytes_read, DSM_TOTAL_BYTES_WRITE, &total_bytes_write, DSM_NONE);
assign_values(disk, total_bytes_read, total_bytes_write); assign_values(disk, total_bytes_read, total_bytes_write);
string mountpoint = mapping.at(disk.dev); string mountpoint = mapping.at(disk.dev);
@ -581,7 +581,6 @@ namespace Mem {
} }
} }
Logger::debug("");
} }
// this code is for ZFS mounts // this code is for ZFS mounts
@ -662,9 +661,9 @@ namespace Mem {
if (show_swap) { if (show_swap) {
char buf[_POSIX2_LINE_MAX]; char buf[_POSIX2_LINE_MAX];
Shared::kvm_openfiles_wrapper kd(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf); Shared::KvmPtr kd {kvm_openfiles(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf)};
struct kvm_swap swap[16]; struct kvm_swap swap[16];
int nswap = kvm_getswapinfo(kd(), swap, 16, 0); int nswap = kvm_getswapinfo(kd.get(), swap, 16, 0);
int totalSwap = 0, usedSwap = 0; int totalSwap = 0, usedSwap = 0;
for (int i = 0; i < nswap; i++) { for (int i = 0; i < nswap; i++) {
totalSwap += swap[i].ksw_total; totalSwap += swap[i].ksw_total;
@ -691,7 +690,7 @@ namespace Mem {
} }
if (show_disks) { if (show_disks) {
unordered_flat_map<string, string> mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint std::unordered_map<string, string> mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint
double uptime = system_uptime(); double uptime = system_uptime();
auto &disks_filter = Config::getS("disks_filter"); auto &disks_filter = Config::getS("disks_filter");
bool filter_exclude = false; bool filter_exclude = false;
@ -807,27 +806,16 @@ namespace Mem {
} // namespace Mem } // namespace Mem
namespace Net { namespace Net {
unordered_flat_map<string, net_info> current_net; std::unordered_map<string, net_info> current_net;
net_info empty_net = {}; net_info empty_net = {};
vector<string> interfaces; vector<string> interfaces;
string selected_iface; string selected_iface;
int errors = 0; int errors = 0;
unordered_flat_map<string, uint64_t> graph_max = {{"download", {}}, {"upload", {}}}; std::unordered_map<string, uint64_t> graph_max = {{"download", {}}, {"upload", {}}};
unordered_flat_map<string, array<int, 2>> max_count = {{"download", {}}, {"upload", {}}}; std::unordered_map<string, array<int, 2>> max_count = {{"download", {}}, {"upload", {}}};
bool rescale = true; bool rescale = true;
uint64_t timestamp = 0; 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 collect(bool no_update) -> net_info & {
auto &net = current_net; auto &net = current_net;
auto &config_iface = Config::getS("net_iface"); auto &config_iface = Config::getS("net_iface");
@ -837,10 +825,10 @@ namespace Net {
if (not no_update and errors < 3) { if (not no_update and errors < 3) {
//? Get interface list using getifaddrs() wrapper //? Get interface list using getifaddrs() wrapper
getifaddr_wrapper if_wrap{}; IfAddrsPtr if_addrs {};
if (if_wrap.status != 0) { if (if_addrs.get_status() != 0) {
errors++; errors++;
Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_addrs.get_status()));
redraw = true; redraw = true;
return empty_net; return empty_net;
} }
@ -852,7 +840,7 @@ namespace Net {
string ipv4, ipv6; string ipv4, ipv6;
//? Iteration over all items in getifaddrs() list //? Iteration over all items in getifaddrs() list
for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) { for (auto *ifa = if_addrs.get(); ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr) continue; if (ifa->ifa_addr == nullptr) continue;
family = ifa->ifa_addr->sa_family; family = ifa->ifa_addr->sa_family;
const auto &iface = ifa->ifa_name; const auto &iface = ifa->ifa_name;
@ -892,7 +880,7 @@ namespace Net {
} //else, ignoring family==AF_LINK (see man 3 getifaddrs) } //else, ignoring family==AF_LINK (see man 3 getifaddrs)
} }
unordered_flat_map<string, std::tuple<uint64_t, uint64_t>> ifstats; std::unordered_map<string, std::tuple<uint64_t, uint64_t>> ifstats;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0};
size_t len; size_t len;
if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) {
@ -919,7 +907,7 @@ namespace Net {
} }
} }
//? Get total recieved and transmitted bytes + device address if no ip was found //? Get total received and transmitted bytes + device address if no ip was found
for (const auto &iface : interfaces) { for (const auto &iface : interfaces) {
for (const string dir : {"download", "upload"}) { for (const string dir : {"download", "upload"}) {
auto &saved_stat = net.at(iface).stat.at(dir); auto &saved_stat = net.at(iface).stat.at(dir);
@ -966,7 +954,6 @@ namespace Net {
else else
it++; it++;
} }
net.compact();
} }
timestamp = new_timestamp; timestamp = new_timestamp;
@ -1037,7 +1024,7 @@ namespace Net {
namespace Proc { namespace Proc {
vector<proc_info> current_procs; vector<proc_info> current_procs;
unordered_flat_map<string, string> uid_user; std::unordered_map<string, string> uid_user;
string current_sort; string current_sort;
string current_filter; string current_filter;
bool current_rev = false; bool current_rev = false;
@ -1159,8 +1146,8 @@ namespace Proc {
int count = 0; int count = 0;
char buf[_POSIX2_LINE_MAX]; char buf[_POSIX2_LINE_MAX];
Shared::kvm_openfiles_wrapper kd(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf); Shared::KvmPtr kd {kvm_openfiles(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf)};
const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_PROC, 0, &count); const struct kinfo_proc* kprocs = kvm_getprocs(kd.get(), KERN_PROC_PROC, 0, &count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
const struct kinfo_proc* kproc = &kprocs[i]; const struct kinfo_proc* kproc = &kprocs[i];
@ -1187,7 +1174,7 @@ namespace Proc {
continue; continue;
} }
new_proc.name = kproc->ki_comm; new_proc.name = kproc->ki_comm;
char** argv = kvm_getargv(kd(), kproc, 0); char** argv = kvm_getargv(kd.get(), kproc, 0);
if (argv) { if (argv) {
for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) { for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) {
new_proc.cmd += argv[i] + " "s; new_proc.cmd += argv[i] + " "s;

View file

@ -17,7 +17,8 @@ tab-size = 4
*/ */
#include <cstdlib> #include <cstdlib>
#include <robin_hood.h> #include <unordered_map>
#include <unordered_set>
#include <fstream> #include <fstream>
#include <ranges> #include <ranges>
#include <cmath> #include <cmath>
@ -31,6 +32,8 @@ tab-size = 4
#include <filesystem> #include <filesystem>
#include <future> #include <future>
#include <dlfcn.h> #include <dlfcn.h>
#include <unordered_map>
#include <utility>
#if defined(RSMI_STATIC) #if defined(RSMI_STATIC)
#include <rocm_smi/rocm_smi.h> #include <rocm_smi/rocm_smi.h>
@ -74,8 +77,8 @@ namespace Cpu {
vector<string> available_sensors = {"Auto"}; vector<string> available_sensors = {"Auto"};
cpu_info current_cpu; cpu_info current_cpu;
fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq";
bool got_sensors{}; // defaults to false bool got_sensors{};
bool cpu_temp_only{}; // defaults to false bool cpu_temp_only{};
//* Populate found_sensors map //* Populate found_sensors map
bool get_sensors(); bool get_sensors();
@ -89,15 +92,15 @@ namespace Cpu {
struct Sensor { struct Sensor {
fs::path path; fs::path path;
string label; string label;
int64_t temp{}; // defaults to 0 int64_t temp{};
int64_t high{}; // defaults to 0 int64_t high{};
int64_t crit{}; // defaults to 0 int64_t crit{};
}; };
unordered_flat_map<string, Sensor> found_sensors; std::unordered_map<string, Sensor> found_sensors;
string cpu_sensor; string cpu_sensor;
vector<string> core_sensors; vector<string> core_sensors;
unordered_flat_map<int, int> core_mapping; std::unordered_map<int, int> core_mapping;
} }
namespace Gpu { namespace Gpu {
@ -157,37 +160,44 @@ namespace Gpu {
namespace Rsmi { namespace Rsmi {
#if !defined(RSMI_STATIC) #if !defined(RSMI_STATIC)
//? RSMI defines, structs & typedefs //? RSMI defines, structs & typedefs
#define RSMI_MAX_NUM_FREQUENCIES 32 #define RSMI_MAX_NUM_FREQUENCIES_V5 32
#define RSMI_STATUS_SUCCESS 0 #define RSMI_MAX_NUM_FREQUENCIES_V6 33
#define RSMI_MEM_TYPE_VRAM 0 #define RSMI_STATUS_SUCCESS 0
#define RSMI_TEMP_CURRENT 0 #define RSMI_MEM_TYPE_VRAM 0
#define RSMI_TEMP_TYPE_EDGE 0 #define RSMI_TEMP_CURRENT 0
#define RSMI_CLK_TYPE_MEM 4 #define RSMI_TEMP_TYPE_EDGE 0
#define RSMI_CLK_TYPE_SYS 0 #define RSMI_CLK_TYPE_MEM 4
#define RSMI_TEMP_MAX 1 #define RSMI_CLK_TYPE_SYS 0
#define RSMI_TEMP_MAX 1
typedef int rsmi_status_t, typedef int rsmi_status_t,
rsmi_temperature_metric_t, rsmi_temperature_metric_t,
rsmi_clk_type_t, rsmi_clk_type_t,
rsmi_memory_type_t; rsmi_memory_type_t;
struct rsmi_frequencies_t {uint32_t num_supported, current, frequency[RSMI_MAX_NUM_FREQUENCIES];}; struct rsmi_version_t {uint32_t major, minor, patch; const char* build;};
struct rsmi_frequencies_t_v5 {uint32_t num_supported, current; uint64_t frequency[RSMI_MAX_NUM_FREQUENCIES_V5];};
struct rsmi_frequencies_t_v6 {bool has_deep_sleep; uint32_t num_supported, current; uint64_t frequency[RSMI_MAX_NUM_FREQUENCIES_V6];};
//? Function pointers //? Function pointers
rsmi_status_t (*rsmi_init)(uint64_t); rsmi_status_t (*rsmi_init)(uint64_t);
rsmi_status_t (*rsmi_shut_down)(); rsmi_status_t (*rsmi_shut_down)();
rsmi_status_t (*rsmi_version_get)(rsmi_version_t*);
rsmi_status_t (*rsmi_num_monitor_devices)(uint32_t*); rsmi_status_t (*rsmi_num_monitor_devices)(uint32_t*);
rsmi_status_t (*rsmi_dev_name_get)(uint32_t, char*, size_t); rsmi_status_t (*rsmi_dev_name_get)(uint32_t, char*, size_t);
rsmi_status_t (*rsmi_dev_power_cap_get)(uint32_t, uint32_t, uint64_t*); rsmi_status_t (*rsmi_dev_power_cap_get)(uint32_t, uint32_t, uint64_t*);
rsmi_status_t (*rsmi_dev_temp_metric_get)(uint32_t, uint32_t, rsmi_temperature_metric_t, int64_t*); rsmi_status_t (*rsmi_dev_temp_metric_get)(uint32_t, uint32_t, rsmi_temperature_metric_t, int64_t*);
rsmi_status_t (*rsmi_dev_busy_percent_get)(uint32_t, uint32_t*); rsmi_status_t (*rsmi_dev_busy_percent_get)(uint32_t, uint32_t*);
rsmi_status_t (*rsmi_dev_memory_busy_percent_get)(uint32_t, uint32_t*); rsmi_status_t (*rsmi_dev_memory_busy_percent_get)(uint32_t, uint32_t*);
rsmi_status_t (*rsmi_dev_gpu_clk_freq_get)(uint32_t, rsmi_clk_type_t, rsmi_frequencies_t*); rsmi_status_t (*rsmi_dev_gpu_clk_freq_get_v5)(uint32_t, rsmi_clk_type_t, rsmi_frequencies_t_v5*);
rsmi_status_t (*rsmi_dev_gpu_clk_freq_get_v6)(uint32_t, rsmi_clk_type_t, rsmi_frequencies_t_v6*);
rsmi_status_t (*rsmi_dev_power_ave_get)(uint32_t, uint32_t, uint64_t*); rsmi_status_t (*rsmi_dev_power_ave_get)(uint32_t, uint32_t, uint64_t*);
rsmi_status_t (*rsmi_dev_memory_total_get)(uint32_t, rsmi_memory_type_t, uint64_t*); rsmi_status_t (*rsmi_dev_memory_total_get)(uint32_t, rsmi_memory_type_t, uint64_t*);
rsmi_status_t (*rsmi_dev_memory_usage_get)(uint32_t, rsmi_memory_type_t, uint64_t*); rsmi_status_t (*rsmi_dev_memory_usage_get)(uint32_t, rsmi_memory_type_t, uint64_t*);
rsmi_status_t (*rsmi_dev_pci_throughput_get)(uint32_t, uint64_t*, uint64_t*, uint64_t*); rsmi_status_t (*rsmi_dev_pci_throughput_get)(uint32_t, uint64_t*, uint64_t*, uint64_t*);
uint32_t version_major = 0;
//? Data //? Data
void* rsmi_dl_handle; void* rsmi_dl_handle;
#endif #endif
@ -291,14 +301,14 @@ namespace Cpu {
string cpuName; string cpuName;
string cpuHz; string cpuHz;
bool has_battery = true; bool has_battery = true;
tuple<int, long, string> current_bat; tuple<int, float, long, string> current_bat;
const array time_names { const array time_names {
"user"s, "nice"s, "system"s, "idle"s, "iowait"s, "user"s, "nice"s, "system"s, "idle"s, "iowait"s,
"irq"s, "softirq"s, "steal"s, "guest"s, "guest_nice"s "irq"s, "softirq"s, "steal"s, "guest"s, "guest_nice"s
}; };
unordered_flat_map<string, long long> cpu_old = { std::unordered_map<string, long long> cpu_old = {
{"totals", 0}, {"totals", 0},
{"idles", 0}, {"idles", 0},
{"user", 0}, {"user", 0},
@ -539,14 +549,14 @@ namespace Cpu {
} }
string get_cpuHz() { string get_cpuHz() {
static int failed{}; // defaults to 0 static int failed{};
if (failed > 4) if (failed > 4)
return ""s; return ""s;
string cpuhz; string cpuhz;
try { try {
double hz{}; // defaults to 0.0 double hz{};
//? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster) //? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster)
if (not freq_path.empty()) { if (not freq_path.empty()) {
hz = stod(readfile(freq_path, "0.0")) / 1000; hz = stod(readfile(freq_path, "0.0")) / 1000;
@ -580,7 +590,7 @@ namespace Cpu {
cpuhz += " GHz"; cpuhz += " GHz";
} }
else if (hz > 0) else if (hz > 0)
cpuhz = to_string((int)round(hz)) + " MHz"; cpuhz = to_string((int)hz) + " MHz";
} }
catch (const std::exception& e) { catch (const std::exception& e) {
@ -595,16 +605,16 @@ namespace Cpu {
return cpuhz; return cpuhz;
} }
auto get_core_mapping() -> unordered_flat_map<int, int> { auto get_core_mapping() -> std::unordered_map<int, int> {
unordered_flat_map<int, int> core_map; std::unordered_map<int, int> core_map;
if (cpu_temp_only) return core_map; if (cpu_temp_only) return core_map;
//? Try to get core mapping from /proc/cpuinfo //? Try to get core mapping from /proc/cpuinfo
ifstream cpuinfo(Shared::procPath / "cpuinfo"); ifstream cpuinfo(Shared::procPath / "cpuinfo");
if (cpuinfo.good()) { if (cpuinfo.good()) {
int cpu{}; // defaults to 0 int cpu{};
int core{}; // defaults to 0 int core{};
int n{}; // defaults to 0 int n{};
for (string instr; cpuinfo >> instr;) { for (string instr; cpuinfo >> instr;) {
if (instr == "processor") { if (instr == "processor") {
cpuinfo.ignore(SSmax, ':'); cpuinfo.ignore(SSmax, ':');
@ -661,15 +671,16 @@ namespace Cpu {
} }
struct battery { struct battery {
fs::path base_dir, energy_now, energy_full, power_now, status, online; fs::path base_dir, energy_now, charge_now, energy_full, charge_full, power_now, current_now, voltage_now, status, online;
string device_type; string device_type;
bool use_energy = true; bool use_energy_or_charge = true;
bool use_power = true;
}; };
auto get_battery() -> tuple<int, long, string> { auto get_battery() -> tuple<int, float, long, string> {
if (not has_battery) return {0, 0, ""}; if (not has_battery) return {0, 0, 0, ""};
static string auto_sel; static string auto_sel;
static unordered_flat_map<string, battery> batteries; static std::unordered_map<string, battery> batteries;
//? Get paths to needed files and check for valid values on first run //? Get paths to needed files and check for valid values on first run
if (batteries.empty() and has_battery) { if (batteries.empty() and has_battery) {
@ -699,19 +710,27 @@ namespace Cpu {
} }
if (fs::exists(bat_dir / "energy_now")) new_bat.energy_now = bat_dir / "energy_now"; if (fs::exists(bat_dir / "energy_now")) new_bat.energy_now = bat_dir / "energy_now";
else if (fs::exists(bat_dir / "charge_now")) new_bat.energy_now = bat_dir / "charge_now"; else if (fs::exists(bat_dir / "charge_now")) new_bat.charge_now = bat_dir / "charge_now";
else new_bat.use_energy = false; else new_bat.use_energy_or_charge = false;
if (fs::exists(bat_dir / "energy_full")) new_bat.energy_full = bat_dir / "energy_full"; if (fs::exists(bat_dir / "energy_full")) new_bat.energy_full = bat_dir / "energy_full";
else if (fs::exists(bat_dir / "charge_full")) new_bat.energy_full = bat_dir / "charge_full"; else if (fs::exists(bat_dir / "charge_full")) new_bat.charge_full = bat_dir / "charge_full";
else new_bat.use_energy = false; else new_bat.use_energy_or_charge = false;
if (not new_bat.use_energy and not fs::exists(bat_dir / "capacity")) { if (not new_bat.use_energy_or_charge and not fs::exists(bat_dir / "capacity")) {
continue; continue;
} }
if (fs::exists(bat_dir / "power_now")) new_bat.power_now = bat_dir / "power_now"; if (fs::exists(bat_dir / "power_now")) {
else if (fs::exists(bat_dir / "current_now")) new_bat.power_now = bat_dir / "current_now"; new_bat.power_now = bat_dir / "power_now";
}
else if ((fs::exists(bat_dir / "current_now")) and (fs::exists(bat_dir / "current_now"))) {
new_bat.current_now = bat_dir / "current_now";
new_bat.voltage_now = bat_dir / "voltage_now";
}
else {
new_bat.use_power = false;
}
if (fs::exists(bat_dir / "AC0/online")) new_bat.online = bat_dir / "AC0/online"; if (fs::exists(bat_dir / "AC0/online")) new_bat.online = bat_dir / "AC0/online";
else if (fs::exists(bat_dir / "AC/online")) new_bat.online = bat_dir / "AC/online"; else if (fs::exists(bat_dir / "AC/online")) new_bat.online = bat_dir / "AC/online";
@ -726,7 +745,7 @@ namespace Cpu {
} }
if (batteries.empty()) { if (batteries.empty()) {
has_battery = false; has_battery = false;
return {0, 0, ""}; return {0, 0, 0, ""};
} }
} }
@ -746,15 +765,9 @@ namespace Cpu {
int percent = -1; int percent = -1;
long seconds = -1; long seconds = -1;
float watts = -1;
//? Try to get battery percentage //? Try to get battery percentage
if (b.use_energy) {
try {
percent = round(100.0 * stoll(readfile(b.energy_now, "-1")) / stoll(readfile(b.energy_full, "1")));
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
if (percent < 0) { if (percent < 0) {
try { try {
percent = stoll(readfile(b.base_dir / "capacity", "-1")); percent = stoll(readfile(b.base_dir / "capacity", "-1"));
@ -762,9 +775,23 @@ namespace Cpu {
catch (const std::invalid_argument&) { } catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { } catch (const std::out_of_range&) { }
} }
if (b.use_energy_or_charge and percent < 0) {
try {
percent = round(100.0 * stoll(readfile(b.energy_now, "-1")) / stoll(readfile(b.energy_full, "1")));
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
if (b.use_energy_or_charge and percent < 0) {
try {
percent = round(100.0 * stoll(readfile(b.charge_now, "-1")) / stoll(readfile(b.charge_full, "1")));
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
if (percent < 0) { if (percent < 0) {
has_battery = false; has_battery = false;
return {0, 0, ""}; return {0, 0, 0, ""};
} }
//? Get charging/discharging status //? Get charging/discharging status
@ -778,13 +805,23 @@ namespace Cpu {
//? Get seconds to empty //? Get seconds to empty
if (not is_in(status, "charging", "full")) { if (not is_in(status, "charging", "full")) {
if (b.use_energy and not b.power_now.empty()) { if (b.use_energy_or_charge ) {
try { if (not b.power_now.empty()) {
seconds = round((double)stoll(readfile(b.energy_now, "0")) / stoll(readfile(b.power_now, "1")) * 3600); try {
seconds = round((double)stoll(readfile(b.energy_now, "0")) / stoll(readfile(b.power_now, "1")) * 3600);
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
else if (not b.current_now.empty()) {
try {
seconds = round((double)stoll(readfile(b.charge_now, "0")) / (double)stoll(readfile(b.current_now, "1")) * 3600);
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
} }
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
} }
if (seconds < 0 and fs::exists(b.base_dir / "time_to_empty")) { if (seconds < 0 and fs::exists(b.base_dir / "time_to_empty")) {
try { try {
seconds = stoll(readfile(b.base_dir / "time_to_empty", "0")) * 60; seconds = stoll(readfile(b.base_dir / "time_to_empty", "0")) * 60;
@ -794,7 +831,26 @@ namespace Cpu {
} }
} }
return {percent, seconds, status}; //? Get power draw
if (b.use_power) {
if (not b.power_now.empty()) {
try {
watts = (float)stoll(readfile(b.power_now, "-1")) / 1000000.0;
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
else if (not b.voltage_now.empty() and not b.current_now.empty()) {
try {
watts = (float)stoll(readfile(b.current_now, "-1")) / 1000000.0 * stoll(readfile(b.voltage_now, "1")) / 1000000.0;
}
catch (const std::invalid_argument&) { }
catch (const std::out_of_range&) { }
}
}
return {percent, watts, seconds, status};
} }
auto collect(bool no_update) -> cpu_info& { auto collect(bool no_update) -> cpu_info& {
@ -946,16 +1002,27 @@ namespace Gpu {
if (initialized) return false; if (initialized) return false;
//? Dynamic loading & linking //? Dynamic loading & linking
nvml_dl_handle = dlopen("libnvidia-ml.so", RTLD_LAZY); //? Try possible library names for libnvidia-ml.so
if (!nvml_dl_handle) { const array libNvAlts = {
Logger::info(std::string("Failed to load libnvidia-ml.so, NVIDIA GPUs will not be detected: ") + dlerror()); "libnvidia-ml.so",
return false; "libnvidia-ml.so.1",
};
for (const auto& l : libNvAlts) {
nvml_dl_handle = dlopen(l, RTLD_LAZY);
if (nvml_dl_handle != nullptr) {
break;
}
} }
if (!nvml_dl_handle) {
Logger::info("Failed to load libnvidia-ml.so, NVIDIA GPUs will not be detected: "s + dlerror());
return false;
}
auto load_nvml_sym = [&](const char sym_name[]) { auto load_nvml_sym = [&](const char sym_name[]) {
auto sym = dlsym(nvml_dl_handle, sym_name); auto sym = dlsym(nvml_dl_handle, sym_name);
auto err = dlerror(); auto err = dlerror();
if (err != NULL) { if (err != nullptr) {
Logger::error(string("NVML: Couldn't find function ") + sym_name + ": " + err); Logger::error(string("NVML: Couldn't find function ") + sym_name + ": " + err);
return (void*)nullptr; return (void*)nullptr;
} else return sym; } else return sym;
@ -1211,19 +1278,32 @@ namespace Gpu {
//? Dynamic loading & linking //? Dynamic loading & linking
#if !defined(RSMI_STATIC) #if !defined(RSMI_STATIC)
rsmi_dl_handle = dlopen("/opt/rocm/lib/librocm_smi64.so", RTLD_LAZY); // first try /lib and /usr/lib, then /opt/rocm/lib if that fails
if (dlerror() != NULL) { //? Try possible library paths and names for librocm_smi64.so
rsmi_dl_handle = dlopen("librocm_smi64.so", RTLD_LAZY); const array libRocAlts = {
} "/opt/rocm/lib/librocm_smi64.so",
"librocm_smi64.so",
"librocm_smi64.so.5", // fedora
"librocm_smi64.so.1.0", // debian
"librocm_smi64.so.6"
};
for (const auto& l : libRocAlts) {
rsmi_dl_handle = dlopen(l, RTLD_LAZY);
if (rsmi_dl_handle != nullptr) {
break;
}
}
if (!rsmi_dl_handle) { if (!rsmi_dl_handle) {
Logger::debug(std::string("Failed to load librocm_smi64.so, AMD GPUs will not be detected: ") + dlerror()); Logger::info("Failed to load librocm_smi64.so, AMD GPUs will not be detected: "s + dlerror());
return false; return false;
} }
auto load_rsmi_sym = [&](const char sym_name[]) { auto load_rsmi_sym = [&](const char sym_name[]) {
auto sym = dlsym(rsmi_dl_handle, sym_name); auto sym = dlsym(rsmi_dl_handle, sym_name);
auto err = dlerror(); auto err = dlerror();
if (err != NULL) { if (err != nullptr) {
Logger::error(string("ROCm SMI: Couldn't find function ") + sym_name + ": " + err); Logger::error(string("ROCm SMI: Couldn't find function ") + sym_name + ": " + err);
return (void*)nullptr; return (void*)nullptr;
} else return sym; } else return sym;
@ -1233,13 +1313,13 @@ namespace Gpu {
LOAD_SYM(rsmi_init); LOAD_SYM(rsmi_init);
LOAD_SYM(rsmi_shut_down); LOAD_SYM(rsmi_shut_down);
LOAD_SYM(rsmi_version_get);
LOAD_SYM(rsmi_num_monitor_devices); LOAD_SYM(rsmi_num_monitor_devices);
LOAD_SYM(rsmi_dev_name_get); LOAD_SYM(rsmi_dev_name_get);
LOAD_SYM(rsmi_dev_power_cap_get); LOAD_SYM(rsmi_dev_power_cap_get);
LOAD_SYM(rsmi_dev_temp_metric_get); LOAD_SYM(rsmi_dev_temp_metric_get);
LOAD_SYM(rsmi_dev_busy_percent_get); LOAD_SYM(rsmi_dev_busy_percent_get);
LOAD_SYM(rsmi_dev_memory_busy_percent_get); LOAD_SYM(rsmi_dev_memory_busy_percent_get);
LOAD_SYM(rsmi_dev_gpu_clk_freq_get);
LOAD_SYM(rsmi_dev_power_ave_get); LOAD_SYM(rsmi_dev_power_ave_get);
LOAD_SYM(rsmi_dev_memory_total_get); LOAD_SYM(rsmi_dev_memory_total_get);
LOAD_SYM(rsmi_dev_memory_usage_get); LOAD_SYM(rsmi_dev_memory_usage_get);
@ -1255,6 +1335,27 @@ namespace Gpu {
return false; return false;
} }
#if !defined(RSMI_STATIC)
//? Check version
rsmi_version_t version;
result = rsmi_version_get(&version);
if (result != RSMI_STATUS_SUCCESS) {
Logger::warning("ROCm SMI: Failed to get version");
return false;
} else if (version.major == 5) {
if ((rsmi_dev_gpu_clk_freq_get_v5 = (decltype(rsmi_dev_gpu_clk_freq_get_v5))load_rsmi_sym("rsmi_dev_gpu_clk_freq_get")) == nullptr)
return false;
// In the release tarballs of rocm 6.0.0 and 6.0.2 the version queried with rsmi_version_get is 7.0.0.0
} else if (version.major == 6 || version.major == 7) {
if ((rsmi_dev_gpu_clk_freq_get_v6 = (decltype(rsmi_dev_gpu_clk_freq_get_v6))load_rsmi_sym("rsmi_dev_gpu_clk_freq_get")) == nullptr)
return false;
} else {
Logger::warning("ROCm SMI: Dynamic loading only supported for version 5 and 6");
return false;
}
version_major = version.major;
#endif
//? Device count //? Device count
result = rsmi_num_monitor_devices(&device_count); result = rsmi_num_monitor_devices(&device_count);
if (result != RSMI_STATUS_SUCCESS) { if (result != RSMI_STATUS_SUCCESS) {
@ -1338,7 +1439,46 @@ namespace Gpu {
if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false; if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false;
} else gpus_slice[i].mem_utilization_percent.push_back((long long)utilization); } else gpus_slice[i].mem_utilization_percent.push_back((long long)utilization);
} }
#if !defined(RSMI_STATIC)
//? Clock speeds
if (gpus_slice[i].supported_functions.gpu_clock) {
if (version_major == 5) {
rsmi_frequencies_t_v5 frequencies;
result = rsmi_dev_gpu_clk_freq_get_v5(i, RSMI_CLK_TYPE_SYS, &frequencies);
if (result != RSMI_STATUS_SUCCESS) {
Logger::warning("ROCm SMI: Failed to get GPU clock speed: ");
if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false;
} else gpus_slice[i].gpu_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz
}
else if (version_major == 6 || version_major == 7) {
rsmi_frequencies_t_v6 frequencies;
result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_SYS, &frequencies);
if (result != RSMI_STATUS_SUCCESS) {
Logger::warning("ROCm SMI: Failed to get GPU clock speed: ");
if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false;
} else gpus_slice[i].gpu_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz
}
}
if (gpus_slice[i].supported_functions.mem_clock) {
if (version_major == 5) {
rsmi_frequencies_t_v5 frequencies;
result = rsmi_dev_gpu_clk_freq_get_v5(i, RSMI_CLK_TYPE_MEM, &frequencies);
if (result != RSMI_STATUS_SUCCESS) {
Logger::warning("ROCm SMI: Failed to get VRAM clock speed: ");
if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false;
} else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz
}
else if (version_major == 6 || version_major == 7) {
rsmi_frequencies_t_v6 frequencies;
result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_MEM, &frequencies);
if (result != RSMI_STATUS_SUCCESS) {
Logger::warning("ROCm SMI: Failed to get VRAM clock speed: ");
if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false;
} else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz
}
}
#else
//? Clock speeds //? Clock speeds
if (gpus_slice[i].supported_functions.gpu_clock) { if (gpus_slice[i].supported_functions.gpu_clock) {
rsmi_frequencies_t frequencies; rsmi_frequencies_t frequencies;
@ -1357,6 +1497,7 @@ namespace Gpu {
if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false;
} else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz } else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz
} }
#endif
//? Power usage & state //? Power usage & state
if (gpus_slice[i].supported_functions.pwr_usage) { if (gpus_slice[i].supported_functions.pwr_usage) {
@ -1482,10 +1623,10 @@ namespace Gpu {
#endif #endif
namespace Mem { namespace Mem {
bool has_swap{}; // defaults to false bool has_swap{};
vector<string> fstab; vector<string> fstab;
fs::file_time_type fstab_time; fs::file_time_type fstab_time;
int disk_ios{}; // defaults to 0 int disk_ios{};
vector<string> last_found; vector<string> last_found;
//?* Find the filepath to the specified ZFS object's stat file //?* Find the filepath to the specified ZFS object's stat file
@ -1613,7 +1754,7 @@ namespace Mem {
auto only_physical = Config::getB("only_physical"); auto only_physical = Config::getB("only_physical");
auto zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto zfs_hide_datasets = Config::getB("zfs_hide_datasets");
auto& disks = mem.disks; auto& disks = mem.disks;
static unordered_flat_map<string, future<pair<disk_info, int>>> disks_stats_promises; static std::unordered_map<string, future<pair<disk_info, int>>> disks_stats_promises;
ifstream diskread; ifstream diskread;
vector<string> filter; vector<string> filter;
@ -1920,7 +2061,7 @@ namespace Mem {
if (access(zfs_pool_stat_path.c_str(), R_OK) == 0) { if (access(zfs_pool_stat_path.c_str(), R_OK) == 0) {
return zfs_pool_stat_path; return zfs_pool_stat_path;
} else { } else {
Logger::debug("Cant access folder: " + zfs_pool_stat_path.string()); Logger::debug("Can't access folder: " + zfs_pool_stat_path.string());
return ""; return "";
} }
} }
@ -1973,10 +2114,10 @@ namespace Mem {
int64_t bytes_read; int64_t bytes_read;
int64_t bytes_write; int64_t bytes_write;
int64_t io_ticks; int64_t io_ticks;
int64_t bytes_read_total{}; // defaults to 0 int64_t bytes_read_total{};
int64_t bytes_write_total{}; // defaults to 0 int64_t bytes_write_total{};
int64_t io_ticks_total{}; // defaults to 0 int64_t io_ticks_total{};
int64_t objects_read{}; // defaults to 0 int64_t objects_read{};
// looking through all files that start with 'objset' // looking through all files that start with 'objset'
for (const auto& file: fs::directory_iterator(disk.stat)) { for (const auto& file: fs::directory_iterator(disk.stat)) {
@ -2048,25 +2189,15 @@ namespace Mem {
} }
namespace Net { namespace Net {
unordered_flat_map<string, net_info> current_net; std::unordered_map<string, net_info> current_net;
net_info empty_net = {}; net_info empty_net = {};
vector<string> interfaces; vector<string> interfaces;
string selected_iface; string selected_iface;
int errors{}; // defaults to 0 int errors{};
unordered_flat_map<string, uint64_t> graph_max = { {"download", {}}, {"upload", {}} }; std::unordered_map<string, uint64_t> graph_max = { {"download", {}}, {"upload", {}} };
unordered_flat_map<string, array<int, 2>> max_count = { {"download", {}}, {"upload", {}} }; std::unordered_map<string, array<int, 2>> max_count = { {"download", {}}, {"upload", {}} };
bool rescale{true}; bool rescale{true};
uint64_t timestamp{}; // defaults to 0 uint64_t timestamp{};
//* 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 collect(bool no_update) -> net_info& {
if (Runner::stopping) return empty_net; if (Runner::stopping) return empty_net;
@ -2078,10 +2209,10 @@ namespace Net {
if (not no_update and errors < 3) { if (not no_update and errors < 3) {
//? Get interface list using getifaddrs() wrapper //? Get interface list using getifaddrs() wrapper
getifaddr_wrapper if_wrap {}; IfAddrsPtr if_addrs {};
if (if_wrap.status != 0) { if (if_addrs.get_status() != 0) {
errors++; errors++;
Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_addrs.get_status()));
redraw = true; redraw = true;
return empty_net; return empty_net;
} }
@ -2093,7 +2224,7 @@ namespace Net {
string ipv4, ipv6; string ipv4, ipv6;
//? Iteration over all items in getifaddrs() list //? Iteration over all items in getifaddrs() list
for (auto* ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) { for (auto* ifa = if_addrs.get(); ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr) continue; if (ifa->ifa_addr == nullptr) continue;
family = ifa->ifa_addr->sa_family; family = ifa->ifa_addr->sa_family;
const auto& iface = ifa->ifa_name; const auto& iface = ifa->ifa_name;
@ -2135,7 +2266,7 @@ namespace Net {
} //else, ignoring family==AF_PACKET (see man 3 getifaddrs) which is the first one in the `for` loop. } //else, ignoring family==AF_PACKET (see man 3 getifaddrs) which is the first one in the `for` loop.
} }
//? Get total recieved and transmitted bytes + device address if no ip was found //? Get total received and transmitted bytes + device address if no ip was found
for (const auto& iface : interfaces) { for (const auto& iface : interfaces) {
if (net.at(iface).ipv4.empty() and net.at(iface).ipv6.empty()) if (net.at(iface).ipv4.empty() and net.at(iface).ipv6.empty())
net.at(iface).ipv4 = readfile("/sys/class/net/" + iface + "/address"); net.at(iface).ipv4 = readfile("/sys/class/net/" + iface + "/address");
@ -2145,7 +2276,7 @@ namespace Net {
auto& saved_stat = net.at(iface).stat.at(dir); auto& saved_stat = net.at(iface).stat.at(dir);
auto& bandwidth = net.at(iface).bandwidth.at(dir); auto& bandwidth = net.at(iface).bandwidth.at(dir);
uint64_t val{}; // defaults to 0 uint64_t val{};
try { val = (uint64_t)stoull(readfile(sys_file, "0")); } try { val = (uint64_t)stoull(readfile(sys_file, "0")); }
catch (const std::invalid_argument&) {} catch (const std::invalid_argument&) {}
catch (const std::out_of_range&) {} catch (const std::out_of_range&) {}
@ -2193,7 +2324,6 @@ namespace Net {
else else
it++; it++;
} }
net.compact();
} }
timestamp = new_timestamp; timestamp = new_timestamp;
@ -2263,22 +2393,22 @@ namespace Net {
namespace Proc { namespace Proc {
vector<proc_info> current_procs; vector<proc_info> current_procs;
unordered_flat_map<string, string> uid_user; std::unordered_map<string, string> uid_user;
string current_sort; string current_sort;
string current_filter; string current_filter;
bool current_rev{}; // defaults to false bool current_rev{};
fs::file_time_type passwd_time; fs::file_time_type passwd_time;
uint64_t cputimes; uint64_t cputimes;
int collapse = -1, expand = -1; int collapse = -1, expand = -1;
uint64_t old_cputimes{}; // defaults to 0 uint64_t old_cputimes{};
atomic<int> numpids{}; // defaults to 0 atomic<int> numpids{};
int filter_found{}; // defaults to 0 int filter_found{};
detail_container detailed; detail_container detailed;
constexpr size_t KTHREADD = 2; constexpr size_t KTHREADD = 2;
static robin_hood::unordered_set<size_t> kernels_procs = {KTHREADD}; static std::unordered_set<size_t> kernels_procs = {KTHREADD};
//* Get detailed info for selected process //* Get detailed info for selected process
void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs) { void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs) {
@ -2406,7 +2536,7 @@ namespace Proc {
const int cmult = (per_core) ? Shared::coreCount : 1; const int cmult = (per_core) ? Shared::coreCount : 1;
bool got_detailed = false; bool got_detailed = false;
static size_t proc_clear_count{}; // defaults to 0 static size_t proc_clear_count{};
//* Use pids from last update if only changing filter, sorting or tree options //* Use pids from last update if only changing filter, sorting or tree options
if (no_update and not current_procs.empty()) { if (no_update and not current_procs.empty()) {
@ -2481,7 +2611,7 @@ namespace Proc {
//? Check if pid already exists in current_procs //? Check if pid already exists in current_procs
auto find_old = rng::find(current_procs, pid, &proc_info::pid); auto find_old = rng::find(current_procs, pid, &proc_info::pid);
bool no_cache{}; // defaults to false bool no_cache{};
if (find_old == current_procs.end()) { if (find_old == current_procs.end()) {
current_procs.push_back({pid}); current_procs.push_back({pid});
find_old = current_procs.end() - 1; find_old = current_procs.end() - 1;

1272
src/openbsd/btop_collect.cpp Normal file

File diff suppressed because it is too large Load diff

157
src/openbsd/internal.h Normal file
View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2019-2021 Brian Callahan <bcallah@openbsd.org>
*
* 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 },
};

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2019-2021 Brian Callahan <bcallah@openbsd.org>
*
* 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 <sys/types.h>
#include <sys/sysctl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#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);
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Brian Callahan <bcallah@openbsd.org>
*
* 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 <sys/types.h>
#include <sys/sysctl.h>
extern int sysctlbyname(const char *, void *, size_t *, void *, size_t);

View file

@ -16,6 +16,7 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <Availability.h>
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h> #include <IOKit/IOKitLib.h>
#include <arpa/inet.h> #include <arpa/inet.h>
@ -44,6 +45,7 @@ tab-size = 4
#include <netinet/in.h> // for inet_ntop #include <netinet/in.h> // for inet_ntop
#include <unistd.h> #include <unistd.h>
#include <stdexcept> #include <stdexcept>
#include <utility>
#include <cmath> #include <cmath>
#include <fstream> #include <fstream>
@ -56,7 +58,9 @@ tab-size = 4
#include "../btop_shared.hpp" #include "../btop_shared.hpp"
#include "../btop_tools.hpp" #include "../btop_tools.hpp"
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
#include "sensors.hpp" #include "sensors.hpp"
#endif
#include "smc.hpp" #include "smc.hpp"
using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater;
@ -95,7 +99,7 @@ namespace Cpu {
string cpu_sensor; string cpu_sensor;
vector<string> core_sensors; vector<string> core_sensors;
unordered_flat_map<int, int> core_mapping; std::unordered_map<int, int> core_mapping;
} // namespace Cpu } // namespace Cpu
namespace Mem { namespace Mem {
@ -187,11 +191,11 @@ namespace Cpu {
string cpuHz; string cpuHz;
bool has_battery = true; bool has_battery = true;
bool macM1 = false; bool macM1 = false;
tuple<int, long, string> current_bat; tuple<int, float, long, string> current_bat;
const array<string, 10> time_names = {"user", "nice", "system", "idle"}; const array<string, 10> time_names = {"user", "nice", "system", "idle"};
unordered_flat_map<string, long long> cpu_old = { std::unordered_map<string, long long> cpu_old = {
{"totals", 0}, {"totals", 0},
{"idles", 0}, {"idles", 0},
{"user", 0}, {"user", 0},
@ -250,6 +254,7 @@ namespace Cpu {
Logger::debug("get_sensors(): show_coretemp=" + std::to_string(Config::getB("show_coretemp")) + " check_temp=" + std::to_string(Config::getB("check_temp"))); Logger::debug("get_sensors(): show_coretemp=" + std::to_string(Config::getB("show_coretemp")) + " check_temp=" + std::to_string(Config::getB("check_temp")));
got_sensors = false; got_sensors = false;
if (Config::getB("show_coretemp") and Config::getB("check_temp")) { if (Config::getB("show_coretemp") and Config::getB("check_temp")) {
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
ThermalSensors sensors; ThermalSensors sensors;
if (sensors.getSensors() > 0) { if (sensors.getSensors() > 0) {
Logger::debug("M1 sensors found"); Logger::debug("M1 sensors found");
@ -257,6 +262,7 @@ namespace Cpu {
cpu_temp_only = true; cpu_temp_only = true;
macM1 = true; macM1 = true;
} else { } else {
#endif
// try SMC (intel) // try SMC (intel)
Logger::debug("checking intel"); Logger::debug("checking intel");
SMCConnection smcCon; SMCConnection smcCon;
@ -281,7 +287,9 @@ namespace Cpu {
// ignore, we don't have temp // ignore, we don't have temp
got_sensors = false; got_sensors = false;
} }
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
} }
#endif
} }
return got_sensors; return got_sensors;
} }
@ -290,11 +298,12 @@ namespace Cpu {
current_cpu.temp_max = 95; // we have no idea how to get the critical temp current_cpu.temp_max = 95; // we have no idea how to get the critical temp
try { try {
if (macM1) { if (macM1) {
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
ThermalSensors sensors; ThermalSensors sensors;
current_cpu.temp.at(0).push_back(sensors.getSensors()); current_cpu.temp.at(0).push_back(sensors.getSensors());
if (current_cpu.temp.at(0).size() > 20) if (current_cpu.temp.at(0).size() > 20)
current_cpu.temp.at(0).pop_front(); current_cpu.temp.at(0).pop_front();
#endif
} else { } else {
SMCConnection smcCon; SMCConnection smcCon;
int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount; int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount;
@ -329,8 +338,8 @@ namespace Cpu {
return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3); return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3);
} }
auto get_core_mapping() -> unordered_flat_map<int, int> { auto get_core_mapping() -> std::unordered_map<int, int> {
unordered_flat_map<int, int> core_map; std::unordered_map<int, int> core_map;
if (cpu_temp_only) return core_map; if (cpu_temp_only) return core_map;
natural_t cpu_count; natural_t cpu_count;
@ -398,8 +407,8 @@ namespace Cpu {
~IOPSList_Wrap() { CFRelease(data); } ~IOPSList_Wrap() { CFRelease(data); }
}; };
auto get_battery() -> tuple<int, long, string> { auto get_battery() -> tuple<int, float, long, string> {
if (not has_battery) return {0, 0, ""}; if (not has_battery) return {0, 0, 0, ""};
uint32_t percent = -1; uint32_t percent = -1;
long seconds = -1; long seconds = -1;
@ -438,7 +447,7 @@ namespace Cpu {
has_battery = false; has_battery = false;
} }
} }
return {percent, seconds, status}; return {percent, -1, seconds, status};
} }
auto collect(bool no_update) -> cpu_info & { auto collect(bool no_update) -> cpu_info & {
@ -591,7 +600,7 @@ namespace Mem {
io_object_t &object; io_object_t &object;
}; };
void collect_disk(unordered_flat_map<string, disk_info> &disks, unordered_flat_map<string, string> &mapping) { void collect_disk(std::unordered_map<string, disk_info> &disks, std::unordered_map<string, string> &mapping) {
io_registry_entry_t drive; io_registry_entry_t drive;
io_iterator_t drive_list; io_iterator_t drive_list;
@ -677,7 +686,7 @@ namespace Mem {
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&p, &info_size) == 0) { if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&p, &info_size) == 0) {
mem.stats.at("free") = p.free_count * Shared::pageSize; mem.stats.at("free") = p.free_count * Shared::pageSize;
mem.stats.at("cached") = p.external_page_count * Shared::pageSize; mem.stats.at("cached") = p.external_page_count * Shared::pageSize;
mem.stats.at("used") = (p.active_count + p.inactive_count + p.wire_count) * Shared::pageSize; mem.stats.at("used") = (p.active_count + p.wire_count) * Shared::pageSize;
mem.stats.at("available") = Shared::totalMem - mem.stats.at("used"); mem.stats.at("available") = Shared::totalMem - mem.stats.at("used");
} }
@ -708,7 +717,7 @@ namespace Mem {
} }
if (show_disks) { if (show_disks) {
unordered_flat_map<string, string> mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint std::unordered_map<string, string> mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint
double uptime = system_uptime(); double uptime = system_uptime();
auto &disks_filter = Config::getS("disks_filter"); auto &disks_filter = Config::getS("disks_filter");
bool filter_exclude = false; bool filter_exclude = false;
@ -821,13 +830,13 @@ namespace Mem {
} // namespace Mem } // namespace Mem
namespace Net { namespace Net {
unordered_flat_map<string, net_info> current_net; std::unordered_map<string, net_info> current_net;
net_info empty_net = {}; net_info empty_net = {};
vector<string> interfaces; vector<string> interfaces;
string selected_iface; string selected_iface;
int errors = 0; int errors = 0;
unordered_flat_map<string, uint64_t> graph_max = {{"download", {}}, {"upload", {}}}; std::unordered_map<string, uint64_t> graph_max = {{"download", {}}, {"upload", {}}};
unordered_flat_map<string, array<int, 2>> max_count = {{"download", {}}, {"upload", {}}}; std::unordered_map<string, array<int, 2>> max_count = {{"download", {}}, {"upload", {}}};
bool rescale = true; bool rescale = true;
uint64_t timestamp = 0; uint64_t timestamp = 0;
@ -904,7 +913,7 @@ namespace Net {
} // else, ignoring family==AF_LINK (see man 3 getifaddrs) } // else, ignoring family==AF_LINK (see man 3 getifaddrs)
} }
unordered_flat_map<string, std::tuple<uint64_t, uint64_t>> ifstats; std::unordered_map<string, std::tuple<uint64_t, uint64_t>> ifstats;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0};
size_t len; size_t len;
if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) {
@ -931,7 +940,7 @@ namespace Net {
} }
} }
//? Get total recieved and transmitted bytes + device address if no ip was found //? Get total received and transmitted bytes + device address if no ip was found
for (const auto &iface : interfaces) { for (const auto &iface : interfaces) {
for (const string dir : {"download", "upload"}) { for (const string dir : {"download", "upload"}) {
auto &saved_stat = net.at(iface).stat.at(dir); auto &saved_stat = net.at(iface).stat.at(dir);
@ -978,7 +987,6 @@ namespace Net {
else else
it++; it++;
} }
net.compact();
} }
timestamp = new_timestamp; timestamp = new_timestamp;
@ -1049,7 +1057,7 @@ namespace Net {
namespace Proc { namespace Proc {
vector<proc_info> current_procs; vector<proc_info> current_procs;
unordered_flat_map<string, string> uid_user; std::unordered_map<string, string> uid_user;
string current_sort; string current_sort;
string current_filter; string current_filter;
bool current_rev = false; bool current_rev = false;
@ -1204,10 +1212,14 @@ namespace Proc {
//? Get program name, command, username, parent pid, nice and status //? Get program name, command, username, parent pid, nice and status
if (no_cache) { if (no_cache) {
char fullname[PROC_PIDPATHINFO_MAXSIZE]; char fullname[PROC_PIDPATHINFO_MAXSIZE];
proc_pidpath(pid, fullname, sizeof(fullname)); int rc = proc_pidpath(pid, fullname, sizeof(fullname));
const string f_name = std::string(fullname); string f_name = "<defunct>";
size_t lastSlash = f_name.find_last_of('/'); if (rc != 0) {
new_proc.name = f_name.substr(lastSlash + 1); f_name = std::string(fullname);
size_t lastSlash = f_name.find_last_of('/');
f_name = f_name.substr(lastSlash + 1);
}
new_proc.name = f_name;
//? Get process arguments if possible, fallback to process path in case of failure //? Get process arguments if possible, fallback to process path in case of failure
if (Shared::arg_max > 0) { if (Shared::arg_max > 0) {
std::unique_ptr<char[]> proc_chars(new char[Shared::arg_max]); std::unique_ptr<char[]> proc_chars(new char[Shared::arg_max]);

View file

@ -16,6 +16,8 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
#include "sensors.hpp" #include "sensors.hpp"
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
@ -109,3 +111,4 @@ long long Cpu::ThermalSensors::getSensors() {
if (temps.empty()) return 0ll; if (temps.empty()) return 0ll;
return round(std::accumulate(temps.begin(), temps.end(), 0ll) / temps.size()); return round(std::accumulate(temps.begin(), temps.end(), 0ll) / temps.size());
} }
#endif

View file

@ -16,9 +16,12 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
namespace Cpu { namespace Cpu {
class ThermalSensors { class ThermalSensors {
public: public:
long long getSensors(); long long getSensors();
}; };
} // namespace Cpu } // namespace Cpu
#endif

View file

@ -18,7 +18,7 @@ theme[main_fg]="#cfd8dc"
# Title color for boxes # Title color for boxes
theme[title]="#ff" theme[title]="#ff"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#90" theme[hi_fg]="#90"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#2e3436"
# Title color for boxes # Title color for boxes
theme[title]="#2e3436" theme[title]="#2e3436"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#1a5fb4" theme[hi_fg]="#1a5fb4"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#99DFFF"
# Title color for boxes # Title color for boxes
theme[title]="#99FFFF" theme[title]="#99FFFF"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#FF7F00" theme[hi_fg]="#FF7F00"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -11,7 +11,7 @@ theme[main_fg]="#eee8d5"
# Title color for boxes # Title color for boxes
theme[title]="#eee8d5" theme[title]="#eee8d5"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#d1302c" theme[hi_fg]="#d1302c"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -0,0 +1,92 @@
# All graphs and meters can be gradients
# For single color graphs leave "mid" and "end" variable empty.
# Use "start" and "end" variables for two color gradient
# Use "start", "mid" and "end" for three color gradient
# Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#2d353b"
# Main text color
theme[main_fg]="#d3c6aa"
# Title color for boxes
theme[title]="#d3c6aa"
# Highlight color for keyboard shortcuts
theme[hi_fg]="#e67e80"
# Background color of selected items
theme[selected_bg]="#3d484d"
# Foreground color of selected items
theme[selected_fg]="#dbbc7f"
# Color of inactive/disabled text
theme[inactive_fg]="#2d353b"
# Color of text appearing on top of graphs, i.e uptime and current network graph scaling
theme[graph_text]="#d3c6aa"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#a7c080"
# Cpu box outline color
theme[cpu_box]="#3d484d"
# Memory/disks box outline color
theme[mem_box]="#3d484d"
# Net up/down box outline color
theme[net_box]="#3d484d"
# Processes box outline color
theme[proc_box]="#3d484d"
# Box divider line and small boxes line color
theme[div_line]="#3d484d"
# Temperature graph colors
theme[temp_start]="#a7c080"
theme[temp_mid]="#dbbc7f"
theme[temp_end]="#f85552"
# CPU graph colors
theme[cpu_start]="#a7c080"
theme[cpu_mid]="#dbbc7f"
theme[cpu_end]="#f85552"
# Mem/Disk free meter
theme[free_start]="#f85552"
theme[free_mid]="#dbbc7f"
theme[free_end]="#a7c080"
# Mem/Disk cached meter
theme[cached_start]="#7fbbb3"
theme[cached_mid]="#83c092"
theme[cached_end]="#a7c080"
# Mem/Disk available meter
theme[available_start]="#f85552"
theme[available_mid]="#dbbc7f"
theme[available_end]="#a7c080"
# Mem/Disk used meter
theme[used_start]="#a7c080"
theme[used_mid]="#dbbc7f"
theme[used_end]="#f85552"
# Download graph colors
theme[download_start]="#a7c080"
theme[download_mid]="#83c092"
theme[download_end]="#7fbbb3"
# Upload graph colors
theme[upload_start]="#dbbc7f"
theme[upload_mid]="#e69875"
theme[upload_end]="#e67e80"
# Process box color gradient for threads, mem and cpu usage
theme[process_start]="#a7c080"
theme[process_mid]="#e67e80"
theme[process_end]="#f85552"

View file

@ -18,7 +18,7 @@ theme[main_fg]="#737680"
# Title color for boxes # Title color for boxes
theme[title]="#272a34" theme[title]="#272a34"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#90" theme[hi_fg]="#90"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#E6E6E6"
# Title color for boxes # Title color for boxes
theme[title]="#ff" theme[title]="#ff"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#90" theme[hi_fg]="#90"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#bb"
# Title color for boxes # Title color for boxes
theme[title]="#cc" theme[title]="#cc"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#90" theme[hi_fg]="#90"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#a89984"
# Title color for boxes # Title color for boxes
theme[title]="#ebdbb2" theme[title]="#ebdbb2"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#d79921" theme[hi_fg]="#d79921"
# Background color of selected items # Background color of selected items

View file

@ -18,7 +18,7 @@ theme[main_fg]="#d4be98"
# Title color for boxes # Title color for boxes
theme[title]="#d4be98" theme[title]="#d4be98"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#ea6962" theme[hi_fg]="#ea6962"
# Background color of selected items # Background color of selected items

View file

@ -18,7 +18,7 @@ theme[main_fg]="#F8F8F2"
# Title color for boxes # Title color for boxes
theme[title]="#F8F8F2" theme[title]="#F8F8F2"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#2eb398" theme[hi_fg]="#2eb398"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#F8F8F2"
# Title color for boxes # Title color for boxes
theme[title]="#F8F8F2" theme[title]="#F8F8F2"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#F92672" theme[hi_fg]="#F92672"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#d6deeb"
# Title color for boxes # Title color for boxes
theme[title]="#ffffff" theme[title]="#ffffff"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#addb67" theme[hi_fg]="#addb67"
# Background color of selected items # Background color of selected items

View file

@ -18,7 +18,7 @@ theme[main_fg]="#D8DEE9"
# Title color for boxes # Title color for boxes
theme[title]="#8FBCBB" theme[title]="#8FBCBB"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#5E81AC" theme[hi_fg]="#5E81AC"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -10,7 +10,7 @@ theme[main_fg]="#abb2bf"
# Title color for boxes # Title color for boxes
theme[title]="#abb2bf" theme[title]="#abb2bf"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#61afef" theme[hi_fg]="#61afef"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -12,7 +12,7 @@ theme[main_fg]="#00"
# Title color for boxes # Title color for boxes
theme[title]="#00" theme[title]="#00"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#CC3E28" theme[hi_fg]="#CC3E28"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#eee8d5"
# Title color for boxes # Title color for boxes
theme[title]="#fdf6e3" theme[title]="#fdf6e3"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#b58900" theme[hi_fg]="#b58900"
# Background color of selected items # Background color of selected items

View file

@ -18,7 +18,7 @@ theme[main_fg]="#586e75"
# Title color for boxes # Title color for boxes
theme[title]="#002b36" theme[title]="#002b36"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#b58900" theme[hi_fg]="#b58900"
# Background color of selected items # Background color of selected items

View file

@ -10,7 +10,7 @@ theme[main_fg]="#cfc9c2"
# Title color for boxes # Title color for boxes
theme[title]="#cfc9c2" theme[title]="#cfc9c2"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#7dcfff" theme[hi_fg]="#7dcfff"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -10,7 +10,7 @@ theme[main_fg]="#cfc9c2"
# Title color for boxes # Title color for boxes
theme[title]="#cfc9c2" theme[title]="#cfc9c2"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#7dcfff" theme[hi_fg]="#7dcfff"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#c5c8c6"
# Title color for boxes # Title color for boxes
theme[title]="#c5c8c6" theme[title]="#c5c8c6"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#81beb7" theme[hi_fg]="#81beb7"
# Background color of selected item in processes box # Background color of selected item in processes box

View file

@ -18,7 +18,7 @@ theme[main_fg]="#30"
# Title color for boxes # Title color for boxes
theme[title]="#10" theme[title]="#10"
# Higlight color for keyboard shortcuts # Highlight color for keyboard shortcuts
theme[hi_fg]="#284d75" theme[hi_fg]="#284d75"
# Background color of selected item in processes box # Background color of selected item in processes box