From 934a9e3cf28114afe5e5578ed6d241481310f5f1 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 2 Jan 2024 15:29:53 +0100 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 285fb215d12a5e0c686b29e1039027cbb2b246da Author: aristocratos Date: Thu Dec 28 13:10:18 2023 +0100 Proc::draw() -> Use std::erase_if() instead of for loops commit 2fba934cde14d72e3ed58c389dc2e24d7297475b Author: aristocratos Date: Wed Dec 27 00:54:28 2023 +0100 Fixed leftover code in GPU init logging false errors commit ad14554f32cc9c19f3cdc8e28ea6012a974a533a Author: aristocratos Date: Tue Dec 26 19:32:43 2023 +0100 Try alternative names for GPU libraries during GPU init commit a8fda16bf6ead94bc5ffafa3e622ee60b1d92d7b Merge: e15e0b7 2796af3 Author: Jakob P. Liljenberg Date: Tue Dec 26 19:19:14 2023 +0100 Merge pull request #696 from aristocratos/map_safety commit 2796af3f37f07ba838d2eeb06e3d798f5ab00e55 Author: aristocratos Date: Tue Dec 26 19:18:37 2023 +0100 Document DEBUG flag for Makefile commit f484326bf20a8deb4ac942aec2b5d9173d58ad4b Merge: b4eb397 e15e0b7 Author: aristocratos Date: Tue Dec 26 19:11:26 2023 +0100 Merge branch 'main' into map_safety commit b4eb397fc69696db18c6f2b90b79172882c39128 Author: aristocratos Date: Mon Dec 25 10:52:52 2023 +0100 Fix errors commit 3c04a7a380a5a27630193e8805ef5f9d249cecb3 Author: aristocratos Date: Mon Dec 25 10:41:15 2023 +0100 Added more checks and debug logging commit 8b81c4a4ecfd7a3e56960befc2b3726f9dddd92d Author: aristocratos Date: Mon Dec 25 03:28:35 2023 +0100 Return const refs commit f836233b64aeee1513be9dc74c4dc60b6248e578 Author: aristocratos Date: Mon Dec 25 02:49:24 2023 +0100 Remove robin_hood.h commit 3a8ceacaf89e7443014613dbc888b8b1657832fe Author: aristocratos Date: Mon Dec 25 02:37:32 2023 +0100 Fix call to compact and missing utility include commit e15e0b71881641ea6d6751e3acd46631d91b494b Author: aristocratos Date: Mon Dec 25 02:27:38 2023 +0100 Revert "Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback" This reverts commit 6c87ab61969f028e49ad596139cff53eb6c3becc. commit ced47a960f649d3c4e81c91128fe97a81e4c4ad5 Author: aristocratos Date: Mon Dec 25 02:26:13 2023 +0100 Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback commit 6c87ab61969f028e49ad596139cff53eb6c3becc Author: aristocratos Date: Mon Dec 25 02:16:15 2023 +0100 Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback commit a2325371d432f62b362fe1a9531a79c79fc56baa Merge: aab2e8c b598f02 Author: Jakob P. Liljenberg Date: Sun Dec 17 19:56:31 2023 +0100 Merge pull request #690 from aristocratos/osx-fix commit b598f02468480a54fe1e283d02db5228969f7522 Merge: b1fe377 aab2e8c Author: Jakob P. Liljenberg Date: Sun Dec 17 12:06:39 2023 +0100 Merge branch 'main' into osx-fix commit aab2e8cc552c752cbb15675c8ab96e1bd785ad3b Author: Jakob P. Liljenberg Date: Sun Dec 17 12:03:47 2023 +0100 Fixed test-snap-can-build.yml commit b1fe3779e1b18c9087cbf399afbcfb3622cebd12 Merge: 7805242 2d15c41 Author: Jakob P. Liljenberg Date: Sun Dec 17 11:56:14 2023 +0100 Merge branch 'main' into osx-fix commit 2d15c41555024dba0c1e0af91220fa2f8406b9f4 Merge: fe699b4 2d3e453 Author: Jakob P. Liljenberg Date: Sun Dec 17 11:54:49 2023 +0100 Merge pull request #684 from kz6fittycent/main commit 2d3e453ed527f2d045b9e615422f708820438298 Merge: 0a38864 fe699b4 Author: kz6fittycent Date: Fri Dec 15 12:02:11 2023 -0600 Merge branch 'main' into main commit 0a388647ccbdaa68a8939a8df806afee29b63489 Author: kz6fittycent Date: Fri Dec 15 12:01:45 2023 -0600 Update test-snap-can-build.yml whoops commit 49f425f35657d9f448e112968a2405421bd1b378 Author: kz6fittycent Date: Fri Dec 15 12:00:48 2023 -0600 Update test-snap-can-build.yml https://github.com/aristocratos/btop/pull/684#issuecomment-1852801811 commit 780524267fe16b11638ca002d636c5feb8cbd5b2 Author: Jos Dehaes Date: Fri Dec 15 09:02:57 2023 +0100 conditional compile on Big Sur and up commit fe699b433398818cf2145d2ca4d2e364a089a4d2 Author: aristocratos Date: Tue Dec 12 23:20:09 2023 +0100 Version bump to 1.3.0 in preparation for upcoming release commit 2d2df23198b6b08bdce21ee0d4f7e3a2b6c37d0b Merge: d7b581e b71538e Author: aristocratos Date: Tue Dec 12 23:19:31 2023 +0100 Merge branch 'main' of github.com:aristocratos/btop commit d7b581eda4560e969459d5f4a1e0ef811e5a08be Author: aristocratos Date: Tue Dec 12 23:17:36 2023 +0100 Updated changes commit b71538eabe06607042201e21af9a078277f3b3e8 Merge: a017056 730af5d Author: Jakob P. Liljenberg Date: Tue Dec 12 23:07:39 2023 +0100 Merge pull request #666 from muneebmahmed/macos-clang commit 730af5d4e1a09996f9e83c6aa26382a11964b03f Merge: 0246b1b a017056 Author: Jakob P. Liljenberg Date: Tue Dec 12 23:05:52 2023 +0100 Merge branch 'main' into macos-clang commit a017056ea07f6532b333f3e6f10a023505fb0470 Author: aristocratos Date: Tue Dec 12 23:05:07 2023 +0100 Added swap to ignore for statvfs() since it will always fail commit e770cccaf82cb75fe1e61c227284929e5a4acde1 Author: aristocratos Date: Tue Dec 12 22:55:48 2023 +0100 Added try->catch for get_zfs_stat_file() to avoid fs error commit 0246b1b971a3f991540acd72ef66b5481d9bc76f Author: Muneeb Ahmed <32603485+muneebmahmed@users.noreply.github.com> Date: Mon Nov 20 12:18:40 2023 -0800 Enable macos clang Apple clang uses different versioning from LLVM, so 15.0.0 is compatible commit 6282f36f8fe2fc1fa95a5858c044fbc6c7d965cf Merge: cfd20a3 be73160 Author: Jakob P. Liljenberg Date: Tue Dec 12 22:06:02 2023 +0100 Merge pull request #675 from imwints/cmake commit be731600f1ec6cb58edc6947d41d2709c6d33635 Merge: f4b14ce cfd20a3 Author: Jakob P. Liljenberg Date: Tue Dec 12 22:01:21 2023 +0100 Merge branch 'main' into cmake commit 450b59b657618beb7a88f34072210e27cab43ca5 Merge: 875f08b cfd20a3 Author: Jakob P. Liljenberg Date: Tue Dec 12 21:55:27 2023 +0100 Merge branch 'main' into main commit cfd20a374b11b75d73e043979d0b6dd4314d2919 Merge: 14e664e b6a8696 Author: Jakob P. Liljenberg Date: Tue Dec 12 21:48:55 2023 +0100 Merge pull request #677 from imwints/cpu-model commit b6a86962e23422b6f09ea40347cd4a7a49ae6647 Merge: 8096433 14e664e Author: Jakob P. Liljenberg Date: Tue Dec 12 21:47:01 2023 +0100 Merge branch 'main' into cpu-model commit 14e664e7564f595d911e462414357caade91eede Merge: 0d35746 5902484 Author: Jakob P. Liljenberg Date: Tue Dec 12 21:41:55 2023 +0100 Merge pull request #679 from masiboss/main commit 875f08ba5ea72327573235a265f13c0441ee67e5 Author: kz6fittycent Date: Tue Dec 12 14:27:16 2023 -0600 Update snapcraft.yaml - opengl - Testing opengl plug commit 3ee4b18e57bc9dfe3124ad615bc68aab1772c283 Author: kz6fittycent Date: Tue Dec 12 14:14:59 2023 -0600 Update bug_report.md - added snap info for bug reports to delineate commit 2973a76f2b3183b571e6706e523aa42f6b1b7f69 Merge: fb782a2 0d35746 Author: kz6fittycent Date: Tue Dec 12 14:06:44 2023 -0600 Merge branch 'aristocratos:main' into main commit fb782a2ab32cc60ff6bcaec5ae13514ce5ca5390 Author: kz6fittycent Date: Tue Dec 12 14:02:27 2023 -0600 Create test-snap-can-build.yml commit 5902484f39fea0751093368e3181ac8cbe37e9a6 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 21:42:11 2023 +0100 simplify removal of "Apple" commit 5beb9e12e5645e64644eb8465b097468172e244f Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 20:56:40 2023 +0100 in case apple decides to add another suffix to the cpu name commit 1b2f11b41234f7cdf83212c81cdbf086f491049d Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 20:49:34 2023 +0100 cut less of cpu name if frequency is not shown commit bcf4ad8ab6a8b69f17ad7936a36595f15dbc0953 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 19:50:12 2023 +0100 fix array out of bounds on regular m chip commit aeefcacbc9c48aa5fad658b07f046d20a0b863c7 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 19:34:11 2023 +0100 fix cpu version not included commit 23698940df1b387d131a250f754f3f47b8480833 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 19:23:58 2023 +0100 strip "Apple" from name of Apple silicon chips commit 809643373648c510788706404d5ec00ba714baba Author: Steffen Winter Date: Tue Dec 5 02:34:24 2023 +0100 Fix printed model name for older Intel CPU commit f4b14ce97e35d439bcc3207c3eb78da442c92fa9 Author: Steffen Winter Date: Tue Dec 5 01:00:14 2023 +0100 Add CMake compile instructions for macOS commit 97b35d97206417a5170324f7d98b815bec002b36 Author: Steffen Winter Date: Sat Dec 2 00:35:13 2023 +0100 Add cmake workflow for all platforms commit e35538fa29e7975a6ca92bb2c0fd0e36d8108ba5 Author: Steffen Winter Date: Sat Dec 2 00:34:32 2023 +0100 Patch RPATH on FreeBSD, support OSX and format commit 0d357468b50a13b48868b4969b4f19000c90d5d6 Merge: ebc46ca 00f58b6 Author: Jakob P. Liljenberg Date: Fri Dec 1 22:52:51 2023 +0100 Merge pull request #674 from imwints/bsd-workflow Provide FreeBSD static release binaries commit 00f58b6228c1ea99ab32d0ec48c3d4aa93611614 Author: Steffen Winter Date: Thu Nov 30 23:07:52 2023 +0100 Provide FreeBSD static release binaries Bumps vmaction@freesdb-vm to version 1 which runs on Linux and doesn't hang all the time. Also uses clang for full static compilation commit ebc46ca12cc0ee0172ac44ad6f9bdd30c72e3685 Author: Jakob P. Liljenberg Date: Mon Nov 27 18:21:42 2023 +0100 Clean up compile instructions commit d1384c9341e73a5289a4d754fdcce847c4dca736 Merge: 2b0cc37 6f12e35 Author: Jakob P. Liljenberg Date: Mon Nov 27 17:53:39 2023 +0100 Merge pull request #671 from imwints/cmake-gpu Bring GPU support to CMake and improve how Make handles the ROCm library build commit 6f12e3555d9558266e5ffc7f6847f0a7273db5b8 Author: Steffen Winter Date: Mon Nov 27 00:33:11 2023 +0100 Properly invoke CMake to build ROCm * Build an optimized library by default * Only build the library target * ROCm is build with debug symbols when `make DEBUG=true` * Enable LTO * Use the more generic CMake build command instead of calling make directly, this always uses all cores by default and makes it easier to switch to another generator e.g. Ninja * Use a variable to store the ROCm source directory. The directory can be changed with `make ROCM_DIR=` * The static library is now directly linked by CMake and not created off of the object files from a shared library build * The C++ compiler used to compile btop is now used to compile ROCm to avoid name mangling when `CXX` from the environment and `make CXX=` differ * CMake is invoked from btop's root directory commit 0585bc9cfbcb023ad10b57cbf52cc90623e6d307 Author: Steffen Winter Date: Mon Nov 27 00:31:10 2023 +0100 Suppress all output from ROCm build Similar to including external include files with `-isystem`, ignore output from ROCm build since these warnings aren't a concern here commit 831be262b0a88090bde36eee0dcac8e8abb7569b Author: Steffen Winter Date: Mon Nov 27 00:29:14 2023 +0100 Remove ROCm object files with `make clean/distclean` commit 2f59e61d875b86cb99d9d24819dea607239ad517 Author: Steffen Winter Date: Sun Nov 26 22:56:58 2023 +0100 Add GPU options for cmake based builds commit 7588d96dd4e0fdbba4943bddd7709004876aa7ab Author: Steffen Winter Date: Sun Nov 26 21:40:29 2023 +0100 Add check for header commit ebbb769a6aaf0a6245111eb55f239d5c45be0cb9 Author: Steffen Winter Date: Sun Nov 26 21:39:13 2023 +0100 Move calls to find_package to where they're required commit ed0fa34a9d628b21a1abed6034ab866c4db72445 Author: Steffen Winter Date: Sun Nov 26 19:39:29 2023 +0100 Bump required CMake version commit 2b0cc3763271a9aae5242dbb7174bfec5c5fdae5 Author: Jakob P. Liljenberg Date: Sat Nov 25 23:11:54 2023 +0100 Update compile instructions for Gpu support commit 359c67136b75efe80bb1d2471d0e87ab5ed5b20c Author: aristocratos Date: Sat Nov 25 22:49:26 2023 +0100 Update changelog commit 5b01235315034f9c75880d0d08ab085b4a804751 Merge: 0267eba 0bb8599 Author: Jakob P. Liljenberg Date: Sat Nov 25 21:57:32 2023 +0100 Merge pull request #529 from romner-set/main Add GPU monitoring support commit 0bb8599a967c92a2767bcf7f488d5e1acfabeef8 Merge: 94d4502 0267eba Author: Jakob P. Liljenberg Date: Sat Nov 25 21:51:09 2023 +0100 Merge branch 'main' into main commit 94d4502901124ff20146a0b6f5c30efe5b4d2a32 Author: aristocratos Date: Sat Nov 25 21:48:50 2023 +0100 Readme update and Makfile fixes. commit 19bcff894bd8f018bc3c76607b1dd91200537923 Author: aristocratos Date: Sat Nov 25 21:01:11 2023 +0100 Squashed commit of the following: commit 0267eba2bbebd6f166b24358159d7fb094fae052 Merge: 50bbab0 e81cf2b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:43:18 2023 +0100 Merge pull request #659 from ivanp7/patch-1 Add alternative key codes for Delete, Insert, Home, End commit 50bbab05122b2f8c35c90e4a6e01d5c52e5eca60 Merge: 9edbf27 5a14c7b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:35:50 2023 +0100 Merge pull request #660 from stradicat/feature/elementarish Elementarish theme: color update according to Elementary palette commit 5a14c7b6fa41a7fe7061c0aa2f2d0c0fe530e495 Merge: 979506f 71eb414 Author: Dennis Mayr Date: Wed Nov 15 17:27:34 2023 -0300 Merge branch 'main' of https://github.com/stradicat/btop commit 979506f18ecdc1475b882d7dadc220386169b7db Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit 71eb4142e8204303af091555067b93d82e5dcec1 Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit e81cf2b7ff4111e279e1111127e49c6858d83d5e Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Tue Nov 7 15:12:27 2023 +0000 Add alternative key codes for Insert, Home, End commit f9452ff6d56af721fb5526ede11206c7d3b885b8 Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Mon Nov 6 13:31:53 2023 +0000 Add alternative Delete key code Delete key not always produces ^[[3~, on some terminals (like st) it produces ^[[P. commit 9edbf27f1b6d5844a97325fcda732762ba086a99 Merge: 2a864f6 ff1f51c Author: Jakob P. Liljenberg Date: Sat Oct 21 02:09:55 2023 +0200 Merge pull request #649 from nobounce/workflow-timeout Set FreeBSD workflow timeout commit ff1f51ccbb6d6133292e57ba1b8edb8b6c941fc9 Author: Steffen Winter Date: Wed Oct 18 22:26:36 2023 +0200 Set FreeBSD workflow timeout Recently the FreeBSD workflow has started to hang in a boot loop when the VM starts up. The issue is being tracked upstream but there is not response at the moment. To work around this set a timeout to not waste CI minutes. Other workflows might also want this change since they don't take 20 minutes anyway. commit 2a864f6f2ea60df16b3f015885eb3c18a48b9b78 Merge: 636eb25 b2bf8ef Author: Jakob P. Liljenberg Date: Sat Oct 7 10:40:54 2023 +0200 Merge pull request #643 from DecklynKern/main Fix scrollbar not clearing sometimes. commit b2bf8ef504f29650f8fe0adab41c3cac35b67567 Author: DecklynKern Date: Fri Oct 6 17:33:38 2023 -0600 Fix scrollbar not clearing sometimes. commit 636eb25f5e31a7af337b024873b2ceb42650ebdb Merge: 260c0f6 b5ba2fc Author: Jakob P. Liljenberg Date: Sat Sep 30 19:51:03 2023 +0200 Merge pull request #623 from rahulaggarwal965/main Add keybind for toggling memory display mode in PROC box commit b5ba2fc9635e540142d3dd3eccd866865c9393fd Author: Rahul Aggarwal Date: Wed Sep 20 22:55:56 2023 -0400 Add keybind for toggling memory display mode in PROC box commit 260c0f662313fe0d0df859645f5731af04fc9fa7 Merge: 52bfff7 e6a06eb Author: Jakob P. Liljenberg Date: Sat Sep 30 18:56:25 2023 +0200 Merge pull request #635 from lvxnull/editorconfig Add hpp files to .editorconfig commit e6a06eb729f11fb8b14f104a041c8504772a8c95 Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Thu Sep 28 19:44:47 2023 +0200 Add hpp files to .editorconfig commit 52bfff7ceb3d259b1a71002fcbfb20261294bdd7 Merge: 1f72e56 19dbbe1 Author: Jakob P. Liljenberg Date: Sat Sep 30 18:55:08 2023 +0200 Merge pull request #636 from nobounce/performance-iili Minor string initialization improvement commit 19dbbe1a17f7e6453709c37a23859e5d73591e53 Author: nobounce Date: Fri Sep 29 12:20:59 2023 +0200 Minor string initialization improvement commit 1f72e56c7d6e70f8851134c0a28e17fb0a824a71 Merge: 278a0e6 cdcf8bc Author: Jakob P. Liljenberg Date: Fri Sep 29 10:43:21 2023 +0200 Merge pull request #633 from crestfallnatwork/main [fix] Made disks statvfs logic asynchronous. commit cdcf8bc92978c826d9c1768b547df3b7484003f2 Author: crestfalln Date: Fri Sep 29 09:07:27 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 9b4e85f08dc3be40d8f4904093cd2bdd096e60fa Author: crestfalln Date: Thu Sep 28 04:57:05 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 889623874ef6233610ed529bff18e1ba2c407e14 Author: crestfalln Date: Wed Sep 27 23:57:06 2023 +0530 made disks stat logic async commit 278a0e6b171a5f967e0680f679c20d9fc8d58c6f Merge: d16adc9 e89519f Author: Jakob P. Liljenberg Date: Thu Sep 28 18:32:09 2023 +0200 Merge pull request #630 from lvxnull/signal-list Fix signal list on non-linux/weird linux platforms commit e89519fbb2cd53ddb06ab0a39093c19fc595277c Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Sun Sep 24 21:44:38 2023 +0200 Fix signal list on non-linux/weird linux platforms commit d16adc9fd03322d46f1b84e5ebe7d426f726a5cc Merge: 2c3ac48 f34b408 Author: Jakob P. Liljenberg Date: Thu Sep 28 18:20:42 2023 +0200 Merge pull request #618 from nobounce/aggregate-child-processes Add option to accumulate a child's resources in parent in tree-view commit f34b40892fef31f657cbe8066c8b0d41ed37c0fc Author: nobounce Date: Sun Sep 24 16:34:50 2023 +0200 Make process thread count better readable when wider than 5 digits commit 6027cedd424e963bc6fe9017252ed4f1c9f8634b Author: nobounce Date: Thu Sep 14 23:27:05 2023 +0200 Add option to accumulate a child's resources in parent in tree-view commit 2c3ac4855de49563edd4ef199b0be74babc7ce32 Merge: f90dc37 5c6a281 Author: Jakob P. Liljenberg Date: Wed Sep 13 21:14:56 2023 +0200 Merge pull request #589 from nobounce/cmake Add CMake support for Linux commit f90dc37c26024f28c2a88d87d041fca1f1b5db1e Merge: 0cac861 68a49c1 Author: Jakob P. Liljenberg Date: Wed Sep 13 20:27:05 2023 +0200 Merge pull request #610 from SidVeld/feature/horizon-theme Horizon theme commit 5c6a2810021c352584a0834c95eff4ece7454c0e Author: nobounce Date: Tue Aug 29 20:39:00 2023 +0200 Add CMake support Linux is completly supported FreeBSD is not able to create a static executable for now. See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=273398 MacOS was not tested commit 68a49c10a63371b65beb670f7a55ccb43fe38311 Author: SidVeld Date: Wed Sep 6 18:03:31 2023 +0300 Add horizon theme commit 0cac8619105521999d603efaeb7d7eed8c38d746 Merge: 31be436 f798acd Author: Jakob P. Liljenberg Date: Tue Sep 5 19:27:38 2023 +0200 Merge pull request #609 from scorpion-26/byteconv Fix short conversion of 1000-1023 *iB commit f798acdaf71455684883df0ffb31d29293f788b2 Author: scorpion-26 Date: Tue Sep 5 18:00:47 2023 +0200 Fix short conversion of 1000-1023*iB floating_humanizer([1000-1024], true) with base 8 returns "2K", whereas it should return "1.0K" to align with other formats. The conversion is also broken for all other units(e.g. 1023M is also broken and returns "2G") commit 31be4362ce122182862ac338a9a479cde3a4c8e2 Author: aristocratos Date: Sun Aug 27 02:00:07 2023 +0200 FreeBSD Github action 13.1 -> 13.2 and static libgcc and libstdc++ commit fc523fd1d0ef49443367056e30653933efc8920c Author: aristocratos Date: Sun Aug 27 01:36:26 2023 +0200 Fix for FreeBSD github action not failing "correctly"... commit b87772611cb6290e834c6beb27eaceea46a10d90 Author: aristocratos Date: Sat Nov 25 20:44:45 2023 +0100 Added definition GPU_SUPPORT to toggle GPU related code commit 0267eba2bbebd6f166b24358159d7fb094fae052 Merge: 50bbab0 e81cf2b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:43:18 2023 +0100 Merge pull request #659 from ivanp7/patch-1 Add alternative key codes for Delete, Insert, Home, End commit 50bbab05122b2f8c35c90e4a6e01d5c52e5eca60 Merge: 9edbf27 5a14c7b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:35:50 2023 +0100 Merge pull request #660 from stradicat/feature/elementarish Elementarish theme: color update according to Elementary palette commit 5a14c7b6fa41a7fe7061c0aa2f2d0c0fe530e495 Merge: 979506f 71eb414 Author: Dennis Mayr Date: Wed Nov 15 17:27:34 2023 -0300 Merge branch 'main' of https://github.com/stradicat/btop commit 979506f18ecdc1475b882d7dadc220386169b7db Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit 71eb4142e8204303af091555067b93d82e5dcec1 Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit e81cf2b7ff4111e279e1111127e49c6858d83d5e Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Tue Nov 7 15:12:27 2023 +0000 Add alternative key codes for Insert, Home, End commit f9452ff6d56af721fb5526ede11206c7d3b885b8 Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Mon Nov 6 13:31:53 2023 +0000 Add alternative Delete key code Delete key not always produces ^[[3~, on some terminals (like st) it produces ^[[P. commit 9edbf27f1b6d5844a97325fcda732762ba086a99 Merge: 2a864f6 ff1f51c Author: Jakob P. Liljenberg Date: Sat Oct 21 02:09:55 2023 +0200 Merge pull request #649 from nobounce/workflow-timeout Set FreeBSD workflow timeout commit ff1f51ccbb6d6133292e57ba1b8edb8b6c941fc9 Author: Steffen Winter Date: Wed Oct 18 22:26:36 2023 +0200 Set FreeBSD workflow timeout Recently the FreeBSD workflow has started to hang in a boot loop when the VM starts up. The issue is being tracked upstream but there is not response at the moment. To work around this set a timeout to not waste CI minutes. Other workflows might also want this change since they don't take 20 minutes anyway. commit 2a864f6f2ea60df16b3f015885eb3c18a48b9b78 Merge: 636eb25 b2bf8ef Author: Jakob P. Liljenberg Date: Sat Oct 7 10:40:54 2023 +0200 Merge pull request #643 from DecklynKern/main Fix scrollbar not clearing sometimes. commit b2bf8ef504f29650f8fe0adab41c3cac35b67567 Author: DecklynKern Date: Fri Oct 6 17:33:38 2023 -0600 Fix scrollbar not clearing sometimes. commit 636eb25f5e31a7af337b024873b2ceb42650ebdb Merge: 260c0f6 b5ba2fc Author: Jakob P. Liljenberg Date: Sat Sep 30 19:51:03 2023 +0200 Merge pull request #623 from rahulaggarwal965/main Add keybind for toggling memory display mode in PROC box commit b5ba2fc9635e540142d3dd3eccd866865c9393fd Author: Rahul Aggarwal Date: Wed Sep 20 22:55:56 2023 -0400 Add keybind for toggling memory display mode in PROC box commit 260c0f662313fe0d0df859645f5731af04fc9fa7 Merge: 52bfff7 e6a06eb Author: Jakob P. Liljenberg Date: Sat Sep 30 18:56:25 2023 +0200 Merge pull request #635 from lvxnull/editorconfig Add hpp files to .editorconfig commit e6a06eb729f11fb8b14f104a041c8504772a8c95 Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Thu Sep 28 19:44:47 2023 +0200 Add hpp files to .editorconfig commit 52bfff7ceb3d259b1a71002fcbfb20261294bdd7 Merge: 1f72e56 19dbbe1 Author: Jakob P. Liljenberg Date: Sat Sep 30 18:55:08 2023 +0200 Merge pull request #636 from nobounce/performance-iili Minor string initialization improvement commit 19dbbe1a17f7e6453709c37a23859e5d73591e53 Author: nobounce Date: Fri Sep 29 12:20:59 2023 +0200 Minor string initialization improvement commit 1f72e56c7d6e70f8851134c0a28e17fb0a824a71 Merge: 278a0e6 cdcf8bc Author: Jakob P. Liljenberg Date: Fri Sep 29 10:43:21 2023 +0200 Merge pull request #633 from crestfallnatwork/main [fix] Made disks statvfs logic asynchronous. commit cdcf8bc92978c826d9c1768b547df3b7484003f2 Author: crestfalln Date: Fri Sep 29 09:07:27 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 9b4e85f08dc3be40d8f4904093cd2bdd096e60fa Author: crestfalln Date: Thu Sep 28 04:57:05 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 889623874ef6233610ed529bff18e1ba2c407e14 Author: crestfalln Date: Wed Sep 27 23:57:06 2023 +0530 made disks stat logic async commit 278a0e6b171a5f967e0680f679c20d9fc8d58c6f Merge: d16adc9 e89519f Author: Jakob P. Liljenberg Date: Thu Sep 28 18:32:09 2023 +0200 Merge pull request #630 from lvxnull/signal-list Fix signal list on non-linux/weird linux platforms commit e89519fbb2cd53ddb06ab0a39093c19fc595277c Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Sun Sep 24 21:44:38 2023 +0200 Fix signal list on non-linux/weird linux platforms commit d16adc9fd03322d46f1b84e5ebe7d426f726a5cc Merge: 2c3ac48 f34b408 Author: Jakob P. Liljenberg Date: Thu Sep 28 18:20:42 2023 +0200 Merge pull request #618 from nobounce/aggregate-child-processes Add option to accumulate a child's resources in parent in tree-view commit f34b40892fef31f657cbe8066c8b0d41ed37c0fc Author: nobounce Date: Sun Sep 24 16:34:50 2023 +0200 Make process thread count better readable when wider than 5 digits commit 6027cedd424e963bc6fe9017252ed4f1c9f8634b Author: nobounce Date: Thu Sep 14 23:27:05 2023 +0200 Add option to accumulate a child's resources in parent in tree-view commit 2c3ac4855de49563edd4ef199b0be74babc7ce32 Merge: f90dc37 5c6a281 Author: Jakob P. Liljenberg Date: Wed Sep 13 21:14:56 2023 +0200 Merge pull request #589 from nobounce/cmake Add CMake support for Linux commit f90dc37c26024f28c2a88d87d041fca1f1b5db1e Merge: 0cac861 68a49c1 Author: Jakob P. Liljenberg Date: Wed Sep 13 20:27:05 2023 +0200 Merge pull request #610 from SidVeld/feature/horizon-theme Horizon theme commit 5c6a2810021c352584a0834c95eff4ece7454c0e Author: nobounce Date: Tue Aug 29 20:39:00 2023 +0200 Add CMake support Linux is completly supported FreeBSD is not able to create a static executable for now. See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=273398 MacOS was not tested commit 68a49c10a63371b65beb670f7a55ccb43fe38311 Author: SidVeld Date: Wed Sep 6 18:03:31 2023 +0300 Add horizon theme commit 0cac8619105521999d603efaeb7d7eed8c38d746 Merge: 31be436 f798acd Author: Jakob P. Liljenberg Date: Tue Sep 5 19:27:38 2023 +0200 Merge pull request #609 from scorpion-26/byteconv Fix short conversion of 1000-1023 *iB commit f798acdaf71455684883df0ffb31d29293f788b2 Author: scorpion-26 Date: Tue Sep 5 18:00:47 2023 +0200 Fix short conversion of 1000-1023*iB floating_humanizer([1000-1024], true) with base 8 returns "2K", whereas it should return "1.0K" to align with other formats. The conversion is also broken for all other units(e.g. 1023M is also broken and returns "2G") commit 975525d38f60b2a3f3257dcf170226fc06b32f62 Author: aristocratos Date: Sun Aug 27 12:34:46 2023 +0200 Fix: Cpu gpu stats always shown when show_gpu_info is On and sizing issues commit 08abf0b9301e9d48f363ec8ffee161b351c4716d Author: aristocratos Date: Sun Aug 27 01:28:36 2023 +0200 Quickfixes for MacOS and FreeBSD compilation. commit 7290109f80175c8f140fa6abd67ad0183e1f21ec Author: aristocratos Date: Sun Aug 27 00:58:30 2023 +0200 Merge fix commit 283d4632427b0a5e3c8f8518e3db3fdd8b257f9c Merge: efddad4 c296ac1 Author: aristocratos Date: Sun Aug 27 00:56:22 2023 +0200 Merge branch 'main' into pr/romner-set/529 commit efddad42dce74ff4553307e30e4cfa58210dc377 Author: aristocratos Date: Sun Aug 27 00:39:57 2023 +0200 Changed: cpu_graph_lower Auto defaults to cpu_graph_upper when show_gpu_info is Off commit a9bc0874d48dfef157a95d075e5e81ad248faa87 Author: aristocratos Date: Sun Aug 27 00:31:07 2023 +0200 Added show_gpu_info setting and Auto options for cpu graphs commit b3970ee19cb6d240fcf40cd0077e61ae346da3af Author: aristocratos Date: Sat Aug 26 20:52:59 2023 +0200 Fixed: Key 5-0 gpu box toggle commit bd5d697830f65adb0ce6c4bd7e292e34c10079db Author: aristocratos Date: Sat Aug 26 20:29:43 2023 +0200 Squashed commit of the following: commit c296ac13cd4c16a11e137c309b7452bab096312e Merge: 9a1e760 091c30a Author: Jakob P. Liljenberg Date: Sat Aug 26 19:29:57 2023 +0200 Merge pull request #590 from nobounce/dangling-reference-config Convert parameters and config keys to std::string_view commit 9a1e760a661c9a160dd83e6d3ab710bf36b19b04 Merge: 9c8af4d 22e64ca Author: Jakob P. Liljenberg Date: Sat Aug 26 19:20:18 2023 +0200 Merge pull request #602 from jfouquart/main Fix getting zfs pool name with '.' char in freebsd commit 9c8af4df436c2847eefa66d2e0eb7ebfd75d70cf Merge: 8a49d8c 2217cbe Author: Jakob P. Liljenberg Date: Sat Aug 26 19:18:55 2023 +0200 Merge pull request #601 from joske/cleanup [macos] don't check /sys on macos commit 8a49d8cf456d0a15db65e7dc704d627b75a0fe43 Merge: 1556388 008fcd8 Author: Jakob P. Liljenberg Date: Sat Aug 26 19:18:07 2023 +0200 Merge pull request #600 from joske/makefile [macos/freebsd] support gcc13 commit 1556388c83644d122fab9241aa876232d94d1928 Merge: 1b126f5 d17e1a2 Author: Jakob P. Liljenberg Date: Sat Aug 26 19:14:00 2023 +0200 Merge pull request #599 from joske/main [macos] fix temp sensor on system with many cores commit d17e1a2dac79458940319d7117a21bdcd73ed53c Author: Jos Dehaes Date: Fri Aug 25 16:18:39 2023 +0200 fix some warnings commit 4d8aa6b11896dac99f81019e6dea11cc8d8856f1 Author: Jos Dehaes Date: Fri Aug 25 15:52:58 2023 +0200 fix core check commit 22e64caaff3d5877b7a494980a8ee3f17ea8f824 Author: Jonathan Fouquart Date: Fri Aug 25 09:37:49 2023 +0200 Fix getting zfs pool name with '.' char in freebsd commit 2217cbe143dd5aa45dbd50b4dc829577e2e1ccda Author: Jos Dehaes Date: Wed Aug 23 16:01:04 2023 +0200 [macos] don't check /sys on macos commit 008fcd889e862f1d378d331dab51b3d3ce9d9f3c Author: Jos Dehaes Date: Wed Aug 23 16:05:00 2023 +0200 also add g++13 commit 0fdca5eb0385253969e029fdfcf1fb9cff83ea33 Author: Jos Dehaes Date: Wed Aug 23 15:54:07 2023 +0200 support gcc13 commit dcbdb7360d44b4071ec0fe0757a0875a12147c8a Author: Jos Dehaes Date: Wed Aug 23 15:46:47 2023 +0200 [macos] fix temp sensor on system with many cores commit 1b126f55e38de76a2cca796593ef1554828d61e6 Author: aristocratos Date: Fri Aug 4 01:08:27 2023 +0200 Update Makefile for partial static compilation on freebsd commit c8ec6bbb000a865f14c50414e456955c473a2f3a Author: aristocratos Date: Thu Aug 3 23:08:33 2023 +0200 Fix freebsd nullptr changes and makefile for gcc12 and newer commit 8a33aab5885f828d7d0d2523aff31f9c33170332 Merge: 94e5c02 e4abcef Author: Jakob P. Liljenberg Date: Sun Jul 30 13:21:48 2023 +0200 Merge pull request #539 from nobounce/replace-NULL-nullptr Modernize using nullptr. commit 94e5c02d113f3fc8956d63ef4f0eecebfbf31b9d Author: aristocratos Date: Thu Jul 27 20:51:21 2023 +0200 Better text editing commit 091c30ab2be074836bb8d9a4f658cec9a5b36303 Author: nobounce Date: Thu Jul 27 14:17:54 2023 +0200 Convert parameters and config keys to std::string_view Using std::string_view instead of std::string& silences a new warning from GCC 13, -Wdangling-reference Also switch return type of `getI` from int& to int, trivial types are cheaper to copy by value commit e4abcefbf92e5d94ad169e1e47c0fbec7279fa6f Author: nobounce Date: Wed Jul 26 16:19:17 2023 +0200 Use nullptr instead of NULL. See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf TLDR: NULL is of type int and relies on proper implicit pointer conversion which may lead to issues when using overloaded functions It is also considered a 'best practise' for modern C++ and conveys the programmers intention more precisly. commit d53307f14cfa9cb416a3d1c8919d4f61cbb20bf7 Author: nobounce Date: Sun Jul 23 19:53:36 2023 +0200 Fix path to Linux CI file in itself The CI file has a list of dependent files including itself. The path was not updated when the CI was split into different files commit 594f42b9ebf886b70f6cdf0fb909d53eb6c5407f Merge: aca2e4b 53d6eba Author: Jakob P. Liljenberg Date: Wed Jul 26 15:38:01 2023 +0200 Merge pull request #584 from nobounce/nb/fix-ci-path Fix path to Linux CI file in itself commit aca2e4be7568186e122f1506fa25bdfb8c4f7f2a Author: aristocratos Date: Wed Jul 26 14:38:48 2023 +0200 Fix whitespace indent -> tab indent commit 33faa01910309895763011dce2a3194b889a0b6a Author: aristocratos Date: Wed Jul 26 14:34:15 2023 +0200 Revert fmt submodule to static fmt folder in include commit 53d6ebabc052a618001fa857eff25d5252a89210 Author: nobounce Date: Sun Jul 23 19:53:36 2023 +0200 Fix path to Linux CI file in itself The CI file has a list of dependent files including itself. The path was not updated when the CI was split into different files commit 346c9e479be1328c2fb133a457259b09884a1db3 Author: romner Date: Wed Jul 19 16:53:58 2023 +0200 Fix GPU text overflow in CPU panel, again commit 3a5e5fd5d37da33cbec48635f5c97502c824755b Author: romner Date: Wed Jul 19 16:22:45 2023 +0200 Improve 0-10 key input commit 972b2b6a011e9376090e1d14997550120eb37f36 Author: romner Date: Wed Jul 19 15:54:35 2023 +0200 Fix available boxes in menu & config description commit 1f73453aeca76cd04d7154c3bc82b6ebf64ba593 Author: romner Date: Wed Jul 19 15:34:23 2023 +0200 Fix crashes when trying to open nth GPU box with only n-1 GPUs in the system commit 46c6be0a296e922a265f1ad6ec0cfdcb487783d7 Author: romner Date: Sun Jul 16 17:19:09 2023 +0200 Fix GPU horizontal text overflow in CPU panel commit 85fb28cee6e6953ce5aed4725ce82bf65a7e5ade Author: romner Date: Fri Jul 14 02:39:44 2023 +0200 Fix RSMI_STATIC=true and add GPU section to README.md commit 3fad8a6fdec3b3efb5d4d407d969cec5e62ffa7a Author: romner-set Date: Mon Jun 26 13:10:31 2023 +0200 Add GPU options commit 746f716a027d0a76d0e48640052c73f9fc188450 Author: romner-set Date: Fri Jun 16 11:11:57 2023 +0200 Remove lib/rocm_smi_lib and add instructions for obtaining it to README commit d8ebbe1181346234edce4dfa030c3b63ab025942 Author: romner Date: Thu Jun 8 20:24:01 2023 +0200 Join NVML PCIe threads only if PCIe TX/RX is supported by GPU commit be1098915169cfb6a87c5fbad7b90cdb078b8257 Author: romner Date: Tue Jun 6 19:47:07 2023 +0200 Parallelize NVML PCIe TX/RX data collection commit 85892a9fe3968b9c72806eb0cb030cf77bd1e3e4 Author: aristocratos Date: Mon Jun 5 21:59:26 2023 +0200 Fix type: ulong -> size_t and compare std::cmp_less commit 85a10f0305cf0e8881c7ad02a8d3d483fe3186b0 Author: romner Date: Fri Jun 2 16:14:24 2023 +0200 Fix ROCm SMI makefile flags commit cd6979277dcc83f80e4e4f9671078592920f2177 Author: romner Date: Fri Jun 2 15:44:44 2023 +0200 Fix error when ROCm SMI static compilation fails commit daaa45324fb3f73a8f755277a91c2007c18b9bbc Author: romner Date: Fri Jun 2 15:34:12 2023 +0200 Load ROCm SMI dynamically by default, optionally statically compile and link commit 093edfe948c0ec4e55bb3f4e3888f13779db2886 Author: aristocratos Date: Thu Jun 1 19:49:00 2023 +0200 Minor changes in wording... commit b9a4d31fa48b33d6b892ebb9c828dd1ba3e7fde0 Author: aristocratos Date: Thu Jun 1 19:37:53 2023 +0200 Fix Makefile dependency order and layout commit a0163ce22007cc5016add0aeba567970d5f1d25e Author: romner Date: Thu Jun 1 16:42:02 2023 +0200 Statically link ROCm SMI commit b2df0696fdaacd7a4553180d065d160cb7405fe2 Author: romner-set Date: Thu Jun 1 03:41:56 2023 +0200 Dynamically load NVML commit 547f17dda33365f15610829448d0afa7472fc013 Author: romner-set Date: Tue May 30 18:24:50 2023 +0200 Add more GPU graph types to the CPU panel commit 842c761a731d64d477dce1181feadfe03de508cf Author: romner-set Date: Mon May 22 09:46:20 2023 +0200 Fix crash when all GPU panels are open but the CPU panel is closed commit 8c96bd51e96dfd602f3ba41da2e162f729a398dc Author: romner Date: Sun May 21 20:34:47 2023 +0200 Handle GPUs which cannot report certain stats in GPU panel commit 414d7eb94c6073823e7874c3947686c23929eb19 Author: romner Date: Sun May 21 18:02:50 2023 +0200 Handle GPUs which cannot report certain stats in btop_collect.cpp and CPU panel commit 005de97e6d1c162154bfa0255be83ba462f8f660 Author: aristocratos Date: Sun May 21 13:58:11 2023 +0200 Add missing fmt prefixes commit 1fee2bc08b9215ce728f16be2ee8a3068ae45618 Author: aristocratos Date: Sun May 21 13:52:19 2023 +0200 Add DebugTimer class and change some Logger::error calls to Logger::debug commit 2e68c0b916a4fb72c70b2fac2f7c25c8fb45b75b Author: aristocratos Date: Sat May 20 17:27:20 2023 +0200 Fixed key > gpu_names check commit 04ed16a9f6846a3231eee0016268e177b21a10db Author: aristocratos Date: Sat May 20 17:15:45 2023 +0200 Merged changes from main commit 8c710a2b687954bd66f07b02265da420bf645379 Author: aristocratos Date: Sat May 20 01:41:04 2023 +0200 Makefile auto detection and initial logic for excluding gpu code when libs are missing commit 8bae1ec092814368b04fb49827c7b89c327c62d6 Author: aristocratos Date: Sat May 20 00:13:00 2023 +0200 Fixed debug timer for gpu commit 01acfd603eda8b40829a6453d1fdb22f9e472238 Author: romner-set Date: Fri May 19 16:42:32 2023 +0200 Bind GPU panel to 5,6,7,8,9,0 and fully implement multi-GPU support commit 22a463976d107a1e5849621e2a11f3e1071ed884 Author: romner Date: Thu May 18 16:07:05 2023 +0200 Add GPU info to CPU panel commit c352bf26137daa0d242fa52ef11ecd6550bf0823 Author: romner-set Date: Mon May 15 19:42:55 2023 +0200 Add ROCm SMI backend for AMD GPU support commit 917d568a77b9e693a6d6a2c7fab57f957eff988d Author: romner-set Date: Mon May 15 13:58:54 2023 +0200 Add multi-GPU support for NVML data collection commit 2d27f2ff610d4894997ffbcede70ed5f716a4f94 Author: romner Date: Sun May 14 17:40:50 2023 +0200 Fix crash when no nvidia GPU is detected commit 0e0025a2c3dd521f9bcdb1bb9b0d76508a4663af Author: romner Date: Sun May 14 17:31:39 2023 +0200 Update makefile text, fix typo and adhere to contibuting guidelines commit bcffcdf19f4d2cb774b916f43493ec7c051ac92a Author: romner Date: Sun May 14 16:53:06 2023 +0200 Make GPU window's size dynamic and integrate it with the rest of btop commit 95b32283083ca2b74824a94ce97561b323ea1d17 Author: romner Date: Sat May 13 19:41:51 2023 +0200 Improve GPU side panel commit adcdc583b098156abc639a068238ef277f166153 Author: romner Date: Sat May 13 00:27:23 2023 +0200 Add GPU side panel commit d522a91ef4a95ccd2ff0df45271ff86fc119ab67 Author: romner Date: Fri May 12 19:34:47 2023 +0200 Add rudimentary, fullscreen single-GPU NVML utilization graph --- .editorconfig | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 5 +- .github/workflows/cmake-freebsd.yml | 40 + .github/workflows/cmake-linux.yml | 40 + .github/workflows/cmake-macos.yml | 47 + .../workflows/continuous-build-freebsd.yml | 24 +- .github/workflows/test-snap-can-build.yml | 42 + .gitignore | 22 +- CHANGELOG.md | 46 + CMakeLists.txt | 194 ++ Makefile | 101 +- README.md | 459 ++- cmake/Modules/Finddevstat.cmake | 23 + cmake/Modules/Findelf.cmake | 23 + cmake/Modules/Findkvm.cmake | 23 + include/robin_hood.h | 2544 ----------------- snap/snapcraft.yaml | 2 +- src/btop.cpp | 109 +- src/btop_config.cpp | 74 +- src/btop_config.hpp | 26 +- src/btop_draw.cpp | 679 ++++- src/btop_draw.hpp | 9 +- src/btop_input.cpp | 36 +- src/btop_input.hpp | 5 +- src/btop_menu.cpp | 202 +- src/btop_menu.hpp | 2 +- src/btop_shared.cpp | 19 + src/btop_shared.hpp | 114 +- src/btop_theme.cpp | 14 +- src/btop_theme.hpp | 9 +- src/btop_tools.cpp | 97 +- src/btop_tools.hpp | 132 +- src/freebsd/btop_collect.cpp | 35 +- src/linux/btop_collect.cpp | 835 +++++- src/osx/btop_collect.cpp | 38 +- src/osx/sensors.cpp | 3 + src/osx/sensors.hpp | 3 + themes/elementarish.theme | 50 +- themes/horizon.theme | 86 + 39 files changed, 3217 insertions(+), 2997 deletions(-) create mode 100644 .github/workflows/cmake-freebsd.yml create mode 100644 .github/workflows/cmake-linux.yml create mode 100644 .github/workflows/cmake-macos.yml create mode 100644 .github/workflows/test-snap-can-build.yml create mode 100644 CMakeLists.txt create mode 100644 cmake/Modules/Finddevstat.cmake create mode 100644 cmake/Modules/Findelf.cmake create mode 100644 cmake/Modules/Findkvm.cmake delete mode 100644 include/robin_hood.h create mode 100644 themes/horizon.theme diff --git a/.editorconfig b/.editorconfig index 63202f0..b27d25a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -[*.{cpp,h,sh,md,cfg,sample}] +[*.{cpp,h,hpp,sh,md,cfg,sample}] indent_style = tab indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3c937d1..7ed6a21 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -29,6 +29,7 @@ Any bug that can be solved by just reading the [prerequisites](https://github.co **Info (please complete the following information):** - btop++ version: `btop -v` + - 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` @@ -40,7 +41,9 @@ Any bug that can be solved by just reading the [prerequisites](https://github.co **Additional context** -contents of `~/.config/btop/btop.log` +Contents of `~/.config/btop/btop.log` + +Note: The snap uses: `~/snap/btop/current/.config/btop` (try running btop with `--debug` flag if btop.log is empty) diff --git a/.github/workflows/cmake-freebsd.yml b/.github/workflows/cmake-freebsd.yml new file mode 100644 index 0000000..6e687f1 --- /dev/null +++ b/.github/workflows/cmake-freebsd.yml @@ -0,0 +1,40 @@ +name: FreeBSD CMake + +on: + push: + branches: main + tags-ignore: '*.*' + paths: + - '.github/workflows/cmake-freebsd.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/freebsd/*pp' + pull_request: + branches: main + paths: + - '.github/workflows/cmake-freebsd.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/freebsd/*pp' + +jobs: + cmake_build_on_freebsd: + runs-on: ubuntu-22.04 + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Compile + uses: vmactions/freebsd-vm@v1 + with: + release: '14.0' + usesh: true + prepare: pkg install -y cmake ninja + 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 new file mode 100644 index 0000000..49754d9 --- /dev/null +++ b/.github/workflows/cmake-linux.yml @@ -0,0 +1,40 @@ +name: Linux CMake + +on: + push: + branches: main + tags-ignore: '*.*' + paths: + - '.github/workflows/cmake-linux.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/linux/*pp' + pull_request: + branches: main + paths: + - '.github/workflows/cmake-linux.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/linux/*pp' + +jobs: + cmake_build_on_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 clang cmake lld ninja + + - 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 new file mode 100644 index 0000000..32d6f7f --- /dev/null +++ b/.github/workflows/cmake-macos.yml @@ -0,0 +1,47 @@ +name: macOS CMake + +on: + push: + branches: main + tags-ignore: '*.*' + paths: + - '.github/workflows/cmake-macos.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/osx/*pp' + pull_request: + branches: main + paths: + - '.github/workflows/cmake-macos.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/osx/*pp' + +jobs: + cmake_build_on_macos: + runs-on: macos-latest + 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: | + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + brew update --quiet + brew install --force --overwrite cmake llvm@17 ninja + + - name: Configure + run: | + export LLVM_PREFIX="$(brew --prefix llvm)" + export CXX="$LLVM_PREFIX/bin/clang++" + export CPPFLAGS="-I$LLVM_PREFIX/include" + export LDFLAGS="-L$LLVM_PREFIX/lib -L$LLVM_PREFIX/lib/c++ -Wl,-rpath,$LLVM_PREFIX/lib/c++ -fuse-ld=$LLVM_PREFIX/bin/ld64.lld" + cmake -B build -G Ninja + + - name: Compile + run: cmake --build build --verbose + diff --git a/.github/workflows/continuous-build-freebsd.yml b/.github/workflows/continuous-build-freebsd.yml index 5caa6d1..041133f 100644 --- a/.github/workflows/continuous-build-freebsd.yml +++ b/.github/workflows/continuous-build-freebsd.yml @@ -27,28 +27,34 @@ on: jobs: build-freebsd: - runs-on: macos-12 + runs-on: ubuntu-22.04 + timeout-minutes: 20 + strategy: + matrix: + compiler: ["clang++", "g++"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Compile - uses: vmactions/freebsd-vm@v0 + uses: vmactions/freebsd-vm@v1 with: - release: 13.2 + release: '14.0' usesh: true prepare: | - pkg install -y gmake gcc11 coreutils git - git config --global --add safe.directory /Users/runner/work/btop/btop + pkg install -y gmake gcc coreutils git + git config --global --add safe.directory /home/runner/work/btop/btop run: | - gmake STATIC=true STRIP=true + CXX=${{ matrix.compiler }} gmake STATIC=true STRIP=true GIT_HASH=$(git rev-parse --short "$GITHUB_SHA") - mv bin/btop bin/btop-$GIT_HASH + COMPILER=$(echo ${{ matrix.compiler }} | sed 's/clang++/llvm/' | sed 's/g++/gcc/') + mv bin/btop bin/btop-"$COMPILER"-"$GIT_HASH" ls -alh bin - uses: actions/upload-artifact@v3 with: - name: btop-x86_64-FreeBSD-13.2 + name: btop-x86_64-freebsd-14 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 new file mode 100644 index 0000000..c66df73 --- /dev/null +++ b/.github/workflows/test-snap-can-build.yml @@ -0,0 +1,42 @@ +name: 🧪 Test snap can be built on x86_64 + +on: + workflow_dispatch: + push: + branches: [ main ] + tags-ignore: + - '*.*' + paths: + - 'src/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.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: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v2 + + - uses: snapcore/action-build@v1 + id: build + + - uses: diddlesnaps/snapcraft-review-action@v1 + with: + snap: ${{ steps.build.outputs.snap }} + isClassic: 'false' diff --git a/.gitignore b/.gitignore index 25ee477..7e2ed8f 100644 --- a/.gitignore +++ b/.gitignore @@ -51,9 +51,11 @@ bin btop .*/ +# Optional libraries +lib/rocm_smi_lib -#do not ignore .github directory -!.github +# Don't ignore .github directory +!.github/ # Ignore files created by Qt Creator *.config @@ -64,3 +66,19 @@ btop *.cxxflags *.files *.includes + +# CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# CLion +cmake-build-* diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a4eceb..bbf273b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +## v1.3.0 + +* Added Gpu Support | @romner-set | PR #529 + +* Enable macos clang | @muneebmahmed | PR #666 + +* Fix Apple Silicon CPUs misprinted | @masiboss | PR #679 + +* Cmake support for MacOS | @imwints | PR #675 + +* Elementarish theme: color update according to Elementary palette | @stradicat | PR #660 + +* Add alternative key codes for Delete, Insert, Home, End | @ivanp7 | PR #659 + +* Fix scrollbar not clearing sometimes. | @DecklynKern | PR #643 + +* Add keybind for toggling memory display mode in PROC box | @rahulaggarwal965 | PR #623 + +* Minor string initialization improvement | @imwints | PR #636 + +* Made disks statvfs logic asynchronous. | @crestfallnatwork | PR #633 + +* Fix signal list on non-linux/weird linux platforms | @lvxnull | PR #630 + +* Add option to accumulate a child's resources in parent in tree-view | @imwints | PR #618 + +* Add CMake support for Linux | @imwints | PR #589 + +* Horizon theme | @SidVeld | PR #610 + +* Fix short conversion of 1000-1023 *iB | @scorpion-26 | #609 + +* Fix integer overflows in btop_collect.cpp | @dorrellmw | #546 + +* Support compiling with LLVM | @imwints | #510 + +* Fix getting zfs pool name with '.' char in freebsd | @jfouquart | #602 + +* [macos/freebsd] support gcc13 | @joske | #600 + +* FreeBSD swap info | @rrveex | #560 + +* Create adwaita.theme | @flipflop133 | #485 + ++ Various fixes by @imwints, @simplepad, @joske, @gwena, @cpalv, @iambeingtracked, @mattico, @NexAdn + ## v1.2.13 * Makefile: VERBOSE=true flag for Makefile to display all compiler commands and fixed so already set CXXFLAGS and LDFLAGS are displayed. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f8c546 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,194 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# CMake configuration for btop +# + +cmake_minimum_required(VERSION 3.24) + +# Disable in-source builds since they would override the Makefile +if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + message(FATAL_ERROR "In-source builds are not allowed") +endif() + +project("btop" + VERSION 1.2.13 + DESCRIPTION "A monitor of resources" + HOMEPAGE_URL "https://github.com/aristocratos/btop" + LANGUAGES CXX +) + +include(CheckCXXCompilerFlag) +include(CheckIncludeFileCXX) +include(CheckIPOSupported) +include(CMakeDependentOption) + +# Make our Find.cmake files available +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") + +# When the build type is not set we can't fortify +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_COLOR_DIAGNOSTICS ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(BTOP_STATIC "Link btop statically" OFF) +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_GPU "Enable GPU support" ON) +cmake_dependent_option(BTOP_RSMI_STATIC "Link statically to ROCm SMI" OFF "BTOP_GPU" OFF) + +if(BTOP_STATIC AND NOT APPLE) + # Set this before calling find_package + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +endif() + +add_executable(btop + src/btop.cpp + src/btop_config.cpp + src/btop_draw.cpp + src/btop_input.cpp + src/btop_menu.cpp + src/btop_shared.cpp + src/btop_theme.cpp + src/btop_tools.cpp +) + +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(LINUX) + target_sources(btop PRIVATE src/linux/btop_collect.cpp) +else() + message(FATAL_ERROR "${CMAKE_SYSTEM_NAME} is not supported") +endif() + +check_include_file_cxx(ranges CXX_HAVE_RANGES) +if(NOT CXX_HAVE_RANGES) + message(FATAL_ERROR "The compiler doesn't support ") +endif() + +# Check for and enable LTO +check_ipo_supported(RESULT ipo_supported) +if(ipo_supported AND BTOP_LTO) + set_target_properties(btop PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON) +endif() + +target_compile_options(btop PRIVATE -Wall -Wextra -Wpedantic -ftree-vectorize) + +if(BTOP_PEDANTIC) + target_compile_options(btop PRIVATE + -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual + -Wconversion -Wsign-conversion -Wdouble-promotion -Wformat=2 -Wimplicit-fallthrough -Weffc++ + $<$:-Wheader-hygiene -Wgnu -Wthread-safety> + $<$:-Wduplicated-cond -Wduplicated-branches -Wlogical-op> + $<$:-Wnull-dereference -Wuseless-cast> + ) +endif() +if(BTOP_WERROR) + target_compile_options(btop PRIVATE -Werror) +endif() + +if(NOT APPLE) + target_compile_options(btop PRIVATE -fstack-clash-protection) +endif() +check_cxx_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR) +if(HAS_FSTACK_PROTECTOR) + target_compile_options(btop PRIVATE -fstack-protector) +endif() +check_cxx_compiler_flag(-fcf-protection HAS_FCF_PROTECTION) +if(HAS_FCF_PROTECTION) + target_compile_options(btop PRIVATE -fcf-protection) +endif() + +target_compile_definitions(btop PRIVATE + _FILE_OFFSET_BITS=64 + $<$:_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1> + # Only has an effect with optimizations enabled + $<$>:_FORTIFY_SOURCE=2> +) + +target_include_directories(btop SYSTEM PRIVATE include) + +# Enable pthreads +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(btop Threads::Threads) + +# Enable GPU support +if(LINUX AND BTOP_GPU) + target_compile_definitions(btop PRIVATE GPU_SUPPORT) + + 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 + set(_CMAKE_MODULE_PATH CMAKE_MODULE_PATH) + unset(CMAKE_MODULE_PATH) + + # NOTE: This might be problematic in the future if other sub projects depend on this or if + # btop starts producing libraries + # Build a static ROCm library + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + + add_subdirectory(lib/rocm_smi_lib EXCLUDE_FROM_ALL) + + add_library(ROCm INTERFACE) + # Export ROCm's properties to a target + target_compile_definitions(ROCm INTERFACE RSMI_STATIC) + target_include_directories(ROCm INTERFACE lib/rocm_smi_lib/include) + target_link_libraries(ROCm INTERFACE rocm_smi64) + + set(CMAKE_MODULE_PATH _CMAKE_MODULE_PATH) + + target_link_libraries(btop ROCm) + endif() +endif() + +if(BTOP_USE_MOLD) + target_link_options(btop PRIVATE -fuse-ld=mold) +endif() + +if(BTOP_STATIC) + target_compile_definitions(btop PRIVATE STATIC_BUILD) + target_link_options(btop PRIVATE -static LINKER:--fatal-warnings) +endif() + +# Other platform depdendent flags +if(APPLE) + target_link_libraries(btop + $ $ + ) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + # Avoid version mismatch for libstdc++ when a specific version of GCC is installed and not the + # default one since all use the default ones RPATH + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + string(REGEX MATCH "^[0-9]+" GCC_VERSION_MAJOR "${CMAKE_CXX_COMPILER_VERSION}") + set_target_properties(btop PROPERTIES + INSTALL_RPATH "/usr/local/lib/gcc${GCC_VERSION_MAJOR}" + BUILD_WITH_INSTALL_RPATH TRUE + ) + endif() + + find_package(devstat REQUIRED) + target_link_libraries(btop devstat::devstat) + if(BTOP_STATIC) + find_package(elf REQUIRED) + find_package(kvm REQUIRED) + target_link_libraries(btop elf::elf kvm::kvm) + endif() +endif() + +install(TARGETS btop RUNTIME) +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 1ff479e..9f4db2e 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,7 @@ else endif ifneq ($(QUIET),true) - override PRE := info info-quiet override QUIET := false -else - override PRE := info-quiet endif OLDCXX := $(CXXFLAGS) @@ -39,6 +36,20 @@ endif override PLATFORM_LC := $(shell echo $(PLATFORM) | tr '[:upper:]' '[:lower:]') +#? GPU Support +ifeq ($(PLATFORM_LC)$(ARCH),linuxx86_64) + ifneq ($(STATIC),true) + GPU_SUPPORT := true + endif +endif +ifneq ($(GPU_SUPPORT),true) + GPU_SUPPORT := false +endif + +ifeq ($(GPU_SUPPORT),true) + override ADDFLAGS += -DGPU_SUPPORT +endif + #? Compiler and Linker ifeq ($(shell $(CXX) --version | grep clang >/dev/null 2>&1; echo $$?),0) override CXX_IS_CLANG := true @@ -48,10 +59,18 @@ override CXX_VERSION_MAJOR := $(shell echo $(CXX_VERSION) | cut -d '.' -f 1) 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) - ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 16; echo $$?),0) + ifeq ($(shell $(CXX) --version | grep Apple >/dev/null 2>&1; echo $$?),0) + MIN_CLANG_VERSION := 15 + endif + ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt $(MIN_CLANG_VERSION); echo $$?),0) CLANG_WORKS := true endif endif @@ -214,24 +233,38 @@ endif P := %% -#? Default Make -all: $(PRE) directories btop +ifeq ($(VERBOSE),true) + # Doesn't work with `&>` + override SUPPRESS := > /dev/null 2> /dev/null +else + override SUPPRESS := +endif +#? Default Make +.ONESHELL: +all: | info rocm_smi info-quiet directories btop + +ifneq ($(QUIET),true) info: @printf " $(BANNER)\n" - @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" - @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" - @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n" - @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n" - @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n" - @printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n" - @printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n" - @printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n" - @printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDCXX)\n" - @printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDLD)\n" + @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" + @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" + @printf "\033[1;95mGPU_SUPPORT \033[1;94m:| \033[0m$(GPU_SUPPORT)\n" + @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n" + @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n" + @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n" + @printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n" + @printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n" + @printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n" + @printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDCXX)\n" + @printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDLD)\n" +else +info: + @true +endif -info-quiet: - @sleep 0.1 2>/dev/null || true + +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" help: @@ -258,11 +291,13 @@ directories: clean: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @rm -rf $(BUILDDIR) + @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) + @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" @@ -308,9 +343,35 @@ uninstall: #? Pull in dependency info for *existing* .o files -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) +#? Compile rocm_smi +ifeq ($(GPU_SUPPORT)$(RSMI_STATIC),truetrue) + ROCM_DIR ?= lib/rocm_smi_lib + ROCM_BUILD_DIR := $(ROCM_DIR)/build + ifeq ($(DEBUG),true) + BUILD_TYPE := Debug + else + BUILD_TYPE := Release + endif +.ONESHELL: +rocm_smi: + @printf "\n\033[1;92mBuilding ROCm SMI static library\033[37m...\033[0m\n" + @TSTAMP=$$(date +%s 2>/dev/null || echo "0") + @$(QUIET) || printf "\033[1;97mRunning CMake...\033[0m\n" + CXX=$(CXX) cmake -S $(ROCM_DIR) -B $(ROCM_BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DBUILD_SHARED_LIBS=OFF $(SUPPRESS) || { printf "\033[1;91mCMake failed, continuing build without statically linking ROCm SMI\033[37m...\033[0m\n"; exit 0; } + @$(QUIET) || printf "\n\033[1;97mBuilding and linking...\033[0m\n" + @cmake --build $(ROCM_BUILD_DIR) -j -t rocm_smi64 $(SUPPRESS) || { printf "\033[1;91mMake failed, continuing build without statically linking ROCm SMI\033[37m...\033[0m\n"; exit 0; } + @printf "\033[1;92m100$(P)\033[10D\033[5C-> \033[1;37m$(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a \033[1;93m(\033[1;97m$$(du -ah $(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a | cut -f1)iB\033[1;93m)\033[0m\n" + @printf "\033[1;92mROCm SMI build complete in \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n" + @$(eval override LDFLAGS += $(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a -DRSMI_STATIC) # TODO: this seems to execute every time, no matter if the compilation failed or succeeded + @$(eval override CXXFLAGS += -DRSMI_STATIC) +else +rocm_smi: + @true +endif + #? Link .ONESHELL: -btop: $(OBJECTS) | directories +btop: $(OBJECTS) | rocm_smi directories @sleep 0.2 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") @$(QUIET) || printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n" @@ -321,7 +382,7 @@ btop: $(OBJECTS) | directories #? Compile .ONESHELL: -$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | directories +$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories @sleep 0.3 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") @$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n" diff --git a/README.md b/README.md index 2eecb90..f220e86 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,33 @@ * [Compilation Linux](#compilation-linux) * [Compilation macOS](#compilation-macos-osx) * [Compilation FreeBSD](#compilation-freebsd) +* [GPU compatibility](#gpu-compatibility) * [Installing the snap](#installing-the-snap) * [Configurability](#configurability) * [License](#license) ## News +##### 25 November 2023 + +GPU monitoring added for Linux! + +Compile from git main to try it out. + +Use keys `5`, `6`, `7` and `0` to show/hide the gpu monitoring boxes. `5` = Gpu 1, `6` = Gpu 2, etc. + +Gpu stats/graphs can also be displayed in the "Cpu box" (not as verbose), see the cpu options menu for info and configuration. + +Note that the binaries provided on the release page (when released) and the continuous builds will not have gpu support enabled. + +Because the GPU support relies on loading of dynamic gpu libraries, gpu support will not work when also static linking. + +See [Compilation Linux](#compilation-linux) for more info on how to compile with gpu monitoring support. + +Many thanks to [@romner-set](https://github.com/romner-set) who wrote the vast majority of the implementation for GPU support. + +Big update with version bump to 1.3 coming soon. + ##### 28 August 2022 [![btop4win](https://github.com/aristocratos/btop4win/raw/master/Img/logo.png)](https://github.com/aristocratos/btop4win) @@ -305,16 +326,46 @@ Also needs a UTF8 locale and a font that covers: ## Compilation Linux - Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). + Requires at least GCC 10 or Clang 16. The makefile also needs GNU coreutils and `sed` (should already be installed on any modern distribution). - For a `cmake` based build alternative see the [fork](https://github.com/jan-guenter/btop/tree/main) by @jan-guenter + ### GPU compatibility + + Btop++ supports NVIDIA and AMD GPUs out of the box on Linux x86_64, provided you have the correct drivers and libraries. + + Compatibility with Intel GPUs using generic DRM calls is planned, as is compatibility for FreeBSD and macOS. + + Gpu support will not work when static linking glibc (or musl, etc.)! + + For x86_64 Linux the flag `GPU_SUPPORT` is automatically set to `true`, to manually disable gpu support set the flag to false, like: + + `make GPU_SUPPORT=false` (or `cmake -DBTOP_GPU=false` with CMake) + + * **NVIDIA** + + You must use an official NVIDIA driver, both the closed-source and [open-source](https://github.com/NVIDIA/open-gpu-kernel-modules) 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** + + 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. + + 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 + ``` + +
+ + +### With Make + 1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** - Use gcc-10 g++-10 if gcc-11 isn't available - ```bash sudo apt install coreutils sed git build-essential gcc-11 g++-11 ``` @@ -328,51 +379,51 @@ Also needs a UTF8 locale and a font that covers: 3. **Compile** - Append `VERBOSE=true` to display full compiler/linker commands. - - Append `STATIC=true` for static compilation. - - Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc. - - Append `QUIET=true` for less verbose output. - - Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag). - - Append `ARCH=` to manually set the target architecture. - If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system. - - Use `ADDFLAGS` variable for appending flags to both compiler and linker. - - For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. - - If `g++` is linked to an older version of gcc on your system specify the correct version by appending `CXX=g++-10` or `CXX=g++-11`. - ```bash make ``` + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `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 | + | `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 | + + 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** - Append `PREFIX=/target/dir` to set target, default: `/usr/local` - - Notice! Only use "sudo" when installing to a NON user owned directory. - ```bash sudo make install ``` + Append `PREFIX=/target/dir` to set target, default: `/usr/local` + + 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)** + ```bash + sudo make setuid + ``` + No need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems. 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 `root` - ```bash - sudo make setuid - ``` - * **Uninstall** ```bash @@ -397,16 +448,94 @@ Also needs a UTF8 locale and a font that covers: make help ``` +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires Clang / GCC, CMake, Ninja and Git + + For example, with Debian Bookworm: + + ```bash + sudo apt install cmake git g++ ninja-build + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + `````` + +3. **Compile** + + ```bash + # Configure + 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_STATIC=` | Enables static linking (OFF by default) | + | `-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_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) | + + 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 + ``` + +
+ ## Compilation macOS OSX - Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). + Requires at least GCC 10 or Clang 16. - GCC 12 needed for macOS Ventura. If you get linker errors on Ventura you'll need to upgrade your command line tools (Version 14.0) is bugged. + With GCC, version 12 (or better) is needed for macOS Ventura. If you get linker errors on Ventura you'll need to upgrade your command line tools (Version 14.0) is bugged. The makefile also needs GNU coreutils and `sed`. Install and use Homebrew or MacPorts package managers for easy dependency installation +
+ + +### With Make + + 1. **Install dependencies (example for Homebrew)** ```bash @@ -419,50 +548,49 @@ Also needs a UTF8 locale and a font that covers: git clone https://github.com/aristocratos/btop.git cd btop ``` - 3. **Compile** - Append `VERBOSE=true` to display full compiler/linker commands. - - Append `STATIC=true` for static compilation (only libgcc and libstdc++ will be static!). - - Append `QUIET=true` for less verbose output. - - Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag). - - Append `ARCH=` to manually set the target architecture. - If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system. - - Use `ADDFLAGS` variable for appending flags to both compiler and linker. - - For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. - ```bash gmake ``` + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `STATIC=true` | For static compilation (only libgcc and libstdc++) | + | `QUIET=true` | For less verbose output | + | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | Manualy set which compiler to use | + + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + 4. **Install** - Append `PREFIX=/target/dir` to set target, default: `/usr/local` - - Notice! Only use "sudo" when installing to a NON user owned directory. - ```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` - ```bash - sudo gmake setuid - ``` - * **Uninstall** ```bash @@ -487,12 +615,92 @@ Also needs a UTF8 locale and a font that covers: gmake help ``` +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires Clang, CMake, Ninja and Git + + ```bash + brew update --quiet + brew install cmake git llvm ninja + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + ``` + +3. **Compile** + + ```bash + # Configure + export LLVM_PREFIX="$(brew --prefix llvm)" + export CXX="$LLVM_PREFIX/bin/clang++" + export CPPFLAGS="-I$LLVM_PREFIX/include" + export LDFLAGS="-L$LLVM_PREFIX/lib -L$LLVM_PREFIX/lib/c++ -Wl,-rpath,$LLVM_PREFIX/lib/c++ -fuse-ld=$LLVM_PREFIX/bin/ld64.lld" + cmake -B build -G Ninja + # Build + cmake --build build + ``` + + _**Note:** btop uses lots of C++ 20 features, so it's necessary to be specific about the compiler and the standard library. If you get a compile with Apple-Clang or GCC, feel free to add the instructions here._ + + 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) | + | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | + + To force any specific 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 + ``` + +
+ ## Compilation FreeBSD - Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). + Requires at least GCC 10 or Clang 16. Note that GNU make (`gmake`) is required to compile on FreeBSD. +
+ + +### With gmake + + 1. **Install dependencies** ```bash @@ -508,47 +716,47 @@ Also needs a UTF8 locale and a font that covers: 3. **Compile** - Append `VERBOSE=true` to display full compiler/linker commands. - - Append `STATIC=true` for static compilation (only libgcc and libstdc++ will be static!). - - Append `QUIET=true` for less verbose output. - - Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag). - - Append `ARCH=` to manually set the target architecture. - If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system. - - Use `ADDFLAGS` variable for appending flags to both compiler and linker. - - For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. - ```bash gmake ``` + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `STATIC=true` | For static compilation (only libgcc and libstdc++) | + | `QUIET=true` | For less verbose output | + | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | Manualy set which compiler to use | + + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + 4. **Install** - Append `PREFIX=/target/dir` to set target, default: `/usr/local` - - Notice! Only use "sudo" when installing to a NON user owned directory. - ```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` - ```bash - sudo gmake setuid - ``` - * **Uninstall** ```bash @@ -573,6 +781,93 @@ Also needs a UTF8 locale and a font that covers: gmake help ``` +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires Clang / GCC, CMake, Ninja 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 + ``` + + FreeBSD 13: + ```bash + pkg install cmake gcc13 ninja + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + ``` + +3. **Compile** + + FreeBSD 14 and later: + ```bash + # Configure + cmake -B build -G Ninja + # Build + cmake --build build + ``` + + FreeBSD 13: + ```bash + # Configure + CXX=g++13 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_STATIC=` | Enables static linking (OFF by default) | + | `-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) | + | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | + + _**Note:** Static linking does not work with GCC._ + + 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) @@ -657,7 +952,7 @@ graph_symbol_net = "default" # Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". graph_symbol_proc = "default" -#* Manually set which boxes to show. Available values are "cpu mem net proc", separate values with whitespace. +#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace. shown_boxes = "proc cpu mem net" #* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs. diff --git a/cmake/Modules/Finddevstat.cmake b/cmake/Modules/Finddevstat.cmake new file mode 100644 index 0000000..694e613 --- /dev/null +++ b/cmake/Modules/Finddevstat.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Find devstat, the Device Statistics Library +# + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + find_path(devstat_INCLUDE_DIR NAMES devstat.h) + find_library(devstat_LIBRARY NAMES devstat) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(devstat REQUIRED_VARS devstat_LIBRARY devstat_INCLUDE_DIR) + + if(devstat_FOUND AND NOT TARGET devstat::devstat) + add_library(devstat::devstat UNKNOWN IMPORTED) + set_target_properties(devstat::devstat PROPERTIES + IMPORTED_LOCATION "${devstat_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${devstat_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(devstat_INCLUDE_DIR devstat_LIBRARY) +endif() + diff --git a/cmake/Modules/Findelf.cmake b/cmake/Modules/Findelf.cmake new file mode 100644 index 0000000..91e0beb --- /dev/null +++ b/cmake/Modules/Findelf.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Find libelf, the ELF Access Library +# + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + find_path(elf_INCLUDE_DIR NAMES libelf.h) + find_library(elf_LIBRARY NAMES elf) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(elf REQUIRED_VARS elf_LIBRARY elf_INCLUDE_DIR) + + if(elf_FOUND AND NOT TARGET elf::elf) + add_library(elf::elf UNKNOWN IMPORTED) + set_target_properties(elf::elf PROPERTIES + IMPORTED_LOCATION "${elf_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${elf_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(elf_INCLUDE_DIR elf_LIBRARY) +endif() + diff --git a/cmake/Modules/Findkvm.cmake b/cmake/Modules/Findkvm.cmake new file mode 100644 index 0000000..a0847de --- /dev/null +++ b/cmake/Modules/Findkvm.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Find libkvm, the Kernel Data Access Library +# + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + find_path(kvm_INCLUDE_DIR NAMES kvm.h) + find_library(kvm_LIBRARY NAMES kvm) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(kvm REQUIRED_VARS kvm_LIBRARY kvm_INCLUDE_DIR) + + if(kvm_FOUND AND NOT TARGET kvm::kvm) + add_library(kvm::kvm UNKNOWN IMPORTED) + set_target_properties(kvm::kvm PROPERTIES + IMPORTED_LOCATION "${kvm_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${kvm_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(kvm_INCLUDE_DIR kvm_LIBRARY) +endif() + 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/snap/snapcraft.yaml b/snap/snapcraft.yaml index 16978fe..f6b2729 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -37,6 +37,7 @@ apps: - network-observe - home - removable-media + - opengl parts: btop: @@ -47,7 +48,6 @@ parts: - PREFIX=/usr/local - STATIC=true - ADDFLAGS="-D SNAPPED" - build-packages: - coreutils - sed diff --git a/src/btop.cpp b/src/btop.cpp index 9a07486..48d0482 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -32,6 +32,7 @@ tab-size = 4 #include #include #include +#include #ifdef __APPLE__ #include #include @@ -75,7 +76,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "1.2.13"; + const string Version = "1.3.0"; int coreCount; string overlay; @@ -183,8 +184,11 @@ void term_resize(bool force) { if (force and refreshed) force = false; } else return; - - static const array all_boxes = {"cpu", "mem", "net", "proc"}; +#ifdef GPU_SUPPORT + static const array all_boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"}; +#else + static const array all_boxes = {"", "cpu", "mem", "net", "proc"}; +#endif Global::resized = true; if (Runner::active) Runner::stop(); Term::refresh(); @@ -222,10 +226,18 @@ void term_resize(bool force) { auto key = Input::get(); if (key == "q") clean_quit(0); - else if (is_in(key, "1", "2", "3", "4")) { - Config::current_preset = -1; - Config::toggle_box(all_boxes.at(std::stoi(key) - 1)); - boxes = Config::getS("shown_boxes"); + else if (key.size() == 1 and isint(key)) { + auto intKey = stoi(key); + #ifdef GPU_SUPPORT + if ((intKey == 0 and Gpu::gpu_names.size() >= 5) or (intKey >= 5 and std::cmp_greater_equal(Gpu::gpu_names.size(), intKey - 4))) { + #else + if (intKey > 0 and intKey < 5) { + #endif + auto box = all_boxes.at(intKey); + Config::current_preset = -1; + Config::toggle_box(box); + boxes = Config::getS("shown_boxes"); + } } } min_size = Term::get_min_size(boxes); @@ -258,6 +270,11 @@ void clean_quit(int sig) { #endif } +#ifdef GPU_SUPPORT + Gpu::Nvml::shutdown(); + Gpu::Rsmi::shutdown(); +#endif + Config::write(); if (Term::initialized) { @@ -388,7 +405,9 @@ namespace Runner { enum debug_actions { collect_begin, + collect_done, draw_begin, + draw_begin_only, draw_done }; @@ -398,7 +417,7 @@ namespace Runner { }; string debug_bg; - unordered_flat_map> debug_times; + std::unordered_map> debug_times; class MyNumPunct : public std::numpunct { @@ -424,6 +443,13 @@ namespace Runner { case collect_begin: debug_times[name].at(collect) = time_micros(); return; + case collect_done: + debug_times[name].at(collect) = time_micros() - debug_times[name].at(collect); + debug_times["total"].at(collect) += debug_times[name].at(collect); + return; + case draw_begin_only: + debug_times[name].at(draw) = time_micros(); + return; case draw_begin: debug_times[name].at(draw) = time_micros(); debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect); @@ -480,10 +506,14 @@ namespace Runner { //! DEBUG stats if (Global::debug) { - if (debug_bg.empty() or redraw) - Runner::debug_bg = Draw::createBox(2, 2, 33, 8, "", true, "μs"); - - + if (debug_bg.empty() or redraw) + Runner::debug_bg = Draw::createBox(2, 2, 33, + #ifdef GPU_SUPPORT + 9, + #else + 8, + #endif + "", true, "μs"); debug_times.clear(); debug_times["total"] = {0, 0}; @@ -493,6 +523,29 @@ namespace Runner { //* Run collection and draw functions for all boxes try { + #ifdef GPU_SUPPORT + //? GPU data collection + const bool gpu_in_cpu_panel = Gpu::gpu_names.size() > 0 and ( + Config::getS("cpu_graph_lower").starts_with("gpu-") or Config::getS("cpu_graph_upper").starts_with("gpu-") + or (Gpu::shown == 0 and Config::getS("show_gpu_info") != "Off") + ); + + vector gpu_panels = {}; + for (auto& box : conf.boxes) + if (box.starts_with("gpu")) + gpu_panels.push_back(box.back()-'0'); + + vector gpus; + if (gpu_in_cpu_panel or not gpu_panels.empty()) { + if (Global::debug) debug_timer("gpu", collect_begin); + gpus = Gpu::collect(conf.no_update); + if (Global::debug) debug_timer("gpu", collect_done); + } + auto& gpus_ref = gpus; + #else + vector gpus_ref{}; + #endif + //? CPU if (v_contains(conf.boxes, "cpu")) { try { @@ -512,7 +565,7 @@ namespace Runner { if (Global::debug) debug_timer("cpu", draw_begin); //? Draw box - if (not pause_output) output += Cpu::draw(cpu, conf.force_redraw, conf.no_update); + if (not pause_output) output += Cpu::draw(cpu, gpus_ref, conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("cpu", draw_done); } @@ -520,7 +573,24 @@ namespace Runner { throw std::runtime_error("Cpu:: -> " + string{e.what()}); } } + #ifdef GPU_SUPPORT + //? GPU + if (not gpu_panels.empty() and not gpus_ref.empty()) { + try { + if (Global::debug) debug_timer("gpu", draw_begin_only); + //? Draw box + if (not pause_output) + for (unsigned long i = 0; i < gpu_panels.size(); ++i) + output += Gpu::draw(gpus_ref[gpu_panels[i]], i, conf.force_redraw, conf.no_update); + + if (Global::debug) debug_timer("gpu", draw_done); + } + catch (const std::exception& e) { + throw std::runtime_error("Gpu:: -> " + string{e.what()}); + } + } + #endif //? MEM if (v_contains(conf.boxes, "mem")) { try { @@ -580,6 +650,7 @@ namespace Runner { throw std::runtime_error("Proc:: -> " + string{e.what()}); } } + } catch (const std::exception& e) { Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()}; @@ -610,8 +681,9 @@ namespace Runner { "{mv3}{hiFg}2 {mainFg}| Show MEM box" "{mv4}{hiFg}3 {mainFg}| Show NET box" "{mv5}{hiFg}4 {mainFg}| Show PROC box" - "{mv6}{hiFg}esc {mainFg}| Show menu" - "{mv7}{hiFg}q {mainFg}| Quit", + "{mv6}{hiFg}5-0 {mainFg}| Show GPU boxes" + "{mv7}{hiFg}esc {mainFg}| Show menu" + "{mv8}{hiFg}q {mainFg}| Quit", "banner"_a = Draw::banner_gen(y, 0, true), "titleFg"_a = Theme::c("title"), "b"_a = Fx::b, "hiFg"_a = Theme::c("hi_fg"), "mainFg"_a = Theme::c("main_fg"), "mv1"_a = Mv::to(y+6, x), @@ -620,7 +692,8 @@ namespace Runner { "mv4"_a = Mv::to(y+10, x), "mv5"_a = Mv::to(y+11, x), "mv6"_a = Mv::to(y+12, x-2), - "mv7"_a = Mv::to(y+13, x) + "mv7"_a = Mv::to(y+13, x-2), + "mv8"_a = Mv::to(y+14, x) ); } output += empty_bg; @@ -634,7 +707,11 @@ namespace Runner { "post"_a = Theme::c("main_fg") + Fx::ub ); static auto loc = std::locale(std::locale::classic(), new MyNumPunct); + #ifdef GPU_SUPPORT + for (const string name : {"cpu", "mem", "net", "proc", "gpu", "total"}) { + #else for (const string name : {"cpu", "mem", "net", "proc", "total"}) { + #endif if (not debug_times.contains(name)) debug_times[name] = {0,0}; const auto& [time_collect, time_draw] = debug_times.at(name); if (name == "total") output += Fx::b; diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6010189..6ddfd43 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -21,6 +21,7 @@ tab-size = 4 #include #include #include +#include #include @@ -73,14 +74,16 @@ namespace Config { "#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view."}, {"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, - +#ifdef GPU_SUPPORT + {"graph_symbol_gpu", "# Graph symbol to use for graphs in gpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, +#endif {"graph_symbol_mem", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, {"graph_symbol_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, {"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, - {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."}, + {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\" and \"gpu0\" through \"gpu5\", separate values with whitespace."}, {"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."}, @@ -107,12 +110,16 @@ namespace Config { {"proc_filter_kernel", "#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop)."}, + {"proc_aggregate", "#* In tree-view, always accumulate child process resources in the parent process."}, + {"cpu_graph_upper", "#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n" "#* Select from a list of detected attributes from the options menu."}, {"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n" "#* Select from a list of detected attributes from the options menu."}, - + #ifdef GPU_SUPPORT + {"show_gpu_info", "#* If gpu info should be shown in the cpu box. Available values = \"Auto\", \"On\" and \"Off\"."}, + #endif {"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."}, {"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."}, @@ -192,21 +199,36 @@ namespace Config { {"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."}, {"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" - "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."} + "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}, + #ifdef GPU_SUPPORT + + {"nvml_measure_pcie_speeds", + "#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards."}, + + {"gpu_mirror_graph", "#* Horizontally mirror the GPU graph."}, + + {"custom_gpu_name0", "#* Custom gpu0 model name, empty string to disable."}, + {"custom_gpu_name1", "#* Custom gpu1 model name, empty string to disable."}, + {"custom_gpu_name2", "#* Custom gpu2 model name, empty string to disable."}, + {"custom_gpu_name3", "#* Custom gpu3 model name, empty string to disable."}, + {"custom_gpu_name4", "#* Custom gpu4 model name, empty string to disable."}, + {"custom_gpu_name5", "#* Custom gpu5 model name, empty string to disable."}, + #endif }; - unordered_flat_map strings = { + std::unordered_map strings = { {"color_theme", "Default"}, {"shown_boxes", "cpu mem net proc"}, {"graph_symbol", "braille"}, {"presets", "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"}, {"graph_symbol_cpu", "default"}, + {"graph_symbol_gpu", "default"}, {"graph_symbol_mem", "default"}, {"graph_symbol_net", "default"}, {"graph_symbol_proc", "default"}, {"proc_sorting", "cpu lazy"}, - {"cpu_graph_upper", "total"}, - {"cpu_graph_lower", "total"}, + {"cpu_graph_upper", "Auto"}, + {"cpu_graph_lower", "Auto"}, {"cpu_sensor", "Auto"}, {"selected_battery", "Auto"}, {"cpu_core_map", ""}, @@ -220,10 +242,19 @@ namespace Config { {"proc_filter", ""}, {"proc_command", ""}, {"selected_name", ""}, + #ifdef GPU_SUPPORT + {"custom_gpu_name0", ""}, + {"custom_gpu_name1", ""}, + {"custom_gpu_name2", ""}, + {"custom_gpu_name3", ""}, + {"custom_gpu_name4", ""}, + {"custom_gpu_name5", ""}, + {"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}, @@ -268,10 +299,15 @@ namespace Config { {"lowcolor", false}, {"show_detailed", false}, {"proc_filtering", false}, + {"proc_aggregate", false}, + #ifdef GPU_SUPPORT + {"nvml_measure_pcie_speeds", true}, + {"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}, @@ -282,7 +318,7 @@ namespace Config { {"proc_selected", 0}, {"proc_last_selected", 0}, }; - unordered_flat_map intsTmp; + std::unordered_map intsTmp; bool _locked(const std::string_view name) { atomic_wait(writelock, true); @@ -318,7 +354,7 @@ namespace Config { validError = "Malformatted preset in config value presets!"; return false; } - if (not is_in(vals.at(0), "cpu", "mem", "net", "proc")) { + if (not is_in(vals.at(0), "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4", "gpu5")) { validError = "Invalid box name in config value presets!"; return false; } @@ -414,6 +450,11 @@ namespace Config { else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) validError = "Invalid box name(s) in shown_boxes!"; + #ifdef GPU_SUPPORT + else if (name == "show_gpu_info" and not v_contains(show_gpu_values, value)) + validError = "Invalid value for show_gpu_info: " + value; + #endif + else if (name == "presets" and not presetsValid(value)) return false; @@ -516,6 +557,13 @@ namespace Config { auto new_boxes = ssplit(boxes); for (auto& box : new_boxes) { 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; + if (std::cmp_greater(gpu_num, Gpu::gpu_names.size())) return false; + } + #endif } current_boxes = std::move(new_boxes); return true; diff --git a/src/btop_config.hpp b/src/btop_config.hpp index e5f4ac1..2b586af 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -22,11 +22,10 @@ tab-size = 4 #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,18 +33,25 @@ 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" }; - const vector valid_boxes = { "cpu", "mem", "net", "proc" }; + const vector valid_boxes = { + "cpu", "mem", "net", "proc" +#ifdef GPU_SUPPORT + ,"gpu0", "gpu1", "gpu2", "gpu3", "gpu4", "gpu5" +#endif + }; const vector temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" }; - +#ifdef GPU_SUPPORT + const vector show_gpu_values = { "Auto", "On", "Off" }; +#endif extern vector current_boxes; extern vector preset_list; extern vector available_batteries; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index a4cbb95..0ceaa2f 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -20,6 +20,9 @@ tab-size = 4 #include #include #include +#include +#include +#include #include "btop_draw.hpp" #include "btop_config.hpp" @@ -29,6 +32,7 @@ tab-size = 4 #include "btop_input.hpp" #include "btop_menu.hpp" + using std::array; using std::clamp; using std::cmp_equal; @@ -51,7 +55,7 @@ namespace Symbols { const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; - const unordered_flat_map> graph_symbols = { + const std::unordered_map> graph_symbols = { { "braille_up", { " ", "⢀", "⢠", "⢰", "⢸", "⡀", "⣀", "⣠", "⣰", "⣸", @@ -297,7 +301,7 @@ 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", ""} @@ -508,44 +512,69 @@ namespace Cpu { int x = 1, y = 1, width = 20, height; int b_columns, b_column_size; int b_x, b_y, b_width, b_height; - int graph_up_height; long unsigned int lavg_str_len = 0; + int graph_up_height, graph_low_height; + int graph_up_width, graph_low_width; + int gpu_meter_width; bool shown = true, redraw = true, mid_line = false; string box; - Draw::Graph graph_upper; - Draw::Graph graph_lower; + vector graphs_upper; + vector graphs_lower; Draw::Meter cpu_meter; + vector gpu_meters; vector core_graphs; vector temp_graphs; + vector gpu_temp_graphs; + vector gpu_mem_graphs; - string draw(const cpu_info& cpu, bool force_redraw, bool data_same) { + string draw(const cpu_info& cpu, const vector& gpus, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; if (force_redraw) redraw = true; bool show_temps = (Config::getB("check_temp") and got_sensors); auto single_graph = Config::getB("cpu_single_graph"); bool hide_cores = show_temps and (cpu_temp_only or not Config::getB("show_coretemp")); const int extra_width = (hide_cores ? max(6, 6 * b_column_size) : 0); - auto& graph_up_field = Config::getS("cpu_graph_upper"); - auto& graph_lo_field = Config::getS("cpu_graph_lower"); + #ifdef GPU_SUPPORT + const auto& show_gpu_info = Config::getS("show_gpu_info"); + const bool gpu_always = show_gpu_info == "On"; + bool show_gpu = (gpus.size() > 0 and (gpu_always or (show_gpu_info == "Auto" and Gpu::shown == 0))); + #else + (void)gpus; + #endif + auto graph_up_field = Config::getS("cpu_graph_upper"); + if (graph_up_field == "Auto" or not v_contains(Cpu::available_fields, graph_up_field)) + graph_up_field = "total"; + auto graph_lo_field = Config::getS("cpu_graph_lower"); + if (graph_lo_field == "Auto" or not v_contains(Cpu::available_fields, graph_lo_field)) { + #ifdef GPU_SUPPORT + graph_lo_field = show_gpu ? "gpu-totals" : graph_up_field; + #else + graph_lo_field = graph_up_field; + #endif + } auto tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_cpu")); auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto& temp_scale = Config::getS("temp_scale"); auto cpu_bottom = Config::getB("cpu_bottom"); + 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 (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); //* Redraw elements not needed to be updated every cycle if (redraw) { mid_line = (not single_graph and graph_up_field != graph_lo_field); - graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0 ? 1 : 0)); - const int graph_low_height = height - 2 - graph_up_height - (mid_line ? 1 : 0); + graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0)); + graph_low_height = height - 2 - graph_up_height - mid_line; const int button_y = cpu_bottom ? y + height - 1 : y; out += box; @@ -562,17 +591,91 @@ namespace Cpu { Input::mouse_mappings["+"] = {button_y, x + width - 5, 1, 2}; //? Graphs & meters - graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", cpu.cpu_percent.at(graph_up_field), graph_symbol, false, true}; + const int graph_default_width = x + width - b_width - 3; + + auto init_graphs = [&](vector& graphs, const int graph_height, int& graph_width, const string& graph_field, bool invert) { + #ifdef GPU_SUPPORT + if (graph_field.starts_with("gpu")) { + if (graph_field.find("totals") != string::npos) { + graphs.resize(gpus.size()); + gpu_temp_graphs.resize(gpus.size()); + gpu_mem_graphs.resize(gpus.size()); + gpu_meters.resize(gpus.size()); + graph_width = graph_default_width/(int)gpus.size() - (int)gpus.size() + 1 + graph_default_width%gpus.size(); + for (unsigned long i = 0;;) { + auto& gpu = gpus[i]; auto& graph = graphs[i]; + + //? GPU graphs/meters + 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", 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" }; + } + + bool utilization_support = gpu.supported_functions.gpu_utilization; + if (++i < gpus.size()) { + if (utilization_support) + 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", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true + }; + break; + } + } + } else { + graphs.resize(1); + graph_width = graph_default_width; + 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()); + for (unsigned long i = 0; i < gpus.size(); ++i) { + 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", 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" }; + } + } + } + } else { + #endif + graphs.resize(1); + graph_width = graph_default_width; + 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()); + gpu_mem_graphs.resize(gpus.size()); + gpu_meters.resize(gpus.size()); + for (unsigned long i = 0; i < gpus.size(); ++i) { + 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", 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" }; + } + } + } + } + #endif + }; + + init_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field, false); + if (not single_graph) + init_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field, Config::getB("cpu_invert_lower")); + cpu_meter = Draw::Meter{b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11), "cpu"}; - if (not single_graph) { - graph_lower = Draw::Graph{ - x + width - b_width - 3, - graph_low_height, "cpu", - cpu.cpu_percent.at(graph_lo_field), - graph_symbol, - Config::getB("cpu_invert_lower"), true - }; - } if (mid_line) { out += Mv::to(y + graph_up_height + 1, x) + Fx::ub + Theme::c("cpu_box") + Symbols::div_left + Theme::c("div_line") @@ -590,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); } } } @@ -605,7 +708,7 @@ namespace Cpu { static long old_seconds{}; // 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", "■"}, @@ -640,10 +743,39 @@ namespace Cpu { } try { - //? Cpu graphs - out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(cpu.cpu_percent.at(graph_up_field), (data_same or redraw)); - if (not single_graph) - out += Mv::to( y + graph_up_height + 1 + (mid_line ? 1 : 0), x + 1) + graph_lower(cpu.cpu_percent.at(graph_lo_field), (data_same or redraw)); + //? Cpu/Gpu graphs + out += Fx::ub + Mv::to(y + 1, x + 1); + auto draw_graphs = [&](vector& graphs, const int graph_height, const int graph_width, const string& graph_field) { + #ifdef GPU_SUPPORT + if (graph_field.starts_with("gpu")) + if (graph_field.find("totals") != string::npos) + for (unsigned long i = 0;;) { + 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 + + Mv::d(graph_height/2) + Mv::r(graph_width - 1 - (graph_width > 5)*4 - i_str.size()); + } + + if (++i < graphs.size()) + out += Theme::c("div_line") + (Symbols::v_line + Mv::l(1) + Mv::u(1))*graph_height + Mv::r(1) + Mv::d(1); + else break; + } + else + 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](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw)); + }; + + draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); + if (not single_graph) { + out += Mv::to(y + graph_up_height + 1 + mid_line, x + 1); + draw_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field); + } //? Uptime if (Config::getB("show_uptime")) { @@ -661,14 +793,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; @@ -679,21 +811,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; } @@ -731,15 +864,231 @@ namespace Cpu { } else { lavg_str_len = lavg_str.length(); } - out += Mv::to(b_y + b_height - 2, b_x + cx + 1) + Theme::c("main_fg") + lavg_str; + #ifdef GPU_SUPPORT + cy = b_height - 2 - (show_gpu ? (gpus.size() - (gpu_always ? 0 : Gpu::shown)) : 0); + #else + cy = b_height - 2; + #endif + out += Mv::to(b_y + cy, b_x + cx + 1) + Theme::c("main_fg") + lavg_str; } + #ifdef GPU_SUPPORT + //? Gpu brief info + if (show_gpu) { + for (unsigned long i = 0; i < gpus.size(); ++i) { + if (gpu_always or not v_contains(Gpu::shown_panels, i)) { + out += Mv::to(b_y + ++cy, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU"; + if (show_temps and gpus[i].supported_functions.temp_info and b_width < 34) { + const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale); + if (temp < 100) out += " "; + } + 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](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()); + out += (meter.size() > 1 ? " " : "") + meter + + 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(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); + else out += Mv::r(5); + } else out += Mv::r(17); + if (show_temps and gpus[i].supported_functions.temp_info) { + const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale); + if (b_width > 38) + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll)) + + gpu_temp_graphs[i](gpus[i].temp, data_same or redraw); + else out += Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll)); + out += rjust(to_string(temp), 3 + (b_width >= 34 or temp > 99)) + Theme::c("main_fg") + unit; + } + } + if (cy < b_height - 1) break; + } + } + #endif + redraw = false; return out + Fx::reset; } } +#ifdef GPU_SUPPORT +namespace Gpu { + int width_p = 100, height_p = 32; + int min_width = 41, min_height = 11; + int width = 41, height; + vector x_vec = {}, y_vec = {}, b_height_vec = {}; + int b_width; + vector b_x_vec = {}, b_y_vec = {}; + vector redraw = {}; + int shown = 0; + vector shown_panels = {}; + int graph_up_height; + vector graph_upper_vec = {}, graph_lower_vec = {}; + vector temp_graph_vec = {}; + vector mem_used_graph_vec = {}, mem_util_graph_vec = {}; + vector gpu_meter_vec = {}; + vector pwr_meter_vec = {}; + vector box = {}; + + string draw(const gpu_info& gpu, unsigned long index, bool force_redraw, bool data_same) { + if (Runner::stopping) return ""; + + auto& b_x = b_x_vec[index]; + auto& b_y = b_y_vec[index]; + auto& x = x_vec[index]; + auto& y = y_vec[index]; + + auto& graph_upper = graph_upper_vec[index]; + auto& graph_lower = graph_lower_vec[index]; + auto& temp_graph = temp_graph_vec[index]; + auto& mem_used_graph = mem_used_graph_vec[index]; + auto& mem_util_graph = mem_util_graph_vec[index]; + auto& gpu_meter = gpu_meter_vec[index]; + auto& pwr_meter = pwr_meter_vec[index]; + + if (force_redraw) redraw[index] = true; + bool show_temps = gpu.supported_functions.temp_info and (Config::getB("check_temp")); + auto tty_mode = Config::getB("tty_mode"); + auto& temp_scale = Config::getS("temp_scale"); + auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_gpu")); + auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); + auto single_graph = !Config::getB("gpu_mirror_graph"); + string out; + out.reserve(width * height); + + //* Redraw elements not needed to be updated every cycle + if (redraw[index]) { + graph_up_height = single_graph ? height - 2 : ceil((double)(height - 2) / 2); + const int graph_low_height = height - 2 - graph_up_height; + out += box[index]; + + if (gpu.supported_functions.gpu_utilization) { + 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", + safeVal(gpu.gpu_percent, "gpu-totals"s), + graph_symbol, + Config::getB("cpu_invert_lower"), true + }; + } + gpu_meter = Draw::Meter{b_width - (show_temps ? 24 : 11), "cpu"}; + } + if (gpu.supported_functions.temp_info) + temp_graph = Draw::Graph{6, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23}; + if (gpu.supported_functions.pwr_usage) + pwr_meter = Draw::Meter{b_width - 24, "cached"}; + 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", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol}; + } + + + //* General GPU info + + //? Gpu graph, meter & clock speed + if (gpu.supported_functions.gpu_utilization) { + 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(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(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) { + const auto [temp, unit] = celsius_to(gpu.temp.back(), temp_scale); + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpu.temp.back() * 100 / gpu.temp_max, 0ll, 100ll)) + + temp_graph(gpu.temp, data_same or redraw[index]); + out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; + } + out += Theme::c("div_line") + Symbols::v_line; + } + + if (gpu.supported_functions.gpu_clock) { + string clock_speed_string = to_string(gpu.gpu_clock_speed); + out += Mv::to(b_y, b_x + b_width - 12) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; + } + + //? 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(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(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state); + } + + if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) { + out += Mv::to(b_y + 3, b_x); + if (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) { + string used_memory_string = floating_humanizer(gpu.mem_used); + + auto offset = (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) + * (1 + 2*(gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) + 2*gpu.supported_functions.mem_utilization); + + //? Used graph, memory section header, total vram + out += Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Fx::b + Theme::c("title") + "vram" + Theme::c("div_line") + Fx::ub + Symbols::title_right + + 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(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(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%'; + + //? Memory utilization + if (gpu.supported_functions.mem_utilization) + out += Mv::l(b_width/2+6) + Mv::d(1) + Theme::c("div_line") + Symbols::div_left+Symbols::h_line + Theme::c("title") + "Utilization:" + Theme::c("div_line") + Symbols::h_line*(b_width/2-14) + Symbols::div_right + + Mv::l(b_width/2) + Mv::d(1) + mem_util_graph(gpu.mem_utilization_percent, (data_same or redraw[index])) + + Mv::l(b_width/2-1) + Mv::u(1) + rjust(to_string(gpu.mem_utilization_percent.back()), 3) + '%'; + + //? Memory clock speed + if (gpu.supported_functions.mem_clock) { + string clock_speed_string = to_string(gpu.mem_clock_speed); + out += Mv::to(b_y + 3, b_x + b_width/2 - 11) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; + } + } else { + out += Theme::c("main_fg") + Mv::r(1); + if (gpu.supported_functions.mem_total) + out += "VRAM total:" + rjust(floating_humanizer(gpu.mem_total), b_width/(1 + gpu.supported_functions.mem_clock)-14); + else out += "VRAM usage:" + rjust(floating_humanizer(gpu.mem_used), b_width/(1 + gpu.supported_functions.mem_clock)-14); + + if (gpu.supported_functions.mem_clock) + out += " VRAM clock:" + rjust(to_string(gpu.mem_clock_speed) + " Mhz", b_width/2-13); + } + } + + //? Processes section header + //out += Mv::to(b_y+8, b_x) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Theme::c("main_fg") + Fx::b + "gpu-proc" + Fx::ub + Theme::c("div_line") + // + Symbols::title_right + Symbols::h_line*(b_width/2-12) + Symbols::div_down + Symbols::h_line*(b_width/2-2) + Symbols::div_right; + + //? PCIe link throughput + if (gpu.supported_functions.pcie_txrx and Config::getB("nvml_measure_pcie_speeds")) { + string tx_string = floating_humanizer(gpu.pcie_tx, 0, 1, 0, 1); + string rx_string = floating_humanizer(gpu.pcie_rx, 0, 1, 0, 1); + out += Mv::to(b_y + b_height_vec[index] - 1, b_x+2) + Theme::c("div_line") + + Symbols::title_left_down + Theme::c("title") + Fx::b + "TX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2-9-tx_string.size()) + + Symbols::title_left_down + Theme::c("title") + Fx::b + tx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used ? Symbols::div_down : Symbols::h_line) + + Symbols::title_left_down + Theme::c("title") + Fx::b + "RX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2+b_width%2-9-rx_string.size()) + + Symbols::title_left_down + Theme::c("title") + Fx::b + rx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::round_right_down; + } + + redraw[index] = false; + return out + Fx::reset; + } + +} +#endif + namespace Mem { int width_p = 45, height_p = 36; int min_width = 36, min_height = 10; @@ -749,11 +1098,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 ""; @@ -785,14 +1134,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)}; } @@ -801,7 +1150,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)); @@ -883,7 +1232,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"; @@ -892,13 +1241,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 { @@ -921,7 +1273,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()) @@ -930,9 +1282,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) @@ -957,7 +1312,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) : ""); @@ -981,7 +1337,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++; @@ -1006,7 +1362,7 @@ namespace Net { int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; 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) { @@ -1026,15 +1382,15 @@ namespace Net { 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 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", @@ -1048,7 +1404,7 @@ namespace Net { 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' + + 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}; @@ -1072,13 +1428,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); @@ -1106,9 +1462,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; @@ -1443,11 +1799,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; @@ -1529,7 +1885,17 @@ namespace Proc { else mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); mem_str += '%'; } - out += (thread_size > 0 ? t_color + rjust(to_string(min(p.threads, (size_t)9999)), thread_size) + ' ' + end : "" ) + + // Shorten process thread representation when larger than 5 digits: 10000 -> 10K ... + const std::string proc_threads_string = [&] { + if (p.threads > 9999) { + return std::to_string(p.threads / 1000) + 'K'; + } else { + return std::to_string(p.threads); + } + }(); + + out += (thread_size > 0 ? t_color + rjust(proc_threads_string, thread_size) + ' ' + end : "" ) + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") @@ -1545,8 +1911,11 @@ namespace Proc { if (numpids > select_max) { const int scroll_pos = clamp((int)round((double)start * select_max / (numpids - select_max)), 0, height - 5); out += Mv::to(y + 1, x + width - 2) + Fx::b + Theme::c("main_fg") + Symbols::up - + Mv::to(y + height - 2, x + width - 2) + Symbols::down - + Mv::to(y + 2 + scroll_pos, x + width - 2) + "█"; + + Mv::to(y + height - 2, x + width - 2) + Symbols::down; + + for (int i = y + 2; i < y + height - 2; i++) { + out += Mv::to(i, x + width - 2) + ((i == y + 2 + scroll_pos) ? "█" : " "); + } } //? Current selection and number of processes @@ -1558,25 +1927,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) { @@ -1599,6 +1961,7 @@ namespace Draw { auto proc_left = Config::getB("proc_left"); Cpu::box.clear(); + Mem::box.clear(); Net::box.clear(); Proc::box.clear(); @@ -1619,6 +1982,24 @@ namespace Draw { Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = true; Cpu::shown = s_contains(boxes, "cpu"); + #ifdef GPU_SUPPORT + Gpu::box.clear(); + Gpu::width = 0; + Gpu::shown_panels.clear(); + if (not Gpu::gpu_names.empty()) { + std::istringstream iss(boxes, std::istringstream::in); + string current; + while (iss >> current) + if ( current == "gpu0" + or current == "gpu1" + or current == "gpu2" + or current == "gpu3" + or current == "gpu4" + or current == "gpu5" + ) Gpu::shown_panels.push_back(current.back()-'0'); + } + Gpu::shown = Gpu::shown_panels.size(); + #endif Mem::shown = s_contains(boxes, "mem"); Net::shown = s_contains(boxes, "net"); Proc::shown = s_contains(boxes, "proc"); @@ -1626,13 +2007,35 @@ namespace Draw { //* Calculate and draw cpu box outlines if (Cpu::shown) { using namespace Cpu; - bool show_temp = (Config::getB("check_temp") and got_sensors); + #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) + ); + 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; + #endif + const bool show_temp = (Config::getB("check_temp") and got_sensors); width = round((double)Term::width * width_p / 100); + #ifdef GPU_SUPPORT + if (Gpu::shown != 0 and not (Mem::shown or Net::shown or Proc::shown)) { + height = Term::height - Gpu::min_height*Gpu::shown - gpus_height_offset; + } else { + height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p/(Gpu::shown+1) + (Gpu::shown != 0)*5) / 100)); + } + if (height <= Term::height-gpus_height_offset) height += gpus_height_offset; + if (height - gpus_extra_height < 7) gpus_extra_height = height - 7; + #else height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p) / 100)); + #endif x = 1; y = cpu_bottom ? Term::height - height + 1 : 1; + #ifdef GPU_SUPPORT + b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - gpus_extra_height - 5))); + #else b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5))); + #endif if (b_columns * (21 + 12 * show_temp) < width - (width / 3)) { b_column_size = 2; b_width = (21 + 12 * show_temp) * b_columns - (b_columns - 1); @@ -1650,7 +2053,11 @@ namespace Draw { } if (b_column_size == 0) b_width = (8 + 6 * show_temp) * b_columns + 1; + #ifdef GPU_SUPPORT + b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4 + gpus_extra_height); + #else b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4); + #endif b_x = x + width - b_width - 1; b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1; @@ -1658,10 +2065,61 @@ namespace Draw { box = createBox(x, y, width, height, Theme::c("cpu_box"), true, (cpu_bottom ? "" : "cpu"), (cpu_bottom ? "cpu" : ""), 1); auto& custom = Config::getS("custom_cpu_name"); - const string cpu_title = uresize((custom.empty() ? Cpu::cpuName : custom) , b_width - 14); + static const bool hasCpuHz = not Cpu::get_cpuHz().empty(); + const string cpu_title = uresize( + (custom.empty() ? Cpu::cpuName : custom), + b_width - (Config::getB("show_cpu_freq") and hasCpuHz ? 14 : 4) + ); box += createBox(b_x, b_y, b_width, b_height, "", false, cpu_title); } + #ifdef GPU_SUPPORT + //* Calculate and draw gpu box outlines + if (Gpu::shown != 0) { + using namespace Gpu; + x_vec.resize(shown); y_vec.resize(shown); + b_x_vec.resize(shown); b_y_vec.resize(shown); + b_height_vec.resize(shown); + box.resize(shown); + graph_upper_vec.resize(shown); graph_lower_vec.resize(shown); + temp_graph_vec.resize(shown); + mem_used_graph_vec.resize(shown); mem_util_graph_vec.resize(shown); + gpu_meter_vec.resize(shown); + pwr_meter_vec.resize(shown); + redraw.resize(shown); + for (auto i = 0; i < shown; ++i) { + redraw[i] = true; + + width = Term::width; + if (Cpu::shown) + if (not (Mem::shown or Net::shown or Proc::shown)) + height = min_height; + else height = Cpu::height; + else + if (not (Mem::shown or Net::shown or Proc::shown)) + height = Term::height/Gpu::shown + (i == 0)*(Term::height%Gpu::shown); + else + height = max(min_height, (int)ceil((double)Term::height * height_p/Gpu::shown / 100)); + + height += (height+Cpu::height == Term::height-1); + x_vec[i] = 1; y_vec[i] = 1 + i*height + (not Config::getB("cpu_bottom"))*Cpu::shown*Cpu::height; + box[i] = createBox(x_vec[i], y_vec[i], width, height, Theme::c("cpu_box"), true, std::string("gpu") + (char)(shown_panels[i]+'0'), "", (shown_panels[i]+5)%10); // TODO gpu_box + + b_height_vec[i] = 2 + gpu_b_height_offsets[shown_panels[i]]; + b_width = clamp(width/2, min_width, 64); + + //? Main statistics box + b_x_vec[i] = x_vec[i] + width - b_width - 1; + b_y_vec[i] = y_vec[i] + ceil((double)(height - 2) / 2) - ceil((double)(b_height_vec[i]) / 2) + 1; + + string name = Config::getS(std::string("custom_gpu_name") + (char)(shown_panels[i]+'0')); + if (name.empty()) name = gpu_names[shown_panels[i]]; + + box[i] += createBox(b_x_vec[i], b_y_vec[i], b_width, b_height_vec[i], "", false, name.substr(0, b_width-5)); + } + } + #endif + //* Calculate and draw mem box outlines if (Mem::shown) { using namespace Mem; @@ -1670,13 +2128,22 @@ namespace Draw { auto mem_graphs = Config::getB("mem_graphs"); width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); + #ifdef GPU_SUPPORT + height = ceil((double)Term::height * (100 - Net::height_p * Net::shown*4 / ((Gpu::shown != 0 and Cpu::shown) + 4)) / 100) - Cpu::height - Gpu::height*Gpu::shown; + #else height = ceil((double)Term::height * (100 - Cpu::height_p * Cpu::shown - Net::height_p * Net::shown) / 100) + 1; - if (height + Cpu::height > Term::height) height = Term::height - Cpu::height; + #endif x = (proc_left and Proc::shown) ? Term::width - width + 1: 1; if (mem_below_net and Net::shown) + #ifdef GPU_SUPPORT + y = Term::height - height + 1 - (cpu_bottom ? Cpu::height + Gpu::height*Gpu::shown : 0); + else + y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; + #else y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); else y = cpu_bottom ? 1 : Cpu::height + 1; + #endif if (show_disks) { mem_width = ceil((double)(width - 3) / 2); @@ -1725,10 +2192,18 @@ namespace Draw { if (Net::shown) { using namespace Net; width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); + #ifdef GPU_SUPPORT + height = Term::height - Cpu::height - Gpu::height*Gpu::shown - Mem::height; + #else height = Term::height - Cpu::height - Mem::height; + #endif x = (proc_left and Proc::shown) ? Term::width - width + 1 : 1; if (mem_below_net and Mem::shown) + #ifdef GPU_SUPPORT + y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; + #else y = cpu_bottom ? 1 : Cpu::height + 1; + #endif else y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); @@ -1747,9 +2222,17 @@ namespace Draw { if (Proc::shown) { using namespace Proc; width = Term::width - (Mem::shown ? Mem::width : (Net::shown ? Net::width : 0)); + #ifdef GPU_SUPPORT + height = Term::height - Cpu::height - Gpu::height*Gpu::shown; + #else height = Term::height - Cpu::height; + #endif x = proc_left ? 1 : Term::width - width + 1; + #ifdef GPU_SUPPORT + y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; + #else y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + 1; + #endif select_max = height - 3; box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4); } diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index af57ed8..b049a57 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; @@ -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 ebdee3b..fb9e46d 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -22,6 +22,15 @@ tab-size = 4 #include #include #include +#include + +#include "btop_input.hpp" +#include "btop_tools.hpp" +#include "btop_config.hpp" +#include "btop_shared.hpp" +#include "btop_menu.hpp" +#include "btop_draw.hpp" + #include "btop_input.hpp" #include "btop_tools.hpp" @@ -40,7 +49,7 @@ 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"}, {"\n", "enter"}, {" ", "space"}, @@ -55,9 +64,13 @@ namespace Input { {"[C", "right"}, {"OC", "right"}, {"[2~", "insert"}, + {"[4h", "insert"}, {"[3~", "delete"}, + {"[P", "delete"}, {"[H", "home"}, + {"[1~", "home"}, {"[F", "end"}, + {"[4~", "end"}, {"[5~", "page_up"}, {"[6~", "page_down"}, {"\t", "tab"}, @@ -79,7 +92,7 @@ namespace Input { std::atomic interrupt (false); std::atomic polling (false); array mouse_pos; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; deque history(50, ""); string old_filter; @@ -260,11 +273,21 @@ namespace Input { Menu::show(Menu::Menus::Options); return; } - else if (is_in(key, "1", "2", "3", "4")) { + else if (key.size() == 1 and isint(key)) { + auto intKey = stoi(key); + #ifdef GPU_SUPPORT + static const array boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"}; + if ((intKey == 0 and Gpu::gpu_names.size() < 5) or (intKey >= 5 and std::cmp_less(Gpu::gpu_names.size(), intKey - 4))) + return; + #else + static const array boxes = {"", "cpu", "mem", "net", "proc"}; + if (intKey == 0 or intKey > 4) + return; + #endif atomic_wait(Runner::active); Config::current_preset = -1; - static const array boxes = {"cpu", "mem", "net", "proc"}; - Config::toggle_box(boxes.at(std::stoi(key) - 1)); + + Config::toggle_box(boxes.at(intKey)); Draw::calcSizes(); Runner::run("all", false, true); return; @@ -343,6 +366,9 @@ namespace Input { else if (key == "c") Config::flip("proc_per_core"); + else if (key == "%") + Config::flip("proc_mem_bytes"); + else if (key == "delete" and not Config::getS("proc_filter").empty()) Config::set("proc_filter", ""s); diff --git a/src/btop_input.hpp b/src/btop_input.hpp index 5a1a28e..fc6fda0 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.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::atomic; using std::deque; @@ -44,7 +43,7 @@ namespace Input { }; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; extern atomic interrupt; extern atomic polling; diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index c9dfbdc..bc97dc2 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; @@ -54,18 +53,76 @@ namespace Menu { int signalKillRet{}; // defaults to 0 const array P_Signals = { - "0", + "0", +#ifdef __linux__ +#if defined(__hppa__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGSTKFLT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGXCPU", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1", + "SIGUSR2", "SIGCHLD", "SIGPWR", "SIGVTALRM", + "SIGPROF", "SIGIO", "SIGWINCH", "SIGSTOP", + "SIGTSTP", "SIGCONT", "SIGTTIN", "SIGTTOU", + "SIGURG", "SIGXFSZ", "SIGSYS" +#elif defined(__mips__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1", + "SIGUSR2", "SIGCHLD", "SIGPWR", "SIGWINCH", + "SIGURG", "SIGIO", "SIGSTOP", "SIGTSTP", + "SIGCONT", "SIGTTIN", "SIGTTOU", "SIGVTALRM", + "SIGPROF", "SIGXCPU", "SIGXFSZ" +#elif defined(__alpha__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG", + "SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD", + "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGPWR", "SIGUSR1", "SIGUSR2" +#elif defined (__sparc__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG", + "SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD", + "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGLOST", "SIGUSR1", "SIGUSR2" +#else "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", - "SIGPIPE", "SIGALRM", "SIGTERM", "16", "SIGCHLD", - "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", - "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", - "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", - "SIGPWR", "SIGSYS" + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", + "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", + "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGIO", "SIGPWR", "SIGSYS" +#endif +#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG", + "SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD", + "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGINFO", "SIGUSR1", "SIGUSR2" +#else + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "7", "SIGFPE", + "SIGKILL", "10", "SIGSEGV", "12", + "SIGPIPE", "SIGALRM", "SIGTERM", "16", + "17", "18", "19", "20", + "21", "22", "23", "24", + "25", "26", "27", "28", + "29", "30", "31" +#endif }; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; const array, 3> menu_normal = { array{ @@ -115,6 +172,7 @@ namespace Menu { {"2", "Toggle MEM box."}, {"3", "Toggle NET box."}, {"4", "Toggle PROC box."}, + {"5", "Toggle GPU box."}, {"d", "Toggle disks view in MEM box."}, {"F2, o", "Shows options."}, {"F1, ?, h", "Shows this window."}, @@ -137,6 +195,7 @@ namespace Menu { {"c", "Toggle per-core cpu usage of processes."}, {"r", "Reverse sorting order in processes box."}, {"e", "Toggle processes tree view."}, + {"%", "Toggles memory display mode in processes box."}, {"Selected +, -", "Expand/collapse the selected process in tree view."}, {"Selected t", "Terminate selected process with SIGTERM - 15."}, {"Selected k", "Kill selected process with SIGKILL - 9."}, @@ -212,6 +271,9 @@ namespace Menu { "Manually set which boxes to show.", "", "Available values are \"cpu mem net proc\".", + #ifdef GPU_SUPPORT + "Or \"gpu0\" through \"gpu5\" for GPU boxes.", + #endif "Separate values with whitespace.", "", "Toggle between presets with key \"p\"."}, @@ -314,23 +376,49 @@ namespace Menu { {"cpu_graph_upper", "Cpu upper graph.", "", - "Sets the CPU stat shown in upper half of", + "Sets the CPU/GPU stat shown in upper half of", "the CPU graph.", "", - "\"total\" = Total cpu usage.", + "CPU:", + "\"total\" = Total cpu usage. (Auto)", "\"user\" = User mode cpu usage.", "\"system\" = Kernel mode cpu usage.", - "+ more depending on kernel."}, + "+ more depending on kernel.", + #ifdef GPU_SUPPORT + "", + "GPU:", + "\"gpu-totals\" = GPU usage split by device.", + "\"gpu-vram-totals\" = VRAM usage split by GPU.", + "\"gpu-pwr-totals\" = Power usage split by GPU.", + "\"gpu-average\" = Avg usage of all GPUs.", + "\"gpu-vram-total\" = VRAM usage of all GPUs.", + "\"gpu-pwr-total\" = Power usage of all GPUs.", + "Not all stats are supported on all devices." + #endif + }, {"cpu_graph_lower", "Cpu lower graph.", "", - "Sets the CPU stat shown in lower half of", + "Sets the CPU/GPU stat shown in lower half of", "the CPU graph.", "", + "CPU:", "\"total\" = Total cpu usage.", "\"user\" = User mode cpu usage.", "\"system\" = Kernel mode cpu usage.", - "+ more depending on kernel."}, + "+ more depending on kernel.", + #ifdef GPU_SUPPORT + "", + "GPU:", + "\"gpu-totals\" = GPU usage split/device. (Auto)", + "\"gpu-vram-totals\" = VRAM usage split by GPU.", + "\"gpu-pwr-totals\" = Power usage split by GPU.", + "\"gpu-average\" = Avg usage of all GPUs.", + "\"gpu-vram-total\" = VRAM usage of all GPUs.", + "\"gpu-pwr-total\" = Power usage of all GPUs.", + "Not all stats are supported on all devices." + #endif + }, {"cpu_invert_lower", "Toggles orientation of the lower CPU graph.", "", @@ -342,12 +430,24 @@ namespace Menu { "to fit to box height.", "", "True or False."}, + #ifdef GPU_SUPPORT + {"show_gpu_info", + "Show gpu info in cpu box.", + "", + "Toggles gpu stats in cpu box and the", + "gpu graph (if \"cpu_graph_lower\" is set to", + "\"Auto\").", + "", + "\"Auto\" to show when no gpu box is shown.", + "\"On\" to always show.", + "\"Off\" to never show."}, + #endif {"check_temp", "Enable cpu temperature reporting.", "", "True or False."}, {"cpu_sensor", - "Cpu temperature sensor", + "Cpu temperature sensor.", "", "Select the sensor that corresponds to", "your cpu temperature.", @@ -387,7 +487,7 @@ namespace Menu { "Rankine, 0 = abosulte zero, 1 degree change", "equals 1 degree change in Fahrenheit."}, {"show_cpu_freq", - "Show CPU frequency", + "Show CPU frequency.", "", "Can cause slowdowns on systems with many", "cores and certain kernel versions."}, @@ -403,6 +503,50 @@ namespace Menu { "", "True or False."}, }, + #ifdef GPU_SUPPORT + { + {"nvml_measure_pcie_speeds", + "Measure PCIe throughput on NVIDIA cards.", + "", + "May impact performance on certain cards.", + "", + "True or False."}, + {"graph_symbol_gpu", + "Graph symbol to use for graphs in gpu box.", + "", + "\"default\", \"braille\", \"block\" or \"tty\".", + "", + "\"default\" for the general default symbol.",}, + {"gpu_mirror_graph", + "Horizontally mirror the GPU graph.", + "", + "True or False."}, + {"custom_gpu_name0", + "Custom gpu0 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name1", + "Custom gpu1 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name2", + "Custom gpu2 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name3", + "Custom gpu3 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name4", + "Custom gpu4 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name5", + "Custom gpu5 model name in gpu stats box.", + "", + "Empty string to disable."}, + }, + #endif { {"mem_below_net", "Mem box location.", @@ -589,6 +733,11 @@ namespace Menu { "Set true to show processes grouped by", "parents with lines drawn between parent", "and child process."}, + {"proc_aggregate", + "Aggregate child's resources in parent.", + "", + "In tree-view, include all child resources", + "with the parent even while expanded."}, {"proc_colors", "Enable colors in process view.", "", @@ -1015,7 +1164,7 @@ namespace Menu { 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)}, @@ -1029,6 +1178,10 @@ namespace Menu { {"cpu_graph_lower", std::cref(Cpu::available_fields)}, {"cpu_sensor", std::cref(Cpu::available_sensors)}, {"selected_battery", std::cref(Config::available_batteries)}, + #ifdef GPU_SUPPORT + {"show_gpu_info", std::cref(Config::show_gpu_values)}, + {"graph_symbol_gpu", std::cref(Config::valid_graph_symbols_def)}, + #endif }; auto tty_mode = Config::getB("tty_mode"); auto vim_keys = Config::getB("vim_keys"); @@ -1080,7 +1233,8 @@ namespace Menu { const auto& option = categories[selected_cat][item_height * page + selected][0]; if (selPred.test(isString) and Config::stringValid(option, editor.text)) { Config::set(option, editor.text); - if (option == "custom_cpu_name") screen_redraw = true; + if (option == "custom_cpu_name" or option.starts_with("custom_gpu_name")) + screen_redraw = true; else if (is_in(option, "shown_boxes", "presets")) { screen_redraw = true; Config::current_preset = -1; @@ -1161,7 +1315,7 @@ namespace Menu { if (--selected_cat < 0) selected_cat = (int)categories.size() - 1; page = selected = 0; } - else if (is_in(key, "1", "2", "3", "4", "5") or key.starts_with("select_cat_")) { + else if (is_in(key, "1", "2", "3", "4", "5", "6") or key.starts_with("select_cat_")) { selected_cat = key.back() - '0' - 1; page = selected = 0; } @@ -1213,7 +1367,7 @@ namespace Menu { Logger::set(optList.at(i)); Logger::info("Logger set to " + optList.at(i)); } - else if (is_in(option, "proc_sorting", "cpu_sensor") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_")) + else if (is_in(option, "proc_sorting", "cpu_sensor", "show_gpu_info") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_")) screen_redraw = true; } else @@ -1259,11 +1413,19 @@ namespace Menu { //? Category buttons out += Mv::to(y+7, x+4); + #ifdef GPU_SUPPORT + for (int i = 0; const auto& m : {"general", "cpu", "gpu", "mem", "net", "proc"}) { + #else for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) { + #endif out += Fx::b + (i == selected_cat ? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']' : Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ') + #ifdef GPU_SUPPORT + + Mv::r(7); + #else + Mv::r(10); + #endif if (string button_name = "select_cat_" + to_string(i + 1); not editing and not mouse_mappings.contains(button_name)) mouse_mappings[button_name] = {y+6, x+2 + 15*i, 3, 15}; i++; diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 0e8832f..74b30d1 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 diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp index e5c9027..b159a97 100644 --- a/src/btop_shared.cpp +++ b/src/btop_shared.cpp @@ -18,12 +18,25 @@ tab-size = 4 #include +#include "btop_config.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" namespace rng = std::ranges; using namespace Tools; +#ifdef GPU_SUPPORT +namespace Gpu { + vector gpu_names; + vector gpu_b_height_offsets; + std::unordered_map> shared_gpu_percent = { + {"gpu-average", {}}, + {"gpu-vram-total", {}}, + {"gpu-pwr-total", {}}, + }; + long long gpu_pwr_total_max; +} +#endif namespace Proc { void proc_sorter(vector& proc_vec, const string& sorting, bool reverse, bool tree) { @@ -156,6 +169,12 @@ namespace Proc { filter_found++; p.filtered = true; } + else if (Config::getB("proc_aggregate")) { + cur_proc.cpu_p += p.cpu_p; + cur_proc.cpu_c += p.cpu_c; + cur_proc.mem += p.mem; + cur_proc.threads += p.threads; + } } if (collapsed or filtering) { return; diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index ecf97f2..ebd1073 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -26,10 +26,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -86,6 +85,91 @@ namespace Shared { } +namespace Gpu { +#ifdef GPU_SUPPORT + extern vector box; + extern int width, height, min_width, min_height; + extern vector x_vec, y_vec; + extern vector redraw; + extern int shown; + extern vector shown_panels; + extern vector gpu_names; + extern vector gpu_b_height_offsets; + extern long long gpu_pwr_total_max; + + extern std::unordered_map> shared_gpu_percent; // averages, power/vram total + + const array mem_names { "used"s, "free"s }; + + //* Container for process information // TODO + /*struct proc_info { + unsigned int pid; + unsigned long long mem; + };*/ + + //* Container for supported Gpu::*::collect() functions + struct gpu_info_supported { + bool gpu_utilization = true, + mem_utilization = true, + gpu_clock = true, + mem_clock = true, + pwr_usage = true, + pwr_state = true, + temp_info = true, + mem_total = true, + mem_used = true, + pcie_txrx = true; + }; + + //* Per-device container for GPU info + struct gpu_info { + std::unordered_map> gpu_percent = { + {"gpu-totals", {}}, + {"gpu-vram-totals", {}}, + {"gpu-pwr-totals", {}}, + }; + unsigned int gpu_clock_speed; // MHz + + long long pwr_usage; // mW + long long pwr_max_usage = 255000; + long long pwr_state; + + deque temp = {0}; + long long temp_max = 110; + + long long mem_total = 0; + long long mem_used = 0; + deque mem_utilization_percent = {0}; // TODO: properly handle GPUs that can't report some stats + long long mem_clock_speed = 0; // MHz + + long long pcie_tx = 0; // KB/s + long long pcie_rx = 0; + + gpu_info_supported supported_functions; + + // vector graphics_processes = {}; // TODO + // vector compute_processes = {}; + }; + + namespace Nvml { + extern bool shutdown(); + } + namespace Rsmi { + extern bool shutdown(); + } + + //* Collect gpu stats and temperatures + auto collect(bool no_update = false) -> vector&; + + //* Draw contents of gpu box using as source + string draw(const gpu_info& gpu, unsigned long index, bool force_redraw, bool data_same); +#else + struct gpu_info { + bool supported = false; + }; +#endif +} + namespace Cpu { extern string box; extern int x, y, width, height, min_width, min_height; @@ -96,7 +180,7 @@ namespace Cpu { extern tuple current_bat; struct cpu_info { - unordered_flat_map> cpu_percent = { + std::unordered_map> cpu_percent = { {"total", {}}, {"user", {}}, {"nice", {}}, @@ -119,11 +203,13 @@ namespace Cpu { auto collect(bool no_update = false) -> cpu_info&; //* Draw contents of cpu box using as source - string draw(const cpu_info& cpu, bool force_redraw = false, bool data_same = false); + 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; @@ -155,13 +241,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; }; @@ -183,7 +269,7 @@ 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 @@ -195,14 +281,14 @@ namespace Net { }; 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 }; - extern unordered_flat_map current_net; + extern std::unordered_map current_net; //* Collect net upload/download stats auto collect(bool no_update=false) -> net_info&; @@ -235,7 +321,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"}, 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..cc3fbdf 100644 --- a/src/btop_theme.hpp +++ b/src/btop_theme.hpp @@ -22,12 +22,11 @@ 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; @@ -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 a8add08..e17edf8 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -30,7 +30,7 @@ tab-size = 4 #include #include -#include "robin_hood.h" +#include "unordered_map" #include "widechar_width.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" @@ -43,7 +43,6 @@ 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 @@ -99,19 +98,31 @@ namespace Term { } auto get_min_size(const string& boxes) -> array { - bool cpu = boxes.find("cpu") != string::npos; - bool mem = boxes.find("mem") != string::npos; - bool net = boxes.find("net") != string::npos; - bool proc = boxes.find("proc") != string::npos; - int width = 0; + bool cpu = boxes.find("cpu") != string::npos; + bool mem = boxes.find("mem") != string::npos; + bool net = boxes.find("net") != string::npos; + bool proc = boxes.find("proc") != string::npos; + #ifdef GPU_SUPPORT + int gpu = 0; + if (not Gpu::gpu_names.empty()) + for (char i = '0'; i <= '5'; ++i) + gpu += (boxes.find(std::string("gpu") + i) != string::npos); + #endif + int width = 0; if (mem) width = Mem::min_width; else if (net) width = Mem::min_width; width += (proc ? Proc::min_width : 0); if (cpu and width < Cpu::min_width) width = Cpu::min_width; + #ifdef GPU_SUPPORT + if (gpu != 0 and width < Gpu::min_width) width = Gpu::min_width; + #endif int height = (cpu ? Cpu::min_height : 0); if (proc) height += Proc::min_height; else height += (mem ? Mem::min_height : 0) + (net ? Net::min_height : 0); + #ifdef GPU_SUPPORT + height += Gpu::min_height*gpu; + #endif return { width, height }; } @@ -445,7 +456,7 @@ namespace Tools { out = to_string((int)round(stod(out))); } if (out.size() > 3) { - out = to_string((int)(out[0] - '0') + 1); + out = to_string((int)(out[0] - '0')) + ".0"; start++; } out.push_back(units[start][0]); @@ -537,6 +548,74 @@ namespace Tools { return (user != nullptr ? user : ""); } + DebugTimer::DebugTimer(const string name, bool start, bool delayed_report) : name(name), delayed_report(delayed_report) { + if (start) + this->start(); + } + + DebugTimer::~DebugTimer() { + if (running) + this->stop(true); + this->force_report(); + } + + void DebugTimer::start() { + if (running) return; + running = true; + start_time = time_micros(); + } + + void DebugTimer::stop(bool report) { + if (not running) return; + running = false; + elapsed_time = time_micros() - start_time; + if (report) this->report(); + } + + void DebugTimer::reset(bool restart) { + running = false; + start_time = 0; + elapsed_time = 0; + if (restart) this->start(); + } + + void DebugTimer::stop_rename_reset(const string &new_name, bool report, bool restart) { + this->stop(report); + name = new_name; + this->reset(restart); + } + + void DebugTimer::report() { + string report_line; + if (start_time == 0 and elapsed_time == 0) + report_line = fmt::format("DebugTimer::report() warning -> Timer [{}] has not been started!", name); + else if (running) + report_line = fmt::format(custom_locale, "Timer [{}] (running) currently at {:L} μs", name, time_micros() - start_time); + else + report_line = fmt::format(custom_locale, "Timer [{}] took {:L} μs", name, elapsed_time); + + if (delayed_report) + report_buffer.emplace_back(report_line); + else + Logger::log_write(log_level, report_line); + } + + void DebugTimer::force_report() { + if (report_buffer.empty()) return; + for (const auto& line : report_buffer) + Logger::log_write(log_level, line); + report_buffer.clear(); + } + + uint64_t DebugTimer::elapsed() { + if (running) + return time_micros() - start_time; + return elapsed_time; + } + + bool DebugTimer::is_running() { + return running; + } } namespace Logger { @@ -568,7 +647,7 @@ namespace Logger { loglevel = v_index(log_levels, level); } - void log_write(const size_t level, const string& msg) { + void log_write(const Level level, const string& msg) { if (loglevel < level or logfile.empty()) return; atomic_lock lck(busy, true); lose_priv neutered{}; diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index f4bc36d..bda89f2 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -31,6 +31,10 @@ tab-size = 4 #include #include #include +#include +#ifdef BTOP_DEBUG +#include +#endif #ifndef HOST_NAME_MAX #ifdef __APPLE__ #define HOST_NAME_MAX 255 @@ -146,11 +150,46 @@ 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 { constexpr auto SSmax = std::numeric_limits::max(); + class MyNumPunct : public std::numpunct { + protected: + virtual char do_thousands_sep() const { return '\''; } + virtual std::string do_grouping() const { return "\03"; } + }; + size_t wide_ulen(const string& str); size_t wide_ulen(const std::wstring& w_str); @@ -298,6 +337,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); @@ -334,27 +417,40 @@ namespace Tools { //* Convert a celsius value to celsius, fahrenheit, kelvin or rankin and return tuple with new value and unit. 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", +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 + //* Usage example: Tools::DebugTimer timer(name:"myTimer", [start:true], [delayed_report:true]) // Create timer and start + //* timer.stop(); // Stop timer and report elapsed time + //* timer.stop_rename_reset("myTimer2"); // Stop timer, report elapsed time, rename timer, reset and restart + class DebugTimer { + uint64_t start_time{}; + uint64_t elapsed_time{}; + bool running{}; + std::locale custom_locale = std::locale(std::locale::classic(), new Tools::MyNumPunct); + vector report_buffer{}; + public: + string name{}; + bool delayed_report{}; + Logger::Level log_level = Logger::DEBUG; + DebugTimer() = default; + DebugTimer(const string name, bool start = true, bool delayed_report = true); + ~DebugTimer(); + + void start(); + void stop(bool report = true); + void reset(bool restart = true); + //* Stops and reports (default), renames timer then resets and restarts (default) + void stop_rename_reset(const string& new_name, bool report = true, bool restart = true); + void report(); + void force_report(); + uint64_t elapsed(); + bool is_running(); }; - extern std::filesystem::path logfile; - //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" - void set(const string& level); - - void log_write(const size_t level, const string& msg); - inline void error(const string msg) { log_write(1, msg); } - inline void warning(const string msg) { log_write(2, msg); } - inline void info(const string msg) { log_write(3, msg); } - inline void debug(const string msg) { log_write(4, msg); } } + + diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 47598f0..2db49a2 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" @@ -74,7 +75,7 @@ using namespace Tools; namespace Cpu { vector core_old_totals; vector core_old_idles; - vector available_fields = {"total"}; + vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; bool got_sensors = false, cpu_temp_only = false; @@ -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,17 +170,23 @@ 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(); } @@ -204,7 +211,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -323,8 +330,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++) { @@ -557,7 +564,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 +579,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 +588,6 @@ namespace Mem { } } - Logger::debug(""); } // this code is for ZFS mounts @@ -691,7 +697,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,13 +813,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; @@ -892,7 +898,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) { @@ -966,7 +972,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1037,7 +1042,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; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 8a475e2..677c3fe 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 @@ -29,6 +30,14 @@ tab-size = 4 #include #include // for inet_ntop() #include +#include +#include +#include +#include + +#if defined(RSMI_STATIC) + #include +#endif #if !(defined(STATIC_BUILD) && defined(__GLIBC__)) #include @@ -48,18 +57,23 @@ using std::numeric_limits; using std::round; using std::streamsize; using std::vector; +using std::future; +using std::async; +using std::pair; + namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; using namespace std::literals; // for operator""s +using namespace std::chrono_literals; //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- namespace Cpu { vector core_old_totals; vector core_old_idles; - vector available_fields; + vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; @@ -83,10 +97,110 @@ namespace Cpu { int64_t crit{}; // defaults to 0 }; - 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 { + vector gpus; +#ifdef GPU_SUPPORT + //? NVIDIA data collection + namespace Nvml { + //? NVML defines, structs & typedefs + #define NVML_DEVICE_NAME_BUFFER_SIZE 64 + #define NVML_SUCCESS 0 + #define NVML_TEMPERATURE_THRESHOLD_SHUTDOWN 0 + #define NVML_CLOCK_GRAPHICS 0 + #define NVML_CLOCK_MEM 2 + #define NVML_TEMPERATURE_GPU 0 + #define NVML_PCIE_UTIL_TX_BYTES 0 + #define NVML_PCIE_UTIL_RX_BYTES 1 + + typedef void* nvmlDevice_t; // we won't be accessing any of the underlying struct's properties, so this is fine + typedef int nvmlReturn_t, // enums are basically ints + nvmlTemperatureThresholds_t, + nvmlClockType_t, + nvmlPstates_t, + nvmlTemperatureSensors_t, + nvmlPcieUtilCounter_t; + + struct nvmlUtilization_t {unsigned int gpu, memory;}; + struct nvmlMemory_t {unsigned long long total, free, used;}; + + //? Function pointers + const char* (*nvmlErrorString)(nvmlReturn_t); + nvmlReturn_t (*nvmlInit)(); + nvmlReturn_t (*nvmlShutdown)(); + nvmlReturn_t (*nvmlDeviceGetCount)(unsigned int*); + nvmlReturn_t (*nvmlDeviceGetHandleByIndex)(unsigned int, nvmlDevice_t*); + nvmlReturn_t (*nvmlDeviceGetName)(nvmlDevice_t, char*, unsigned int); + nvmlReturn_t (*nvmlDeviceGetPowerManagementLimit)(nvmlDevice_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetTemperatureThreshold)(nvmlDevice_t, nvmlTemperatureThresholds_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetUtilizationRates)(nvmlDevice_t, nvmlUtilization_t*); + nvmlReturn_t (*nvmlDeviceGetClockInfo)(nvmlDevice_t, nvmlClockType_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetPowerUsage)(nvmlDevice_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetPowerState)(nvmlDevice_t, nvmlPstates_t*); + nvmlReturn_t (*nvmlDeviceGetTemperature)(nvmlDevice_t, nvmlTemperatureSensors_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetMemoryInfo)(nvmlDevice_t, nvmlMemory_t*); + nvmlReturn_t (*nvmlDeviceGetPcieThroughput)(nvmlDevice_t, nvmlPcieUtilCounter_t, unsigned int*); + + //? Data + void* nvml_dl_handle; + bool initialized = false; + bool init(); + bool shutdown(); + template bool collect(gpu_info* gpus_slice); + vector devices; + unsigned int device_count = 0; + } + + //? AMD data collection + 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 + + 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];}; + + //? Function pointers + rsmi_status_t (*rsmi_init)(uint64_t); + rsmi_status_t (*rsmi_shut_down)(); + 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_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*); + + //? Data + void* rsmi_dl_handle; + #endif + bool initialized = false; + bool init(); + bool shutdown(); + template bool collect(gpu_info* gpus_slice); + uint32_t device_count = 0; + } +#endif } namespace Mem { @@ -139,7 +253,7 @@ namespace Shared { Cpu::collect(); if (Runner::coreNum_reset) Runner::coreNum_reset = false; for (auto& [field, vec] : Cpu::current_cpu.cpu_percent) { - if (not vec.empty()) Cpu::available_fields.push_back(field); + 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(); @@ -148,12 +262,32 @@ namespace Shared { } Cpu::core_mapping = Cpu::get_core_mapping(); + //? Init for namespace Gpu + #ifdef GPU_SUPPORT + Gpu::Nvml::init(); + Gpu::Rsmi::init(); + if (not Gpu::gpu_names.empty()) { + for (auto const& [key, _] : Gpu::gpus[0].gpu_percent) + Cpu::available_fields.push_back(key); + for (auto const& [key, _] : Gpu::shared_gpu_percent) + Cpu::available_fields.push_back(key); + + using namespace Gpu; + gpu_b_height_offsets.resize(gpus.size()); + for (size_t i = 0; i < gpu_b_height_offsets.size(); ++i) + gpu_b_height_offsets[i] = gpus[i].supported_functions.gpu_utilization + + gpus[i].supported_functions.pwr_usage + + (gpus[i].supported_functions.mem_total or gpus[i].supported_functions.mem_used) + * (1 + 2*(gpus[i].supported_functions.mem_total and gpus[i].supported_functions.mem_used) + 2*gpus[i].supported_functions.mem_utilization); + } + #endif + //? Init for namespace Mem Mem::old_uptime = system_uptime(); Mem::collect(); + Logger::debug("Shared::init() : Initialized."); } - } namespace Cpu { @@ -167,7 +301,7 @@ namespace Cpu { "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}, @@ -208,7 +342,7 @@ namespace Cpu { } - auto name_vec = ssplit(name); + 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); @@ -224,7 +358,7 @@ namespace Cpu { } 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) != "@") + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1).size() != 1) name = name_vec.at(cpu_pos + 1); else name.clear(); @@ -464,8 +598,8 @@ 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 @@ -538,7 +672,7 @@ namespace Cpu { auto get_battery() -> tuple { if (not has_battery) return {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) { @@ -807,6 +941,572 @@ namespace Cpu { } } +#ifdef GPU_SUPPORT +namespace Gpu { + //? NVIDIA + namespace Nvml { + bool init() { + if (initialized) return false; + + //? Dynamic loading & linking + //? 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 != nullptr) { + Logger::error(string("NVML: Couldn't find function ") + sym_name + ": " + err); + return (void*)nullptr; + } else return sym; + }; + + #define LOAD_SYM(NAME) if ((NAME = (decltype(NAME))load_nvml_sym(#NAME)) == nullptr) return false + + LOAD_SYM(nvmlErrorString); + LOAD_SYM(nvmlInit); + LOAD_SYM(nvmlShutdown); + LOAD_SYM(nvmlDeviceGetCount); + LOAD_SYM(nvmlDeviceGetHandleByIndex); + LOAD_SYM(nvmlDeviceGetName); + LOAD_SYM(nvmlDeviceGetPowerManagementLimit); + LOAD_SYM(nvmlDeviceGetTemperatureThreshold); + LOAD_SYM(nvmlDeviceGetUtilizationRates); + LOAD_SYM(nvmlDeviceGetClockInfo); + LOAD_SYM(nvmlDeviceGetPowerUsage); + LOAD_SYM(nvmlDeviceGetPowerState); + LOAD_SYM(nvmlDeviceGetTemperature); + LOAD_SYM(nvmlDeviceGetMemoryInfo); + LOAD_SYM(nvmlDeviceGetPcieThroughput); + + #undef LOAD_SYM + + //? Function calls + nvmlReturn_t result = nvmlInit(); + if (result != NVML_SUCCESS) { + Logger::debug(std::string("Failed to initialize NVML, NVIDIA GPUs will not be detected: ") + nvmlErrorString(result)); + return false; + } + + //? Device count + result = nvmlDeviceGetCount(&device_count); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get device count: ") + nvmlErrorString(result)); + return false; + } + + if (device_count > 0) { + devices.resize(device_count); + gpus.resize(device_count); + gpu_names.resize(device_count); + + initialized = true; + + //? Check supported functions & get maximums + Nvml::collect<1>(gpus.data()); + + return true; + } else {initialized = true; shutdown(); return false;} + } + + bool shutdown() { + if (!initialized) return false; + nvmlReturn_t result = nvmlShutdown(); + if (NVML_SUCCESS == result) { + initialized = false; + dlclose(nvml_dl_handle); + } else Logger::warning(std::string("Failed to shutdown NVML: ") + nvmlErrorString(result)); + + return !initialized; + } + + template // collect<1> is called in Nvml::init(), and populates gpus.supported_functions + bool collect(gpu_info* gpus_slice) { // raw pointer to vector data, size == device_count + if (!initialized) return false; + + nvmlReturn_t result; + std::thread pcie_tx_thread, pcie_rx_thread; + // DebugTimer nvTotalTimer("Nvidia Total"); + for (unsigned int i = 0; i < device_count; ++i) { + if constexpr(is_init) { + //? Device Handle + result = nvmlDeviceGetHandleByIndex(i, devices.data() + i); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get device handle: ") + nvmlErrorString(result)); + gpus[i].supported_functions = {false, false, false, false, false, false, false, false}; + continue; + } + + //? Device name + char name[NVML_DEVICE_NAME_BUFFER_SIZE]; + result = nvmlDeviceGetName(devices[i], name, NVML_DEVICE_NAME_BUFFER_SIZE); + if (result != NVML_SUCCESS) + Logger::warning(std::string("NVML: Failed to get device name: ") + nvmlErrorString(result)); + else { + gpu_names[i] = string(name); + for (const auto& brand : {"NVIDIA", "Nvidia", "(R)", "(TM)"}) { + gpu_names[i] = s_replace(gpu_names[i], brand, ""); + } + gpu_names[i] = trim(gpu_names[i]); + } + + //? Power usage + unsigned int max_power; + result = nvmlDeviceGetPowerManagementLimit(devices[i], &max_power); + if (result != NVML_SUCCESS) + Logger::warning(std::string("NVML: Failed to get maximum GPU power draw, defaulting to 225W: ") + nvmlErrorString(result)); + else { + gpus[i].pwr_max_usage = max_power; // RSMI reports power in microWatts + gpu_pwr_total_max += max_power; + } + + //? Get temp_max + unsigned int temp_max; + result = nvmlDeviceGetTemperatureThreshold(devices[i], NVML_TEMPERATURE_THRESHOLD_SHUTDOWN, &temp_max); + if (result != NVML_SUCCESS) + Logger::warning(std::string("NVML: Failed to get maximum GPU temperature, defaulting to 110°C: ") + nvmlErrorString(result)); + else gpus[i].temp_max = (long long)temp_max; + } + + //? PCIe link speeds, the data collection takes >=20ms each call so they run on separate threads + if (gpus_slice[i].supported_functions.pcie_txrx and (Config::getB("nvml_measure_pcie_speeds") or is_init)) { + pcie_tx_thread = std::thread([gpus_slice, i]() { + unsigned int tx; + nvmlReturn_t result = nvmlDeviceGetPcieThroughput(devices[i], NVML_PCIE_UTIL_TX_BYTES, &tx); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get PCIe TX throughput: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.pcie_txrx = false; + } else gpus_slice[i].pcie_tx = (long long)tx; + }); + + pcie_rx_thread = std::thread([gpus_slice, i]() { + unsigned int rx; + nvmlReturn_t result = nvmlDeviceGetPcieThroughput(devices[i], NVML_PCIE_UTIL_RX_BYTES, &rx); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get PCIe RX throughput: ") + nvmlErrorString(result)); + } else gpus_slice[i].pcie_rx = (long long)rx; + }); + } + + // DebugTimer nvTimer("Nv utilization"); + //? GPU & memory utilization + if (gpus_slice[i].supported_functions.gpu_utilization) { + nvmlUtilization_t utilization; + result = nvmlDeviceGetUtilizationRates(devices[i], &utilization); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU utilization: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_utilization = false; + if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false; + } else { + gpus_slice[i].gpu_percent.at("gpu-totals").push_back((long long)utilization.gpu); + gpus_slice[i].mem_utilization_percent.push_back((long long)utilization.memory); + } + } + + // nvTimer.stop_rename_reset("Nv clock"); + //? Clock speeds + if (gpus_slice[i].supported_functions.gpu_clock) { + unsigned int gpu_clock; + result = nvmlDeviceGetClockInfo(devices[i], NVML_CLOCK_GRAPHICS, &gpu_clock); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU clock speed: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false; + } else gpus_slice[i].gpu_clock_speed = (long long)gpu_clock; + } + + if (gpus_slice[i].supported_functions.mem_clock) { + unsigned int mem_clock; + result = nvmlDeviceGetClockInfo(devices[i], NVML_CLOCK_MEM, &mem_clock); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get VRAM clock speed: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; + } else gpus_slice[i].mem_clock_speed = (long long)mem_clock; + } + + // nvTimer.stop_rename_reset("Nv power"); + //? Power usage & state + if (gpus_slice[i].supported_functions.pwr_usage) { + unsigned int power; + result = nvmlDeviceGetPowerUsage(devices[i], &power); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU power usage: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_usage = false; + } else { + gpus_slice[i].pwr_usage = (long long)power; + gpus_slice[i].gpu_percent.at("gpu-pwr-totals").push_back(clamp((long long)round((double)gpus_slice[i].pwr_usage * 100.0 / (double)gpus_slice[i].pwr_max_usage), 0ll, 100ll)); + } + } + + if (gpus_slice[i].supported_functions.pwr_state) { + nvmlPstates_t pState; + result = nvmlDeviceGetPowerState(devices[i], &pState); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU power state: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_state = false; + } else gpus_slice[i].pwr_state = static_cast(pState); + } + + // nvTimer.stop_rename_reset("Nv temp"); + //? GPU temperature + if (gpus_slice[i].supported_functions.temp_info) { + if (Config::getB("check_temp")) { + unsigned int temp; + nvmlReturn_t result = nvmlDeviceGetTemperature(devices[i], NVML_TEMPERATURE_GPU, &temp); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU temperature: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.temp_info = false; + } else gpus_slice[i].temp.push_back((long long)temp); + } + } + + // nvTimer.stop_rename_reset("Nv mem"); + //? Memory info + if (gpus_slice[i].supported_functions.mem_total) { + nvmlMemory_t memory; + result = nvmlDeviceGetMemoryInfo(devices[i], &memory); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get VRAM info: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_total = false; + if constexpr(is_init) gpus_slice[i].supported_functions.mem_used = false; + } else { + gpus_slice[i].mem_total = memory.total; + gpus_slice[i].mem_used = memory.used; + //gpu.mem_free = memory.free; + + auto used_percent = (long long)round((double)memory.used * 100.0 / (double)memory.total); + gpus_slice[i].gpu_percent.at("gpu-vram-totals").push_back(used_percent); + } + } + + //? TODO: Processes using GPU + /*unsigned int proc_info_len; + nvmlProcessInfo_t* proc_info = 0; + result = nvmlDeviceGetComputeRunningProcesses_v3(device, &proc_info_len, proc_info); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get compute processes: ") + nvmlErrorString(result)); + } else { + for (unsigned int i = 0; i < proc_info_len; ++i) + gpus_slice[i].graphics_processes.push_back({proc_info[i].pid, proc_info[i].usedGpuMemory}); + }*/ + + // nvTimer.stop_rename_reset("Nv pcie thread join"); + //? Join PCIE TX/RX threads + if constexpr(is_init) { // there doesn't seem to be a better way to do this, but this should be fine considering it's just 2 lines + pcie_tx_thread.join(); + pcie_rx_thread.join(); + } else if (gpus_slice[i].supported_functions.pcie_txrx and Config::getB("nvml_measure_pcie_speeds")) { + pcie_tx_thread.join(); + pcie_rx_thread.join(); + } + } + + return true; + } + } + + //? AMD + namespace Rsmi { + bool init() { + if (initialized) return false; + + //? Dynamic loading & linking + #if !defined(RSMI_STATIC) + + //? 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 + }; + + for (const auto& l : libRocAlts) { + rsmi_dl_handle = dlopen(l, RTLD_LAZY); + if (rsmi_dl_handle != nullptr) { + break; + } + } + + if (!rsmi_dl_handle) { + 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 != nullptr) { + Logger::error(string("ROCm SMI: Couldn't find function ") + sym_name + ": " + err); + return (void*)nullptr; + } else return sym; + }; + + #define LOAD_SYM(NAME) if ((NAME = (decltype(NAME))load_rsmi_sym(#NAME)) == nullptr) return false + + LOAD_SYM(rsmi_init); + LOAD_SYM(rsmi_shut_down); + 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); + LOAD_SYM(rsmi_dev_pci_throughput_get); + + #undef LOAD_SYM + #endif + + //? Function calls + rsmi_status_t result = rsmi_init(0); + if (result != RSMI_STATUS_SUCCESS) { + Logger::debug("Failed to initialize ROCm SMI, AMD GPUs will not be detected"); + return false; + } + + //? Device count + result = rsmi_num_monitor_devices(&device_count); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to fetch number of devices"); + return false; + } + + if (device_count > 0) { + gpus.resize(gpus.size() + device_count); + gpu_names.resize(gpus.size() + device_count); + + initialized = true; + + //? Check supported functions & get maximums + Rsmi::collect<1>(gpus.data() + Nvml::device_count); + + return true; + } else {initialized = true; shutdown(); return false;} + } + + bool shutdown() { + if (!initialized) return false; + if (rsmi_shut_down() == RSMI_STATUS_SUCCESS) { + initialized = false; + #if !defined(RSMI_STATIC) + dlclose(rsmi_dl_handle); + #endif + } else Logger::warning("Failed to shutdown ROCm SMI"); + + return true; + } + + template + bool collect(gpu_info* gpus_slice) { // raw pointer to vector data, size == device_count, offset by Nvml::device_count elements + if (!initialized) return false; + rsmi_status_t result; + + for (uint32_t i = 0; i < device_count; ++i) { + if constexpr(is_init) { + //? Device name + char name[NVML_DEVICE_NAME_BUFFER_SIZE]; // ROCm SMI does not provide a constant for this as far as I can tell, this should be good enough + result = rsmi_dev_name_get(i, name, NVML_DEVICE_NAME_BUFFER_SIZE); + if (result != RSMI_STATUS_SUCCESS) + Logger::warning("ROCm SMI: Failed to get device name"); + else gpu_names[Nvml::device_count + i] = string(name); + + //? Power usage + uint64_t max_power; + result = rsmi_dev_power_cap_get(i, 0, &max_power); + if (result != RSMI_STATUS_SUCCESS) + Logger::warning("ROCm SMI: Failed to get maximum GPU power draw, defaulting to 225W"); + else { + gpus_slice[i].pwr_max_usage = (long long)(max_power/1000); // RSMI reports power in microWatts + gpu_pwr_total_max += gpus_slice[i].pwr_max_usage; + } + + //? Get temp_max + int64_t temp_max; + result = rsmi_dev_temp_metric_get(i, RSMI_TEMP_TYPE_EDGE, RSMI_TEMP_MAX, &temp_max); + if (result != RSMI_STATUS_SUCCESS) + Logger::warning("ROCm SMI: Failed to get maximum GPU temperature, defaulting to 110°C"); + else gpus_slice[i].temp_max = (long long)temp_max; + } + + //? GPU utilization + if (gpus_slice[i].supported_functions.gpu_utilization) { + uint32_t utilization; + result = rsmi_dev_busy_percent_get(i, &utilization); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU utilization"); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_utilization = false; + } else gpus_slice[i].gpu_percent.at("gpu-totals").push_back((long long)utilization); + } + + //? Memory utilization + if (gpus_slice[i].supported_functions.mem_utilization) { + uint32_t utilization; + result = rsmi_dev_memory_busy_percent_get(i, &utilization); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get VRAM utilization"); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false; + } else gpus_slice[i].mem_utilization_percent.push_back((long long)utilization); + } + + //? Clock speeds + if (gpus_slice[i].supported_functions.gpu_clock) { + rsmi_frequencies_t frequencies; + result = rsmi_dev_gpu_clk_freq_get(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) { + rsmi_frequencies_t frequencies; + result = rsmi_dev_gpu_clk_freq_get(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 + } + + //? Power usage & state + if (gpus_slice[i].supported_functions.pwr_usage) { + uint64_t power; + result = rsmi_dev_power_ave_get(i, 0, &power); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU power usage"); + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_usage = false; + } else gpus_slice[i].gpu_percent.at("gpu-pwr-totals").push_back(clamp((long long)round((double)gpus_slice[i].pwr_usage * 100.0 / (double)gpus_slice[i].pwr_max_usage), 0ll, 100ll)); + + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_state = false; + } + + //? GPU temperature + if (gpus_slice[i].supported_functions.temp_info) { + if (Config::getB("check_temp") or is_init) { + int64_t temp; + result = rsmi_dev_temp_metric_get(i, RSMI_TEMP_TYPE_EDGE, RSMI_TEMP_CURRENT, &temp); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU temperature"); + if constexpr(is_init) gpus_slice[i].supported_functions.temp_info = false; + } else gpus_slice[i].temp.push_back((long long)temp/1000); + } + } + + //? Memory info + if (gpus_slice[i].supported_functions.mem_total) { + uint64_t total; + result = rsmi_dev_memory_total_get(i, RSMI_MEM_TYPE_VRAM, &total); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get total VRAM"); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_total = false; + } else gpus_slice[i].mem_total = total; + } + + if (gpus_slice[i].supported_functions.mem_used) { + uint64_t used; + result = rsmi_dev_memory_usage_get(i, RSMI_MEM_TYPE_VRAM, &used); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get VRAM usage"); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_used = false; + } else { + gpus_slice[i].mem_used = used; + if (gpus_slice[i].supported_functions.mem_total) + gpus_slice[i].gpu_percent.at("gpu-vram-totals").push_back((long long)round((double)used * 100.0 / (double)gpus_slice[i].mem_total)); + } + } + + //? PCIe link speeds + if (gpus_slice[i].supported_functions.pcie_txrx) { + uint64_t tx, rx; + result = rsmi_dev_pci_throughput_get(i, &tx, &rx, 0); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get PCIe throughput"); + if constexpr(is_init) gpus_slice[i].supported_functions.pcie_txrx = false; + } else { + gpus_slice[i].pcie_tx = (long long)tx; + gpus_slice[i].pcie_rx = (long long)rx; + } + } + } + + return true; + } + } + + // TODO: Intel + + //? Collect data from GPU-specific libraries + auto collect(bool no_update) -> vector& { + if (Runner::stopping or (no_update and not gpus.empty())) return gpus; + + // DebugTimer gpu_timer("GPU Total"); + + //* Collect data + Nvml::collect<0>(gpus.data()); // raw pointer to vector data, size == Nvml::device_count + Rsmi::collect<0>(gpus.data() + Nvml::device_count); // size = Rsmi::device_count + + //* Calculate average usage + long long avg = 0; + long long mem_usage_total = 0; + long long mem_total = 0; + long long pwr_total = 0; + for (auto& gpu : gpus) { + if (gpu.supported_functions.gpu_utilization) + avg += gpu.gpu_percent.at("gpu-totals").back(); + if (gpu.supported_functions.mem_used) + mem_usage_total += gpu.mem_used; + if (gpu.supported_functions.mem_total) + mem_total += gpu.mem_total; + if (gpu.supported_functions.pwr_usage) + mem_total += gpu.pwr_usage; + + //* Trim vectors if there are more values than needed for graphs + if (width != 0) { + //? GPU & memory utilization + while (cmp_greater(gpu.gpu_percent.at("gpu-totals").size(), width * 2)) gpu.gpu_percent.at("gpu-totals").pop_front(); + while (cmp_greater(gpu.mem_utilization_percent.size(), width)) gpu.mem_utilization_percent.pop_front(); + //? Power usage + while (cmp_greater(gpu.gpu_percent.at("gpu-pwr-totals").size(), width)) gpu.gpu_percent.at("gpu-pwr-totals").pop_front(); + //? Temperature + while (cmp_greater(gpu.temp.size(), 18)) gpu.temp.pop_front(); + //? Memory usage + while (cmp_greater(gpu.gpu_percent.at("gpu-vram-totals").size(), width/2)) gpu.gpu_percent.at("gpu-vram-totals").pop_front(); + } + } + + shared_gpu_percent.at("gpu-average").push_back(avg / gpus.size()); + if (mem_total != 0) + shared_gpu_percent.at("gpu-vram-total").push_back(mem_usage_total / mem_total); + if (gpu_pwr_total_max != 0) + shared_gpu_percent.at("gpu-pwr-total").push_back(pwr_total / gpu_pwr_total_max); + + if (width != 0) { + while (cmp_greater(shared_gpu_percent.at("gpu-average").size(), width * 2)) shared_gpu_percent.at("gpu-average").pop_front(); + while (cmp_greater(shared_gpu_percent.at("gpu-pwr-total").size(), width * 2)) shared_gpu_percent.at("gpu-pwr-total").pop_front(); + while (cmp_greater(shared_gpu_percent.at("gpu-vram-total").size(), width * 2)) shared_gpu_percent.at("gpu-vram-total").pop_front(); + } + + return gpus; + } +} +#endif + namespace Mem { bool has_swap{}; // defaults to false vector fstab; @@ -939,6 +1639,7 @@ namespace Mem { auto only_physical = Config::getB("only_physical"); auto zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto& disks = mem.disks; + static std::unordered_map>> disks_stats_promises; ifstream diskread; vector filter; @@ -1078,31 +1779,47 @@ namespace Mem { diskread.close(); //? Get disk/partition stats - bool new_ignored = false; - for (auto& [mountpoint, disk] : disks) { - if (std::error_code ec; not fs::exists(mountpoint, ec) or v_contains(ignore_list, mountpoint)) continue; - struct statvfs vfs; - if (statvfs(mountpoint.c_str(), &vfs) < 0) { - Logger::warning("Failed to get disk/partition stats for mount \""+ mountpoint + "\" with statvfs error code: " + to_string(errno) + ". Ignoring..."); - ignore_list.push_back(mountpoint); - new_ignored = true; + for (auto it = disks.begin(); it != disks.end(); ) { + auto &[mountpoint, disk] = *it; + if (v_contains(ignore_list, mountpoint) or disk.name == "swap") { + it = disks.erase(it); continue; } - disk.total = vfs.f_blocks * vfs.f_frsize; - disk.free = (free_priv ? vfs.f_bfree : vfs.f_bavail) * 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; - } - - //? Remove any problematic disks added to the ignore_list - if (new_ignored) { - for (auto it = disks.begin(); it != disks.end();) { - if (v_contains(ignore_list, it->first)) + if(auto promises_it = disks_stats_promises.find(mountpoint); promises_it != disks_stats_promises.end()){ + auto& promise = promises_it->second; + if(promise.valid() && + promise.wait_for(0s) == std::future_status::timeout) { + ++it; + continue; + } + auto promise_res = promises_it->second.get(); + if(promise_res.second != -1){ + ignore_list.push_back(mountpoint); + Logger::warning("Failed to get disk/partition stats for mount \""+ mountpoint + "\" with statvfs error code: " + to_string(promise_res.second) + ". Ignoring..."); it = disks.erase(it); - else - it++; + continue; + } + auto &updated_stats = promise_res.first; + disk.total = updated_stats.total; + disk.free = updated_stats.free; + disk.used = updated_stats.used; + disk.used_percent = updated_stats.used_percent; + disk.free_percent = updated_stats.free_percent; } + disks_stats_promises[mountpoint] = async(std::launch::async, [mountpoint, &free_priv]() -> pair { + struct statvfs vfs; + disk_info disk; + if (statvfs(mountpoint.c_str(), &vfs) < 0) { + return pair{disk, errno}; + } + disk.total = vfs.f_blocks * vfs.f_frsize; + disk.free = (free_priv ? vfs.f_bfree : vfs.f_bavail) * 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; + return pair{disk, -1}; + }); + ++it; } //? Setup disks order in UI and add swap if enabled @@ -1245,29 +1962,32 @@ namespace Mem { } // looking through all files that start with 'objset' to find the one containing `device_name` object stats - for (const auto& file: fs::directory_iterator(zfs_pool_stat_path)) { - filename = file.path().filename(); - if (filename.starts_with("objset")) { - filestream.open(file.path()); - if (filestream.good()) { - // skip first two lines - for (int i = 0; i < 2; i++) filestream.ignore(numeric_limits::max(), '\n'); - // skip characters until '7' is reached, indicating data type 7, next value will be object name - filestream.ignore(numeric_limits::max(), '7'); - filestream >> name_compare; - if (name_compare == device_name) { - filestream.close(); - if (access(file.path().c_str(), R_OK) == 0) { - return file.path(); - } else { - Logger::debug("Can't access file: " + file.path().string()); - return ""; + try { + for (const auto& file: fs::directory_iterator(zfs_pool_stat_path)) { + filename = file.path().filename(); + if (filename.starts_with("objset")) { + filestream.open(file.path()); + if (filestream.good()) { + // skip first two lines + for (int i = 0; i < 2; i++) filestream.ignore(numeric_limits::max(), '\n'); + // skip characters until '7' is reached, indicating data type 7, next value will be object name + filestream.ignore(numeric_limits::max(), '7'); + filestream >> name_compare; + if (name_compare == device_name) { + filestream.close(); + if (access(file.path().c_str(), R_OK) == 0) { + return file.path(); + } else { + Logger::debug("Can't access file: " + file.path().string()); + return ""; + } } } + filestream.close(); } - filestream.close(); } } + catch (fs::filesystem_error& e) {} Logger::debug("Could not read directory: " + zfs_pool_stat_path.string()); return ""; @@ -1354,13 +2074,13 @@ 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", {}} }; + std::unordered_map graph_max = { {"download", {}}, {"upload", {}} }; + std::unordered_map> max_count = { {"download", {}}, {"upload", {}} }; bool rescale{true}; uint64_t timestamp{}; // defaults to 0 @@ -1499,7 +2219,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1569,7 +2288,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{}; // defaults to false @@ -1584,7 +2303,7 @@ namespace Proc { 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) { @@ -2070,6 +2789,6 @@ namespace Tools { catch (const std::invalid_argument&) {} catch (const std::out_of_range&) {} } - throw std::runtime_error("Failed get uptime from from " + string{Shared::procPath} + "/uptime"); + throw std::runtime_error("Failed to get uptime from " + string{Shared::procPath} + "/uptime"); } } diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 26a93d3..76b63c0 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; @@ -70,7 +74,7 @@ using namespace Tools; namespace Cpu { vector core_old_totals; vector core_old_idles; - vector available_fields = {"total"}; + vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; bool got_sensors = false, cpu_temp_only = false; @@ -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 { @@ -191,7 +195,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -236,7 +240,7 @@ namespace Cpu { name += n + ' '; } name.pop_back(); - for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Core"}) { + for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Apple", "Core"}) { name = s_replace(name, replace, ""); name = s_replace(name, " ", " "); } @@ -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; @@ -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; @@ -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) { @@ -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; 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/elementarish.theme b/themes/elementarish.theme index 8d5b9d1..0fe5398 100644 --- a/themes/elementarish.theme +++ b/themes/elementarish.theme @@ -3,7 +3,7 @@ # By: Dennis Mayr # Main bg -theme[main_bg]="#2b2b2b" +theme[main_bg]="#333333" # Main text color theme[main_fg]="#eee8d5" @@ -12,71 +12,71 @@ theme[main_fg]="#eee8d5" theme[title]="#eee8d5" # Higlight color for keyboard shortcuts -theme[hi_fg]="#dc322f" +theme[hi_fg]="#d1302c" # Background color of selected item in processes box -theme[selected_bg]="#268bd2" +theme[selected_bg]="#268ad0" # Foreground color of selected item in processes box theme[selected_fg]="#eee8d5" # Color of inactive/disabled text -theme[inactive_fg]="#586e75" +theme[inactive_fg]="#657b83" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text -theme[proc_misc]="#268bd2" +theme[proc_misc]="#268ad0" # Cpu box outline color -theme[cpu_box]="#586e75" +theme[cpu_box]="#657b83" # Memory/disks box outline color -theme[mem_box]="#586e75" +theme[mem_box]="#657b83" # Net up/down box outline color -theme[net_box]="#586e75" +theme[net_box]="#657b83" # Processes box outline color -theme[proc_box]="#586e75" +theme[proc_box]="#657b83" # Box divider line and small boxes line color -theme[div_line]="#586e75" +theme[div_line]="#657b83" # Temperature graph colors theme[temp_start]="#859900" -theme[temp_mid]="#b58901" -theme[temp_end]="#dc322f" +theme[temp_mid]="#b28602" +theme[temp_end]="#d1302c" # CPU graph colors theme[cpu_start]="#859900" -theme[cpu_mid]="#b58901" -theme[cpu_end]="#dc322f" +theme[cpu_mid]="#b28602" +theme[cpu_end]="#d1302c" # Mem/Disk free meter -theme[free_start]="#268bd2" +theme[free_start]="#268ad0" theme[free_mid]="#6c71c4" theme[free_end]="#2a9d95" # Mem/Disk cached meter -theme[cached_start]="#268bd2" +theme[cached_start]="#268ad0" theme[cached_mid]="#6c71c4" -theme[cached_end]="#dc322f" +theme[cached_end]="#d1302c" # Mem/Disk available meter -theme[available_start]="#268bd2" +theme[available_start]="#268ad0" theme[available_mid]="#6c71c4" -theme[available_end]="#dc322f" +theme[available_end]="#d1302c" # Mem/Disk used meter theme[used_start]="#859900" -theme[used_mid]="#b58901" -theme[used_end]="#dc322f" +theme[used_mid]="#b28602" +theme[used_end]="#d1302c" # Download graph colors -theme[download_start]="#268bd2" +theme[download_start]="#268ad0" theme[download_mid]="#6c71c4" -theme[download_end]="#dc322f" +theme[download_end]="#d1302c" # Upload graph colors -theme[upload_start]="#268bd2" +theme[upload_start]="#268ad0" theme[upload_mid]="#6c71c4" -theme[upload_end]="#dc322f" +theme[upload_end]="#d1302c" diff --git a/themes/horizon.theme b/themes/horizon.theme new file mode 100644 index 0000000..15c2d0e --- /dev/null +++ b/themes/horizon.theme @@ -0,0 +1,86 @@ +# 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]="#1C1E26" + +# Main text color +theme[main_fg]="#f8f8f2" + +# Title color for boxes +theme[title]="#f8f8f2" + +# Highlight color for keyboard shortcuts +theme[hi_fg]="#B877DB" + +# Background color of selected items +theme[selected_bg]="#282b37" + +# Foreground color of selected items +theme[selected_fg]="#f8f8f2" + +# Color of inactive/disabled text +theme[inactive_fg]="#272e33" + +# Color of text appearing on top of graphs, i.e uptime and current network graph scaling +theme[graph_text]="#f8f8f2" + +# Misc colors for processes box including mini cpu graphs, details memory graph and details status text +theme[proc_misc]="#27D796" + +# Cpu box outline color +theme[cpu_box]="#B877DB" + +# Memory/disks box outline color +theme[mem_box]="#27D796" + +# Net up/down box outline color +theme[net_box]="#E95678" + +# Processes box outline color +theme[proc_box]="#25B2BC" + +# Box divider line and small boxes line color +theme[div_line]="#272e33" + +# Temperature graph colors +theme[temp_start]="#27D796" +theme[temp_mid]="#FAC29A" +theme[temp_end]="#E95678" + +# CPU graph colors +theme[cpu_start]="#27D796" +theme[cpu_mid]="#FAC29A" +theme[cpu_end]="#E95678" + +# Mem/Disk free meter +theme[free_start]="#E95678" +theme[free_mid]="#FAC29A" +theme[free_end]="#27D796" + +# Mem/Disk cached meter +theme[cached_start]="#27D796" +theme[cached_mid]="#FAC29A" +theme[cached_end]="#E95678" + +# Mem/Disk available meter +theme[available_start]="#27D796" +theme[available_mid]="#FAC29A" +theme[available_end]="#E95678" + +# Mem/Disk used meter +theme[used_start]="#27D796" +theme[used_mid]="#FAC29A" +theme[used_end]="#E95678" + +# Download graph colors +theme[download_start]="#27D796" +theme[download_mid]="#FAC29A" +theme[download_end]="#E95678" + +# Upload graph colors +theme[upload_start]="#27D796" +theme[upload_mid]="#FAC29A" +theme[upload_end]="#E95678"