mirror of
https://github.com/aristocratos/btop.git
synced 2024-06-22 16:10:22 +12:00
commitc2c4fe47db
Author: aristocratos <admin@qvantnet.com> Date: Sat Nov 13 23:15:53 2021 +0100 Changes from main + fixes commitbd5d867089
Author: aristocratos <admin@qvantnet.com> Date: Sat Nov 13 21:24:01 2021 +0100 Fixes commitdc5f0606cb
Author: aristocratos <admin@qvantnet.com> Date: Sat Nov 13 20:51:06 2021 +0100 Fixed leak in Proc::collect() commit8b59ab6e11
Author: aristocratos <admin@qvantnet.com> Date: Sat Nov 13 19:59:56 2021 +0100 Fixed process cpu usage calculation commit53c8a0325b
Author: aristocratos <admin@qvantnet.com> Date: Mon Oct 25 13:01:53 2021 +0200 Cpu temp set to average of pACC and eACC for mac m1 commit940cd0a513
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 24 21:12:16 2021 +0200 limit cpu temp to 20 commit254ceb9c2d
Author: aristocratos <admin@qvantnet.com> Date: Sun Oct 24 11:24:04 2021 +0200 Fixed up Makefile commit6fa3bf41d7
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 21 13:12:45 2021 +0200 Makefile fixed WARNFLAGS commit2c98e2ca29
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 21 13:08:12 2021 +0200 Updated Makefile commite69780e9bc
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 20 23:26:09 2021 +0200 fix build commitde62167921
Merge:a590dd3
a0ee404
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 20 23:10:36 2021 +0200 Merge branch 'main' into OSX commita590dd3f67
Author: aristocratos <admin@qvantnet.com> Date: Mon Oct 18 11:20:12 2021 +0200 Ignore format-truncation commit4c30742d41
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 18 10:08:01 2021 +0200 comments about temp sensors commit46030de77c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 18 08:10:37 2021 +0200 available = total - used commit4c228de0ef
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 17 22:19:41 2021 +0200 use sysconf to get arg_max - seems simpler commitc60fc29f0f
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 17 22:10:50 2021 +0200 arg_max should be int on macos commit0b5a931a6d
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 17 22:01:42 2021 +0200 only fetch max_args once commit71d5cd5fd9
Author: aristocratos <admin@qvantnet.com> Date: Sat Oct 16 23:24:07 2021 +0200 Reverted mutexes back to custom atomic bool based locks commit3f34a67df6
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 16 21:47:55 2021 +0200 these helpers can be static commitfc19c46c8a
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 16 21:44:49 2021 +0200 code cleanup: put the code in .cpp to enable incremental build commitc252c618c0
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 16 21:09:21 2021 +0200 don't crash on intel commit9f88187c29
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 16 21:06:18 2021 +0200 small improvement commit808f09c974
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 16 20:59:48 2021 +0200 don't iterate 3 times commitd8408336e3
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 16 20:59:27 2021 +0200 remove debug commit4f078c3beb
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 13 23:38:27 2021 +0200 more temperature (M1 + intel) commit289880aaa6
Merge:88a2528
3ffb212
Author: aristocratos <admin@qvantnet.com> Date: Sat Oct 16 19:37:09 2021 +0200 Merge branch 'OSX' of github.com:aristocratos/btop into OSX commit88a2528ca3
Author: aristocratos <admin@qvantnet.com> Date: Sat Oct 16 19:34:10 2021 +0200 Merge changes from main commit3ffb21203a
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 16 19:20:45 2021 +0200 release a bit more - still has negative temps for 2 cores on my system commit70b4871062
Author: aristocratos <admin@qvantnet.com> Date: Sat Oct 16 01:59:44 2021 +0200 Fixed leaks in Mem and attempt at fixing leaks in sensors.cpp commitfbae907720
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Fri Oct 15 23:41:37 2021 +0200 temperature sensors via IOKit commitcef0f0a68d
Author: aristocratos <admin@qvantnet.com> Date: Fri Oct 15 18:39:17 2021 +0200 Process command line arguments commit921cfa01ff
Author: aristocratos <admin@qvantnet.com> Date: Wed Oct 13 23:20:15 2021 +0200 Re-enable setuid and set default SU_GROUP to wheel for OSX commita416c888c7
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 13 21:16:41 2021 +0200 temperature commite7afe00ce7
Author: aristocratos <admin@qvantnet.com> Date: Wed Oct 13 12:54:43 2021 +0200 Cpu usage working again commit4193ef8921
Author: aristocratos <admin@qvantnet.com> Date: Wed Oct 13 10:36:51 2021 +0200 Fixed cpu lazy sorting commit93fcb6ff04
Author: aristocratos <admin@qvantnet.com> Date: Tue Oct 12 22:22:45 2021 +0200 Update README.md commit683354cd2e
Merge:8a399c4
6d724d6
Author: aristocratos <admin@qvantnet.com> Date: Tue Oct 12 22:19:30 2021 +0200 Merge pull request #80 from ShrirajHegde/OSX Add github workflow for MacOS commit8a399c499a
Author: aristocratos <admin@qvantnet.com> Date: Tue Oct 12 21:50:46 2021 +0200 pointer to smart pointer, first pass commit772605003a
Author: aristocratos <admin@qvantnet.com> Date: Tue Oct 12 18:54:38 2021 +0200 Fixed detailed memory not updating commit28cb677533
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 11 22:19:25 2021 +0200 more memory free-up - still leaks like crazy commit304457863f
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 11 21:48:07 2021 +0200 more RAII cleanup commit82e2e3c55c
Author: aristocratos <admin@qvantnet.com> Date: Mon Oct 11 12:40:25 2021 +0200 Removed non present cpu fields and fixed calculation for selectable cpu field graphs commit68603f2b37
Author: aristocratos <admin@qvantnet.com> Date: Mon Oct 11 10:57:04 2021 +0200 RAII Wrappers for Cpu::get_battery() commitd5cb24fbeb
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 10 20:23:11 2021 +0200 RAII commit8fad5a61be
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 9 21:44:16 2021 +0200 get more disk IO stats commit7fa903cf16
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 9 21:43:48 2021 +0200 fix build commit98036db660
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 9 21:18:25 2021 +0200 remove unnecessary uptime param commitaae7ae35ca
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 9 21:07:23 2021 +0200 remove debug logging commit5187420b04
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 9 21:06:43 2021 +0200 fix process elapsed time commit89582c0ea6
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 9 21:06:29 2021 +0200 don't double free commit6d724d6155
Author: Shriraj Hegde <shriraj.hegde@gmail.com> Date: Sat Oct 9 21:46:33 2021 +0530 Change Upload filename commit4f94ecc8ad
Author: Shriraj Hegde <shriraj.hegde@gmail.com> Date: Sat Oct 9 21:40:11 2021 +0530 Fix upload path commite1d6d0a1f2
Author: Shriraj Hegde <shriraj.hegde@gmail.com> Date: Sat Oct 9 21:36:13 2021 +0530 Skip installing gcc via Homebrew Change job name commit02cdd9d759
Author: Shriraj Hegde <shriraj.hegde@gmail.com> Date: Sat Oct 9 21:30:40 2021 +0530 Fix uploading Remove distclean commit39eb6c396f
Author: Shriraj Hegde <shriraj.hegde@gmail.com> Date: Sat Oct 9 21:26:57 2021 +0530 Disable static compilation commit099592bccd
Author: aristocratos <admin@qvantnet.com> Date: Sat Oct 9 17:52:10 2021 +0200 Ignore empty pid 0 to fix tree mode commita28e17556e
Author: Shriraj Hegde <shriraj.hegde@gmail.com> Date: Sat Oct 9 21:17:11 2021 +0530 Add workflow for MacOS commitaee9179c0a
Author: aristocratos <admin@qvantnet.com> Date: Sat Oct 9 17:36:46 2021 +0200 Disable failed tty mode detection for OSX commit4b7b98058d
Author: aristocratos <admin@qvantnet.com> Date: Sat Oct 9 11:28:32 2021 +0200 Fixed disk io and added io activity based on read/write commitbfa0629e7d
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Fri Oct 8 22:28:10 2021 +0200 fill in 0 for ioticks commita016ff8a03
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Fri Oct 8 22:16:01 2021 +0200 disk io from IOreg. Does not show any io though commitf98606c6db
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Fri Oct 8 09:32:06 2021 +0200 per process IO stats commitc8b50ed488
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Fri Oct 8 00:11:08 2021 +0200 don't show autofs, it's useless commitc4df64d440
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 7 18:41:49 2021 +0200 Fixed compile time display for gmake and command timings commitb3e6f495f7
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 7 18:26:15 2021 +0200 Fixed clk_tck -> clkTck commite53799188f
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 7 18:25:14 2021 +0200 Fixed better detection for OSX commitb864edf984
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 7 18:24:37 2021 +0200 Fixed cumulative cpu usage commit6a3c5d9b97
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 7 13:20:30 2021 +0200 Proc::collect() better cpu percent accurazy commit84d0596294
Merge:3564f8e
98e1e87
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 7 12:56:55 2021 +0200 Merge branch 'OSX' of github.com:aristocratos/btop into OSX commit3564f8e4c2
Author: aristocratos <admin@qvantnet.com> Date: Thu Oct 7 12:56:27 2021 +0200 Proc::collect() fixed cputimes and cpu percentage calc commit98e1e87405
Merge:60c5636
d96fdd7
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Thu Oct 7 11:32:41 2021 +0200 Merge branch 'main' into OSX commit60c5636cd7
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 22:45:54 2021 +0200 fix warning commit489e446152
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 22:38:40 2021 +0200 details + process states commit7e5a808c73
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 22:38:19 2021 +0200 avoid details crash commit9c9da4606b
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 21:03:21 2021 +0200 fix quit on macos commitec7415384d
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 00:41:37 2021 +0200 fix mistake in makefile commit5ac8fa4c8a
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 18:56:13 2021 +0200 don't show /dev commitd901bbebd9
Author: aristocratos <gnmjpl@gmail.com> Date: Wed Oct 6 17:27:51 2021 +0200 Ignore tags and other branches commitc7f1e71e29
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 16:00:41 2021 +0200 comment commitb9d58e3faf
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 15:55:58 2021 +0200 impossible to get CPU freq on M1 apparently commit66072711c2
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 15:33:43 2021 +0200 detect full commit6bb0e930a2
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 00:38:46 2021 +0200 CPU freq in GHz commita5f10f1a0f
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 15:13:18 2021 +0200 check array length commit155c848b97
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 15:05:20 2021 +0200 switch to other way to get CPU freq (still does not work) commitcf51ba2ebe
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 14:45:44 2021 +0200 remove some warnings commit775dff5f72
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 14:16:45 2021 +0200 fix link commit8c67967775
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 14:16:38 2021 +0200 reduce diff more commit70b47d2ca8
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 14:10:23 2021 +0200 reduce diff with main branch commitca9cb48054
Merge:d0c6c0a
c66b46f
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 13:49:20 2021 +0200 Merge remote-tracking branch 'origin/main' into OSX commitc66b46f850
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 13:41:57 2021 +0200 battery state via CoreFoundation commitd0c6c0a362
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 10:51:36 2021 +0200 all disks + load averages commitca67526dc1
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 10:33:55 2021 +0200 show all disks commit56119f99a9
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 00:17:41 2021 +0200 procs sorting/filtering commit8d86011d72
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 23:42:17 2021 +0200 battery states commita9b64d62e4
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 23:24:59 2021 +0200 battery hack works on M1 commitce51031142
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 23:18:22 2021 +0200 ugly hack to get battery commitd5e6725c6c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 22:42:42 2021 +0200 CPU stuff commit5c02bd8c83
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 21:25:42 2021 +0200 network commitd5da9d4983
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 15:43:05 2021 +0200 correct cached size commit5f11aba504
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 12:03:48 2021 +0200 vm stats from syscall + swap commit776fc96852
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 10:48:07 2021 +0200 seems to work indeed commit7b40e2835a
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 10:46:14 2021 +0200 allow override optimization flag commit005ea24e4c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 10:09:24 2021 +0200 update Makefile commitaf8cec9deb
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 15:32:55 2021 +0200 some more params commit7ebe4f7594
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 15:15:55 2021 +0200 show more disks commite50a56394a
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 14:52:56 2021 +0200 disks show something commit6497a8c202
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 09:15:35 2021 +0200 reformat commit28e152b80c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 23:21:13 2021 +0200 decrease diff with upstream commit40da88e9ca
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 22:56:14 2021 +0200 try to get disks to show commiteaf2bb56a5
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 22:42:01 2021 +0200 don't crash on deque::back() commitf66b6f712c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 22:08:21 2021 +0200 cpu freq, name & process uid/name commit34a8a61f4d
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 21:46:11 2021 +0200 basic process info commit29bb2dcc5f
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 21:45:39 2021 +0200 initialize mutex (needed on macos apparently and not on linux) commitfb5970b000
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 2 23:53:41 2021 +0200 comment commit49d16cdddd
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 2 23:51:29 2021 +0200 extract delimiters commit3db9d66476
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 2 23:48:28 2021 +0200 first infos on macos: memory used & free commitf8acb2f854
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Sep 28 23:37:03 2021 +0200 make it compile on macos (M1 - arm64). Does not run though commitbbba17cd35
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 10:51:36 2021 +0200 all disks + load averages commit548203e93d
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 10:33:55 2021 +0200 show all disks commit0ab2be3985
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Oct 6 00:17:41 2021 +0200 procs sorting/filtering commit096104c90b
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 23:42:17 2021 +0200 battery states commit0ad93684c2
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 23:24:59 2021 +0200 battery hack works on M1 commitc75b0f1cea
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 23:18:22 2021 +0200 ugly hack to get battery commit600b4f72b3
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 22:42:42 2021 +0200 CPU stuff commit4eb812d52c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 21:25:42 2021 +0200 network commit899be68a78
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 15:43:05 2021 +0200 correct cached size commita1c7f935e3
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 12:03:48 2021 +0200 vm stats from syscall + swap commitbd1050a740
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 10:48:07 2021 +0200 seems to work indeed commit5094b73758
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 10:46:14 2021 +0200 allow override optimization flag commit8811270332
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Oct 5 10:09:24 2021 +0200 update Makefile commit42f966f448
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 15:32:55 2021 +0200 some more params commitc1e6d6a62e
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 15:15:55 2021 +0200 show more disks commit50fcdaa854
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 14:52:56 2021 +0200 disks show something commit264bf2d7da
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Mon Oct 4 09:15:35 2021 +0200 reformat commit1fd625086b
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 23:21:13 2021 +0200 decrease diff with upstream commit17f9f3703c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 22:56:14 2021 +0200 try to get disks to show commit8462ae6431
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 22:42:01 2021 +0200 don't crash on deque::back() commit78bce5b5a6
Merge:53e379d
f9505a4
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 22:08:34 2021 +0200 Merge branch 'aristocratos:main' into main commit53e379d74d
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 22:08:21 2021 +0200 cpu freq, name & process uid/name commit2a44b307ef
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 21:46:11 2021 +0200 basic process info commit66534eb5b5
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sun Oct 3 21:45:39 2021 +0200 initialize mutex (needed on macos apparently and not on linux) commit0983917f26
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 2 23:53:41 2021 +0200 comment commit9732507248
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 2 23:51:29 2021 +0200 extract delimiters commit6e704ce838
Merge:fe4db7c
7bfbd83
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 2 23:48:43 2021 +0200 Merge branch 'main' of github.com:joske/btop commitfe4db7c16c
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Sat Oct 2 23:48:28 2021 +0200 first infos on macos: memory used & free commit7bfbd83a47
Merge:8c8139b
a15f961
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Fri Oct 1 17:05:26 2021 +0200 Merge branch 'aristocratos:main' into main commit8c8139bd1d
Merge:679d21c
a246c09
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Sep 29 21:50:00 2021 +0200 Merge branch 'aristocratos:main' into main commit679d21cd22
Merge:4c70c5b
a49b8f9
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Sep 29 20:23:34 2021 +0200 Merge branch 'aristocratos:main' into main commit4c70c5bdd9
Merge:84a9746
c70667e
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Wed Sep 29 08:30:05 2021 +0200 Merge branch 'aristocratos:main' into main commit84a974695a
Author: Jos Dehaes <jos.dehaes@gmail.com> Date: Tue Sep 28 23:37:03 2021 +0200 make it compile on macos (M1 - arm64). Does not run though Co-authored-by: Jos Dehaes <jos.dehaes@gmail.com>
1405 lines
48 KiB
C++
1405 lines
48 KiB
C++
/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
indent = tab
|
|
tab-size = 4
|
|
*/
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <arpa/inet.h>
|
|
#include <ifaddrs.h>
|
|
#include <libproc.h>
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_host.h>
|
|
#include <mach/mach_init.h>
|
|
#include <mach/mach_types.h>
|
|
#include <mach/processor_info.h>
|
|
#include <mach/vm_statistics.h>
|
|
#include <mach/mach_time.h>
|
|
#include <net/if.h>
|
|
#include <net/if_dl.h>
|
|
#include <netdb.h>
|
|
#include <netinet/tcp_fsm.h>
|
|
#include <pwd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <stdexcept>
|
|
|
|
#include <btop_config.hpp>
|
|
#include <btop_shared.hpp>
|
|
#include <btop_tools.hpp>
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <numeric>
|
|
#include <ranges>
|
|
#include <regex>
|
|
#include <string>
|
|
|
|
#include "sensors.hpp"
|
|
#include "smc.hpp"
|
|
|
|
using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater;
|
|
using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min;
|
|
namespace fs = std::filesystem;
|
|
namespace rng = std::ranges;
|
|
using namespace Tools;
|
|
|
|
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
|
|
|
namespace Cpu {
|
|
vector<long long> core_old_totals;
|
|
vector<long long> core_old_idles;
|
|
vector<string> available_fields = {"total"};
|
|
vector<string> available_sensors = {"Auto"};
|
|
cpu_info current_cpu;
|
|
fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq";
|
|
bool got_sensors = false, cpu_temp_only = false;
|
|
|
|
//* Populate found_sensors map
|
|
bool get_sensors();
|
|
|
|
//* Get current cpu clock speed
|
|
string get_cpuHz();
|
|
|
|
//* Search /proc/cpuinfo for a cpu name
|
|
string get_cpuName();
|
|
|
|
struct Sensor {
|
|
fs::path path;
|
|
string label;
|
|
int64_t temp = 0;
|
|
int64_t high = 0;
|
|
int64_t crit = 0;
|
|
};
|
|
|
|
string cpu_sensor;
|
|
vector<string> core_sensors;
|
|
unordered_flat_map<int, int> core_mapping;
|
|
} // namespace Cpu
|
|
|
|
namespace Mem {
|
|
double old_uptime;
|
|
}
|
|
|
|
class MachProcessorInfo {
|
|
public:
|
|
processor_info_array_t info_array;
|
|
mach_msg_type_number_t info_count;
|
|
MachProcessorInfo() {}
|
|
virtual ~MachProcessorInfo() {vm_deallocate(mach_task_self(), (vm_address_t)info_array, (vm_size_t)sizeof(processor_info_array_t) * info_count);}
|
|
};
|
|
|
|
namespace Shared {
|
|
|
|
fs::path passwd_path;
|
|
uint64_t totalMem;
|
|
long pageSize, coreCount, clkTck, physicalCoreCount, arg_max;
|
|
double machTck;
|
|
int totalMem_len;
|
|
|
|
void init() {
|
|
//? Shared global variables init
|
|
|
|
coreCount = sysconf(_SC_NPROCESSORS_ONLN); // this returns all logical cores (threads)
|
|
if (coreCount < 1) {
|
|
coreCount = 1;
|
|
Logger::warning("Could not determine number of cores, defaulting to 1.");
|
|
}
|
|
|
|
size_t physicalCoreCountSize = sizeof(physicalCoreCount);
|
|
if (sysctlbyname("hw.physicalcpu", &physicalCoreCount, &physicalCoreCountSize, NULL, 0) < 0) {
|
|
Logger::error("Could not get physical core count");
|
|
}
|
|
|
|
pageSize = sysconf(_SC_PAGE_SIZE);
|
|
if (pageSize <= 0) {
|
|
pageSize = 4096;
|
|
Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect.");
|
|
}
|
|
|
|
mach_timebase_info_data_t convf;
|
|
if (mach_timebase_info(&convf) == KERN_SUCCESS) {
|
|
machTck = convf.numer / convf.denom;
|
|
} else {
|
|
Logger::warning("Could not get mach clock tick conversion factor. Defaulting to 100, processes cpu usage might be incorrect.");
|
|
machTck = 100;
|
|
}
|
|
|
|
clkTck = sysconf(_SC_CLK_TCK);
|
|
if (clkTck <= 0) {
|
|
clkTck = 100;
|
|
Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect.");
|
|
}
|
|
|
|
int64_t memsize = 0;
|
|
size_t size = sizeof(memsize);
|
|
if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) < 0) {
|
|
Logger::warning("Could not get memory size");
|
|
}
|
|
totalMem = memsize;
|
|
|
|
//* Get maximum length of process arguments
|
|
arg_max = sysconf(_SC_ARG_MAX);
|
|
|
|
//? Init for namespace Cpu
|
|
if (not fs::exists(Cpu::freq_path) or access(Cpu::freq_path.c_str(), R_OK) == -1) Cpu::freq_path.clear();
|
|
Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {});
|
|
Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {});
|
|
Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0);
|
|
Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0);
|
|
Cpu::collect();
|
|
for (auto &[field, vec] : Cpu::current_cpu.cpu_percent) {
|
|
if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field);
|
|
}
|
|
Cpu::cpuName = Cpu::get_cpuName();
|
|
Cpu::got_sensors = Cpu::get_sensors();
|
|
Cpu::core_mapping = Cpu::get_core_mapping();
|
|
|
|
//? Init for namespace Mem
|
|
Mem::old_uptime = system_uptime();
|
|
Mem::collect();
|
|
}
|
|
|
|
} // namespace Shared
|
|
|
|
namespace Cpu {
|
|
string cpuName;
|
|
string cpuHz;
|
|
bool has_battery = true;
|
|
bool macM1 = false;
|
|
tuple<int, long, string> current_bat;
|
|
|
|
const array<string, 10> time_names = {"user", "nice", "system", "idle"};
|
|
|
|
unordered_flat_map<string, long long> cpu_old = {
|
|
{"totals", 0},
|
|
{"idles", 0},
|
|
{"user", 0},
|
|
{"nice", 0},
|
|
{"system", 0},
|
|
{"idle", 0}
|
|
};
|
|
|
|
string get_cpuName() {
|
|
string name;
|
|
char buffer[1024];
|
|
size_t size = sizeof(buffer);
|
|
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &size, NULL, 0) < 0) {
|
|
Logger::error("Failed to get CPU name");
|
|
return name;
|
|
}
|
|
name = string(buffer);
|
|
|
|
auto name_vec = ssplit(name);
|
|
|
|
if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) {
|
|
auto cpu_pos = v_index(name_vec, "CPU"s);
|
|
if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')'))
|
|
name = name_vec.at(cpu_pos + 1);
|
|
else
|
|
name.clear();
|
|
} else if (v_contains(name_vec, "Ryzen"s)) {
|
|
auto ryz_pos = v_index(name_vec, "Ryzen"s);
|
|
name = "Ryzen" + (ryz_pos < name_vec.size() - 1 ? ' ' + name_vec.at(ryz_pos + 1) : "") + (ryz_pos < name_vec.size() - 2 ? ' ' + name_vec.at(ryz_pos + 2) : "");
|
|
} else if (s_contains(name, "Intel"s) and v_contains(name_vec, "CPU"s)) {
|
|
auto cpu_pos = v_index(name_vec, "CPU"s);
|
|
if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1) != "@")
|
|
name = name_vec.at(cpu_pos + 1);
|
|
else
|
|
name.clear();
|
|
} else
|
|
name.clear();
|
|
|
|
if (name.empty() and not name_vec.empty()) {
|
|
for (const auto &n : name_vec) {
|
|
if (n == "@") break;
|
|
name += n + ' ';
|
|
}
|
|
name.pop_back();
|
|
for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Core"}) {
|
|
name = s_replace(name, replace, "");
|
|
name = s_replace(name, " ", " ");
|
|
}
|
|
name = trim(name);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
bool get_sensors() {
|
|
got_sensors = false;
|
|
if (Config::getB("show_coretemp") and Config::getB("check_temp")) {
|
|
ThermalSensors sensors;
|
|
if (sensors.getSensors() > 0) {
|
|
got_sensors = true;
|
|
cpu_temp_only = true;
|
|
macM1 = true;
|
|
} else {
|
|
// try SMC (intel)
|
|
SMCConnection smcCon;
|
|
try {
|
|
long long t = smcCon.getTemp(-1); // check if we have package T
|
|
if (t > -1) {
|
|
got_sensors = true;
|
|
} else {
|
|
got_sensors = false;
|
|
}
|
|
} catch (std::runtime_error &e) {
|
|
// ignore, we don't have temp
|
|
got_sensors = false;
|
|
}
|
|
}
|
|
}
|
|
return got_sensors;
|
|
}
|
|
|
|
void update_sensors() {
|
|
current_cpu.temp_max = 95; // we have no idea how to get the critical temp
|
|
try {
|
|
if (macM1) {
|
|
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();
|
|
|
|
} else {
|
|
SMCConnection smcCon;
|
|
int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount;
|
|
long long packageT = smcCon.getTemp(-1); // -1 returns package T
|
|
current_cpu.temp.at(0).push_back(packageT);
|
|
|
|
if (Config::getB("show_coretemp") and not cpu_temp_only) {
|
|
for (int core = 0; core < Shared::coreCount; core++) {
|
|
long long temp = smcCon.getTemp(core / threadsPerCore); // same temp for all threads of same physical core
|
|
if (cmp_less(core + 1, current_cpu.temp.size())) {
|
|
current_cpu.temp.at(core + 1).push_back(temp);
|
|
if (current_cpu.temp.at(core + 1).size() > 20)
|
|
current_cpu.temp.at(core + 1).pop_front();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (std::runtime_error &e) {
|
|
got_sensors = false;
|
|
Logger::error("failed getting CPU temp");
|
|
}
|
|
}
|
|
|
|
string get_cpuHz() {
|
|
unsigned int freq = 1;
|
|
size_t size = sizeof(freq);
|
|
|
|
int mib[] = {CTL_HW, HW_CPU_FREQ};
|
|
|
|
if (sysctl(mib, 2, &freq, &size, NULL, 0) < 0) {
|
|
// this fails on Apple Silicon macs. Apparently you're not allowed to know
|
|
return "";
|
|
}
|
|
return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3);
|
|
}
|
|
|
|
auto get_core_mapping() -> unordered_flat_map<int, int> {
|
|
unordered_flat_map<int, int> core_map;
|
|
if (cpu_temp_only) return core_map;
|
|
|
|
natural_t cpu_count;
|
|
natural_t i;
|
|
MachProcessorInfo info {};
|
|
kern_return_t error;
|
|
|
|
error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
|
|
if (error != KERN_SUCCESS) {
|
|
Logger::error("Failed getting CPU info");
|
|
return core_map;
|
|
}
|
|
for (i = 0; i < cpu_count; i++) {
|
|
core_map[i] = i;
|
|
}
|
|
|
|
//? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely, map 0-0 1-1 2-2 etc.
|
|
if (cmp_less(core_map.size(), Shared::coreCount)) {
|
|
if (Shared::coreCount % 2 == 0 and (long) core_map.size() == Shared::coreCount / 2) {
|
|
for (int i = 0, n = 0; i < Shared::coreCount / 2; i++) {
|
|
if (std::cmp_greater_equal(n, core_sensors.size())) n = 0;
|
|
core_map[Shared::coreCount / 2 + i] = n++;
|
|
}
|
|
} else {
|
|
core_map.clear();
|
|
for (int i = 0, n = 0; i < Shared::coreCount; i++) {
|
|
if (std::cmp_greater_equal(n, core_sensors.size())) n = 0;
|
|
core_map[i] = n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//? Apply user set custom mapping if any
|
|
const auto &custom_map = Config::getS("cpu_core_map");
|
|
if (not custom_map.empty()) {
|
|
try {
|
|
for (const auto &split : ssplit(custom_map)) {
|
|
const auto vals = ssplit(split, ':');
|
|
if (vals.size() != 2) continue;
|
|
int change_id = std::stoi(vals.at(0));
|
|
int new_id = std::stoi(vals.at(1));
|
|
if (not core_map.contains(change_id) or cmp_greater(new_id, core_sensors.size())) continue;
|
|
core_map.at(change_id) = new_id;
|
|
}
|
|
} catch (...) {
|
|
}
|
|
}
|
|
|
|
return core_map;
|
|
}
|
|
|
|
class IOPSInfo_Wrap {
|
|
CFTypeRef data;
|
|
public:
|
|
IOPSInfo_Wrap() { data = IOPSCopyPowerSourcesInfo(); }
|
|
CFTypeRef& operator()() { return data; }
|
|
~IOPSInfo_Wrap() { CFRelease(data); }
|
|
};
|
|
|
|
class IOPSList_Wrap {
|
|
CFArrayRef data;
|
|
public:
|
|
IOPSList_Wrap(CFTypeRef cft_ref) { data = IOPSCopyPowerSourcesList(cft_ref); }
|
|
CFArrayRef& operator()() { return data; }
|
|
~IOPSList_Wrap() { CFRelease(data); }
|
|
};
|
|
|
|
auto get_battery() -> tuple<int, long, string> {
|
|
if (not has_battery) return {0, 0, ""};
|
|
|
|
uint32_t percent = -1;
|
|
long seconds = -1;
|
|
string status = "discharging";
|
|
IOPSInfo_Wrap ps_info{};
|
|
if (ps_info()) {
|
|
IOPSList_Wrap one_ps_descriptor(ps_info());
|
|
if (one_ps_descriptor()) {
|
|
if (CFArrayGetCount(one_ps_descriptor())) {
|
|
CFDictionaryRef one_ps = IOPSGetPowerSourceDescription(ps_info(), CFArrayGetValueAtIndex(one_ps_descriptor(), 0));
|
|
has_battery = true;
|
|
CFNumberRef remaining = (CFNumberRef)CFDictionaryGetValue(one_ps, CFSTR(kIOPSTimeToEmptyKey));
|
|
int32_t estimatedMinutesRemaining;
|
|
if (remaining) {
|
|
CFNumberGetValue(remaining, kCFNumberSInt32Type, &estimatedMinutesRemaining);
|
|
seconds = estimatedMinutesRemaining * 60;
|
|
}
|
|
CFNumberRef charge = (CFNumberRef)CFDictionaryGetValue(one_ps, CFSTR(kIOPSCurrentCapacityKey));
|
|
if (charge) {
|
|
CFNumberGetValue(charge, kCFNumberSInt32Type, &percent);
|
|
}
|
|
CFBooleanRef charging = (CFBooleanRef)CFDictionaryGetValue(one_ps, CFSTR(kIOPSIsChargingKey));
|
|
if (charging) {
|
|
bool isCharging = CFBooleanGetValue(charging);
|
|
if (isCharging) {
|
|
status = "charging";
|
|
}
|
|
}
|
|
if (percent == 100) {
|
|
status = "full";
|
|
}
|
|
} else {
|
|
has_battery = false;
|
|
}
|
|
} else {
|
|
has_battery = false;
|
|
}
|
|
}
|
|
return {percent, seconds, status};
|
|
}
|
|
|
|
auto collect(const bool no_update) -> cpu_info & {
|
|
if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty()))
|
|
return current_cpu;
|
|
auto &cpu = current_cpu;
|
|
|
|
double avg[3];
|
|
|
|
if (getloadavg(avg, sizeof(avg)) < 0) {
|
|
Logger::error("failed to get load averages");
|
|
}
|
|
|
|
cpu.load_avg = { (float)avg[0], (float)avg[1], (float)avg[2]};
|
|
|
|
natural_t cpu_count;
|
|
natural_t i;
|
|
kern_return_t error;
|
|
processor_cpu_load_info_data_t *cpu_load_info = NULL;
|
|
|
|
MachProcessorInfo info{};
|
|
error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
|
|
if (error != KERN_SUCCESS) {
|
|
Logger::error("Failed getting CPU load info");
|
|
}
|
|
cpu_load_info = (processor_cpu_load_info_data_t *)info.info_array;
|
|
long long global_totals = 0;
|
|
long long global_idles = 0;
|
|
vector<long long> times_summed = {0, 0, 0, 0};
|
|
for (i = 0; i < cpu_count; i++) {
|
|
vector<long long> times;
|
|
//? 0=user, 1=nice, 2=system, 3=idle
|
|
for (int x = 0; const unsigned int c_state : {CPU_STATE_USER, CPU_STATE_NICE, CPU_STATE_SYSTEM, CPU_STATE_IDLE}) {
|
|
auto val = cpu_load_info[i].cpu_ticks[c_state];
|
|
times.push_back(val);
|
|
times_summed.at(x++) += val;
|
|
}
|
|
|
|
try {
|
|
//? All values
|
|
const long long totals = std::accumulate(times.begin(), times.end(), 0ll);
|
|
|
|
//? Idle time
|
|
const long long idles = times.at(3);
|
|
|
|
global_totals += totals;
|
|
global_idles += idles;
|
|
|
|
//? Calculate cpu total for each core
|
|
if (i > Shared::coreCount) break;
|
|
const long long calc_totals = max(0ll, totals - core_old_totals.at(i));
|
|
const long long calc_idles = max(0ll, idles - core_old_idles.at(i));
|
|
core_old_totals.at(i) = totals;
|
|
core_old_idles.at(i) = idles;
|
|
|
|
cpu.core_percent.at(i).push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll));
|
|
|
|
//? Reduce size if there are more values than needed for graph
|
|
if (cpu.core_percent.at(i).size() > 40) cpu.core_percent.at(i).pop_front();
|
|
|
|
} catch (const std::exception &e) {
|
|
Logger::error("Cpu::collect() : " + (string)e.what());
|
|
throw std::runtime_error("collect() : " + (string)e.what());
|
|
}
|
|
}
|
|
|
|
const long long calc_totals = max(1ll, global_totals - cpu_old.at("totals"));
|
|
const long long calc_idles = max(1ll, global_idles - cpu_old.at("idles"));
|
|
|
|
//? Populate cpu.cpu_percent with all fields from syscall
|
|
for (int ii = 0; const auto &val : times_summed) {
|
|
cpu.cpu_percent.at(time_names.at(ii)).push_back(clamp((long long)round((double)(val - cpu_old.at(time_names.at(ii))) * 100 / calc_totals), 0ll, 100ll));
|
|
cpu_old.at(time_names.at(ii)) = val;
|
|
|
|
//? Reduce size if there are more values than needed for graph
|
|
while (cmp_greater(cpu.cpu_percent.at(time_names.at(ii)).size(), width * 2)) cpu.cpu_percent.at(time_names.at(ii)).pop_front();
|
|
|
|
ii++;
|
|
}
|
|
|
|
cpu_old.at("totals") = global_totals;
|
|
cpu_old.at("idles") = global_idles;
|
|
|
|
//? Total usage of cpu
|
|
cpu.cpu_percent.at("total").push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll));
|
|
|
|
//? Reduce size if there are more values than needed for graph
|
|
while (cmp_greater(cpu.cpu_percent.at("total").size(), width * 2)) cpu.cpu_percent.at("total").pop_front();
|
|
|
|
if (Config::getB("show_cpu_freq")) {
|
|
auto hz = get_cpuHz();
|
|
if (hz != "") {
|
|
cpuHz = hz;
|
|
}
|
|
}
|
|
|
|
if (Config::getB("check_temp") and got_sensors)
|
|
update_sensors();
|
|
|
|
if (Config::getB("show_battery") and has_battery)
|
|
current_bat = get_battery();
|
|
|
|
return cpu;
|
|
}
|
|
} // namespace Cpu
|
|
|
|
namespace Mem {
|
|
bool has_swap = false;
|
|
vector<string> fstab;
|
|
fs::file_time_type fstab_time;
|
|
int disk_ios = 0;
|
|
vector<string> last_found;
|
|
|
|
mem_info current_mem{};
|
|
|
|
uint64_t get_totalMem() {
|
|
return Shared::totalMem;
|
|
}
|
|
|
|
int64_t getCFNumber(CFDictionaryRef dict, const void *key) {
|
|
CFNumberRef ref = (CFNumberRef)CFDictionaryGetValue(dict, key);
|
|
if (ref) {
|
|
int64_t value;
|
|
CFNumberGetValue(ref, kCFNumberSInt64Type, &value);
|
|
return value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
string getCFString(io_registry_entry_t volumeRef, CFStringRef key) {
|
|
CFStringRef bsdNameRef = (CFStringRef)IORegistryEntryCreateCFProperty(volumeRef, key, kCFAllocatorDefault, 0);
|
|
if (bsdNameRef) {
|
|
char buf[200];
|
|
CFStringGetCString(bsdNameRef, buf, 200, kCFStringEncodingASCII);
|
|
CFRelease(bsdNameRef);
|
|
return string(buf);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool isWhole(io_registry_entry_t volumeRef) {
|
|
CFBooleanRef isWhole = (CFBooleanRef)IORegistryEntryCreateCFProperty(volumeRef, CFSTR("Whole"), kCFAllocatorDefault, 0);
|
|
Boolean val = CFBooleanGetValue(isWhole);
|
|
CFRelease(isWhole);
|
|
return bool(val);
|
|
}
|
|
|
|
class IOObject {
|
|
public:
|
|
IOObject(string name, io_object_t& obj) : name(name), object(obj) {}
|
|
virtual ~IOObject() { IOObjectRelease(object); }
|
|
private:
|
|
string name;
|
|
io_object_t &object;
|
|
};
|
|
|
|
void collect_disk(unordered_flat_map<string, disk_info> &disks, unordered_flat_map<string, string> &mapping) {
|
|
io_registry_entry_t drive;
|
|
io_iterator_t drive_list;
|
|
|
|
mach_port_t libtop_master_port;
|
|
if (IOMasterPort(bootstrap_port, &libtop_master_port)) {
|
|
Logger::error("errot getting master port");
|
|
return;
|
|
}
|
|
/* Get the list of all drive objects. */
|
|
if (IOServiceGetMatchingServices(libtop_master_port,
|
|
IOServiceMatching("IOMediaBSDClient"), &drive_list)) {
|
|
Logger::error("Error in IOServiceGetMatchingServices()");
|
|
return;
|
|
}
|
|
auto d = IOObject("drive list", drive_list); // dummy var so it gets destroyed
|
|
while ((drive = IOIteratorNext(drive_list)) != 0) {
|
|
auto dr = IOObject("drive", drive);
|
|
io_registry_entry_t volumeRef;
|
|
IORegistryEntryGetParentEntry(drive, kIOServicePlane, &volumeRef);
|
|
if (volumeRef) {
|
|
if (!isWhole(volumeRef)) {
|
|
string bsdName = getCFString(volumeRef, CFSTR("BSD Name"));
|
|
string device = getCFString(volumeRef, CFSTR("VolGroupMntFromName"));
|
|
if (!mapping.contains(device)) {
|
|
device = "/dev/" + bsdName; // try again with BSD name - not all volumes seem to have VolGroupMntFromName property
|
|
}
|
|
if (device != "") {
|
|
if (mapping.contains(device)) {
|
|
string mountpoint = mapping.at(device);
|
|
if (disks.contains(mountpoint)) {
|
|
auto& disk = disks.at(mountpoint);
|
|
CFDictionaryRef properties;
|
|
IORegistryEntryCreateCFProperties(volumeRef, (CFMutableDictionaryRef *)&properties, kCFAllocatorDefault, 0);
|
|
if (properties) {
|
|
CFDictionaryRef statistics = (CFDictionaryRef)CFDictionaryGetValue(properties, CFSTR("Statistics"));
|
|
if (statistics) {
|
|
disk_ios++;
|
|
int64_t readBytes = getCFNumber(statistics, CFSTR("Bytes read from block device"));
|
|
if (disk.io_read.empty())
|
|
disk.io_read.push_back(0);
|
|
else
|
|
disk.io_read.push_back(max((int64_t)0, (readBytes - disk.old_io.at(0))));
|
|
disk.old_io.at(0) = readBytes;
|
|
while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front();
|
|
|
|
int64_t writeBytes = getCFNumber(statistics, CFSTR("Bytes written to block device"));
|
|
if (disk.io_write.empty())
|
|
disk.io_write.push_back(0);
|
|
else
|
|
disk.io_write.push_back(max((int64_t)0, (writeBytes - disk.old_io.at(1))));
|
|
disk.old_io.at(1) = writeBytes;
|
|
while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front();
|
|
|
|
// IOKit does not give us IO times, (use IO read + IO write with 1 MiB being 100% to get some activity indication)
|
|
if (disk.io_activity.empty())
|
|
disk.io_activity.push_back(0);
|
|
else
|
|
disk.io_activity.push_back(clamp((long)round((double)(disk.io_write.back() + disk.io_read.back()) / (1 << 20)), 0l, 100l));
|
|
while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front();
|
|
}
|
|
}
|
|
CFRelease(properties);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto collect(const bool no_update) -> mem_info & {
|
|
if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty()))
|
|
return current_mem;
|
|
|
|
auto &show_swap = Config::getB("show_swap");
|
|
auto &show_disks = Config::getB("show_disks");
|
|
auto &swap_disk = Config::getB("swap_disk");
|
|
auto &mem = current_mem;
|
|
static const bool snapped = (getenv("BTOP_SNAPPED") != NULL);
|
|
|
|
vm_statistics64 p;
|
|
mach_msg_type_number_t info_size = HOST_VM_INFO64_COUNT;
|
|
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&p, &info_size) == 0) {
|
|
mem.stats.at("free") = p.free_count * Shared::pageSize;
|
|
mem.stats.at("cached") = p.external_page_count * Shared::pageSize;
|
|
mem.stats.at("used") = (p.active_count + p.inactive_count + p.wire_count) * Shared::pageSize;
|
|
mem.stats.at("available") = Shared::totalMem - mem.stats.at("used");
|
|
}
|
|
|
|
int mib[2] = {CTL_VM, VM_SWAPUSAGE};
|
|
|
|
struct xsw_usage swap;
|
|
size_t len = sizeof(struct xsw_usage);
|
|
if (sysctl(mib, 2, &swap, &len, NULL, 0) == 0) {
|
|
mem.stats.at("swap_total") = swap.xsu_total;
|
|
mem.stats.at("swap_free") = swap.xsu_avail;
|
|
mem.stats.at("swap_used") = swap.xsu_used;
|
|
}
|
|
|
|
if (show_swap and mem.stats.at("swap_total") > 0) {
|
|
for (const auto &name : swap_names) {
|
|
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total")));
|
|
while (cmp_greater(mem.percent.at(name).size(), width * 2))
|
|
mem.percent.at(name).pop_front();
|
|
}
|
|
has_swap = true;
|
|
} else
|
|
has_swap = false;
|
|
//? Calculate percentages
|
|
for (const auto &name : mem_names) {
|
|
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / Shared::totalMem));
|
|
while (cmp_greater(mem.percent.at(name).size(), width * 2))
|
|
mem.percent.at(name).pop_front();
|
|
}
|
|
|
|
if (show_disks) {
|
|
unordered_flat_map<string, string> mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint
|
|
double uptime = system_uptime();
|
|
auto &disks_filter = Config::getS("disks_filter");
|
|
bool filter_exclude = false;
|
|
// auto &only_physical = Config::getB("only_physical");
|
|
auto &disks = mem.disks;
|
|
vector<string> filter;
|
|
if (not disks_filter.empty()) {
|
|
filter = ssplit(disks_filter);
|
|
if (filter.at(0).starts_with("exclude=")) {
|
|
filter_exclude = true;
|
|
filter.at(0) = filter.at(0).substr(8);
|
|
}
|
|
}
|
|
|
|
struct statfs *stfs;
|
|
int count = getmntinfo(&stfs, MNT_WAIT);
|
|
vector<string> found;
|
|
found.reserve(last_found.size());
|
|
for (int i = 0; i < count; i++) {
|
|
std::error_code ec;
|
|
string mountpoint = stfs[i].f_mntonname;
|
|
string dev = stfs[i].f_mntfromname;
|
|
mapping[dev] = mountpoint;
|
|
|
|
if (string(stfs[i].f_fstypename) == "autofs") {
|
|
continue;
|
|
}
|
|
|
|
//? Match filter if not empty
|
|
if (not filter.empty()) {
|
|
bool match = v_contains(filter, mountpoint);
|
|
if ((filter_exclude and match) or (not filter_exclude and not match))
|
|
continue;
|
|
}
|
|
|
|
found.push_back(mountpoint);
|
|
if (not disks.contains(mountpoint)) {
|
|
disks[mountpoint] = disk_info{fs::canonical(dev, ec), fs::path(mountpoint).filename()};
|
|
|
|
if (disks.at(mountpoint).dev.empty())
|
|
disks.at(mountpoint).dev = dev;
|
|
|
|
if (disks.at(mountpoint).name.empty())
|
|
disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint);
|
|
}
|
|
|
|
|
|
if (not v_contains(last_found, mountpoint))
|
|
redraw = true;
|
|
|
|
disks.at(mountpoint).free = stfs[i].f_bfree;
|
|
disks.at(mountpoint).total = stfs[i].f_iosize;
|
|
}
|
|
|
|
//? Remove disks no longer mounted or filtered out
|
|
if (swap_disk and has_swap) found.push_back("swap");
|
|
for (auto it = disks.begin(); it != disks.end();) {
|
|
if (not v_contains(found, it->first))
|
|
it = disks.erase(it);
|
|
else
|
|
it++;
|
|
}
|
|
if (found.size() != last_found.size()) redraw = true;
|
|
last_found = std::move(found);
|
|
|
|
//? Get disk/partition stats
|
|
for (auto &[mountpoint, disk] : disks) {
|
|
if (std::error_code ec; not fs::exists(mountpoint, ec))
|
|
continue;
|
|
struct statvfs vfs;
|
|
if (statvfs(mountpoint.c_str(), &vfs) < 0) {
|
|
Logger::warning("Failed to get disk/partition stats with statvfs() for: " + mountpoint);
|
|
continue;
|
|
}
|
|
disk.total = vfs.f_blocks * vfs.f_frsize;
|
|
disk.free = vfs.f_bfree * vfs.f_frsize;
|
|
disk.used = disk.total - disk.free;
|
|
disk.used_percent = round((double)disk.used * 100 / disk.total);
|
|
disk.free_percent = 100 - disk.used_percent;
|
|
}
|
|
|
|
//? Setup disks order in UI and add swap if enabled
|
|
mem.disks_order.clear();
|
|
if (snapped and disks.contains("/mnt"))
|
|
mem.disks_order.push_back("/mnt");
|
|
else if (disks.contains("/"))
|
|
mem.disks_order.push_back("/");
|
|
if (swap_disk and has_swap) {
|
|
mem.disks_order.push_back("swap");
|
|
if (not disks.contains("swap"))
|
|
disks["swap"] = {"", "swap"};
|
|
disks.at("swap").total = mem.stats.at("swap_total");
|
|
disks.at("swap").used = mem.stats.at("swap_used");
|
|
disks.at("swap").free = mem.stats.at("swap_free");
|
|
disks.at("swap").used_percent = mem.percent.at("swap_used").back();
|
|
disks.at("swap").free_percent = mem.percent.at("swap_free").back();
|
|
}
|
|
for (const auto &name : last_found)
|
|
if (not is_in(name, "/", "swap", "/dev"))
|
|
mem.disks_order.push_back(name);
|
|
|
|
disk_ios = 0;
|
|
collect_disk(disks, mapping);
|
|
|
|
old_uptime = uptime;
|
|
}
|
|
return mem;
|
|
}
|
|
|
|
} // namespace Mem
|
|
|
|
namespace Net {
|
|
unordered_flat_map<string, net_info> current_net;
|
|
net_info empty_net = {};
|
|
vector<string> interfaces;
|
|
string selected_iface;
|
|
int errors = 0;
|
|
unordered_flat_map<string, uint64_t> graph_max = {{"download", {}}, {"upload", {}}};
|
|
unordered_flat_map<string, array<int, 2>> max_count = {{"download", {}}, {"upload", {}}};
|
|
bool rescale = true;
|
|
uint64_t timestamp = 0;
|
|
|
|
//* RAII wrapper for getifaddrs
|
|
class getifaddr_wrapper {
|
|
struct ifaddrs *ifaddr;
|
|
|
|
public:
|
|
int status;
|
|
getifaddr_wrapper() { status = getifaddrs(&ifaddr); }
|
|
~getifaddr_wrapper() { freeifaddrs(ifaddr); }
|
|
auto operator()() -> struct ifaddrs * { return ifaddr; }
|
|
};
|
|
|
|
auto collect(const bool no_update) -> net_info & {
|
|
auto &net = current_net;
|
|
auto &config_iface = Config::getS("net_iface");
|
|
auto &net_sync = Config::getB("net_sync");
|
|
auto &net_auto = Config::getB("net_auto");
|
|
auto new_timestamp = time_ms();
|
|
|
|
if (not no_update and errors < 3) {
|
|
//? Get interface list using getifaddrs() wrapper
|
|
getifaddr_wrapper if_wrap{};
|
|
if (if_wrap.status != 0) {
|
|
errors++;
|
|
Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status));
|
|
redraw = true;
|
|
return empty_net;
|
|
}
|
|
int family = 0;
|
|
char ip[NI_MAXHOST];
|
|
interfaces.clear();
|
|
string ipv4, ipv6;
|
|
|
|
//? Iteration over all items in getifaddrs() list
|
|
for (auto *ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) {
|
|
if (ifa->ifa_addr == NULL) continue;
|
|
family = ifa->ifa_addr->sa_family;
|
|
const auto &iface = ifa->ifa_name;
|
|
//? Get IPv4 address
|
|
if (family == AF_INET) {
|
|
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
|
|
net[iface].ipv4 = ip;
|
|
}
|
|
//? Get IPv6 address
|
|
else if (family == AF_INET6) {
|
|
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
|
|
net[iface].ipv6 = ip;
|
|
}
|
|
|
|
//? Update available interfaces vector and get status of interface
|
|
if (not v_contains(interfaces, iface)) {
|
|
interfaces.push_back(iface);
|
|
net[iface].connected = (ifa->ifa_flags & IFF_RUNNING);
|
|
}
|
|
}
|
|
|
|
unordered_flat_map<string, std::tuple<uint64_t, uint64_t>> ifstats;
|
|
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0};
|
|
size_t len;
|
|
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
|
|
Logger::error("failed getting network interfaces");
|
|
} else {
|
|
std::unique_ptr<char[]> buf(new char[len]);
|
|
if (sysctl(mib, 6, buf.get(), &len, NULL, 0) < 0) {
|
|
Logger::error("failed getting network interfaces");
|
|
} else {
|
|
char *lim = buf.get() + len;
|
|
char *next = NULL;
|
|
for (next = buf.get(); next < lim;) {
|
|
struct if_msghdr *ifm = (struct if_msghdr *)next;
|
|
next += ifm->ifm_msglen;
|
|
if (ifm->ifm_type == RTM_IFINFO2) {
|
|
struct if_msghdr2 *if2m = (struct if_msghdr2 *)ifm;
|
|
struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1);
|
|
char iface[32];
|
|
strncpy(iface, sdl->sdl_data, sdl->sdl_nlen);
|
|
iface[sdl->sdl_nlen] = 0;
|
|
ifstats[iface] = std::tuple(if2m->ifm_data.ifi_ibytes, if2m->ifm_data.ifi_obytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//? Get total recieved and transmitted bytes + device address if no ip was found
|
|
for (const auto &iface : interfaces) {
|
|
for (const string dir : {"download", "upload"}) {
|
|
auto &saved_stat = net.at(iface).stat.at(dir);
|
|
auto &bandwidth = net.at(iface).bandwidth.at(dir);
|
|
auto dirval = dir == "download" ? std::get<0>(ifstats[iface]) : std::get<1>(ifstats[iface]);
|
|
uint64_t val = saved_stat.last;
|
|
try {
|
|
val = max(dirval, val);
|
|
} catch (const std::invalid_argument &) {
|
|
} catch (const std::out_of_range &) {
|
|
}
|
|
|
|
//? Update speed, total and top values
|
|
saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000));
|
|
if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed;
|
|
if (saved_stat.offset > val) saved_stat.offset = 0;
|
|
saved_stat.total = val - saved_stat.offset;
|
|
saved_stat.last = val;
|
|
|
|
//? Add values to graph
|
|
bandwidth.push_back(saved_stat.speed);
|
|
while (cmp_greater(bandwidth.size(), width * 2)) bandwidth.pop_front();
|
|
|
|
//? Set counters for auto scaling
|
|
if (net_auto and selected_iface == iface) {
|
|
if (saved_stat.speed > graph_max[dir]) {
|
|
++max_count[dir][0];
|
|
if (max_count[dir][1] > 0) --max_count[dir][1];
|
|
} else if (graph_max[dir] > 10 << 10 and saved_stat.speed < graph_max[dir] / 10) {
|
|
++max_count[dir][1];
|
|
if (max_count[dir][0] > 0) --max_count[dir][0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//? Clean up net map if needed
|
|
if (net.size() > interfaces.size()) {
|
|
for (auto it = net.begin(); it != net.end();) {
|
|
if (not v_contains(interfaces, it->first))
|
|
it = net.erase(it);
|
|
else
|
|
it++;
|
|
}
|
|
net.compact();
|
|
}
|
|
|
|
timestamp = new_timestamp;
|
|
}
|
|
//? Return empty net_info struct if no interfaces was found
|
|
if (net.empty())
|
|
return empty_net;
|
|
|
|
//? Find an interface to display if selected isn't set or valid
|
|
if (selected_iface.empty() or not v_contains(interfaces, selected_iface)) {
|
|
max_count["download"][0] = max_count["download"][1] = max_count["upload"][0] = max_count["upload"][1] = 0;
|
|
redraw = true;
|
|
if (net_auto) rescale = true;
|
|
if (not config_iface.empty() and v_contains(interfaces, config_iface))
|
|
selected_iface = config_iface;
|
|
else {
|
|
//? Sort interfaces by total upload + download bytes
|
|
auto sorted_interfaces = interfaces;
|
|
rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) {
|
|
return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total,
|
|
net.at(b).stat["download"].total + net.at(b).stat["upload"].total);
|
|
});
|
|
selected_iface.clear();
|
|
//? Try to set to a connected interface
|
|
for (const auto &iface : sorted_interfaces) {
|
|
if (net.at(iface).connected) selected_iface = iface;
|
|
break;
|
|
}
|
|
//? If no interface is connected set to first available
|
|
if (selected_iface.empty() and not sorted_interfaces.empty())
|
|
selected_iface = sorted_interfaces.at(0);
|
|
else if (sorted_interfaces.empty())
|
|
return empty_net;
|
|
}
|
|
}
|
|
|
|
//? Calculate max scale for graphs if needed
|
|
if (net_auto) {
|
|
bool sync = false;
|
|
for (const auto &dir : {"download", "upload"}) {
|
|
for (const auto &sel : {0, 1}) {
|
|
if (rescale or max_count[dir][sel] >= 5) {
|
|
const uint64_t avg_speed = (net[selected_iface].bandwidth[dir].size() > 5
|
|
? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0) / 5
|
|
: net[selected_iface].stat[dir].speed);
|
|
graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10);
|
|
max_count[dir][0] = max_count[dir][1] = 0;
|
|
redraw = true;
|
|
if (net_sync) sync = true;
|
|
break;
|
|
}
|
|
}
|
|
//? Sync download/upload graphs if enabled
|
|
if (sync) {
|
|
const auto other = (string(dir) == "upload" ? "download" : "upload");
|
|
graph_max[other] = graph_max[dir];
|
|
max_count[other][0] = max_count[other][1] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
rescale = false;
|
|
return net.at(selected_iface);
|
|
}
|
|
} // namespace Net
|
|
|
|
namespace Proc {
|
|
|
|
vector<proc_info> current_procs;
|
|
unordered_flat_map<string, string> uid_user;
|
|
string current_sort;
|
|
string current_filter;
|
|
bool current_rev = false;
|
|
|
|
fs::file_time_type passwd_time;
|
|
|
|
uint64_t cputimes;
|
|
int collapse = -1, expand = -1;
|
|
uint64_t old_cputimes = 0;
|
|
atomic<int> numpids = 0;
|
|
int filter_found = 0;
|
|
|
|
detail_container detailed;
|
|
|
|
//* Generate process tree list
|
|
void _tree_gen(proc_info &cur_proc, vector<proc_info> &in_procs, vector<std::reference_wrapper<proc_info>> &out_procs, int cur_depth, const bool collapsed, const string &filter, bool found = false, const bool no_update = false, const bool should_filter = false) {
|
|
auto cur_pos = out_procs.size();
|
|
bool filtering = false;
|
|
|
|
//? If filtering, include children of matching processes
|
|
if (not found and (should_filter or not filter.empty())) {
|
|
if (not s_contains(std::to_string(cur_proc.pid), filter) and not s_contains(cur_proc.name, filter) and not s_contains(cur_proc.cmd, filter) and not s_contains(cur_proc.user, filter)) {
|
|
filtering = true;
|
|
cur_proc.filtered = true;
|
|
filter_found++;
|
|
} else {
|
|
found = true;
|
|
cur_depth = 0;
|
|
}
|
|
} else if (cur_proc.filtered)
|
|
cur_proc.filtered = false;
|
|
|
|
//? Set tree index position for process if not filtered out or currently in a collapsed sub-tree
|
|
if (not collapsed and not filtering) {
|
|
out_procs.push_back(std::ref(cur_proc));
|
|
cur_proc.tree_index = out_procs.size() - 1;
|
|
//? Try to find name of the binary file and append to program name if not the same
|
|
if (cur_proc.short_cmd.empty() and not cur_proc.cmd.empty()) {
|
|
std::string_view cmd_view = cur_proc.cmd;
|
|
cmd_view = cmd_view.substr((size_t)0, min(cmd_view.find(' '), cmd_view.size()));
|
|
cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
|
|
cur_proc.short_cmd = (string)cmd_view;
|
|
}
|
|
} else {
|
|
cur_proc.tree_index = in_procs.size();
|
|
}
|
|
|
|
//? Recursive iteration over all children
|
|
int children = 0;
|
|
for (auto &p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) {
|
|
if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) {
|
|
out_procs.back().get().cpu_p += p.cpu_p;
|
|
out_procs.back().get().mem += p.mem;
|
|
out_procs.back().get().threads += p.threads;
|
|
filter_found++;
|
|
}
|
|
if (collapsed and not filtering) {
|
|
cur_proc.filtered = true;
|
|
} else
|
|
children++;
|
|
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter);
|
|
}
|
|
if (collapsed or filtering)
|
|
return;
|
|
|
|
//? Add tree terminator symbol if it's the last child in a sub-tree
|
|
if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─"))
|
|
out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ ");
|
|
|
|
//? Add collapse/expand symbols if process have any children
|
|
out_procs.at(cur_pos).get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ ");
|
|
}
|
|
|
|
string get_status(char s) {
|
|
if (s & SRUN) return "Running";
|
|
if (s & SSLEEP) return "Sleeping";
|
|
if (s & SIDL) return "Idle";
|
|
if (s & SSTOP) return "Stopped";
|
|
if (s & SZOMB) return "Zombie";
|
|
return "Unknown";
|
|
}
|
|
|
|
//* Get detailed info for selected process
|
|
void _collect_details(const size_t pid, vector<proc_info> &procs) {
|
|
if (pid != detailed.last_pid) {
|
|
detailed = {};
|
|
detailed.last_pid = pid;
|
|
detailed.skip_smaps = not Config::getB("proc_info_smaps");
|
|
}
|
|
|
|
//? Copy proc_info for process from proc vector
|
|
auto p_info = rng::find(procs, pid, &proc_info::pid);
|
|
detailed.entry = *p_info;
|
|
|
|
//? Update cpu percent deque for process cpu graph
|
|
if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount;
|
|
detailed.cpu_percent.push_back(clamp((long long)round(detailed.entry.cpu_p), 0ll, 100ll));
|
|
while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front();
|
|
|
|
//? Process runtime : current time - start time (both in unix time - seconds since epoch)
|
|
struct timeval currentTime;
|
|
gettimeofday(¤tTime, NULL);
|
|
detailed.elapsed = sec_to_dhms(currentTime.tv_sec - (detailed.entry.cpu_s / 1'000'000));
|
|
if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3);
|
|
|
|
//? Get parent process name
|
|
if (detailed.parent.empty()) {
|
|
auto p_entry = rng::find(procs, detailed.entry.ppid, &proc_info::pid);
|
|
if (p_entry != procs.end()) detailed.parent = p_entry->name;
|
|
}
|
|
|
|
//? Expand process status from single char to explanative string
|
|
detailed.status = get_status(detailed.entry.state);
|
|
|
|
detailed.mem_bytes.push_back(detailed.entry.mem);
|
|
detailed.memory = floating_humanizer(detailed.entry.mem);
|
|
|
|
if (detailed.first_mem == -1 or detailed.first_mem < detailed.mem_bytes.back() / 2 or detailed.first_mem > detailed.mem_bytes.back() * 4) {
|
|
detailed.first_mem = min((uint64_t)detailed.mem_bytes.back() * 2, Mem::get_totalMem());
|
|
redraw = true;
|
|
}
|
|
|
|
while (cmp_greater(detailed.mem_bytes.size(), width)) detailed.mem_bytes.pop_front();
|
|
|
|
rusage_info_current rusage;
|
|
if (proc_pid_rusage(pid, RUSAGE_INFO_CURRENT, (void **)&rusage) == 0) {
|
|
// this fails for processes we don't own - same as in Linux
|
|
detailed.io_read = floating_humanizer(rusage.ri_diskio_bytesread);
|
|
detailed.io_write = floating_humanizer(rusage.ri_diskio_byteswritten);
|
|
}
|
|
}
|
|
|
|
//* Collects and sorts process information from /proc
|
|
auto collect(const bool no_update) -> vector<proc_info> & {
|
|
const auto &sorting = Config::getS("proc_sorting");
|
|
const auto &reverse = Config::getB("proc_reversed");
|
|
const auto &filter = Config::getS("proc_filter");
|
|
const auto &per_core = Config::getB("proc_per_core");
|
|
const auto &tree = Config::getB("proc_tree");
|
|
const auto &show_detailed = Config::getB("show_detailed");
|
|
const size_t detailed_pid = Config::getI("detailed_pid");
|
|
bool should_filter = current_filter != filter;
|
|
if (should_filter) current_filter = filter;
|
|
const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter);
|
|
if (sorted_change) {
|
|
current_sort = sorting;
|
|
current_rev = reverse;
|
|
}
|
|
|
|
const int cmult = (per_core) ? Shared::coreCount : 1;
|
|
bool got_detailed = false;
|
|
|
|
//* Use pids from last update if only changing filter, sorting or tree options
|
|
if (no_update and not current_procs.empty()) {
|
|
if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, current_procs);
|
|
} else {
|
|
//* ---------------------------------------------Collection start----------------------------------------------
|
|
|
|
{ //* Get CPU totals
|
|
natural_t cpu_count;
|
|
kern_return_t error;
|
|
processor_cpu_load_info_data_t *cpu_load_info = NULL;
|
|
MachProcessorInfo info{};
|
|
error = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info.info_array, &info.info_count);
|
|
if (error != KERN_SUCCESS) {
|
|
Logger::error("Failed getting CPU load info");
|
|
}
|
|
cpu_load_info = (processor_cpu_load_info_data_t *)info.info_array;
|
|
cputimes = 0;
|
|
for (natural_t i = 0; i < cpu_count; i++) {
|
|
cputimes += (cpu_load_info[i].cpu_ticks[CPU_STATE_USER]
|
|
+ cpu_load_info[i].cpu_ticks[CPU_STATE_NICE]
|
|
+ cpu_load_info[i].cpu_ticks[CPU_STATE_SYSTEM]
|
|
+ cpu_load_info[i].cpu_ticks[CPU_STATE_IDLE]);
|
|
}
|
|
}
|
|
|
|
should_filter = true;
|
|
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
|
|
vector<size_t> found;
|
|
size_t size = 0;
|
|
const auto timeNow = time_micros();
|
|
|
|
if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) {
|
|
Logger::error("Unable to get size of kproc_infos");
|
|
}
|
|
uint64_t cpu_t = 0;
|
|
|
|
std::unique_ptr<kinfo_proc[]> processes(new kinfo_proc[size / sizeof(kinfo_proc)]);
|
|
if (sysctl(mib, 4, processes.get(), &size, NULL, 0) == 0) {
|
|
size_t count = size / sizeof(struct kinfo_proc);
|
|
for (size_t i = 0; i < count; i++) { //* iterate over all processes in kinfo_proc
|
|
struct kinfo_proc& kproc = processes.get()[i];
|
|
const size_t pid = (size_t)kproc.kp_proc.p_pid;
|
|
if (pid < 1) continue;
|
|
found.push_back(pid);
|
|
|
|
//? Check if pid already exists in current_procs
|
|
bool no_cache = false;
|
|
auto find_old = rng::find(current_procs, pid, &proc_info::pid);
|
|
if (find_old == current_procs.end()) {
|
|
current_procs.push_back({pid});
|
|
find_old = current_procs.end() - 1;
|
|
no_cache = true;
|
|
}
|
|
|
|
auto &new_proc = *find_old;
|
|
|
|
//? Get program name, command, username, parent pid, nice and status
|
|
if (no_cache) {
|
|
char fullname[PROC_PIDPATHINFO_MAXSIZE];
|
|
proc_pidpath(pid, fullname, sizeof(fullname));
|
|
const string f_name = std::string(fullname);
|
|
size_t lastSlash = f_name.find_last_of('/');
|
|
new_proc.name = f_name.substr(lastSlash + 1);
|
|
//? Get process arguments if possible, fallback to process path in case of failure
|
|
if (Shared::arg_max > 0) {
|
|
std::unique_ptr<char[]> proc_chars(new char[Shared::arg_max]);
|
|
int mib[] = {CTL_KERN, KERN_PROCARGS2, (int)pid};
|
|
size_t argmax = Shared::arg_max;
|
|
if (sysctl(mib, 3, proc_chars.get(), &argmax, NULL, 0) == 0) {
|
|
int argc = 0;
|
|
memcpy(&argc, &proc_chars.get()[0], sizeof(argc));
|
|
std::string_view proc_args(proc_chars.get(), argmax);
|
|
if (size_t null_pos = proc_args.find('\0', sizeof(argc)); null_pos != string::npos) {
|
|
if (size_t start_pos = proc_args.find_first_not_of('\0', null_pos); start_pos != string::npos) {
|
|
while (argc-- > 0 and null_pos != string::npos) {
|
|
null_pos = proc_args.find('\0', start_pos);
|
|
new_proc.cmd += (string)proc_args.substr(start_pos, null_pos - start_pos) + ' ';
|
|
start_pos = null_pos + 1;
|
|
}
|
|
}
|
|
}
|
|
if (not new_proc.cmd.empty()) new_proc.cmd.pop_back();
|
|
}
|
|
}
|
|
if (new_proc.cmd.empty()) new_proc.cmd = f_name;
|
|
new_proc.ppid = kproc.kp_eproc.e_ppid;
|
|
new_proc.cpu_s = kproc.kp_proc.p_starttime.tv_sec * 1'000'000 + kproc.kp_proc.p_starttime.tv_usec;
|
|
struct passwd *pwd = getpwuid(kproc.kp_eproc.e_ucred.cr_uid);
|
|
new_proc.user = pwd->pw_name;
|
|
}
|
|
new_proc.p_nice = kproc.kp_proc.p_nice;
|
|
new_proc.state = kproc.kp_proc.p_stat;
|
|
|
|
//? Get threads, mem and cpu usage
|
|
struct proc_taskinfo pti;
|
|
if (sizeof(pti) == proc_pidinfo(new_proc.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) {
|
|
new_proc.threads = pti.pti_threadnum;
|
|
new_proc.mem = pti.pti_resident_size;
|
|
cpu_t = pti.pti_total_user + pti.pti_total_system;
|
|
|
|
if (new_proc.cpu_t == 0) new_proc.cpu_t = cpu_t;
|
|
}
|
|
|
|
//? Process cpu usage since last update
|
|
new_proc.cpu_p = clamp(round(((cpu_t - new_proc.cpu_t) * Shared::machTck) / ((cputimes - old_cputimes) * Shared::clkTck)) * cmult / 1000.0, 0.0, 100.0 * Shared::coreCount);
|
|
|
|
//? Process cumulative cpu usage since process start
|
|
new_proc.cpu_c = (double)(cpu_t * Shared::machTck) / (timeNow - new_proc.cpu_s);
|
|
|
|
//? Update cached value with latest cpu times
|
|
new_proc.cpu_t = cpu_t;
|
|
|
|
if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
|
|
got_detailed = true;
|
|
}
|
|
}
|
|
|
|
// //? Clear dead processes from current_procs
|
|
auto eraser = rng::remove_if(current_procs, [&](const auto &element) { return not v_contains(found, element.pid); });
|
|
current_procs.erase(eraser.begin(), eraser.end());
|
|
|
|
//? Update the details info box for process if active
|
|
if (show_detailed and got_detailed) {
|
|
_collect_details(detailed_pid, current_procs);
|
|
} else if (show_detailed and not got_detailed and detailed.status != "Dead") {
|
|
detailed.status = "Dead";
|
|
redraw = true;
|
|
}
|
|
|
|
old_cputimes = cputimes;
|
|
}
|
|
}
|
|
|
|
//* ---------------------------------------------Collection done-----------------------------------------------
|
|
|
|
//* Sort processes
|
|
if (sorted_change or not no_update) {
|
|
switch (v_index(sort_vector, sorting)) {
|
|
case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break;
|
|
case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break;
|
|
case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break;
|
|
case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break;
|
|
case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break;
|
|
case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break;
|
|
case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break;
|
|
case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break;
|
|
}
|
|
if (reverse) rng::reverse(current_procs);
|
|
|
|
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
|
|
if (not tree and not reverse and sorting == "cpu lazy") {
|
|
double max = 10.0, target = 30.0;
|
|
for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) {
|
|
if (i <= 5 and current_procs.at(i).cpu_p > max)
|
|
max = current_procs.at(i).cpu_p;
|
|
else if (i == 6)
|
|
target = (max > 30.0) ? max : 10.0;
|
|
if (i == offset and current_procs.at(i).cpu_p > 30.0)
|
|
offset++;
|
|
else if (current_procs.at(i).cpu_p > target) {
|
|
rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1);
|
|
if (++x > 10) break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//* Match filter if defined
|
|
if (should_filter) {
|
|
filter_found = 0;
|
|
for (auto &p : current_procs) {
|
|
if (not tree and not filter.empty()) {
|
|
if (not s_contains(to_string(p.pid), filter) and not s_contains(p.name, filter) and not s_contains(p.cmd, filter) and not s_contains(p.user, filter)) {
|
|
p.filtered = true;
|
|
filter_found++;
|
|
} else {
|
|
p.filtered = false;
|
|
}
|
|
} else {
|
|
p.filtered = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//* Generate tree view if enabled
|
|
if (tree and (not no_update or should_filter or sorted_change)) {
|
|
if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) {
|
|
auto collapser = rng::find(current_procs, find_pid, &proc_info::pid);
|
|
if (collapser != current_procs.end()) {
|
|
if (collapse == expand) {
|
|
collapser->collapsed = not collapser->collapsed;
|
|
} else if (collapse > -1) {
|
|
collapser->collapsed = true;
|
|
} else if (expand > -1) {
|
|
collapser->collapsed = false;
|
|
}
|
|
}
|
|
collapse = expand = -1;
|
|
}
|
|
if (should_filter or not filter.empty()) filter_found = 0;
|
|
|
|
vector<std::reference_wrapper<proc_info>> tree_procs;
|
|
tree_procs.reserve(current_procs.size());
|
|
|
|
//? Stable sort to retain selected sorting among processes with the same parent
|
|
rng::stable_sort(current_procs, rng::less{}, &proc_info::ppid);
|
|
|
|
//? Start recursive iteration over processes with the lowest shared parent pids
|
|
for (auto &p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
|
|
_tree_gen(p, current_procs, tree_procs, 0, false, filter, false, no_update, should_filter);
|
|
}
|
|
|
|
//? Final sort based on tree index
|
|
rng::sort(current_procs, rng::less{}, &proc_info::tree_index);
|
|
if (reverse) rng::reverse(current_procs);
|
|
}
|
|
|
|
numpids = (int)current_procs.size() - filter_found;
|
|
return current_procs;
|
|
}
|
|
} // namespace Proc
|
|
|
|
namespace Tools {
|
|
double system_uptime() {
|
|
struct timeval ts, currTime;
|
|
std::size_t len = sizeof(ts);
|
|
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
|
|
if (sysctl(mib, 2, &ts, &len, NULL, 0) != -1) {
|
|
gettimeofday(&currTime, NULL);
|
|
return currTime.tv_sec - ts.tv_sec;
|
|
}
|
|
return 0.0;
|
|
}
|
|
} // namespace Tools
|