Furtop v1.0

This commit is contained in:
Theri 2024-12-16 08:21:58 +00:00
commit 568a10253e
31 changed files with 7112 additions and 0 deletions

7
.editorconfig Normal file
View file

@ -0,0 +1,7 @@
[*.{sh,md,cfg,sample}]
indent_style = tab
indent_size = 4
[furtop]
indent_style = tab
indent_size = 4

12
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: aristocratos
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

41
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,41 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: aristocratos
---
**Describe the bug**
[A clear and concise description of what the bug is.]
**To Reproduce**
[Steps to reproduce the behavior:]
**Expected behavior**
[A clear and concise description of what you expected to happen.]
**Screenshots**
[If applicable, add screenshots to help explain your problem.]
**Info (please complete the following information):**
- furtop version:
- (Linux) Linux distribution and version:
- (Linux) Data collection type (/proc or psutil):
- Psutil version: `python3 -c "import psutil; print(psutil.version_info)"` (version 5.7.0 or above is required):
- (OSX/FreeBSD) Os release version:
- Terminal used:
- Font used:
- Bash version, `bash --version` (version 4.4 or above is required):
- Locales: output of `locale -v`
**Additional context**
contents of `$HOME/.config/furtop/error.log`
(enable error-logging in "$HOME/.config/furtop/furtop.cfg" if missing)

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[REQUEST]"
labels: enhancement
assignees: aristocratos
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
DEB/furtop_*
DEB/usr/*

9
.gitmodules vendored Normal file
View file

@ -0,0 +1,9 @@
[submodule "test/libs/bats"]
path = test/libs/bats
url = https://github.com/sstephenson/bats
[submodule "test/libs/bats-assert"]
path = test/libs/bats-assert
url = https://github.com/ztombol/bats-assert
[submodule "test/libs/bats-support"]
path = test/libs/bats-support
url = https://github.com/ztombol/bats-support

9
.travis.yml Normal file
View file

@ -0,0 +1,9 @@
# travis does not offer python container for OSX or Windows, not sure if we can get automated testing?
os: linux
language: python
python:
- "3.6"
dist: bionic
install:
- pip install -r requirements.txt
script: ./test.sh

292
CHANGELOG.md Normal file
View file

@ -0,0 +1,292 @@
# Changelog
## v0.9.25
* Fixed: Crash when using "/proc" data collection and filesystem type is 9p, by @bolapara
## v0.9.24
* Fixed: Psutil script crash on OSX
* Fixed: Error handling for malformed osx-cpu-temp output
## v0.9.23
* Fixed: kill/terminate/interrupt process not working in OsX and FreeBSD
## v0.9.22
* Added: Added handler for mktemp failure for psutil script
* Removed: Secondary mktemp command for psutil script
* Fixed: Insecure test import of psutil changed
## v0.9.21
* Changed: Config file comments for theme locations
* Added: Check for correct theme file path prefix
* Added: Support for application cursor mode input
* Fixed: Incorrect value calculation for reversed proc gradient
## v0.9.20
* Fixed: Psutil script security issue when placed directly in temp folder
## v0.9.19
* Added: Option for timestamps with python on bash < 5
* Changed: Reverted "date" command timestamps to not using fifo
* Added missing # from hex value in monokai theme
## v0.9.18
* Fixed: Errors caused by process scroll change
* Fixed: Process graph creation ignored for process below 0.5%
## v0.9.17
* Changed: Process list now scrolls instead of "page jump" and shows number of processes instead of number of pages
* Fixed: Inverted gradient on dark text in processes box
## v0.9.16
* Fixed: Errors in v0.9.15 psutil disk collection fix
* Added: Additional graph creation error checks
## v0.9.15
* Fixed: Psutil error on disk collection now fallback to df and iostat
## v0.9.14
* Added: Additional processes error checking
* Added: Additional sensors error checking
* Added: Additional psutil error checking
## v0.9.13
* Added: More robust psutil error handling
## v0.9.12
* Changed: Psutil data collection now runs a python script in a coprocess taking commands and sending output over coproc pipes
* Added: Psutil data collection now replaces most external calls including sensors, cpu info, disks info and io collection
* Changed: Tree view is now a toggle instead of sorting option
* Fixed: Cpu temp check not using vcgencmd when sensors is available
## v0.9.11
* Fixed: Processes text color now sets RGB instead of RBB...
## v0.9.10
* Fixed: Humanizer function now round values 1000-1023 up to 1024 to fit size constraints.
* Added: More error checks for psutil
* Changed: Terminal title now includes original title if $TERMINAL_TITLE is set, suggested by @theytaz
## v0.9.9
* Fixed: Fixed theme downloader not reporting new themes and corrected comment in config
## v0.9.8
* Added: Nord theme by Justin Zobel
* Changed: Theme downloader now overwrites default themes, folder user_themes (safe from overwrites) added
* Changed: Cleaned up monokai theme variants
* Added: Base for testing with BATS by Maciek Swiech
## v0.9.7
* Changed: UTF-8 locale check, try to find UTF-8 for current language if LANG is set but not with "UTF-8" suffix
## v0.9.6
* Fixed: UTF-8 locale check
## v0.9.5
* Added: UTF-8 locale check and automatic LANG variable set if not UTF-8
* Fixed: Filter out zero sized disks and added some psutil error checks
## v0.9.4
* Fixed: Missing path for OSX df and correct swap usage reporting for OSX
## v0.9.3
* Fixed: Resizing problems in iTerm2
* Changed: Removed redundant error checking in print function for lower cpu usage
* Fixed: Memory in OSX now shows active memory usage and /private/var/vm as swap memory
* Fixed: Disks in OSX changed from using "GNU df" to "BSD df" for better compatibility
## v0.9.2
* Fixed: Correct prefixes for some missed GNU tools
* Added: Startup progress screen
* Changed: replaced tput commands with escape sequence commands
## v0.9.1
* Added: FreeBSD support with python3 psutil data collection
* Added: Check for gnu tools on non Linux platforms
* Fixed: Increased graph history to avoid cut off on high resolution graphs
## v0.9.0
* Added: Mac OS X support with python3 psutil data collection
* Added: Ability to switch between all available network devices
## v0.8.32
* Fixed: Error in theme error checking corrupting default theme
## v0.8.31
* Fixed: Theme 2-color gradient generation
* Fixed: Theme file error checking
## v0.8.30
* Fixed: Crash on missing net device
## v0.8.29
* Fixed: Cpu temperature colors not working when above high temp value
* Fixed: Unescaped "\" in process list and indent fixes
* Changed: Changes to net graph rescaling parameters
## v0.8.28
* Fixed: Ctrl-C and Ctrl-Z not registering after change to "dd"
* Added: Option to switch to high resolution graphs
* Added: Current peak value for download/upload graphs
## v0.8.27
* Fixed: Use value for "Inactive"+"MemFree" if "MemAvailable" is missing in /proc/meminfo
* Added: Option to toggle update check at start
## v0.8.26
* Fixed: Escaped delimiter for sed to fix config not saving "/" character
* Fixed: Detailed process view missing info and slowdown in certain cases
* Optimization: Fork cleanup
## v0.8.25
* Fixed: Backspace not registering when not set to send ascii delete
* Fixed: Broken cpu temperature graph when is value over cpu high temp
* Added: Possibility to run date through background fifo for bash <5
## v0.8.24
* Fixed: Input error freezes, by changing from using "read" command to using "dd" for reading keyboard input.
## v0.8.23
* Added: Support for Raspberry Pi cpu temperature reporting
* Fixed: Decreased chance of read command stalling on lower spec systems
* Added: Failover to nproc if lscpu are reporting 0 cpu cores
* Changed: Moved page display for options and help to bottom and changed to Page Up/Down for changing page
## v0.8.22
* Added: Sorting option "tree", shows processes in a tree structure
* Added: Option to toggle process cpu usage per core instead of total available cpu power
* Fixed: Possible fix for stalling read command
* Added: Multiple while loop fail safes
## v0.8.21
* Fixed: iostat flag compatibility for older iostat versions
* Fixed: possible fix for script stall on bash 4
## v0.8.20
* Fixed: Update slowdown when not sorting by cpu
* Added: New version desktop notification
## v0.8.19
* Added: Disks read and write stats, requires new optional dependency "iostat (part of sysstat)"
* Fixed: Ctrl-C not working when showing resize error message
* Fixed: Network download/upload offset auto switched off if /proc/net/dev resets
* Fixed: Removed trailing whitespace in script
## v0.8.18
* Added: Pagination for help and options windows if items don't fit
* Added: Option to turn off color gradient in process list
* Changed: bash version check to use $BASH_VERSINFO array
* Added: Filter for shown disks
* Added: Option to reset network totals in options menu
## v0.8.17
* Fixed: Not showing CPU temperatures when "Package" temp is missing
* Added: CPU temperature support for AMD Ryzen
* Changed: Minimum size changed from 80x25 to 80x24
* Fixed: High cpu usage on systems with a lot of mounted disks
## v0.8.16
* Added: Bash version check, by Calinou
* Added: OS check, by kpucynski
* Fixed: number of themes reported in options when theme folder is empty, by deluxe
* Fixed: README.md typos, by lucaskim1233
* Added: CHANGELOG.md
## v0.8.15
* Added: deb build script by Jukoo
* Fixed: load average and uptime not showing
* Fixed: freeze on reverse process order when showing detailed information
* Fixed: single quotes on associative arrays
## v0.8.14
* Fixed: disks usage runaway array
* Fixed: disks used not reporting new values
* Changed: memory and disks update frequency increased
## v0.8.13
* Fixed: get_value() regex
* Added: 2 new themes, flat-remix and flat-remix-light, by Daniel Ruiz de Alegría
* Other: general cleanup and formatting
## v0.8.12
* Fixed: changed remaining ps thcount flags to nlwp
## v0.8.11
* Fixed: ps flag thcount changed to nlwp for greater compability
* Fixed: regex and float to int rounding in get_value()
## v0.8.10
* Fixed: erroneous regular expressions
## v0.8.9
* Added: functions is_int, is_float, is_hex
* Fixes: error checking on internal functions
## v0.8.8
* Fixed: load average max length
## v0.8.7
* Fixed: load average clipping
* Fixed: cpu box calculations error
## v0.8.6
* Added: load average and uptime
* Fixed: cohesive window size representation
* Added: unset LC_ALL to not override wanted locale
* Fixed: cpu box calculation errors
## v0.8.5
* Fixed: cpu frequency and /proc/stat error checks

76
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at admin@qvantnet.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

51
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,51 @@
# Contributing guidelines
## When submitting pull requests
* Explain your thinking in why a change or addition is needed.
* Is it a requested change or feature?
* If not, open a feature request to get feedback before making a pull request.
* If it's a fix for a unreported bug, make a bug report and link the pull request.
* Split up multiple unrelated changes in multiple pull requests.
* [Shellcheck](https://github.com/koalaman/shellcheck) your work. Current shellsheck exceptions at the beginning of [furtop](furtop).
* Purely cosmetic changes won't be accepted without a very good explanation of its value.
* (Some design choices are for better configurability of syntax highlighting.)
## Formatting
### Follow the current syntax design
* Indent type: Tabs
* Tab size: 4
* Use the longer "if, elif, then, else, fi" statements and indent conditionals, loops etc.
* Use "[[ ]]", "(( ))" for conditions and "$( ), <( )" for command substitution.
* Create functions instead of repeating blocks of code.
* Don't stack unrelated blocks of code, leave blank lines for better readability.
* Comment new code that isn't very obvious in it's function.
* Name new variables and functions in lower-case and after what purpose they serve.
* (Exception arithmetic with many variables, make sure to comment what's happening instead.)
## Optimization
* Avoid forks if possible.
* Avoid writing to disk if possible.
* Make sure variables/arrays are cleaned up if not reused.
* Compare cpu and memory usage with and without your code and look for alternatives if they cause a noticeable negative impact.
For questions contact Aristocratos at admin@qvantnet.com
For proposing changes to this document create a [new issue](https://github.com/aristocratos/furtop/issues/new/choose).

10
DEB/DEBIAN/control Normal file
View file

@ -0,0 +1,10 @@
Package: furtop
Version: 0.0.0
Section: base
Priority: optional
Architecture: all
Depends: bash (>= 4.4),curl (>= 7.16.2), coreutils, sed, awk, grep
Maintainer: your_name <xxx@xxx.xxx>
Description: Resource monitor that shows usage
and stats for processor, memory, disks, network and processes
Homepage: https://github.com/aristocratos/furtop

4
DEB/DEBIAN/postinst Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
echo -e "[\033[1;32m done \033[0m]"

4
DEB/DEBIAN/postrm Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
echo -e "[\033[1;32m successfully removed \033[0m]"

4
DEB/DEBIAN/preinst Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
echo -e "[\033[1;32m starting installation \033[0m]"

4
DEB/DEBIAN/prerm Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
echo -e "[\033[1;33m removing packet from the system \033[0m]"

115
DEB/build Executable file
View file

@ -0,0 +1,115 @@
#!/bin/bash
# this little script is just to automate the creation of the .deb package under debian and also the automatic installation of the furtop program in the system.
# How does it work?
# ----
# It parses the furtop file to retrieve the last version specified in the script to allow a fresh creation of the .deb package
# +by also retrieving the most recent version of the script then it proceeds to the installation ...
set -o errexit
#set -x # just for debuging
readonly file_src_location=../furtop # furtop location ^
readonly ubin=usr/bin/
readonly file_name=${file_src_location##*/}
readonly ctrl_file=DEBIAN/control
readonly architecture=`dpkg --print-architecture` # for all architectures
readonly root_uid=0
declare version build_version
[[ ROOT::PERMISSION ]]
{
[[ $UID -ne ${root_uid} ]] && {
echo -e "require root user"
exit $UID
}
}
[[ ARGUMENTS::HANDLER ]]
{
if [[ -n $1 ]] ; then
case $1 in
"--remove")
[[ -x /${ubin}/${file_name} ]] && {
dpkg --remove ${file_name}
test $? -eq 0 && exit 0
}||{
echo -e "~ nothing todo: furtop is removed "
exit 0
}
;;
esac
fi
}
echo -e "+ building package ..."
sleep 1
[[ FILECHECK ]]
{
#+ require furtop file to read inside
[[ ! -f ${file_src_location} ]] && {
echo -e "undefine ${file_name}"
exit 3 # just a basic exit
}|| {
echo -e "+ populate DEB folder "
[[ -d $ubin ]] || mkdir -p $ubin
`cp $file_src_location $ubin`
}
#+ require control file to write inside
[[ ! -f ${ctrl_file} ]] && {
echo -e "undefined ${ctrl_file##*/}"
exit 3
}
}
[[ IO::SEMVERS ]]
{
echo -e "+ fetching the lastest version of ${file_name}"
get_current_version () {
local watch_version=`grep -i "declare version" ${file_src_location}`
local semvers=${watch_version##*=}
echo ${semvers:1:-1}
}
set_new_version_ctrl() {
local catch_package_version=`grep -i version ${ctrl_file}`
version=${catch_package_version%%:*}
build_version=${catch_package_version##*:}
[[ -n $1 ]] && build_version=$1
version+=": ${build_version}"
echo -e "+ set new version control"
`sed -i "s/$catch_package_version/${version}/g" ${ctrl_file}`
}
set_new_version_ctrl $(get_current_version)
}
[[ PACKAGER_BUILD::DEB ]]
{
build_for_debian_base (){
local debian_package_name=${file_name}_${build_version}-${architecture}.deb
#echo ${debian_package_name}
dpkg-deb --build ../DEB ${debian_package_name}
test $? -eq 0 && {
if [[ -f ${debian_package_name} ]] ;then
dpkg -i ${debian_package_name}
[[ $? -eq 0 ]] && {
exit $?
}||{
echo -e "Installation failed"
exit $?
}
fi
}||{
echo -e "build failed"
exit 5
}
}
build_for_debian_base
}

BIN
Imgs/bpytop.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
Imgs/logo-t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

BIN
Imgs/main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

BIN
Imgs/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

BIN
Imgs/options.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

202
LICENSE Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

16
Makefile Normal file
View file

@ -0,0 +1,16 @@
PREFIX ?= /usr/local
DOCDIR ?= $(PREFIX)/share/doc/furtop
all:
@echo Run \'make install\' to install furtop.
install:
@mkdir -p $(DESTDIR)$(PREFIX)/bin
@cp -p furtop $(DESTDIR)$(PREFIX)/bin/furtop
@mkdir -p $(DESTDIR)$(DOCDIR)
@cp -p README.md $(DESTDIR)$(DOCDIR)
@chmod 755 $(DESTDIR)$(PREFIX)/bin/furtop
uninstall:
@rm -rf $(DESTDIR)$(PREFIX)/bin/furtop
@rm -rf $(DESTDIR)$(DOCDIR)

403
README.md Normal file
View file

@ -0,0 +1,403 @@
# ![furtop](Imgs/logo-t.png)
![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux)
![OSX](https://img.shields.io/badge/-OSX-black?logo=apple)
![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd)
![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-blue)
![Bash](https://img.shields.io/badge/Bash-v4.4%5E-green?logo=GNU%20bash)
![Python](https://img.shields.io/badge/Python-v3.6%5E-orange?logo=python)
#
## Index
* [Documents](#documents)
* [Description](#description)
* [Features](#features)
* [Themes](#themes)
* [Support and funding](#support-and-funding)
* [Prerequisites](#prerequisites)
* [Dependencies](#dependencies)
* [Screenshots](#screenshots)
* [Installation](#installation)
* [Configurability](#configurability)
* [TODO](#todo)
* [License](#license)
## Documents
#### [CHANGELOG.md](CHANGELOG.md)
#### [CONTRIBUTING.md](CONTRIBUTING.md)
#### [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
## Description
Resource monitor that shows usage and stats for processor, memory, disks, network and processes.
## Features
* Easy to use, with a game inspired menu system.
* Fast and "mostly" responsive UI with UP, DOWN keys process selection.
* Function for showing detailed stats for selected process.
* Ability to filter processes.
* Easy switching between sorting options.
* Send SIGTERM, SIGKILL, SIGINT to selected process.
* UI menu for changing all config file options.
* Auto scaling graph for network usage.
* Shows message in menu if new version is available
* Shows current read and write speeds for disks
* Multiple data collection methods which can be switched if running on Linux
## Themes
furtop now has theme support and a function to download missing local themes from repository.
See [themes](themes) folder for available themes.
The builtin theme downloader places the default themes in `$HOME/.config/furtop/themes`.
User created themes should be placed in `$HOME/.config/furtop/user_themes` to be safe from overwrites.
Let me know if you want to contribute with new themes.
## Support and funding
Bug fixes and updates might be slow during normal workdays since I work full time as an industrial worker and don't have much time or energy left during the week.
I'm looking into ways of funding this project that would allow me to take off time from my day job to work on this.
Any advice on how to get funding for open source projects is very welcome!
#### Update
You can now sponsor this project through github, see [my sponsors page](https://github.com/sponsors/aristocratos) for options.
Also added donation links for [paypal](https://paypal.me/aristocratos) and [ko-fi](https://ko-fi.com/aristocratos).
Any support is greatly appreciated!
## Prerequisites
#### Mac Os X
Will not display correctly in the standard terminal!
Recommended alternative [iTerm2](https://www.iterm2.com/)
Will also need to be run as superuser to display stats for processes not owned by user.
#### Linux, Mac Os X and FreeBSD
For correct display, a terminal with support for:
* 24-bit truecolor ([See list of terminals with truecolor support](https://gist.github.com/XVilka/8346728))
* Wide characters (Are sometimes problematic in web-based terminals)
Also needs a UTF8 locale and a font that covers:
* Unicode Block “Braille Patterns” U+2800 - U+28FF
* Unicode Block “Geometric Shapes” U+25A0 - U+25FF
* Unicode Block "Box Drawing" and "Block Elements" U+2500 - U+259F
#### Notice
Dropbear seems to not be able to set correct locale. So if accessing furtop over ssh, OpenSSH is recommended.
## Dependencies
## Linux, OSX and FreeBSD
**[bash](https://www.gnu.org/software/bash/)** (v4.4 or later) Script functionality will most probably break with earlier versions.
Bash version 5 is highly recommended to make use of $EPOCHREALTIME variable instead of a lot of external date command calls.
**[GNU coreutils](https://www.gnu.org/software/coreutils/)**
**[GNU sed](https://www.gnu.org/software/sed/)**
## Linux using /proc for data collection
**[GNU grep](https://www.gnu.org/software/grep/)**
**[ps from procps-ng](https://gitlab.com/procps-ng/procps)** (v3.1.15 or later)
**[GNU awk](https://www.gnu.org/software/gawk/)**
## OSX and FreeBSD or Linux using psutil for data collection
**[Python3](https://www.python.org/downloads/)** (v3.6 or later)
**[psutil python module](https://github.com/giampaolo/psutil)** (v5.7.0 or later)
## Optionals for additional stats
(Optional OSX) **[osx-cpu-temp](https://github.com/lavoiesl/osx-cpu-temp)** Needed to show CPU temperatures.
(Optional Linux) **[lm-sensors](https://github.com/lm-sensors/lm-sensors)** Needed to show CPU temperatures.
(Optional Linux) **[iostat (part of sysstat)](https://github.com/sysstat/sysstat)** Needed if you want disk read/write stats and are not using psutil data collection.
(Optional OSX/Linux/FreeBSD) **[curl](https://curl.haxx.se/download.html)** (v7.16.2 or later) Needed if you want messages about updates and the ability to download themes.
## Screenshots
Main UI showing details for a selected process.
![Screenshot 1](Imgs/main.png)
Main menu.
![Screenshot 2](Imgs/menu.png)
Options menu.
![Screenshot 3](Imgs/options.png)
## Installation
#### Dependencies installation OSX
>Install homebrew if not already installed
``` bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
```
>If you got python 3.6 or later installed outside of brew:
``` bash
sudo python3 -m ensurepip
sudo python3 -m pip install psutil
```
>If you haven't got python3 installed:
```
brew install python3
python3 -m pip install psutil
```
>Install dependencies
``` bash
brew install bash coreutils gnu-sed git
```
>Install optional dependency osx-cpu-temp
``` bash
brew install osx-cpu-temp
```
#### Dependencies installation FreeBSD
>Install with pkg and pip
``` bash
sudo pkg install coreutils gsed git py37-psutil
```
#### Manual installation Linux, OSX and FreeBSD
>Clone and install
``` bash
git clone https://github.com/aristocratos/furtop.git
cd furtop
sudo make install
```
>to uninstall it
``` bash
sudo make uninstall
```
#### FreeBSD package
Available in [FreeBSD ports](https://www.freshports.org/sysutils/furtop/)
Install pre-built pacakge
``` bash
sudo pkg install furtop
```
#### Arch based
Available in the AUR as [furtop-git](https://aur.archlinux.org/packages/furtop-git/)
Available in the Arch Linux repository as [furtop](https://www.archlinux.org/packages/community/any/furtop/)
#### Debian based
Available in [official Debian repository](https://tracker.debian.org/pkg/furtop) since Debian 11
Available for debian/ubuntu from [Azlux's repository](http://packages.azlux.fr/)
Or use quick installation:
>Quick install go to DEB folder and type
``` bash
sudo ./build
```
>to uninstall it go to DEB folder and type
``` bash
sudo ./build --remove
```
#### Guix based
Available in [official Guix repository](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/packages/admin.scm) since 6bbd0fd2
>Installation
``` bash
guix install furtop
```
#### Ubuntu based
Available in [official Ubuntu repository](https://launchpad.net/ubuntu/+source/furtop) since Ubuntu 20.10
Available for Ubuntu from [PPA repository](https://code.launchpad.net/~furtop-monitor/+archive/ubuntu/furtop)
>Add PPA repository and install furtop
``` bash
sudo add-apt-repository ppa:furtop-monitor/furtop
sudo apt update
sudo apt install furtop
```
#### Fedora
Available in the Fedora repository.
>Installation
``` bash
sudo dnf install furtop
```
#### CentOS 8
>Installation
``` bash
dnf config-manager --set-enabled PowerTools
dnf install epel-release
dnf install furtop
```
#### RHEL 8
>Installation
``` bash
ARCH=$( /bin/arch )
subscription-manager repos --enable
"codeready-builder-for-rhel-8-${ARCH}-rpms"
dnf install epel-release
dnf install furtop
```
## Configurability
All options changeable from within UI.
Config files stored in "$HOME/.config/furtop" folder
#### furtop.cfg: (auto generated if not found)
```bash
#? Config file for furtop v. 0.9.21
#* Color theme, looks for a .theme file in "$HOME/.config/furtop/themes" and "$HOME/.config/furtop/user_themes"
#* Should be prefixed with either "themes/" or "user_themes/" depending on location, "Default" for builtin default theme
color_theme="Default"
#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs
update_ms="2500"
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive"
#* "cpu lazy" updates sorting over time, "cpu responsive" updates sorting directly
proc_sorting="cpu lazy"
#* Reverse sorting order, "true" or "false"
proc_reversed="false"
#* Show processes as a tree
proc_tree="false"
#* Check cpu temperature, only works if "sensors", "vcgencmd" or "osx-cpu-temp" commands is available
check_temp="true"
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable
draw_clock="%X"
#* Update main ui when menus are showing, set this to false if the menus is flickering too much for comfort
background_update="true"
#* Custom cpu model name, empty string to disable
custom_cpu_name=""
#* Enable error logging to "$HOME/.config/furtop/error.log", "true" or "false"
error_logging="true"
#* Show color gradient in process list, "true" or "false"
proc_gradient="true"
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power
proc_per_core="false"
#* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space
disks_filter=""
#* Enable check for new version from github.com/aristocratos/furtop at start
update_check="true"
#* Enable graphs with double the horizontal resolution, increases cpu usage
hires_graphs="false"
#* Enable the use of psutil python3 module for data collection, default on OSX
use_psutil="true"
```
#### Command line options: (not yet implemented)
``` bash
USAGE: furtop
```
## TODO
Might finish off items out of order since I usually work on multiple at a time.
- [x] Add options to change colors for text, graphs and meters.
- [x] Fix cross platform compatibility for Mac OSX and *BSD: Working on OSX, and FreeBSD.
- [x] Add support for showing AMD cpu temperatures.
- [x] Add option to show tree view of processes.
- [x] Add option to reset network download/upload totals.
- [x] Add option to turn of gradient in processes list.
- [ ] Add gpu temp and usage. (If feasible)
- [x] Add io stats for disks.
- [ ] Add cpu and mem stats for docker containers. (If feasible)
- [x] Change process list to line scroll instead of page change.
- [ ] Add optional window for tailing log files.
- [ ] Add options for resizing all boxes.
- [ ] Add command line argument parsing.
- [ ] Builtin updater. Relevant PR #96 by Jukoo
- [ ] Add support for zram in memory box. Relevant PR #122 by perkinslr
- [ ] Miscellaneous optimizations and code cleanup.
- [ ] Add more commenting where it's sparse.
- [ ] Python port. (Porting started)
## LICENSE
[Apache License 2.0](LICENSE)

5312
furtop Executable file

File diff suppressed because it is too large Load diff

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
psutil==5.7.0

408
src/furtop.psutil.py Executable file
View file

@ -0,0 +1,408 @@
#!/usr/bin/env python3
'''This is a copy of the python script that furtop starts in a coprocess when using psutil for data collection'''
import os, sys, subprocess, re, time, psutil
from datetime import timedelta
from collections import defaultdict
from typing import List, Set, Dict, Tuple, Optional, Union
system: str
if "linux" in sys.platform: system = "Linux"
elif "bsd" in sys.platform: system = "BSD"
elif "darwin" in sys.platform: system = "MacOS"
else: system = "Other"
parent_pid: int = psutil.Process(os.getpid()).ppid()
allowed_commands: Tuple[str] = (
'get_proc',
'get_disks',
'get_cpu_name',
'get_cpu_cores',
'get_nics',
'get_cpu_cores',
'get_cpu_usage',
'get_cpu_freq',
'get_uptime',
'get_load_avg',
'get_mem',
'get_detailed_names_cmd',
'get_detailed_mem_time',
'get_net',
'get_cmd_out',
'get_sensors',
'get_sensors_check',
'get_ms'
)
command: str = ''
cpu_count: int = psutil.cpu_count()
disk_hist: Dict = {}
def cleaned(string: str) -> str:
'''Escape characters not suitable for "echo -e" in bash'''
return string.replace("\\", "\\\\").replace("$", "\\$").replace("\n", "\\n").replace("\t", "\\t").replace("\"", "\\\"").replace("\'", "\\\'")
def get_cmd_out(cmd: str):
'''Save bash the trouble of creating child processes by running through python instead'''
print(subprocess.check_output(cmd, shell=True, universal_newlines=True).rstrip())
def get_ms():
'''Get current epoch millisecond'''
t = str(time.time()).split(".")
print(f'{t[0]}{t[1][:3]}')
def get_sensors():
'''A clone of "sensors" but using psutil'''
temps = psutil.sensors_temperatures()
if not temps:
return
try:
for name, entries in temps.items():
print(name)
for entry in entries:
print(f'{entry.label or name}: {entry.current}°C (high = {entry.high}°C, crit = {entry.critical}°C)')
print()
except:
pass
def get_sensors_check():
'''Check if get_sensors() output contains accepted CPU temperature values'''
if not hasattr(psutil, "sensors_temperatures"): print("false"); return
try:
temps = psutil.sensors_temperatures()
except:
pass
print("false"); return
if not temps: print("false"); return
try:
for _, entries in temps.items():
for entry in entries:
if entry.label.startswith(('Package', 'Core 0', 'Tdie')):
print("true")
return
except:
pass
print("false")
def get_cpu_name():
'''Fetch a suitable CPU identifier from the CPU model name string'''
name: str = ""
command: str = ""
all_info: str = ""
rem_line: str = ""
if system == "Linux":
command = "cat /proc/cpuinfo"
rem_line = "model name"
elif system == "MacOS":
command ="sysctl -n machdep.cpu.brand_string"
elif system == "BSD":
command ="sysctl hw.model"
rem_line = "hw.model"
all_info = subprocess.check_output("LANG=C " + command, shell=True, universal_newlines=True)
if rem_line:
for line in all_info.split("\n"):
if rem_line in line:
name = re.sub( ".*" + rem_line + ".*:", "", line,1).lstrip()
else:
name = all_info
if "Xeon" in name:
name = name.split(" ")
name = name[name.index("CPU")+1]
elif "Ryzen" in name:
name = name.split(" ")
name = " ".join(name[name.index("Ryzen"):name.index("Ryzen")+3])
elif "CPU" in name:
name = name.split(" ")
name = name[name.index("CPU")-1]
print(name)
def get_cpu_cores():
'''Get number of CPU cores and threads'''
cores: int = psutil.cpu_count(logical=False)
threads: int = psutil.cpu_count(logical=True)
print(f'{cores} {threads if threads else cores}')
def get_cpu_usage():
cpu: float = psutil.cpu_percent(percpu=False)
threads: List[float] = psutil.cpu_percent(percpu=True)
print(f'{cpu:.0f}')
for thread in threads:
print(f'{thread:.0f}')
def get_cpu_freq():
'''Get current CPU frequency'''
try:
print(f'{psutil.cpu_freq().current:.0f}')
except:
print(0)
def get_uptime():
'''Get current system uptime'''
print(str(timedelta(seconds=round(time.time()-psutil.boot_time(),0)))[:-3])
def get_load_avg():
'''Get CPU load average'''
for lavg in os.getloadavg():
print(round(lavg, 2), ' ', end='')
print()
def get_mem():
'''Get current system memory and swap usage'''
mem = psutil.virtual_memory()
swap = psutil.swap_memory()
try:
cmem = mem.cached>>10
except:
cmem = mem.active>>10
print(mem.total>>10, mem.free>>10, mem.available>>10, cmem, swap.total>>10, swap.free>>10)
def get_nics():
'''Get a list of all network devices sorted by highest throughput'''
io_all = psutil.net_io_counters(pernic=True)
up_stat = psutil.net_if_stats()
for nic in sorted(psutil.net_if_addrs(), key=lambda nic: (io_all[nic].bytes_recv + io_all[nic].bytes_sent), reverse=True):
if up_stat[nic].isup is False:
continue
print(nic)
def get_net(net_dev: str):
'''Emulated /proc/net/dev for selected network device'''
net = psutil.net_io_counters(pernic=True)[net_dev]
print(0,net.bytes_recv,0,0,0,0,0,0,0,net.bytes_sent)
def get_detailed_names_cmd(pid: int):
'''Get name, parent name, username and arguments for selected pid'''
p = psutil.Process(pid)
pa = psutil.Process(p.ppid())
with p.oneshot():
print(p.name())
print(pa.name())
print(p.username())
cmd = ' '.join(p.cmdline()) or '[' + p.name() + ']'
print(cleaned(cmd))
def get_detailed_mem_time(pid: int):
'''Get memory usage and runtime for selected pid'''
p = psutil.Process(pid)
with p.oneshot():
print(p.memory_info().rss)
print(timedelta(seconds=round(time.time()-p.create_time(),0)))
def get_proc(sorting='cpu lazy', tree=False, prog_len=0, arg_len=0, search='', reverse=True, proc_per_cpu=True, max_lines=0):
'''List all processess with pid, name, arguments, threads, username, memory percent and cpu percent'''
line_count: int = 0
err: float = 0.0
reverse = not reverse
if sorting == 'pid':
sort_cmd = "p.info['pid']"
elif sorting == 'program' or tree and sorting == "arguments":
sort_cmd = "p.info['name']"
reverse = not reverse
elif sorting == 'arguments':
sort_cmd = "' '.join(str(p.info['cmdline'])) or p.info['name']"
reverse = not reverse
elif sorting == 'threads':
sort_cmd = "str(p.info['num_threads'])"
elif sorting == 'user':
sort_cmd = "p.info['username']"
reverse = not reverse
elif sorting == 'memory':
sort_cmd = "str(p.info['memory_percent'])"
elif sorting == 'cpu responsive':
sort_cmd = "p.info['cpu_percent']" if proc_per_cpu else "(p.info['cpu_percent'] / cpu_count)"
else:
sort_cmd = "(sum(p.info['cpu_times'][:2] if not p.info['cpu_times'] == 0.0 else [0.0, 0.0]) * 1000 / (time.time() - p.info['create_time']))"
if tree:
proc_tree(width=prog_len + arg_len, sorting=sort_cmd, reverse=reverse, max_lines=max_lines, proc_per_cpu=proc_per_cpu, search=search)
return
print(f"{'Pid:':>7} {'Program:':<{prog_len}}", f"{'Arguments:':<{arg_len-4}}" if arg_len else '', f"{'Threads:' if arg_len else ' Tr:'} {'User:':<9}Mem%{'Cpu%':>11}", sep='')
for p in sorted(psutil.process_iter(['pid', 'name', 'cmdline', 'num_threads', 'username', 'memory_percent', 'cpu_percent', 'cpu_times', 'create_time'], err), key=lambda p: eval(sort_cmd), reverse=reverse):
if p.info['name'] == 'idle' or p.info['name'] == err or p.info['pid'] == err:
continue
if p.info['cmdline'] == err:
p.info['cmdline'] = ""
if p.info['username'] == err:
p.info['username'] = "?"
if p.info['num_threads'] == err:
p.info['num_threads'] = 0
if search:
found = False
for value in [ p.info['name'], ' '.join(p.info['cmdline']), str(p.info['pid']), p.info['username'] ]:
if search in value:
found = True
break
if not found:
continue
cpu = p.info['cpu_percent'] if proc_per_cpu else (p.info['cpu_percent'] / psutil.cpu_count())
mem = p.info['memory_percent']
cmd = ' '.join(p.info['cmdline']) or '[' + p.info['name'] + ']'
print(f"{p.info['pid']:>7} ",
f"{cleaned(p.info['name']):<{prog_len}.{prog_len-1}}",
f"{cleaned(cmd):<{arg_len}.{arg_len-1}}" if arg_len else '',
f"{p.info['num_threads']:>4} " if p.info['num_threads'] < 1000 else '999> ',
f"{p.info['username']:<9.9}" if len(p.info['username']) < 10 else f"{p.info['username'][:8]:<8}+",
f"{mem:>4.1f}" if mem < 100 else f"{mem:>4.0f} ",
f"{cpu:>11.1f} " if cpu < 100 else f"{cpu:>11.0f} ",
sep='')
line_count += 1
if max_lines and line_count == max_lines:
break
def proc_tree(width: int, sorting: str = 'cpu lazy', reverse: bool = True, max_lines: int = 0, proc_per_cpu=True, search=''):
'''List all processess in a tree view with pid, name, threads, username, memory percent and cpu percent'''
tree_line_count: int = 0
err: float = 0.0
def create_tree(parent: int, tree, indent: str = '', inindent: str = ' ', found: bool = False):
nonlocal infolist, tree_line_count, max_lines, tree_width, proc_per_cpu, search
cont: bool = True
if max_lines and tree_line_count >= max_lines:
return
try:
name: str = psutil.Process(parent).name()
if name == "idle": return
except psutil.Error:
pass
name: str = ''
try:
getinfo: Dict = infolist[parent]
except:
pass
getinfo: bool = False
if search and not found:
for value in [ name, str(parent), getinfo['username'] if getinfo else '' ]:
if search in value:
found = True
break
if not found:
cont = False
if cont: print(f"{f'{inindent}{parent} {cleaned(name)}':<{tree_width}.{tree_width-1}}", sep='', end='')
if getinfo and cont:
if getinfo['cpu_times'] == err:
getinfo['num_threads'] = 0
if p.info['username'] == err:
p.info['username'] = "?"
cpu = getinfo['cpu_percent'] if proc_per_cpu else (getinfo['cpu_percent'] / psutil.cpu_count())
print(f"{getinfo['num_threads']:>4} " if getinfo['num_threads'] < 1000 else '999> ',
f"{getinfo['username']:<9.9}" if len(getinfo['username']) < 10 else f"{getinfo['username'][:8]:<8}+",
f"{getinfo['memory_percent']:>4.1f}" if getinfo['memory_percent'] < 100 else f"{getinfo['memory_percent']:>4.0f} ",
f"{cpu:>11.1f} " if cpu < 100 else f"{cpu:>11.0f} ",
sep='')
elif cont:
print(f"{'':>14}{'0.0':>4}{'0.0':>11} ", sep='')
tree_line_count += 1
if parent not in tree:
return
children = tree[parent][:-1]
for child in children:
create_tree(child, tree, indent + "", indent + " ├─ ", found=found)
if max_lines and tree_line_count >= max_lines:
break
child = tree[parent][-1]
create_tree(child, tree, indent + " ", indent + " └─ ")
infolist: Dict = {}
tree: List = defaultdict(list)
for p in sorted(psutil.process_iter(['pid', 'name', 'num_threads', 'username', 'memory_percent', 'cpu_percent', 'cpu_times', 'create_time'], err), key=lambda p: eval(sorting), reverse=reverse):
try:
tree[p.ppid()].append(p.pid)
except (psutil.NoSuchProcess, psutil.ZombieProcess):
pass
else:
infolist[p.pid] = p.info
if 0 in tree and 0 in tree[0]:
tree[0].remove(0)
tree_width: int = width + 8
print(f"{' Tree:':<{tree_width-4}}", 'Threads: ', f"{'User:':<9}Mem%{'Cpu%':>11}", sep='')
create_tree(min(tree), tree)
def get_disks(exclude: str = None, filtering: str = None):
'''Get stats, current read and current write for all disks'''
global disk_hist
disk_read: int = 0
disk_write: int = 0
dev_name: str
disk_name: str
disk_list: List[str] = []
excludes: List[str] = []
if exclude: excludes = exclude.split(' ')
if system == "BSD": excludes += ["devfs", "tmpfs", "procfs", "linprocfs", "gvfs", "fusefs"]
if filtering: filtering: Tuple[str] = tuple(filtering.split(' '))
io_counters = psutil.disk_io_counters(perdisk=True if system == "Linux" else False, nowrap=True)
print("Ignored line")
for disk in psutil.disk_partitions():
disk_io = None
disk_name = disk.mountpoint.rsplit('/', 1)[-1] if not disk.mountpoint == "/" else "root"
while disk_name in disk_list: disk_name += "_"
disk_list += [disk_name]
if excludes and disk.fstype in excludes or filtering and not disk_name.endswith(filtering):
continue
if system == "MacOS" and disk.mountpoint == "/private/var/vm":
continue
try:
disk_u = psutil.disk_usage(disk.mountpoint)
except:
pass
print(f'{disk.device} {disk_u.total >> 10} {disk_u.used >> 10} {disk_u.free >> 10} {disk_u.percent:.0f} ', end='')
try:
if system == "Linux":
dev_name = os.path.realpath(disk.device).rsplit('/', 1)[-1]
if dev_name.startswith("md"):
try:
dev_name = dev_name[:dev_name.index("p")]
except:
pass
disk_io = io_counters[dev_name]
elif disk.mountpoint == "/":
disk_io = io_counters
else:
raise Exception
disk_read = disk_io.read_bytes
disk_write = disk_io.write_bytes
disk_read -= disk_hist[disk.device][0]
disk_write -= disk_hist[disk.device][1]
except:
pass
disk_read = 0
disk_write = 0
if disk_io: disk_hist[disk.device] = (disk_io.read_bytes, disk_io.write_bytes)
print(f'{disk_read >> 10} {disk_write >> 10} {disk_name}')
#* The script takes input over coproc pipes and runs command if in the accepted commands list
while command != 'quit':
if not psutil.pid_exists(parent_pid):
quit()
try:
command = input()
except:
pass
quit()
if not command or command == 'test':
continue
elif command.startswith(allowed_commands):
try:
exec(command)
except Exception as e:
pass
print()
print('/ERROR')
print(f'PSUTIL ERROR! Command: {command}\n{e}', file=sys.stderr)
else:
continue
print('/EOL')
#print(f'{command}', file=sys.stderr)

3
test.sh Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
./test/libs/bats/bin/bats test/*.bats

17
test/basic_test.bats Normal file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env bats
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load test_helper
@test "Sourcing works, by checking if \$system is set" {
run echo $system
refute_output ""
}
@test "#get_themes populates themes" {
get_themes
assert_success
assert [ ${#themes[@]} -gt 0 ]
}

1
test/test_helper.bash Normal file
View file

@ -0,0 +1 @@
source furtop

89
themes/Default.theme Normal file
View file

@ -0,0 +1,89 @@
#Bashtop theme with nord palette (https://www.nordtheme.com)
#by Justin Zobel <justin.zobel@gmail.com>
# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255"
# example for white: "#ffffff", "#ff" or "255 255 255".
# All graphs and meters can be gradients
# For single color graphs leave "mid" and "end" variable empty.
# Use "start" and "end" variables for two color gradient
# Use "start", "mid" and "end" for three color gradient
# Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]=""
# Main text color
theme[main_fg]="#ffccbb"
# Title color for boxes
theme[title]="#aabbbb"
# Higlight color for keyboard shortcuts
theme[hi_fg]="#aa77bb"
# Background color of selected item in processes box
theme[selected_bg]="#775577"
# Foreground color of selected item in processes box
theme[selected_fgF]="#ffeeee"
# Color of inactive/disabled text
theme[inactive_fg]="#555577"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#aa77bb"
# Cpu box outline color
theme[cpu_box]="#555566"
# Memory/disks box outline color
theme[mem_box]="#336688"
# Net up/down box outline color
theme[net_box]="#665588"
# Processes box outline color
theme[proc_box]="#664488"
# Box divider line and small boxes line color
theme[div_line]="#665599"
# Temperature graph colors
theme[temp_start]="#aaaadd"
theme[temp_mid]="#88bbcc"
theme[temp_end]="#ffeeff"
# CPU graph colors
theme[cpu_start]="#aaaadd"
theme[cpu_mid]="#88bbcc"
theme[cpu_end]="#ffeeff"
# Mem/Disk free meter
theme[free_start]="#aaaadd"
theme[free_mid]="#88bbcc"
theme[free_end]="#ffeeff"
# Mem/Disk cached meter
theme[cached_start]="#aaaadd"
theme[cached_mid]="#88bbcc"
theme[cached_end]="#ffeeff"
# Mem/Disk available meter
theme[available_start]="#aaaadd"
theme[available_mid]="#88bbcc"
theme[available_end]="#ffeeff"
# Mem/Disk used meter
theme[used_start]="#aaaadd"
theme[used_mid]="#88bbcc"
theme[used_end]="#ffeeff"
# Download graph colors
theme[download_start]="#aaaadd"
theme[download_mid]="#88bbcc"
theme[download_end]="#ffeeff"
# Upload graph colors
theme[upload_start]="#aaaadd"
theme[upload_mid]="#88bbcc"
theme[upload_end]="#ffeeff"