[SWDEV-538483] Add NPM API's and CLI (#817)

* Added Python & C API's for new node devices. Currently these are functional for node 0 only.
 - amdsmi_get_node_handle
 - amdsmi_get_npm_info
* Added `amd-smi node` CLI for Node Power Management

---------

Signed-off-by: Bindhiya Kanangot Balakrishnan <Bindhiya.KanangotBalakrishnan@amd.com>
Signed-off-by: Maisam Arif <Maisam.Arif@amd.com>
Co-authored-by: Maisam Arif <Maisam.Arif@amd.com>
이 커밋은 다음에 포함됨:
Kanangot Balakrishnan, Bindhiya
2025-11-13 21:51:31 -06:00
커밋한 사람 GitHub
부모 00a893d299
커밋 f8e4771363
13개의 변경된 파일과 616개의 추가작업 그리고 8개의 파일을 삭제
+1
파일 보기
@@ -159,6 +159,7 @@ if __name__ == "__main__":
amd_smi_commands.xgmi,
amd_smi_commands.partition,
amd_smi_commands.ras,
amd_smi_commands.node,
amd_smi_commands.default,
sys_argv=sys.argv,
helpers=amd_smi_helpers)
+80
파일 보기
@@ -53,6 +53,7 @@ class AMDSMICommands():
self.device_handles = []
self.cpu_handles = []
self.core_handles = []
self.node_handle = None
self.stop = ''
self.group_check_printed = False
@@ -75,6 +76,20 @@ class AMDSMICommands():
logging.error('Unable to detect any GPU devices, check amdgpu version and module status (sudo modprobe amdgpu)')
exit_flag = True
# Resolve the node handle.
for dev in self.device_handles:
try:
nh = amdsmi_interface.amdsmi_get_node_handle(dev)
if nh is not None:
self.node_handle = nh
continue
except amdsmi_exception.AmdSmiLibraryException as e:
if e.err_code in (amdsmi_interface.amdsmi_wrapper.AMDSMI_STATUS_NOT_SUPPORTED,
amdsmi_interface.amdsmi_wrapper.AMDSMI_STATUS_INVAL):
logging.debug("Unable to get node handle: %s", e.get_error_info())
else:
raise e
if self.helpers.is_amd_hsmp_initialized():
try:
self.cpu_handles = amdsmi_interface.amdsmi_get_cpusocket_handles()
@@ -7231,6 +7246,71 @@ class AMDSMICommands():
time.sleep(1)
def node(self, args, multiple_devices=False, nodes=None, power_management=None):
"""List node informations
Args:
args (Namespace): Namespace containing the parsed CLI args
multiple_devices (bool, optional): True if checking for multiple devices.
Defaults to False.
Returns:
None: Print output via AMDSMILogger to destination
"""
# Set args.* to passed in arguments
if nodes:
args.nodes = nodes
if power_management:
args.power_management = power_management
if getattr(args, 'nodes', None) is None:
args.nodes = self.node_handle
if not self.group_check_printed:
self.helpers.check_required_groups(check_render=True, check_video=False)
self.group_check_printed = True
# Get NPM info
if args.nodes is not None:
try:
npm_info = amdsmi_interface.amdsmi_get_npm_info(args.nodes)
except amdsmi_exception.AmdSmiLibraryException as e:
logging.debug("amdsmi_get_npm_info failed: %s", e.get_error_info())
npm_info = "N/A"
else:
logging.debug('No node handle available to query NPM info')
npm_info = "N/A"
# Log outputs
npm_dict = {"limit": "N/A", "status": "N/A"}
power_unit ="W"
limit = "N/A"
if isinstance(npm_info, dict):
limit = npm_info.get('limit', "N/A")
status = npm_info.get('status', npm_info.get('current', "N/A"))
if limit !="N/A":
npm_dict['limit'] = limit
status = "DISABLED" if status == amdsmi_interface.amdsmi_wrapper.AMDSMI_NPM_STATUS_DISABLED else "ENABLED"
npm_dict.update({"status": status})
if self.logger.is_human_readable_format() and self.logger.destination == 'stdout':
print(f"NODE:\n POWER_MANAGEMENT:\n LIMIT: {npm_dict.get('limit', 'N/A')} {power_unit}\n STATUS: {npm_dict.get('status', 'N/A')}")
else:
if self.logger.is_csv_format():
csv_dict = {}
csv_dict['limit'] = npm_dict.get('limit', "N/A")
csv_dict['status'] = npm_dict.get('status', "N/A")
self.logger.output = csv_dict
else:
# For JSON and human readable format with file output
npm_dict["limit"] = self.helpers.unit_format(self.logger, limit, power_unit)
self.logger.output = {'node': {'power_management': npm_dict}}
if multiple_devices:
self.logger.store_multiple_device_output()
return
self.logger.print_output()
def default(self, args):
"""Display the default amdsmi view when no args are given."""
+35
파일 보기
@@ -624,6 +624,41 @@ class AMDSMIHelpers():
return False, args.core
# The below handle_nodes function is currently unused as only node 0 is supported.
# Marked as a private function until it is needed in the future.
def _handle_nodes(self, args, logger, subcommand):
"""This function will run execute the subcommands based on the number
of nodes passed in via args.
params:
args - argparser args to pass to subcommand
current_platform_args (list) - GPU supported platform arguments
current_platform_values (list) - GPU supported values for the arguments
logger (AMDSMILogger) - Logger to print out output
subcommand (AMDSMICommands) - Function that can handle multiple gpus
return:
tuple(bool, device_handle) :
bool - True if executed subcommand for multiple devices
device_handle - Return the device_handle if the list of devices is a length of 1
(handled_multiple_nodes, device_handle)
"""
if isinstance(args.node, list):
if len(args.node) > 1:
for node_handle in args.node:
# Handle multiple_devices to print all output at once
subcommand(args, multiple_devices=True, node=node_handle)
logger.print_output(multiple_device_enabled=True)
return True, args.node
elif len(args.node) == 1:
args.node = args.node[0]
return False, args.node
else:
logging.debug("args.node has an empty list")
else:
return False, args.node
def handle_watch(self, args, subcommand, logger):
"""This function will run the subcommand multiple times based
on the passed watch, watch_time, and iterations passed in.
+33 -2
파일 보기
@@ -70,7 +70,8 @@ class AMDSMIParser(argparse.ArgumentParser):
"""
def __init__(self, version, list, static, firmware, bad_pages, metric,
process, profile, event, topology, set_value, reset, monitor,
xgmi, partition, ras, default, sys_argv=None, helpers=None):
xgmi, partition, ras, node, default, sys_argv=None,
helpers=None):
# Helper variables
if helpers is None:
@@ -122,7 +123,8 @@ class AMDSMIParser(argparse.ArgumentParser):
# Store possible subcommands & aliases for later errors
self.possible_commands = ['version', 'list', 'static', 'firmware', 'ucode', 'bad-pages',
'metric', 'process', 'profile', 'event', 'topology', 'set',
'reset', 'monitor', 'dmon', 'xgmi', 'partition', 'ras', 'default']
'reset', 'monitor', 'dmon', 'xgmi', 'partition', 'ras',
'node', 'default']
# Add all subparsers
if sys_argv is not None:
@@ -143,6 +145,7 @@ class AMDSMIParser(argparse.ArgumentParser):
self._add_xgmi_parser(self.subparsers, xgmi)
self._add_partition_parser(self.subparsers, partition)
self._add_ras_parser(self.subparsers, ras)
self._add_node_parser(self.subparsers, node)
elif any(arg in sys_argv for arg in ['version']):
self._add_version_parser(self.subparsers, version)
elif any(arg in sys_argv for arg in ['list']):
@@ -175,6 +178,8 @@ class AMDSMIParser(argparse.ArgumentParser):
self._add_partition_parser(self.subparsers, partition)
elif any(arg in sys_argv for arg in ['ras']):
self._add_ras_parser(self.subparsers, ras)
elif any(arg in sys_argv for arg in ['node']):
self._add_node_parser(self.subparsers, node)
else:
# If no subcommand is given, add the default parser
self._add_default_parser(self.subparsers, default)
@@ -1564,6 +1569,32 @@ class AMDSMIParser(argparse.ArgumentParser):
self._add_command_modifiers(ras_parser)
def _add_node_parser(self, subparsers: argparse._SubParsersAction, func):
if self.helpers.is_virtual_os():
# This subparser is only available to Guest and Hypervisor systems
return
# Subparser help text
node_help = "Gets power information for the node"
node_subcommand_help = f"{self.description}\n\nReturns information for node 0 on the system.\
\nIf no node argument is provided, all node information will be displayed."
node_optionals_title = "Node arguments"
# Help text for Node arguments
power_management_help = "Displays power management information"
node_parser = subparsers.add_parser("node", help=node_help, description=node_subcommand_help)
node_parser._optionals.title = node_optionals_title
node_parser.formatter_class = lambda prog: AMDSMISubparserHelpFormatter(prog)
node_parser.set_defaults(func=func)
# Optional Args
node_parser.add_argument('-p', '--power-management', action='store_true', required=False, help=power_management_help)
# Add Universal Arguments
self._add_command_modifiers(node_parser)
def error(self, message):
outputformat = self.helpers.get_output_format()