# -*- coding: UTF-8 -*-
# This file is part of the jetson_stats package (https://github.com/rbonghi/jetson_stats or http://rnext.it).
# Copyright (c) 2019-2023 Raffaello Bonghi.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
import re
import os
import time
import logging
# Launch command
from datetime import timedelta
from threading import Thread
# Local functions and classes
from .command import Command
from .common import get_uptime
from .exceptions import JtopException
# Create logger
logger = logging.getLogger(__name__)
PATH_JETSON_CLOCKS = ['/usr/bin/jetson_clocks', '/home/nvidia/jetson_clocks.sh']
COMMAND_TIMEOUT = 3.0
CONFIG_DEFAULT_BOOT = False
CONFIG_DEFAULT_DELAY = 60 # In seconds
CONFIG_DEFAULT_L4T_FILE = "l4t_dfs.conf"
# Type Engine
JC_ENGINES = re.compile(r'^(?P[^ ]+) .* MaxFreq=(?P[^ ]+) .*')
def decode_show_message(lines):
status = {}
for line in lines:
# Search configuration CPU config
match = JC_ENGINES.search(line)
if match:
parsed_line = match.groupdict()
# Extract name
name = 'CPU' if 'cpu' in parsed_line['name'] else parsed_line['name']
# Extrac max frequency
freq = int(parsed_line['frq'])
# add engine
status[name] = freq
return status
def jetson_clocks_alive(engines, data):
for engine in engines:
if engine == 'CPU':
# Check minum and max frequency
for cpu in data['cpu']['cpu']:
if not cpu['online']:
continue
cpu_frqs = cpu['freq']
if cpu_frqs['max'] != cpu_frqs['min']:
return False
elif engine == 'GPU':
# Check minum and max frequency
for _, gpu in data['gpu'].items():
gpu_freqs = gpu['freq']
if gpu_freqs['max'] != gpu_freqs['min']:
return False
elif engine == 'EMC':
# Check minum and max frequency
# EMC check only if current frequency is the same of max
emc = data['mem']['EMC']
if 'max' not in emc:
continue
if emc['max'] != emc['cur']:
return False
else:
# Find enging and check frequencies
for _, engines_group in data['engines'].items():
for name, engine_data in engines_group.items():
if engine == name and 'max' in engine_data:
# for all Engines check only if current frequency is the same of max
if engine_data['max'] != engine_data['cur']:
return False
return True
class JetsonClocks(object):
"""
jetson_clocks is a tool provided for all NVIDIA Jetson to maximize all performance, read reference for more information.
With this class you can control when activate, deactivate or enable on boot.
You can use in a simple way like a jtop attribute :py:attr:`~jtop.jtop.jetson_clocks`
.. code-block:: python
with jtop() as jetson:
if jetson.ok():
# Change status jetson_clocks
jetson.jetson_clocks = not jetson.jetson_clocks
# Set on board boot
jetson.jetson_clocks.boot = True
# Read status jetson_clocks service
print(jetson.jetson_clocks.status)
.. admonition:: Reference
#. `jetson_clocks - Jetson TX/Nano `_
#. `jetson_clocks - Jetson Xavier `_
#. `jetson_clocks - Jetson Orin `_
""" # noqa
def __init__(self, controller):
self._controller = controller
self._enable = False
self._thread = ''
self._boot = False
self._config = False
def _update(self, jc_status):
self._enable = jc_status['enable']
self._thread = jc_status['thread']
self._config = jc_status['config']
self._boot = jc_status['boot']
def set_enable(self, enable):
"""
Enable jetson_clocks on your board. This method is equivalent to:
.. code-block:: python
with jtop() as jetson:
if jetson.ok():
# Set a new state for jetson_clocks
jetson.jetson_clocks.set_enable(True)
# same of
jetson.jetson_clocks = True
:param enable: Boolean status for enable and disable
:type enable: bool
:raises ValueError: if enable is not a boolean
:raises JtopException: if jetson_clocks is in uncontrolled status
"""
if not isinstance(enable, bool):
raise ValueError("Use a boolean")
if not self._config and not enable:
raise JtopException("I cannot set jetson_clocks.\nPlease shutdown manually jetson_clocks")
# Check if service is not started otherwise skip
if self._thread in ['booting', 'activating', 'deactivating', 'uncontrolled']:
return
# Set new jetson_clocks configuration
if enable is not self._enable:
self._controller.put({'jc': {'enable': enable}})
def get_enable(self):
"""
Return current jetson_clocks status. This method is equivalent to:
.. code-block:: python
with jtop() as jetson:
if jetson.ok():
# Read jetson_clocks status
print(jetson.jetson_clocks.get_enable())
# same of
print(jetson.jetson_clocks)
:return: Status jetson_clocks
:rtype: bool
"""
return self._enable
def set_boot(self, value):
"""
Enable jetson_clocks on board boot. This method is equivalent to:
.. code-block:: python
with jtop() as jetson:
if jetson.ok():
# Set a new state for jetson_clocks
jetson.jetson_clocks.set_boot(True)
# same of
jetson.jetson_clocks.boot = True
:param value: Boolean status for enable and disable jetson_clocks on boot
:type value: bool
:raises ValueError: if value is not a boolean
"""
if not isinstance(value, bool):
raise ValueError("Use a boolean")
# Don't send a message if value is the same
if value is self._boot:
return
# Set new jetson_clocks configuration
self._controller.put({'jc': {'boot': value}})
def get_boot(self):
"""
Return if jetson_clocks start of board boot. This method is equivalent to:
.. code-block:: python
with jtop() as jetson:
if jetson.ok():
# Read jetson_clocks status
print(jetson.jetson_clocks.get_boot())
# same of
print(jetson.jetson_clocks.boot)
:return: Status jetson_clocks on boot
:rtype: bool
"""
return self._boot
@property
def boot(self):
"""
Same of :py:class:`~set_boot` and :py:class:`~get_boot` you can quickly enable and disable jetson_clocks.
.. code-block:: python
with jtop() as jetson:
if jetson.ok():
# Read jetson_clocks status
print(jetson.jetson_clocks.boot)
# Set a new state for jetson_clocks
jetson.jetson_clocks.boot = True
:return: Status jetson_clocks on boot
:rtype: bool
"""
return self._boot
@boot.setter
def boot(self, value):
self.set_boot(value)
def is_config(self):
"""
Return the jetson_clocks configuration status. If true jtop has stored a configuration file with all data
:return: configuration stored
:rtype: bool
"""
return self._config
def get_status(self):
"""
Return the jetson_clocks service status:
There are **six** options:
============== =============================================
Status Description
============== =============================================
running jetson_clocks is currently running
booting jetson_clocks is booting. This status appears in the first 60s when your board is started
activating jtop is running the service to activating jetson_clocks
deactivating jtop is running the service to deactivating jetson_clocks
uncontrolled jtop is not able to setup jetson_clocks. This message can appears if jetson_clocks was already running before jtop service started
inactive jetson_clocks is inactive
============== =============================================
:return: status jetson_clocks service
:rtype: str
"""
# Check if is alive jetson_clocks
if self._enable:
return 'running'
# Otherwise check status thread jetson_clocks
return self._thread
@property
def status(self):
"""
This property return the same output of :py:class:`~get_status`
:return: status jetson_clocks service
:rtype: str
"""
return self.get_status()
def clear_config(self):
"""
This method clear the jetson_clocks configuration that use jtop.
"""
# Clear jetson_clocks configuration
self._controller.put({'jc': {'clear': True}})
def __nonzero__(self):
return self._enable
def __bool__(self):
return self._enable
def __and__(self, other):
return self._enable & bool(other)
def __or__(self, other):
return self._enable | bool(other)
def __xor__(self, other):
return self._enable ^ bool(other)
def __int__(self):
return int(self._enable)
def __index__(self):
return int(self._enable)
def __eq__(self, other):
if isinstance(other, bool):
return self._enable == other
elif isinstance(other, JetsonClocks):
return self._enable == other._enable
else:
return False
def __repr__(self):
return str(self._enable)
class JetsonClocksService(object):
"""
This controller manage the jetson_clocks service.
"""
def __init__(self, config, fan):
self._data = {}
# Status thread enable/disable
self._thread_status = 'inactive'
# Initialization Jetson Clocks Object thread
self._jetson_clocks_thread = None
# List engines request
self._engines_list = {}
# Error message
self._error = None
# nvpmodel
self._nvpmodel = None
# Load configuration
self._config = config
# other modules
self._fan = fan
# Config file
jetson_clocks_file = config.get('jetson_clocks', {}).get('l4t_file', CONFIG_DEFAULT_L4T_FILE)
self._config_l4t = os.path.join(config.path, jetson_clocks_file)
# Jetson Clocks path
self._jetson_clocks_bin = ''
for path in PATH_JETSON_CLOCKS:
if os.path.exists(path):
self._jetson_clocks_bin = path
logger.info("jetson_clocks found in {cmd}".format(cmd=self._jetson_clocks_bin))
break
if not self._jetson_clocks_bin:
logger.warning("jetson_clocks not available")
def exists(self):
return True if self._jetson_clocks_bin else False
def initialization(self, nvpmodel, data):
self._data = data
self._nvpmodel = nvpmodel
# List of all engines required
self._engines_list = self.show()
# Check if jetson_clocks is alive
is_enabled = self.get_enable()
# Check if exist configuration file
if not os.path.isfile(self._config_l4t):
if is_enabled:
self._thread_status = 'uncontrolled'
logger.warning("I can't store jetson_clocks configuration is already running!")
else:
self.store()
# Check which version is L4T is loaded
# if is before the 28.1 require to launch jetson_clock.sh only 60sec before the boot
# https://devtalk.nvidia.com/default/topic/1027388/jetson-tx2/jetson_clock-sh-1-minute-delay/
# Temporary disabled to find a best way to start this service.
# The service on demand disabled doesn't improve the performance of the start-up
# If jetson_clocks on boot run a thread
config = self._config.get('jetson_clocks', {})
if config.get('boot', CONFIG_DEFAULT_BOOT):
logger.info("Starting jetson_clocks on boot")
# Start thread Service client
self.set_enable(True)
def set_enable(self, enable, reset_nvpmodel=False):
# If there are exception raise
self._error_status()
# Check status jetson_clocks
running_status = self.is_running()
if running_status != 'inactive':
logger.warning("jetson_clocks is {status}".format(status=running_status))
return False
if enable:
# Check if the configuration exist
if not self.is_config():
if not self.store():
return False
# Start thread Service client
self._jetson_clocks_thread = Thread(target=self._th_start, args=(reset_nvpmodel, ))
self._jetson_clocks_thread.start()
else:
# Start thread Service client
self._jetson_clocks_thread = Thread(target=self._th_stop, args=(reset_nvpmodel, ))
self._jetson_clocks_thread.start()
return True
def get_enable(self):
return jetson_clocks_alive(self._engines_list, self._data)
def set_boot(self, value):
# Extract configuration
config = self._config.get('jetson_clocks', {})
# Add new value
config['boot'] = value
# Set new jetson_clocks configuration
self._config.set('jetson_clocks', config)
def get_boot(self):
config = self._config.get('jetson_clocks', {})
return config.get('boot', CONFIG_DEFAULT_BOOT)
def is_config(self):
return os.path.isfile(self._config_l4t)
def is_running(self):
if self._jetson_clocks_thread is not None:
if not self._jetson_clocks_thread.is_alive():
self._thread_status = 'inactive'
return self._thread_status
def _fix_fan(self, status_fan):
# Fix fan speed
for name in status_fan:
profile = status_fan[name]['profile']
self._fan.set_profile(name, profile)
logger.debug("Fan \"{name}\" profile: {profile}".format(name=name, profile=profile))
if profile == 'manual':
for idx, speed in enumerate(status_fan[name]['speed']):
self._fan.set_speed(name, speed, idx)
logger.debug("fan \"{name}\"[{idx}]: {speed}".format(name=name, idx=idx, speed=speed))
def _th_start(self, reset_nvpmodel):
self._thread_status = 'activating'
# Check which version is L4T is loaded
# if is before the 28.1 require to launch jetson_clock.sh only 60sec before the boot
# https://devtalk.nvidia.com/default/topic/1027388/jetson-tx2/jetson_clock-sh-1-minute-delay/
# Temporary disabled to find a best way to start this service.
# The service on demand disabled doesn't improve the performance of the start-up
# Measure remaining time from boot
boot_time = timedelta(seconds=self._config.get('wait', CONFIG_DEFAULT_DELAY))
up_time = timedelta(seconds=get_uptime())
# If need time make a sleep
if up_time < boot_time:
self._thread_status = 'booting'
delta = (boot_time - up_time).total_seconds()
logger.info("Starting jetson_clocks in: {delta}s".format(delta=delta))
# Status jetson_clocks
logger.info("Start jetson_clocks with {status}".format(status=self._thread_status))
if up_time < boot_time:
time.sleep(delta)
# Read fan speed
status_fan = self._fan.get_status()
if reset_nvpmodel and self._nvpmodel.exists():
nvpmodel_id = self._nvpmodel.get_nvpmodel_id()
# Start jetson_clocks
Command.run_command([self._jetson_clocks_bin], repeat=5, timeout=COMMAND_TIMEOUT)
# Fix fan speed
self._fix_fan(status_fan)
# Reset nvpmodel
if reset_nvpmodel and self._nvpmodel.exists():
self._nvpmodel.set_nvpmodel_id(nvpmodel_id, False)
logger.info("jetson_clocks started")
def _th_stop(self, reset_nvpmodel):
self._thread_status = 'deactivating'
logger.debug("Start jetson_clocks with {status}".format(status=self._thread_status))
# Read fan speed
status_fan = self._fan.get_status()
if reset_nvpmodel and self._nvpmodel.exists():
nvpmodel_id = self._nvpmodel.get_nvpmodel_id()
# Run jetson_clocks
Command.run_command([self._jetson_clocks_bin, '--restore', self._config_l4t], repeat=5, timeout=COMMAND_TIMEOUT)
# Fix fan speed
self._fix_fan(status_fan)
# Reset nvpmodel
if reset_nvpmodel and self._nvpmodel.exists():
self._nvpmodel.set_nvpmodel_id(nvpmodel_id, False)
logger.info("jetson_clocks stopped")
def _error_status(self):
# Catch exception if exist
if self._error:
# Extract exception and raise
ex_type, ex_value, tb_str = self._error
ex_value.__traceback__ = tb_str
raise ex_value
def close(self):
# Switch off thread jetson clocks
self._running = False
# If jetson_clocks doesn't exist skip
if not self.exists():
return
# If there are no thread running skip
if self._jetson_clocks_thread is None:
return
if self._jetson_clocks_thread.is_alive():
logger.warning("Wait switch off jetson_clocks thread")
self._jetson_clocks_thread.join(COMMAND_TIMEOUT)
def show(self):
cmd = Command([self._jetson_clocks_bin, '--show'])
list_engines = {}
try:
lines = cmd(timeout=COMMAND_TIMEOUT)
list_engines = decode_show_message(lines)
except Command.TimeoutException as e:
logger.warning("Timeout {}".format(e))
return list_engines
def store(self):
if self.is_config():
logger.error("Configuration already stored in {file}".format(file=self._config_l4t))
return False
if self.get_enable():
self._thread_status = 'uncontrolled'
logger.error("jetson_clocks is running. I cannot store configuration")
return False
# Store configuration jetson_clocks
cmd = Command([self._jetson_clocks_bin, '--store', self._config_l4t])
try:
message = cmd(timeout=COMMAND_TIMEOUT)
except Command.CommandException:
return False
logger.info("Store jetson_clocks configuration in {file}".format(file=self._config_l4t))
# Extract result
return True if message else False
def clear(self):
if os.path.isfile(self._config_l4t):
logger.info("Clear jetson_clocks config in {path}".format(path=self._config_l4t))
# Remove configuration file
os.remove(self._config_l4t)
return True
return False
def get_status(self, data):
self._data = data
return {
'enable': self.get_enable(),
'thread': self.is_running(),
'config': self.is_config(),
'boot': self.get_boot(),
}
# EOF