├── requirements.txt ├── docs ├── images │ ├── jtop.gif │ ├── jtop.png │ ├── favicon.png │ ├── jetson_env.png │ ├── jtop-restore.png │ ├── jetson_release.png │ ├── jtop-fail-user.png │ ├── pages │ │ ├── 01-jtop.png │ │ ├── 02-jtop.png │ │ ├── 03-jtop.png │ │ ├── 04-jtop.png │ │ ├── 05-jtop.png │ │ ├── 06-jtop.png │ │ ├── 06B-jtop.png │ │ └── 07-jtop.png │ ├── jtop-color-filter.png │ ├── architecture.drawio.png │ ├── jetson_config-01-main.png │ ├── jetson_config-02-jtop.png │ ├── architecture-old.drawio.png │ └── jetson_config-03-desktop.png ├── reference │ ├── fan.rst │ ├── gpu.rst │ ├── memory.rst │ ├── exceptions.rst │ ├── nvpmodel.rst │ ├── jetson_clocks.rst │ ├── jtop.rst │ └── index.rst ├── other-tools │ ├── index.rst │ ├── environment_variables.rst │ ├── jetson_release.rst │ ├── jetson_swap.rst │ └── jetson_config.rst ├── _templates │ ├── sidebar │ │ └── adsense.html │ └── base.html ├── sponsors.rst ├── requirements.txt ├── docker.rst ├── jtop │ └── how_is_it_works.rst ├── advanced-usage.rst ├── nosudo.rst ├── index.rst ├── troubleshooting.rst ├── conf.py ├── Makefile ├── make.bat └── contributing.rst ├── .github ├── FUNDING.yml ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── jetpack-missing.md │ ├── config.yml │ ├── feature-request.md │ ├── hardware-missing.md │ ├── engine-gui.md │ └── bug-report.md ├── SECURITY.md ├── PULL_REQUEST_TEMPLATE │ └── jetpack-missing.md └── workflows │ ├── auto-merge-dependabot.yml │ └── auto-merge-jetpack.yml ├── tests ├── nvfancontrol.service ├── Dockerfile.tox ├── nvfancontrol.conf ├── Dockerfile.sphinx ├── nvfancontrol └── local_test.sh ├── .dockerignore ├── .gitignore ├── setup.cfg ├── jtop ├── tests │ ├── __init__.py │ ├── test_00_service.py │ ├── test_05_gui.py │ ├── test_02_fan.py │ ├── test_03_jetson_clocks.py │ ├── test_01_outputs.py │ ├── marco_functions.py │ └── test_04_nvpmodel.py ├── tests_gui │ ├── __init__.py │ ├── test_gui_page.py │ ├── test_linear_gauge.py │ ├── test_chart.py │ └── test_process_table.py ├── gui │ ├── lib │ │ ├── __init__.py │ │ ├── dialog_window.py │ │ ├── process_table.py │ │ └── colors.py │ └── __init__.py ├── core │ ├── __init__.py │ ├── hw_detect.py │ ├── exceptions.py │ ├── timer_reader.py │ ├── hardware.py │ ├── config.py │ ├── thor_cuda_mem.py │ ├── tegrastats.py │ ├── engine.py │ └── thor_power.py ├── __init__.py ├── terminal_colors.py └── jetson_swap.py ├── Dockerfile ├── services └── jtop.service ├── examples ├── quick_read.py ├── jtop_processes.py ├── jtop_memory.py ├── jtop_engines.py ├── jtop_fan.py ├── jtop_hardware.py ├── jtop_cpu.py ├── jtop_callback.py ├── jtop_flask_server.py ├── jtop_logger.py ├── jtop_properties.py ├── jtop_server.py └── jtop_control.py ├── MANIFEST.in ├── scripts ├── jtop_env.sh └── install_jtop_torun_without_sudo.sh ├── tox.ini ├── pyproject.toml ├── CODE_OF_CONDUCT.md └── setup.py /requirements.txt: -------------------------------------------------------------------------------- 1 | smbus2 2 | distro 3 | nvidia-ml-py 4 | -------------------------------------------------------------------------------- /docs/images/jtop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jtop.gif -------------------------------------------------------------------------------- /docs/images/jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jtop.png -------------------------------------------------------------------------------- /docs/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/favicon.png -------------------------------------------------------------------------------- /docs/images/jetson_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jetson_env.png -------------------------------------------------------------------------------- /docs/images/jtop-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jtop-restore.png -------------------------------------------------------------------------------- /docs/images/jetson_release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jetson_release.png -------------------------------------------------------------------------------- /docs/images/jtop-fail-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jtop-fail-user.png -------------------------------------------------------------------------------- /docs/images/pages/01-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/01-jtop.png -------------------------------------------------------------------------------- /docs/images/pages/02-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/02-jtop.png -------------------------------------------------------------------------------- /docs/images/pages/03-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/03-jtop.png -------------------------------------------------------------------------------- /docs/images/pages/04-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/04-jtop.png -------------------------------------------------------------------------------- /docs/images/pages/05-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/05-jtop.png -------------------------------------------------------------------------------- /docs/images/pages/06-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/06-jtop.png -------------------------------------------------------------------------------- /docs/images/pages/06B-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/06B-jtop.png -------------------------------------------------------------------------------- /docs/images/pages/07-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/pages/07-jtop.png -------------------------------------------------------------------------------- /docs/reference/fan.rst: -------------------------------------------------------------------------------- 1 | Fan 2 | === 3 | 4 | .. autoclass:: jtop.core.fan.Fan 5 | :members: 6 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/gpu.rst: -------------------------------------------------------------------------------- 1 | GPU 2 | === 3 | 4 | .. autoclass:: jtop.core.gpu.GPU 5 | :members: 6 | :show-inheritance: -------------------------------------------------------------------------------- /docs/images/jtop-color-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jtop-color-filter.png -------------------------------------------------------------------------------- /docs/images/architecture.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/architecture.drawio.png -------------------------------------------------------------------------------- /docs/images/jetson_config-01-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jetson_config-01-main.png -------------------------------------------------------------------------------- /docs/images/jetson_config-02-jtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jetson_config-02-jtop.png -------------------------------------------------------------------------------- /docs/reference/memory.rst: -------------------------------------------------------------------------------- 1 | Memory 2 | ====== 3 | 4 | .. autoclass:: jtop.core.memory.Memory 5 | :members: 6 | :show-inheritance: -------------------------------------------------------------------------------- /docs/images/architecture-old.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/architecture-old.drawio.png -------------------------------------------------------------------------------- /docs/images/jetson_config-03-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbonghi/jetson_stats/HEAD/docs/images/jetson_config-03-desktop.png -------------------------------------------------------------------------------- /docs/reference/exceptions.rst: -------------------------------------------------------------------------------- 1 | Exceptions 2 | ========== 3 | 4 | .. autoclass:: jtop.JtopException 5 | :members: 6 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/nvpmodel.rst: -------------------------------------------------------------------------------- 1 | NVPModel 2 | ======== 3 | 4 | .. autoclass:: jtop.core.nvpmodel.NVPModel 5 | :members: 6 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/jetson_clocks.rst: -------------------------------------------------------------------------------- 1 | jetson_clocks 2 | ============= 3 | 4 | .. autoclass:: jtop.core.jetson_clocks.JetsonClocks 5 | :members: 6 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/jtop.rst: -------------------------------------------------------------------------------- 1 | jtop 2 | ==== 3 | 4 | .. autoclass:: jtop.jtop 5 | :members: 6 | :show-inheritance: 7 | 8 | .. automethod:: __init__ 9 | 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: rbonghi 4 | patreon: # Replace with a single Patreon username 5 | custom: https://paypal.me/RaffaelloBonghi 6 | -------------------------------------------------------------------------------- /docs/reference/index.rst: -------------------------------------------------------------------------------- 1 | 📖 API Reference 2 | ================= 3 | 4 | .. toctree:: 5 | jtop 6 | gpu 7 | memory 8 | fan 9 | jetson_clocks 10 | nvpmodel 11 | exceptions -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Restrict all files related to deploying to 2 | # require lead maintainer approval. 3 | 4 | .github/workflows/ @rbonghi 5 | .github/CODEOWNERS @rbonghi 6 | setup.py @rbonghi 7 | -------------------------------------------------------------------------------- /docs/other-tools/index.rst: -------------------------------------------------------------------------------- 1 | 🧰 Other tools 2 | ================ 3 | 4 | When you install jetson-stats there are also other tools included 5 | 6 | .. toctree:: 7 | 8 | jetson_config 9 | jetson_release 10 | jetson_swap 11 | environment_variables -------------------------------------------------------------------------------- /tests/nvfancontrol.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Fake nvfanservice control 3 | 4 | [Service] 5 | ExecStart=/usr/bin/touch /tmp/nvfancontrol_tmp 6 | ExecStop=/bin/rm /tmp/nvfancontrol_tmp 7 | RemainAfterExit=yes 8 | 9 | [Install] 10 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Compiled python modules. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | _dist/ 6 | _build/ 7 | doc/ 8 | 9 | # Python egg metadata, regenerated from source files by setuptools. 10 | /*.egg-info 11 | 12 | # log file 13 | *.log 14 | 15 | # Test virtual environment 16 | venv/ -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_templates/sidebar/adsense.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 9 |
-------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: docker 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | # Check for updates to GitHub Actions every weekday 12 | interval: "daily" 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/jetpack-missing.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Jetpack missing 3 | about: jetson-stats miss a JetPack 4 | title: Jetpack missing [L4T e.g. 5.2.1] 5 | labels: Jetpack,missing 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please update jetson-stats with new jetpack 11 | 12 | ### Linux for Tegra 13 | 14 | - L4T: 15 | 16 | ### jetson-stats 17 | 18 | - Version: 19 | -------------------------------------------------------------------------------- /docs/other-tools/environment_variables.rst: -------------------------------------------------------------------------------- 1 | Environment variables 2 | ===================== 3 | 4 | This script generate the easy environment variables to know which is your 5 | Hardware version of the Jetson and which Jetpack you have already installed. 6 | 7 | .. image:: /images/jetson_env.png 8 | :align: center 9 | 10 | You can read all variables with: 11 | 12 | .. code-block:: bash 13 | 14 | export | grep JETSON -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: 🆘 Troubleshooting 4 | url: https://rnext.it/jetson_stats/troubleshooting.html 5 | about: Check if your bug is documented here 6 | - name: 💬 Ask the Community 7 | url: https://discord.gg/BFbuJNhYzS 8 | about: Join jtop's Discord server 9 | - name: 📚 Documentation 10 | url: https://https://rnext.it/jetson_stats 11 | about: Make sure you read the relevant docs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled python modules. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | _build/ 6 | dist/ 7 | doc/ 8 | build/ 9 | 10 | # Python egg metadata, regenerated from source files by setuptools. 11 | /*.egg-info 12 | 13 | # log file 14 | *.log 15 | 16 | # Test virtual environment 17 | venv/ 18 | 19 | # Unit test / coverage reports 20 | .tox/ 21 | .cache/ 22 | swapfile 23 | test.* 24 | jtop_test.* 25 | *.csv 26 | .dccache 27 | 28 | .vscode/ 29 | .venv*/ 30 | .idea/ 31 | __pycache__/ -------------------------------------------------------------------------------- /docs/sponsors.rst: -------------------------------------------------------------------------------- 1 | 💖 Sponsor 2 | ========== 3 | 4 | Please consider sponsoring jetson-stats development, especially if your company benefits from this library. 5 | 6 | Your contribution will go towards adding new features to jetson-stats and making sure all functionality continues to meet our high quality standards. 7 | 8 | .. important:: 9 | 10 | `Get in contact `_ for additional 11 | details on sponsorship and perks before making a contribution 12 | through `GitHub Sponsors `_ if you have questions. 13 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | In general, due to limited maintainer bandwidth, only the latest version of jetson-stats is supported with patch releases. 6 | Exceptions may be made depending on the severity of the bug and the feasibility of backporting a fix to older releases. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | jetson-stats uses GitHub's security advisory functionality for private vulnerability reports. 11 | To make a private report, use the "Report a vulnerability" button on https://github.com/rbonghi/jetson_stats/security/advisories 12 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>3.0.0 2 | requests 3 | furo 4 | sphinx-copybutton 5 | sphinx-autobuild 6 | sphinxext-opengraph 7 | numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability 8 | tornado>=6.4.2 # not directly required, pinned by Snyk to avoid a vulnerability 9 | pillow>=10.3.0 # not directly required, pinned by Snyk to avoid a vulnerability 10 | fonttools>=4.43.0 # not directly required, pinned by Snyk to avoid a vulnerability 11 | zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability 12 | urllib3>=2.6.0 # not directly required, pinned by Snyk to avoid a vulnerability 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | # This includes the license file(s) in the wheel. 3 | # https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file 4 | license_files = LICENSE 5 | 6 | [bdist_wheel] 7 | # This flag says to generate wheels that support both Python 2 and Python 8 | # 3. If your code will not run unchanged on both Python 2 and 3, you will 9 | # need to generate separate wheels for each Python version that you 10 | # support. Removing this line (or setting universal to 0) will prevent 11 | # bdist_wheel from trying to make a universal wheel. For more see: 12 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels 13 | # universal=0 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/hardware-missing.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Hardware Missing 3 | about: jetson-stats does not recognize this hardware 4 | title: Hardware Missing [] 5 | labels: Hardware,missing 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please update jetson-stats with this board 11 | 16 | ### Board 17 | 18 | - Model: 19 | - 699-level Part Number: 20 | - P-Number: 21 | - Module: 22 | - SoC: 23 | - CUDA Arch BIN: 24 | - Codename: 25 | - L4T: 26 | - Jetpack: 27 | 28 | 29 | ### jetson-stats 30 | 31 | - Version: 32 | 33 | 34 | ### RAW Data 35 | 36 | File from `jtop --error-log` attached 37 | -------------------------------------------------------------------------------- /docs/_templates/base.html: -------------------------------------------------------------------------------- 1 | {% extends "!base.html" %} 2 | 3 | {%- block extrahead %} 4 | {{ super() }} 5 | {% endblock %} 6 | 7 | {% block theme_scripts %} 8 | {{ super() }} 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 | {% endblock %} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/engine-gui.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: GUI compact engine page missing 3 | about: jtop is not using a specific compact information page for this board 4 | title: GUI compact engine page missing [] 5 | labels: GUI,missing 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please add a new jtop engine compact page for 11 | 16 | ### Board 17 | 18 | - Model: 19 | - 699-level Part Number: 20 | - P-Number: 21 | - Module: 22 | - SoC: 23 | - CUDA Arch BIN: 24 | - Codename: 25 | - L4T: 26 | - Jetpack: 27 | 28 | 29 | ### jetson-stats 30 | 31 | - Version: 32 | 33 | 34 | ### Screenshot engine page 35 | 36 | Screnshoot page from jtop engine page attacched -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/jetpack-missing.md: -------------------------------------------------------------------------------- 1 | # Add New Jetpack 2 | 3 | To add a new NVIDIA Jetpack release you can quicky do: 4 | 5 | Open file **`jtop/core/jetson_variables.py`** around line *49* there is a variable called **`NVIDIA_JETPACK`** add the new jetpack following the rule below: 6 | 7 | ```python 8 | "L4T version": "Jetpack" 9 | ``` 10 | 11 | Checklist: 12 | 13 | * [ ] Add Jetpack on **`jtop/core/jetson_variables.py`** 14 | * [ ] Increase with a minor release jtop variable **`__version__`** in **`jtop/__init__.py`** 15 | * [ ] See if all tests pass 16 | * [ ] Merge the release pull request with message "`Jetpack Release `" where `` is the same release in **`jtop/__init__.py`** 17 | * [ ] Get the release Pull request approved by a [CODEOWNER](https://github.com/rbonghi/jetson_stats/blob/master/.github/CODEOWNERS) 18 | -------------------------------------------------------------------------------- /docs/other-tools/jetson_release.rst: -------------------------------------------------------------------------------- 1 | jetson_release 2 | ============== 3 | 4 | The command show the status and all information about your NVIDIA Jetson 5 | 6 | .. code-block:: bash 7 | 8 | jetson_release 9 | 10 | .. image:: /images/jetson_release.png 11 | :align: center 12 | 13 | Options available 14 | 15 | .. code-block:: console 16 | :class: no-copybutton 17 | 18 | nvidia@jetson-nano:~$ jetson_release --help 19 | Software part of jetson-stats 4.2.0 - (c) 2026, Raffaello Bonghi 20 | usage: jetson_release [-h] [-v] [-s] 21 | 22 | Show detailed information about this board. Machine, Jetpack, libraries and 23 | other 24 | 25 | optional arguments: 26 | -h, --help show this help message and exit 27 | -v, --verbose Show all variables (default: False) 28 | -s, --serial Show serial number (default: False) 29 | -------------------------------------------------------------------------------- /jtop/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /jtop/tests_gui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /jtop/gui/lib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # flake8: noqa 19 | -------------------------------------------------------------------------------- /jtop/core/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # flake8: noqa 19 | 20 | # EOF 21 | -------------------------------------------------------------------------------- /docs/other-tools/jetson_swap.rst: -------------------------------------------------------------------------------- 1 | jetson_swap 2 | =========== 3 | 4 | Simple manager to switch on and switch off a swapfile in your jetson. 5 | 6 | .. code-block:: bash 7 | 8 | jetson_swap 9 | 10 | All options available 11 | 12 | .. code-block:: console 13 | :class: no-copybutton 14 | 15 | nvidia@jetson-nano:~$ sudo jetson_swap -h 16 | usage: jetson_swap [-h] [-d DIRECTORY] [-n NAME] [-s SIZE] [-a] [-t] [--off] 17 | 18 | Create a swap file and enable on boot (require sudo) 19 | 20 | optional arguments: 21 | -h, --help show this help message and exit 22 | -d DIRECTORY, --dir DIRECTORY 23 | Directory to place swapfile (default: ) 24 | -n NAME, --name NAME Name swap file (default: swapfile) 25 | -s SIZE, --size SIZE Size in Gigabytes (default: 8) 26 | -a, --auto Enable swap on boot (default: False) 27 | -t, --status Check if the swap is currently active (default: False) 28 | --off Switch off the swap (default: False) 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | FROM python:3.13.6-slim-bullseye 20 | 21 | ADD . /jetson_stats 22 | 23 | WORKDIR /jetson_stats 24 | 25 | RUN python3 -m pip install --upgrade pip && \ 26 | pip3 install -v . 27 | 28 | CMD ["jtop"] -------------------------------------------------------------------------------- /services/jtop.service: -------------------------------------------------------------------------------- 1 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 2 | # Copyright (c) 2019-2026 Raffaello Bonghi. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | [Unit] 17 | Description=jtop service 18 | After=multi-user.target 19 | 20 | [Service] 21 | # Type=exec 22 | Environment="JTOP_SERVICE=True" 23 | ExecStart=/usr/local/bin/jtop --force 24 | Restart=on-failure 25 | RestartSec=10s 26 | TimeoutStartSec=30s 27 | TimeoutStopSec=30s 28 | 29 | [Install] 30 | WantedBy=multi-user.target 31 | -------------------------------------------------------------------------------- /jtop/core/hw_detect.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # jtop/core/hw_detect.py 19 | 20 | import os 21 | 22 | THOR_GPC = "/sys/class/devfreq/gpu-gpc-0" 23 | 24 | 25 | def is_thor() -> bool: 26 | return os.path.isdir(THOR_GPC) 27 | 28 | 29 | def devfreq_nodes(): 30 | roots = ["/sys/class/devfreq/gpu-gpc-0", "/sys/class/devfreq/gpu-nvd-0"] 31 | return [p for p in roots if os.path.isdir(p)] 32 | -------------------------------------------------------------------------------- /examples/quick_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("Simple jtop reader") 25 | 26 | with jtop() as jetson: 27 | # jetson.ok() will provide the proper update frequency 28 | while jetson.ok(): 29 | # Read tegra stats 30 | print(jetson.stats) 31 | # EOF 32 | -------------------------------------------------------------------------------- /docs/other-tools/jetson_config.rst: -------------------------------------------------------------------------------- 1 | jetson_config 2 | ============= 3 | 4 | Check jetson-stats health, enable/disable desktop, enable/disable jetson_clocks, 5 | improve the performance of your wifi are available only in one click using jetson_config 6 | 7 | .. code-block:: bash 8 | 9 | sudo jetson_config 10 | 11 | .. image:: /images/jetson_config-01-main.png 12 | :align: center 13 | 14 | **jetson_config** have different pages 15 | 16 | - **Health** - Check the status of jetson-stats 17 | - **Update** - Update this tool to the latest version 18 | - **Desktop** - Enable/Disable boot from desktop 19 | - **About** - Information about this configuration tool 20 | 21 | Health 22 | ------ 23 | 24 | This page help to self check this package and automatically fix broken parts, there are these submenus: 25 | 26 | - **jetson-stats** - Fix jetson-stats service 27 | - **Permissions** - Fix permissions for your user 28 | - **variables** - Check if are installed all variables :doc:`environment_variables` 29 | 30 | .. image:: /images/jetson_config-02-jtop.png 31 | :align: center 32 | 33 | Desktop 34 | ------- 35 | 36 | This menu enable and disable the Desktop on your jetson. 37 | 38 | **Remember ssh require a login to work** 39 | 40 | .. image:: /images/jetson_config-03-desktop.png 41 | :align: center 42 | 43 | -------------------------------------------------------------------------------- /examples/jtop_processes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("Simple jtop process reader") 25 | 26 | with jtop() as jetson: 27 | # jetson.ok() will provide the proper update frequency 28 | while jetson.ok(): 29 | # Print all cpu 30 | for process in jetson.processes: 31 | print(process) 32 | # EOF 33 | -------------------------------------------------------------------------------- /docs/docker.rst: -------------------------------------------------------------------------------- 1 | 🐋 Docker 2 | ========= 3 | 4 | .. currentmodule:: jtop 5 | 6 | You can run directly in Docker jtop, you need only to: 7 | 8 | 1. Install jetson-stats on your **host** 9 | 2. Install jetson-stats on your container as well 10 | 3. Pass to your container ``/run/jtop.sock:/run/jtop.sock`` 11 | 12 | You can try running this command 13 | 14 | .. code-block:: bash 15 | 16 | docker run --rm -it -v /run/jtop.sock:/run/jtop.sock rbonghi/jetson_stats:latest 17 | 18 | Design your Dockerfile 19 | ---------------------- 20 | 21 | jetson-stats need few things to be installed on your container. 22 | 23 | 1. ``apt-get install -y python3`` 24 | 2. ``apt-get install -y python3-pip`` *or* you can install from **source** 25 | 26 | Below a simple example to install jetson-stats 27 | 28 | .. code-block:: docker 29 | 30 | FROM python:3-buster 31 | RUN pip install -U jetson-stats 32 | 33 | Tips and tricks 34 | --------------- 35 | 36 | If you work with different **multiple users** on your docker container: 37 | 38 | .. code-block:: bash 39 | 40 | docker run --group-add $JTOP_GID --rm -it -v /run/jtop.sock:/run/jtop.sock rbonghi/jetson_stats:latest 41 | 42 | You can get the ``JTOP_GID`` by running: 43 | 44 | .. code-block:: bash 45 | 46 | getent group jtop | awk -F: '{print $3}' 47 | 48 | Issue reference `#391 `_ -------------------------------------------------------------------------------- /tests/Dockerfile.tox: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | ARG PYTHON_VERSION=3.12.0a3-slim-bullseye 19 | FROM python:${PYTHON_VERSION} 20 | 21 | RUN apt-get update && \ 22 | apt-get install -y sudo bc systemctl && \ 23 | rm -rf /var/lib/apt/lists/* 24 | 25 | COPY . /jetson_stats 26 | 27 | WORKDIR /jetson_stats 28 | 29 | RUN sudo groupadd jtop && \ 30 | sudo -H python -m pip install --upgrade pip && \ 31 | sudo -H pip install tox 32 | 33 | # Run tox 34 | RUN sudo tox -e py${PYTHON_VERSION%.*} 35 | 36 | CMD ["bash"] -------------------------------------------------------------------------------- /examples/jtop_memory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("Simple jtop memory reader") 25 | 26 | with jtop() as jetson: 27 | # jetson.ok() will provide the proper update frequency 28 | if jetson.ok(): 29 | # Print all cpu 30 | for name, data in jetson.memory.items(): 31 | print("------ {name} ------".format(name=name)) 32 | print(data) 33 | # EOF 34 | -------------------------------------------------------------------------------- /tests/nvfancontrol.conf: -------------------------------------------------------------------------------- 1 | POLLING_INTERVAL 2 2 | 3 | TMARGIN ENABLED 4 | FAN_GOVERNOR pid { 5 | STEP_SIZE 10 6 | } 7 | FAN_PROFILE cool { 8 | #TEMP HYST PWM RPM 9 | 0 0 255 2900 10 | 18 9 255 2900 11 | 30 11 202 2300 12 | 45 11 149 1700 13 | 60 14 88 1000 14 | 105 0 0 0 15 | } 16 | FAN_PROFILE quiet { 17 | #TEMP HYST PWM RPM 18 | 0 0 202 2300 19 | 18 9 202 2300 20 | 30 11 158 1800 21 | 45 11 114 1300 22 | 60 14 62 700 23 | 105 0 0 0 24 | } 25 | THERMAL_GROUP 0 { 26 | GROUP_MAX_TEMP 105 27 | #Thermal-Zone Coeffs Max-Temp 28 | CPU-therm 20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0 29 | GPU-therm 20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0 30 | SOC0-therm 20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0 31 | SOC1-therm 20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0 32 | SOC2-therm 20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0 33 | } 34 | FAN_DEFAULT_CONTROL open_loop 35 | FAN_DEFAULT_PROFILE cool 36 | FAN_DEFAULT_GOVERNOR pid 37 | -------------------------------------------------------------------------------- /jtop/core/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | class JtopException(Exception): 20 | """ 21 | raise when jtop fail. The message attached show the reason. 22 | """ 23 | 24 | def __init__(self, message, errors=""): 25 | super(JtopException, self).__init__(message, errors) 26 | # Now for your custom code... 27 | self.message = message 28 | self.errors = errors 29 | 30 | def __repr__(self): 31 | return str(self.message) 32 | 33 | def __str__(self): 34 | return str(self.message) 35 | # EOF 36 | -------------------------------------------------------------------------------- /tests/Dockerfile.sphinx: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | ARG PYTHON_VERSION=3.11 19 | FROM python:${PYTHON_VERSION} 20 | 21 | RUN apt-get update && \ 22 | apt-get install -y sudo bc make gcc g++ make && \ 23 | sudo -H python -m pip install --upgrade pip && \ 24 | rm -rf /var/lib/apt/lists/* 25 | 26 | COPY . /jetson_stats 27 | 28 | WORKDIR /jetson_stats 29 | 30 | # Install jtop 31 | RUN sudo pip3 install -v -e . 32 | # Install sphinx requirements 33 | RUN pip install -r docs/requirements.txt 34 | 35 | WORKDIR /jetson_stats/docs 36 | 37 | RUN sphinx-build -b html -W . _build/html 38 | 39 | CMD ["bash"] -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 2 | # Copyright (c) 2019-2026 Raffaello Bonghi. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | # Include the README 18 | include *.md 19 | 20 | # Include requirements 21 | include requirements.txt 22 | 23 | # Include the license file 24 | include LICENSE 25 | 26 | # Include the data files 27 | recursive-include scripts * 28 | 29 | # Include services files 30 | recursive-include services *.service 31 | 32 | # Include package tests 33 | recursive-include jtop/tests *.py 34 | recursive-include jtop/tests_gui *.py 35 | 36 | # Remove test docs and examples and tox.ini 37 | exclude tox.ini 38 | exclude Dockerfile 39 | exclude .dockerignore 40 | prune tests 41 | prune docs 42 | prune examples 43 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Auto-approve and merge Dependabot PRs 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - synchronize 8 | - reopened 9 | 10 | jobs: 11 | auto-approve: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | # Step to auto-approve Dependabot PRs 16 | - name: Auto-approve Dependabot PR 17 | if: github.actor == 'dependabot[bot]' 18 | uses: hmarr/auto-approve-action@v4 19 | with: 20 | github-token: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | auto-merge: 23 | needs: auto-approve 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | # Step to checkout the repository 28 | - name: Checkout repository 29 | uses: actions/checkout@v6 30 | 31 | # Step to find the PR number of the open Dependabot PR 32 | - name: Find PR number 33 | id: find_pr 34 | run: | 35 | PR_NUMBER=$(gh pr list --state open --author "dependabot[bot]" --json number --jq '.[0].number') 36 | echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | # Step to enable auto-merge for the found PR number 41 | - name: Enable auto-merge 42 | if: ${{ steps.find_pr.outputs.PR_NUMBER != '' }} 43 | run: gh pr merge --auto --squash "${{ steps.find_pr.outputs.PR_NUMBER }}" 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /examples/jtop_engines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("Simple jtop engine reader") 25 | 26 | with jtop() as jetson: 27 | # jetson.ok() will provide the proper update frequency 28 | while jetson.ok(): 29 | # Read engines list 30 | engines = jetson.engine 31 | # Print all engines 32 | for engine_name in engines: 33 | engine = engines[engine_name] 34 | print("{engine_name} = {engine}".format(engine_name=engine_name, engine=engine)) 35 | # EOF 36 | -------------------------------------------------------------------------------- /jtop/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # flake8: noqa 19 | 20 | from .core.exceptions import JtopException 21 | from .core.gpu import GPU 22 | from .core.memory import Memory 23 | from .core.fan import Fan 24 | from .core.jetson_clocks import JetsonClocks 25 | from .core.nvpmodel import NVPModel 26 | from .jtop import jtop 27 | 28 | __author__ = "Raffaello Bonghi" 29 | __email__ = "raffaello@rnext.it" 30 | __cr__ = "(c) 2026, RB" 31 | __copyright__ = "(c) 2026, Raffaello Bonghi" 32 | # Version package 33 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#choosing-a-versioning-scheme 34 | __version__ = "4.5.4" 35 | # EOF 36 | -------------------------------------------------------------------------------- /jtop/tests_gui/test_gui_page.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import curses 19 | # GUI jtop interface 20 | from ..gui import JTOPGUI, Page 21 | # jtop client 22 | from ..jtop import jtop 23 | 24 | # How to run 25 | # python3 -m jtop.tests_gui.test_gui_page 26 | 27 | 28 | class TestPage(Page): 29 | 30 | def __init__(self, stdscr, jetson): 31 | super(TestPage, self).__init__("Test", stdscr, jetson) 32 | 33 | def draw(self, key, mouse): 34 | return super().draw(key, mouse) 35 | 36 | 37 | def main(): 38 | 39 | with jtop() as jetson: 40 | curses.wrapper(JTOPGUI, jetson, [TestPage]) 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | # EOF 46 | -------------------------------------------------------------------------------- /examples/jtop_fan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("Simple jtop fan reader") 25 | 26 | with jtop() as jetson: 27 | # jetson.ok() will provide the proper update frequency 28 | while jetson.ok(): 29 | print("Print fan status") 30 | print(jetson.fan) 31 | # Print for each fan all info 32 | for name in jetson.fan: 33 | speed = jetson.fan.get_speed(name) 34 | profile = jetson.fan.get_profile(name) 35 | print("{name} profile={profile} speed={speed}".format(name=name, profile=profile, speed=speed)) 36 | # EOF 37 | -------------------------------------------------------------------------------- /examples/jtop_hardware.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("Simple jtop hardware reader") 25 | 26 | with jtop() as jetson: 27 | # jetson.ok() will provide the proper update frequency 28 | if jetson.ok(): 29 | # Read hardware, platform and libraries list 30 | # Print all values 31 | for name_category, category in jetson.board.items(): 32 | print("{name}:".format(name=name_category)) 33 | # Print all category 34 | for name, value in category.items(): 35 | print(" - {name}: {value}".format(name=name, value=value)) 36 | # EOF 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ## To Reproduce 15 | 16 | Steps to reproduce the behavior: 17 | 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | ## Screenshots 24 | 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | ## Expected behavior 28 | 29 | A clear and concise description of what you expected to happen. 30 | 31 | ## Additional context 32 | 33 | Add any other context about the problem here. 34 | 35 | ### Board 36 | 37 | Output from `jetson_release -v`: 38 | 44 | 45 | * jetson-stats version: [e.g. 1.8] 46 | * P-Number: [e.g. pXXXX-XXXX] 47 | * Module: [e.g. NVIDIA Jetson XXX] 48 | * Jetpack: [e.g. 4.3] 49 | * L4T: [e.g. 5.2.1] 50 | 51 | ### Log from jtop.service 52 | 53 | Attach here the output from: `journalctl -u jtop.service -n 100 --no-pager` 54 | 55 | 58 | 59 | ### Log from jetson-stats installation 60 | 61 | Attach here the output from: `sudo -H pip3 install --no-cache-dir -v -U jetson-stats` 62 | 63 | 66 | 67 | ### RAW Data 68 | 69 | File from `jtop --error-log` attached 70 | 71 | -------------------------------------------------------------------------------- /jtop/terminal_colors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | class bcolors: 20 | HEADER = '\033[95m' 21 | OKBLUE = '\033[94m' 22 | OKGREEN = '\033[92m' 23 | WARNING = '\033[93m' 24 | FAIL = '\033[91m' 25 | ENDC = '\033[0m' 26 | BOLD = '\033[1m' 27 | UNDERLINE = '\033[4m' 28 | 29 | @staticmethod 30 | def ok(message="OK"): 31 | return bcolors.OKGREEN + str(message) + bcolors.ENDC 32 | 33 | @staticmethod 34 | def warning(message="WARN"): 35 | return bcolors.WARNING + str(message) + bcolors.ENDC 36 | 37 | @staticmethod 38 | def fail(message="ERR"): 39 | return bcolors.FAIL + str(message) + bcolors.ENDC 40 | 41 | @staticmethod 42 | def bold(message): 43 | return bcolors.BOLD + str(message) + bcolors.ENDC 44 | # EOF 45 | -------------------------------------------------------------------------------- /docs/jtop/how_is_it_works.rst: -------------------------------------------------------------------------------- 1 | How is it works 2 | =============== 3 | 4 | jtop is a power monitor that uses a service and a Python client library. 5 | 6 | .. image:: /images/architecture.drawio.png 7 | :align: center 8 | 9 | Like the image above, when jtop service start, load and decode the information on your board. 10 | 11 | Initialization 12 | -------------- 13 | 14 | #. Read NVIDIA Jetson EEPROM to detect which NVIDIA Jetson is running 15 | #. decode jetson_clocks to know if is running and which engines are involved when it starts. 16 | #. decode the NVPmodel to know which model is selected 17 | #. Open the ``/run/jtop.sock`` socket and wait for a jtop client connection 18 | 19 | Loop 20 | ---- 21 | 22 | When jtop is running read all status from your current board and share all this data to all jtop python connections. 23 | 24 | #. Read and estimate the CPU utilization from ``/proc/stat`` 25 | #. Read status from all devices in ``/sys/devices/system`` 26 | #. Read and decode memory status from ``/proc/meminfo`` 27 | #. Decode and read the status from all swaps using ``swapon`` command 28 | #. Check status from jetson_clocks 29 | #. Check which nvpmodel is running 30 | 31 | jtop.sock 32 | --------- 33 | 34 | jtop uses a service to share the data between client (jtop gui or your Python script) and a server. 35 | 36 | This service, called ``jtop.service`` use a socket file. It is located in: 37 | 38 | .. code-block:: console 39 | :class: no-copybutton 40 | 41 | /run/jtop.sock 42 | 43 | This socket is protected by access mode: **660** equivalent to ``srw-rw----`` and by the group. 44 | 45 | Only other users in ``jtop`` **group** have access to this socket 46 | -------------------------------------------------------------------------------- /scripts/jtop_env.sh: -------------------------------------------------------------------------------- 1 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 2 | # Copyright (c) 2019-2026 Raffaello Bonghi. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | # THIS SCRIPT MUST HAVE .SH ! 18 | 19 | # Load JETSON environment variables 20 | # Export variables to be loaded on bash script 21 | # https://blog.tintoy.io/2017/06/exporting-environment-variables-from-python-to-bash/ 22 | JETSON_VARIABLE="" 23 | JETSON_PYTHON_NAME="" 24 | if type -P python3 >/dev/null 2>&1 ; then 25 | JETSON_VARIABLE=$(python3 -c "import jtop; print(jtop.__path__[0])" 2> /dev/null) 26 | JETSON_PYTHON_NAME="python3" 27 | fi 28 | if type -P python >/dev/null 2>&1 && [ -z $JETSON_VARIABLE ] ; then 29 | JETSON_VARIABLE=$(python -c "import jtop; print(jtop.__path__[0])" 2> /dev/null) 30 | JETSON_PYTHON_NAME="python" 31 | fi 32 | 33 | # Load variables only if not empty the variable 34 | if [ ! -z $JETSON_VARIABLE ] ; then 35 | eval $($JETSON_PYTHON_NAME -m jtop.core.jetson_variables) 36 | fi 37 | # EOF 38 | -------------------------------------------------------------------------------- /examples/jtop_cpu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("Simple jtop cpu reader") 25 | 26 | with jtop() as jetson: 27 | # jetson.ok() will provide the proper update frequency 28 | if jetson.ok(): 29 | # Print all cpu 30 | for idx, cpu in enumerate(jetson.cpu['cpu']): 31 | print("------ CPU{idx} ------".format(idx=idx)) 32 | for key, value in cpu.items(): 33 | print("{key}: {value}".format(key=key, value=value)) 34 | # read aggregate CPU status 35 | total = jetson.cpu['total'] 36 | print("------ TOTAL ------") 37 | for key, value in total.items(): 38 | print("{key}: {value}".format(key=key, value=value)) 39 | # EOF 40 | -------------------------------------------------------------------------------- /docs/advanced-usage.rst: -------------------------------------------------------------------------------- 1 | 👨‍💻 Advanced Usage 2 | ===================== 3 | 4 | .. currentmodule:: jtop 5 | 6 | You can install jtop in a virtual environment or in a docker following the guidelines below 7 | 8 | .. admonition:: Virtual environment 9 | 10 | If you need to install in a virtual environment like *virtualenv*, you **must** install before in your host **and after** in your environment, like: 11 | 12 | .. code-block:: bash 13 | 14 | virtualenv venv 15 | source venv/bin/activate 16 | pip install -U jetson-stats 17 | 18 | 19 | **jtop** is a complete controller of all systems in your NVIDIA Jetson 20 | 21 | * Tegrastats 22 | * NVP Model 23 | * Fan 24 | * Status board (i.g. Model version, Jetpack, … ) 25 | 26 | .. code-block:: python 27 | 28 | from jtop import jtop 29 | 30 | 31 | You can initialize the jtop node like a file i.g. 32 | 33 | .. code-block:: python 34 | 35 | with jtop() as jetson: 36 | # jetson.ok() will provide the proper update frequency 37 | while jetson.ok(): 38 | # Read tegra stats 39 | print(jetson.stats) 40 | 41 | Or manually start up with the basic function open/close 42 | 43 | .. code-block:: python 44 | 45 | jetson = jtop() 46 | jetson.start() 47 | stat = jetson.stats 48 | jetson.close() 49 | 50 | You can read the status of your NVIDIA Jetson via callback 51 | 52 | .. code-block:: python 53 | 54 | def read_stats(jetson): 55 | print(jetson.stats) 56 | 57 | # Open the jtop 58 | jetson = jtop() 59 | # Attach a function where you can read the status of your jetson 60 | jetson.attach(read_stats) 61 | jetson.loop_for_ever() 62 | 63 | Other examples are available in `example folder `_. -------------------------------------------------------------------------------- /jtop/gui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # flake8: noqa 19 | 20 | from .jtopguiconfig import JTOPCONFIG 21 | from .jtopgui import JTOPGUI, Page 22 | from .pall import ALL 23 | from .pcpu import CPU 24 | from .pengine import ENGINE, engine_model 25 | from .pmem import MEM 26 | from .pcontrol import CTRL 27 | from .pinfo import INFO 28 | 29 | try: 30 | from jtop.core.hw_detect import is_thor 31 | _is_thor = is_thor() 32 | except Exception: 33 | try: 34 | from jtop.core.thor_power import devfreq_nodes 35 | _is_thor = bool(devfreq_nodes()) 36 | except Exception: 37 | _is_thor = False 38 | try: 39 | if _is_thor: 40 | from .pgpu_thor import GPU 41 | else: 42 | from .pgpu import GPU 43 | 44 | except Exception: 45 | from .pgpu import GPU 46 | 47 | if 'GPU' not in globals(): 48 | from .pgpu import GPU 49 | 50 | 51 | # EOF 52 | -------------------------------------------------------------------------------- /examples/jtop_callback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop, JtopException 20 | 21 | 22 | def read_stats(jetson): 23 | """ 24 | This is your callback function where you can read all files when are available. 25 | """ 26 | print(jetson.stats) 27 | 28 | 29 | if __name__ == "__main__": 30 | print("Initialize jtop callback") 31 | # Open the jtop 32 | jetson = jtop() 33 | # Attach a function where you can read the status of your jetson 34 | jetson.attach(read_stats) 35 | 36 | # This try excpet will catch jtop exception 37 | try: 38 | # This loop will manage the jtop status all the time 39 | # This is a blocking a function, if you do not want use you can use as well 40 | # start: jetson.start() 41 | # stop: jetson.stop() 42 | jetson.loop_for_ever() 43 | except JtopException as e: 44 | print(e) 45 | # EOF 46 | -------------------------------------------------------------------------------- /jtop/tests_gui/test_linear_gauge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import curses 19 | # GUI jtop interface 20 | from ..gui import JTOPGUI, Page 21 | from ..gui.lib.linear_gauge import linear_gauge 22 | # jtop client 23 | from ..jtop import jtop 24 | 25 | # How to run 26 | # python3 -m jtop.tests_gui.test_linear_gauge 27 | 28 | 29 | class TestPage(Page): 30 | 31 | def __init__(self, stdscr, jetson): 32 | super(TestPage, self).__init__("Test", stdscr, jetson) 33 | 34 | def draw(self, key, mouse): 35 | # Screen size 36 | height, width, first = self.size_page() 37 | # Print a linear gauge 38 | for idx in range(21): 39 | linear_gauge(self.stdscr, offset=first + 1 + idx, start=0, size=width, value=idx * 5) 40 | 41 | 42 | def main(): 43 | 44 | with jtop() as jetson: 45 | curses.wrapper(JTOPGUI, jetson, [TestPage]) 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | # EOF 51 | -------------------------------------------------------------------------------- /jtop/tests/test_00_service.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import time 19 | from jtop import jtop, JtopException 20 | from ..service import JtopServer 21 | from .conftest import reset_environment, emulate_device 22 | 23 | 24 | def test_service(): 25 | device = 'orin' 26 | emulate_device(device) 27 | # Start jtop Server 28 | jtop_server = JtopServer() 29 | jtop_server.start() 30 | # Check if is alive 31 | assert jtop_server.is_alive() 32 | # Init and open jtop 33 | jetson = jtop() 34 | jetson.start() 35 | # Wait 36 | time.sleep(0.5) 37 | # Close service 38 | jtop_server.close() 39 | # Close jetson 40 | try: 41 | jetson.close() 42 | except JtopException: 43 | pass 44 | # Check if service is off 45 | assert not jtop_server.is_alive() 46 | # Clear configuration 47 | jtop_server.config_clear() 48 | # Reset environment 49 | reset_environment(device) 50 | # EOF 51 | -------------------------------------------------------------------------------- /jtop/tests_gui/test_chart.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import curses 19 | # GUI jtop interface 20 | from ..gui import JTOPGUI, Page 21 | from ..gui.lib.chart import Chart 22 | # jtop client 23 | from ..jtop import jtop 24 | 25 | # How to run this test package 26 | # python3 -m jtop.tests_gui.test_chart 27 | 28 | 29 | class TestPage(Page): 30 | 31 | def __init__(self, stdscr, jetson): 32 | super(TestPage, self).__init__("Test", stdscr, jetson) 33 | # Create a chart 34 | self.chart = Chart(jetson, "Chart Test", self.update_chart) 35 | 36 | def update_chart(self, jetson, name): 37 | return {'value': [50]} 38 | 39 | def draw(self, key, mouse): 40 | # Screen size 41 | height, width, first = self.size_page() 42 | # Full size page 43 | size_x = [1, width - 1] 44 | size_y = [first + 1, height - 2] 45 | # Draw chart 46 | self.chart.draw(self.stdscr, size_x, size_y) 47 | 48 | 49 | def main(): 50 | 51 | with jtop() as jetson: 52 | curses.wrapper(JTOPGUI, jetson, [TestPage]) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | # EOF 58 | -------------------------------------------------------------------------------- /jtop/tests_gui/test_process_table.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import curses 19 | # GUI jtop interface 20 | from ..gui import JTOPGUI, Page 21 | from ..gui.lib.process_table import ProcessTable 22 | # jtop client 23 | from ..jtop import jtop 24 | 25 | # How to run 26 | # python3 -m jtop.tests_gui.test_process_table 27 | 28 | 29 | class TestJtop(jtop): 30 | def __init__(self): 31 | super(TestJtop, self).__init__() 32 | 33 | @property 34 | def processes(self): 35 | ''' Return an empty list of processes for testing. ''' 36 | return [] 37 | 38 | 39 | class TestPage(Page): 40 | 41 | def __init__(self, stdscr, jetson): 42 | super(TestPage, self).__init__("Test", stdscr, jetson) 43 | 44 | self.process_table = ProcessTable(stdscr, jetson) 45 | 46 | def draw(self, key, mouse): 47 | # Screen size 48 | height, width, first = self.size_page() 49 | line_counter = first + 1 50 | 51 | # Draw process table 52 | line_counter += self.process_table.draw(line_counter, 0, width, height, key, mouse) 53 | 54 | 55 | def main(): 56 | 57 | with TestJtop() as jetson: 58 | curses.wrapper(JTOPGUI, jetson, [TestPage]) 59 | 60 | 61 | if __name__ == "__main__": 62 | main() 63 | # EOF 64 | -------------------------------------------------------------------------------- /docs/nosudo.rst: -------------------------------------------------------------------------------- 1 | 🧩 How it works 2 | =============== 3 | 4 | The ``install_jtop_torun_without_sudo.sh`` script automates a safe, 5 | user-friendly installation of **jtop** that allows you to run it without 6 | ``sudo``. It performs the following steps: 7 | 8 | 1. **Safety check** 9 | 10 | - Refuses to run if invoked as ``root`` or via ``sudo``. 11 | - Instructs you to run ``sudo -v`` first so that later privileged 12 | commands (``apt``, ``systemctl``) can execute without repeated password prompts. 13 | 14 | 2. **Prepare environment** 15 | 16 | - Verifies whether ``pip3`` and ``uv`` are installed; if either is missing, installs them via ``apt``. 17 | - Ensures ``~/.local/bin`` is on your ``PATH`` so that ``uv`` applications are available in future shells. 18 | 19 | 3. **Install uv. Then install jtop with uv** 20 | 21 | - Uses uv pip install --python ""$HOME/.local/share/jtop/bin/python" --upgrade "git+https://github.com/rbonghi/jetson_stats.git" 22 | - to install the latest jtop version in the user's environment. 23 | 24 | 25 | 4. **Systemd service setup** 26 | 27 | - Creates or updates ``/etc/systemd/system/jtop.service`` to point to /usr/local/bin/jtop. 28 | - Can now be run with or without sudo. 29 | - Sets safe defaults (``Restart=on-failure``, ``RestartSec=10s``, etc.) to make the service robust. 30 | 31 | 5. **Enable and start the service** 32 | 33 | - Reloads systemd, enables ``jtop.service`` to start automatically at boot, 34 | and restarts it immediately. 35 | 36 | 6. **Result** 37 | 38 | After installation, you can simply launch jtop from your terminal 39 | as a normal user (no ``sudo`` required): 40 | 41 | .. code-block:: bash 42 | 43 | jtop 44 | 45 | 46 | Why use this method 47 | =================== 48 | 49 | .. code-block:: text 50 | 51 | • Keeps the system (root) Python environment clean and untouched 52 | • Uses uv’s isolation, making upgrades and uninstalls clean and safe 53 | • Fully compatible with Ubuntu 24.04+ and later Debian-based systems 54 | • Ensures jtop runs under your user account (avoids root-owned processes) 55 | • Provides systemd integration for automatic startup and recovery 56 | -------------------------------------------------------------------------------- /examples/jtop_flask_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from flask import Flask, Response 20 | from jtop import jtop 21 | import json 22 | 23 | 24 | class WebService: 25 | 26 | def __init__(self): 27 | # Load Service 28 | self._app = Flask(__name__) 29 | # Register update function 30 | self._app.add_url_rule('/status', 'status', self.status) 31 | # Initialize jtop 32 | self._jetson = jtop() 33 | # start service 34 | self.start() 35 | 36 | def status(self): 37 | response = {} 38 | # Get uptime 39 | response['uptime'] = str(self._jetson.uptime) 40 | # Spin jtop 41 | self._jetson.ok(spin=True) 42 | # return data 43 | return Response( 44 | response=json.dumps(response), 45 | status=201, 46 | mimetype="application/json" 47 | ) 48 | 49 | def start(self): 50 | print("Init server ...") 51 | # Start jtop 52 | self._jetson.start() 53 | # Start server 54 | self._app.run(debug=False) 55 | 56 | def stop(self): 57 | print("switch off server") 58 | # Stop jtop 59 | self._jetson.close() 60 | # Stop server 61 | 62 | 63 | if __name__ == "__main__": 64 | # Initialize service 65 | service = WebService() 66 | service.stop() 67 | # EOF 68 | -------------------------------------------------------------------------------- /jtop/tests/test_05_gui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | import curses 20 | from jtop import jtop 21 | from .conftest import emulate_all_devices 22 | # Import gui test 23 | from ..gui.lib.chart import Chart 24 | from ..gui import JTOPGUI, ALL, GPU, CPU, MEM, ENGINE, CTRL, INFO 25 | 26 | 27 | @pytest.fixture 28 | def reset_chart(): 29 | Chart.reset_color_counter() 30 | 31 | 32 | def openGUI(stdscr, jetson): 33 | # Initialization Menu 34 | pages = [ALL] 35 | if jetson.gpu: 36 | pages += [GPU] 37 | pages += [CPU, MEM] 38 | if jetson.engine: 39 | pages += [ENGINE] 40 | pages += [CTRL, INFO] 41 | pages = JTOPGUI(stdscr, jetson, pages, start=False) 42 | return pages 43 | 44 | 45 | def test_openGUI(setup_jtop_server, reset_chart): 46 | # Load command line controller 47 | stdscr = curses.initscr() 48 | # Initialize colors 49 | curses.start_color() 50 | # Run jtop 51 | with jtop() as jetson: 52 | if jetson.ok(): 53 | # Reset counter charts 54 | Chart.reset_color_counter() 55 | assert Chart.COLOR_COUNTER == 0 56 | # Open JTOPGUI 57 | pages = openGUI(stdscr, jetson) 58 | # Start with selected page 59 | pages.set(0) 60 | assert True 61 | 62 | 63 | test_openGUI = pytest.mark.parametrize("setup_jtop_server", emulate_all_devices(), indirect=True)(test_openGUI) 64 | # EOF 65 | -------------------------------------------------------------------------------- /examples/jtop_logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop, JtopException 20 | import csv 21 | import argparse 22 | 23 | 24 | if __name__ == "__main__": 25 | parser = argparse.ArgumentParser(description='Simple jtop logger') 26 | # Standard file to store the logs 27 | parser.add_argument('--file', action="store", dest="file", default="log.csv") 28 | args = parser.parse_args() 29 | 30 | print("Simple jtop logger") 31 | print("Saving log on {file}".format(file=args.file)) 32 | 33 | try: 34 | with jtop() as jetson: 35 | # Make csv file and setup csv 36 | with open(args.file, 'w') as csvfile: 37 | stats = jetson.stats 38 | # Initialize cws writer 39 | writer = csv.DictWriter(csvfile, fieldnames=stats.keys()) 40 | # Write header 41 | writer.writeheader() 42 | # Write first row 43 | writer.writerow(stats) 44 | # Start loop 45 | while jetson.ok(): 46 | stats = jetson.stats 47 | # Write row 48 | writer.writerow(stats) 49 | print("Log at {time}".format(time=stats['time'])) 50 | except JtopException as e: 51 | print(e) 52 | except KeyboardInterrupt: 53 | print("Closed with CTRL-C") 54 | except IOError: 55 | print("I/O error") 56 | # EOF 57 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 2 | # Copyright (c) 2019-2026 Raffaello Bonghi. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | [tox] 19 | envlist = py{3.7,3.8,3.9,3.10,3.11,3.12,3.13,3.14} 20 | skip_missing_interpreters = true 21 | 22 | [testenv] 23 | setenv = 24 | TERM=linux 25 | TERMINFO=/etc/terminfo 26 | JTOP_TESTING=true 27 | basepython = 28 | py3.7: python3.7 29 | py3.8: python3.8 30 | py3.9: python3.9 31 | py3.10: python3.10 32 | py3.11: python3.11 33 | py3.12: python3.12 34 | py3.13: python3.13 35 | py3.14: python3.14 36 | deps = 37 | # Reference https://github.com/mgedmin/check-manifest/issues/98 38 | # Use a version compatible with Python 3.12+ where distutils is no longer part of 39 | # the standard library. 40 | check-manifest>=0.50 41 | flake8 42 | pytest 43 | # pytest-cov 44 | # coveralls 45 | commands = 46 | # List of enviroments variables 47 | # https://tox.readthedocs.io/en/latest/config.html#substitutions-for-virtualenv-related-sections 48 | check-manifest --ignore=tox.ini --ignore=jtop/tests* --ignore=docs* --ignore=examples* 49 | python setup.py check -m -s 50 | flake8 . 51 | # Run tests 52 | py.test -v 53 | 54 | [flake8] 55 | max-line-length = 160 56 | exclude = 57 | .git, 58 | .tox, 59 | .venv*, 60 | *.egg, 61 | build, 62 | test.*, 63 | data 64 | select = E,W,F 65 | 66 | [pytest] 67 | # log_cli = true 68 | # addopts = --capture=no --ignore=jtop/tests_gui 69 | addopts = --ignore=jtop/tests_gui 70 | log_cli_level = DEBUG 71 | log_format = %(asctime)s [%(levelname)s] %(name)s - %(message)s 72 | log_date_format = %Y-%m-%d %H:%M:%S 73 | filterwarnings = ignore:.* is deprecated:DeprecationWarning 74 | -------------------------------------------------------------------------------- /examples/jtop_properties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop 20 | 21 | 22 | if __name__ == "__main__": 23 | 24 | print("All accessible jtop properties") 25 | 26 | with jtop() as jetson: 27 | # boards 28 | print('*** board ***') 29 | print(jetson.board) 30 | # jetson.ok() will provide the proper update frequency 31 | while jetson.ok(): 32 | # CPU 33 | print('*** CPUs ***') 34 | print(jetson.cpu) 35 | # CPU 36 | print('*** Memory ***') 37 | print(jetson.memory) 38 | # GPU 39 | print('*** GPU ***') 40 | print(jetson.gpu) 41 | # Engines 42 | print('*** engine ***') 43 | print(jetson.engine) 44 | # nvpmodel 45 | print('*** NV Power Model ***') 46 | print(jetson.nvpmodel) 47 | # jetson_clocks 48 | print('*** jetson_clocks ***') 49 | print(jetson.jetson_clocks) 50 | # Status disk 51 | print('*** disk ***') 52 | print(jetson.disk) 53 | # Status fans 54 | print('*** fan ***') 55 | print(jetson.fan) 56 | # uptime 57 | print('*** uptime ***') 58 | print(jetson.uptime) 59 | # local interfaces 60 | print('*** local interfaces ***') 61 | print(jetson.local_interfaces) 62 | # Temperature 63 | print('*** temperature ***') 64 | print(jetson.temperature) 65 | # Power 66 | print('*** power ***') 67 | print(jetson.power) 68 | # EOF 69 | -------------------------------------------------------------------------------- /jtop/tests/test_02_fan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | from jtop import jtop 20 | from .marco_functions import set_fan_profile, set_fan_speed 21 | from .conftest import emulate_all_devices 22 | 23 | 24 | def test_fan(setup_jtop_server): 25 | device, jtop_server = setup_jtop_server 26 | # Run test 27 | with jtop() as jetson: 28 | print("Running test with parameter:", device) 29 | if jetson.ok(): 30 | # Read fan status 31 | fan = jetson.fan 32 | # Status fan 33 | print("Fan output: {fan}".format(fan=fan)) 34 | # Check depend of parameter 35 | if device in ['simple', 'tk']: 36 | assert len(fan) == 0 37 | else: 38 | assert len(fan) > 0 39 | 40 | 41 | def test_fan_set_profile(setup_jtop_server): 42 | device, jtop_server = setup_jtop_server 43 | with jtop() as jetson: 44 | # Detect which emulation is running and select a test profile 45 | if device in ['tx', 'nano']: 46 | set_fan_profile(jetson, 'temp_control') 47 | else: 48 | set_fan_profile(jetson, 'quiet') 49 | 50 | 51 | def test_fan_set_speed(setup_jtop_server): 52 | with jtop() as jetson: 53 | # Set a new fan speed 54 | set_fan_speed(jetson, 100) 55 | 56 | 57 | test_fan = pytest.mark.parametrize("setup_jtop_server", emulate_all_devices(), indirect=True)(test_fan) 58 | test_fan_set_profile = pytest.mark.parametrize( 59 | "setup_jtop_server", ['tx', 'nano', 'xavier', 'orin'], indirect=True)(test_fan_set_profile) 60 | test_fan_set_speed = pytest.mark.parametrize( 61 | "setup_jtop_server", ['tx', 'nano', 'xavier', 'orin'], indirect=True)(test_fan_set_speed) 62 | # EOF 63 | -------------------------------------------------------------------------------- /examples/jtop_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | 20 | from jtop import jtop, JtopException 21 | import socket 22 | import json 23 | import argparse 24 | 25 | parser = argparse.ArgumentParser(description='Simple jtop server.') 26 | 27 | # Standard loopback interface address (localhost) 28 | parser.add_argument('--host', action="store", dest="host", default="127.0.0.1") 29 | 30 | # Port to listen on (non-privileged ports are > 1023) 31 | parser.add_argument('--port', action="store", dest="port", type=int, default=65432) 32 | 33 | # Optional argument to return message in a valid HTTP response 34 | parser.add_argument('--http', action="store_true") 35 | 36 | args = parser.parse_args() 37 | 38 | if __name__ == "__main__": 39 | 40 | print("Simple jtop server") 41 | 42 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 43 | sock.bind((args.host, args.port)) 44 | print("Open server jtop to {}:{}".format(args.host, args.port)) 45 | sock.listen(1) 46 | 47 | try: 48 | with jtop() as jetson: 49 | while jetson.ok(): 50 | # Wait socket request 51 | conn, addr = sock.accept() 52 | print("Connected to {}".format(conn)) 53 | # Read and convert in JSON the jetson stats 54 | stats = json.dumps(jetson.stats) 55 | # Send by socket 56 | if args.http: 57 | message = "HTTP/1.1 200 OK\r\nHost: {host}:{port}\r\nContent-Type: application/json\r\nContent-Length: {length}\r\n\r\n{stats}" 58 | conn.send(message.format(host=args.host, port=args.port, length=len(stats), stats=stats.encode())) 59 | else: 60 | conn.send(stats.encode()) 61 | # Close connection 62 | conn.close() 63 | except JtopException as e: 64 | print(e) 65 | except Exception as e: 66 | print(e) 67 | finally: 68 | sock.close() 69 | # EOF 70 | -------------------------------------------------------------------------------- /scripts/install_jtop_torun_without_sudo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script installs jtop into a user's uv venv but sets up 3 | # a system-wide symlink and a root-level systemd service. 4 | set -Eeuo pipefail 5 | 6 | # Configuration 7 | APP_NAME="jtop" 8 | PKG_NAME="jetson_stats" 9 | VENV_DIR="$HOME/.local/share/$APP_NAME" 10 | JTOP_BIN="$VENV_DIR/bin/$APP_NAME" 11 | SYMLINK_PATH="/usr/local/bin/$APP_NAME" 12 | SYSTEMD_UNIT="/etc/systemd/system/${APP_NAME}.service" 13 | JTOP_REF="${JTOP_REF:-git+https://github.com/rbonghi/jetson_stats.git}" 14 | 15 | # Error Handling 16 | cleanup() { 17 | echo "Error on line $BASH_LINENO. Exit code: $?" >&2 18 | } 19 | trap cleanup ERR 20 | 21 | if [[ $EUID -eq 0 ]]; then 22 | echo "Please first run 'sudo -v'" 23 | echo "This script must be run as a regular user. NOT with sudo." 24 | exit 1 25 | fi 26 | 27 | # Installation 28 | echo "Installing prerequisites (curl, ca-certificates)..." 29 | sudo apt update -y 30 | sudo apt install -y --no-install-recommends curl ca-certificates python3-pip 31 | 32 | # Ensure uv is installed 33 | if ! command -v uv >/dev/null 2>&1; then 34 | echo "Installing 'uv' (an exceptionally fast Python package installer)" 35 | curl -fsSL https://astral.sh/uv/install.sh | sh 36 | else 37 | echo "'uv' is already installed." 38 | fi 39 | 40 | export PATH="$HOME/.local/bin:$PATH" 41 | 42 | echo "Creating Python virtual environment in $VENV_DIR..." 43 | uv venv "$VENV_DIR" -p python3.12 --seed 44 | 45 | echo "Installing/upgrading $PKG_NAME from: $JTOP_REF" 46 | uv pip install --python "$VENV_DIR/bin/python" --upgrade "$JTOP_REF" 47 | 48 | 49 | # Verify binary exists (run as user) 50 | if ! test -x "$JTOP_BIN"; then 51 | echo " Installation failed: '$JTOP_BIN' binary not found." 52 | exit 1 53 | fi 54 | 55 | # This makes 'jtop' (user) and 'sudo jtop' (root) work correctly 56 | sudo ln -sf "$JTOP_BIN" "$SYMLINK_PATH" 57 | echo "Symlink created: $SYMLINK_PATH" 58 | 59 | if [ -f "$SYSTEMD_UNIT" ]; then 60 | echo "Found existing jtop service. It will be overwritten." 61 | fi 62 | 63 | echo "Creating systemd service: $SYSTEMD_UNIT" 64 | sudo tee "$SYSTEMD_UNIT" >/dev/null <. 17 | 18 | import curses 19 | from .smallbutton import ButtonList 20 | # Gui refresh rate 21 | GUI_REFRESH = 1000 // 20 22 | 23 | 24 | class DialogWindow(object): 25 | def __init__(self, title, text, on_click, buttons, width=44, height=6): 26 | self.dialog_width = width 27 | self.dialog_height = height 28 | self.title = title 29 | self.text = text 30 | self.on_click = on_click 31 | self.buttons = buttons 32 | self.enable_dialog_window = False 33 | self._dialog_win = None 34 | self.info = {} 35 | 36 | def enable(self, title="", info={}): 37 | if title: 38 | self.title = title 39 | self.info = info 40 | self.enable_dialog_window = True 41 | 42 | def disable(self): 43 | self.enable_dialog_window = False 44 | 45 | def show(self, stdscr, key, mouse): 46 | if self.enable_dialog_window: 47 | self._draw(stdscr, key, mouse) 48 | 49 | def _draw(self, stdscr, key, mouse): 50 | # Refresh the window to show the changes 51 | height, width = stdscr.getmaxyx() 52 | dialog_y, dialog_x = (height - self.dialog_height) // 2, (width - self.dialog_width) // 2 53 | self._dialog_win = curses.newwin(self.dialog_height, self.dialog_width, dialog_y, dialog_x) 54 | # Create a list of buttons 55 | self._buttons_profile = ButtonList(self._dialog_win, self._on_click, self.buttons, info=self.info, linear=True) 56 | # Add a border around the window 57 | self._dialog_win.border() 58 | # Add the title and the text 59 | self._dialog_win.addstr(1, 2, self.title, curses.A_BOLD) 60 | self._dialog_win.addstr(2, 2, self.text) 61 | # Add the buttons 62 | align_mouse = (mouse[0] - dialog_x, mouse[1] - dialog_y) if mouse else () 63 | self._buttons_profile.update(4, 2, key, align_mouse, "") 64 | # Refresh the window to show the changes 65 | self._dialog_win.refresh() 66 | self._dialog_win.timeout(GUI_REFRESH) 67 | 68 | def _on_click(self, info, selected): 69 | self.on_click(info, selected) 70 | self.enable_dialog_window = False 71 | # EOF 72 | -------------------------------------------------------------------------------- /examples/jtop_control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | from jtop import jtop, JtopException 20 | 21 | if __name__ == "__main__": 22 | 23 | print("Simple jtop controller") 24 | 25 | try: 26 | # All options are not blocking 27 | with jtop() as jetson: 28 | # Read jetson_clocks status 29 | print(jetson.jetson_clocks) 30 | # Set a new status 31 | jetson.jetson_clocks = True 32 | # Read nvpmodel 33 | if jetson.nvpmodel: 34 | # Read nvpmodel 35 | print(jetson.nvpmodel) 36 | # Set new state 37 | jetson.nvpmodel = 0 # You can write the name of the model as well 38 | # Wait nvpmodel changed 39 | while jetson.ok(): 40 | # deepcode ignore AttributeLoadOnPrimitive: nvpmodel is an object with different attribute. See documentation 41 | if jetson.nvpmodel.id == 0: 42 | break 43 | # You can increase or decrease the nvpmodel using 44 | jetson.nvpmodel += 1 # or jetson.nvpmodel = jetson.nvpmodel + 1 45 | # Wait nvpmodel changed 46 | while jetson.ok(): 47 | # deepcode ignore AttributeLoadOnPrimitive: nvpmodel is an object with different attribute. See documentation 48 | if jetson.nvpmodel.id == 1: 49 | break 50 | # You can control the fan 51 | if jetson.fan: 52 | # read fan status 53 | print(jetson.fan) 54 | # You can change mode and setting 55 | jetson.fan.mode = 'system' 56 | # Wait nvpmodel changed 57 | while jetson.ok(): 58 | if jetson.fan.mode == 'system': 59 | break 60 | # Or you can change the fan speed 61 | jetson.fan.speed = 100 62 | while jetson.ok(): 63 | # Print jetson fan status 64 | print(jetson.fan) 65 | # Leave when fan measure is at 100% 66 | if jetson.fan.speed == 100: 67 | break 68 | except JtopException as e: 69 | print(e) 70 | # EOF 71 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | jetson-stats 2 | ============ 3 | 4 | .. toctree:: 5 | :hidden: 6 | :maxdepth: 3 7 | 8 | rnext.it 9 | Community Discord 10 | sponsors 11 | jtop/jtop 12 | advanced-usage 13 | docker 14 | reference/index 15 | other-tools/index 16 | troubleshooting 17 | contributing 18 | GitHub 19 | nosudo 20 | 21 | .. toctree:: 22 | :hidden: 23 | :caption: Project 24 | 25 | ros_jetson_stats 26 | isaac_ros_jetson 27 | Grafana plugin 28 | 29 | 30 | jetson-stats is a package for monitoring and control your NVIDIA Jetson [Thor, Orin, Xavier, Nano, TX] series. Works with all NVIDIA Jetson ecosystem. 31 | 32 | .. image:: images/jtop.gif 33 | :align: center 34 | 35 | Installing 36 | ---------- 37 | 38 | jetson-stats can be installed with `pip `_ 39 | 40 | .. code-block:: bash 41 | 42 | sudo pip3 install -U jetson-stats 43 | 44 | Don't forget to **logout/login** or **reboot** your board 45 | 46 | **🚀 That's it! 🚀** 47 | 48 | Run 49 | --- 50 | 51 | Start jtop it's pretty simple just write `jtop`! 52 | 53 | .. code-block:: bash 54 | 55 | jtop 56 | 57 | A simple interface will appear on your terminal, more capabilities are documented at :doc:`jtop/jtop` page. 58 | 59 | Advanced usage 60 | -------------- 61 | 62 | The more in-depth :doc:`advanced-usage` guide is the place to jtop such a python library and use for your project. 63 | 64 | .. code-block:: python 65 | 66 | from jtop import jtop 67 | 68 | with jtop() as jetson: 69 | # jetson.ok() will provide the proper update frequency 70 | while jetson.ok(): 71 | # Read tegra stats 72 | print(jetson.stats) 73 | 74 | The :doc:`reference/index` documentation provides API-level documentation. 75 | 76 | 77 | Compatibility 78 | ------------- 79 | 80 | jetson-stats is compatible with: 81 | 82 | * NVIDIA Clara AGX (experimental) 83 | * NVIDIA Jetson AGX Thor 84 | * NVIDIA Jetson Orin Series 85 | * NVIDIA Jetson Orin Nano 86 | * NVIDIA Jetson Orin NX 87 | * NVIDIA Jetson AGX Orin 88 | * NVIDIA Jetson IGX Orin (experimental) 89 | * NVIDIA Jetson Xavier Series 90 | * NVIDIA Jetson AGX Xavier Industrial 91 | * NVIDIA Jetson AGX Xavier 92 | * NVIDIA Jetson Xavier NX 93 | * NVIDIA Jetson Nano 94 | * NVIDIA Jetson TX Series 95 | * NVIDIA Jetson TX2 NX 96 | * NVIDIA Jetson TX2i 97 | * NVIDIA Jetson TX2 98 | * NVIDIA Jetson TX1 99 | * NVIDIA Jetson TK1 100 | * Nintendo Switch 101 | 102 | If you need a specific Compatibility open an `issue `_. 103 | 104 | 105 | License 106 | ------- 107 | 108 | jetson-stats is made available under the AGPL-3.0 license. For more details, see `LICENSE `_. 109 | 110 | 111 | Contributing 112 | ------------ 113 | 114 | We happily welcome contributions, please see :doc:`contributing` for details. 115 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "jetson-stats" 7 | dynamic = ["version"] 8 | description = "Interactive system-monitor and process viewer for all NVIDIA Jetson [Thor, Orin, Xavier, Nano, TX] series" 9 | readme = "README.md" 10 | authors = [ 11 | {name = "Raffaello Bonghi", email = "raffaello@rnext.it"}, 12 | ] 13 | maintainers = [ 14 | {name = "Raffaello Bonghi", email = "raffaello@rnext.it"}, 15 | ] 16 | license = {text = "AGPL-3.0"} 17 | keywords = [ 18 | "jetson_stats", 19 | "jtop", 20 | "python", 21 | "system-monitor", 22 | "docker", 23 | "nvidia", 24 | "Jetson", 25 | "Thor", 26 | "AGXThor", 27 | "Orin", 28 | "AGXOrin", 29 | "Xavier", 30 | "AGXXavier", 31 | "XavierNX", 32 | "Nano", 33 | "TX1", 34 | "TX2", 35 | "process", 36 | "viewer" 37 | ] 38 | classifiers = [ 39 | "Development Status :: 5 - Production/Stable", 40 | "Intended Audience :: Developers", 41 | "Topic :: Software Development :: Embedded Systems", 42 | "Topic :: Software Development :: Debuggers", 43 | "Topic :: Software Development :: Libraries", 44 | "Topic :: Software Development :: User Interfaces", 45 | "Topic :: System :: Hardware", 46 | "Topic :: System :: Logging", 47 | "Topic :: System :: Monitoring", 48 | "Topic :: System :: Operating System", 49 | "Topic :: System :: Operating System Kernels", 50 | "Topic :: System :: Shells", 51 | "Topic :: System :: Systems Administration", 52 | "Topic :: Terminals", 53 | "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", 54 | "Programming Language :: Unix Shell", 55 | "Programming Language :: Python :: 3", 56 | "Programming Language :: Python :: 3.7", 57 | "Programming Language :: Python :: 3.8", 58 | "Programming Language :: Python :: 3.9", 59 | "Programming Language :: Python :: 3.10", 60 | "Programming Language :: Python :: 3.11", 61 | "Programming Language :: Python :: 3.12", 62 | "Programming Language :: Python :: 3.13", 63 | "Programming Language :: Python :: 3.14", 64 | "Operating System :: POSIX :: Linux", 65 | ] 66 | requires-python = ">=3.7" 67 | dependencies = [ 68 | "smbus2", 69 | "distro", 70 | "nvidia-ml-py>=13.0", 71 | ] 72 | 73 | [project.urls] 74 | Homepage = "https://github.com/rbonghi/jetson_stats" 75 | Documentation = "https://rnext.it/jetson_stats" 76 | Repository = "https://github.com/rbonghi/jetson_stats" 77 | Issues = "https://github.com/rbonghi/jetson_stats/issues" 78 | Funding = "https://github.com/sponsors/rbonghi" 79 | Discord = "https://discord.gg/BFbuJNhYzS" 80 | Examples = "https://github.com/rbonghi/jetson_stats/tree/master/examples" 81 | 82 | [project.scripts] 83 | jtop = "jtop.__main__:main" 84 | jetson_release = "jtop.jetson_release:main" 85 | jetson_config = "jtop.jetson_config:main" 86 | jetson_swap = "jtop.jetson_swap:main" 87 | 88 | [tool.setuptools] 89 | packages = ["jtop", "jtop.core", "jtop.gui", "jtop.gui.lib"] 90 | include-package-data = true 91 | zip-safe = false 92 | 93 | [tool.setuptools.dynamic] 94 | version = {attr = "jtop.__version__"} 95 | 96 | [tool.setuptools.data-files] 97 | "share/jetson_stats" = ["services/jtop.service", "scripts/jtop_env.sh"] 98 | 99 | [tool.setuptools.exclude-package-data] 100 | "*" = ["tests*", "*.tests*", "examples*"] 101 | -------------------------------------------------------------------------------- /jtop/core/timer_reader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import sys 19 | import time 20 | from threading import Thread, Event 21 | # Logging 22 | import logging 23 | # Create logger 24 | logger = logging.getLogger(__name__) 25 | 26 | TIMER_READER_MIN_SLEEP = 0.05 27 | 28 | 29 | class TimerReader: 30 | 31 | def __init__(self, callback): 32 | self._stop_event = Event() 33 | # Initialize callback 34 | self._callback = callback 35 | # Error message from thread 36 | self._error = None 37 | # Define Thread 38 | self._thread = None 39 | 40 | def _timer_callback(self, interval, stop_event): 41 | logger.debug("jtop timer start at {interval}s".format(interval=interval)) 42 | try: 43 | while stop_event.is_set(): 44 | start = time.time() 45 | # Callback function 46 | self._callback() 47 | # Measure timer_callback sleep time 48 | delta = time.time() - start 49 | # Start to sleep 50 | if interval > delta: 51 | time.sleep(interval - delta) 52 | except (KeyboardInterrupt, SystemExit): 53 | logger.info("KeyboardInterrupt or SystemExit, exit timer_reader thread") 54 | except Exception as e: 55 | logger.fatal("Exception in 'timer_reader thread': {}".format(e)) 56 | # Store error message 57 | self._error = sys.exc_info() 58 | logger.debug("jtop timer stopped") 59 | 60 | def open(self, interval=0.5): 61 | # Catch exception if exist 62 | self._error_status() 63 | # Check if not running 64 | if self._thread is not None: 65 | return False 66 | # Check if thread or process exist 67 | self._stop_event.set() 68 | # Start thread Service client 69 | self._thread = Thread(target=self._timer_callback, args=(interval, self._stop_event, )) 70 | self._thread.start() 71 | return True 72 | 73 | def close(self, timeout=None): 74 | # Catch exception if exist 75 | self._error_status() 76 | # Check if thread and process are already empty 77 | self._stop_event.clear() 78 | if self._thread is not None: 79 | self._thread.join(timeout) 80 | self._thread = None 81 | return True 82 | 83 | def _error_status(self): 84 | # Catch exception if exist 85 | if not self._error: 86 | return 87 | # Extract exception and raise 88 | ex_type, ex_value, tb_str = self._error 89 | ex_value.__traceback__ = tb_str 90 | raise ex_value 91 | # EOF 92 | -------------------------------------------------------------------------------- /docs/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | 🆘 Troubleshooting 2 | ================== 3 | 4 | Let's resolve here the common issues can happening using jetson-stats (jtop). 5 | 6 | Before to start, have you updated jetson-stats to the latest release? 7 | 8 | if not, write: 9 | 10 | .. code-block:: bash 11 | 12 | sudo pip3 install -U jetson-stats 13 | 14 | If nothing changed follow the help below. 15 | 16 | pip3: command not found 17 | ^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | When you try to install jetson-stats, you read an output like: 20 | 21 | .. code-block:: console 22 | :class: no-copybutton 23 | 24 | user@board:~$ sudo pip3 install -U jetson-stats 25 | sudo: pip3: command not found 26 | 27 | you can simple install python3-pip 28 | 29 | .. code-block:: bash 30 | 31 | sudo apt install python3-pip 32 | 33 | jtop.service inactive 34 | ^^^^^^^^^^^^^^^^^^^^^ 35 | 36 | When the *jtop.service* (or previously *jetson-stats.service*) is inactive there are few different reasons 37 | 38 | First step, restart jtop service following the command below 39 | 40 | .. code-block:: bash 41 | 42 | sudo systemctl restart jtop.service 43 | 44 | If the error is not fixed run: 45 | 46 | .. code-block:: bash 47 | 48 | journalctl -u jtop.service -n 100 --no-pager 49 | 50 | If you read here an error, open an `issue `_ reporting the output. 51 | 52 | jtop start only with sudo 53 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 54 | 55 | If jtop start only with ``sudo jtop`` and when you run without sudo you read this error: 56 | 57 | .. code-block:: console 58 | :class: no-copybutton 59 | 60 | user@board:~$ jtop 61 | I can't access jtop.service. 62 | Please logout or reboot this board. 63 | 64 | The reason can be your user is not allowed to have access to jtop. 65 | 66 | There are two way to fix it: 67 | 68 | 1. Run the command below and check if is all **OK** if you read **FAIL** on fix permissions, press **Fix all** and logout/login. 69 | 70 | .. code-block:: bash 71 | 72 | sudo jtop --health 73 | 74 | .. image:: images/jtop-fail-user.png 75 | :align: center 76 | 77 | 1. You can manually do writing the command below 78 | 79 | .. code-block:: bash 80 | 81 | sudo usermod -a -G jtop $USER 82 | 83 | remember to logout/login. 84 | 85 | Bad visualization on Putty 86 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 87 | 88 | If you experiences a bad visualization working with jtop on Putty, like a sequence of "qqqqqwqqqq and xxxx" you can fix following the steps below: 89 | 90 | 1. Window -> Translation 91 | 2. Enable VT100 line drawing even in UTF-8 mode 92 | 93 | Nothing fix my error 94 | ^^^^^^^^^^^^^^^^^^^^ 95 | 96 | Before to open an `issue`_, try to reinstall the latest version with this command 97 | 98 | .. code-block:: bash 99 | 100 | sudo pip3 install --no-cache-dir -v -U jetson-stats 101 | 102 | Save the output somewhere, if this command doesn't fix can be helpful when you one an `issue`_. 103 | 104 | Run this command and save the output. This output help me to understand the reason of this error. 105 | 106 | .. code-block:: bash 107 | 108 | journalctl -u jtop.service -n 100 --no-pager 109 | 110 | Remember also to add other information about your board 111 | 112 | You can find on: 113 | 114 | .. code-block:: bash 115 | 116 | jetson_release -v 117 | 118 | - jetson-stats version: [e.g. 1.8] 119 | 120 | - P-Number: [e.g. pXXXX-XXXX] 121 | - Module: [e.g. NVIDIA Jetson XXX] 122 | 123 | - Jetpack: [e.g. 4.3] 124 | - L4T: [e.g. 5.2.1] 125 | -------------------------------------------------------------------------------- /jtop/core/hardware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from .jetson_variables import get_jetson_variables 19 | from .common import cat 20 | import os 21 | import logging 22 | import platform 23 | # Load distro library from python3 or use platform 24 | try: 25 | import distro 26 | except ImportError: 27 | distro = platform 28 | # Create logger 29 | logger = logging.getLogger(__name__) 30 | 31 | 32 | def get_parameter(path): 33 | if os.path.isfile(path): 34 | return cat(path).strip() 35 | 36 | 37 | def get_platform_variables(): 38 | return { 39 | 'Machine': platform.machine(), 40 | 'System': platform.system(), 41 | 'Distribution': " ".join(distro.linux_distribution()), 42 | 'Release': platform.release(), 43 | 'Python': platform.python_version(), 44 | } 45 | 46 | 47 | def get_x86_64_variables(): 48 | hardware = {} 49 | hardware_path = "/sys/devices/virtual/dmi/id/" 50 | items = os.listdir(hardware_path) 51 | for item in sorted(items): 52 | if item in ['uevent', 'modalias', 'board_serial', 'bios_release', 'product_uuid', 'chassis_type']: 53 | continue 54 | path = os.path.join(hardware_path, item) 55 | output = "" 56 | if os.path.isfile(path): 57 | output = cat(path).strip() 58 | if not output or output == 'Default string': 59 | continue 60 | name = item.replace("_", " ").capitalize() 61 | hardware[name] = output 62 | return hardware 63 | 64 | 65 | def get_hardware(): 66 | # If hardware is ARM check if NVIDIA Jetson 67 | platform_board = platform.machine() 68 | logger.info("Hardware detected {}".format(platform_board)) 69 | if platform_board == 'aarch64': 70 | # Load Jetson data 71 | jetson = get_jetson_variables() 72 | # Print main jetson variables 73 | if '699-level Part Number' in jetson: 74 | logger.info("NVIDIA Jetson 699-level Part Number={}".format(jetson['699-level Part Number'])) 75 | else: 76 | logger.error("NVIDIA Jetson No 699-level Part Number detected!") 77 | if 'Module' in jetson: 78 | logger.info("NVIDIA Jetson Module={}".format(jetson['Module'])) 79 | else: 80 | logger.error("NVIDIA Jetson No Module detected!") 81 | # Check L4T detection 82 | if jetson['L4T']: 83 | logger.info("NVIDIA Jetson detected L4T={}".format(jetson['L4T'])) 84 | else: 85 | logger.error("NVIDIA Jetson L4T not detected!") 86 | return jetson 87 | elif platform_board == 'x86_64': 88 | return get_x86_64_variables() 89 | else: 90 | logger.warning("Unrecognized board {}".format(platform_board)) 91 | return {} 92 | # EOF 93 | -------------------------------------------------------------------------------- /jtop/tests/test_03_jetson_clocks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | from jtop import jtop, JetsonClocks 20 | from .conftest import emulate_all_devices 21 | from .marco_functions import set_jetson_clocks_boot 22 | # test functions 23 | MAX_COUNT = 10 24 | 25 | 26 | def test_jetson_clocks_output(setup_jtop_server): 27 | device, jtop_server = setup_jtop_server 28 | with jtop() as jetson: 29 | print("Running test with parameter:", device) 30 | if jetson.ok(): 31 | # Read jetson_clocks status 32 | jetson_clocks = jetson.jetson_clocks 33 | # Status jetson_clocks 34 | print("jetson_clocks output: {jetson_clocks}".format(jetson_clocks=jetson_clocks)) 35 | # Check depend of parameter 36 | if device in ['simple', 'tk']: 37 | assert jetson_clocks is None 38 | else: 39 | assert isinstance(jetson_clocks, JetsonClocks) 40 | 41 | 42 | def test_set_true_false(setup_jtop_server): 43 | with jtop() as jetson: 44 | # Check jetson_clocks status 45 | assert jetson.jetson_clocks.status == 'inactive' 46 | # check status is false 47 | assert not jetson.jetson_clocks 48 | # Set true jetson_clocks 49 | jetson.jetson_clocks = True 50 | # Wait jetson_clocks on 51 | counter = 0 52 | while jetson.ok(): 53 | if jetson.jetson_clocks or counter == MAX_COUNT: 54 | break 55 | counter += 1 56 | # Check jetson_clocks status 57 | assert jetson.jetson_clocks.status == 'running' 58 | # Check if is true 59 | assert jetson.jetson_clocks 60 | # Switch off jetson_clocks 61 | jetson.jetson_clocks = False 62 | # Wait jetson_clocks on 63 | counter = 0 64 | while jetson.ok(): 65 | if not jetson.jetson_clocks or counter == MAX_COUNT: 66 | break 67 | counter += 1 68 | # Check jetson_clocks status 69 | assert jetson.jetson_clocks.status == 'inactive' 70 | # Set to false jetson_clocks 71 | assert not jetson.jetson_clocks 72 | 73 | 74 | def test_set_boot(setup_jtop_server): 75 | device, jtop_server = setup_jtop_server 76 | with jtop() as jetson: 77 | # Set to false jetson_clocks 78 | assert not jetson.jetson_clocks 79 | # Enable on boot 80 | set_jetson_clocks_boot(jetson, True) 81 | # Disable on boot 82 | assert jetson.jetson_clocks.boot 83 | 84 | 85 | test_jetson_clocks_output = pytest.mark.parametrize( 86 | "setup_jtop_server", emulate_all_devices(), indirect=True)(test_jetson_clocks_output) 87 | test_set_true_false = pytest.mark.parametrize( 88 | "setup_jtop_server", ['tx', 'nano', 'xavier', 'orin'], indirect=True)(test_set_true_false) 89 | test_set_boot = pytest.mark.parametrize( 90 | "setup_jtop_server", ['tx', 'nano', 'xavier', 'orin'], indirect=True)(test_set_boot) 91 | # EOF 92 | -------------------------------------------------------------------------------- /jtop/jetson_swap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import re 19 | import os 20 | import sys 21 | import argparse 22 | from .terminal_colors import bcolors 23 | from .core.memory import MemoryService, read_swapon 24 | from .core.common import get_var 25 | # Version match 26 | VERSION_RE = re.compile(r""".*__version__ = ["'](.*?)['"]""", re.S) 27 | COPYRIGHT_RE = re.compile(r""".*__copyright__ = ["'](.*?)['"]""", re.S) 28 | 29 | 30 | def main(): 31 | parser = argparse.ArgumentParser( 32 | description='Create a swap file and enable on boot (require sudo)', 33 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 34 | parser.add_argument('-d', '--dir', dest="directory", help='Directory to place swapfile', type=str, default='') 35 | parser.add_argument('-n', '--name', dest="name", help='Name swap file', type=str, default='swapfile') 36 | parser.add_argument('-s', '--size', dest="size", help='Size in Gigabytes', type=int, default='8') 37 | parser.add_argument('-a', '--auto', dest="auto", help='Enable swap on boot', action="store_true", default=False) 38 | parser.add_argument('-t', '--status', dest="status", help='Check if the swap is currently active', action="store_true", default=False) 39 | parser.add_argument('--off', dest="off", help='Switch off the swap', action="store_true", default=False) 40 | # Parse arguments 41 | args = parser.parse_args() 42 | # Copyrights 43 | print("Software part of jetson-stats {version} - {copyright}".format(version=get_var(VERSION_RE), copyright=get_var(COPYRIGHT_RE))) 44 | # Status swap 45 | if args.status: 46 | # Print all swap 47 | swap_table = read_swapon() 48 | print("NAME\t\tTYPE\t\tPRIO\t\tSIZE\t\tUSED") 49 | for name, swap in swap_table.items(): 50 | print("{name}\t{type}\t\t{prio}\t\t{size}{unit}\t{used}{unit}".format( 51 | name=name, 52 | type=swap['type'], 53 | prio=swap['prio'], 54 | size=swap['size'], 55 | used=swap['used'], 56 | unit='k')) 57 | sys.exit(0) 58 | # Check if running a root 59 | if os.getuid() != 0: 60 | # Quit with error 61 | print(bcolors.fail("Please run with sudo")) 62 | parser.print_help(sys.stderr) 63 | sys.exit(1) 64 | # Define Memory Service 65 | memory_service = MemoryService 66 | # Path swap 67 | size = args.size 68 | auto = args.auto 69 | path_swap = "{directory}/{name}".format(directory=args.directory, name=args.name) 70 | if args.off: 71 | print("Switch off swap {path_swap}".format(path_swap=path_swap)) 72 | memory_service.swap_deactivate(path_swap) 73 | sys.exit(0) 74 | # Create Swap 75 | print("Create swap {path_swap} [{size}GB] - on boot={auto}".format(path_swap=path_swap, size=size, auto=auto)) 76 | # Create swap 77 | memory_service.swap_set(size, path_swap, auto) 78 | 79 | 80 | if __name__ == "__main__": 81 | main() 82 | # EOF 83 | -------------------------------------------------------------------------------- /tests/nvfancontrol: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # Emulation of 19 | # https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html 20 | 21 | usage() 22 | { 23 | if [ "$1" != "" ]; then 24 | tput setaf 1 25 | echo "$1" 26 | tput sgr0 27 | fi 28 | 29 | echo "FAKE Nvidia Fan Control Userspace Daemon" 30 | echo 31 | echo "Usage:" 32 | echo "nvfancontrol [-h | --help] [--verbose] [-q | --query] [-f | --file]" 33 | echo " -h, --help: Print this help info." 34 | echo " -f, --file: nvfancontrol conf file path." 35 | echo " -q, --query: print the current fan control status info." 36 | echo " --verbose: Enable verbose log." 37 | } 38 | 39 | 40 | query() 41 | { 42 | # Input file to be read 43 | input_file="/etc/nvfancontrol.conf" 44 | 45 | local fan_profile="" 46 | local fan_governor="" 47 | local fan_control="" 48 | # Loop through each line of the file 49 | while read line; do 50 | if [[ "$line" == *"FAN_DEFAULT_PROFILE"* ]]; then 51 | # Extract the second word from the line 52 | fan_profile=$(echo "$line" | awk '{print $2}') 53 | fi 54 | if [[ "$line" == *"FAN_DEFAULT_GOVERNOR"* ]]; then 55 | # Extract the second word from the line 56 | fan_governor=$(echo "$line" | awk '{print $2}') 57 | fi 58 | if [[ "$line" == *"FAN_DEFAULT_CONTROL"* ]]; then 59 | # Extract the second word from the line 60 | fan_control=$(echo "$line" | awk '{print $2}') 61 | fi 62 | done < "$input_file" 63 | 64 | echo "FAN1:FAN_PROFILE:$fan_profile" 65 | echo "FAN1:FAN_GOVERNOR:$fan_governor" 66 | echo "FAN1:FAN_CONTROL:$fan_control" 67 | 68 | mkdir -p /var/lib/nvfancontrol 69 | 70 | echo "FAN1:FAN_PROFILE:$fan_profile" > /var/lib/nvfancontrol/status 71 | echo "FAN1:FAN_GOVERNOR:$fan_governor" >> /var/lib/nvfancontrol/status 72 | echo "FAN1:FAN_CONTROL:$fan_control" >> /var/lib/nvfancontrol/status 73 | } 74 | 75 | 76 | main() 77 | { 78 | local VERBOSE=false 79 | 80 | if [ $(id -u) -ne 0 ] ; then 81 | echo "NVFAN ERROR: Sudo permissions are required to execute nvfancontrol user daemon" 82 | exit 1 83 | fi 84 | 85 | # Decode all information from startup 86 | while [ -n "$1" ]; do 87 | case "$1" in 88 | -q) 89 | query 90 | exit 0 91 | ;; 92 | --verbose) 93 | VERBOSE=true 94 | ;; 95 | -h|--help) 96 | usage 97 | exit 0 98 | ;; 99 | *) 100 | usage "[ERROR] Unknown option: $1" 101 | exit 1 102 | ;; 103 | esac 104 | shift 1 105 | done 106 | } 107 | 108 | main $@ 109 | exit 0 110 | 111 | # EOF 112 | -------------------------------------------------------------------------------- /jtop/gui/lib/process_table.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import curses 20 | from .colors import NColors 21 | from .common import size_to_string 22 | 23 | 24 | header = [ 25 | ("PID", {'clm': 7, 'fn': lambda x: str(x)}), 26 | ("USER", {'clm': 9, 'fn': lambda x: x}), 27 | ("GPU", {'clm': 5, 'fn': lambda x: x}), 28 | ("TYPE", {'clm': 6, 'fn': lambda x: x[0]}), 29 | ("PRI", {'clm': 5, 'fn': lambda x: str(x)}), 30 | ("S", {'clm': 4, 'fn': lambda x: x}), 31 | ("CPU%", {'clm': 7, 'fn': lambda x: "{:.1f}".format(x)}), 32 | ("MEM", {'clm': 8, 'fn': lambda x: size_to_string(x, 'k')}), 33 | ("GPU MEM", {'clm': 12, 'fn': lambda x: size_to_string(x, 'k')}), 34 | ("Command", {'clm': 20, 'fn': lambda x: x}), 35 | ] 36 | 37 | 38 | class ProcessTable(object): 39 | 40 | def __init__(self, stdscr, jetson): 41 | self.stdscr = stdscr 42 | self.jetson = jetson 43 | self.line_sort = 8 44 | self.type_reverse = True 45 | 46 | def draw(self, pos_y, pos_x, width, height, key, mouse): 47 | # Plot low bar background line 48 | try: 49 | self.stdscr.addstr(pos_y, 0, " " * width, NColors.igreen()) 50 | except curses.error: 51 | return 0 52 | title_counter = 0 53 | for idx, (title, info) in enumerate(header): 54 | try: 55 | # Check if pressed 56 | if mouse and mouse[1] == pos_y and title_counter <= mouse[0] <= title_counter + info['clm']: 57 | if self.line_sort != idx: 58 | self.line_sort = idx 59 | self.type_reverse = True 60 | else: 61 | self.type_reverse = not self.type_reverse 62 | # Draw title 63 | title = "[{}]".format(title) if idx == self.line_sort else title 64 | self.stdscr.addstr(pos_y, title_counter, title, NColors.igreen() | curses.A_BOLD) 65 | title_counter += info['clm'] 66 | except curses.error: 67 | break 68 | # Sort table for selected line 69 | try: 70 | sorted_processes = self.jetson.processes 71 | sorted_processes = sorted(sorted_processes, key=lambda x: x[self.line_sort], reverse=self.type_reverse) 72 | except IndexError: 73 | pass 74 | # Draw all processes 75 | # Instantiate the number of process variable to avoid an unbound local error if the process table is empty. 76 | nprocess = 0 77 | for nprocess, process in enumerate(sorted_processes): 78 | # Skip unit size process 79 | counter = 0 80 | for (value, (name, info)) in zip(process, header): 81 | # Print all values in a nice view 82 | try: 83 | self.stdscr.addstr(pos_y + nprocess + 1, counter, info['fn'](value), curses.A_NORMAL) 84 | counter += info['clm'] 85 | except curses.error: 86 | break 87 | # Stop loop if table is bigger than height 88 | if nprocess > height - 2: 89 | break 90 | return nprocess 91 | # EOF 92 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge-jetpack.yml: -------------------------------------------------------------------------------- 1 | name: Check Jetpack Release Version 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | 9 | jobs: 10 | check_version: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v6 15 | 16 | - name: Extract version from PR title 17 | id: extract_version 18 | run: | 19 | PR_TITLE="${{ github.event.pull_request.title }}" 20 | if [[ "$PR_TITLE" =~ ^Jetpack\ Release\ ([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then 21 | echo "version=${BASH_REMATCH[1]}" >> $GITHUB_ENV 22 | else 23 | echo "::notice::Skipping workflow as PR title does not match 'Jetpack Release '." 24 | exit 0 25 | fi 26 | 27 | - name: Check version in jtop/__init__.py 28 | id: check_version 29 | run: | 30 | FILE_VERSION=$(grep -oP "__version__\s*=\s*\"\K[0-9]+\.[0-9]+\.[0-9]+(?=\")" jtop/__init__.py) 31 | if [[ "$FILE_VERSION" == "${{ env.version }}" ]]; then 32 | echo "Version match confirmed." 33 | else 34 | echo "Version mismatch: Expected $FILE_VERSION but found ${{ env.version }} in PR title." >&2 35 | echo "::error::Version mismatch: Expected $FILE_VERSION but found ${{ env.version }} in PR title." >> $GITHUB_STEP_SUMMARY 36 | exit 1 37 | fi 38 | 39 | - name: Check version upgrade step 40 | id: check_version_step 41 | run: | 42 | OLD_VERSION=$(grep -oP "__version__\s*=\s*\"\K[0-9]+\.[0-9]+\.[0-9]+(?=\")" jtop/__init__.py) 43 | IFS='.' read -r old_major old_minor old_patch <<< "$OLD_VERSION" 44 | IFS='.' read -r new_major new_minor new_patch <<< "${{ env.version }}" 45 | if [[ "$new_major" -ne "$old_major" || "$new_minor" -ne "$old_minor" || "$new_patch" -ne $((old_patch + 1)) ]]; then 46 | echo "::error::Version upgrade is not a minor step upgrade. Expected $old_major.$old_minor.$((old_patch + 1)) but found $new_major.$new_minor.$new_patch." >> $GITHUB_STEP_SUMMARY 47 | exit 1 48 | fi 49 | 50 | - name: Check if jtop/core/jetson_variables.py is modified 51 | id: check_file_modified 52 | run: | 53 | if git diff --name-only origin/${{ github.event.pull_request.base.ref }} | grep -q "jtop/core/jetson_variables.py"; then 54 | echo "File jtop/core/jetson_variables.py is modified." 55 | else 56 | echo "File jtop/core/jetson_variables.py is not modified." >&2 57 | echo "::error::File jtop/core/jetson_variables.py is not modified but is required." >> $GITHUB_STEP_SUMMARY 58 | exit 1 59 | 60 | - name: Add comment on PR if check fails 61 | if: failure() 62 | uses: actions/github-script@v8 63 | with: 64 | github-token: ${{ secrets.GITHUB_TOKEN }} 65 | script: | 66 | const issue_number = context.payload.pull_request.number; 67 | const message = `🚨 The PR is missing required changes: 68 | - Ensure the PR title follows 'Jetpack Release '. 69 | - Ensure the version in jtop/__init__.py matches the PR title. 70 | - Ensure the version upgrade is a minor step (y.x.z → y.x.z+1). 71 | - Ensure jtop/core/jetson_variables.py is modified with the new Jetpack.`; 72 | github.rest.issues.createComment({ 73 | owner: context.repo.owner, 74 | repo: context.repo.repo, 75 | issue_number: issue_number, 76 | body: message 77 | }); 78 | 79 | - name: Create Release Branch 80 | if: success() 81 | run: | 82 | git checkout -b release/${{ env.version }} 83 | git push origin release/${{ env.version }} 84 | 85 | - name: Request Maintainer Approval 86 | if: success() 87 | uses: hmarr/auto-approve-action@v4 88 | with: 89 | github-token: ${{ secrets.GITHUB_TOKEN }} 90 | -------------------------------------------------------------------------------- /tests/local_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | bold=`tput bold` 19 | red=`tput setaf 1` 20 | green=`tput setaf 2` 21 | yellow=`tput setaf 3` 22 | blue=`tput setaf 4` 23 | reset=`tput sgr0` 24 | 25 | 26 | usage() 27 | { 28 | if [ "$1" != "" ]; then 29 | echo "${red}$1${reset}" 30 | fi 31 | 32 | echo "Jetson_stats tox local test. USE ONLY IN A TEST DESKTOP MACHINE!" 33 | echo "Usage:" 34 | echo "$0 [options]" 35 | echo "options," 36 | echo " -h|--help | This help" 37 | echo " --debug | Run image" 38 | echo " -py|--python [PYHTON] | Set a specific python version, example PYTHON=3.9" 39 | echo " --doc | Run and build ONLY the documentation" 40 | 41 | } 42 | 43 | main() 44 | { 45 | local DOCKER_BUILD=true 46 | local DOCUMENTATION_BUILD=true 47 | local PYTHON_LIST="2.7 3.6 3.8 3.9 3.10 3.11" 48 | local PYTHON_DEBUG=false 49 | local DOCUMENTATION=false 50 | 51 | # Decode all information from startup 52 | while [ -n "$1" ]; do 53 | case "$1" in 54 | -h|--help) 55 | # Load help 56 | usage 57 | exit 0 58 | ;; 59 | --doc) 60 | DOCKER_BUILD=false 61 | DOCUMENTATION=true 62 | ;; 63 | --debug) 64 | PYTHON_DEBUG=true 65 | DOCUMENTATION_BUILD=false 66 | DOCKER_BUILD=false 67 | ;; 68 | -py|--python) 69 | PYTHON_LIST=$2 70 | DOCUMENTATION_BUILD=false 71 | shift 1 72 | ;; 73 | *) 74 | usage "[ERROR] Unknown option: $1" 75 | exit 1 76 | ;; 77 | esac 78 | shift 1 79 | done 80 | 81 | 82 | if $DOCUMENTATION_BUILD ; then 83 | echo "- ${green}Build and compile jetson-stats documentation with sphinx${reset}" 84 | docker build -t rbonghi/jetson-stats:doc -f tests/Dockerfile.sphinx . || { echo "${red}docker build failure!${reset}"; exit 1; } 85 | fi 86 | 87 | if $DOCKER_BUILD ; then 88 | # Build all images 89 | for PYTHON_VERSION in $PYTHON_LIST; do 90 | echo "- ${green}Build and test image with python:${bold}$PYTHON_VERSION${reset}" 91 | docker build -t rbonghi/jetson-stats:tox-py$PYTHON_VERSION --build-arg "PYTHON_VERSION=$PYTHON_VERSION" -f tests/Dockerfile.tox . || { echo "${red}docker build failure!${reset}"; exit 1; } 92 | done 93 | fi 94 | 95 | if $PYTHON_DEBUG ; then 96 | if $DOCUMENTATION ; then 97 | echo "- ${yellow}Debug documentation image${reset}" 98 | docker run -v $(pwd):/jetson_stats -it --rm rbonghi/jetson-stats:doc 99 | else 100 | PYTHON_VERSION=$PYTHON_LIST 101 | echo "- ${yellow}Debug Image with python:${bold}$PYTHON_VERSION${reset}" 102 | docker run -v $(pwd):/jetson_stats -it --rm rbonghi/jetson-stats:tox-py$PYTHON_VERSION 103 | fi 104 | fi 105 | 106 | } 107 | 108 | main $@ 109 | exit 0 110 | 111 | #EOF 112 | -------------------------------------------------------------------------------- /jtop/gui/lib/colors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import curses 19 | 20 | 21 | def init_colorscale_pair(num, fg, bg): 22 | curses.init_pair(num, fg if curses.COLORS >= 256 else curses.COLOR_WHITE, bg if curses.COLORS >= 256 else curses.COLOR_BLACK) 23 | 24 | 25 | class NColors: 26 | 27 | RED = 1 28 | GREEN = 2 29 | YELLOW = 3 30 | BLUE = 4 31 | MAGENTA = 5 32 | CYAN = 6 33 | 34 | iRED = 7 35 | iGREEN = 8 36 | iYELLOW = 9 37 | iBLUE = 10 38 | iMAGENTA = 11 39 | iCYAN = 12 40 | 41 | def __init__(self, color_filter): 42 | # Define pairing colors 43 | curses.init_pair(NColors.RED, curses.COLOR_RED if not color_filter else curses.COLOR_BLUE, curses.COLOR_BLACK) 44 | curses.init_pair(NColors.GREEN, curses.COLOR_GREEN, curses.COLOR_BLACK) 45 | curses.init_pair(NColors.YELLOW, curses.COLOR_YELLOW, curses.COLOR_BLACK) 46 | curses.init_pair(NColors.BLUE, curses.COLOR_BLUE, curses.COLOR_BLACK) 47 | curses.init_pair(NColors.MAGENTA, curses.COLOR_MAGENTA, curses.COLOR_BLACK) 48 | curses.init_pair(NColors.CYAN, curses.COLOR_CYAN, curses.COLOR_BLACK) 49 | # background 50 | curses.init_pair(NColors.iRED, curses.COLOR_WHITE, curses.COLOR_RED if not color_filter else curses.COLOR_BLUE) 51 | curses.init_pair(NColors.iGREEN, curses.COLOR_WHITE, curses.COLOR_GREEN) 52 | curses.init_pair(NColors.iYELLOW, curses.COLOR_BLACK, curses.COLOR_YELLOW) 53 | curses.init_pair(NColors.iBLUE, curses.COLOR_WHITE, curses.COLOR_BLUE) 54 | curses.init_pair(NColors.iMAGENTA, curses.COLOR_WHITE, curses.COLOR_MAGENTA) 55 | curses.init_pair(NColors.iCYAN, curses.COLOR_WHITE, curses.COLOR_CYAN) 56 | 57 | @staticmethod 58 | def init_grey(num): 59 | init_colorscale_pair(num, 240, curses.COLOR_BLACK) 60 | 61 | @staticmethod 62 | def italic(): 63 | # Check if Italic is included 64 | return curses.A_ITALIC if hasattr(curses, 'A_ITALIC') else curses.A_NORMAL 65 | 66 | @staticmethod 67 | def red(): 68 | return curses.color_pair(NColors.RED) 69 | 70 | @staticmethod 71 | def green(): 72 | return curses.color_pair(NColors.GREEN) 73 | 74 | @staticmethod 75 | def yellow(): 76 | return curses.color_pair(NColors.YELLOW) 77 | 78 | @staticmethod 79 | def blue(): 80 | return curses.color_pair(NColors.BLUE) 81 | 82 | @staticmethod 83 | def magenta(): 84 | return curses.color_pair(NColors.MAGENTA) 85 | 86 | @staticmethod 87 | def cyan(): 88 | return curses.color_pair(NColors.CYAN) 89 | 90 | @staticmethod 91 | def ired(): 92 | return curses.color_pair(NColors.iRED) 93 | 94 | @staticmethod 95 | def igreen(): 96 | return curses.color_pair(NColors.iGREEN) 97 | 98 | @staticmethod 99 | def iyellow(): 100 | return curses.color_pair(NColors.iYELLOW) 101 | 102 | @staticmethod 103 | def iblue(): 104 | return curses.color_pair(NColors.iBLUE) 105 | 106 | @staticmethod 107 | def imagenta(): 108 | return curses.color_pair(NColors.iMAGENTA) 109 | 110 | @staticmethod 111 | def icyan(): 112 | return curses.color_pair(NColors.iCYAN) 113 | # EOF 114 | -------------------------------------------------------------------------------- /jtop/tests/test_01_outputs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | import os 20 | from datetime import timedelta 21 | from multiprocessing.pool import Pool 22 | from jtop import jtop, Memory, Fan, GPU 23 | from .conftest import emulate_all_devices 24 | NUM_PROCESSES = 20 25 | 26 | 27 | def check_attributes(jetson): 28 | # Jetson stats 29 | assert isinstance(jetson.stats, dict) 30 | # Jetson json 31 | assert isinstance(jetson.json(), str) 32 | assert isinstance(jetson.json(stats=True), str) 33 | # uptime 34 | assert isinstance(jetson.uptime, timedelta) 35 | # CPU 36 | assert isinstance(jetson.cpu, dict) 37 | # GPU 38 | assert isinstance(jetson.gpu, GPU) 39 | # Processes 40 | assert isinstance(jetson.processes, list) 41 | # Memory 42 | assert isinstance(jetson.memory, Memory) 43 | # Fan 44 | assert isinstance(jetson.fan, Fan) 45 | # Engines 46 | assert isinstance(jetson.engine, dict) 47 | # Status disk 48 | assert isinstance(jetson.disk, dict) 49 | # local interfaces 50 | assert isinstance(jetson.local_interfaces, dict) 51 | # Check power 52 | assert isinstance(jetson.power, dict) 53 | # Check temperature 54 | assert isinstance(jetson.temperature, dict) 55 | 56 | 57 | def test_hardware(setup_jtop_server): 58 | with jtop() as jetson: 59 | # Check contain hardware variables 60 | assert len(jetson.board['hardware']) > 0 61 | # Check contain Libraries information 62 | assert len(jetson.board['libraries']) > 0 63 | # Check contain platform variables 64 | assert len(jetson.board['platform']) > 0 65 | 66 | 67 | def test_open(setup_jtop_server): 68 | with jtop() as jetson: 69 | # Check status OK 70 | assert jetson.ok() 71 | # Check all attributes 72 | check_attributes(jetson) 73 | 74 | 75 | def jtop_callback(jetson): 76 | # Check all attributes 77 | check_attributes(jetson) 78 | # Close connection 79 | jetson.close() 80 | 81 | 82 | def test_open_callback(setup_jtop_server): 83 | # Initialize object 84 | jetson = jtop() 85 | # Attach callback 86 | jetson.attach(jtop_callback) 87 | # start jtop 88 | jetson.start() 89 | 90 | 91 | def jtop_worker(x): 92 | with jtop() as jetson: 93 | print("[{x}] jtop started on PID {pid}".format(x=x, pid=os.getpid())) 94 | # Check status OK 95 | for _ in range(10): 96 | assert jetson.ok() 97 | return True 98 | 99 | 100 | def test_multiple_run(setup_jtop_server): 101 | p = Pool(NUM_PROCESSES) 102 | pool_output = p.map(jtop_worker, range(NUM_PROCESSES)) 103 | # Check all return true 104 | assert all(pool_output) 105 | # Close pool 106 | p.close() 107 | p.join() 108 | 109 | 110 | test_hardware = pytest.mark.parametrize("setup_jtop_server", emulate_all_devices(), indirect=True)(test_hardware) 111 | test_open = pytest.mark.parametrize("setup_jtop_server", emulate_all_devices(), indirect=True)(test_open) 112 | test_open_callback = pytest.mark.parametrize("setup_jtop_server", emulate_all_devices(), indirect=True)(test_open_callback) 113 | test_multiple_run = pytest.mark.parametrize("setup_jtop_server", emulate_all_devices(), indirect=True)(test_multiple_run) 114 | # EOF 115 | -------------------------------------------------------------------------------- /jtop/core/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | import os 20 | import json 21 | import sys 22 | import copy 23 | # Create logger 24 | logger = logging.getLogger(__name__) 25 | JTOP_DATA_FOLDER = 'local/jtop' 26 | 27 | 28 | def make_config_service(data_folder=JTOP_DATA_FOLDER): 29 | path = get_config_service(data_folder) 30 | if not os.path.isdir(path): 31 | logger.info("Build service folder in {path}".format(path=path)) 32 | # Make folder directory 33 | os.makedirs(path) 34 | 35 | 36 | def get_config_service(data_folder=JTOP_DATA_FOLDER): 37 | path = sys.prefix 38 | if hasattr(sys, 'real_prefix'): 39 | path = sys.real_prefix 40 | if hasattr(sys, 'base_prefix'): 41 | path = sys.base_prefix 42 | # Return directory folder 43 | return "{path}/{data_folder}".format(path=path, data_folder=data_folder) 44 | 45 | 46 | class Config: 47 | 48 | def __init__(self): 49 | # Build folder if doesn't exists 50 | make_config_service() 51 | # Load configuration path 52 | self.config_file = self.path + '/config.json' 53 | # Load configuration 54 | self._config = self._load() 55 | self._last_config = copy.deepcopy(self._config) 56 | 57 | def set(self, instance, default=None): 58 | # Update configuration 59 | self._config[instance] = default 60 | # Store configuration 61 | if self._last_config != self._config: 62 | self._store() 63 | # Update last configuration 64 | self._last_config = copy.deepcopy(self._config) 65 | 66 | def get(self, instance, default=None): 67 | return self._config.get(instance, default) 68 | 69 | @property 70 | def path(self): 71 | return get_config_service() 72 | 73 | def _load(self): 74 | config = {} 75 | # Load configuration if exist 76 | if not os.path.isfile(self.config_file): 77 | return config 78 | logger.info("Load config from {path}".format(path=self.config_file)) 79 | with open(self.config_file) as json_file: 80 | config = json.load(json_file) 81 | return config 82 | 83 | def _store(self): 84 | logger.info("Store config to {path}".format(path=self.config_file)) 85 | # Write configuration 86 | with open(self.config_file, 'w') as outfile: 87 | json.dump(self._config, outfile, sort_keys=True, indent=4) 88 | 89 | def clear(self): 90 | self._config = {} 91 | self._last_config = {} 92 | if os.path.isfile(self.config_file): 93 | logger.info("Clear config in {path}".format(path=self.config_file)) 94 | # Remove configuration file 95 | os.remove(self.config_file) 96 | return True 97 | return False 98 | 99 | def items(self): 100 | return self._config.items() 101 | 102 | def keys(self): 103 | return self._config.keys() 104 | 105 | def values(self): 106 | return self._config.values() 107 | 108 | def __contains__(self, key): 109 | return key in self._config 110 | 111 | def __repr__(self): 112 | return repr(self._config) 113 | 114 | def __str__(self): 115 | return str(self._config) 116 | # EOF 117 | -------------------------------------------------------------------------------- /jtop/core/thor_cuda_mem.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # jtop/core/thor_cuda_mem.py 19 | import ctypes 20 | from ctypes import byref, c_int, c_void_p, c_uint, c_size_t 21 | from contextlib import contextmanager 22 | 23 | # Lazy-loaded CUDA driver 24 | _libcuda = None 25 | 26 | 27 | def _cuda(): 28 | global _libcuda 29 | if _libcuda is None: 30 | _libcuda = ctypes.CDLL("libcuda.so") 31 | 32 | # Prototypes (CUDA Driver API) 33 | _libcuda.cuInit.argtypes = [c_uint] 34 | _libcuda.cuInit.restype = c_int 35 | 36 | _libcuda.cuDeviceGet.argtypes = [ctypes.POINTER(c_int), c_int] 37 | _libcuda.cuDeviceGet.restype = c_int 38 | 39 | _libcuda.cuDevicePrimaryCtxRetain.argtypes = [ctypes.POINTER(c_void_p), c_int] 40 | _libcuda.cuDevicePrimaryCtxRetain.restype = c_int 41 | 42 | _libcuda.cuDevicePrimaryCtxRelease.argtypes = [c_int] 43 | _libcuda.cuDevicePrimaryCtxRelease.restype = c_int 44 | 45 | _libcuda.cuCtxPushCurrent_v2.argtypes = [c_void_p] 46 | _libcuda.cuCtxPushCurrent_v2.restype = c_int 47 | 48 | _libcuda.cuCtxPopCurrent_v2.argtypes = [ctypes.POINTER(c_void_p)] 49 | _libcuda.cuCtxPopCurrent_v2.restype = c_int 50 | 51 | _libcuda.cuMemGetInfo_v2.argtypes = [ctypes.POINTER(c_size_t), ctypes.POINTER(c_size_t)] 52 | _libcuda.cuMemGetInfo_v2.restype = c_int 53 | 54 | return _libcuda 55 | 56 | 57 | @contextmanager 58 | def _pushed_primary_ctx(device_index: int = 0): 59 | """Retains device's primary context and pushes it current; always pops/releases.""" 60 | lib = _cuda() 61 | dev = c_int(device_index) 62 | ctx = c_void_p(0) 63 | 64 | # Init + device 65 | rc = lib.cuInit(0) # CU_SUCCESS == 0 66 | if rc != 0: 67 | raise RuntimeError(f"cuInit rc={rc}") 68 | 69 | rc = lib.cuDeviceGet(byref(dev), device_index) 70 | if rc != 0: 71 | raise RuntimeError(f"cuDeviceGet rc={rc}") 72 | 73 | # Retain + push 74 | rc = lib.cuDevicePrimaryCtxRetain(byref(ctx), dev.value) 75 | if rc != 0: 76 | raise RuntimeError(f"cuDevicePrimaryCtxRetain rc={rc}") 77 | 78 | try: 79 | rc = lib.cuCtxPushCurrent_v2(ctx) 80 | if rc != 0: 81 | raise RuntimeError(f"cuCtxPushCurrent rc={rc}") 82 | try: 83 | yield 84 | finally: 85 | popped = c_void_p(0) 86 | lib.cuCtxPopCurrent_v2(byref(popped)) 87 | finally: 88 | lib.cuDevicePrimaryCtxRelease(dev.value) 89 | 90 | 91 | def cuda_gpu_mem_bytes(device_index: int = 0, verbose: bool = False): 92 | """ 93 | Returns (used_bytes, total_bytes) via CUDA Driver API. 94 | Uses the device's PRIMARY context (safe to call while other apps run). 95 | """ 96 | try: 97 | lib = _cuda() 98 | except OSError as e: 99 | if verbose: 100 | print(f"[cuda] libcuda.so not found: {e}") 101 | return None 102 | 103 | try: 104 | with _pushed_primary_ctx(device_index): 105 | free_b = c_size_t(0) 106 | total_b = c_size_t(0) 107 | rc = lib.cuMemGetInfo_v2(byref(free_b), byref(total_b)) 108 | if rc != 0: 109 | if verbose: 110 | print(f"[cuda] cuMemGetInfo rc={rc}") 111 | return None 112 | used = int(total_b.value) - int(free_b.value) 113 | return used, int(total_b.value) 114 | except Exception as e: 115 | if verbose: 116 | print(f"[cuda] exception: {e}") 117 | return None 118 | 119 | # EOF 120 | -------------------------------------------------------------------------------- /jtop/tests/marco_functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import warnings 19 | # Max count to wait 20 | MAX_COUNT = 50 21 | # TEST NVP MODELS: 22 | # - [0] MAXTEST (DEFAULT) 23 | # - [1] TEST 24 | # - [2] MINTEST 25 | # - [3] MIN_MAX_TEST 26 | 27 | 28 | def set_fan_profile(jetson, new_profile): 29 | # Set new speed 30 | if jetson.ok(): 31 | jetson.fan.profile = new_profile 32 | # Wait jetson_clocks on 33 | counter = 0 34 | while jetson.ok(): 35 | if jetson.fan.profile == new_profile or counter == MAX_COUNT: 36 | break 37 | counter += 1 38 | if counter == MAX_COUNT: 39 | warnings.warn("Max time counter {counter}".format(counter=MAX_COUNT), UserWarning) 40 | # Check if is true 41 | assert jetson.fan.profile == new_profile 42 | 43 | 44 | def set_fan_speed(jetson, new_speed): 45 | # Set new speed 46 | if jetson.ok(): 47 | jetson.fan.speed = new_speed 48 | # Wait jetson_clocks on 49 | counter = 0 50 | while jetson.ok(): 51 | if jetson.fan.speed == new_speed or counter == MAX_COUNT: 52 | break 53 | counter += 1 54 | if counter == MAX_COUNT: 55 | warnings.warn("Max time counter {counter}".format(counter=MAX_COUNT), UserWarning) 56 | # Check if is true 57 | assert jetson.fan.speed == new_speed 58 | 59 | 60 | def set_jetson_clocks(jetson, status): 61 | if jetson.jetson_clocks is None: 62 | warnings.warn("jetson_clocks does not exists, please check file", UserWarning) 63 | return 64 | # Check if status is different 65 | if jetson.jetson_clocks != status: 66 | # Set true jetson_clocks 67 | if jetson.ok(): 68 | jetson.jetson_clocks = status 69 | # Wait jetson_clocks on 70 | counter = 0 71 | while jetson.ok(): 72 | if jetson.jetson_clocks == status or counter == MAX_COUNT: 73 | break 74 | counter += 1 75 | if counter == MAX_COUNT: 76 | warnings.warn("Max time counter {counter}".format(counter=MAX_COUNT), UserWarning) 77 | # Check if is true 78 | assert jetson.jetson_clocks == status 79 | 80 | 81 | def set_jetson_clocks_boot(jetson, status): 82 | if jetson.jetson_clocks is None: 83 | warnings.warn("jetson_clocks does not exists, please check file", UserWarning) 84 | return 85 | # Set true jetson_clocks 86 | if jetson.ok(): 87 | jetson.jetson_clocks.boot = status 88 | # Wait jetson_clocks on 89 | counter = 0 90 | while jetson.ok(): 91 | if jetson.jetson_clocks.boot == status or counter == MAX_COUNT: 92 | break 93 | counter += 1 94 | if counter == MAX_COUNT: 95 | warnings.warn("Max time counter {counter}".format(counter=MAX_COUNT), UserWarning) 96 | # Check if is true 97 | assert jetson.jetson_clocks.boot == status 98 | 99 | 100 | def set_nvp_mode(jetson, mode): 101 | # Check if status is different 102 | print("NVP-MODE: BEFORE assert str(jetson.nvpmodel)={nvp} - mode={mode}".format(nvp=str(jetson.nvpmodel), mode=mode)) 103 | if str(jetson.nvpmodel) != mode: 104 | # Check status nvpmodel 105 | if jetson.ok(): 106 | jetson.nvpmodel = mode 107 | # Wait change nvpmodel 108 | counter = 0 109 | while jetson.ok(): 110 | if str(jetson.nvpmodel) == mode or counter == MAX_COUNT: 111 | break 112 | counter += 1 113 | if counter == MAX_COUNT: 114 | warnings.warn("Max time counter {counter}".format(counter=MAX_COUNT), UserWarning) 115 | # Check if is same model 116 | print("NVP-MODE: assert jetson.nvpmodel={nvp} - mode={mode}".format(nvp=str(jetson.nvpmodel), mode=mode)) 117 | assert str(jetson.nvpmodel) == mode 118 | # Check name variable 119 | assert jetson.nvpmodel.name == mode 120 | # EOF 121 | -------------------------------------------------------------------------------- /jtop/tests/test_04_nvpmodel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | import warnings 20 | from jtop import jtop, NVPModel 21 | from .marco_functions import set_jetson_clocks, set_nvp_mode 22 | from .conftest import emulate_all_devices 23 | # Max count to wait 24 | MAX_COUNT = 50 25 | 26 | 27 | def test_nvpmodel_output(setup_jtop_server): 28 | device, jtop_server = setup_jtop_server 29 | with jtop() as jetson: 30 | print("Running test with parameter:", device) 31 | if jetson.ok(): 32 | # Read nvpmodel status 33 | nvpmodel = jetson.nvpmodel 34 | # Status nvpmodel 35 | print("nvpmodel output: {nvpmodel}".format(nvpmodel=nvpmodel)) 36 | # Check depend of parameter 37 | if device in ['simple', 'tk', 'tx']: 38 | assert nvpmodel is None 39 | else: 40 | assert isinstance(nvpmodel, NVPModel) 41 | 42 | 43 | def test_nvpmodel(setup_jtop_server): 44 | with jtop() as jetson: 45 | # Check status nvpmodel 46 | set_nvp_mode(jetson, "MIN_MAX_TEST") 47 | 48 | 49 | def test_nvpmodel_fail(setup_jtop_server): 50 | with jtop() as jetson: 51 | # Check status nvpmodel 52 | if jetson.ok(): 53 | jetson.nvpmodel = "MINTEST" 54 | # Check if is same model 55 | assert str(jetson.nvpmodel) != "MINTEST" 56 | # Check name variable 57 | assert jetson.nvpmodel.name != "MINTEST" 58 | 59 | 60 | def test_nvpmodel_increment_decrement(setup_jtop_server): 61 | with jtop() as jetson: 62 | # Save nvp ID 63 | nvp_id = jetson.nvpmodel.id 64 | # Set new NVP mode 65 | jetson.nvpmodel += 1 66 | # Wait change nvpmodel 67 | counter = 0 68 | while jetson.ok(): 69 | if jetson.nvpmodel.id == nvp_id + 1 or counter == MAX_COUNT: 70 | break 71 | counter += 1 72 | if counter == MAX_COUNT: 73 | warnings.warn("Max time counter {counter}".format(counter=MAX_COUNT), UserWarning) 74 | # Check if is same model 75 | assert jetson.nvpmodel.id == nvp_id + 1 76 | # Save nvp ID 77 | nvp_id = jetson.nvpmodel.id 78 | # Set new NVP mode 79 | jetson.nvpmodel = jetson.nvpmodel - 1 80 | # Wait change nvpmodel 81 | counter = 0 82 | while jetson.ok(): 83 | if jetson.nvpmodel.id == nvp_id - 1 or counter == MAX_COUNT: 84 | break 85 | counter += 1 86 | if counter == MAX_COUNT: 87 | warnings.warn("Max time counter {counter}".format(counter=MAX_COUNT), UserWarning) 88 | # Check if is same model 89 | assert jetson.nvpmodel.id == nvp_id - 1 90 | 91 | 92 | def test_nvpmodel_jetson_clocks(setup_jtop_server): 93 | with jtop() as jetson: 94 | # Enable jetson_clocks 95 | set_jetson_clocks(jetson, True) 96 | # Check status nvpmodel 97 | set_nvp_mode(jetson, "TEST") 98 | # Disable jetson_clocks 99 | set_jetson_clocks(jetson, False) 100 | # Check status nvpmodel 101 | set_nvp_mode(jetson, "MIN_MAX_TEST") 102 | 103 | 104 | test_nvpmodel_output = pytest.mark.parametrize( 105 | "setup_jtop_server", emulate_all_devices(), indirect=True)(test_nvpmodel_output) 106 | test_nvpmodel = pytest.mark.parametrize( 107 | "setup_jtop_server", ['nano', 'xavier', 'orin'], indirect=True)(test_nvpmodel) 108 | test_nvpmodel_fail = pytest.mark.parametrize( 109 | "setup_jtop_server", ['nano', 'xavier', 'orin'], indirect=True)(test_nvpmodel_fail) 110 | test_nvpmodel_increment_decrement = pytest.mark.parametrize( 111 | "setup_jtop_server", ['nano', 'xavier', 'orin'], indirect=True)(test_nvpmodel_increment_decrement) 112 | test_nvpmodel_jetson_clocks = pytest.mark.parametrize( 113 | "setup_jtop_server", ['nano', 'xavier', 'orin'], indirect=True)(test_nvpmodel_jetson_clocks) 114 | # EOF 115 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from __future__ import annotations 4 | import jtop 5 | 6 | import os 7 | import sys 8 | from datetime import date 9 | 10 | # -- Path setup -------------------------------------------------------------- 11 | 12 | # If extensions (or modules to document with autodoc) are in another directory, 13 | # add these directories to sys.path here. If the directory is relative to the 14 | # documentation root, use os.path.abspath to make it absolute, like shown here. 15 | root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 16 | sys.path.insert(0, root_path) 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | 21 | project = 'jetson-stats' 22 | author = 'Raffaello Bonghi' 23 | copyright = f"{date.today().year}, {author}" 24 | 25 | 26 | # The short X.Y version. 27 | version = jtop.__version__ 28 | # The full version, including alpha/beta/rc tags. 29 | release = version 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | # https://github.com/wpilibsuite/sphinxext-opengraph 37 | extensions = [ 38 | "sphinx.ext.autodoc", 39 | "sphinx_copybutton", 40 | "sphinx.ext.doctest", 41 | "sphinx.ext.intersphinx", 42 | "sphinxext.opengraph", 43 | ] 44 | 45 | source_suffix = ['.rst'] 46 | 47 | # The master toctree document. 48 | master_doc = "index" 49 | 50 | # Add any paths that contain templates here, relative to this directory. 51 | templates_path = ['_templates'] 52 | 53 | # List of patterns, relative to source directory, that match files and 54 | # directories to ignore when looking for source files. 55 | # This pattern also affects html_static_path and html_extra_path. 56 | exclude_patterns = ["_build"] 57 | 58 | # The name of the Pygments (syntax highlighting) style to use. 59 | pygments_style = "friendly" 60 | 61 | intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} 62 | 63 | # Show typehints as content of the function or method 64 | autodoc_typehints = "description" 65 | 66 | copybutton_selector = "div:not(.no-copybutton) > div.highlight > pre" 67 | 68 | # opengraph configuration 69 | ogp_site_url = "https://rnext.it/jetson_stats/" 70 | ogp_image = "https://rnext.it/jetson_stats/_images/jtop.png" 71 | ogp_enable_meta_description = True 72 | 73 | # -- Options for HTML output ------------------------------------------------- 74 | 75 | # The theme to use for HTML and HTML Help pages. See the documentation for 76 | # a list of builtin themes. 77 | # 78 | # Reference: https://pradyunsg.me/furo/ 79 | html_theme = "furo" 80 | html_favicon = "images/favicon.png" 81 | 82 | html_title = f"{project} {version}" 83 | 84 | # Add any paths that contain custom static files (such as style sheets) here, 85 | # relative to this directory. They are copied after the builtin static files, 86 | # so a file named "default.css" will overwrite the builtin "default.css". 87 | html_static_path = [] 88 | 89 | html_theme_options = { 90 | "announcement": """ 91 | 93 | 💖 Support jetson-stats on GitHub Sponsors 94 | 95 | """, 96 | "footer_icons": [ 97 | { 98 | "name": "GitHub", 99 | "url": "https://github.com/rbonghi/jetson_stats", 100 | "html": """ 101 | 102 | 103 | 104 | """, 105 | "class": "", 106 | }, 107 | ], 108 | "source_repository": "https://github.com/rbonghi/jetson_stats", 109 | "source_branch": "master", 110 | "source_directory": "docs/", 111 | } 112 | 113 | html_sidebars = { 114 | "**": [ 115 | "sidebar/brand.html", 116 | "sidebar/search.html", 117 | "sidebar/scroll-start.html", 118 | "sidebar/navigation.html", 119 | "sidebar/ethical-ads.html", 120 | "sidebar/scroll-end.html", 121 | "sidebar/variant-selector.html", 122 | "sidebar/adsense.html", 123 | ] 124 | } 125 | # EOF 126 | -------------------------------------------------------------------------------- /jtop/core/tegrastats.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # Logging 19 | import logging 20 | import sys 21 | # Launch command 22 | import subprocess as sp 23 | # Threading 24 | from threading import Thread, Event 25 | # Tegrastats parser 26 | from .tegra_parse import DATE, VALS, MTS, RAM, SWAP, IRAM, CPUS, TEMPS, WATTS 27 | from .common import locate_commands 28 | # Create logger for tegrastats 29 | logger = logging.getLogger(__name__) 30 | 31 | 32 | class Tegrastats: 33 | """ 34 | - Subprocess read: 35 | https://stackoverflow.com/questions/375427/non-blocking-read-on-a-subprocess-pipe-in-python/4896288#4896288 36 | - Property 37 | https://www.programiz.com/python-programming/property 38 | """ 39 | 40 | def __init__(self, callback, tegrastats_path): 41 | self._running = Event() 42 | # Error message from thread 43 | self._error = None 44 | # Start process tegrastats 45 | self.path = locate_commands("tegrastats", tegrastats_path) 46 | # Define Tegrastats process 47 | self._thread = None 48 | # Initialize callback 49 | self.callback = callback 50 | 51 | def _decode(self, text): 52 | # Remove date from tegrastats string 53 | text = DATE(text) 54 | # Find and parse all single values 55 | stats = VALS(text) 56 | # Parse if exist MTS 57 | mts = MTS(text) 58 | if mts: 59 | stats['MTS'] = mts 60 | # Parse RAM 61 | stats['RAM'] = RAM(text) 62 | # If exists parse SWAP 63 | swap = SWAP(text) 64 | if swap: 65 | stats['SWAP'] = swap 66 | # If exists parse IRAM 67 | iram = IRAM(text) 68 | if iram: 69 | stats['IRAM'] = iram 70 | # Parse CPU status 71 | stats['CPU'] = CPUS(text) 72 | # Parse temperatures 73 | stats['TEMP'] = TEMPS(text) 74 | # Parse Watts 75 | stats['WATT'] = WATTS(text) 76 | return stats 77 | 78 | def _read_tegrastats(self, interval, running): 79 | pts = sp.Popen([self.path, '--interval', str(interval)], stdout=sp.PIPE) 80 | try: 81 | # Reading loop 82 | while running.is_set(): 83 | if pts.poll() is not None: 84 | continue 85 | out = pts.stdout 86 | if out is not None: 87 | # Read line process output 88 | line = out.readline() 89 | # Decode line in UTF-8 90 | tegrastats_data = line.decode("utf-8") 91 | # Decode and store 92 | stats = self._decode(tegrastats_data) 93 | # Launch callback 94 | self.callback(stats) 95 | except AttributeError: 96 | pass 97 | except IOError: 98 | pass 99 | except Exception: 100 | # Write error message 101 | self._error = sys.exc_info() 102 | finally: 103 | # Kill process 104 | try: 105 | pts.kill() 106 | except OSError: 107 | pass 108 | 109 | def open(self, interval=0.5): 110 | if self._thread is not None: 111 | return False 112 | # Set timeout 113 | interval = int(interval * 1000) 114 | # Check if thread or process exist 115 | self._running.set() 116 | # Start thread Service client 117 | self._thread = Thread(target=self._read_tegrastats, args=(interval, self._running, )) 118 | self._thread.start() 119 | return True 120 | 121 | def close(self, timeout=None): 122 | # Catch exception if exist 123 | if self._error: 124 | # Extract exception and raise 125 | ex_type, ex_value, tb_str = self._error 126 | ex_value.__traceback__ = tb_str 127 | raise ex_value 128 | # Check if thread and process are already empty 129 | self._running.clear() 130 | if self._thread is not None: 131 | self._thread.join(timeout) 132 | self._thread = None 133 | return True 134 | # EOF 135 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS = '-W' 7 | SPHINXBUILD = sphinx-build 8 | PAPER = 9 | BUILDDIR = _build 10 | 11 | # Internal variables 12 | PAPEROPT_a4 = -D latex_paper_size=a4 13 | PAPEROPT_letter = -D latex_paper_size=letter 14 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 15 | 16 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 17 | 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " devhelp to make HTML files and a Devhelp project" 28 | @echo " epub to make an epub" 29 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 30 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 31 | @echo " text to make text files" 32 | @echo " man to make manual pages" 33 | @echo " changes to make an overview of all changed/added/deprecated items" 34 | @echo " linkcheck to check all external links for integrity" 35 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 36 | 37 | clean: 38 | -rm -rf $(BUILDDIR)/* 39 | 40 | html: 41 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 42 | @echo 43 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 44 | 45 | dirhtml: 46 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 47 | @echo 48 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 49 | 50 | singlehtml: 51 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 52 | @echo 53 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 54 | 55 | pickle: 56 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 57 | @echo 58 | @echo "Build finished; now you can process the pickle files." 59 | 60 | json: 61 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 62 | @echo 63 | @echo "Build finished; now you can process the JSON files." 64 | 65 | htmlhelp: 66 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 67 | @echo 68 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 69 | ".hhp project file in $(BUILDDIR)/htmlhelp." 70 | 71 | qthelp: 72 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 73 | @echo 74 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 75 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 76 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/urllib3.qhcp" 77 | @echo "To view the help file:" 78 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/urllib3.qhc" 79 | 80 | devhelp: 81 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 82 | @echo 83 | @echo "Build finished." 84 | @echo "To view the help file:" 85 | @echo "# mkdir -p $$HOME/.local/share/devhelp/urllib3" 86 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/urllib3" 87 | @echo "# devhelp" 88 | 89 | epub: 90 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 91 | @echo 92 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 93 | 94 | latex: 95 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 96 | @echo 97 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 98 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 99 | "(use \`make latexpdf' here to do that automatically)." 100 | 101 | latexpdf: 102 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 103 | @echo "Running LaTeX files through pdflatex..." 104 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 105 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 106 | 107 | text: 108 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 109 | @echo 110 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 111 | 112 | man: 113 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 114 | @echo 115 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 116 | 117 | changes: 118 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 119 | @echo 120 | @echo "The overview file is in $(BUILDDIR)/changes." 121 | 122 | linkcheck: 123 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 124 | @echo 125 | @echo "Link check complete; look for any errors in the above output " \ 126 | "or in $(BUILDDIR)/linkcheck/output.txt." 127 | 128 | doctest: 129 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 130 | @echo "Testing of doctests in the sources finished, look at the " \ 131 | "results in $(BUILDDIR)/doctest/output.txt." 132 | 133 | # https://github.com/executablebooks/sphinx-autobuild 134 | livehtml: 135 | sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\urllib3.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\urllib3.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | ✨ Contributing 2 | ================ 3 | 4 | jetson-stats is a community-maintained project and we happily accept contributions. 5 | 6 | If you want to add a new **Jetpack** release follow these quick rules or if you want make a new feature or fix a bug you are on the right page. 7 | 8 | Add a new Jetpack 9 | ----------------- 10 | 11 | If you want to add a new Jetpack to fix the warning: 12 | 13 | .. code-block:: console 14 | :class: no-copybutton 15 | 16 | user@board:~$ jtop 17 | [WARN] jetson-stats not supported for [L4T 35.2.1] 18 | Please, try: sudo pip3 install -U jetson-stats or 19 | open a Github issue (press CTRL + Click) 20 | 21 | 1. Open file **jtop/core/jetson_variables.py** around line *49* there is a variable called **NVIDIA_JETPACK** add the new jetpack following the rule below: 22 | 23 | .. code-block:: python 24 | :class: no-copybutton 25 | 26 | "L4T version": "Jetpack" 27 | 28 | 2. Increase with a minor release jtop variable **__version__** in **jtop/__init__.py** 29 | 3. Create a pull request and append ``&template=jetpack-missing.md`` to the URL before submitting in order to include our release checklist in the pull request description. 30 | 4. Open a pull request with message "**Jetpack Release **" where **** is the same release in **jtop/__init__.py** 31 | 5. Follow the checklist! 32 | 33 | Add new feature or fix a bug 34 | ---------------------------- 35 | 36 | If you wish to add a new feature or fix a bug: 37 | 38 | #. `Check for open issues `_ or open 39 | a fresh issue to start a discussion around a feature idea or a bug. There is 40 | a *Contributor Friendly* tag for issues that should be ideal for people who 41 | are not very familiar with the codebase yet. 42 | #. Fork the `jetson-stats repository on Github `_ 43 | to start making your changes. 44 | #. Write a test which shows that the bug was fixed or that the feature works 45 | as expected. 46 | #. Send a pull request and bug the maintainer until it gets merged and published. 47 | 48 | Setting up your developing environment 49 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 | 51 | Clone and build in developer mode jetson-stats 52 | 53 | .. code-block:: console 54 | 55 | git clone https://github.com/rbonghi/jetson_stats.git 56 | cd jetson_stats 57 | sudo pip3 install -v -e . 58 | 59 | 60 | Manually stop and disable jtop service 61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | 63 | If you want to manually control the jtop service you need to disable the service and manually start one in a terminal, 64 | following the commands below 65 | 66 | .. code-block:: console 67 | 68 | sudo systemctl stop jtop.service 69 | sudo systemctl disable jtop.service 70 | 71 | Now you can work running in your terminal the jtop service 72 | 73 | .. code-block:: console 74 | 75 | sudo JTOP_SERVICE=True jtop --force 76 | 77 | Restore jtop service 78 | ^^^^^^^^^^^^^^^^^^^^ 79 | 80 | .. code-block:: console 81 | 82 | sudo systemctl enable jtop.service 83 | sudo systemctl start jtop.service 84 | 85 | Test this package 86 | ----------------- 87 | 88 | Before commit you can test jetson-stats on multiple python version and check if the documentation is built 89 | 90 | This script works with docker, and you can quickly run it. 91 | 92 | .. code-block:: console 93 | 94 | bash tests/local_test.sh 95 | 96 | When you run this script will do: 97 | 98 | 1. Build and compile all python images (2.7,3.6,3.7,3.8,3.9.3.10,3.11) 99 | 2. Build documentation image (Sphinx) 100 | 101 | There are different options: 102 | 103 | .. code-block:: console 104 | :class: no-copybutton 105 | 106 | user@workstation:~/jetson_stats$ bash tests/local_test.sh --help 107 | Jetson_stats tox local test. USE ONLY IN A TEST DESKTOP MACHINE! 108 | Usage: 109 | tests/local_test.sh [options] 110 | options, 111 | -h|--help | This help 112 | --debug | Run image 113 | -py|--python [PYHTON] | Set a specific python version, example PYTHON=3.9 114 | --doc | Run and build ONLY the documentation 115 | 116 | Live docker with tox 117 | ^^^^^^^^^^^^^^^^^^^^ 118 | 119 | Run tox or work live from the terminal 120 | 121 | .. code-block:: console 122 | 123 | bash tests/local_test.sh --debug -py 3.9 124 | 125 | Test documentation 126 | ^^^^^^^^^^^^^^^^^^ 127 | 128 | If you want to run **only** the documentation: 129 | 130 | .. code-block:: console 131 | 132 | bash tests/local_test.sh --doc 133 | 134 | Test GUI 135 | ^^^^^^^^ 136 | 137 | If you want to test or develop the GUI library 138 | 139 | You can run this command from your terminal `python3 -m jtop.tests_gui.x` where **x** is the name of the file, example 140 | 141 | .. code-block:: console 142 | 143 | python3 -m jtop.tests_gui.gui_page 144 | 145 | Release 146 | ------- 147 | 148 | - Announce intent to release on `Discord `_, see if anyone wants to include last minute changes. 149 | - Update ``jtop/__init__.py`` with the proper version number 150 | - Commit the changes to a ``release-X.Y.Z`` branch. 151 | - Create a pull request with name ``Release/X.Y.Z`` 152 | - Release a new `tag `_ will automatically generate a new version 153 | 154 | .. code-block:: console 155 | :class: no-copybutton 156 | 157 | git tag -a -m -------------------------------------------------------------------------------- /jtop/core/engine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | # Logging 20 | import logging 21 | # from .exceptions import JtopException 22 | # Create logger 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | def read_engine(path): 27 | # Read status online 28 | engine = {} 29 | # Check if access to this file 30 | if os.access(path + "/clk_enable_count", os.R_OK): 31 | with open(path + "/clk_enable_count", 'r') as f: 32 | # Write online engine 33 | engine['online'] = int(f.read()) == 1 34 | # Check if access to this file 35 | if os.access(path + "/clk_rate", os.R_OK): 36 | with open(path + "/clk_rate", 'r') as f: 37 | # Write current engine 38 | engine['cur'] = int(f.read()) // 1000 39 | # Decode clock rate 40 | max_value = False 41 | if os.access(path + "/clk_max_rate", os.R_OK): 42 | with open(path + "/clk_max_rate", 'r') as f: 43 | # Write status engine 44 | value = int(f.read()) 45 | # 18446744073709551615 = FFFF FFFF FFFF FFFF = 2 ^ 16 46 | if value != 18446744073709551615: 47 | engine['max'] = value // 1000 48 | max_value = True 49 | if os.access(path + "/clk_min_rate", os.R_OK) and max_value: 50 | with open(path + "/clk_min_rate", 'r') as f: 51 | # Write status engine 52 | engine['min'] = int(f.read()) // 1000 53 | return engine 54 | 55 | 56 | class EngineService(object): 57 | 58 | ENGINES = ['ape', 'dla', 'pva', 'vic', 'nvjpg', 'nvenc', 'nvdec', 'se.', 'cvnas', 'msenc', 'ofa'] 59 | 60 | def __init__(self): 61 | # Sort list before start 62 | EngineService.ENGINES.sort() 63 | self.engines_path = {} 64 | # List all engines available 65 | engine_path = "/sys/kernel/debug/clk" 66 | if os.getenv('JTOP_TESTING', False): 67 | engine_path = "/fake_sys/kernel/debug/clk" 68 | logger.warning("Running in JTOP_TESTING folder={root_dir}".format(root_dir=engine_path)) 69 | list_all_engines = [x[0] for x in os.walk(engine_path)] 70 | # Search all available engines 71 | for name in EngineService.ENGINES: 72 | if name.endswith('.'): 73 | name = name[:-1] 74 | local_path = "{path}/{name}".format(path=engine_path, name=name) 75 | if os.path.isdir(local_path): 76 | self.engines_path[name.upper()] = [local_path] 77 | else: 78 | # https://stackoverflow.com/questions/4843158/how-to-check-if-a-string-is-a-substring-of-items-in-a-list-of-strings 79 | local_path = "{path}/{name}".format(path=engine_path, name=name) 80 | # In this search are removed all engines that have a '.' on their name 81 | # like ape.buffer or nvdec.buf 82 | matching = [s for s in list_all_engines if local_path in s and '.' not in s] 83 | # Add in list all engines 84 | if matching: 85 | # Check if name end with a number, if true collect by number 86 | # dla0 dla1 ... 87 | if os.path.basename(matching[0]).split('_')[0] == "{name}0".format(name=name): 88 | logger.info("Special Engine group found: [{name}X]".format(name=name)) 89 | for num in range(10): 90 | name_engine = "{name}{counter}".format(name=name, counter=num) 91 | new_match = [match for match in matching if name_engine in match] 92 | if new_match: 93 | self.engines_path[name_engine.upper()] = sorted(new_match) 94 | else: 95 | break 96 | else: 97 | self.engines_path[name.upper()] = sorted(matching) 98 | # Print all engines found 99 | if self.engines_path: 100 | engines_string = ' '.join(name for name in self.engines_path) 101 | logger.info("Engines found: [{engines}]".format(engines=engines_string)) 102 | else: 103 | logger.warn("Not engines found!") 104 | 105 | def get_status(self): 106 | status = {} 107 | # Read status from all engines 108 | for engine in self.engines_path: 109 | status[engine] = {} 110 | for local_path in self.engines_path[engine]: 111 | name_engine = os.path.basename(local_path).upper() 112 | logger.debug("Status [{engine}] in {path}".format(engine=name_engine, path=local_path)) 113 | status[engine][name_engine] = read_engine(local_path) 114 | return status 115 | # EOF 116 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | @rbonghi. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: UTF-8 -*- 3 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 4 | # Copyright (c) 2019-2026 Raffaello Bonghi. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | """ 20 | Minimal setup.py for backward compatibility and custom install commands. 21 | Main configuration is in pyproject.toml (PEP 517/518/621 compliant). 22 | """ 23 | 24 | from setuptools import setup 25 | from setuptools.command.develop import develop 26 | from setuptools.command.install import install 27 | import os 28 | import sys 29 | import logging 30 | 31 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 32 | log = logging.getLogger() 33 | 34 | 35 | def is_virtualenv(): 36 | # Check if in virtual environment 37 | has_real_prefix = hasattr(sys, 'real_prefix') 38 | has_base_prefix = ( 39 | hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix 40 | ) 41 | return bool(has_real_prefix or has_base_prefix) 42 | 43 | 44 | def is_docker(): 45 | # Check if running in Docker container 46 | if os.path.exists('/.dockerenv'): 47 | return True 48 | # Check cgroup 49 | path = '/proc/self/cgroup' 50 | if os.path.isfile(path): 51 | with open(path) as f: 52 | for line in f: 53 | if 'docker' in line or 'buildkit' in line: 54 | return True 55 | return False 56 | 57 | 58 | def is_superuser(): 59 | return os.getuid() == 0 60 | 61 | 62 | def pypi_installer(installer, obj, copy): 63 | """Main installation function for jtop services.""" 64 | # Import here to avoid import errors during build 65 | try: 66 | from jtop.service import status_service, remove_service_pipe, uninstall_service, set_service_permission, install_service 67 | from jtop.core.jetson_variables import uninstall_variables, install_variables 68 | from jtop.terminal_colors import bcolors 69 | except ImportError: 70 | # If imports fail, we're likely in build phase, so skip custom installation 71 | installer.run(obj) 72 | return 73 | 74 | log.info("Install status:") 75 | log.info(f" - [{'X' if is_superuser() else ' '}] super_user") 76 | log.info(f" - [{'X' if is_virtualenv() else ' '}] virtualenv") 77 | log.info(f" - [{'X' if is_docker() else ' '}] docker") 78 | 79 | # Run the uninstaller before to copy all scripts 80 | if not is_virtualenv() and not is_docker(): 81 | if is_superuser(): 82 | # remove service jtop.service 83 | uninstall_service() 84 | # Remove service path 85 | remove_service_pipe() 86 | # Uninstall variables 87 | uninstall_variables() 88 | else: 89 | log.info("----------------------------------------") 90 | log.info("Install on your host using superuser permission, like:") 91 | log.info(bcolors.bold("sudo pip3 install -U jetson-stats")) 92 | sys.exit(1) 93 | elif is_docker(): 94 | log.info("Skip uninstall in docker") 95 | else: 96 | if is_superuser(): 97 | log.info("Skip uninstall on virtual environment") 98 | elif not status_service(): 99 | log.info("----------------------------------------") 100 | log.info("Please, before install in your virtual environment, install jetson-stats on your host with superuser permission, like:") 101 | log.info(bcolors.bold("sudo pip3 install -U jetson-stats")) 102 | sys.exit(1) 103 | 104 | # Run the default installation script 105 | installer.run(obj) 106 | 107 | # Run the restart all services before to close the installer 108 | if not is_virtualenv() and not is_docker() and is_superuser(): 109 | folder, _ = os.path.split(os.path.realpath(__file__)) # This folder 110 | # Install variables 111 | install_variables(folder, copy=copy) 112 | # Set service permissions 113 | set_service_permission() 114 | # Install service (linking only for develop) 115 | install_service(folder, copy=copy) 116 | else: 117 | log.info("Skip install service") 118 | 119 | 120 | class JTOPInstallCommand(install): 121 | """Custom installation command for production install.""" 122 | 123 | def run(self): 124 | # Run the custom installer 125 | pypi_installer(install, self, True) 126 | 127 | 128 | class JTOPDevelopCommand(develop): 129 | """Custom installation command for development mode.""" 130 | 131 | def run(self): 132 | # Run the custom installer with linking 133 | pypi_installer(develop, self, False) 134 | 135 | 136 | # Minimal setup() call - most configuration is in pyproject.toml 137 | if __name__ == '__main__': 138 | setup( 139 | # Custom commands for backward compatibility 140 | cmdclass={ 141 | 'develop': JTOPDevelopCommand, 142 | 'install': JTOPInstallCommand, 143 | }, 144 | # Include data files that need special installation 145 | data_files=[('jetson_stats', ['services/jtop.service', 'scripts/jtop_env.sh'])], 146 | ) 147 | -------------------------------------------------------------------------------- /jtop/core/thor_power.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it). 3 | # Copyright (c) 2019-2026 Raffaello Bonghi. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | # Minimal, dependency-free helpers for rail-gating (runtime PM) and devfreq governors (3D-scaling) 19 | from __future__ import annotations 20 | from typing import Dict, List, Optional, Tuple 21 | import glob 22 | import os 23 | import logging 24 | logger = logging.getLogger(__name__) 25 | 26 | 27 | _DEVFREQ_NODES = ("/sys/class/devfreq/gpu-gpc-0", "/sys/class/devfreq/gpu-nvd-0") 28 | 29 | 30 | def _read(path: str) -> Optional[str]: 31 | try: 32 | with open(path, "r", encoding="utf-8", errors="ignore") as f: 33 | return f.read().strip() 34 | except Exception: 35 | return None 36 | 37 | 38 | def _write(path: str, data: str) -> Tuple[bool, Optional[str]]: 39 | try: 40 | with open(path, "w", encoding="utf-8") as f: 41 | f.write(data) 42 | return True, None 43 | except Exception as e: 44 | return False, str(e) 45 | 46 | 47 | def _exists(p: str) -> bool: 48 | return os.path.exists(p) 49 | 50 | # Rail-gating (runtime PM) 51 | 52 | 53 | def _pm_control_path() -> Optional[str]: 54 | # Prefer BDF derived from /proc (robust on Jetson/Thor) 55 | for p in glob.glob("/proc/driver/nvidia/gpus/*/power"): 56 | bdf = os.path.basename(os.path.dirname(p)) 57 | cand = f"/sys/bus/pci/devices/{bdf}/power/control" 58 | if _exists(cand): 59 | return cand 60 | # Fallback 61 | cand = "/sys/bus/pci/devices/0000:01:00.0/power/control" 62 | return cand if _exists(cand) else None 63 | 64 | 65 | def rail_status() -> Dict: 66 | """Return presence, readable status, and control info.""" 67 | power_files = glob.glob("/proc/driver/nvidia/gpus/*/power") 68 | present = bool(power_files) 69 | enabled = None 70 | if present: 71 | txt = _read(power_files[0]) or "" 72 | for line in txt.splitlines(): 73 | if "Rail-Gating" in line: 74 | enabled = ("Enabled" in line) 75 | break 76 | ctrl = _pm_control_path() 77 | value = _read(ctrl) if ctrl else None # "on" or "auto" 78 | return { 79 | "present": present, 80 | "enabled": enabled, # from /proc (read-only text) 81 | "control_path": ctrl, # /sys/bus/pci/devices/.../power/control 82 | "control_value": value, # "on" (kept on) or "auto" (idle gating allowed) 83 | "control_writable": _exists(ctrl) and os.access(ctrl, os.W_OK) if ctrl else False, 84 | } 85 | 86 | 87 | def set_rail(allow_idle: bool) -> Tuple[bool, Optional[str]]: 88 | """allow_idle=True -> 'auto'; False -> 'on'.""" 89 | if not (ctrl := _pm_control_path()): 90 | return False, "GPU runtime PM control node not found" 91 | return _write(ctrl, "auto" if allow_idle else "on") 92 | 93 | 94 | def toggle_rail() -> Tuple[bool, Optional[str]]: 95 | ctrl = _pm_control_path() 96 | if not ctrl: 97 | return False, "GPU runtime PM control node not found" 98 | cur = _read(ctrl) 99 | nxt = "on" if cur == "auto" else "auto" 100 | return _write(ctrl, nxt) 101 | 102 | # Devfreq (3D-scaling) 103 | 104 | 105 | def devfreq_nodes() -> List[str]: 106 | return [p for p in _DEVFREQ_NODES if _exists(p)] 107 | 108 | 109 | def available_governors() -> List[str]: 110 | out: List[str] = [] 111 | for n in devfreq_nodes(): 112 | s = _read(os.path.join(n, "available_governors")) or "" 113 | for g in s.split(): 114 | if g not in out: 115 | out.append(g) 116 | return out 117 | 118 | 119 | def current_governor() -> Optional[str]: 120 | for n in devfreq_nodes(): 121 | if g := _read(os.path.join(n, "governor")): 122 | return g 123 | return None 124 | 125 | 126 | def set_governor(gov: str) -> Tuple[bool, Optional[str]]: 127 | ok_all, last_err = True, None 128 | for n in devfreq_nodes(): 129 | p = os.path.join(n, "governor") 130 | ok, err = _write(p, gov) 131 | if not ok: 132 | ok_all, last_err = False, err 133 | return ok_all, last_err 134 | 135 | 136 | def toggle_governor() -> Tuple[bool, Optional[str]]: 137 | """Prefer explicit flip between performance <-> nvhost_podgov; else cycle whatever exists.""" 138 | cur = current_governor() 139 | avail = available_governors() 140 | if "performance" in avail and "nvhost_podgov" in avail: 141 | target = "performance" if cur != "performance" else "nvhost_podgov" 142 | else: 143 | avail = avail or ["performance", "nvhost_podgov"] 144 | target = avail[(avail.index(cur) + 1) % len(avail)] if cur in avail else avail[0] 145 | return set_governor(target) 146 | 147 | # nvhost_podgov tunables 148 | 149 | 150 | def podgov_path(node: str) -> Optional[str]: 151 | p = os.path.join(node, "nvhost_podgov") 152 | return p if _exists(p) else None 153 | 154 | 155 | def read_podgov(node: str) -> Dict[str, Optional[str]]: 156 | p = podgov_path(node) 157 | params = ["load_max", "load_target", "load_margin", "k", "up_freq_margin", "down_freq_margin"] 158 | return {k: (_read(os.path.join(p, k)) if p else None) for k in params} 159 | 160 | 161 | def write_podgov(node: str, name: str, value: str) -> Tuple[bool, Optional[str]]: 162 | if not (p := podgov_path(node)): 163 | return False, f"nvhost_podgov not present on {node}" 164 | return _write(os.path.join(p, name), value) 165 | 166 | # EOF 167 | --------------------------------------------------------------------------------