btop/src/osx/btop_collect.cpp
aristocratos c0e17a64d3 Squashed commit of the following:
commit c2c4fe47db
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Nov 13 23:15:53 2021 +0100

    Changes from main + fixes

commit bd5d867089
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Nov 13 21:24:01 2021 +0100

    Fixes

commit dc5f0606cb
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Nov 13 20:51:06 2021 +0100

    Fixed leak in Proc::collect()

commit 8b59ab6e11
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Nov 13 19:59:56 2021 +0100

    Fixed process cpu usage calculation

commit 53c8a0325b
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

commit 940cd0a513
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 24 21:12:16 2021 +0200

    limit cpu temp to 20

commit 254ceb9c2d
Author: aristocratos <admin@qvantnet.com>
Date:   Sun Oct 24 11:24:04 2021 +0200

    Fixed up Makefile

commit 6fa3bf41d7
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 21 13:12:45 2021 +0200

    Makefile fixed WARNFLAGS

commit 2c98e2ca29
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 21 13:08:12 2021 +0200

    Updated Makefile

commit e69780e9bc
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 20 23:26:09 2021 +0200

    fix build

commit de62167921
Merge: a590dd3 a0ee404
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 20 23:10:36 2021 +0200

    Merge branch 'main' into OSX

commit a590dd3f67
Author: aristocratos <admin@qvantnet.com>
Date:   Mon Oct 18 11:20:12 2021 +0200

    Ignore format-truncation

commit 4c30742d41
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 18 10:08:01 2021 +0200

    comments about temp sensors

commit 46030de77c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 18 08:10:37 2021 +0200

    available = total - used

commit 4c228de0ef
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 17 22:19:41 2021 +0200

    use sysconf to get arg_max - seems simpler

commit c60fc29f0f
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 17 22:10:50 2021 +0200

    arg_max should be int on macos

commit 0b5a931a6d
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 17 22:01:42 2021 +0200

    only fetch max_args once

commit 71d5cd5fd9
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Oct 16 23:24:07 2021 +0200

    Reverted mutexes back to custom atomic bool based locks

commit 3f34a67df6
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 16 21:47:55 2021 +0200

    these helpers can be static

commit fc19c46c8a
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

commit c252c618c0
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 16 21:09:21 2021 +0200

    don't crash on intel

commit 9f88187c29
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 16 21:06:18 2021 +0200

    small improvement

commit 808f09c974
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 16 20:59:48 2021 +0200

    don't iterate 3 times

commit d8408336e3
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 16 20:59:27 2021 +0200

    remove debug

commit 4f078c3beb
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 13 23:38:27 2021 +0200

    more temperature (M1 + intel)

commit 289880aaa6
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

commit 88a2528ca3
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Oct 16 19:34:10 2021 +0200

    Merge changes from main

commit 3ffb21203a
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

commit 70b4871062
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

commit fbae907720
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Fri Oct 15 23:41:37 2021 +0200

    temperature sensors via IOKit

commit cef0f0a68d
Author: aristocratos <admin@qvantnet.com>
Date:   Fri Oct 15 18:39:17 2021 +0200

    Process command line arguments

commit 921cfa01ff
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

commit a416c888c7
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 13 21:16:41 2021 +0200

    temperature

commit e7afe00ce7
Author: aristocratos <admin@qvantnet.com>
Date:   Wed Oct 13 12:54:43 2021 +0200

    Cpu usage working again

commit 4193ef8921
Author: aristocratos <admin@qvantnet.com>
Date:   Wed Oct 13 10:36:51 2021 +0200

    Fixed cpu lazy sorting

commit 93fcb6ff04
Author: aristocratos <admin@qvantnet.com>
Date:   Tue Oct 12 22:22:45 2021 +0200

    Update README.md

commit 683354cd2e
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

commit 8a399c499a
Author: aristocratos <admin@qvantnet.com>
Date:   Tue Oct 12 21:50:46 2021 +0200

    pointer to smart pointer, first pass

commit 772605003a
Author: aristocratos <admin@qvantnet.com>
Date:   Tue Oct 12 18:54:38 2021 +0200

    Fixed detailed memory not updating

commit 28cb677533
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 11 22:19:25 2021 +0200

    more memory free-up - still leaks like crazy

commit 304457863f
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 11 21:48:07 2021 +0200

    more RAII cleanup

commit 82e2e3c55c
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

commit 68603f2b37
Author: aristocratos <admin@qvantnet.com>
Date:   Mon Oct 11 10:57:04 2021 +0200

    RAII Wrappers for Cpu::get_battery()

commit d5cb24fbeb
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 10 20:23:11 2021 +0200

    RAII

commit 8fad5a61be
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 9 21:44:16 2021 +0200

    get more disk IO stats

commit 7fa903cf16
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 9 21:43:48 2021 +0200

    fix build

