--------------------------------------------------------------------------------
/.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 |
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 |
--------------------------------------------------------------------------------