diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7ed6a21..e4096cb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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.] **Info (please complete the following information):** - - btop++ version: `btop -v` + - btop++ version: `btop --version` - If using snap: `snap info btop` - Binary: [self compiled or static binary from release] - - (If compiled) Compiler and version: - Architecture: [x86_64, aarch64, etc.] `uname -m` - Platform: [Linux, FreeBSD, OsX] - (Linux) Kernel: `uname -r` diff --git a/.github/workflows/cmake-freebsd.yml b/.github/workflows/cmake-freebsd.yml index 6e687f1..3f1c962 100644 --- a/.github/workflows/cmake-freebsd.yml +++ b/.github/workflows/cmake-freebsd.yml @@ -33,8 +33,7 @@ jobs: with: release: '14.0' usesh: true - prepare: pkg install -y cmake ninja + prepare: pkg install -y cmake ninja lowdown run: | CXX=clang++ cmake -B build -G Ninja -DBTOP_STATIC=ON cmake --build build --verbose - diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index 49754d9..7864a04 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -30,11 +30,10 @@ jobs: - uses: actions/checkout@v4 - 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 run: CXX=clang++ LDFLAGS=-fuse-ld=lld cmake -B build -G Ninja -DBTOP_STATIC=ON - name: Compile run: cmake --build build --verbose - diff --git a/.github/workflows/cmake-macos.yml b/.github/workflows/cmake-macos.yml index 32d6f7f..ee2ef97 100644 --- a/.github/workflows/cmake-macos.yml +++ b/.github/workflows/cmake-macos.yml @@ -32,7 +32,7 @@ jobs: run: | export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew update --quiet - brew install --force --overwrite cmake llvm@17 ninja + brew install --force --overwrite cmake llvm@17 ninja lowdown - name: Configure run: | @@ -44,4 +44,3 @@ jobs: - name: Compile run: cmake --build build --verbose - diff --git a/.github/workflows/continuous-build-freebsd.yml b/.github/workflows/continuous-build-freebsd.yml index 041133f..5521ff7 100644 --- a/.github/workflows/continuous-build-freebsd.yml +++ b/.github/workflows/continuous-build-freebsd.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/osx/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-freebsd.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/osx/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-freebsd.yml' @@ -43,7 +45,7 @@ jobs: release: '14.0' usesh: true 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 run: | CXX=${{ matrix.compiler }} gmake STATIC=true STRIP=true @@ -57,4 +59,3 @@ jobs: name: btop-x86_64-freebsd-14 path: 'bin/*' if-no-files-found: error - diff --git a/.github/workflows/continuous-build-gpu.yml b/.github/workflows/continuous-build-gpu.yml new file mode 100644 index 0000000..e3bdf01 --- /dev/null +++ b/.github/workflows/continuous-build-gpu.yml @@ -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 + diff --git a/.github/workflows/continuous-build-linux.yml b/.github/workflows/continuous-build-linux.yml index 3ef236c..39de640 100644 --- a/.github/workflows/continuous-build-linux.yml +++ b/.github/workflows/continuous-build-linux.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-linux.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-linux.yml' diff --git a/.github/workflows/continuous-build-macos.yml b/.github/workflows/continuous-build-macos.yml index 717e9a7..c8915dd 100644 --- a/.github/workflows/continuous-build-macos.yml +++ b/.github/workflows/continuous-build-macos.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-macos.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-macos.yml' @@ -44,18 +46,18 @@ jobs: with: name: btop-x86_64-macos11-BigSur path: 'bin/*' - + build-macos12: runs-on: macos-12 steps: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: latest-stable - uses: actions/checkout@v3 with: submodules: recursive - + - name: Compile run: | make CXX=g++-12 ARCH=x86_64 STATIC=true STRIP=true diff --git a/.github/workflows/continuous-build-openbsd.yml b/.github/workflows/continuous-build-openbsd.yml new file mode 100644 index 0000000..6925ebb --- /dev/null +++ b/.github/workflows/continuous-build-openbsd.yml @@ -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 diff --git a/.github/workflows/test-snap-can-build.yml b/.github/workflows/test-snap-can-build.yml index bd63d14..6df8e47 100644 --- a/.github/workflows/test-snap-can-build.yml +++ b/.github/workflows/test-snap-can-build.yml @@ -1,18 +1,26 @@ name: 🧪 Test snap can be built on x86_64 on: + workflow_dispatch: push: - branches: [ main ] + branches: [ main ] tags-ignore: - '*.*' - paths: + paths: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' - 'include/**' - 'Makefile' - - '.github/workflows/continuous-build-linux.yml' - pull_request: - branches: [ main ] + - '.github/workflows/test-snap-can-build.yml' + pull_request: + branches: [ main ] + paths: + - 'src/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/test-snap-can-build.yml' jobs: build: diff --git a/.gitignore b/.gitignore index 7e2ed8f..3a2cae2 100644 --- a/.gitignore +++ b/.gitignore @@ -46,9 +46,14 @@ stage/ *.out *.app +# Compiled man page +btop.1 + build bin btop +/obj/ +config.h .*/ # Optional libraries diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf273b..d2c4bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 -* 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 @@ -42,6 +66,16 @@ * 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 ## v1.2.13 @@ -146,7 +180,7 @@ * 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 @@ -442,7 +476,7 @@ * 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f8c546..9a4dbec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,6 @@ if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") endif() project("btop" - VERSION 1.2.13 DESCRIPTION "A monitor of resources" HOMEPAGE_URL "https://github.com/aristocratos/btop" LANGUAGES CXX @@ -41,6 +40,7 @@ option(BTOP_LTO "Enable LTO" ON) option(BTOP_USE_MOLD "Use mold to link btop" OFF) option(BTOP_PEDANTIC "Enable a bunch of additional warnings" 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) 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) elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") 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) target_sources(btop PRIVATE src/linux/btop_collect.cpp) else() @@ -75,6 +77,21 @@ if(NOT CXX_HAVE_RANGES) message(FATAL_ERROR "The compiler doesn't support ") 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_ipo_supported(RESULT ipo_supported) if(ipo_supported AND BTOP_LTO) @@ -97,7 +114,7 @@ if(BTOP_WERROR) endif() if(NOT APPLE) - target_compile_options(btop PRIVATE -fstack-clash-protection) + target_compile_options(btop PRIVATE -fstack-clash-protection) endif() check_cxx_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR) if(HAS_FSTACK_PROTECTOR) @@ -109,10 +126,11 @@ if(HAS_FCF_PROTECTION) endif() target_compile_definitions(btop PRIVATE + FMT_HEADER_ONLY _FILE_OFFSET_BITS=64 $<$:_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1> # Only has an effect with optimizations enabled - $<$>:_FORTIFY_SOURCE=2> + $<$>,$>:_FORTIFY_SOURCE=3> ) target_include_directories(btop SYSTEM PRIVATE include) @@ -129,7 +147,7 @@ if(LINUX AND BTOP_GPU) if(BTOP_RSMI_STATIC) # ROCm doesn't properly add it's folders to the module path if `CMAKE_MODULE_PATH` is already # 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) unset(CMAKE_MODULE_PATH) @@ -178,12 +196,34 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") endif() 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) find_package(elf REQUIRED) - find_package(kvm REQUIRED) - target_link_libraries(btop elf::elf kvm::kvm) + target_link_libraries(btop elf::elf) 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() 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.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "btop.svg") install(DIRECTORY "themes" DESTINATION "share/btop") - diff --git a/Makefile b/Makefile index 970e818..e489164 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,11 @@ ifeq ($(GPU_SUPPORT),true) override ADDFLAGS += -DGPU_SUPPORT endif +FORTIFY_SOURCE ?= true +ifeq ($(FORTIFY_SOURCE),true) + override ADDFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 +endif + #? Compiler and Linker ifeq ($(shell $(CXX) --version | grep clang >/dev/null 2>&1; echo $$?),0) override CXX_IS_CLANG := true @@ -61,6 +66,10 @@ CLANG_WORKS = false GCC_WORKS = false MIN_CLANG_VERSION = 16 +ifeq ($(DEBUG),true) + override ADDFLAGS += -DBTOP_DEBUG +endif + #? Supported is Clang 16.0.0 and later ifeq ($(CXX_IS_CLANG),true) 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) CLANG_WORKS := true endif -endif -ifeq ($(CLANG_WORKS),false) - #? Try to find a newer GCC version - ifeq ($(shell command -v g++-13 >/dev/null; echo $$?),0) - CXX := g++-13 - else ifeq ($(shell command -v g++13 >/dev/null; echo $$?),0) - CXX := g++13 - else ifeq ($(shell command -v g++-12 >/dev/null; echo $$?),0) - CXX := g++-12 - else ifeq ($(shell command -v g++12 >/dev/null; echo $$?),0) - CXX := g++12 - else ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0) - CXX := g++-11 - else ifeq ($(shell command -v g++11 >/dev/null; echo $$?),0) - CXX := g++11 - else ifeq ($(shell command -v g++ >/dev/null; echo $$?),0) - CXX := g++ - else - GCC_NOT_FOUND := true - endif - ifndef GCC_NOT_FOUND - override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0) - override CXX_VERSION_MAJOR := $(shell echo $(CXX_VERSION) | cut -d '.' -f 1) - ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0) - GCC_WORKS := true - endif +else + ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0) + GCC_WORKS := true endif endif @@ -154,6 +140,12 @@ else ifeq ($(PLATFORM_LC),macos) THREADS := $(shell sysctl -n hw.ncpu || echo 1) override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation SU_GROUP := wheel +else ifeq ($(PLATFORM_LC),openbsd) + PLATFORM_DIR := openbsd + THREADS := $(shell sysctl -n hw.ncpu || echo 1) + override ADDFLAGS += -lkvm -static-libstdc++ + export MAKE = gmake + SU_GROUP := wheel else $(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m")) endif @@ -171,6 +163,12 @@ else LTO := $(THREADS) 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 SRCDIR := src INCDIRS := include $(wildcard lib/**/include) @@ -187,10 +185,10 @@ override GOODFLAGS := $(foreach flag,$(TESTFLAGS),$(strip $(shell echo "int main override REQFLAGS := -std=c++20 WARNFLAGS := -Wall -Wextra -pedantic 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 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 ifdef DEBUG @@ -230,7 +228,7 @@ endif #? Default Make .ONESHELL: -all: | info rocm_smi info-quiet directories btop +all: | info rocm_smi info-quiet directories btop.1 config.h btop ifneq ($(QUIET),true) info: @@ -251,7 +249,6 @@ info: @true endif - 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" @@ -275,17 +272,33 @@ directories: @$(VERBOSE) || printf "mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)\n" @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: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @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 distclean: clean @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @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: @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" @mkdir -p $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps @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 setuid: @@ -316,17 +333,20 @@ setuid: @printf "\033[1;92mSetting SUID bit\033[0m\n" @chmod u+s $(DESTDIR)$(PREFIX)/bin/btop +# With 'rm -v' user will see what files (if any) got removed uninstall: @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" - @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" - @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" - @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" - @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 -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) @@ -370,7 +390,7 @@ btop: $(OBJECTS) | rocm_smi directories #? Compile .ONESHELL: -$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories +$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories config.h @sleep 0.3 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") @$(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" #? Non-File Targets -.PHONY: all msg help pre +.PHONY: all config.h msg help pre diff --git a/README.md b/README.md index 47f8f08..fdd4c31 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) ![macOS](https://img.shields.io/badge/-OSX-black?logo=apple) ![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd) +![OpenBSD](https://img.shields.io/badge/-OpenBSD-black?logo=openbsd) ![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow) ![c++20](https://img.shields.io/badge/cpp-c%2B%2B20-green) ![latest_release](https://img.shields.io/github/v/tag/aristocratos/btop?label=release) @@ -17,6 +18,7 @@ [![Continuous Build Linux](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml) [![Continuous Build macOS](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml) [![Continuous Build FreeBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml) +[![Continuous Build OpenBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-openbsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-openbsd.yml) ## Index @@ -33,6 +35,7 @@ * [Compilation Linux](#compilation-linux) * [Compilation macOS](#compilation-macos-osx) * [Compilation FreeBSD](#compilation-freebsd) +* [Compilation OpenBSD](#compilation-openbsd) * [GPU compatibility](#gpu-compatibility) * [Installing the snap](#installing-the-snap) * [Configurability](#configurability) @@ -40,6 +43,15 @@ ## 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 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 +
+More... + ##### 16 January 2022 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! -
-More... - ##### 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. @@ -94,7 +106,7 @@ If you want to help out, test for bugs/fix bugs or just try out the branches: **macOS / OSX** ```bash # 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 cd btop git checkout OSX @@ -103,7 +115,7 @@ gmake **FreeBSD** ```bash -sudo pkg install gmake gcc11 coreutils git +sudo pkg install gmake gcc11 coreutils git lowdown git clone https://github.com/aristocratos/btop.git cd btop 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 "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)** * 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** - 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: ```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 ```
@@ -364,11 +392,11 @@ Also needs a UTF8 locale and a font that covers: ### With Make -1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** +1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** ```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** @@ -391,17 +419,19 @@ Also needs a UTF8 locale and a font that covers: | `STATIC=true` | For static compilation | | `QUIET=true` | For less verbose output | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | | `ARCH=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `GPU_SUPPORT=` | 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 | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manually set which compiler to use | 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. -4. **Install** +4. **Install** ```bash 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. -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 sudo make setuid @@ -456,12 +486,12 @@ Also needs a UTF8 locale and a font that covers: 1. **Install build dependencies** - Requires Clang / GCC, CMake, Ninja and Git + Requires Clang / GCC, CMake, Ninja, Lowdown and Git For example, with Debian Bookworm: ```bash - sudo apt install cmake git g++ ninja-build + sudo apt install cmake git g++ ninja-build lowdown ``` 2. **Clone the repository** @@ -490,6 +520,7 @@ Also needs a UTF8 locale and a font that covers: | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | | `-DBTOP_GPU=` | Enable GPU support (ON by default) | | `-DBTOP_RSMI_STATIC=` | Build and link the ROCm SMI library statically (OFF by default) | | `-DCMAKE_INSTALL_PREFIX=` | 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)** ```bash - brew install coreutils make gcc@12 + brew install coreutils make gcc@12 lowdown ``` 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++) | | `QUIET=true` | For less verbose output | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | - | `ARCH=` | To manually set the target architecture | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | 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 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. -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 sudo gmake setuid @@ -622,11 +655,11 @@ Also needs a UTF8 locale and a font that covers: 1. **Install build dependencies** - Requires Clang, CMake, Ninja and Git + Requires Clang, CMake, Ninja, Lowdown and Git ```bash brew update --quiet - brew install cmake git llvm ninja + brew install cmake git llvm ninja lowdown ``` 2. **Clone the repository** @@ -660,6 +693,7 @@ Also needs a UTF8 locale and a font that covers: | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | To force any specific compiler, run `CXX= cmake -B build -G Ninja` @@ -702,7 +736,7 @@ Also needs a UTF8 locale and a font that covers: 1. **Install dependencies** ```bash - sudo pkg install gmake gcc11 coreutils git + sudo pkg install gmake gcc11 coreutils git lowdown ``` 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++) | | `QUIET=true` | For less verbose output | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | - | `ARCH=` | To manually set the target architecture | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | 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** ```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. @@ -747,7 +783,7 @@ Also needs a UTF8 locale and a font that covers: ```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. @@ -787,18 +823,18 @@ Also needs a UTF8 locale and a font that covers: 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._ FreeBSD 14 and later: ```bash - pkg install cmake ninja + pkg install cmake ninja lowdown ``` FreeBSD 13: ```bash - pkg install cmake gcc13 ninja + pkg install cmake gcc13 ninja lowdown ``` 2. **Clone the repository** @@ -836,6 +872,7 @@ Also needs a UTF8 locale and a font that covers: | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | _**Note:** Static linking does not work with GCC._ @@ -865,6 +902,169 @@ Also needs a UTF8 locale and a font that covers:
+## Compilation OpenBSD + + Requires at least GCC 10. + + Note that GNU make (`gmake`) is required to compile on OpenBSD. + +
+ + +### With gmake + + +1. **Install dependencies** + + ```bash + pkg_add gmake gcc%11 g++%11 coreutils git 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=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | 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 + ``` + +
+
+ + +### With CMake (Community maintained) + + +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=` | Enables link time optimization (ON by default) | + | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | + | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | + | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | + | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | + + To force any other compiler, run `CXX= 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 + ``` + +
+ ## Installing the snap [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) diff --git a/cmake/Modules/Findkvm.cmake b/cmake/Modules/Findkvm.cmake index a0847de..9e4d82b 100644 --- a/cmake/Modules/Findkvm.cmake +++ b/cmake/Modules/Findkvm.cmake @@ -3,7 +3,7 @@ # Find libkvm, the Kernel Data Access Library # -if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") +if(BSD) find_path(kvm_INCLUDE_DIR NAMES kvm.h) find_library(kvm_LIBRARY NAMES kvm) diff --git a/include/robin_hood.h b/include/robin_hood.h deleted file mode 100644 index 0af031f..0000000 --- a/include/robin_hood.h +++ /dev/null @@ -1,2544 +0,0 @@ -// ______ _____ ______ _________ -// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / -// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / -// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / -// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ -// _/_____/ -// -// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 -// https://github.com/martinus/robin-hood-hashing -// -// Licensed under the MIT License . -// SPDX-License-Identifier: MIT -// Copyright (c) 2018-2021 Martin Ankerl -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#ifndef ROBIN_HOOD_H_INCLUDED -#define ROBIN_HOOD_H_INCLUDED - -// see https://semver.org/ -#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes -#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner -#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes - -#include -#include -#include -#include -#include -#include // only to support hash of smart pointers -#include -#include -#include -#include -#if __cplusplus >= 201703L -# include -#endif - -// #define ROBIN_HOOD_LOG_ENABLED -#ifdef ROBIN_HOOD_LOG_ENABLED -# include -# define ROBIN_HOOD_LOG(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_LOG(x) -#endif - -// #define ROBIN_HOOD_TRACE_ENABLED -#ifdef ROBIN_HOOD_TRACE_ENABLED -# include -# define ROBIN_HOOD_TRACE(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_TRACE(x) -#endif - -// #define ROBIN_HOOD_COUNT_ENABLED -#ifdef ROBIN_HOOD_COUNT_ENABLED -# include -# define ROBIN_HOOD_COUNT(x) ++counts().x; -namespace robin_hood { -struct Counts { - uint64_t shiftUp{}; - uint64_t shiftDown{}; -}; -inline std::ostream& operator<<(std::ostream& os, Counts const& c) { - return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; -} - -static Counts& counts() { - static Counts counts{}; - return counts; -} -} // namespace robin_hood -#else -# define ROBIN_HOOD_COUNT(x) -#endif - -// all non-argument macros should use this facility. See -// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ -#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() - -// mark unused members with this macro -#define ROBIN_HOOD_UNUSED(identifier) - -// bitness -#if SIZE_MAX == UINT32_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 -#elif SIZE_MAX == UINT64_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 -#else -# error Unsupported bitness -#endif - -// endianess -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#endif - -// inline -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) -#endif - -// exceptions -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 -#endif - -// count leading/trailing bits -#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) -# ifdef _MSC_VER -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 -# endif -# include -# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ - [](size_t mask) noexcept -> int { \ - unsigned long index; \ - return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ - : ROBIN_HOOD(BITNESS); \ - }(x) -# else -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll -# endif -# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) -# endif -#endif - -// fallthrough -#ifndef __has_cpp_attribute // For backwards compatibility -# define __has_cpp_attribute(x) 0 -#endif -#if __has_cpp_attribute(clang::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] -#elif __has_cpp_attribute(gnu::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() -#endif - -// likely/unlikely -#ifdef _MSC_VER -# define ROBIN_HOOD_LIKELY(condition) condition -# define ROBIN_HOOD_UNLIKELY(condition) condition -#else -# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) -# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) -#endif - -// detect if native wchar_t type is availiable in MSVC -#ifdef _MSC_VER -# ifdef _NATIVE_WCHAR_T_DEFINED -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -#endif - -// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr -#ifdef _MSC_VER -# if _MSC_VER <= 1900 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -#endif - -// workaround missing "is_trivially_copyable" in g++ < 5.0 -// See https://stackoverflow.com/a/31798726/48181 -#if defined(__GNUC__) && __GNUC__ < 5 -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) -#else -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value -#endif - -// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() -#endif - -namespace robin_hood { - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) -# define ROBIN_HOOD_STD std -#else - -// c++11 compatibility layer -namespace ROBIN_HOOD_STD { -template -struct alignment_of - : std::integral_constant::type)> {}; - -template -class integer_sequence { -public: - using value_type = T; - static_assert(std::is_integral::value, "not integral type"); - static constexpr std::size_t size() noexcept { - return sizeof...(Ints); - } -}; -template -using index_sequence = integer_sequence; - -namespace detail_ { -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); - - template - struct IntSeqCombiner; - - template - struct IntSeqCombiner, integer_sequence> { - using TResult = integer_sequence; - }; - - using TResult = - typename IntSeqCombiner::TResult, - typename IntSeqImpl::TResult>::TResult; -}; - -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; -}; - -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; -}; -} // namespace detail_ - -template -using make_integer_sequence = typename detail_::IntSeqImpl::TResult; - -template -using make_index_sequence = make_integer_sequence; - -template -using index_sequence_for = make_index_sequence; - -} // namespace ROBIN_HOOD_STD - -#endif - -namespace detail { - -// make sure we static_cast to the correct type for hash_int -#if ROBIN_HOOD(BITNESS) == 64 -using SizeT = uint64_t; -#else -using SizeT = uint32_t; -#endif - -template -T rotr(T x, unsigned k) { - return (x >> k) | (x << (8U * sizeof(T) - k)); -} - -// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to -// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with -// care! -template -inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { - return reinterpret_cast(ptr); -} - -template -inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { - return reinterpret_cast(ptr); -} - -// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other -// inlinings more difficult. Throws are also generally the slow path. -template -[[noreturn]] ROBIN_HOOD(NOINLINE) -#if ROBIN_HOOD(HAS_EXCEPTIONS) - void doThrow(Args&&... args) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - throw E(std::forward(args)...); -} -#else - void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { - abort(); -} -#endif - -template -T* assertNotNull(T* t, Args&&... args) { - if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { - doThrow(std::forward(args)...); - } - return t; -} - -template -inline T unaligned_load(void const* ptr) noexcept { - // using memcpy so we don't get into unaligned load problems. - // compiler should optimize this very well anyways. - T t; - std::memcpy(&t, ptr, sizeof(T)); - return t; -} - -// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, -// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a -// pointer. -template -class BulkPoolAllocator { -public: - BulkPoolAllocator() noexcept = default; - - // does not copy anything, just creates a new allocator. - BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept - : mHead(nullptr) - , mListForFree(nullptr) {} - - BulkPoolAllocator(BulkPoolAllocator&& o) noexcept - : mHead(o.mHead) - , mListForFree(o.mListForFree) { - o.mListForFree = nullptr; - o.mHead = nullptr; - } - - BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { - reset(); - mHead = o.mHead; - mListForFree = o.mListForFree; - o.mListForFree = nullptr; - o.mHead = nullptr; - return *this; - } - - BulkPoolAllocator& - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { - // does not do anything - return *this; - } - - ~BulkPoolAllocator() noexcept { - reset(); - } - - // Deallocates all allocated memory. - void reset() noexcept { - while (mListForFree) { - T* tmp = *mListForFree; - ROBIN_HOOD_LOG("std::free") - std::free(mListForFree); - mListForFree = reinterpret_cast_no_cast_align_warning(tmp); - } - mHead = nullptr; - } - - // allocates, but does NOT initialize. Use in-place new constructor, e.g. - // T* obj = pool.allocate(); - // ::new (static_cast(obj)) T(); - T* allocate() { - T* tmp = mHead; - if (!tmp) { - tmp = performAllocation(); - } - - mHead = *reinterpret_cast_no_cast_align_warning(tmp); - return tmp; - } - - // does not actually deallocate but puts it in store. - // make sure you have already called the destructor! e.g. with - // obj->~T(); - // pool.deallocate(obj); - void deallocate(T* obj) noexcept { - *reinterpret_cast_no_cast_align_warning(obj) = mHead; - mHead = obj; - } - - // Adds an already allocated block of memory to the allocator. This allocator is from now on - // responsible for freeing the data (with free()). If the provided data is not large enough to - // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. - void addOrFree(void* ptr, const size_t numBytes) noexcept { - // calculate number of available elements in ptr - if (numBytes < ALIGNMENT + ALIGNED_SIZE) { - // not enough data for at least one element. Free and return. - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } else { - ROBIN_HOOD_LOG("add to buffer") - add(ptr, numBytes); - } - } - - void swap(BulkPoolAllocator& other) noexcept { - using std::swap; - swap(mHead, other.mHead); - swap(mListForFree, other.mListForFree); - } - -private: - // iterates the list of allocated memory to calculate how many to alloc next. - // Recalculating this each time saves us a size_t member. - // This ignores the fact that memory blocks might have been added manually with addOrFree. In - // practice, this should not matter much. - ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { - auto tmp = mListForFree; - size_t numAllocs = MinNumAllocs; - - while (numAllocs * 2 <= MaxNumAllocs && tmp) { - auto x = reinterpret_cast(tmp); - tmp = *x; - numAllocs *= 2; - } - - return numAllocs; - } - - // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). - void add(void* ptr, const size_t numBytes) noexcept { - const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; - - auto data = reinterpret_cast(ptr); - - // link free list - auto x = reinterpret_cast(data); - *x = mListForFree; - mListForFree = data; - - // create linked list for newly allocated data - auto* const headT = - reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); - - auto* const head = reinterpret_cast(headT); - - // Visual Studio compiler automatically unrolls this loop, which is pretty cool - for (size_t i = 0; i < numElements; ++i) { - *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = - head + (i + 1) * ALIGNED_SIZE; - } - - // last one points to 0 - *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = - mHead; - mHead = headT; - } - - // Called when no memory is available (mHead == 0). - // Don't inline this slow path. - ROBIN_HOOD(NOINLINE) T* performAllocation() { - size_t const numElementsToAlloc = calcNumElementsToAlloc(); - - // alloc new memory: [prev |T, T, ... T] - size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; - ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE - << " * " << numElementsToAlloc) - add(assertNotNull(std::malloc(bytes)), bytes); - return mHead; - } - - // enforce byte alignment of the T's -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) - static constexpr size_t ALIGNMENT = - (std::max)(std::alignment_of::value, std::alignment_of::value); -#else - static const size_t ALIGNMENT = - (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) - ? ROBIN_HOOD_STD::alignment_of::value - : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround -#endif - - static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; - - static_assert(MinNumAllocs >= 1, "MinNumAllocs"); - static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); - static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); - static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); - static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); - - T* mHead{nullptr}; - T** mListForFree{nullptr}; -}; - -template -struct NodeAllocator; - -// dummy allocator that does nothing -template -struct NodeAllocator { - - // we are not using the data, so just free it. - void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } -}; - -template -struct NodeAllocator : public BulkPoolAllocator {}; - -// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making -// my own here. -namespace swappable { -#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) -using std::swap; -template -struct nothrow { - static const bool value = noexcept(swap(std::declval(), std::declval())); -}; -#else -template -struct nothrow { - static const bool value = std::is_nothrow_swappable::value; -}; -#endif -} // namespace swappable - -} // namespace detail - -struct is_transparent_tag {}; - -// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, -// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is -// also tested. -template -struct pair { - using first_type = T1; - using second_type = T2; - - template ::value && - std::is_default_constructible::value>::type> - constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) - : first() - , second() {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair const& o) noexcept( - noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) - : first(o.first) - , second(o.second) {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair&& o) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(o.first)) - , second(std::move(o.second)) {} - - constexpr pair(T1&& a, T2&& b) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(a)) - , second(std::move(b)) {} - - template - constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( - std::declval()))) && noexcept(T2(std::forward(std::declval())))) - : first(std::forward(a)) - , second(std::forward(b)) {} - - template - // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members" - // if this constructor is constexpr -#if !ROBIN_HOOD(BROKEN_CONSTEXPR) - constexpr -#endif - pair(std::piecewise_construct_t /*unused*/, std::tuple a, - std::tuple - b) noexcept(noexcept(pair(std::declval&>(), - std::declval&>(), - ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()))) - : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()) { - } - - // constructor called from the std::piecewise_construct_t ctor - template - pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( - noexcept(T1(std::forward(std::get( - std::declval&>()))...)) && noexcept(T2(std:: - forward(std::get( - std::declval&>()))...))) - : first(std::forward(std::get(a))...) - , second(std::forward(std::get(b))...) { - // make visual studio compiler happy about warning about unused a & b. - // Visual studio's pair implementation disables warning 4100. - (void)a; - (void)b; - } - - void swap(pair& o) noexcept((detail::swappable::nothrow::value) && - (detail::swappable::nothrow::value)) { - using std::swap; - swap(first, o.first); - swap(second, o.second); - } - - T1 first; // NOLINT(misc-non-private-member-variables-in-classes) - T2 second; // NOLINT(misc-non-private-member-variables-in-classes) -}; - -template -inline void swap(pair& a, pair& b) noexcept( - noexcept(std::declval&>().swap(std::declval&>()))) { - a.swap(b); -} - -template -inline constexpr bool operator==(pair const& x, pair const& y) { - return (x.first == y.first) && (x.second == y.second); -} -template -inline constexpr bool operator!=(pair const& x, pair const& y) { - return !(x == y); -} -template -inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( - std::declval() < std::declval()) && noexcept(std::declval() < - std::declval())) { - return x.first < y.first || (!(y.first < x.first) && x.second < y.second); -} -template -inline constexpr bool operator>(pair const& x, pair const& y) { - return y < x; -} -template -inline constexpr bool operator<=(pair const& x, pair const& y) { - return !(x > y); -} -template -inline constexpr bool operator>=(pair const& x, pair const& y) { - return !(x < y); -} - -inline size_t hash_bytes(void const* ptr, size_t len) noexcept { - static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); - static constexpr uint64_t seed = UINT64_C(0xe17a1465); - static constexpr unsigned int r = 47; - - auto const* const data64 = static_cast(ptr); - uint64_t h = seed ^ (len * m); - - size_t const n_blocks = len / 8; - for (size_t i = 0; i < n_blocks; ++i) { - auto k = detail::unaligned_load(data64 + i); - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - auto const* const data8 = reinterpret_cast(data64 + n_blocks); - switch (len & 7U) { - case 7: - h ^= static_cast(data8[6]) << 48U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 6: - h ^= static_cast(data8[5]) << 40U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 5: - h ^= static_cast(data8[4]) << 32U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 4: - h ^= static_cast(data8[3]) << 24U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 3: - h ^= static_cast(data8[2]) << 16U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 2: - h ^= static_cast(data8[1]) << 8U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 1: - h ^= static_cast(data8[0]); - h *= m; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - default: - break; - } - - h ^= h >> r; - - // not doing the final step here, because this will be done by keyToIdx anyways - // h *= m; - // h ^= h >> r; - return static_cast(h); -} - -inline size_t hash_int(uint64_t x) noexcept { - // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested, - // and doesn't need any special 128bit operations. - x ^= x >> 33U; - x *= UINT64_C(0xff51afd7ed558ccd); - x ^= x >> 33U; - - // not doing the final step here, because this will be done by keyToIdx anyways - // x *= UINT64_C(0xc4ceb9fe1a85ec53); - // x ^= x >> 33U; - return static_cast(x); -} - -// A thin wrapper around std::hash, performing an additional simple mixing step of the result. -template -struct hash : public std::hash { - size_t operator()(T const& obj) const - noexcept(noexcept(std::declval>().operator()(std::declval()))) { - // call base hash - auto result = std::hash::operator()(obj); - // return mixed of that, to be save against identity has - return hash_int(static_cast(result)); - } -}; - -template -struct hash> { - size_t operator()(std::basic_string const& str) const noexcept { - return hash_bytes(str.data(), sizeof(CharT) * str.size()); - } -}; - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) -template -struct hash> { - size_t operator()(std::basic_string_view const& sv) const noexcept { - return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); - } -}; -#endif - -template -struct hash { - size_t operator()(T* ptr) const noexcept { - return hash_int(reinterpret_cast(ptr)); - } -}; - -template -struct hash> { - size_t operator()(std::unique_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } -}; - -template -struct hash> { - size_t operator()(std::shared_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } -}; - -template -struct hash::value>::type> { - size_t operator()(Enum e) const noexcept { - using Underlying = typename std::underlying_type::type; - return hash{}(static_cast(e)); - } -}; - -#define ROBIN_HOOD_HASH_INT(T) \ - template <> \ - struct hash { \ - size_t operator()(T const& obj) const noexcept { \ - return hash_int(static_cast(obj)); \ - } \ - } - -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif -// see https://en.cppreference.com/w/cpp/utility/hash -ROBIN_HOOD_HASH_INT(bool); -ROBIN_HOOD_HASH_INT(char); -ROBIN_HOOD_HASH_INT(signed char); -ROBIN_HOOD_HASH_INT(unsigned char); -ROBIN_HOOD_HASH_INT(char16_t); -ROBIN_HOOD_HASH_INT(char32_t); -#if ROBIN_HOOD(HAS_NATIVE_WCHART) -ROBIN_HOOD_HASH_INT(wchar_t); -#endif -ROBIN_HOOD_HASH_INT(short); -ROBIN_HOOD_HASH_INT(unsigned short); -ROBIN_HOOD_HASH_INT(int); -ROBIN_HOOD_HASH_INT(unsigned int); -ROBIN_HOOD_HASH_INT(long); -ROBIN_HOOD_HASH_INT(long long); -ROBIN_HOOD_HASH_INT(unsigned long); -ROBIN_HOOD_HASH_INT(unsigned long long); -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic pop -#endif -namespace detail { - -template -struct void_type { - using type = void; -}; - -template -struct has_is_transparent : public std::false_type {}; - -template -struct has_is_transparent::type> - : public std::true_type {}; - -// using wrapper classes for hash and key_equal prevents the diamond problem when the same type -// is used. see https://stackoverflow.com/a/28771920/48181 -template -struct WrapHash : public T { - WrapHash() = default; - explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} -}; - -template -struct WrapKeyEqual : public T { - WrapKeyEqual() = default; - explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} -}; - -// A highly optimized hashmap implementation, using the Robin Hood algorithm. -// -// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but -// be about 2x faster in most cases and require much less allocations. -// -// This implementation uses the following memory layout: -// -// [Node, Node, ... Node | info, info, ... infoSentinel ] -// -// * Node: either a DataNode that directly has the std::pair as member, -// or a DataNode with a pointer to std::pair. Which DataNode representation to use -// depends on how fast the swap() operation is. Heuristically, this is automatically choosen -// based on sizeof(). there are always 2^n Nodes. -// -// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. -// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the -// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it -// actually belongs to the previous position and was pushed out because that place is already -// taken. -// -// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the -// need for a idx variable. -// -// According to STL, order of templates has effect on throughput. That's why I've moved the -// boolean to the front. -// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ -template -class Table - : public WrapHash, - public WrapKeyEqual, - detail::NodeAllocator< - typename std::conditional< - std::is_void::value, Key, - robin_hood::pair::type, T>>::type, - 4, 16384, IsFlat> { -public: - static constexpr bool is_flat = IsFlat; - static constexpr bool is_map = !std::is_void::value; - static constexpr bool is_set = !is_map; - static constexpr bool is_transparent = - has_is_transparent::value && has_is_transparent::value; - - using key_type = Key; - using mapped_type = T; - using value_type = typename std::conditional< - is_set, Key, - robin_hood::pair::type, T>>::type; - using size_type = size_t; - using hasher = Hash; - using key_equal = KeyEqual; - using Self = Table; - -private: - static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, - "MaxLoadFactor100 needs to be >10 && < 100"); - - using WHash = WrapHash; - using WKeyEqual = WrapKeyEqual; - - // configuration defaults - - // make sure we have 8 elements, needed to quickly rehash mInfo - static constexpr size_t InitialNumElements = sizeof(uint64_t); - static constexpr uint32_t InitialInfoNumBits = 5; - static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; - static constexpr size_t InfoMask = InitialInfoInc - 1U; - static constexpr uint8_t InitialInfoHashShift = 0; - using DataPool = detail::NodeAllocator; - - // type needs to be wider than uint8_t. - using InfoType = uint32_t; - - // DataNode //////////////////////////////////////////////////////// - - // Primary template for the data node. We have special implementations for small and big - // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these - // on the heap so swap merely swaps a pointer. - template - class DataNode {}; - - // Small: just allocate on the stack. - template - class DataNode final { - public: - template - explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( - noexcept(value_type(std::forward(args)...))) - : mData(std::forward(args)...) {} - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( - std::is_nothrow_move_constructible::value) - : mData(std::move(n.mData)) {} - - // doesn't do anything - void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} - void destroyDoNotDeallocate() noexcept {} - - value_type const* operator->() const noexcept { - return &mData; - } - value_type* operator->() noexcept { - return &mData; - } - - const value_type& operator*() const noexcept { - return mData; - } - - value_type& operator*() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData.second; - } - - void swap(DataNode& o) noexcept( - noexcept(std::declval().swap(std::declval()))) { - mData.swap(o.mData); - } - - private: - value_type mData; - }; - - // big object: allocate on heap. - template - class DataNode { - public: - template - explicit DataNode(M& map, Args&&... args) - : mData(map.allocate()) { - ::new (static_cast(mData)) value_type(std::forward(args)...); - } - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept - : mData(std::move(n.mData)) {} - - void destroy(M& map) noexcept { - // don't deallocate, just put it into list of datapool. - mData->~value_type(); - map.deallocate(mData); - } - - void destroyDoNotDeallocate() noexcept { - mData->~value_type(); - } - - value_type const* operator->() const noexcept { - return mData; - } - - value_type* operator->() noexcept { - return mData; - } - - const value_type& operator*() const { - return *mData; - } - - value_type& operator*() { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData->second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData->second; - } - - void swap(DataNode& o) noexcept { - using std::swap; - swap(mData, o.mData); - } - - private: - value_type* mData; - }; - - using Node = DataNode; - - // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required) - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { - return n.getFirst(); - } - - // in case we have void mapped_type, we are not using a pair, thus we just route k through. - // No need to disable this because it's just not used if not applicable. - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { - return k; - } - - // in case we have non-void mapped_type, we have a standard robin_hood::pair - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, key_type const&>::type - getFirstConst(value_type const& vt) const noexcept { - return vt.first; - } - - // Cloner ////////////////////////////////////////////////////////// - - template - struct Cloner; - - // fast path: Just copy data, without allocating anything. - template - struct Cloner { - void operator()(M const& source, M& target) const { - auto const* const src = reinterpret_cast(source.mKeyVals); - auto* tgt = reinterpret_cast(target.mKeyVals); - auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); - std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); - } - }; - - template - struct Cloner { - void operator()(M const& s, M& t) const { - auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); - std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); - - for (size_t i = 0; i < numElementsWithBuffer; ++i) { - if (t.mInfo[i]) { - ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); - } - } - } - }; - - // Destroyer /////////////////////////////////////////////////////// - - template - struct Destroyer {}; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - } - }; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroy(m); - n.~Node(); - } - } - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroyDoNotDeallocate(); - n.~Node(); - } - } - } - }; - - // Iter //////////////////////////////////////////////////////////// - - struct fast_forward_tag {}; - - // generic iterator for both const_iterator and iterator. - template - // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) - class Iter { - private: - using NodePtr = typename std::conditional::type; - - public: - using difference_type = std::ptrdiff_t; - using value_type = typename Self::value_type; - using reference = typename std::conditional::type; - using pointer = typename std::conditional::type; - using iterator_category = std::forward_iterator_tag; - - // default constructed iterator can be compared to itself, but WON'T return true when - // compared to end(). - Iter() = default; - - // Rule of zero: nothing specified. The conversion constructor is only enabled for - // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. - - // Conversion constructor from iterator to const_iterator. - template ::type> - // NOLINTNEXTLINE(hicpp-explicit-conversions) - Iter(Iter const& other) noexcept - : mKeyVals(other.mKeyVals) - , mInfo(other.mInfo) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr, - fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) { - fastForward(); - } - - template ::type> - Iter& operator=(Iter const& other) noexcept { - mKeyVals = other.mKeyVals; - mInfo = other.mInfo; - return *this; - } - - // prefix increment. Undefined behavior if we are at end()! - Iter& operator++() noexcept { - mInfo++; - mKeyVals++; - fastForward(); - return *this; - } - - Iter operator++(int) noexcept { - Iter tmp = *this; - ++(*this); - return tmp; - } - - reference operator*() const { - return **mKeyVals; - } - - pointer operator->() const { - return &**mKeyVals; - } - - template - bool operator==(Iter const& o) const noexcept { - return mKeyVals == o.mKeyVals; - } - - template - bool operator!=(Iter const& o) const noexcept { - return mKeyVals != o.mKeyVals; - } - - private: - // fast forward to the next non-free info byte - // I've tried a few variants that don't depend on intrinsics, but unfortunately they are - // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. - void fastForward() noexcept { - size_t n = 0; - while (0U == (n = detail::unaligned_load(mInfo))) { - mInfo += sizeof(size_t); - mKeyVals += sizeof(size_t); - } -#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) - // we know for certain that within the next 8 bytes we'll find a non-zero one. - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 4; - mKeyVals += 4; - } - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 2; - mKeyVals += 2; - } - if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { - mInfo += 1; - mKeyVals += 1; - } -#else -# if ROBIN_HOOD(LITTLE_ENDIAN) - auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; -# else - auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; -# endif - mInfo += inc; - mKeyVals += inc; -#endif - } - - friend class Table; - NodePtr mKeyVals{nullptr}; - uint8_t const* mInfo{nullptr}; - }; - - //////////////////////////////////////////////////////////////////// - - // highly performance relevant code. - // Lower bits are used for indexing into the array (2^n size) - // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. - template - void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { - // In addition to whatever hash is used, add another mul & shift so we get better hashing. - // This serves as a bad hash prevention, if the given data is - // badly mixed. - auto h = static_cast(WHash::operator()(key)); - - h *= mHashMultiplier; - h ^= h >> 33U; - - // the lower InitialInfoNumBits are reserved for info. - *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); - *idx = (static_cast(h) >> InitialInfoNumBits) & mMask; - } - - // forwards the index by one, wrapping around at the end - void next(InfoType* info, size_t* idx) const noexcept { - *idx = *idx + 1; - *info += mInfoInc; - } - - void nextWhileLess(InfoType* info, size_t* idx) const noexcept { - // unrolling this by hand did not bring any speedups. - while (*info < mInfo[*idx]) { - next(info, idx); - } - } - - // Shift everything up by one element. Tries to move stuff around. - void - shiftUp(size_t startIdx, - size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { - auto idx = startIdx; - ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); - while (--idx != insertion_idx) { - mKeyVals[idx] = std::move(mKeyVals[idx - 1]); - } - - idx = startIdx; - while (idx != insertion_idx) { - ROBIN_HOOD_COUNT(shiftUp) - mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); - if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - --idx; - } - } - - void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { - // until we find one that is either empty or has zero offset. - // TODO(martinus) we don't need to move everything, just the last one for the same - // bucket. - mKeyVals[idx].destroy(*this); - - // until we find one that is either empty or has zero offset. - while (mInfo[idx + 1] >= 2 * mInfoInc) { - ROBIN_HOOD_COUNT(shiftDown) - mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); - mKeyVals[idx] = std::move(mKeyVals[idx + 1]); - ++idx; - } - - mInfo[idx] = 0; - // don't destroy, we've moved it - // mKeyVals[idx].destroy(*this); - mKeyVals[idx].~Node(); - } - - // copy of find(), except that it returns iterator instead of const_iterator. - template - ROBIN_HOOD(NODISCARD) - size_t findIdx(Other const& key) const { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - do { - // unrolling this twice gives a bit of a speedup. More unrolling did not help. - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found! - return mMask == 0 ? 0 - : static_cast(std::distance( - mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); - } - - void cloneData(const Table& o) { - Cloner()(o, *this); - } - - // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. - // @return True on success, false if something went wrong - void insert_move(Node&& keyval) { - // we don't retry, fail if overflowing - // don't need to check max num elements - if (0 == mMaxNumElementsAllowed && !try_increase_info()) { - throwOverflowError(); - } - - size_t idx{}; - InfoType info{}; - keyToIdx(keyval.getFirst(), &idx, &info); - - // skip forward. Use <= because we are certain that the element is not there. - while (info <= mInfo[idx]) { - idx = idx + 1; - info += mInfoInc; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = static_cast(info); - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - auto& l = mKeyVals[insertion_idx]; - if (idx == insertion_idx) { - ::new (static_cast(&l)) Node(std::move(keyval)); - } else { - shiftUp(idx, insertion_idx); - l = std::move(keyval); - } - - // put at empty spot - mInfo[insertion_idx] = insertion_info; - - ++mNumElements; - } - -public: - using iterator = Iter; - using const_iterator = Iter; - - Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) - : WHash() - , WKeyEqual() { - ROBIN_HOOD_TRACE(this) - } - - // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. - // This tremendously speeds up ctor & dtor of a map that never receives an element. The - // penalty is payed at the first insert, and not before. Lookup of this empty map works - // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the - // standard, but we can ignore it. - explicit Table( - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - } - - template - Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, - const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(first, last); - } - - Table(std::initializer_list initlist, - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(initlist.begin(), initlist.end()); - } - - Table(Table&& o) noexcept - : WHash(std::move(static_cast(o))) - , WKeyEqual(std::move(static_cast(o))) - , DataPool(std::move(static_cast(o))) { - ROBIN_HOOD_TRACE(this) - if (o.mMask) { - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - // set other's mask to 0 so its destructor won't do anything - o.init(); - } - } - - Table& operator=(Table&& o) noexcept { - ROBIN_HOOD_TRACE(this) - if (&o != this) { - if (o.mMask) { - // only move stuff if the other map actually has some data - destroy(); - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - WHash::operator=(std::move(static_cast(o))); - WKeyEqual::operator=(std::move(static_cast(o))); - DataPool::operator=(std::move(static_cast(o))); - - o.init(); - - } else { - // nothing in the other map => just clear us. - clear(); - } - } - return *this; - } - - Table(const Table& o) - : WHash(static_cast(o)) - , WKeyEqual(static_cast(o)) - , DataPool(static_cast(o)) { - ROBIN_HOOD_TRACE(this) - if (!o.empty()) { - // not empty: create an exact copy. it is also possible to just iterate through all - // elements and insert them, but copying is probably faster. - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mHashMultiplier = o.mHashMultiplier; - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - // no need for calloc because clonData does memcpy - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - } - } - - // Creates a copy of the given map. Copy constructor of each entry is used. - // Not sure why clang-tidy thinks this doesn't handle self assignment, it does - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - Table& operator=(Table const& o) { - ROBIN_HOOD_TRACE(this) - if (&o == this) { - // prevent assigning of itself - return *this; - } - - // we keep using the old allocator and not assign the new one, because we want to keep - // the memory available. when it is the same size. - if (o.empty()) { - if (0 == mMask) { - // nothing to do, we are empty too - return *this; - } - - // not empty: destroy what we have there - // clear also resets mInfo to 0, that's sometimes not necessary. - destroy(); - init(); - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - - return *this; - } - - // clean up old stuff - Destroyer::value>{}.nodes(*this); - - if (mMask != o.mMask) { - // no luck: we don't have the same array size allocated, so we need to realloc. - if (0 != mMask) { - // only deallocate if we actually have data! - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - - // no need for calloc here because cloneData performs a memcpy. - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - // sentinel is set in cloneData - } - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - mHashMultiplier = o.mHashMultiplier; - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - - return *this; - } - - // Swaps everything between the two maps. - void swap(Table& o) { - ROBIN_HOOD_TRACE(this) - using std::swap; - swap(o, *this); - } - - // Clears all data, without resizing. - void clear() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - // don't do anything! also important because we don't want to write to - // DummyInfoByte::b, even though we would just write 0 to it. - return; - } - - Destroyer::value>{}.nodes(*this); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - // clear everything, then set the sentinel again - uint8_t const z = 0; - std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // Destroys the map and all it's contents. - ~Table() { - ROBIN_HOOD_TRACE(this) - destroy(); - } - - // Checks if both tables contain the same entries. Order is irrelevant. - bool operator==(const Table& other) const { - ROBIN_HOOD_TRACE(this) - if (other.size() != size()) { - return false; - } - for (auto const& otherEntry : other) { - if (!has(otherEntry)) { - return false; - } - } - - return true; - } - - bool operator!=(const Table& other) const { - ROBIN_HOOD_TRACE(this) - return !operator==(other); - } - - template - typename std::enable_if::value, Q&>::type operator[](const key_type& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(key), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(key), std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - typename std::enable_if::value, Q&>::type operator[](key_type&& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - void insert(Iter first, Iter last) { - for (; first != last; ++first) { - // value_type ctor needed because this might be called with std::pair's - insert(value_type(*first)); - } - } - - void insert(std::initializer_list ilist) { - for (auto&& vt : ilist) { - insert(std::move(vt)); - } - } - - template - std::pair emplace(Args&&... args) { - ROBIN_HOOD_TRACE(this) - Node n{*this, std::forward(args)...}; - auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n)); - switch (idxAndState.second) { - case InsertionState::key_found: - n.destroy(*this); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node(*this, std::move(n)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = std::move(n); - break; - - case InsertionState::overflow_error: - n.destroy(*this); - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - iterator emplace_hint(const_iterator position, Args&&... args) { - (void)position; - return emplace(std::forward(args)...).first; - } - - template - std::pair try_emplace(const key_type& key, Args&&... args) { - return try_emplace_impl(key, std::forward(args)...); - } - - template - std::pair try_emplace(key_type&& key, Args&&... args) { - return try_emplace_impl(std::move(key), std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) { - (void)hint; - return try_emplace_impl(key, std::forward(args)...).first; - } - - template - iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) { - (void)hint; - return try_emplace_impl(std::move(key), std::forward(args)...).first; - } - - template - std::pair insert_or_assign(const key_type& key, Mapped&& obj) { - return insertOrAssignImpl(key, std::forward(obj)); - } - - template - std::pair insert_or_assign(key_type&& key, Mapped&& obj) { - return insertOrAssignImpl(std::move(key), std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(key, std::forward(obj)).first; - } - - template - iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(std::move(key), std::forward(obj)).first; - } - - std::pair insert(const value_type& keyval) { - ROBIN_HOOD_TRACE(this) - return emplace(keyval); - } - - iterator insert(const_iterator hint, const value_type& keyval) { - (void)hint; - return emplace(keyval).first; - } - - std::pair insert(value_type&& keyval) { - return emplace(std::move(keyval)); - } - - iterator insert(const_iterator hint, value_type&& keyval) { - (void)hint; - return emplace(std::move(keyval)).first; - } - - // Returns 1 if key is found, 0 otherwise. - size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type count(const OtherKey& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - return 1U == count(key); - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type contains(const OtherKey& key) const { - return 1U == count(key); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q&>::type at(key_type const& key) { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q const&>::type at(key_type const& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - template - const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - template - typename std::enable_if::type // NOLINT(modernize-use-nodiscard) - find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - iterator find(const key_type& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - template - iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - template - typename std::enable_if::type find(const OtherKey& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - iterator begin() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - return end(); - } - return iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - const_iterator begin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cbegin(); - } - const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - if (empty()) { - return cend(); - } - return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - - iterator end() { - ROBIN_HOOD_TRACE(this) - // no need to supply valid info pointer: end() must not be dereferenced, and only node - // pointer is compared. - return iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; - } - const_iterator end() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cend(); - } - const_iterator cend() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return const_iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; - } - - iterator erase(const_iterator pos) { - ROBIN_HOOD_TRACE(this) - // its safe to perform const cast here - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - return erase(iterator{const_cast(pos.mKeyVals), const_cast(pos.mInfo)}); - } - - // Erases element at pos, returns iterator to the next element. - iterator erase(iterator pos) { - ROBIN_HOOD_TRACE(this) - // we assume that pos always points to a valid entry, and not end(). - auto const idx = static_cast(pos.mKeyVals - mKeyVals); - - shiftDown(idx); - --mNumElements; - - if (*pos.mInfo) { - // we've backward shifted, return this again - return pos; - } - - // no backward shift, return next element - return ++pos; - } - - size_t erase(const key_type& key) { - ROBIN_HOOD_TRACE(this) - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - // check while info matches with the source idx - do { - if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - shiftDown(idx); - --mNumElements; - return 1; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found to delete - return 0; - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // exactly the same as reserve(c). - void rehash(size_t c) { - // forces a reserve - reserve(c, true); - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. - void reserve(size_t c) { - // reserve, but don't force rehash - reserve(c, false); - } - - // If possible reallocates the map to a smaller one. This frees the underlying table. - // Does not do anything if load_factor is too large for decreasing the table's size. - void compact() { - ROBIN_HOOD_TRACE(this) - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (newSize < mMask + 1) { - rehashPowerOfTwo(newSize, true); - } - } - - size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return mNumElements; - } - - size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(-1); - } - - ROBIN_HOOD(NODISCARD) bool empty() const noexcept { - ROBIN_HOOD_TRACE(this) - return 0 == mNumElements; - } - - float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return MaxLoadFactor100 / 100.0F; - } - - // Average number of elements per bucket. Since we allow only 1 per bucket - float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(size()) / static_cast(mMask + 1); - } - - ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { - ROBIN_HOOD_TRACE(this) - return mMask; - } - - ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { - if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { - return maxElements * MaxLoadFactor100 / 100; - } - - // we might be a bit inprecise, but since maxElements is quite large that doesn't matter - return (maxElements / 100) * MaxLoadFactor100; - } - - ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { - // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load - // 64bit types. - return numElements + sizeof(uint64_t); - } - - ROBIN_HOOD(NODISCARD) - size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { - auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); - return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); - } - - // calculation only allowed for 2^n values - ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { -#if ROBIN_HOOD(BITNESS) == 64 - return numElements * sizeof(Node) + calcNumBytesInfo(numElements); -#else - // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. - auto const ne = static_cast(numElements); - auto const s = static_cast(sizeof(Node)); - auto const infos = static_cast(calcNumBytesInfo(numElements)); - - auto const total64 = ne * s + infos; - auto const total = static_cast(total64); - - if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { - throwOverflowError(); - } - return total; -#endif - } - -private: - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - auto it = find(e.first); - return it != end() && it->second == e.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - return find(e) != end(); - } - - void reserve(size_t c, bool forceRehash) { - ROBIN_HOOD_TRACE(this) - auto const minElementsAllowed = (std::max)(c, mNumElements); - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (forceRehash || newSize > mMask + 1) { - rehashPowerOfTwo(newSize, false); - } - } - - // reserves space for at least the specified number of elements. - // only works if numBuckets if power of two - // True on success, false otherwise - void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { - ROBIN_HOOD_TRACE(this) - - Node* const oldKeyVals = mKeyVals; - uint8_t const* const oldInfo = mInfo; - - const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - // resize operation: move stuff - initData(numBuckets); - if (oldMaxElementsWithBuffer > 1) { - for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { - if (oldInfo[i] != 0) { - // might throw an exception, which is really bad since we are in the middle of - // moving stuff. - insert_move(std::move(oldKeyVals[i])); - // destroy the node but DON'T destroy the data. - oldKeyVals[i].~Node(); - } - } - - // this check is not necessary as it's guarded by the previous if, but it helps - // silence g++'s overeager "attempt to free a non-heap object 'map' - // [-Werror=free-nonheap-object]" warning. - if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - // don't destroy old data: put it into the pool instead - if (forceFree) { - std::free(oldKeyVals); - } else { - DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); - } - } - } - } - - ROBIN_HOOD(NOINLINE) void throwOverflowError() const { -#if ROBIN_HOOD(HAS_EXCEPTIONS) - throw std::overflow_error("robin_hood::map overflow"); -#else - abort(); -#endif - } - - template - std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - std::pair insertOrAssignImpl(OtherKey&& key, Mapped&& obj) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - mKeyVals[idxAndState.first].getSecond() = std::forward(obj); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - void initData(size_t max_elements) { - mNumElements = 0; - mMask = max_elements - 1; - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); - - // malloc & zero mInfo. Faster than calloc everything. - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = reinterpret_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node)); - - // set sentinel - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - enum class InsertionState { overflow_error, key_found, new_node, overwrite_node }; - - // Finds key, and if not already present prepares a spot where to pot the key & value. - // This potentially shifts nodes out of the way, updates mInfo and number of inserted - // elements, so the only operation left to do is create/assign a new node at that spot. - template - std::pair insertKeyPrepareEmptySpot(OtherKey&& key) { - for (int i = 0; i < 256; ++i) { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - nextWhileLess(&info, &idx); - - // while we potentially have a match - while (info == mInfo[idx]) { - if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - // key already exists, do NOT insert. - // see http://en.cppreference.com/w/cpp/container/unordered_map/insert - return std::make_pair(idx, InsertionState::key_found); - } - next(&info, &idx); - } - - // unlikely that this evaluates to true - if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { - if (!increase_size()) { - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - continue; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = info; - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - if (idx != insertion_idx) { - shiftUp(idx, insertion_idx); - } - // put at empty spot - mInfo[insertion_idx] = static_cast(insertion_info); - ++mNumElements; - return std::make_pair(insertion_idx, idx == insertion_idx - ? InsertionState::new_node - : InsertionState::overwrite_node); - } - - // enough attempts failed, so finally give up. - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - - bool try_increase_info() { - ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements - << ", maxNumElementsAllowed=" - << calcMaxNumElementsAllowed(mMask + 1)) - if (mInfoInc <= 2) { - // need to be > 2 so that shift works (otherwise undefined behavior!) - return false; - } - // we got space left, try to make info smaller - mInfoInc = static_cast(mInfoInc >> 1U); - - // remove one bit of the hash, leaving more space for the distance info. - // This is extremely fast because we can operate on 8 bytes at once. - ++mInfoHashShift; - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - for (size_t i = 0; i < numElementsWithBuffer; i += 8) { - auto val = unaligned_load(mInfo + i); - val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); - std::memcpy(mInfo + i, &val, sizeof(val)); - } - // update sentinel, which might have been cleared out! - mInfo[numElementsWithBuffer] = 1; - - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - return true; - } - - // True if resize was possible, false otherwise - bool increase_size() { - // nothing allocated yet? just allocate InitialNumElements - if (0 == mMask) { - initData(InitialNumElements); - return true; - } - - auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - if (mNumElements < maxNumElementsAllowed && try_increase_info()) { - return true; - } - - ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" - << maxNumElementsAllowed << ", load=" - << (static_cast(mNumElements) * 100.0 / - (static_cast(mMask) + 1))) - - if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { - // we have to resize, even though there would still be plenty of space left! - // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case - // we have to rehash a few times - nextHashMultiplier(); - rehashPowerOfTwo(mMask + 1, true); - } else { - // we've reached the capacity of the map, so the hash seems to work nice. Keep using it. - rehashPowerOfTwo((mMask + 1) * 2, false); - } - return true; - } - - void nextHashMultiplier() { - // adding an *even* number, so that the multiplier will always stay odd. This is necessary - // so that the hash stays a mixing function (and thus doesn't have any information loss). - mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54); - } - - void destroy() { - if (0 == mMask) { - // don't deallocate! - return; - } - - Destroyer::value>{} - .nodesDoNotDeallocate(*this); - - // This protection against not deleting mMask shouldn't be needed as it's sufficiently - // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise - // reports a compile error: attempt to free a non-heap object 'fm' - // [-Werror=free-nonheap-object] - if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - } - - void init() noexcept { - mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); - mInfo = reinterpret_cast(&mMask); - mNumElements = 0; - mMask = 0; - mMaxNumElementsAllowed = 0; - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // members are sorted so no padding occurs - uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8 - Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 16 - uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 24 - size_t mNumElements = 0; // 8 byte 32 - size_t mMask = 0; // 8 byte 40 - size_t mMaxNumElementsAllowed = 0; // 8 byte 48 - InfoType mInfoInc = InitialInfoInc; // 4 byte 52 - InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56 - // 16 byte 56 if NodeAllocator -}; - -} // namespace detail - -// map - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_flat_map = detail::Table; - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_node_map = detail::Table; - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_map = - detail::Table) <= sizeof(size_t) * 6 && - std::is_nothrow_move_constructible>::value && - std::is_nothrow_move_assignable>::value, - MaxLoadFactor100, Key, T, Hash, KeyEqual>; - -// set - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_flat_set = detail::Table; - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_node_set = detail::Table; - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_set = detail::Table::value && - std::is_nothrow_move_assignable::value, - MaxLoadFactor100, Key, void, Hash, KeyEqual>; - -} // namespace robin_hood - -#endif diff --git a/manpage.md b/manpage.md new file mode 100644 index 0000000..105a7c5 --- /dev/null +++ b/manpage.md @@ -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". diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 94d7f87..e716cc4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -45,9 +45,12 @@ parts: source-type: git plugin: make make-parameters: + - CXX=g++-11 - PREFIX=/usr/local - STATIC=true - 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: - coreutils - sed @@ -55,7 +58,7 @@ parts: - build-essential - gcc-11 - g++-11 - + override-pull: | snapcraftctl pull snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1)" diff --git a/src/btop.cpp b/src/btop.cpp index 252f2a9..8eae107 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -16,6 +16,7 @@ indent = tab tab-size = 4 */ +#include #include #include #include @@ -32,6 +33,7 @@ tab-size = 4 #include #include #include +#include #ifdef __APPLE__ #include #include @@ -50,6 +52,9 @@ tab-size = 4 #include "btop_theme.hpp" #include "btop_draw.hpp" #include "btop_menu.hpp" +#include "config.h" +#include "fmt/core.h" +#include "fmt/ostream.h" using std::atomic; using std::cout; @@ -75,7 +80,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "1.3.0"; + const string Version = "1.3.2"; int coreCount; string overlay; @@ -93,9 +98,9 @@ namespace Global { string exit_error_msg; atomic thread_exception (false); - bool debuginit{}; // defaults to false - bool debug{}; // defaults to false - bool utf_force{}; // defaults to false + bool debuginit{}; + bool debug{}; + bool utf_force{}; uint64_t start_time; @@ -104,19 +109,35 @@ namespace Global { atomic should_quit (false); atomic should_sleep (false); atomic _runner_started (false); + atomic init_conf (false); + atomic reload_conf (false); - bool arg_tty{}; // defaults to false - bool arg_low_color{}; // defaults to false + bool arg_tty{}; + bool arg_low_color{}; 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 -void argumentParser(const int& argc, char **argv) { +void argumentParser(const int argc, char **argv) { for(int i = 1; i < argc; i++) { const string argument = argv[i]; if (is_in(argument, "-h", "--help")) { fmt::println( - "usage: btop [-h] [-v] [-/+t] [-p ] [--utf-force] [--debug]\n\n" + "usage: btop [-h] [-v] [-/+t] [-p ] [-u ] [--utf-force] [--debug]\n\n" "optional arguments:\n" " -h, --help show this help message and exit\n" " -v, --version show version info and exit\n" @@ -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_off force (OFF) tty mode\n" " -p, --preset start with preset, integer value between 0-9\n" + " -u, --update set the program update rate in milliseconds\n" " --utf-force force start even if no UTF-8 locale was detected\n" " --debug start in DEBUG mode: shows microsecond timer for information collect\n" " and screen draw functions and sets loglevel to DEBUG" ); exit(0); } - else if (is_in(argument, "-v", "--version")) { - fmt::println("btop version: {}", Global::Version); + else if (is_in(argument, "-v")) { + print_version(); + exit(0); + } + else if (is_in(argument, "--version")) { + print_version_with_build_info(); exit(0); } else if (is_in(argument, "-lc", "--low-color")) { @@ -158,6 +184,19 @@ void argumentParser(const int& argc, char **argv) { exit(1); } } + else if (is_in(argument, "-u", "--update")) { + if (++i >= argc) { + fmt::println("ERROR: Update option needs an argument"); + exit(1); + } + const std::string value = argv[i]; + if (isint(value)) { + Global::arg_update = std::clamp(std::stoi(value), 100, Config::ONE_DAY_MILLIS); + } else { + fmt::println("ERROR: Invalid update rate"); + exit(1); + } + } else if (argument == "--utf-force") Global::utf_force = true; else if (argument == "--debug") @@ -175,7 +214,7 @@ void term_resize(bool force) { static atomic resizing (false); if (Input::polling) { Global::resized = true; - Input::interrupt = true; + Input::interrupt(); return; } atomic_lock lck(resizing, true); @@ -245,7 +284,7 @@ void term_resize(bool force) { else if (not Term::refresh()) break; } - Input::interrupt = true; + Input::interrupt(); } //* Exit handler; stops threads, restores terminal and saves config changes @@ -254,7 +293,7 @@ void clean_quit(int sig) { Global::quitting = true; Runner::stop(); if (Global::_runner_started) { - #ifdef __APPLE__ + #if defined __APPLE__ || defined __OpenBSD__ if (pthread_join(Runner::runner_id, nullptr) != 0) { Logger::warning("Failed to join _runner thread on exit!"); pthread_cancel(Runner::runner_id); @@ -290,7 +329,7 @@ void clean_quit(int sig) { const auto excode = (sig != -1 ? sig : 0); -#ifdef __APPLE__ +#if defined __APPLE__ || defined __OpenBSD__ _Exit(excode); #else quick_exit(excode); @@ -320,7 +359,7 @@ void _signal_handler(const int sig) { if (Runner::active) { Global::should_quit = true; Runner::stopping = true; - Input::interrupt = true; + Input::interrupt(); } else { clean_quit(0); @@ -330,7 +369,7 @@ void _signal_handler(const int sig) { if (Runner::active) { Global::should_sleep = true; Runner::stopping = true; - Input::interrupt = true; + Input::interrupt(); } else { _sleep(); @@ -342,9 +381,41 @@ void _signal_handler(const int sig) { case SIGWINCH: term_resize(); 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 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 namespace Runner { atomic 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 { int status = -1; public: @@ -397,7 +468,7 @@ namespace Runner { string output; string empty_bg; - bool pause_output{}; // defaults to false + bool pause_output{}; sigset_t mask; pthread_t runner_id; pthread_mutex_t mtx; @@ -416,7 +487,7 @@ namespace Runner { }; string debug_bg; - unordered_flat_map> debug_times; + std::unordered_map> debug_times; class MyNumPunct : public std::numpunct { @@ -476,7 +547,7 @@ namespace Runner { if (pt_lck.status != 0) { Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status); Global::thread_exception = true; - Input::interrupt = true; + Input::interrupt(); stopping = true; } @@ -487,7 +558,7 @@ namespace Runner { if (active) { Global::exit_error_msg = "Runner thread failed to get active lock!"; Global::thread_exception = true; - Input::interrupt = true; + Input::interrupt(); stopping = true; } if (stopping or Global::resized) { @@ -557,7 +628,7 @@ namespace Runner { coreNum_reset = false; Cpu::core_mapping = Cpu::get_core_mapping(); Global::resized = true; - Input::interrupt = true; + Input::interrupt(); continue; } @@ -654,7 +725,7 @@ namespace Runner { catch (const std::exception& e) { Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()}; Global::thread_exception = true; - Input::interrupt = true; + Input::interrupt(); stopping = true; } @@ -814,7 +885,7 @@ int main(int argc, char **argv) { 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::set_uid = geteuid(); if (Global::real_uid != Global::set_uid) { @@ -828,29 +899,23 @@ int main(int argc, char **argv) { //? Call argument parser if launched with arguments if (argc > 1) argumentParser(argc, argv); - //? Setup paths for config, log and user themes - for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) { - if (std::getenv(env) != nullptr and access(std::getenv(env), W_OK) != -1) { - Config::conf_dir = fs::path(std::getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop"); - break; - } - } - if (Config::conf_dir.empty()) { - fmt::println("WARNING: Could not get path user HOME folder.\n" - "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this."); - } - else { - if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) { - fmt::println("WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n" - "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this."); - } - else { + { + const auto config_dir = Config::get_config_dir(); + if (config_dir.has_value()) { + Config::conf_dir = config_dir.value(); Config::conf_file = Config::conf_dir / "btop.conf"; Logger::logfile = Config::conf_dir / "btop.log"; Theme::user_theme_dir = Config::conf_dir / "themes"; - if (not fs::exists(Theme::user_theme_dir) and not fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear(); + + // If necessary create the user theme directory + std::error_code error; + if (not fs::exists(Theme::user_theme_dir, error) and not fs::create_directories(Theme::user_theme_dir, error)) { + Theme::user_theme_dir.clear(); + Logger::warning("Failed to create user theme directory: " + error.message()); + } } } + //? Try to find global btop theme path relative to binary path #ifdef __linux__ { std::error_code ec; @@ -879,22 +944,7 @@ int main(int argc, char **argv) { } //? Config init - { vector load_warnings; - 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); - } + init_config(); //? Try to find and set a UTF-8 locale 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 { string found; - bool set_failure{}; // defaults to false - for (const auto loc_env : array{"LANG", "LC_ALL"}) { + bool set_failure{}; + 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")) { found = std::getenv(loc_env); if (std::setlocale(LC_ALL, found.c_str()) == nullptr) { @@ -930,7 +980,7 @@ int main(int argc, char **argv) { catch (...) { found.clear(); } } } - + // #ifdef __APPLE__ if (found.empty()) { CFLocaleRef cflocale = CFLocaleCopyCurrent(); @@ -974,7 +1024,7 @@ int main(int argc, char **argv) { Config::set("tty_mode", true); Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols"); } -#ifndef __APPLE__ +#if not defined __APPLE__ && not defined __OpenBSD__ else if (not Global::arg_tty and Term::current_tty.starts_with("/dev/tty")) { Config::set("tty_mode", true); Logger::info("Real tty detected: setting 16 color mode and using tty friendly graph symbols"); @@ -1003,6 +1053,11 @@ int main(int argc, char **argv) { 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 Theme::updateThemes(); Theme::setTheme(); @@ -1013,6 +1068,13 @@ int main(int argc, char **argv) { std::signal(SIGTSTP, _signal_handler); std::signal(SIGCONT, _signal_handler); std::signal(SIGWINCH, _signal_handler); + std::signal(SIGUSR1, _signal_handler); + std::signal(SIGUSR2, _signal_handler); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &mask, &Input::signal_mask); //? Start runner thread Runner::thread_sem_init(); @@ -1034,9 +1096,10 @@ int main(int argc, char **argv) { { const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes")); if (Term::height < y or Term::width < x) { + pthread_sigmask(SIG_SETMASK, &Input::signal_mask, &mask); term_resize(true); + pthread_sigmask(SIG_SETMASK, &mask, nullptr); Global::resized = false; - Input::interrupt = false; } } @@ -1049,15 +1112,36 @@ int main(int argc, char **argv) { //? ------------------------------------------------ MAIN LOOP ---------------------------------------------------- + if (Global::arg_update != 0) { + Config::set("update_ms", Global::arg_update); + } uint64_t update_ms = Config::getI("update_ms"); auto future_time = time_ms(); try { while (not true not_eq not false) { //? Check for exceptions in secondary thread and exit with fail signal if true - if (Global::thread_exception) clean_quit(1); - else if (Global::should_quit) clean_quit(0); - else if (Global::should_sleep) { Global::should_sleep = false; _sleep(); } + if (Global::thread_exception) { + clean_quit(1); + } + 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) term_resize(Global::resized); @@ -1092,9 +1176,9 @@ int main(int argc, char **argv) { update_ms = Config::getI("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; - + } //? Poll for input and process any input detected else if (Input::poll(min((uint64_t)1000, future_time - current_time))) { if (not Runner::active) Config::unlock(); diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 0f723b7..aa4d813 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -21,8 +21,10 @@ tab-size = 4 #include #include #include +#include #include +#include #include "btop_config.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."}, + {"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" "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}, #ifdef GPU_SUPPORT @@ -215,7 +219,7 @@ namespace Config { #endif }; - unordered_flat_map strings = { + std::unordered_map strings = { {"color_theme", "Default"}, {"shown_boxes", "cpu mem net proc"}, {"graph_symbol", "braille"}, @@ -251,9 +255,9 @@ namespace Config { {"show_gpu_info", "Auto"} #endif }; - unordered_flat_map stringsTmp; + std::unordered_map stringsTmp; - unordered_flat_map bools = { + std::unordered_map bools = { {"theme_background", true}, {"truecolor", true}, {"rounded_corners", true}, @@ -291,6 +295,7 @@ namespace Config { {"net_auto", true}, {"net_sync", true}, {"show_battery", true}, + {"show_battery_watts", true}, {"vim_keys", false}, {"tty_mode", false}, {"disk_free_priv", false}, @@ -304,9 +309,9 @@ namespace Config { {"gpu_mirror_graph", true}, #endif }; - unordered_flat_map boolsTmp; + std::unordered_map boolsTmp; - unordered_flat_map ints = { + std::unordered_map ints = { {"update_ms", 2000}, {"net_download", 100}, {"net_upload", 100}, @@ -317,7 +322,66 @@ namespace Config { {"proc_selected", 0}, {"proc_last_selected", 0}, }; - unordered_flat_map intsTmp; + std::unordered_map intsTmp; + + // Returns a valid config dir or an empty optional + // The config dir might be read only, a warning is printed, but a path is returned anyway + [[nodiscard]] std::optional get_config_dir() noexcept { + fs::path config_dir; + { + std::error_code error; + if (const auto xdg_config_home = std::getenv("XDG_CONFIG_HOME"); xdg_config_home != nullptr) { + if (fs::exists(xdg_config_home, error)) { + config_dir = fs::path(xdg_config_home) / "btop"; + } + } else if (const auto home = std::getenv("HOME"); home != nullptr) { + error.clear(); + if (fs::exists(home, error)) { + config_dir = fs::path(home) / ".config" / "btop"; + } + if (error) { + fmt::print(stderr, "\033[0;31mWarning: \033[0m{} could not be accessed: {}\n", config_dir.string(), error.message()); + config_dir = ""; + } + } + } + + // FIXME: This warnings can be noisy if the user deliberately has a non-writable config dir + // offer an alternative | disable messages by default | disable messages if config dir is not writable | disable messages with a flag + // FIXME: Make happy path not branch + if (not config_dir.empty()) { + std::error_code error; + if (fs::exists(config_dir, error)) { + if (fs::is_directory(config_dir, error)) { + struct statvfs stats {}; + if ((fs::status(config_dir, error).permissions() & fs::perms::owner_write) == fs::perms::owner_write and + statvfs(config_dir.c_str(), &stats) == 0 and (stats.f_flag & ST_RDONLY) == 0) { + return config_dir; + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` is not writable\n", fs::absolute(config_dir).string()); + // If the config is readable we can still use the provided config, but changes will not be persistent + if ((fs::status(config_dir, error).permissions() & fs::perms::owner_read) == fs::perms::owner_read) { + fmt::print(stderr, "\033[0;31mWarning: \033[0mLogging is disabled, config changes are not persistent\n"); + return config_dir; + } + } + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` is not a directory\n", fs::absolute(config_dir).string()); + } + } else { + // Doesn't exist + if (fs::create_directories(config_dir, error)) { + return config_dir; + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` could not be created: {}\n", fs::absolute(config_dir).string(), error.message()); + } + } + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0mCould not determine config path: Make sure `$XDG_CONFIG_HOME` or `$HOME` is set\n"); + } + fmt::print(stderr, "\033[0;31mWarning: \033[0mLogging is disabled, config changes are not persistent\n"); + return {}; + } bool _locked(const std::string_view name) { atomic_wait(writelock, true); @@ -427,8 +491,8 @@ namespace Config { if (name == "update_ms" and i_value < 100) validError = "Config value update_ms set too low (<100)."; - else if (name == "update_ms" and i_value > 86400000) - validError = "Config value update_ms set too high (>86400000)."; + else if (name == "update_ms" and i_value > ONE_DAY_MILLIS) + validError = fmt::format("Config value update_ms set too high (>{}).", ONE_DAY_MILLIS); else return true; @@ -446,7 +510,7 @@ namespace Config { 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); - 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!"; #ifdef GPU_SUPPORT @@ -558,8 +622,7 @@ namespace Config { if (not v_contains(valid_boxes, box)) return false; #ifdef GPU_SUPPORT if (box.starts_with("gpu")) { - size_t gpu_num = stoi(box.substr(3)); - if (gpu_num == 0) gpu_num = 5; + size_t gpu_num = stoi(box.substr(3)) + 1; if (std::cmp_greater(gpu_num, Gpu::gpu_names.size())) return false; } #endif @@ -593,12 +656,17 @@ namespace Config { } void load(const fs::path& conf_file, vector& load_warnings) { + std::error_code error; if (conf_file.empty()) return; - else if (not fs::exists(conf_file)) { + else if (not fs::exists(conf_file, error)) { write_new = true; return; } + if (error) { + return; + } + std::ifstream cread(conf_file); if (cread.good()) { vector valid_names; @@ -664,9 +732,9 @@ namespace Config { if (geteuid() != Global::real_uid and seteuid(Global::real_uid) != 0) return; std::ofstream cwrite(conf_file, std::ios::trunc); 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) { - cwrite << "\n\n" << (description.empty() ? "" : description + "\n") + cwrite << "\n" << (description.empty() ? "" : description + "\n") << name << " = "; if (strings.contains(name)) cwrite << "\"" << strings.at(name) << "\""; @@ -674,6 +742,7 @@ namespace Config { cwrite << ints.at(name); else if (bools.contains(name)) cwrite << (bools.at(name) ? "True" : "False"); + cwrite << "\n"; } } } diff --git a/src/btop_config.hpp b/src/btop_config.hpp index c71d29c..3651566 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -18,15 +18,15 @@ tab-size = 4 #pragma once +#include +#include #include #include -#include -#include +#include using std::string; using std::vector; -using robin_hood::unordered_flat_map; //* Functions and variables for reading and writing the btop config file namespace Config { @@ -34,12 +34,12 @@ namespace Config { extern std::filesystem::path conf_dir; extern std::filesystem::path conf_file; - extern unordered_flat_map strings; - extern unordered_flat_map stringsTmp; - extern unordered_flat_map bools; - extern unordered_flat_map boolsTmp; - extern unordered_flat_map ints; - extern unordered_flat_map intsTmp; + extern std::unordered_map strings; + extern std::unordered_map stringsTmp; + extern std::unordered_map bools; + extern std::unordered_map boolsTmp; + extern std::unordered_map ints; + extern std::unordered_map intsTmp; const vector valid_graph_symbols = { "braille", "block", "tty" }; const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; @@ -58,6 +58,10 @@ namespace Config { extern vector available_batteries; extern int current_preset; + constexpr int ONE_DAY_MILLIS = 1000 * 60 * 60 * 24; + + [[nodiscard]] std::optional get_config_dir() noexcept; + //* Check if string only contains space separated valid names for boxes bool check_boxes(const string& boxes); @@ -95,7 +99,7 @@ namespace Config { } //* Set config key to int - inline void set(const std::string_view name, const int& value) { + inline void set(const std::string_view name, const int value) { if (_locked(name)) intsTmp.insert_or_assign(name, value); else ints.at(name) = value; } diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 5ae357e..bfd12ce 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -22,6 +22,7 @@ tab-size = 4 #include #include #include +#include #include "btop_draw.hpp" #include "btop_config.hpp" @@ -54,7 +55,7 @@ namespace Symbols { const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; - const unordered_flat_map> graph_symbols = { + const std::unordered_map> graph_symbols = { { "braille_up", { " ", "⢀", "⢠", "⢰", "⢸", "⡀", "⣀", "⣠", "⣰", "⣸", @@ -300,14 +301,14 @@ namespace Draw { return false; } - static const unordered_flat_map clock_custom_format = { + static const std::unordered_map clock_custom_format = { {"/user", Tools::username()}, {"/host", Tools::hostname()}, {"/uptime", ""} }; - static time_t c_time{}; // defaults to 0 - static size_t clock_len{}; // defaults to 0 + static time_t c_time{}; + static size_t clock_len{}; static string clock_str; 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_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); static int bat_pos = 0, bat_len = 0; - if (cpu.cpu_percent.at("total").empty() - or cpu.core_percent.at(0).empty() - or (show_temps and cpu.temp.at(0).empty())) return ""; - if (cpu.cpu_percent.at("total").empty() - or cpu.core_percent.at(0).empty() - or (show_temps and cpu.temp.at(0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; string out; out.reserve(width * height); @@ -608,7 +609,7 @@ namespace Cpu { 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 }; 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) { 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" }; @@ -617,12 +618,12 @@ namespace Cpu { bool utilization_support = gpu.supported_functions.gpu_utilization; if (++i < gpus.size()) { 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 { if (utilization_support) graph = Draw::Graph{ 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; } @@ -630,7 +631,7 @@ namespace Cpu { } else { graphs.resize(1); 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_mem_graphs.resize(gpus.size()); gpu_meters.resize(gpus.size()); @@ -638,7 +639,7 @@ namespace Cpu { 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 }; 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) { 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" }; @@ -649,7 +650,7 @@ namespace Cpu { #endif graphs.resize(1); 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 if (std::cmp_less(Gpu::shown, gpus.size())) { gpu_temp_graphs.resize(gpus.size()); @@ -659,7 +660,7 @@ namespace Cpu { 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 }; 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) { 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" }; @@ -692,10 +693,10 @@ namespace Cpu { if (show_temps) { 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) { 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) { static int old_percent{}; // defaults to = 0 static long old_seconds{}; // defaults to = 0 + static float old_watts{}; // defaults to = 0 static string old_status; static Draw::Meter bat_meter {10, "cpu", true}; - static const unordered_flat_map bat_symbols = { + static const std::unordered_map bat_symbols = { {"charging", "▲"}, {"discharging", "▼"}, {"full", "■"}, {"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_watts = watts; old_seconds = seconds; old_status = status; const string str_time = (seconds > 0 ? sec_to_dhms(seconds, true, true) : ""); 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 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; 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 + (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) { @@ -749,7 +753,7 @@ namespace Cpu { if (graph_field.starts_with("gpu")) if (graph_field.find("totals") != string::npos) 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) { auto i_str = to_string(i); 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 - 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 (void)graph_height; (void)graph_width; #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); @@ -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()) + 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()) - + 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") + '%'; + 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(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) { - const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); - if (b_column_size > 1 or b_columns > 1) + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); + 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) and temp_graphs.size() >= 1ll) 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 += 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); if (Shared::coreCount >= 100) core_width++; 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 : "") + ljust(to_string(n), core_width); 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) - + 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 += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; + out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); + 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) { - const auto [temp, unit] = celsius_to(cpu.temp.at(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)); + if (show_temps and not hide_cores and std::cmp_greater_equal(temp_graphs.size(), n)) { + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); + 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) 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; } @@ -882,14 +887,14 @@ namespace Cpu { } if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9)); 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 - + 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); 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()) - + gpu_mem_graphs[i](gpus[i].gpu_percent.at("gpu-vram-totals"), data_same or redraw) + Theme::c("main_fg") + 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](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); if (gpus[i].supported_functions.mem_total) 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]; 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) { graph_lower = Draw::Graph{ x + width - b_width - 3, graph_low_height, "cpu", - gpu.gpu_percent.at("gpu-totals"), + safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, Config::getB("cpu_invert_lower"), true }; @@ -986,7 +991,7 @@ namespace Gpu { 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 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 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) - 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()) - + 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") + '%'; + 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(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 if (show_temps) { @@ -1019,10 +1024,10 @@ namespace Gpu { //? Power usage meter, power state 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()) - + 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'; + 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(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 - 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) { @@ -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 + 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 - + 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::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 if (gpu.supported_functions.mem_utilization) @@ -1096,11 +1101,11 @@ namespace Mem { int disks_io_half = 0; bool shown = true, redraw = true; string box; - unordered_flat_map mem_meters; - unordered_flat_map mem_graphs; - unordered_flat_map disk_meters_used; - unordered_flat_map disk_meters_free; - unordered_flat_map io_graphs; + std::unordered_map mem_meters; + std::unordered_map mem_graphs; + std::unordered_map disk_meters_used; + std::unordered_map disk_meters_free; + std::unordered_map io_graphs; string draw(const mem_info& mem, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; @@ -1132,14 +1137,14 @@ namespace Mem { for (const auto& name : mem_names) { 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 mem_meters[name] = Draw::Meter{mem_meter, name}; } if (show_swap and has_swap) { for (const auto& name : swap_names) { 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 mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; } @@ -1148,7 +1153,7 @@ namespace Mem { //? Disk meters and io graphs if (show_disks) { if (show_io_stat or io_mode) { - unordered_flat_map custom_speeds; + std::unordered_map custom_speeds; int half_height = 0; if (io_mode) { 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; 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; cy += 1; title = "Used"; @@ -1239,13 +1244,16 @@ namespace Mem { title = "Free"; 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 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) { 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+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); } else { @@ -1268,7 +1276,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = disks.at(mount); + const auto& disk = safeVal(disks, mount); if (disk.io_read.empty()) continue; 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()) @@ -1277,9 +1285,12 @@ namespace Mem { 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; } + 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) + 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 (not io_graphs.contains(mount)) continue; if (io_graph_combined) { 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) @@ -1304,7 +1315,8 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; 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); 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) : ""); @@ -1328,7 +1340,7 @@ namespace Mem { + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); 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") + ' ' + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; @@ -1352,8 +1364,9 @@ namespace Net { int x = 1, y, width = 20, height; int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; + const int MAX_IFNAMSIZ = 15; string old_ip; - unordered_flat_map graphs; + std::unordered_map graphs; string box; string draw(const net_info& net, bool force_redraw, bool data_same) { @@ -1372,16 +1385,16 @@ namespace Net { out.reserve(width * height); 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 int i_size = min((int)selected_iface.size(), 10); - const long long down_max = (net_auto ? graph_max.at("download") : ((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 int i_size = min((int)selected_iface.size(), MAX_IFNAMSIZ); + 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 ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8); //* Redraw elements not needed to be updated every cycle if (redraw) { out = box; //? Graphs 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; graphs["download"] = Draw::Graph{ width - b_width - 2, u_graph_height, "download", @@ -1394,8 +1407,8 @@ namespace Net { //? Interface selector and buttons out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "" + 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' + + 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") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z' + Theme::c("title") + "ero" + title_right; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; @@ -1419,13 +1432,13 @@ namespace Net { //? Graphs and stats int cy = 0; 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") + 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_bits = (b_width >= 20 ? floating_humanizer(net.stat.at(dir).speed, false, 0, true, true) : ""); - const string top = floating_humanizer(net.stat.at(dir).top, false, 0, true, true); - const string total = floating_humanizer(net.stat.at(dir).total); + const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true); + const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : ""); + const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true); + const string total = floating_humanizer(safeVal(net.stat, dir).total); 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) : ""); cy += (b_height == 5 ? 2 : 1); @@ -1453,9 +1466,9 @@ namespace Proc { bool shown = true, redraw = true; int selected_pid = 0, selected_depth = 0; string selected_name; - unordered_flat_map p_graphs; - unordered_flat_map p_wide_cmd; - unordered_flat_map p_counters; + std::unordered_map p_graphs; + std::unordered_map p_wide_cmd; + std::unordered_map p_counters; int counter = 0; Draw::TextEdit filter; Draw::Graph detailed_cpu_graph; @@ -1790,11 +1803,11 @@ namespace Proc { p_counters[p.pid] = 0; } 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); } else - p_counters.at(p.pid) = 0; + p_counters[p.pid] = 0; } out += Fx::reset; @@ -1918,25 +1931,18 @@ namespace Proc { //? Clear out left over graphs from dead processes at a regular interval if (not data_same and ++counter >= 100) { 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();) { - if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { - element = p_wide_cmd.erase(element); - } - else - ++element; - } - p_wide_cmd.compact(); + std::erase_if(p_graphs, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); + + std::erase_if(p_counters, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); + + 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) { @@ -2008,7 +2014,7 @@ namespace Draw { #ifdef GPU_SUPPORT const bool show_gpu_on = Config::getS("show_gpu_info") == "On"; 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; 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 { } } } + diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index af57ed8..205fa85 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -21,10 +21,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::deque; using std::string; @@ -66,8 +65,8 @@ namespace Draw { //* An editable text field class TextEdit { - size_t pos{}; // defaults to 0 - size_t upos{}; // defaults to 0 + size_t pos{}; + size_t upos{}; bool numeric; public: string text; @@ -108,7 +107,7 @@ namespace Draw { long long offset; long long last = 0, max_value = 0; bool current = true, tty_mode = false; - unordered_flat_map> graphs = { {true, {}}, {false, {}}}; + std::unordered_map> graphs = { {true, {}}, {false, {}}}; //* Create two representations of the graph to switch between to represent two values for each braille character void _create(const deque& data, int data_offset); @@ -135,6 +134,6 @@ namespace Draw { namespace Proc { extern Draw::TextEdit filter; - extern unordered_flat_map p_graphs; - extern unordered_flat_map p_counters; + extern std::unordered_map p_graphs; + extern std::unordered_map p_counters; } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 88f307b..6bfe9d8 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -16,12 +16,13 @@ indent = tab tab-size = 4 */ -#include +#include #include #include #include #include #include +#include #include #include "btop_input.hpp" @@ -31,17 +32,6 @@ tab-size = 4 #include "btop_menu.hpp" #include "btop_draw.hpp" - -#include "btop_input.hpp" -#include "btop_tools.hpp" -#include "btop_config.hpp" -#include "btop_shared.hpp" -#include "btop_menu.hpp" -#include "btop_draw.hpp" - - -using std::cin; - using namespace Tools; using namespace std::literals; // for operator""s namespace rng = std::ranges; @@ -49,8 +39,9 @@ namespace rng = std::ranges; namespace Input { //* Map for translating key codes to readable values - const unordered_flat_map Key_escapes = { + const std::unordered_map Key_escapes = { {"\033", "escape"}, + {"\x12", "ctrl_r"}, {"\n", "enter"}, {" ", "space"}, {"\x7f", "backspace"}, @@ -89,83 +80,45 @@ namespace Input { {"[24~", "f12"} }; - std::atomic interrupt (false); + sigset_t signal_mask; std::atomic polling (false); array mouse_pos; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; deque history(50, ""); string old_filter; + string input; - struct InputThr { - InputThr() : thr(run, this) { - } - - static void run(InputThr* that) { - that->runImpl(); - } - - void runImpl() { - char ch = 0; - - // TODO(pg83): read whole buffer - while (cin.get(ch)) { - std::lock_guard g(lock); - current.push_back(ch); - if (current.size() > 100) { - current.clear(); - } - } - } - - size_t avail() { - std::lock_guard g(lock); - - return current.size(); - } - - std::string get() { - std::string res; - - { - std::lock_guard g(lock); - - res.swap(current); - } - - return res; - } - - static InputThr& instance() { - // intentional memory leak, to simplify shutdown process - static InputThr* input = new InputThr(); - - return *input; - } - - std::string current; - // TODO(pg83): use std::conditional_variable instead of sleep - std::mutex lock; - std::thread thr; - }; - - bool poll(int timeout) { + bool poll(const uint64_t timeout) { atomic_lock lck(polling); - if (timeout < 1) return InputThr::instance().avail() > 0; - while (timeout > 0) { - if (interrupt) { - interrupt = false; - return false; - } - if (InputThr::instance().avail() > 0) return true; - sleep_ms(timeout < 10 ? timeout : 10); - timeout -= 10; + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + struct timespec wait; + struct timespec *waitptr = nullptr; + + if(timeout != std::numeric_limits::max()) { + wait.tv_sec = timeout / 1000; + wait.tv_nsec = (timeout % 1000) * 1000000; + waitptr = &wait; } + + if(pselect(STDIN_FILENO + 1, &fds, nullptr, nullptr, waitptr, &signal_mask) > 0) { + input.clear(); + char buf[1024]; + ssize_t count = 0; + while((count = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { + input.append(std::string_view(buf, count)); + } + + return true; + } + return false; } string get() { - string key = InputThr::instance().get(); + string key = input; if (not key.empty()) { //? Remove escape code prefix if present if (key.substr(0, 2) == Fx::e) { @@ -238,12 +191,14 @@ namespace Input { } string wait() { - while (InputThr::instance().avail() < 1) { - sleep_ms(10); - } + while(not poll(std::numeric_limits::max())) {} return get(); } + void interrupt() { + kill(getpid(), SIGUSR1); + } + void clear() { // do not need it, actually } @@ -304,8 +259,10 @@ namespace Input { Draw::calcSizes(); Runner::run("all", false, true); return; - } - else + } else if (is_in(key, "ctrl_r")) { + kill(getpid(), SIGUSR2); + return; + } else keep_going = true; if (not keep_going) return; diff --git a/src/btop_input.hpp b/src/btop_input.hpp index 5a1a28e..91b9dc9 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -21,18 +21,17 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; using std::string; -/* The input functions relies on the following std::cin options being set: - cin.sync_with_stdio(false); - cin.tie(nullptr); +/* The input functions rely on the following termios parameters being set: + Non-canonical mode (c_lflags & ~(ICANON)) + VMIN and VTIME (c_cc) set to 0 These will automatically be set when running Term::init() from btop_tools.cpp */ @@ -44,9 +43,11 @@ namespace Input { }; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; + + //* Signal mask used during polling read + extern sigset_t signal_mask; - extern atomic interrupt; extern atomic polling; //* Mouse column and line position @@ -55,8 +56,8 @@ namespace Input { //* Last entered key extern deque history; - //* Poll keyboard & mouse input for ms and return input availabilty as a bool - bool poll(int timeout=0); + //* Poll keyboard & mouse input for ms and return input availability as a bool + bool poll(const uint64_t timeout=0); //* Get a key or mouse action from input string get(); @@ -64,6 +65,9 @@ namespace Input { //* Wait until input is available and return key string wait(); + //* Interrupt poll/wait + void interrupt(); + //* Clears last entered key void clear(); diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 206052f..bf57e8c 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -17,7 +17,7 @@ tab-size = 4 */ #include -#include +#include #include #include #include @@ -31,7 +31,6 @@ tab-size = 4 #include "btop_draw.hpp" #include "btop_shared.hpp" -using robin_hood::unordered_flat_map; using std::array; using std::ceil; using std::max; @@ -50,8 +49,8 @@ namespace Menu { bool redraw{true}; int currentMenu = -1; msgBox messageBox; - int signalToSend{}; // defaults to 0 - int signalKillRet{}; // defaults to 0 + int signalToSend{}; + int signalKillRet{}; const array P_Signals = { "0", @@ -123,7 +122,7 @@ namespace Menu { #endif }; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; const array, 3> menu_normal = { array{ @@ -178,6 +177,7 @@ namespace Menu { {"F2, o", "Shows options."}, {"F1, ?, h", "Shows this window."}, {"ctrl + z", "Sleep program and put in background."}, + {"ctrl + r", "Reloads config file from disk."}, {"q, ctrl + c", "Quits program."}, {"+, -", "Add/Subtract 100ms to/from update timer."}, {"Up, Down", "Select in process list."}, @@ -354,6 +354,11 @@ namespace Menu { "Can be both batteries and UPS.", "", "\"Auto\" for auto detection."}, + {"show_battery_watts", + "Show battery power.", + "", + "Show discharge power when discharging.", + "Show charging power when charging."}, {"log_level", "Set loglevel for error.log", "", @@ -485,7 +490,7 @@ namespace Menu { "Kelvin, 0 = absolute zero, 1 degree change", "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."}, {"show_cpu_freq", "Show CPU frequency.", @@ -646,7 +651,7 @@ namespace Menu { "", "Begin line with \"exclude=\" to change to", "exclude filter.", - "Oterwise defaults to \"most include\" filter.", + "Otherwise defaults to \"most include\" filter.", "", "Example:", "\"exclude=/boot /home/user\""}, @@ -869,8 +874,8 @@ namespace Menu { 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")); - static int x{}; // defaults to 0 - static int y{}; // defaults to 0 + static int x{}; + static int y{}; static int selected_signal = -1; if (bg.empty()) selected_signal = -1; @@ -1071,8 +1076,8 @@ namespace Menu { int mainMenu(const string& key) { enum MenuItems { Options, Help, Quit }; - static int y{}; // defaults to 0 - static int selected{}; // defaults to 0 + static int y{}; + static int selected{}; static vector colors_selected; static vector colors_normal; auto tty_mode = Config::getB("tty_mode"); @@ -1150,22 +1155,22 @@ namespace Menu { int optionsMenu(const string& key) { enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable}; - static int y{}; // defaults to 0 - static int x{}; // defaults to 0 - static int height{}; // defaults to 0 - static int page{}; // defaults to 0 - static int pages{}; // defaults to 0 - static int selected{}; // defaults to 0 - static int select_max{}; // defaults to 0 - static int item_height{}; // defaults to 0 - static int selected_cat{}; // defaults to 0 - static int max_items{}; // defaults to 0 - static int last_sel{}; // defaults to 0 - static bool editing{}; // defaults to false + static int y{}; + static int x{}; + static int height{}; + static int page{}; + static int pages{}; + static int selected{}; + static int select_max{}; + static int item_height{}; + static int selected_cat{}; + static int max_items{}; + static int last_sel{}; + static bool editing{}; static Draw::TextEdit editor; static string warnings; static bitset<8> selPred; - static const unordered_flat_map>> optionsList = { + static const std::unordered_map>> optionsList = { {"color_theme", std::cref(Theme::themes)}, {"log_level", std::cref(Logger::log_levels)}, {"temp_scale", std::cref(Config::temp_scales)}, @@ -1197,9 +1202,9 @@ namespace Menu { Theme::updateThemes(); } int retval = Changed; - bool recollect{}; // defaults to false - bool screen_redraw{}; // defaults to false - bool theme_refresh{}; // defaults to false + bool recollect{}; + bool screen_redraw{}; + bool theme_refresh{}; //? Draw background if needed else process input if (redraw) { @@ -1501,11 +1506,11 @@ namespace Menu { } int helpMenu(const string& key) { - static int y{}; // defaults to 0 - static int x{}; // defaults to 0 - static int height{}; // defaults to 0 - static int page{}; // defaults to 0 - static int pages{}; // defaults to 0 + static int y{}; + static int x{}; + static int height{}; + static int page{}; + static int pages{}; if (bg.empty()) page = 0; int retval = Changed; diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 0e8832f..63974e4 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -38,7 +38,7 @@ namespace Menu { extern bool redraw; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; //* Creates a message box centered on screen //? 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 class msgBox { string box_contents, button_left, button_right; - int height{}; // defaults to 0 - int width{}; // defaults to 0 - int boxtype{}; // defaults to 0 - int selected{}; // defaults to 0 - int x{}; // defaults to 0 - int y{}; // defaults to 0 + int height{}; + int width{}; + int boxtype{}; + int selected{}; + int x{}; + int y{}; public: enum BoxTypes { OK, YES_NO, NO_YES }; enum msgReturn { diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp index fe0f334..b159a97 100644 --- a/src/btop_shared.cpp +++ b/src/btop_shared.cpp @@ -29,7 +29,7 @@ using namespace Tools; namespace Gpu { vector gpu_names; vector gpu_b_height_offsets; - unordered_flat_map> shared_gpu_percent = { + std::unordered_map> shared_gpu_percent = { {"gpu-average", {}}, {"gpu-vram-total", {}}, {"gpu-pwr-total", {}}, diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 017c7f8..f927e05 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -25,11 +25,19 @@ tab-size = 4 #include #include #include -#include -#include +#include #include -using robin_hood::unordered_flat_map; +// From `man 3 getifaddrs`: must be included before +// clang-format off +#include +#include +// clang-format on + +#if defined(__FreeBSD__) || defined(__OpenBSD__) +# include +#endif + using std::array; using std::atomic; using std::deque; @@ -55,6 +63,7 @@ namespace Global { extern string overlay; extern string clock; extern uid_t real_uid, set_uid; + extern atomic init_conf; } namespace Runner { @@ -83,6 +92,15 @@ namespace Shared { void init(); 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; +#endif } @@ -98,7 +116,7 @@ namespace Gpu { extern vector gpu_b_height_offsets; extern long long gpu_pwr_total_max; - extern unordered_flat_map> shared_gpu_percent; // averages, power/vram total + extern std::unordered_map> shared_gpu_percent; // averages, power/vram total const array mem_names { "used"s, "free"s }; @@ -124,7 +142,7 @@ namespace Gpu { //* Per-device container for GPU info struct gpu_info { - unordered_flat_map> gpu_percent = { + std::unordered_map> gpu_percent = { {"gpu-totals", {}}, {"gpu-vram-totals", {}}, {"gpu-pwr-totals", {}}, @@ -178,10 +196,10 @@ namespace Cpu { extern string cpuName, cpuHz; extern vector available_fields; extern vector available_sensors; - extern tuple current_bat; + extern tuple current_bat; struct cpu_info { - unordered_flat_map> cpu_percent = { + std::unordered_map> cpu_percent = { {"total", {}}, {"user", {}}, {"nice", {}}, @@ -207,13 +225,13 @@ namespace Cpu { string draw(const cpu_info& cpu, const vector& gpu, bool force_redraw = false, bool data_same = false); //* Parse /proc/cpu info for mapping of core ids - auto get_core_mapping() -> unordered_flat_map; - extern unordered_flat_map core_mapping; + auto get_core_mapping() -> std::unordered_map; + extern std::unordered_map core_mapping; auto get_cpuHz() -> string; //* Get battery info from /sys - auto get_battery() -> tuple; + auto get_battery() -> tuple; } namespace Mem { @@ -229,11 +247,11 @@ namespace Mem { string name; string fstype{}; // defaults to "" std::filesystem::path stat{}; // defaults to "" - int64_t total{}; // defaults to 0 - int64_t used{}; // defaults to 0 - int64_t free{}; // defaults to 0 - int used_percent{}; // defaults to 0 - int free_percent{}; // defaults to 0 + int64_t total{}; + int64_t used{}; + int64_t free{}; + int used_percent{}; + int free_percent{}; array old_io = {0, 0, 0}; deque io_read = {}; @@ -242,13 +260,13 @@ namespace Mem { }; struct mem_info { - unordered_flat_map stats = + std::unordered_map stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; - unordered_flat_map> percent = + std::unordered_map> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; - unordered_flat_map disks; + std::unordered_map disks; vector disks_order; }; @@ -270,26 +288,37 @@ namespace Net { extern string selected_iface; extern vector interfaces; extern bool rescale; - extern unordered_flat_map graph_max; + extern std::unordered_map graph_max; struct net_stat { - uint64_t speed{}; // defaults to 0 - uint64_t top{}; // defaults to 0 - uint64_t total{}; // defaults to 0 - uint64_t last{}; // defaults to 0 - uint64_t offset{}; // defaults to 0 - uint64_t rollover{}; // defaults to 0 + uint64_t speed{}; + uint64_t top{}; + uint64_t total{}; + uint64_t last{}; + uint64_t offset{}; + uint64_t rollover{}; }; struct net_info { - unordered_flat_map> bandwidth = { {"download", {}}, {"upload", {}} }; - unordered_flat_map stat = { {"download", {}}, {"upload", {}} }; + std::unordered_map> bandwidth = { {"download", {}}, {"upload", {}} }; + std::unordered_map stat = { {"download", {}}, {"upload", {}} }; string ipv4{}; // defaults to "" string ipv6{}; // defaults to "" - bool connected{}; // defaults to false + bool connected{}; }; - extern unordered_flat_map 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 current_net; //* Collect net upload/download stats auto collect(bool no_update=false) -> net_info&; @@ -322,7 +351,7 @@ namespace Proc { }; //? Translation from process state char to explanative string - const unordered_flat_map proc_states = { + const std::unordered_map proc_states = { {'R', "Running"}, {'S', "Sleeping"}, {'D', "Waiting"}, @@ -338,32 +367,32 @@ namespace Proc { //* Container for process information struct proc_info { - size_t pid{}; // defaults to 0 + size_t pid{}; string name{}; // defaults to "" string cmd{}; // defaults to "" string short_cmd{}; // defaults to "" - size_t threads{}; // defaults to 0 - int name_offset{}; // defaults to 0 + size_t threads{}; + int name_offset{}; string user{}; // defaults to "" - uint64_t mem{}; // defaults to 0 + uint64_t mem{}; double cpu_p{}; // defaults to = 0.0 double cpu_c{}; // defaults to = 0.0 char state = '0'; - int64_t p_nice{}; // defaults to 0 - uint64_t ppid{}; // defaults to 0 - uint64_t cpu_s{}; // defaults to 0 - uint64_t cpu_t{}; // defaults to 0 + int64_t p_nice{}; + uint64_t ppid{}; + uint64_t cpu_s{}; + uint64_t cpu_t{}; string prefix{}; // defaults to "" - size_t depth{}; // defaults to 0 - size_t tree_index{}; // defaults to 0 - bool collapsed{}; // defaults to false - bool filtered{}; // defaults to false + size_t depth{}; + size_t tree_index{}; + bool collapsed{}; + bool filtered{}; }; //* Container for process info box struct detail_container { - size_t last_pid{}; // defaults to 0 - bool skip_smaps{}; // defaults to false + size_t last_pid{}; + bool skip_smaps{}; proc_info entry; string elapsed, parent, status, io_read, io_write, memory; long long first_mem = -1; diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index 8a641fe..136adbf 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -42,11 +42,11 @@ namespace Theme { fs::path theme_dir; fs::path user_theme_dir; vector themes; - unordered_flat_map colors; - unordered_flat_map> rgbs; - unordered_flat_map> gradients; + std::unordered_map colors; + std::unordered_map> rgbs; + std::unordered_map> gradients; - const unordered_flat_map Default_theme = { + const std::unordered_map Default_theme = { { "main_bg", "#00" }, { "main_fg", "#cc" }, { "title", "#ee" }, @@ -91,7 +91,7 @@ namespace Theme { { "process_end", "#d45454" } }; - const unordered_flat_map TTY_theme = { + const std::unordered_map TTY_theme = { { "main_bg", "\x1b[0;40m" }, { "main_fg", "\x1b[37m" }, { "title", "\x1b[97m" }, @@ -224,7 +224,7 @@ namespace Theme { } //* Generate colors and rgb decimal vectors for the theme - void generateColors(const unordered_flat_map& source) { + void generateColors(const std::unordered_map& source) { vector t_rgb; string depth; bool t_to_256 = Config::getB("lowcolor"); @@ -372,7 +372,7 @@ namespace Theme { //* Load a .theme file from disk auto loadFile(const string& filename) { - unordered_flat_map theme_out; + std::unordered_map theme_out; const fs::path filepath = filename; if (not fs::exists(filepath)) return Default_theme; diff --git a/src/btop_theme.hpp b/src/btop_theme.hpp index 3fb30a4..dcc00e4 100644 --- a/src/btop_theme.hpp +++ b/src/btop_theme.hpp @@ -22,18 +22,17 @@ tab-size = 4 #include #include #include -#include +#include using std::array; using std::string; using std::vector; -using robin_hood::unordered_flat_map; namespace Theme { extern std::filesystem::path 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 themes; //* 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 void setTheme(); - extern unordered_flat_map colors; - extern unordered_flat_map> rgbs; - extern unordered_flat_map> gradients; + extern std::unordered_map colors; + extern std::unordered_map> rgbs; + extern std::unordered_map> gradients; //* Return escape code for color inline const string& c(const string& name) { return colors.at(name); } diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 88bf711..d1351e4 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -26,24 +26,23 @@ tab-size = 4 #include #include -#include -#include +#include #include +#include +#include -#include "robin_hood.h" +#include "unordered_map" #include "widechar_width.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" #include "btop_config.hpp" -using std::cin; using std::cout; using std::floor; using std::flush; using std::max; using std::string_view; using std::to_string; -using robin_hood::unordered_flat_map; 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 namespace Term { - atomic initialized{}; // defaults to false - atomic width{}; // defaults to 0 - atomic height{}; // defaults to 0 + atomic initialized{}; + atomic width{}; + atomic height{}; string current_tty; namespace { @@ -77,7 +76,11 @@ namespace Term { struct termios settings; if (tcgetattr(STDIN_FILENO, &settings)) return false; if (on) settings.c_lflag |= ICANON; - else settings.c_lflag &= ~(ICANON); + else { + settings.c_lflag &= ~(ICANON); + settings.c_cc[VMIN] = 0; + settings.c_cc[VTIME] = 0; + } if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false; if (on) setlinebuf(stdin); else setbuf(stdin, nullptr); @@ -86,12 +89,27 @@ namespace Term { } bool refresh(bool only_check) { - struct winsize w; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0) return false; - if (width != w.ws_col or height != w.ws_row) { + // Query dimensions of '/dev/tty' of the 'STDOUT_FILENO' isn't available. + // This variable is set in those cases to avoid calls to ioctl + constinit static bool uses_dev_tty = false; + struct winsize wsize {}; + if (uses_dev_tty || ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsize) < 0 || (wsize.ws_col == 0 && wsize.ws_row == 0)) { + Logger::error(R"(Couldn't determine terminal size of "STDOUT_FILENO"!)"); + auto dev_tty = open("/dev/tty", O_RDONLY); + if (dev_tty != -1) { + ioctl(dev_tty, TIOCGWINSZ, &wsize); + close(dev_tty); + } + else { + Logger::error(R"(Couldn't determine terminal size of "/dev/tty"!)"); + return false; + } + uses_dev_tty = true; + } + if (width != wsize.ws_col or height != wsize.ws_row) { if (not only_check) { - width = w.ws_col; - height = w.ws_row; + width = wsize.ws_col; + height = wsize.ws_row; } return true; } @@ -135,12 +153,12 @@ namespace Term { tcgetattr(STDIN_FILENO, &initial_settings); current_tty = (ttyname(STDIN_FILENO) != nullptr ? static_cast(ttyname(STDIN_FILENO)) : "unknown"); - //? Disable stream sync - cin.sync_with_stdio(false); + //? Disable stream sync - this does not seem to work on OpenBSD +#ifndef __OpenBSD__ cout.sync_with_stdio(false); +#endif //? Disable stream ties - cin.tie(nullptr); cout.tie(nullptr); echo(false); linebuffered(false); @@ -626,7 +644,7 @@ namespace Logger { size_t loglevel; 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 { int status = -1; public: @@ -652,6 +670,7 @@ namespace Logger { lose_priv neutered{}; std::error_code ec; try { + // NOTE: `exist()` could throw but since we return with an empty logfile we don't care if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) { auto old_log = logfile; old_log += ".1"; diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index c4d9670..07f7de3 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -18,6 +18,10 @@ tab-size = 4 #pragma once +#if !defined(NDEBUG) +# define BTOP_DEBUG +#endif + #include // for std::ranges::count_if #include #include @@ -31,6 +35,10 @@ tab-size = 4 #include #include #include +#include +#ifdef BTOP_DEBUG +#include +#endif #ifndef HOST_NAME_MAX #ifdef __APPLE__ #define HOST_NAME_MAX 255 @@ -38,11 +46,9 @@ tab-size = 4 #define HOST_NAME_MAX 64 #endif #endif -#define FMT_HEADER_ONLY + #include "fmt/core.h" #include "fmt/format.h" -#include "fmt/ostream.h" -#include "fmt/ranges.h" using std::array; using std::atomic; @@ -108,7 +114,7 @@ namespace Mv { //* Save cursor position const string save = Fx::e + "s"; - //* Restore saved cursor postion + //* Restore saved cursor position const string restore = Fx::e + "u"; } @@ -146,6 +152,35 @@ namespace Term { void restore(); } +//* Simple logging implementation +namespace Logger { + const vector 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 ----------------------------------------------------- namespace Tools { @@ -251,7 +286,7 @@ namespace Tools { 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) { return all_of(str.begin(), str.end(), ::isdigit); } @@ -304,6 +339,50 @@ namespace Tools { //* Add std::string operator * : Repeat string number of times std::string operator*(const string& str, int64_t n); + template +#ifdef BTOP_DEBUG + const T& safeVal(const std::unordered_map& 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& 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 +#ifdef BTOP_DEBUG + const T& safeVal(const std::vector& 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& 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 format string strf_time(const string& strf); @@ -329,7 +408,7 @@ namespace Tools { //* Sets atomic to true on construct, sets to false on destruct class atomic_lock { atomic& atom; - bool not_true{}; // defaults to false + bool not_true{}; public: atomic_lock(atomic& atom, bool wait = false); ~atomic_lock(); @@ -342,35 +421,6 @@ namespace Tools { auto celsius_to(const long long& celsius, const string& scale) -> tuple; } -//* Simple logging implementation -namespace Logger { - const vector 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 { //* 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 diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..f4a6ed3 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +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@"; diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 7f93322..a99f8df 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -58,6 +58,7 @@ tab-size = 4 #include #include #include +#include #include "../btop_config.hpp" #include "../btop_shared.hpp" @@ -98,7 +99,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -169,42 +170,36 @@ namespace Shared { Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); + Logger::debug("Init -> Cpu::collect()"); Cpu::collect(); for (auto &[field, vec] : Cpu::current_cpu.cpu_percent) { if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field); } + Logger::debug("Init -> Cpu::get_cpuName()"); Cpu::cpuName = Cpu::get_cpuName(); + Logger::debug("Init -> Cpu::get_sensors()"); Cpu::got_sensors = Cpu::get_sensors(); + Logger::debug("Init -> Cpu::get_core_mapping()"); Cpu::core_mapping = Cpu::get_core_mapping(); //? Init for namespace Mem Mem::old_uptime = system_uptime(); + Logger::debug("Init -> Mem::collect()"); Mem::collect(); + Logger::debug("Init -> 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 Cpu { string cpuName; string cpuHz; bool has_battery = true; - tuple current_bat; + tuple current_bat; const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -270,7 +265,7 @@ namespace Cpu { got_sensors = true; int 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... 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 } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; for (long i = 0; i < Shared::coreCount; i++) { @@ -366,10 +361,11 @@ namespace Cpu { return core_map; } - auto get_battery() -> tuple { - if (not has_battery) return {0, 0, ""}; + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; long seconds = -1; + float watts = -1; uint32_t percent = -1; size_t size = sizeof(percent); string status = "discharging"; @@ -381,6 +377,10 @@ namespace Cpu { if (sysctlbyname("hw.acpi.battery.time", &seconds, &size, nullptr, 0) < 0) { seconds = 0; } + size = sizeof(watts); + if (sysctlbyname("hw.acpi.battery.rate", &watts, &size, nullptr, 0) < 0) { + watts = -1; + } int state; size = sizeof(state); 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 & { @@ -557,7 +557,7 @@ namespace Mem { } } - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { // this bit is for 'regular' mounts static struct statinfo cur; long double etime = 0; @@ -572,7 +572,7 @@ namespace Mem { auto d = cur.dinfo->devices[i]; 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 - 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); assign_values(disk, total_bytes_read, total_bytes_write); string mountpoint = mapping.at(disk.dev); @@ -581,7 +581,6 @@ namespace Mem { } } - Logger::debug(""); } // this code is for ZFS mounts @@ -662,9 +661,9 @@ namespace Mem { if (show_swap) { 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]; - int nswap = kvm_getswapinfo(kd(), swap, 16, 0); + int nswap = kvm_getswapinfo(kd.get(), swap, 16, 0); int totalSwap = 0, usedSwap = 0; for (int i = 0; i < nswap; i++) { totalSwap += swap[i].ksw_total; @@ -691,7 +690,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -807,27 +806,16 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; - //* RAII wrapper for getifaddrs - class getifaddr_wrapper { - struct ifaddrs *ifaddr; - - public: - int status; - getifaddr_wrapper() { status = getifaddrs(&ifaddr); } - ~getifaddr_wrapper() { freeifaddrs(ifaddr); } - auto operator()() -> struct ifaddrs * { return ifaddr; } - }; - auto collect(bool no_update) -> net_info & { auto &net = current_net; auto &config_iface = Config::getS("net_iface"); @@ -837,10 +825,10 @@ namespace Net { if (not no_update and errors < 3) { //? Get interface list using getifaddrs() wrapper - getifaddr_wrapper if_wrap{}; - if (if_wrap.status != 0) { + IfAddrsPtr if_addrs {}; + if (if_addrs.get_status() != 0) { 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; return empty_net; } @@ -852,7 +840,7 @@ namespace Net { string ipv4, ipv6; //? 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; family = ifa->ifa_addr->sa_family; const auto &iface = ifa->ifa_name; @@ -892,7 +880,7 @@ namespace Net { } //else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -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 string dir : {"download", "upload"}) { auto &saved_stat = net.at(iface).stat.at(dir); @@ -966,7 +954,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1037,7 +1024,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; @@ -1159,8 +1146,8 @@ namespace Proc { int count = 0; char buf[_POSIX2_LINE_MAX]; - Shared::kvm_openfiles_wrapper kd(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf); - const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_PROC, 0, &count); + Shared::KvmPtr kd {kvm_openfiles(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf)}; + const struct kinfo_proc* kprocs = kvm_getprocs(kd.get(), KERN_PROC_PROC, 0, &count); for (int i = 0; i < count; i++) { const struct kinfo_proc* kproc = &kprocs[i]; @@ -1187,7 +1174,7 @@ namespace Proc { continue; } new_proc.name = kproc->ki_comm; - char** argv = kvm_getargv(kd(), kproc, 0); + char** argv = kvm_getargv(kd.get(), kproc, 0); if (argv) { for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) { new_proc.cmd += argv[i] + " "s; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 33a0be8..c549872 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -17,7 +17,8 @@ tab-size = 4 */ #include -#include +#include +#include #include #include #include @@ -31,6 +32,8 @@ tab-size = 4 #include #include #include +#include +#include #if defined(RSMI_STATIC) #include @@ -74,8 +77,8 @@ namespace Cpu { vector available_sensors = {"Auto"}; cpu_info current_cpu; fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; - bool got_sensors{}; // defaults to false - bool cpu_temp_only{}; // defaults to false + bool got_sensors{}; + bool cpu_temp_only{}; //* Populate found_sensors map bool get_sensors(); @@ -89,15 +92,15 @@ namespace Cpu { struct Sensor { fs::path path; string label; - int64_t temp{}; // defaults to 0 - int64_t high{}; // defaults to 0 - int64_t crit{}; // defaults to 0 + int64_t temp{}; + int64_t high{}; + int64_t crit{}; }; - unordered_flat_map found_sensors; + std::unordered_map found_sensors; string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } namespace Gpu { @@ -157,37 +160,44 @@ namespace Gpu { namespace Rsmi { #if !defined(RSMI_STATIC) //? RSMI defines, structs & typedefs - #define RSMI_MAX_NUM_FREQUENCIES 32 - #define RSMI_STATUS_SUCCESS 0 - #define RSMI_MEM_TYPE_VRAM 0 - #define RSMI_TEMP_CURRENT 0 - #define RSMI_TEMP_TYPE_EDGE 0 - #define RSMI_CLK_TYPE_MEM 4 - #define RSMI_CLK_TYPE_SYS 0 - #define RSMI_TEMP_MAX 1 + #define RSMI_MAX_NUM_FREQUENCIES_V5 32 + #define RSMI_MAX_NUM_FREQUENCIES_V6 33 + #define RSMI_STATUS_SUCCESS 0 + #define RSMI_MEM_TYPE_VRAM 0 + #define RSMI_TEMP_CURRENT 0 + #define RSMI_TEMP_TYPE_EDGE 0 + #define RSMI_CLK_TYPE_MEM 4 + #define RSMI_CLK_TYPE_SYS 0 + #define RSMI_TEMP_MAX 1 typedef int rsmi_status_t, rsmi_temperature_metric_t, rsmi_clk_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 rsmi_status_t (*rsmi_init)(uint64_t); 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_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_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_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_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_pci_throughput_get)(uint32_t, uint64_t*, uint64_t*, uint64_t*); + uint32_t version_major = 0; + //? Data void* rsmi_dl_handle; #endif @@ -291,14 +301,14 @@ namespace Cpu { string cpuName; string cpuHz; bool has_battery = true; - tuple current_bat; + tuple current_bat; const array time_names { "user"s, "nice"s, "system"s, "idle"s, "iowait"s, "irq"s, "softirq"s, "steal"s, "guest"s, "guest_nice"s }; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -539,14 +549,14 @@ namespace Cpu { } string get_cpuHz() { - static int failed{}; // defaults to 0 + static int failed{}; if (failed > 4) return ""s; string cpuhz; try { - double hz{}; // defaults to 0.0 + double hz{}; //? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster) if (not freq_path.empty()) { hz = stod(readfile(freq_path, "0.0")) / 1000; @@ -580,7 +590,7 @@ namespace Cpu { cpuhz += " GHz"; } else if (hz > 0) - cpuhz = to_string((int)round(hz)) + " MHz"; + cpuhz = to_string((int)hz) + " MHz"; } catch (const std::exception& e) { @@ -595,16 +605,16 @@ namespace Cpu { return cpuhz; } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; //? Try to get core mapping from /proc/cpuinfo ifstream cpuinfo(Shared::procPath / "cpuinfo"); if (cpuinfo.good()) { - int cpu{}; // defaults to 0 - int core{}; // defaults to 0 - int n{}; // defaults to 0 + int cpu{}; + int core{}; + int n{}; for (string instr; cpuinfo >> instr;) { if (instr == "processor") { cpuinfo.ignore(SSmax, ':'); @@ -661,15 +671,16 @@ namespace Cpu { } 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; - bool use_energy = true; + bool use_energy_or_charge = true; + bool use_power = true; }; - auto get_battery() -> tuple { - if (not has_battery) return {0, 0, ""}; + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; static string auto_sel; - static unordered_flat_map batteries; + static std::unordered_map batteries; //? Get paths to needed files and check for valid values on first run 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"; - else if (fs::exists(bat_dir / "charge_now")) new_bat.energy_now = bat_dir / "charge_now"; - else new_bat.use_energy = false; + else if (fs::exists(bat_dir / "charge_now")) new_bat.charge_now = bat_dir / "charge_now"; + else new_bat.use_energy_or_charge = false; 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 new_bat.use_energy = false; + else if (fs::exists(bat_dir / "charge_full")) new_bat.charge_full = bat_dir / "charge_full"; + 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; } - if (fs::exists(bat_dir / "power_now")) new_bat.power_now = bat_dir / "power_now"; - else if (fs::exists(bat_dir / "current_now")) new_bat.power_now = bat_dir / "current_now"; + if (fs::exists(bat_dir / "power_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"; else if (fs::exists(bat_dir / "AC/online")) new_bat.online = bat_dir / "AC/online"; @@ -726,7 +745,7 @@ namespace Cpu { } if (batteries.empty()) { has_battery = false; - return {0, 0, ""}; + return {0, 0, 0, ""}; } } @@ -746,15 +765,9 @@ namespace Cpu { int percent = -1; long seconds = -1; + float watts = -1; //? 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) { try { percent = stoll(readfile(b.base_dir / "capacity", "-1")); @@ -762,9 +775,23 @@ namespace Cpu { 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.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) { has_battery = false; - return {0, 0, ""}; + return {0, 0, 0, ""}; } //? Get charging/discharging status @@ -778,13 +805,23 @@ namespace Cpu { //? Get seconds to empty if (not is_in(status, "charging", "full")) { - if (b.use_energy and not b.power_now.empty()) { - try { - seconds = round((double)stoll(readfile(b.energy_now, "0")) / stoll(readfile(b.power_now, "1")) * 3600); + if (b.use_energy_or_charge ) { + if (not b.power_now.empty()) { + 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")) { try { 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& { @@ -946,16 +1002,27 @@ namespace Gpu { if (initialized) return false; //? Dynamic loading & linking - nvml_dl_handle = dlopen("libnvidia-ml.so", RTLD_LAZY); - if (!nvml_dl_handle) { - Logger::info(std::string("Failed to load libnvidia-ml.so, NVIDIA GPUs will not be detected: ") + dlerror()); - return false; + //? Try possible library names for libnvidia-ml.so + const array libNvAlts = { + "libnvidia-ml.so", + "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 sym = dlsym(nvml_dl_handle, sym_name); auto err = dlerror(); - if (err != NULL) { + if (err != nullptr) { Logger::error(string("NVML: Couldn't find function ") + sym_name + ": " + err); return (void*)nullptr; } else return sym; @@ -1211,19 +1278,32 @@ namespace Gpu { //? Dynamic loading & linking #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) { - rsmi_dl_handle = dlopen("librocm_smi64.so", RTLD_LAZY); - } + + //? Try possible library paths and names for librocm_smi64.so + 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) { - 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; } auto load_rsmi_sym = [&](const char sym_name[]) { auto sym = dlsym(rsmi_dl_handle, sym_name); auto err = dlerror(); - if (err != NULL) { + if (err != nullptr) { Logger::error(string("ROCm SMI: Couldn't find function ") + sym_name + ": " + err); return (void*)nullptr; } else return sym; @@ -1233,13 +1313,13 @@ namespace Gpu { LOAD_SYM(rsmi_init); LOAD_SYM(rsmi_shut_down); + LOAD_SYM(rsmi_version_get); LOAD_SYM(rsmi_num_monitor_devices); LOAD_SYM(rsmi_dev_name_get); LOAD_SYM(rsmi_dev_power_cap_get); LOAD_SYM(rsmi_dev_temp_metric_get); LOAD_SYM(rsmi_dev_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_memory_total_get); LOAD_SYM(rsmi_dev_memory_usage_get); @@ -1255,6 +1335,27 @@ namespace Gpu { 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 result = rsmi_num_monitor_devices(&device_count); if (result != RSMI_STATUS_SUCCESS) { @@ -1338,7 +1439,46 @@ namespace Gpu { if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false; } 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 if (gpus_slice[i].supported_functions.gpu_clock) { rsmi_frequencies_t frequencies; @@ -1357,6 +1497,7 @@ namespace Gpu { 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 } + #endif //? Power usage & state if (gpus_slice[i].supported_functions.pwr_usage) { @@ -1482,10 +1623,10 @@ namespace Gpu { #endif namespace Mem { - bool has_swap{}; // defaults to false + bool has_swap{}; vector fstab; fs::file_time_type fstab_time; - int disk_ios{}; // defaults to 0 + int disk_ios{}; vector last_found; //?* 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 zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto& disks = mem.disks; - static unordered_flat_map>> disks_stats_promises; + static std::unordered_map>> disks_stats_promises; ifstream diskread; vector filter; @@ -1920,7 +2061,7 @@ namespace Mem { if (access(zfs_pool_stat_path.c_str(), R_OK) == 0) { return zfs_pool_stat_path; } else { - Logger::debug("Cant access folder: " + zfs_pool_stat_path.string()); + Logger::debug("Can't access folder: " + zfs_pool_stat_path.string()); return ""; } } @@ -1973,10 +2114,10 @@ namespace Mem { int64_t bytes_read; int64_t bytes_write; int64_t io_ticks; - int64_t bytes_read_total{}; // defaults to 0 - int64_t bytes_write_total{}; // defaults to 0 - int64_t io_ticks_total{}; // defaults to 0 - int64_t objects_read{}; // defaults to 0 + int64_t bytes_read_total{}; + int64_t bytes_write_total{}; + int64_t io_ticks_total{}; + int64_t objects_read{}; // looking through all files that start with 'objset' for (const auto& file: fs::directory_iterator(disk.stat)) { @@ -2048,25 +2189,15 @@ namespace Mem { } namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; - int errors{}; // defaults to 0 - unordered_flat_map graph_max = { {"download", {}}, {"upload", {}} }; - unordered_flat_map> max_count = { {"download", {}}, {"upload", {}} }; + int errors{}; + std::unordered_map graph_max = { {"download", {}}, {"upload", {}} }; + std::unordered_map> max_count = { {"download", {}}, {"upload", {}} }; bool rescale{true}; - uint64_t timestamp{}; // defaults to 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; } - }; + uint64_t timestamp{}; auto collect(bool no_update) -> net_info& { if (Runner::stopping) return empty_net; @@ -2078,10 +2209,10 @@ namespace Net { if (not no_update and errors < 3) { //? Get interface list using getifaddrs() wrapper - getifaddr_wrapper if_wrap {}; - if (if_wrap.status != 0) { + IfAddrsPtr if_addrs {}; + if (if_addrs.get_status() != 0) { 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; return empty_net; } @@ -2093,7 +2224,7 @@ namespace Net { string ipv4, ipv6; //? 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; family = ifa->ifa_addr->sa_family; 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. } - //? 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) { if (net.at(iface).ipv4.empty() and net.at(iface).ipv6.empty()) 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& 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")); } catch (const std::invalid_argument&) {} catch (const std::out_of_range&) {} @@ -2193,7 +2324,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -2263,22 +2393,22 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; - bool current_rev{}; // defaults to false + bool current_rev{}; fs::file_time_type passwd_time; uint64_t cputimes; int collapse = -1, expand = -1; - uint64_t old_cputimes{}; // defaults to 0 - atomic numpids{}; // defaults to 0 - int filter_found{}; // defaults to 0 + uint64_t old_cputimes{}; + atomic numpids{}; + int filter_found{}; detail_container detailed; constexpr size_t KTHREADD = 2; - static robin_hood::unordered_set kernels_procs = {KTHREADD}; + static std::unordered_set kernels_procs = {KTHREADD}; //* Get detailed info for selected process void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { @@ -2406,7 +2536,7 @@ namespace Proc { const int cmult = (per_core) ? Shared::coreCount : 1; 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 if (no_update and not current_procs.empty()) { @@ -2481,7 +2611,7 @@ namespace Proc { //? Check if pid already exists in current_procs 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()) { current_procs.push_back({pid}); find_old = current_procs.end() - 1; diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp new file mode 100644 index 0000000..9b3e1f2 --- /dev/null +++ b/src/openbsd/btop_collect.cpp @@ -0,0 +1,1272 @@ +/* Copyright 2021 Aristocratos (jakob@qvantnet.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +indent = tab +tab-size = 4 +*/ +#include +#include +#include +#include +#include +// man 3 getifaddrs: "BUGS: If both and are being included, must be included before " +#include +#include +#include +#include +#include +#include +#include // for inet_ntop stuff +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../btop_config.hpp" +#include "../btop_shared.hpp" +#include "../btop_tools.hpp" + +#include "./sysctlbyname.h" + +using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; +using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min; +namespace fs = std::filesystem; +namespace rng = std::ranges; +using namespace Tools; + +//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- + +namespace Cpu { + vector core_old_totals; + vector core_old_idles; + vector available_fields = {"total"}; + vector available_sensors = {"Auto"}; + cpu_info current_cpu; + bool got_sensors = false, cpu_temp_only = false; + + //* Populate found_sensors map + bool get_sensors(); + + //* Get current cpu clock speed + string get_cpuHz(); + + //* Search /proc/cpuinfo for a cpu name + string get_cpuName(); + + struct Sensor { + fs::path path; + string label; + int64_t temp = 0; + int64_t high = 0; + int64_t crit = 0; + }; + + string cpu_sensor; + vector core_sensors; + std::unordered_map core_mapping; +} // namespace Cpu + +namespace Mem { + double old_uptime; +} + +namespace Shared { + + fs::path passwd_path; + uint64_t totalMem; + long pageSize, clkTck, coreCount, physicalCoreCount, arg_max; + int totalMem_len, kfscale; + long bootTime; + + void init() { + //? Shared global variables init + int mib[2]; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + int ncpu; + size_t len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, nullptr, 0) == -1) { + Logger::warning("Could not determine number of cores, defaulting to 1."); + } else { + coreCount = ncpu; + } + + pageSize = sysconf(_SC_PAGE_SIZE); + if (pageSize <= 0) { + pageSize = 4096; + Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect."); + } + + clkTck = sysconf(_SC_CLK_TCK); + if (clkTck <= 0) { + clkTck = 100; + Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect."); + } + + int64_t memsize = 0; + size_t size = sizeof(memsize); + if (sysctlbyname("hw.physmem", &memsize, &size, nullptr, 0) < 0) { + Logger::warning("Could not get memory size"); + } + totalMem = memsize; + + struct timeval result; + size = sizeof(result); + if (sysctlbyname("kern.boottime", &result, &size, nullptr, 0) < 0) { + Logger::warning("Could not get boot time"); + } else { + bootTime = result.tv_sec; + } + + size = sizeof(kfscale); + if (sysctlbyname("kern.fscale", &kfscale, &size, nullptr, 0) == -1) { + kfscale = 2048; + } + + //* Get maximum length of process arguments + arg_max = sysconf(_SC_ARG_MAX); + + //? Init for namespace Cpu + Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {}); + Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); + Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); + Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); + Cpu::collect(); + for (auto &[field, vec] : Cpu::current_cpu.cpu_percent) { + if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field); + } + Cpu::cpuName = Cpu::get_cpuName(); + Cpu::got_sensors = Cpu::get_sensors(); + Cpu::core_mapping = Cpu::get_core_mapping(); + + //? Init for namespace Mem + Mem::old_uptime = system_uptime(); + Mem::collect(); + } +} // namespace Shared + +namespace Cpu { + string cpuName; + string cpuHz; + bool has_battery = true; + tuple current_bat; + + const array time_names = {"user", "nice", "system", "idle"}; + + std::unordered_map cpu_old = { + {"totals", 0}, + {"idles", 0}, + {"user", 0}, + {"nice", 0}, + {"system", 0}, + {"idle", 0} + }; + + string get_cpuName() { + string name; + char buffer[1024]; + size_t size = sizeof(buffer); + if (sysctlbyname("hw.model", &buffer, &size, nullptr, 0) < 0) { + Logger::error("Failed to get CPU name"); + return name; + } + name = string(buffer); + + auto name_vec = ssplit(name); + + if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) { + auto cpu_pos = v_index(name_vec, "CPU"s); + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')')) + name = name_vec.at(cpu_pos + 1); + else + name.clear(); + } else if (v_contains(name_vec, "Ryzen"s)) { + auto ryz_pos = v_index(name_vec, "Ryzen"s); + name = "Ryzen" + (ryz_pos < name_vec.size() - 1 ? ' ' + name_vec.at(ryz_pos + 1) : "") + (ryz_pos < name_vec.size() - 2 ? ' ' + name_vec.at(ryz_pos + 2) : ""); + } else if (s_contains(name, "Intel"s) and v_contains(name_vec, "CPU"s)) { + auto cpu_pos = v_index(name_vec, "CPU"s); + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1) != "@") + name = name_vec.at(cpu_pos + 1); + else + name.clear(); + } else + name.clear(); + + if (name.empty() and not name_vec.empty()) { + for (const auto &n : name_vec) { + if (n == "@") break; + name += n + ' '; + } + name.pop_back(); + for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Core"}) { + name = s_replace(name, replace, ""); + name = s_replace(name, " ", " "); + } + name = trim(name); + } + + return name; + } + + int64_t get_sensor(string device, sensor_type type, int num) { + int64_t temp = -1; + struct sensordev sensordev; + struct sensor sensor; + size_t sdlen, slen; + int dev; + int mib[] = {CTL_HW, HW_SENSORS, 0, 0, 0}; + + sdlen = sizeof(sensordev); + slen = sizeof(sensor); + for (dev = 0;; dev++) { + mib[2] = dev; + if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { + if (errno == ENXIO) + continue; + if (errno == ENOENT) + break; + } + if (strstr(sensordev.xname, device.c_str())) { + mib[3] = type; + mib[4] = num; + if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) { + if (errno != ENOENT) { + Logger::warning("sysctl"); + continue; + } + } + temp = sensor.value; + break; + } + } + return temp; + } + + bool get_sensors() { + got_sensors = false; + if (Config::getB("show_coretemp") and Config::getB("check_temp")) { + if (get_sensor(string("cpu0") , SENSOR_TEMP, 0) > 0) { + got_sensors = true; + current_cpu.temp_max = 100; // we don't have this info + } else { + Logger::warning("Could not get temp sensor"); + } + } + return got_sensors; + } + +#define MUKTOC(v) ((v - 273150000) / 1000000.0) + + void update_sensors() { + int temp = 0; + int p_temp = 0; + + temp = get_sensor(string("cpu0"), SENSOR_TEMP, 0); + if (temp > -1) { + temp = MUKTOC(temp); + p_temp = temp; + for (int i = 0; i < Shared::coreCount; i++) { + if (cmp_less(i + 1, current_cpu.temp.size())) { + current_cpu.temp.at(i + 1).push_back(temp); + if (current_cpu.temp.at(i + 1).size() > 20) + current_cpu.temp.at(i + 1).pop_front(); + } + } + current_cpu.temp.at(0).push_back(p_temp); + if (current_cpu.temp.at(0).size() > 20) + current_cpu.temp.at(0).pop_front(); + } + + } + + string get_cpuHz() { + unsigned int freq = 1; + size_t size = sizeof(freq); + + if (sysctlbyname("hw.cpuspeed", &freq, &size, nullptr, 0) < 0) { + return ""; + } + return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz + } + + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; + if (cpu_temp_only) return core_map; + + for (long i = 0; i < Shared::coreCount; i++) { + core_map[i] = i; + } + + //? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely, map 0-0 1-1 2-2 etc. + if (cmp_less(core_map.size(), Shared::coreCount)) { + if (Shared::coreCount % 2 == 0 and (long) core_map.size() == Shared::coreCount / 2) { + for (int i = 0, n = 0; i < Shared::coreCount / 2; i++) { + if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; + core_map[Shared::coreCount / 2 + i] = n++; + } + } else { + core_map.clear(); + for (int i = 0, n = 0; i < Shared::coreCount; i++) { + if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; + core_map[i] = n++; + } + } + } + + //? Apply user set custom mapping if any + const auto &custom_map = Config::getS("cpu_core_map"); + if (not custom_map.empty()) { + try { + for (const auto &split : ssplit(custom_map)) { + const auto vals = ssplit(split, ':'); + if (vals.size() != 2) continue; + int change_id = std::stoi(vals.at(0)); + int new_id = std::stoi(vals.at(1)); + if (not core_map.contains(change_id) or cmp_greater(new_id, core_sensors.size())) continue; + core_map.at(change_id) = new_id; + } + } catch (...) { + } + } + + return core_map; + } + + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; + + long seconds = -1; + uint32_t percent = -1; + string status = "discharging"; + int64_t full, remaining; + full = get_sensor("acpibat0", SENSOR_AMPHOUR, 0); + remaining = get_sensor("acpibat0", SENSOR_AMPHOUR, 3); + int64_t state = get_sensor("acpibat0", SENSOR_INTEGER, 0); + if (full < 0) { + has_battery = false; + Logger::warning("failed to get battery"); + } else { + float_t f = full / 1000; + float_t r = remaining / 1000; + has_battery = true; + percent = r / f * 100; + if (percent == 100) { + status = "full"; + } + switch (state) { + case 0: + status = "full"; + percent = 100; + break; + case 2: + status = "charging"; + break; + } + } + + return {percent, -1, seconds, status}; + } + + auto collect(bool no_update) -> cpu_info & { + if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) + return current_cpu; + auto &cpu = current_cpu; + + if (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) { + Logger::error("failed to get load averages"); + } + + auto cp_time = std::unique_ptr{ + new struct cpustats[Shared::coreCount] + }; + size_t size = Shared::coreCount * sizeof(struct cpustats); + static int cpustats_mib[] = {CTL_KERN, KERN_CPUSTATS, /*fillme*/0}; + for (int i = 0; i < Shared::coreCount; i++) { + cpustats_mib[2] = i / 2; + if (sysctl(cpustats_mib, 3, &cp_time[i], &size, NULL, 0) == -1) { + Logger::error("sysctl kern.cpustats failed"); + } + } + long long global_totals = 0; + long long global_idles = 0; + vector times_summed = {0, 0, 0, 0}; + + for (long i = 0; i < Shared::coreCount; i++) { + vector times; + //? 0=user, 1=nice, 2=system, 3=idle + for (int x = 0; const unsigned int c_state : {CP_USER, CP_NICE, CP_SYS, CP_IDLE}) { + auto val = cp_time[i].cs_time[c_state]; + times.push_back(val); + times_summed.at(x++) += val; + } + try { + //? All values + const long long totals = std::accumulate(times.begin(), times.end(), 0ll); + + //? Idle time + const long long idles = times.at(3); + + global_totals += totals; + global_idles += idles; + + //? Calculate cpu total for each core + if (i > Shared::coreCount) break; + const long long calc_totals = max(0ll, totals - core_old_totals.at(i)); + const long long calc_idles = max(0ll, idles - core_old_idles.at(i)); + core_old_totals.at(i) = totals; + core_old_idles.at(i) = idles; + + cpu.core_percent.at(i).push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll)); + + //? Reduce size if there are more values than needed for graph + if (cpu.core_percent.at(i).size() > 40) cpu.core_percent.at(i).pop_front(); + + } catch (const std::exception &e) { + Logger::error("Cpu::collect() : " + (string)e.what()); + throw std::runtime_error("collect() : " + (string)e.what()); + } + + } + + const long long calc_totals = max(1ll, global_totals - cpu_old.at("totals")); + const long long calc_idles = max(1ll, global_idles - cpu_old.at("idles")); + + //? Populate cpu.cpu_percent with all fields from syscall + for (int ii = 0; const auto &val : times_summed) { + cpu.cpu_percent.at(time_names.at(ii)).push_back(clamp((long long)round((double)(val - cpu_old.at(time_names.at(ii))) * 100 / calc_totals), 0ll, 100ll)); + cpu_old.at(time_names.at(ii)) = val; + + //? Reduce size if there are more values than needed for graph + while (cmp_greater(cpu.cpu_percent.at(time_names.at(ii)).size(), width * 2)) cpu.cpu_percent.at(time_names.at(ii)).pop_front(); + + ii++; + } + + cpu_old.at("totals") = global_totals; + cpu_old.at("idles") = global_idles; + + //? Total usage of cpu + cpu.cpu_percent.at("total").push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll)); + + //? Reduce size if there are more values than needed for graph + while (cmp_greater(cpu.cpu_percent.at("total").size(), width * 2)) cpu.cpu_percent.at("total").pop_front(); + + if (Config::getB("show_cpu_freq")) { + auto hz = get_cpuHz(); + if (hz != "") { + cpuHz = hz; + } + } + + if (Config::getB("check_temp") and got_sensors) + update_sensors(); + + if (Config::getB("show_battery") and has_battery) + current_bat = get_battery(); + + return cpu; + } +} // namespace Cpu + +namespace Mem { + bool has_swap = false; + vector fstab; + fs::file_time_type fstab_time; + int disk_ios = 0; + vector last_found; + + mem_info current_mem{}; + + uint64_t get_totalMem() { + return Shared::totalMem; + } + + void assign_values(struct disk_info& disk, int64_t readBytes, int64_t writeBytes) { + disk_ios++; + if (disk.io_read.empty()) { + disk.io_read.push_back(0); + } else { + disk.io_read.push_back(max((int64_t)0, (readBytes - disk.old_io.at(0)))); + } + disk.old_io.at(0) = readBytes; + while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); + + if (disk.io_write.empty()) { + disk.io_write.push_back(0); + } else { + disk.io_write.push_back(max((int64_t)0, (writeBytes - disk.old_io.at(1)))); + } + disk.old_io.at(1) = writeBytes; + while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front(); + + // no io times - need to push something anyway or we'll get an ABORT + if (disk.io_activity.empty()) + disk.io_activity.push_back(0); + else + disk.io_activity.push_back(clamp((long)round((double)(disk.io_write.back() + disk.io_read.back()) / (1 << 20)), 0l, 100l)); + while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); + } + + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { + uint64_t total_bytes_read = 0; + uint64_t total_bytes_write = 0; + + int num_drives = 0; + int mib[2] = { CTL_HW, HW_DISKCOUNT }; + + size_t size; + if (sysctl(mib, 2, &num_drives, &size, NULL, 0) >= 0) { + mib[0] = CTL_HW; + mib[1] = HW_DISKSTATS; + size = num_drives * sizeof(struct diskstats); + auto p = std::unique_ptr { + reinterpret_cast(malloc(size)), + free + }; + if (sysctl(mib, 2, p.get(), &size, NULL, 0) == -1) { + Logger::error("failed to get disk stats"); + return; + } + for (int i = 0; i < num_drives; i++) { + for (auto& [ignored, disk] : disks) { + if (disk.dev.string().find(p[i].ds_name) != string::npos) { + string mountpoint = mapping.at(disk.dev); + total_bytes_read = p[i].ds_rbytes; + total_bytes_write = p[i].ds_wbytes; + assign_values(disk, total_bytes_read, total_bytes_write); + } + } + } + } + } + + auto collect(bool no_update) -> mem_info & { + if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) + return current_mem; + + auto show_swap = Config::getB("show_swap"); + auto show_disks = Config::getB("show_disks"); + auto swap_disk = Config::getB("swap_disk"); + auto &mem = current_mem; + static bool snapped = (getenv("BTOP_SNAPPED") != nullptr); + + u_int memActive, memWire, cachedMem; + // u_int freeMem; + size_t size; + static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; + static int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT}; + struct uvmexp uvmexp; + struct bcachestats bcstats; + size = sizeof(uvmexp); + if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) { + Logger::error("sysctl failed"); + bzero(&uvmexp, sizeof(uvmexp)); + } + size = sizeof(bcstats); + if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) == -1) { + Logger::error("sysctl failed"); + bzero(&bcstats, sizeof(bcstats)); + } + memActive = uvmexp.active * Shared::pageSize; + memWire = uvmexp.wired; + // freeMem = uvmexp.free * Shared::pageSize; + cachedMem = bcstats.numbufpages * Shared::pageSize; + mem.stats.at("used") = memActive; + mem.stats.at("available") = Shared::totalMem - memActive - memWire; + mem.stats.at("cached") = cachedMem; + mem.stats.at("free") = Shared::totalMem - memActive - memWire; + + if (show_swap) { + int total = uvmexp.swpages * Shared::pageSize; + mem.stats.at("swap_total") = total; + int swapped = uvmexp.swpgonly * Shared::pageSize; + mem.stats.at("swap_used") = swapped; + mem.stats.at("swap_free") = total - swapped; + } + + if (show_swap and mem.stats.at("swap_total") > 0) { + for (const auto &name : swap_names) { + mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total"))); + while (cmp_greater(mem.percent.at(name).size(), width * 2)) + mem.percent.at(name).pop_front(); + } + has_swap = true; + } else + has_swap = false; + //? Calculate percentages + for (const auto &name : mem_names) { + mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / Shared::totalMem)); + while (cmp_greater(mem.percent.at(name).size(), width * 2)) + mem.percent.at(name).pop_front(); + } + + if (show_disks) { + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + double uptime = system_uptime(); + auto &disks_filter = Config::getS("disks_filter"); + bool filter_exclude = false; + // auto only_physical = Config::getB("only_physical"); + auto &disks = mem.disks; + vector filter; + if (not disks_filter.empty()) { + filter = ssplit(disks_filter); + if (filter.at(0).starts_with("exclude=")) { + filter_exclude = true; + filter.at(0) = filter.at(0).substr(8); + } + } + + struct statfs *stfs; + int count = getmntinfo(&stfs, MNT_WAIT); + vector found; + found.reserve(last_found.size()); + for (int i = 0; i < count; i++) { + auto fstype = string(stfs[i].f_fstypename); + if (fstype == "autofs" || fstype == "devfs" || fstype == "linprocfs" || fstype == "procfs" || fstype == "tmpfs" || fstype == "linsysfs" || + fstype == "fdesckfs") { + // in memory filesystems -> not useful to show + continue; + } + + std::error_code ec; + string mountpoint = stfs[i].f_mntonname; + string dev = stfs[i].f_mntfromname; + mapping[dev] = mountpoint; + + //? Match filter if not empty + if (not filter.empty()) { + bool match = v_contains(filter, mountpoint); + if ((filter_exclude and match) or (not filter_exclude and not match)) + continue; + } + + found.push_back(mountpoint); + if (not disks.contains(mountpoint)) { + disks[mountpoint] = disk_info{fs::canonical(dev, ec), fs::path(mountpoint).filename()}; + + if (disks.at(mountpoint).dev.empty()) + disks.at(mountpoint).dev = dev; + + if (disks.at(mountpoint).name.empty()) + disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint); + } + + + if (not v_contains(last_found, mountpoint)) + redraw = true; + + disks.at(mountpoint).free = stfs[i].f_bfree; + disks.at(mountpoint).total = stfs[i].f_iosize; + } + + //? Remove disks no longer mounted or filtered out + if (swap_disk and has_swap) found.push_back("swap"); + for (auto it = disks.begin(); it != disks.end();) { + if (not v_contains(found, it->first)) + it = disks.erase(it); + else + it++; + } + if (found.size() != last_found.size()) redraw = true; + last_found = std::move(found); + + //? Get disk/partition stats + for (auto &[mountpoint, disk] : disks) { + if (std::error_code ec; not fs::exists(mountpoint, ec)) + continue; + struct statvfs vfs; + if (statvfs(mountpoint.c_str(), &vfs) < 0) { + Logger::warning("Failed to get disk/partition stats with statvfs() for: " + mountpoint); + continue; + } + disk.total = vfs.f_blocks * vfs.f_frsize; + disk.free = vfs.f_bfree * vfs.f_frsize; + disk.used = disk.total - disk.free; + disk.used_percent = round((double)disk.used * 100 / disk.total); + disk.free_percent = 100 - disk.used_percent; + } + + //? Setup disks order in UI and add swap if enabled + mem.disks_order.clear(); + if (snapped and disks.contains("/mnt")) + mem.disks_order.push_back("/mnt"); + else if (disks.contains("/")) + mem.disks_order.push_back("/"); + if (swap_disk and has_swap) { + mem.disks_order.push_back("swap"); + if (not disks.contains("swap")) + disks["swap"] = {"", "swap"}; + disks.at("swap").total = mem.stats.at("swap_total"); + disks.at("swap").used = mem.stats.at("swap_used"); + disks.at("swap").free = mem.stats.at("swap_free"); + disks.at("swap").used_percent = mem.percent.at("swap_used").back(); + disks.at("swap").free_percent = mem.percent.at("swap_free").back(); + } + for (const auto &name : last_found) + if (not is_in(name, "/", "swap", "/dev")) + mem.disks_order.push_back(name); + + disk_ios = 0; + collect_disk(disks, mapping); + + old_uptime = uptime; + } + return mem; + } + +} // namespace Mem + +namespace Net { + std::unordered_map current_net; + net_info empty_net = {}; + vector interfaces; + string selected_iface; + int errors = 0; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; + bool rescale = true; + uint64_t timestamp = 0; + + auto collect(bool no_update) -> net_info & { + auto &net = current_net; + auto &config_iface = Config::getS("net_iface"); + auto net_sync = Config::getB("net_sync"); + auto net_auto = Config::getB("net_auto"); + auto new_timestamp = time_ms(); + + if (not no_update and errors < 3) { + //? Get interface list using getifaddrs() wrapper + IfAddrsPtr if_addrs {}; + if (if_addrs.get_status() != 0) { + errors++; + Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_addrs.get_status())); + redraw = true; + return empty_net; + } + int family = 0; + static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); // 46 >= 16, compile-time assurance. + enum { IPBUFFER_MAXSIZE = INET6_ADDRSTRLEN }; // manually using the known biggest value, guarded by the above static_assert + char ip[IPBUFFER_MAXSIZE]; + interfaces.clear(); + string ipv4, ipv6; + + //? Iteration over all items in getifaddrs() list + for (auto *ifa = if_addrs.get(); ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr) continue; + family = ifa->ifa_addr->sa_family; + const auto &iface = ifa->ifa_name; + //? Update available interfaces vector and get status of interface + if (not v_contains(interfaces, iface)) { + interfaces.push_back(iface); + net[iface].connected = (ifa->ifa_flags & IFF_RUNNING); + + // An interface can have more than one IP of the same family associated with it, + // but we pick only the first one to show in the NET box. + // Note: Interfaces without any IPv4 and IPv6 set are still valid and monitorable! + net[iface].ipv4.clear(); + net[iface].ipv6.clear(); + } + //? Get IPv4 address + if (family == AF_INET) { + if (net[iface].ipv4.empty()) { + if (nullptr != inet_ntop(family, &(reinterpret_cast(ifa->ifa_addr)->sin_addr), ip, IPBUFFER_MAXSIZE)) { + + net[iface].ipv4 = ip; + } else { + int errsv = errno; + Logger::error("Net::collect() -> Failed to convert IPv4 to string for iface " + string(iface) + ", errno: " + strerror(errsv)); + } + } + } + //? Get IPv6 address + else if (family == AF_INET6) { + if (net[iface].ipv6.empty()) { + if (nullptr != inet_ntop(family, &(reinterpret_cast(ifa->ifa_addr)->sin6_addr), ip, IPBUFFER_MAXSIZE)) { + net[iface].ipv6 = ip; + } else { + int errsv = errno; + Logger::error("Net::collect() -> Failed to convert IPv6 to string for iface " + string(iface) + ", errno: " + strerror(errsv)); + } + } + } //else, ignoring family==AF_LINK (see man 3 getifaddrs) + } + + std::unordered_map> ifstats; + int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; + size_t len; + if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { + Logger::error("failed getting network interfaces"); + } else { + std::unique_ptr buf(new char[len]); + if (sysctl(mib, 6, buf.get(), &len, nullptr, 0) < 0) { + Logger::error("failed getting network interfaces"); + } else { + char *lim = buf.get() + len; + char *next = nullptr; + for (next = buf.get(); next < lim;) { + struct if_msghdr *ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + struct if_data ifm_data = ifm->ifm_data; + if (ifm->ifm_addrs & RTA_IFP) { + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(ifm + 1); + char iface[32]; + strncpy(iface, sdl->sdl_data, sdl->sdl_nlen); + iface[sdl->sdl_nlen] = 0; + ifstats[iface] = std::tuple(ifm_data.ifi_ibytes, ifm_data.ifi_obytes); + } + } + } + } + + //? Get total received and transmitted bytes + device address if no ip was found + for (const auto &iface : interfaces) { + for (const string dir : {"download", "upload"}) { + auto &saved_stat = net.at(iface).stat.at(dir); + auto &bandwidth = net.at(iface).bandwidth.at(dir); + uint64_t val = dir == "download" ? std::get<0>(ifstats[iface]) : std::get<1>(ifstats[iface]); + + //? Update speed, total and top values + if (val < saved_stat.last) { + saved_stat.rollover += saved_stat.last; + saved_stat.last = 0; + } + if (cmp_greater((unsigned long long)saved_stat.rollover + (unsigned long long)val, numeric_limits::max())) { + saved_stat.rollover = 0; + saved_stat.last = 0; + } + saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000)); + if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed; + if (saved_stat.offset > val + saved_stat.rollover) saved_stat.offset = 0; + saved_stat.total = (val + saved_stat.rollover) - saved_stat.offset; + saved_stat.last = val; + + //? Add values to graph + bandwidth.push_back(saved_stat.speed); + while (cmp_greater(bandwidth.size(), width * 2)) bandwidth.pop_front(); + + //? Set counters for auto scaling + if (net_auto and selected_iface == iface) { + if (saved_stat.speed > graph_max[dir]) { + ++max_count[dir][0]; + if (max_count[dir][1] > 0) --max_count[dir][1]; + } else if (graph_max[dir] > 10 << 10 and saved_stat.speed < graph_max[dir] / 10) { + ++max_count[dir][1]; + if (max_count[dir][0] > 0) --max_count[dir][0]; + } + } + } + } + + //? Clean up net map if needed + if (net.size() > interfaces.size()) { + for (auto it = net.begin(); it != net.end();) { + if (not v_contains(interfaces, it->first)) + it = net.erase(it); + else + it++; + } + } + + timestamp = new_timestamp; + } + //? Return empty net_info struct if no interfaces was found + if (net.empty()) + return empty_net; + + //? Find an interface to display if selected isn't set or valid + if (selected_iface.empty() or not v_contains(interfaces, selected_iface)) { + max_count["download"][0] = max_count["download"][1] = max_count["upload"][0] = max_count["upload"][1] = 0; + redraw = true; + if (net_auto) rescale = true; + if (not config_iface.empty() and v_contains(interfaces, config_iface)) + selected_iface = config_iface; + else { + //? Sort interfaces by total upload + download bytes + auto sorted_interfaces = interfaces; + rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) { + return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total, + net.at(b).stat["download"].total + net.at(b).stat["upload"].total); + }); + selected_iface.clear(); + //? Try to set to a connected interface + for (const auto &iface : sorted_interfaces) { + if (net.at(iface).connected) selected_iface = iface; + break; + } + //? If no interface is connected set to first available + if (selected_iface.empty() and not sorted_interfaces.empty()) + selected_iface = sorted_interfaces.at(0); + else if (sorted_interfaces.empty()) + return empty_net; + } + } + + //? Calculate max scale for graphs if needed + if (net_auto) { + bool sync = false; + for (const auto &dir : {"download", "upload"}) { + for (const auto &sel : {0, 1}) { + if (rescale or max_count[dir][sel] >= 5) { + const long long avg_speed = (net[selected_iface].bandwidth[dir].size() > 5 + ? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0ll) / 5 + : net[selected_iface].stat[dir].speed); + graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10); + max_count[dir][0] = max_count[dir][1] = 0; + redraw = true; + if (net_sync) sync = true; + break; + } + } + //? Sync download/upload graphs if enabled + if (sync) { + const auto other = (string(dir) == "upload" ? "download" : "upload"); + graph_max[other] = graph_max[dir]; + max_count[other][0] = max_count[other][1] = 0; + break; + } + } + } + + rescale = false; + return net.at(selected_iface); + } +} // namespace Net + +namespace Proc { + + vector current_procs; + std::unordered_map uid_user; + string current_sort; + string current_filter; + bool current_rev = false; + + fs::file_time_type passwd_time; + + uint64_t cputimes; + int collapse = -1, expand = -1; + uint64_t old_cputimes = 0; + atomic numpids = 0; + int filter_found = 0; + + detail_container detailed; + + string get_status(char s) { + if (s & SRUN) return "Running"; + if (s & SSLEEP) return "Sleeping"; + if (s & SIDL) return "Idle"; + if (s & SSTOP) return "Stopped"; + if (s & SZOMB) return "Zombie"; + return "Unknown"; + } + + //* Get detailed info for selected process + void _collect_details(const size_t pid, vector &procs) { + if (pid != detailed.last_pid) { + detailed = {}; + detailed.last_pid = pid; + detailed.skip_smaps = not Config::getB("proc_info_smaps"); + } + + //? Copy proc_info for process from proc vector + auto p_info = rng::find(procs, pid, &proc_info::pid); + detailed.entry = *p_info; + + //? Update cpu percent deque for process cpu graph + if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount; + detailed.cpu_percent.push_back(clamp((long long)round(detailed.entry.cpu_p), 0ll, 100ll)); + while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front(); + + //? Process runtime : current time - start time (both in unix time - seconds since epoch) + struct timeval currentTime; + gettimeofday(¤tTime, nullptr); + detailed.elapsed = sec_to_dhms(currentTime.tv_sec - detailed.entry.cpu_s); // only interested in second granularity, so ignoring tc_usec + if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3); + + //? Get parent process name + if (detailed.parent.empty()) { + auto p_entry = rng::find(procs, detailed.entry.ppid, &proc_info::pid); + if (p_entry != procs.end()) detailed.parent = p_entry->name; + } + + //? Expand process status from single char to explanative string + detailed.status = get_status(detailed.entry.state); + + detailed.mem_bytes.push_back(detailed.entry.mem); + detailed.memory = floating_humanizer(detailed.entry.mem); + + if (detailed.first_mem == -1 or detailed.first_mem < detailed.mem_bytes.back() / 2 or detailed.first_mem > detailed.mem_bytes.back() * 4) { + detailed.first_mem = min((uint64_t)detailed.mem_bytes.back() * 2, Mem::get_totalMem()); + redraw = true; + } + + while (cmp_greater(detailed.mem_bytes.size(), width)) detailed.mem_bytes.pop_front(); + } + + //* Collects and sorts process information from /proc + auto collect(bool no_update) -> vector & { + const auto &sorting = Config::getS("proc_sorting"); + auto reverse = Config::getB("proc_reversed"); + const auto &filter = Config::getS("proc_filter"); + auto per_core = Config::getB("proc_per_core"); + auto tree = Config::getB("proc_tree"); + auto show_detailed = Config::getB("show_detailed"); + const size_t detailed_pid = Config::getI("detailed_pid"); + bool should_filter = current_filter != filter; + if (should_filter) current_filter = filter; + bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter); + if (sorted_change) { + current_sort = sorting; + current_rev = reverse; + } + + const int cmult = (per_core) ? Shared::coreCount : 1; + bool got_detailed = false; + + static vector found; + + //* Use pids from last update if only changing filter, sorting or tree options + if (no_update and not current_procs.empty()) { + if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, current_procs); + } else { + //* ---------------------------------------------Collection start---------------------------------------------- + + should_filter = true; + found.clear(); + struct timeval currentTime; + gettimeofday(¤tTime, nullptr); + const double timeNow = currentTime.tv_sec + (currentTime.tv_usec / 1'000'000); + + int count = 0; + char buf[_POSIX2_LINE_MAX]; + Shared::KvmPtr kd {kvm_openfiles(nullptr, nullptr, nullptr, KVM_NO_FILES, buf)}; + const struct kinfo_proc* kprocs = kvm_getprocs(kd.get() , KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count); + + for (int i = 0; i < count; i++) { + const struct kinfo_proc* kproc = &kprocs[i]; + const size_t pid = (size_t)kproc->p_pid; + if (pid < 1) continue; + found.push_back(pid); + + //? Check if pid already exists in current_procs + bool no_cache = false; + auto find_old = rng::find(current_procs, pid, &proc_info::pid); + if (find_old == current_procs.end()) { + current_procs.push_back({pid}); + find_old = current_procs.end() - 1; + no_cache = true; + } + + auto &new_proc = *find_old; + + //? Get program name, command, username, parent pid, nice and status + if (no_cache) { + if (string(kproc->p_comm) == "idle"s) { + current_procs.pop_back(); + found.pop_back(); + continue; + } + new_proc.name = kproc->p_comm; + char** argv = kvm_getargv(kd.get(), kproc, 0); + if (argv) { + for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) { + new_proc.cmd += argv[i] + " "s; + } + if (not new_proc.cmd.empty()) new_proc.cmd.pop_back(); + } + if (new_proc.cmd.empty()) new_proc.cmd = new_proc.name; + if (new_proc.cmd.size() > 1000) { + new_proc.cmd.resize(1000); + new_proc.cmd.shrink_to_fit(); + } + new_proc.ppid = kproc->p_ppid; + new_proc.cpu_s = round(kproc->p_ustart_sec); + struct passwd *pwd = getpwuid(kproc->p_uid); + if (pwd) + new_proc.user = pwd->pw_name; + } + new_proc.p_nice = kproc->p_nice; + new_proc.state = kproc->p_stat; + + int cpu_t = 0; + cpu_t = kproc->p_uctime_usec * 1'000'000 + kproc->p_uctime_sec; + + new_proc.mem = kproc->p_vm_rssize * Shared::pageSize; + new_proc.threads = 1; // can't seem to find this in kinfo_proc + + //? Process cpu usage since last update + new_proc.cpu_p = clamp((100.0 * kproc->p_pctcpu / Shared::kfscale) * cmult, 0.0, 100.0 * Shared::coreCount); + + //? Process cumulative cpu usage since process start + new_proc.cpu_c = (double)(cpu_t * Shared::clkTck / 1'000'000) / max(1.0, timeNow - new_proc.cpu_s); + + //? Update cached value with latest cpu times + new_proc.cpu_t = cpu_t; + + if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) { + got_detailed = true; + } + } + + //? Clear dead processes from current_procs + auto eraser = rng::remove_if(current_procs, [&](const auto &element) { return not v_contains(found, element.pid); }); + current_procs.erase(eraser.begin(), eraser.end()); + + //? Update the details info box for process if active + if (show_detailed and got_detailed) { + _collect_details(detailed_pid, current_procs); + } else if (show_detailed and not got_detailed and detailed.status != "Dead") { + detailed.status = "Dead"; + redraw = true; + } + + old_cputimes = cputimes; + + } + + //* ---------------------------------------------Collection done----------------------------------------------- + + //* Match filter if defined + if (should_filter) { + filter_found = 0; + for (auto& p : current_procs) { + if (not tree and not filter.empty()) { + if (not s_contains_ic(to_string(p.pid), filter) + and not s_contains_ic(p.name, filter) + and not s_contains_ic(p.cmd, filter) + and not s_contains_ic(p.user, filter)) { + p.filtered = true; + filter_found++; + } + else { + p.filtered = false; + } + } + else { + p.filtered = false; + } + } + } + + //* Sort processes + if (sorted_change or not no_update) { + proc_sorter(current_procs, sorting, reverse, tree); + } + + //* Generate tree view if enabled + if (tree and (not no_update or should_filter or sorted_change)) { + bool locate_selection = false; + if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) { + auto collapser = rng::find(current_procs, find_pid, &proc_info::pid); + if (collapser != current_procs.end()) { + if (collapse == expand) { + collapser->collapsed = not collapser->collapsed; + } + else if (collapse > -1) { + collapser->collapsed = true; + } + else if (expand > -1) { + collapser->collapsed = false; + } + if (Config::ints.at("proc_selected") > 0) locate_selection = true; + } + collapse = expand = -1; + } + if (should_filter or not filter.empty()) filter_found = 0; + + vector tree_procs; + tree_procs.reserve(current_procs.size()); + + for (auto& p : current_procs) { + if (not v_contains(found, p.ppid)) p.ppid = 0; + } + + //? Stable sort to retain selected sorting among processes with the same parent + rng::stable_sort(current_procs, rng::less{}, & proc_info::ppid); + + //? Start recursive iteration over processes with the lowest shared parent pids + for (auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { + _tree_gen(p, current_procs, tree_procs, 0, false, filter, false, no_update, should_filter); + } + + //? Recursive sort over tree structure to account for collapsed processes in the tree + int index = 0; + tree_sort(tree_procs, sorting, reverse, index, current_procs.size()); + + //? Add tree begin symbol to first item if childless + if (tree_procs.front().children.empty()) + tree_procs.front().entry.get().prefix.replace(tree_procs.front().entry.get().prefix.size() - 8, 8, " ┌─ "); + + //? Add tree terminator symbol to last item if childless + if (tree_procs.back().children.empty()) + tree_procs.back().entry.get().prefix.replace(tree_procs.back().entry.get().prefix.size() - 8, 8, " └─ "); + + //? Final sort based on tree index + rng::sort(current_procs, rng::less{}, & proc_info::tree_index); + + //? Move current selection/view to the selected process when collapsing/expanding in the tree + if (locate_selection) { + int loc = rng::find(current_procs, Proc::selected_pid, &proc_info::pid)->tree_index; + if (Config::ints.at("proc_start") >= loc or Config::ints.at("proc_start") <= loc - Proc::select_max) + Config::ints.at("proc_start") = max(0, loc - 1); + Config::ints.at("proc_selected") = loc - Config::ints.at("proc_start") + 1; + } + } + + numpids = (int)current_procs.size() - filter_found; + return current_procs; + } +} // namespace Proc + +namespace Tools { + double system_uptime() { + struct timeval ts, currTime; + std::size_t len = sizeof(ts); + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + if (sysctl(mib, 2, &ts, &len, nullptr, 0) != -1) { + gettimeofday(&currTime, nullptr); + return currTime.tv_sec - ts.tv_sec; + } + return 0.0; + } +} // namespace Tools diff --git a/src/openbsd/internal.h b/src/openbsd/internal.h new file mode 100644 index 0000000..17840b6 --- /dev/null +++ b/src/openbsd/internal.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019-2021 Brian Callahan + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct sysctls { + const char *name; + int mib0; + int mib1; + int mib2; +} sysctlnames[] = { + { "hw.machine", CTL_HW, HW_MACHINE, 0 }, + { "hw.model", CTL_HW, HW_MODEL, 0 }, + { "hw.ncpu", CTL_HW, HW_NCPU, 0 }, + { "hw.byteorder", CTL_HW, HW_BYTEORDER, 0 }, + { "hw.pagesize", CTL_HW, HW_PAGESIZE, 0 }, + { "hw.disknames", CTL_HW, HW_DISKNAMES, 0 }, + { "hw.diskcount", CTL_HW, HW_DISKCOUNT, 0 }, + { "hw.sensors", CTL_HW, HW_SENSORS, 0 }, + { "hw.model", CTL_HW, HW_MODEL, 0 }, + { "hw.ncpu", CTL_HW, HW_NCPU, 0 }, + { "hw.byteorder", CTL_HW, HW_BYTEORDER, 0 }, + { "hw.pagesize", CTL_HW, HW_PAGESIZE, 0 }, + { "hw.disknames", CTL_HW, HW_DISKNAMES, 0 }, + { "hw.diskcount", CTL_HW, HW_DISKCOUNT, 0 }, + { "hw.sensors", CTL_HW, HW_SENSORS, 0 }, + { "hw.cpuspeed", CTL_HW, HW_CPUSPEED, 0 }, + { "hw.setperf", CTL_HW, HW_SETPERF, 0 }, + { "hw.vendor", CTL_HW, HW_VENDOR, 0 }, + { "hw.product", CTL_HW, HW_PRODUCT, 0 }, + { "hw.serialno", CTL_HW, HW_SERIALNO, 0 }, + { "hw.uuid", CTL_HW, HW_UUID, 0 }, + { "hw.physmem", CTL_HW, HW_PHYSMEM64, 0 }, + { "hw.usermem", CTL_HW, HW_USERMEM64, 0 }, + { "hw.ncpufound", CTL_HW, HW_NCPUFOUND, 0 }, + { "hw.allowpowerdown", CTL_HW, HW_ALLOWPOWERDOWN, 0 }, + { "hw.perfpolicy", CTL_HW, HW_PERFPOLICY, 0 }, + { "hw.smt", CTL_HW, HW_SMT, 0 }, + { "hw.ncpuonline", CTL_HW, HW_NCPUONLINE, 0 }, + { "hw.cpuspeed", CTL_HW, HW_CPUSPEED, 0 }, + { "hw.setperf", CTL_HW, HW_SETPERF, 0 }, + { "hw.vendor", CTL_HW, HW_VENDOR, 0 }, + { "hw.product", CTL_HW, HW_PRODUCT, 0 }, + { "hw.serialno", CTL_HW, HW_SERIALNO, 0 }, + { "hw.uuid", CTL_HW, HW_UUID, 0 }, + { "hw.physmem", CTL_HW, HW_PHYSMEM64, 0 }, + { "hw.usermem", CTL_HW, HW_USERMEM64, 0 }, + { "hw.ncpufound", CTL_HW, HW_NCPUFOUND, 0 }, + { "hw.allowpowerdown", CTL_HW, HW_ALLOWPOWERDOWN, 0 }, + { "hw.perfpolicy", CTL_HW, HW_PERFPOLICY, 0 }, + { "hw.smt", CTL_HW, HW_SMT, 0 }, + { "hw.ncpuonline", CTL_HW, HW_NCPUONLINE, 0 }, + { "kern.ostype", CTL_KERN, KERN_OSTYPE, 0 }, + { "kern.osrelease", CTL_KERN, KERN_OSRELEASE, 0 }, + { "kern.osrevision", CTL_KERN, KERN_OSREV, 0 }, + { "kern.version", CTL_KERN, KERN_VERSION, 0 }, + { "kern.maxvnodes", CTL_KERN, KERN_MAXVNODES, 0 }, + { "kern.maxproc", CTL_KERN, KERN_MAXPROC, 0 }, + { "kern.maxfiles", CTL_KERN, KERN_MAXFILES, 0 }, + { "kern.argmax", CTL_KERN, KERN_ARGMAX, 0 }, + { "kern.securelevel", CTL_KERN, KERN_SECURELVL, 0 }, + { "kern.hostname", CTL_KERN, KERN_HOSTNAME, 0 }, + { "kern.hostid", CTL_KERN, KERN_HOSTID, 0 }, + { "kern.clockrate", CTL_KERN, KERN_CLOCKRATE, 0 }, + { "kern.profiling", CTL_KERN, KERN_PROF, 0 }, + { "kern.posix1version", CTL_KERN, KERN_POSIX1, 0 }, + { "kern.ngroups", CTL_KERN, KERN_NGROUPS, 0 }, + { "kern.job_control", CTL_KERN, KERN_JOB_CONTROL, 0 }, + { "kern.saved_ids", CTL_KERN, KERN_SAVED_IDS, 0 }, + { "kern.boottime", CTL_KERN, KERN_BOOTTIME, 0 }, + { "kern.domainname", CTL_KERN, KERN_DOMAINNAME, 0 }, + { "kern.maxpartitions", CTL_KERN, KERN_MAXPARTITIONS, 0 }, + { "kern.rawpartition", CTL_KERN, KERN_RAWPARTITION, 0 }, + { "kern.maxthread", CTL_KERN, KERN_MAXTHREAD, 0 }, + { "kern.nthreads", CTL_KERN, KERN_NTHREADS, 0 }, + { "kern.osversion", CTL_KERN, KERN_OSVERSION, 0 }, + { "kern.somaxconn", CTL_KERN, KERN_SOMAXCONN, 0 }, + { "kern.sominconn", CTL_KERN, KERN_SOMINCONN, 0 }, + { "kern.nosuidcoredump", CTL_KERN, KERN_NOSUIDCOREDUMP, 0 }, + { "kern.fsync", CTL_KERN, KERN_FSYNC, 0 }, + { "kern.sysvmsg", CTL_KERN, KERN_SYSVMSG, 0 }, + { "kern.sysvsem", CTL_KERN, KERN_SYSVSEM, 0 }, + { "kern.sysvshm", CTL_KERN, KERN_SYSVSHM, 0 }, + { "kern.msgbufsize", CTL_KERN, KERN_MSGBUFSIZE, 0 }, + { "kern.malloc", CTL_KERN, KERN_MALLOCSTATS, 0 }, + { "kern.cp_time", CTL_KERN, KERN_CPTIME, 0 }, + { "kern.nchstats", CTL_KERN, KERN_NCHSTATS, 0 }, + { "kern.forkstat", CTL_KERN, KERN_FORKSTAT, 0 }, + { "kern.tty", CTL_KERN, KERN_TTY, 0 }, + { "kern.ccpu", CTL_KERN, KERN_CCPU, 0 }, + { "kern.fscale", CTL_KERN, KERN_FSCALE, 0 }, + { "kern.nprocs", CTL_KERN, KERN_NPROCS, 0 }, + { "kern.msgbuf", CTL_KERN, KERN_MSGBUF, 0 }, + { "kern.pool", CTL_KERN, KERN_POOL, 0 }, + { "kern.stackgap_random", CTL_KERN, KERN_STACKGAPRANDOM, 0 }, + { "kern.sysvipc_info", CTL_KERN, KERN_SYSVIPC_INFO, 0 }, + { "kern.allowkmem", CTL_KERN, KERN_ALLOWKMEM, 0 }, + { "kern.witnesswatch", CTL_KERN, KERN_WITNESSWATCH, 0 }, + { "kern.splassert", CTL_KERN, KERN_SPLASSERT, 0 }, + { "kern.procargs", CTL_KERN, KERN_PROC_ARGS, 0 }, + { "kern.nfiles", CTL_KERN, KERN_NFILES, 0 }, + { "kern.ttycount", CTL_KERN, KERN_TTYCOUNT, 0 }, + { "kern.numvnodes", CTL_KERN, KERN_NUMVNODES, 0 }, + { "kern.mbstat", CTL_KERN, KERN_MBSTAT, 0 }, + { "kern.witness", CTL_KERN, KERN_WITNESS, 0 }, + { "kern.seminfo", CTL_KERN, KERN_SEMINFO, 0 }, + { "kern.shminfo", CTL_KERN, KERN_SHMINFO, 0 }, + { "kern.intrcnt", CTL_KERN, KERN_INTRCNT, 0 }, + { "kern.watchdog", CTL_KERN, KERN_WATCHDOG, 0 }, + { "kern.proc", CTL_KERN, KERN_PROC, 0 }, + { "kern.maxclusters", CTL_KERN, KERN_MAXCLUSTERS, 0 }, + { "kern.evcount", CTL_KERN, KERN_EVCOUNT, 0 }, + { "kern.timecounter", CTL_KERN, KERN_TIMECOUNTER, 0 }, + { "kern.maxlocksperuid", CTL_KERN, KERN_MAXLOCKSPERUID, 0 }, + { "kern.cp_time2", CTL_KERN, KERN_CPTIME2, 0 }, + { "kern.bufcachepercent", CTL_KERN, KERN_CACHEPCT, 0 }, + { "kern.file", CTL_KERN, KERN_FILE, 0 }, + { "kern.wxabort", CTL_KERN, KERN_WXABORT, 0 }, + { "kern.consdev", CTL_KERN, KERN_CONSDEV, 0 }, + { "kern.netlivelocks", CTL_KERN, KERN_NETLIVELOCKS, 0 }, + { "kern.pool_debug", CTL_KERN, KERN_POOL_DEBUG, 0 }, + { "kern.proc_cwd", CTL_KERN, KERN_PROC_CWD, 0 }, + { "kern.proc_nobroadcastkill", CTL_KERN, KERN_PROC_NOBROADCASTKILL, 0 }, + { "kern.proc_vmap", CTL_KERN, KERN_PROC_VMMAP, 0 }, + { "kern.global_ptrace", CTL_KERN, KERN_GLOBAL_PTRACE, 0 }, + { "kern.consbufsize", CTL_KERN, KERN_CONSBUFSIZE, 0 }, + { "kern.consbuf", CTL_KERN, KERN_CONSBUF, 0 }, + { "kern.audio", CTL_KERN, KERN_AUDIO, 0 }, + { "kern.cpustats", CTL_KERN, KERN_CPUSTATS, 0 }, + { "kern.pfstatus", CTL_KERN, KERN_PFSTATUS, 0 }, + { "kern.timeout_stats", CTL_KERN, KERN_TIMEOUT_STATS, 0 }, + { "kern.utc_offset", CTL_KERN, KERN_UTC_OFFSET, 0 }, + { "vm.vmmeter", CTL_VM, VM_METER, 0 }, + { "vm.loadavg", CTL_VM, VM_LOADAVG, 0 }, + { "vm.psstrings", CTL_VM, VM_PSSTRINGS, 0 }, + { "vm.uvmexp", CTL_VM, VM_UVMEXP, 0 }, + { "vm.swapencrypt", CTL_VM, VM_SWAPENCRYPT, 0 }, + { "vm.nkmempages", CTL_VM, VM_NKMEMPAGES, 0 }, + { "vm.anonmin", CTL_VM, VM_ANONMIN, 0 }, + { "vm.vtextmin", CTL_VM, VM_VTEXTMIN, 0 }, + { "vm.vnodemin", CTL_VM, VM_VNODEMIN, 0 }, + { "vm.maxslp", CTL_VM, VM_MAXSLP, 0 }, + { "vm.uspace", CTL_VM, VM_USPACE, 0 }, + { "vm.malloc_conf", CTL_VM, VM_MALLOC_CONF, 0 }, + { NULL, 0, 0, 0 }, +}; diff --git a/src/openbsd/sysctlbyname.cpp b/src/openbsd/sysctlbyname.cpp new file mode 100644 index 0000000..b92484a --- /dev/null +++ b/src/openbsd/sysctlbyname.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2021 Brian Callahan + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include + +#include "internal.h" +#include "../btop_tools.hpp" + +int +sysctlbyname(const char *name, void *oldp, size_t *oldlenp, + void *newp, size_t newlen) +{ + int i, mib[2]; + + for (i = 0; i < 132; i++) { + // for (i = 0; i < sizeof(sysctlnames) / sizeof(sysctlnames[0]); i++) { + if (!strcmp(name, sysctlnames[i].name)) { + mib[0] = sysctlnames[i].mib0; + mib[1] = sysctlnames[i].mib1; + + return sysctl(mib, 2, oldp, oldlenp, newp, newlen); + } + } + + errno = ENOENT; + + return (-1); +} diff --git a/src/openbsd/sysctlbyname.h b/src/openbsd/sysctlbyname.h new file mode 100644 index 0000000..3b02382 --- /dev/null +++ b/src/openbsd/sysctlbyname.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2019 Brian Callahan + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +extern int sysctlbyname(const char *, void *, size_t *, void *, size_t); diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index e410c74..a2c4f62 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -16,6 +16,7 @@ indent = tab tab-size = 4 */ +#include #include #include #include @@ -44,6 +45,7 @@ tab-size = 4 #include // for inet_ntop #include #include +#include #include #include @@ -56,7 +58,9 @@ tab-size = 4 #include "../btop_shared.hpp" #include "../btop_tools.hpp" +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 #include "sensors.hpp" +#endif #include "smc.hpp" 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; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -187,11 +191,11 @@ namespace Cpu { string cpuHz; bool has_battery = true; bool macM1 = false; - tuple current_bat; + tuple current_bat; const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 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"))); got_sensors = false; if (Config::getB("show_coretemp") and Config::getB("check_temp")) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 ThermalSensors sensors; if (sensors.getSensors() > 0) { Logger::debug("M1 sensors found"); @@ -257,6 +262,7 @@ namespace Cpu { cpu_temp_only = true; macM1 = true; } else { +#endif // try SMC (intel) Logger::debug("checking intel"); SMCConnection smcCon; @@ -281,7 +287,9 @@ namespace Cpu { // ignore, we don't have temp got_sensors = false; } +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 } +#endif } return got_sensors; } @@ -290,11 +298,12 @@ namespace Cpu { current_cpu.temp_max = 95; // we have no idea how to get the critical temp try { if (macM1) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 ThermalSensors sensors; current_cpu.temp.at(0).push_back(sensors.getSensors()); if (current_cpu.temp.at(0).size() > 20) current_cpu.temp.at(0).pop_front(); - +#endif } else { SMCConnection smcCon; 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); } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; natural_t cpu_count; @@ -398,8 +407,8 @@ namespace Cpu { ~IOPSList_Wrap() { CFRelease(data); } }; - auto get_battery() -> tuple { - if (not has_battery) return {0, 0, ""}; + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; uint32_t percent = -1; long seconds = -1; @@ -438,7 +447,7 @@ namespace Cpu { has_battery = false; } } - return {percent, seconds, status}; + return {percent, -1, seconds, status}; } auto collect(bool no_update) -> cpu_info & { @@ -591,7 +600,7 @@ namespace Mem { io_object_t &object; }; - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { io_registry_entry_t drive; 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) { mem.stats.at("free") = p.free_count * Shared::pageSize; mem.stats.at("cached") = p.external_page_count * Shared::pageSize; - mem.stats.at("used") = (p.active_count + p.inactive_count + p.wire_count) * Shared::pageSize; + mem.stats.at("used") = (p.active_count + p.wire_count) * Shared::pageSize; mem.stats.at("available") = Shared::totalMem - mem.stats.at("used"); } @@ -708,7 +717,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -821,13 +830,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -904,7 +913,7 @@ namespace Net { } // else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; size_t len; 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 string dir : {"download", "upload"}) { auto &saved_stat = net.at(iface).stat.at(dir); @@ -978,7 +987,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1049,7 +1057,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; @@ -1204,10 +1212,14 @@ namespace Proc { //? Get program name, command, username, parent pid, nice and status if (no_cache) { char fullname[PROC_PIDPATHINFO_MAXSIZE]; - proc_pidpath(pid, fullname, sizeof(fullname)); - const string f_name = std::string(fullname); - size_t lastSlash = f_name.find_last_of('/'); - new_proc.name = f_name.substr(lastSlash + 1); + int rc = proc_pidpath(pid, fullname, sizeof(fullname)); + string f_name = ""; + if (rc != 0) { + 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 if (Shared::arg_max > 0) { std::unique_ptr proc_chars(new char[Shared::arg_max]); diff --git a/src/osx/sensors.cpp b/src/osx/sensors.cpp index 965d9a9..bee8978 100644 --- a/src/osx/sensors.cpp +++ b/src/osx/sensors.cpp @@ -16,6 +16,8 @@ indent = tab tab-size = 4 */ +#include +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 #include "sensors.hpp" #include @@ -109,3 +111,4 @@ long long Cpu::ThermalSensors::getSensors() { if (temps.empty()) return 0ll; return round(std::accumulate(temps.begin(), temps.end(), 0ll) / temps.size()); } +#endif diff --git a/src/osx/sensors.hpp b/src/osx/sensors.hpp index 08581e1..fcb28b0 100644 --- a/src/osx/sensors.hpp +++ b/src/osx/sensors.hpp @@ -16,9 +16,12 @@ indent = tab tab-size = 4 */ +#include +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 namespace Cpu { class ThermalSensors { public: long long getSensors(); }; } // namespace Cpu +#endif diff --git a/themes/adapta.theme b/themes/adapta.theme index e3e2128..4448228 100644 --- a/themes/adapta.theme +++ b/themes/adapta.theme @@ -18,7 +18,7 @@ theme[main_fg]="#cfd8dc" # Title color for boxes theme[title]="#ff" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/adwaita.theme b/themes/adwaita.theme index 25e6bbf..97a52bc 100644 --- a/themes/adwaita.theme +++ b/themes/adwaita.theme @@ -18,7 +18,7 @@ theme[main_fg]="#2e3436" # Title color for boxes theme[title]="#2e3436" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#1a5fb4" # Background color of selected item in processes box diff --git a/themes/dusklight.theme b/themes/dusklight.theme index be60f1a..01e5ae6 100644 --- a/themes/dusklight.theme +++ b/themes/dusklight.theme @@ -18,7 +18,7 @@ theme[main_fg]="#99DFFF" # Title color for boxes theme[title]="#99FFFF" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#FF7F00" # Background color of selected item in processes box diff --git a/themes/elementarish.theme b/themes/elementarish.theme index 0fe5398..436b6f8 100644 --- a/themes/elementarish.theme +++ b/themes/elementarish.theme @@ -11,7 +11,7 @@ theme[main_fg]="#eee8d5" # Title color for boxes theme[title]="#eee8d5" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#d1302c" # Background color of selected item in processes box diff --git a/themes/everforest-dark-medium.theme b/themes/everforest-dark-medium.theme new file mode 100644 index 0000000..f6aeadf --- /dev/null +++ b/themes/everforest-dark-medium.theme @@ -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" + diff --git a/themes/flat-remix-light.theme b/themes/flat-remix-light.theme index db1afe4..8344a96 100644 --- a/themes/flat-remix-light.theme +++ b/themes/flat-remix-light.theme @@ -18,7 +18,7 @@ theme[main_fg]="#737680" # Title color for boxes theme[title]="#272a34" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/flat-remix.theme b/themes/flat-remix.theme index 54585e7..0a53a95 100644 --- a/themes/flat-remix.theme +++ b/themes/flat-remix.theme @@ -18,7 +18,7 @@ theme[main_fg]="#E6E6E6" # Title color for boxes theme[title]="#ff" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/greyscale.theme b/themes/greyscale.theme index 442a82f..aee12af 100644 --- a/themes/greyscale.theme +++ b/themes/greyscale.theme @@ -18,7 +18,7 @@ theme[main_fg]="#bb" # Title color for boxes theme[title]="#cc" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/gruvbox_dark.theme b/themes/gruvbox_dark.theme index feed893..8cd9d37 100644 --- a/themes/gruvbox_dark.theme +++ b/themes/gruvbox_dark.theme @@ -18,7 +18,7 @@ theme[main_fg]="#a89984" # Title color for boxes theme[title]="#ebdbb2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#d79921" # Background color of selected items diff --git a/themes/gruvbox_material_dark.theme b/themes/gruvbox_material_dark.theme index 749c7a7..5dc5822 100644 --- a/themes/gruvbox_material_dark.theme +++ b/themes/gruvbox_material_dark.theme @@ -18,7 +18,7 @@ theme[main_fg]="#d4be98" # Title color for boxes theme[title]="#d4be98" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#ea6962" # Background color of selected items diff --git a/themes/matcha-dark-sea.theme b/themes/matcha-dark-sea.theme index 34889ff..07a0b38 100644 --- a/themes/matcha-dark-sea.theme +++ b/themes/matcha-dark-sea.theme @@ -18,7 +18,7 @@ theme[main_fg]="#F8F8F2" # Title color for boxes theme[title]="#F8F8F2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#2eb398" # Background color of selected item in processes box diff --git a/themes/monokai.theme b/themes/monokai.theme index f379dc9..29299f5 100644 --- a/themes/monokai.theme +++ b/themes/monokai.theme @@ -18,7 +18,7 @@ theme[main_fg]="#F8F8F2" # Title color for boxes theme[title]="#F8F8F2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#F92672" # Background color of selected item in processes box diff --git a/themes/night-owl.theme b/themes/night-owl.theme index 7537fea..0de70ae 100644 --- a/themes/night-owl.theme +++ b/themes/night-owl.theme @@ -18,7 +18,7 @@ theme[main_fg]="#d6deeb" # Title color for boxes theme[title]="#ffffff" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#addb67" # Background color of selected items diff --git a/themes/nord.theme b/themes/nord.theme index 07c43c2..fbd0af1 100644 --- a/themes/nord.theme +++ b/themes/nord.theme @@ -18,7 +18,7 @@ theme[main_fg]="#D8DEE9" # Title color for boxes theme[title]="#8FBCBB" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#5E81AC" # Background color of selected item in processes box diff --git a/themes/onedark.theme b/themes/onedark.theme index f4441de..5a5ad01 100644 --- a/themes/onedark.theme +++ b/themes/onedark.theme @@ -10,7 +10,7 @@ theme[main_fg]="#abb2bf" # Title color for boxes theme[title]="#abb2bf" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#61afef" # Background color of selected item in processes box diff --git a/themes/paper.theme b/themes/paper.theme index 111289d..00f96a2 100644 --- a/themes/paper.theme +++ b/themes/paper.theme @@ -12,7 +12,7 @@ theme[main_fg]="#00" # Title color for boxes theme[title]="#00" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#CC3E28" # Background color of selected item in processes box diff --git a/themes/solarized_dark.theme b/themes/solarized_dark.theme index 6069823..2b3c62b 100644 --- a/themes/solarized_dark.theme +++ b/themes/solarized_dark.theme @@ -18,7 +18,7 @@ theme[main_fg]="#eee8d5" # Title color for boxes theme[title]="#fdf6e3" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#b58900" # Background color of selected items diff --git a/themes/solarized_light.theme b/themes/solarized_light.theme index 9478410..c786ce2 100644 --- a/themes/solarized_light.theme +++ b/themes/solarized_light.theme @@ -18,7 +18,7 @@ theme[main_fg]="#586e75" # Title color for boxes theme[title]="#002b36" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#b58900" # Background color of selected items diff --git a/themes/tokyo-night.theme b/themes/tokyo-night.theme index aae6a3b..b8200ec 100644 --- a/themes/tokyo-night.theme +++ b/themes/tokyo-night.theme @@ -10,7 +10,7 @@ theme[main_fg]="#cfc9c2" # Title color for boxes theme[title]="#cfc9c2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#7dcfff" # Background color of selected item in processes box diff --git a/themes/tokyo-storm.theme b/themes/tokyo-storm.theme index a4edfcf..e987f1c 100644 --- a/themes/tokyo-storm.theme +++ b/themes/tokyo-storm.theme @@ -10,7 +10,7 @@ theme[main_fg]="#cfc9c2" # Title color for boxes theme[title]="#cfc9c2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#7dcfff" # Background color of selected item in processes box diff --git a/themes/tomorrow-night.theme b/themes/tomorrow-night.theme index a745005..5900044 100644 --- a/themes/tomorrow-night.theme +++ b/themes/tomorrow-night.theme @@ -18,7 +18,7 @@ theme[main_fg]="#c5c8c6" # Title color for boxes theme[title]="#c5c8c6" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#81beb7" # Background color of selected item in processes box diff --git a/themes/whiteout.theme b/themes/whiteout.theme index 23c8ebe..891bb7d 100644 --- a/themes/whiteout.theme +++ b/themes/whiteout.theme @@ -18,7 +18,7 @@ theme[main_fg]="#30" # Title color for boxes theme[title]="#10" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#284d75" # Background color of selected item in processes box