commit 98036db660
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 9 21:18:25 2021 +0200

    remove unnecessary uptime param

commit aae7ae35ca
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 9 21:07:23 2021 +0200

    remove debug logging

commit 5187420b04
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 9 21:06:43 2021 +0200

    fix process elapsed time

commit 89582c0ea6
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 9 21:06:29 2021 +0200

    don't double free

commit 6d724d6155
Author: Shriraj Hegde <shriraj.hegde@gmail.com>
Date:   Sat Oct 9 21:46:33 2021 +0530

    Change Upload filename

commit 4f94ecc8ad
Author: Shriraj Hegde <shriraj.hegde@gmail.com>
Date:   Sat Oct 9 21:40:11 2021 +0530

    Fix upload path

commit e1d6d0a1f2
Author: Shriraj Hegde <shriraj.hegde@gmail.com>
Date:   Sat Oct 9 21:36:13 2021 +0530

    Skip installing gcc via Homebrew
    Change job name

commit 02cdd9d759
Author: Shriraj Hegde <shriraj.hegde@gmail.com>
Date:   Sat Oct 9 21:30:40 2021 +0530

    Fix uploading
    Remove distclean

commit 39eb6c396f
Author: Shriraj Hegde <shriraj.hegde@gmail.com>
Date:   Sat Oct 9 21:26:57 2021 +0530

    Disable static compilation

commit 099592bccd
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Oct 9 17:52:10 2021 +0200

    Ignore empty pid 0 to fix tree mode

commit a28e17556e
Author: Shriraj Hegde <shriraj.hegde@gmail.com>
Date:   Sat Oct 9 21:17:11 2021 +0530

    Add workflow for MacOS

commit aee9179c0a
Author: aristocratos <admin@qvantnet.com>
Date:   Sat Oct 9 17:36:46 2021 +0200

    Disable failed tty mode detection for OSX

commit 4b7b98058d
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

commit bfa0629e7d
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Fri Oct 8 22:28:10 2021 +0200

    fill in 0 for ioticks

commit a016ff8a03
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

commit f98606c6db
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Fri Oct 8 09:32:06 2021 +0200

    per process IO stats

commit c8b50ed488
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Fri Oct 8 00:11:08 2021 +0200

    don't show autofs, it's useless

commit c4df64d440
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 7 18:41:49 2021 +0200

    Fixed compile time display for gmake and command timings

commit b3e6f495f7
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 7 18:26:15 2021 +0200

    Fixed clk_tck -> clkTck

commit e53799188f
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 7 18:25:14 2021 +0200

    Fixed better detection for OSX

commit b864edf984
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 7 18:24:37 2021 +0200

    Fixed cumulative cpu usage

commit 6a3c5d9b97
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 7 13:20:30 2021 +0200

    Proc::collect() better cpu percent accurazy

commit 84d0596294
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

commit 3564f8e4c2
Author: aristocratos <admin@qvantnet.com>
Date:   Thu Oct 7 12:56:27 2021 +0200

    Proc::collect() fixed cputimes and cpu percentage calc

commit 98e1e87405
Merge: 60c5636 d96fdd7
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Thu Oct 7 11:32:41 2021 +0200

    Merge branch 'main' into OSX

commit 60c5636cd7
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 22:45:54 2021 +0200

    fix warning

commit 489e446152
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 22:38:40 2021 +0200

    details + process states

commit 7e5a808c73
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 22:38:19 2021 +0200

    avoid details crash

commit 9c9da4606b
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 21:03:21 2021 +0200

    fix quit on macos

commit ec7415384d
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 00:41:37 2021 +0200

    fix mistake in makefile

commit 5ac8fa4c8a
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 18:56:13 2021 +0200

    don't show /dev

commit d901bbebd9
Author: aristocratos <gnmjpl@gmail.com>
Date:   Wed Oct 6 17:27:51 2021 +0200

     Ignore tags and other branches

commit c7f1e71e29
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 16:00:41 2021 +0200

    comment

commit b9d58e3faf
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 15:55:58 2021 +0200

    impossible to get CPU freq on M1 apparently

commit 66072711c2
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 15:33:43 2021 +0200

    detect full

commit 6bb0e930a2
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 00:38:46 2021 +0200

    CPU freq in GHz

commit a5f10f1a0f
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 15:13:18 2021 +0200

    check array length

commit 155c848b97
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)

commit cf51ba2ebe
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 14:45:44 2021 +0200

    remove some warnings

commit 775dff5f72
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 14:16:45 2021 +0200

    fix link

commit 8c67967775
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 14:16:38 2021 +0200

    reduce diff more

commit 70b47d2ca8
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 14:10:23 2021 +0200

    reduce diff with main branch

commit ca9cb48054
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

commit c66b46f850
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 13:41:57 2021 +0200

    battery state via CoreFoundation

