[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>
이 커밋은 다음에 포함됨:
커밋한 사람
GitHub
부모
00a893d299
커밋
f8e4771363
@@ -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)
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
새 이슈에서 참조
사용자 차단