commit d0c6c0a362
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 10:51:36 2021 +0200

    all disks + load averages

commit ca67526dc1
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 10:33:55 2021 +0200

    show all disks

commit 56119f99a9
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 00:17:41 2021 +0200

    procs sorting/filtering

commit 8d86011d72
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 23:42:17 2021 +0200

    battery states

commit a9b64d62e4
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 23:24:59 2021 +0200

    battery hack works on M1

commit ce51031142
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 23:18:22 2021 +0200

    ugly hack to get battery

commit d5e6725c6c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 22:42:42 2021 +0200

    CPU stuff

commit 5c02bd8c83
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 21:25:42 2021 +0200

    network

commit d5da9d4983
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 15:43:05 2021 +0200

    correct cached size

commit 5f11aba504
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 12:03:48 2021 +0200

    vm stats from syscall + swap

commit 776fc96852
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 10:48:07 2021 +0200

    seems to work indeed

commit 7b40e2835a
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 10:46:14 2021 +0200

    allow override optimization flag

commit 005ea24e4c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 10:09:24 2021 +0200

    update Makefile

commit af8cec9deb
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 15:32:55 2021 +0200

    some more params

commit 7ebe4f7594
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 15:15:55 2021 +0200

    show more disks

commit e50a56394a
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 14:52:56 2021 +0200

    disks show something

commit 6497a8c202
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 09:15:35 2021 +0200

    reformat

commit 28e152b80c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 23:21:13 2021 +0200

    decrease diff with upstream

commit 40da88e9ca
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 22:56:14 2021 +0200

    try to get disks to show

commit eaf2bb56a5
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 22:42:01 2021 +0200

    don't crash on deque::back()

commit f66b6f712c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 22:08:21 2021 +0200

    cpu freq, name & process uid/name

commit 34a8a61f4d
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 21:46:11 2021 +0200

    basic process info

commit 29bb2dcc5f
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)

commit fb5970b000
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 2 23:53:41 2021 +0200

    comment

commit 49d16cdddd
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 2 23:51:29 2021 +0200

    extract delimiters

commit 3db9d66476
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 2 23:48:28 2021 +0200

    first infos on macos: memory used & free

commit f8acb2f854
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

commit bbba17cd35
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 10:51:36 2021 +0200

    all disks + load averages

commit 548203e93d
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 10:33:55 2021 +0200

    show all disks

commit 0ab2be3985
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Wed Oct 6 00:17:41 2021 +0200

    procs sorting/filtering

commit 096104c90b
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 23:42:17 2021 +0200

    battery states

commit 0ad93684c2
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 23:24:59 2021 +0200

    battery hack works on M1

commit c75b0f1cea
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 23:18:22 2021 +0200

    ugly hack to get battery

commit 600b4f72b3
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 22:42:42 2021 +0200

    CPU stuff

commit 4eb812d52c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 21:25:42 2021 +0200

    network

commit 899be68a78
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 15:43:05 2021 +0200

    correct cached size

commit a1c7f935e3
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 12:03:48 2021 +0200

    vm stats from syscall + swap

commit bd1050a740
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 10:48:07 2021 +0200

    seems to work indeed

commit 5094b73758
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 10:46:14 2021 +0200

    allow override optimization flag

commit 8811270332
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Tue Oct 5 10:09:24 2021 +0200

    update Makefile

commit 42f966f448
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 15:32:55 2021 +0200

    some more params

commit c1e6d6a62e
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 15:15:55 2021 +0200

    show more disks

commit 50fcdaa854
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 14:52:56 2021 +0200

    disks show something

commit 264bf2d7da
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Mon Oct 4 09:15:35 2021 +0200

    reformat

commit 1fd625086b
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 23:21:13 2021 +0200

    decrease diff with upstream

commit 17f9f3703c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 22:56:14 2021 +0200

    try to get disks to show

commit 8462ae6431
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 22:42:01 2021 +0200

    don't crash on deque::back()

commit 78bce5b5a6
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

commit 53e379d74d
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 22:08:21 2021 +0200

    cpu freq, name & process uid/name

commit 2a44b307ef
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sun Oct 3 21:46:11 2021 +0200

    basic process info

commit 66534eb5b5
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)

commit 0983917f26
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 2 23:53:41 2021 +0200

    comment

commit 9732507248
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 2 23:51:29 2021 +0200

    extract delimiters

commit 6e704ce838
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

commit fe4db7c16c
Author: Jos Dehaes <jos.dehaes@gmail.com>
Date:   Sat Oct 2 23:48:28 2021 +0200

    first infos on macos: memory used & free

commit 7bfbd83a47
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

commit 8c8139bd1d
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

commit 679d21cd22
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

commit 4c70c5bdd9
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

commit 84a974695a
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>
2021-11-13 23:37:23 +01:00

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(&currentTime, 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